Skip to main content

Module persistent_resume

Module persistent_resume 

Source
Expand description

One spawn attempt’s stale---resume retry decision, plus any terminal error-result line observed for it, as a single explicit state machine.

This replaces three fields that used to live directly on persistent::PersistentInner — pending_resume_retry, confirmed_stale_resume_retry, and pending_error_result_line — which were mutated directly by four independently-scheduled tokio tasks (stdin writer, stdout reader, stderr reader, process-waiter) racing on a shared mutex. That shape caused the exact bug this module exists to prevent, twice: issue #2368’s original report, and a live-reproduced recurrence (agent “Marks”, 2026-07-30) where the stderr reader’s poison_resume promotion landed ~500ms before the stdout reader finished draining lines, and a check of only one of the two fields missed the stash entirely.

update() is a pure (state, event) -> (state, effects) function — every transition is exhaustively unit-testable without a real child process, a real tokio task, or any timing assumptions. Callers (the I/O tasks persistent/spawn.rs starts: persistent/stdout_reader.rs and persistent/process_waiter.rs) are responsible for actually executing the returned ResumeEffects (persisting/dropping/flushing a line, firing the retry, publishing terminal status) — this module only decides, never performs I/O.

Every event that could affect in-flight state carries the exact generation it was observed for. update() ignores an event whose generation doesn’t match the state’s currently-tracked generation, so a stale event from an already-resolved generation can never corrupt a later, unrelated generation’s state — generation numbers are never reused (see spawn_generation’s doc comment in persistent/mod.rs).

Structs§

QueuedRetryEntry 🔒
One redeliverable stdin message in a RetryPayload batch: the formatted JSON plus its queue identity.
RetryPayload 🔒
The spawn config + the growing batch of stdin messages to redeliver if this generation’s --resume turns out to be stale. Named separately from the state enum so MessageAppendedToRetryBatch can grow it in place without reconstructing the whole variant.

Enums§

ResumeEffect 🔒
What the caller must actually DO in response to an event — kept separate from ResumeState so update() stays a pure function with no I/O, fully exercisable in a unit test.
ResumeEvent 🔒
Events the stdin writer / stdout reader / stderr reader / process- waiter sensor tasks report. Each carries the exact spawn generation it was observed for.
ResumeState 🔒
One spawn attempt’s resume/error-line lifecycle.
SessionOutcome 🔒
A resume attempt’s outcome, once definitively known. See ResumeEffect::EmitSessionOutcome’s doc comment for how this is used.

Functions§

capture_resolution_effects 🔒
Effects of a SessionCaptured that resolves tracking (a different sid, or a confirmed success): flush any held error line, then emit the session outcome.
resolve_superseded_generation 🔒
Resolves a PRIOR generation’s still-unresolved tracking (AwaitingOutcome/ConfirmedRetry) that a fresh spawn is about to supersede.
update 🔒
The pure state transition. See the module doc comment for why this shape exists and what it replaces.