Skip to content

fix(io): latch the live join shape on rejection instead of assuming it (#331 follow-up) - #336

Merged
superuser404notfound merged 1 commit into
mainfrom
fix/331-live-offset-latch
Aug 8, 2026
Merged

fix(io): latch the live join shape on rejection instead of assuming it (#331 follow-up)#336
superuser404notfound merged 1 commit into
mainfrom
fix/331-live-offset-latch

Conversation

@superuser404notfound

Copy link
Copy Markdown
Owner

Summary

Follow-up to #332 (merged). The live request shape is now decided by the origin's answer instead of by the isLive flag.

#332 made every live persistent request bytes=0-. That is right for a ring-buffer IPTV panel, which has no byte addresses and rejects a frontier with 416. It is wrong for the other live shape: a source that IS a growing file (a Jellyfin live stream file, a misdeclared VOD) answers the frontier correctly and resumes exactly where delivery stopped, and asking that one for byte zero re-delivers its whole buffer on top of the window. Both shapes reach AVIOReader through LoadOptions.isLive, which is a host declaration, so the flag cannot tell them apart.

What changed

  • The frontier is kept until an origin answers 416 to a nonzero live offset. That rejection latches the join shape (bytes=0-) for the rest of the reader's life. A ring-buffer panel pays exactly one rejected request per reader; a byte-addressable live source pays nothing.
  • The latch is named once in the log, so a field capture separates "this origin has no byte addresses" from "the reconnect is broken".
  • Nonzero offsets only: a 416 at offset zero is a different fault and must not silently change the request shape.

Test plan

  • swift test: 1623 tests green.
  • LiveWindowBackpressureTests covers both origin shapes. Witness-checked in both directions by disabling the guard: never latching starves after one 4 MB burst (13 rejected requests at the same offset, reproducing the field trace); latching unconditionally makes the range-honouring origin's refill restart at byte zero.

Reported in #331 (kept open until the reporter retests on device)

🤖 Generated with Claude Code

https://claude.ai/code/session_01HX5zV3Fcf7Nzq4DYGdXvQE

#331 follow-up)

A live reconnect asked `bytes=<frontier>-` and starved against panels that answer
416 to any offset they cannot satisfy. #332 made every live request `bytes=0-`,
which fixes those panels but is wrong for the other live shape: a source that IS
a growing file (a Jellyfin live stream file, a misdeclared VOD) answers the
frontier correctly and resumes there, and asking it for byte zero re-delivers its
whole buffer on top of the window.

The rejection is the signal. Keep the frontier until an origin answers 416 to a
nonzero live offset, latch the join shape from that point, and name the latch in
the log so a field capture separates "this origin has no byte addresses" from "the
reconnect is broken". A ring-buffer panel pays exactly one rejected request per
reader; a byte-addressable live source pays nothing and keeps resuming.

Both directions witnessed by disabling the guard: never latching starves after one
4 MB burst (13 rejected requests at the same offset, the field trace), latching
unconditionally restarts a range-honouring live origin at byte zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HX5zV3Fcf7Nzq4DYGdXvQE
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