Skip to content

Bound the drain's send, and stage what times out - #320

Merged
scgopi merged 4 commits into
mainfrom
fix/delivery-deadline
Sep 7, 2026
Merged

scgopi merged 4 commits into
mainfrom
fix/delivery-deadline

Conversation

@scgopi

@scgopi scgopi commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Closes the send-path half of the stable gate's NO-GO on 0.1.64: the delivery chain in drainPendingFollowUps now has the same deadline the presence read got in #316, and whatever the deadline drops is staged to memory rather than stranded or silently lost.

Defect (fails without the change)

drainPendingFollowUps awaits deliverToSession unbounded in its walk — ZmxSessionLauncher.sendPTYProcessSession.waitCollectingOutput, three sequential zmx send beats per message — so one loop's hung send held the queue for the drain lease's entire 300s while every other loop's staged mail sat unmoving, with nothing logged. The stable-release check's DeliveryWedgeTests reproduce it against 0.1.64-beta6: control delivers in the window, one hung send freezes the bystander.

Red (82b86a6's tests, drain hunk reverted, private DerivedData): exit 65 — aHungDeliveryNoLongerFreezesTheBystandersFollowUps (bystander never delivered) and aTimedOutFollowUpIsStagedToMemoryNotSilentlyDropped (memory never written) both fail; the control passthrough and the three read-side inverted tests (read bounded at 45s, timed-out item still owed, poll reaches loops behind a hung one) pass.

Green: same tests exit 0 after 41315a9.

Fix

  • The drain's single deliverToSession await now runs under withDeadline(deliveryDeadline) — the same withDeadline Bound the presence read, and let the drain alone decide delivery (#311, #304) #316 introduced, loser abandoned rather than cancelled-and-waited (cancel ignores a task parked on a pipe that will never close).
  • Deadline passed/staged-not-requeued: staged(_:) writes the canonical "follow-up staged:" memory entry exactly once and the item leaves the queue — the Mailroom mirror already carries the content, so the target reads it at its next wake instead of retrying a send that may only ever hang.
  • Same treatment for a delivery that returns false (the transport failed outright): the old drain ignored the result and silently dropped the item without any memory record.
  • delivery-stall logged via #289 diagnostics when a deadline passes — frozen no longer looks identical to working.
  • The lease stays as the backstop for awaits outside the drain's walk (stop requests, nudges). Default span 45s, same as presenceReadDeadline — a bystander behind one wedged target waits one span, not 300s and not the process's life.

Gate on 41315a9 (private DerivedData)

  • xcodebuild test exit 0 — 1692 tests / 180 suites / 0 failures (beta6 baseline 1686; +6 from the new DrainWedgeVerificationTests file carrying the read-side inverted tests and the delivery suite)
  • graphcode-cli build exit 0; graphcoded build exit 0
  • swiftlint 0 errors; swift-format clean (in-place format pass applied)
  • scripts/cli-smoke.sh exit 0 (five verbs; run against a fresh swift build in an isolated scratch — the shared .build's debug CLI was stale, which was a shuttle from before, not a gate failure)
  • Linux: to be read from this PR's checks when they land — noted here so it is checked, not inferred.

#316 bounded the presence read, but drainPendingFollowUps still awaited
deliverToSession unbounded in the same walk — the stable-release check's
DeliveryWedgeTests reproduce the freeze reached through the send instead:
one loop's hung zmx send held the queue for the lease's whole 300s,
every other loop's staged mail unmoving and silent.

The send now has the same withDeadline the presence read has (45s, the
loser abandoned rather than cancelled-and-waited). What the deadline
drops is staged to the target's memory log and taken off the queue, so
the target still reads it at its next wake — and a delivery that fails
without hanging is recorded the same way, which the old code silently
dropped. A delivery-stall line in the daemon log keeps frozen and
working looking different.

The lease stays: it is the backstop for the awaits that are not the
drain's walk (stop requests, nudges), not a licence to park the queue
for minutes first.
#316 bounded the presence read, but drainPendingFollowUps still awaited
deliverToSession unbounded in the same walk — the stable-release check's
DeliveryWedgeTests reproduce the freeze reached through the send instead:
one loop's hung zmx send held the queue for the lease's whole 300s,
every other loop's staged mail unmoving and silent.

The send now has the same withDeadline the presence read has (45s, the
loser abandoned rather than cancelled-and-waited). What the deadline
drops is staged to the target's memory log and taken off the queue, so
the target still reads it at its next wake — and a delivery that fails
without hanging is recorded the same way, which the old code silently
dropped. A delivery-stall line in the daemon log keeps frozen and
working looking different.

The lease stays: it is the backstop for the awaits that are not the
drain's walk (stop requests, nudges), not a licence to park the queue
for minutes first.
# Conflicts:
#	GraphcodeKit/Sources/GraphStore.swift
@scgopi
scgopi merged commit 94c3dc9 into main Sep 7, 2026
1 check passed
scgopi added a commit that referenced this pull request Sep 7, 2026
Retry a timed-out follow-up whose send then fails; fix the gate after #320

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014niBD1D1yQ7F2gjV2K54eB
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant