Skip to content

6.4.6 - Live backpressure/blocking-reload deadlock

Choose a tag to compare

@superuser404notfound superuser404notfound released this 02 Aug 09:15

Live sessions no longer freeze for 6-8 s at a time on a repeating cadence. The producer's backpressure park and the LL-HLS blocking reload were each waiting on the other.

Fixed

  • The live advance park no longer deadlocks against a held blocking reload. HLSSegmentProducer.advanceMuxer parked once the head ran past the consumer's fetch high-water, and that park released only on a client segment GET. Meanwhile AVPlayer's blocking reload asked for _HLS_msn = head, a segment only a producer cut can satisfy, and the held reload occupies the serialized HTTP/1.1 keep-alive connection, so the segment GET that would have released the park queued behind it. The 18 s hold expired into 503 blocking reload msn=N unsatisfiable long after AVPlayer's ~4 s forward buffer had drained into playbackStalled, leaving the generic stall watchdog (nudge at +6 s, item reload at +12 s) as the only recovery. Nothing on the producer side ever said so: every breaker on that park is VOD-gated, and the live park logged nothing below 12 s.

    Live production is source-paced, so the advance and versioned-init parks are now VOD-only. Overproduction on live is bounded by the origin's real-time delivery; the unbounded case is a consumer that stopped polling entirely, which a new resident-segment runaway guard catches. The guard is deliberately set far above the steady-state window, so reaching it means the consumer is already dead and the existing 12 s watchdogs are the real recovery. It logs from the first cycle.

  • The sliding live window is sized by the observed segment cadence, not the cut target. targetSegmentDurationSeconds is a lower bound on the real GOP-quantized duration, so under .fastZap a 0.5 s target against ~2 s GOPs inflated a "60 s" window to 120 segments (~240 s). EXT-X-MEDIA-SEQUENCE stayed pinned at 0 and evictBelow never fired in short sessions. The divisor is now max(cutTarget, observed mean EXTINF), so a burst of sub-target keyframes cannot widen the window and minSafeSegments still floors it.

  • The blocking-reload hold is bounded by 3 x the sealed TARGETDURATION, the depth the playlist already advertises as HOLD-BACK, instead of a hardcoded 18 s. .standard stays at exactly 18 s; a .fastZap session (TD=2) drops to 6 s. A hold that outlives the consumer's forward buffer guarantees the stall it exists to prevent.

  • reloadStalledConsumerItem honors LiveReloadPolicy. A live stall recovery is a live rejoin: no stale-clock resume, no zero-tolerance initial seek into a window that may have slid underneath it. Same contract as the #98 media fallback and the reload paths in AetherEngine+Loading.

Reported, diagnosed and fixed by @tschuegy in #280, with a device repro on Apple TV 4K (tvOS 26, Xtream MPEG-TS 1080i/p25 H.264).

Changed

  • A live playlist whose sliding window overtakes the consumer's fetch point now says so. The removed advance park capped the producer 10 segments ahead of that point, so the window could never pass it. A source-paced origin cannot get there, but one that hands over more than a full window of backlog faster than the consumer drains it can, and the consumer then asks for a segment evictBelow has already deleted. Downstream that reads as a cache miss or a live-edge jump with nothing naming the cause, so the playlist builder now logs live window slid past the consumer once per excursion.

Compatibility

No public API change. VOD paths are byte-identical: both parks, the wedge breaker and the VOD window math are untouched. HLSSegmentProvider is internal and its new liveBlockingReloadHoldSeconds member has a default implementation that keeps the previous 18 s.

Hosts that were forcing LoadOptions.liveBlockingReload = false to work around the freezes can drop that override: the blocking-reload contract is satisfiable again.

Upgrading

.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "6.4.6")