Skip to content

fix(video): recover from a VOD producer that dies mid-session at the tail (#169)#198

Merged
superuser404notfound merged 1 commit into
mainfrom
fix/ae169-dead-producer-tail-wedge
Jul 22, 2026
Merged

fix(video): recover from a VOD producer that dies mid-session at the tail (#169)#198
superuser404notfound merged 1 commit into
mainfrom
fix/ae169-dead-producer-tail-wedge

Conversation

@superuser404notfound

Copy link
Copy Markdown
Owner

Context

Round-2 reopen of #169 by rrgomes with a decisive trace: the earlier "fixed on 5.16.2" verdict was their host-side near-end workaround firing, not the engine. With the workaround removed, the tail failure is unchanged on 5.18.4, and it is a different mechanism than the 5.16.2 symptoms: the producer provably stops mid-session before writing the final segment (seg719 never produced; avioFetchedMB/packetsWritten frozen across four 30 s memprobes), and the request for seg719 re-arms a 30 s backpressure wait forever (cache miss after 30005ms (cache=46 restarted=false) x11) until AVPlayer dies with -12889.

Root cause (three defects lined up)

  1. No recovery arm for a mid-session VOD readError exit. handlePumpFinished only surfaced the nothing-ever-produced case as fatal (Unknown-length HTTP MP4 enters streaming mode and returns ready with zero packets; the same URL plays in VLC #126) and assumed "the scrub/wedge arms cover recovery" for the rest. Both arms are unreachable here: the Presented frame drifts ~6 s ahead of currentTime/sourceTime after a rapid bidirectional seek burst (loopback-HLS native path) — frameAhead/producerShift/hostShift all report 0 #65 wedge detector runs inside the pump and died with it, and the provider's restart escalation (2) never fires.
  2. Forward-wait liveness gap (the mechanism rrgomes identified, confirmed verbatim): in the r.1 < index <= r.1 + forwardWaitWindow branch, needsRestart = index > activeMarchFront + forwardWaitWindow answers "will this arrive?" by index distance alone. A dead producer freezes the front just below the request, so the same 30 s wait re-arms indefinitely with no escalation. The final segment is maximally exposed: there is nothing beyond it for the march to advance into.
  3. producerCovers veto. Even an escalated restart would not have fired: the restart loop's producerCovers guard reads the dead producer's still-installed base, which covers the requested index.

Fix (defense in depth, VOD only)

Live sessions are untouched: they keep their pump watchdogs (#167/#177) and reopen machinery, and the escalation is gated !isLive.

Retest signatures (for the reporter)

  • Pump exit case: #169 VOD pump died mid-session (readError N); rebuilding producer on a fresh demuxer at ... followed by seg719 producing and serving.
  • Silent-stop case: seg719: #169 forward-wait march dead (front=718 frozen across a full wait); escalating to restart.

Verification

  • New Issue169DeadProducerEscalationTests (7 tests): immediate restart on finished pump (including the producerCovers bypass), second-miss escalation on a frozen front, patience preserved while the front advances, live never escalates, pure-decision matrices for forwardWaitMarchDead and shouldReviveVODAfterReadError.
  • Full suite 965/965 green; swift build -Xswiftc -strict-concurrency=complete clean; tvOS Simulator build green.

Refs #169 (leaving the issue to the reporter's device retest; not auto-closing).

🤖 Generated with Claude Code

https://claude.ai/code/session_01NAPCg4tCDdSqkK6tPkNptw

…tail (#169)

rrgomes' round-2 trace (4K DV P8.1 MKV over LAN, 5.18.4): the producer died
mid-session before writing the final segment (source read error after
reconnect churn; memprobes show avioFetchedMB/packetsWritten frozen across
the whole wedge), and the request for seg719 parked in the forward-wait
branch forever: cache miss after 30005ms (cache=46 restarted=false) x11
into -12889. Their earlier "fixed on 5.16.2" was their host-side near-end
workaround firing, not the engine.

Three defects lined up:

1. handlePumpFinished had NO recovery arm for a VOD readError exit that had
   already produced media. #126 only surfaced the nothing-ever-produced case
   as fatal and assumed the scrub/wedge arms covered the rest; the wedge
   detector died with the pump and the provider escalation (2) never fired.
2. VideoSegmentProvider's forward-wait branch judged "will this arrive?" by
   index distance alone (index > activeMarchFront + forwardWaitWindow). A
   dead producer freezes the front just below the request, so the 30 s
   backpressure wait re-armed indefinitely with no escalation.
3. Even an escalated restart would have been vetoed: the restart loop's
   producerCovers guard reads the dead producer's still-installed base.

Fixes, defense in depth:

- Event-driven arm: a mid-session VOD readError exit now gets a bounded
  revive (own gate, 2 attempts) mirroring the #99 muxerFailed arm, aimed at
  the pending seek target or AVPlayer's real position. The demuxer whose
  read just threw is marked suspect-dead, so performRestart replaces it via
  the #79 fresh-demuxer path instead of seeking the failed connection.
- Provider-side liveness: a FINISHED pump skips the forward wait entirely
  and restarts immediately. A pump that never finishes (blocked in a read)
  escalates when a full backpressure wait elapsed with zero march-front
  progress for the same index; an advancing front keeps the #141/#93
  patience. Both bypass the producerCovers veto (marchProvenDead).

VOD only; live keeps its pump watchdogs and reopen machinery. Suite
965/965, strict-concurrency build and tvOS-Sim build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAPCg4tCDdSqkK6tPkNptw
@superuser404notfound
superuser404notfound merged commit 1ba1b8c into main Jul 22, 2026
3 checks passed
@superuser404notfound
superuser404notfound deleted the fix/ae169-dead-producer-tail-wedge branch July 22, 2026 17:28
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