Skip to content

fix(io): stop re-fetching bytes the open already has - #285

Merged
superuser404notfound merged 3 commits into
mainfrom
fix/avio-window-retention
Aug 2, 2026
Merged

fix(io): stop re-fetching bytes the open already has#285
superuser404notfound merged 3 commits into
mainfrom
fix/avio-window-retention

Conversation

@superuser404notfound

Copy link
Copy Markdown
Owner

Follow-up to #281, from measuring rather than reasoning. aetherctl against a Range-logging origin, four MP4 layouts, all sized above the 32 MB range so their tails lie outside the first window.

Three defects, all the same shape

A range that finished delivering was fetched again. A completed range clears activeTask exactly as a dropped one does, and the no-connection branch reconnected at the READ position regardless, which resets winStart and drops everything still resident. The parse pass is precisely the consumer this bites: it reads one 256 KB AVIO buffer at a time while the transfer runs at line rate. A 764450 B trailing moov cost three connections and 1506918 delivered bytes, 1.97x its own size, each reconnect landing exactly one AVIO buffer further on. The branch already excluded the hard-cap end for this reason; the reasoning was never extended to a range that simply finished. Serving what is in hand first also revives the #220 frontier refill, which could not run while this branch preempted it on every completed range.

A read at EOF opened a connection for it. The EOF decision sat below that reconnect, so a position at exactly fileSize issued bytes=<fileSize>- and took an empty 206, whose reconnect reset winStart past the last byte and dropped the window the parse was still reading.

The parked window was cut from the wrong place. #281 parks at seek time from winStart, on the reasoning that the demuxer returns to the window's start. It returns to the FILE's start: landings of 48, 1161, 5752 here and 265159 in the field trace on #281. Those coincide only when the parse seeks away before reading anything. A fragmented MP4 reads 33 MB first, so the parked copy begins megabytes past everything the return asks for and covered nothing. The head is now collected as the data connection delivers it, which is the only point at which it can be: trimWindowLocked drops it as the parse moves forward.

Measured

Request counts, loopback:

layout before after
moov at end, 764450 B 5 3
fragmented, mfra at end 5 3
moov at end, 23337 B 2 2
faststart 2 2

Wall clock, same origin with 300 ms of latency per request (loopback cannot show an overlap):

layout before after
fragmented, mfra at end 1732 ms / 4 req 1219 ms / 3 req
moov at end, 764450 B 678 ms / 3 req 708 ms / 3 req
moov at end, 23337 B 390 ms / 2 req 385 ms / 2 req
faststart 420 ms / 2 req 420 ms / 2 req

Tests

Three new, each verified red against the old behaviour: a completed range is read out of the window, a read at EOF opens nothing, and the return to the head survives a parse that read past the window's start. 1435 green.

Kept on the argument rather than on evidence

The parked window stays. It covers a return to a mid-file offset, which the retained head cannot, and it did not fire on any layout measured here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HX5zV3Fcf7Nzq4DYGdXvQE

superuser404notfound and others added 3 commits August 2, 2026 17:55
Measured with aetherctl against a Range-logging origin while looking for what
was left of #281. Two defects in the persistent read loop, both of which turn
one round trip into three on a container with a large trailing object.

A range that has been delivered IN FULL clears `activeTask` exactly as a
dropped one does. The no-connection branch reconnected at the READ position
regardless, which resets `winStart` and drops everything still resident, so a
consumer slower than the transfer re-fetched what it had just been handed. The
parse pass is exactly that consumer: it reads one 256 KB AVIO buffer at a time
while the transfer runs at line rate. A 764450 B trailing `moov` cost three
connections and 1506918 delivered bytes, 1.97x its own size, each reconnect
landing exactly one AVIO buffer further on. The branch already excluded the
hard-cap end for this precise reason; the reasoning was never extended to a
range that simply finished.

The EOF decision then sat BELOW that reconnect, so a read at exactly `fileSize`
issued `bytes=<fileSize>-` first and took an empty 206, whose reconnect reset
`winStart` past the last byte and dropped the window the parse was still
reading. That was the third connection.

Serving what is in hand first also revives the #220 frontier refill, which
could not run while the no-connection branch preempted it on every completed
range.

Measured before and after on four MP4 layouts, all above the 32 MB range size
so their tails lie outside the first window:

  moov at end, 764450 B     5 requests -> 3
  fragmented, mfra at end   5 requests -> 4
  moov at end, 23337 B      2 requests -> 2 (already optimal)
  faststart                 2 requests -> 2 (already optimal)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HX5zV3Fcf7Nzq4DYGdXvQE
#281 parked the open window across a parse seek, cut from `winStart`, on the
reasoning that the demuxer returns to the window's start. Measured against four
MP4 layouts with aetherctl and a Range-logging origin, it returns to the FILE's
start: landings of 48, 1161, 5752, and 265159 in a field trace.

Those coincide only while the parse seeks away before reading anything, which is
the moov-at-end case and nothing else. A fragmented MP4 reads 33 MB of the head
first, so `winStart` has long left byte zero and the parked copy begins
megabytes past everything the return trip asks for. It covered nothing.

The head is now collected as the data connection delivers it, capped at the same
4 MB and released with the rest of the open-phase state. Collecting it later is
not possible: `trimWindowLocked` drops the head as the parse reads forward, so by
the time a seek asks for a copy there is nothing left to copy.

Measured against an origin with 300 ms of latency per request, since a loopback
one cannot show an overlap:

  fragmented, mfra at end    1732ms / 4 requests -> 1219ms / 3
  moov at end, 764450 B       678ms / 3 requests ->  708ms / 3
  moov at end, 23337 B        390ms / 2 requests ->  385ms / 2
  faststart                   420ms / 2 requests ->  420ms / 2

The parked window stays. It covers a return to a mid-file offset, which the head
cannot, and it did not fire on any layout measured here, so it is kept on the
argument rather than on evidence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HX5zV3Fcf7Nzq4DYGdXvQE
Kept in the previous commit on an argument rather than on evidence: it covers a
return to a mid-file offset, which the retained head cannot. Measured across six
container layouts, four MP4 and two MKV, it served no read at all. The demuxer
returns to the head of the FILE in every one of them, which is what the retained
head covers, so the park was carrying a premise the measurement contradicts and
up to 4 MB of transient footprint next to the copy that actually serves.

Layout matrix, 300 ms of origin latency per request, requests and which span
answered:

  faststart.mp4      2   nothing (the suffix fetch is spent, deliberately)
  moovend.mp4        2   tail prefetch (trailing moov, 23337 B)
  bigmoov_end.mp4    3   retained head (trailing moov, 764450 B, too big to fit)
  frag.mp4           3   tail prefetch (mfra) + retained head
  cues_end.mkv       2   nothing; matroska does not read its Cues at open
  cues_front.mkv     2   nothing

Request counts are unchanged by the removal on all six.

Also measured, and the reason the speculative suffix fetch stays despite paying
off on only two of the six: at 250 KB/s with 300 ms latency it costs 252 ms on a
layout that does not use it (64 KB of link time, exactly) and saves 360 ms on one
that does. Those are equal at about 2 Mbit/s, which cannot stream the material
this engine opens; at 10 Mbit/s the cost is 51 ms against a round trip that the
#281 field trace measured at 438 ms.

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

Copy link
Copy Markdown
Owner Author

Both points I had left on an argument rather than on a measurement are now measured, and one of them did not survive.

The parked window is gone

It served no read on any of six container layouts. The demuxer returns to the head of the FILE in every one of them, which the retained head covers, so the park was carrying a premise the measurement contradicts plus up to 4 MB of transient footprint next to the copy that actually answers. Request counts are unchanged by the removal on all six.

layout requests what answered
faststart.mp4 2 nothing
moovend.mp4 2 tail prefetch (trailing moov, 23337 B)
bigmoov_end.mp4 3 retained head (trailing moov, 764450 B, too big to fit the suffix)
frag.mp4 3 tail prefetch (mfra) + retained head
cues_end.mkv 2 nothing
cues_front.mkv 2 nothing

Worth recording separately: matroska does not read its Cues at open, wherever they sit. The speculative fetch is spent on both MKV layouts.

The speculative suffix fetch stays, and now there is a number for it

It pays off on two of the six, so the question was whether the other four are paying for them. Five runs per point at 250 KB/s with 300 ms of latency, spread under 30 ms:

layout with without delta
faststart.mp4 1587 ms 1335 ms +252 ms
cues_end.mkv 1590 ms 1350 ms +240 ms
moovend.mp4 1716 ms 2076 ms -360 ms

The cost is exactly the link time of 64 KB (64 KB / 250 KB/s = 256 ms). The benefit is one round trip plus the sequential tail fetch it replaces. They are equal at about 2 Mbit/s, which cannot stream the material this engine opens; at 10 Mbit/s the cost is 51 ms against a round trip the #281 field trace measured at 438 ms. Keeping it.

1435 tests green.

@superuser404notfound
superuser404notfound merged commit 7cf7cfc into main Aug 2, 2026
3 checks passed
@superuser404notfound
superuser404notfound deleted the fix/avio-window-retention branch August 2, 2026 17:57
superuser404notfound added a commit that referenced this pull request Aug 3, 2026
…a suffix-blind origin is asked once

#281 retest, two findings from the reporter's 6.5.2 traces.

The head was released one read too early. `markOpenPhaseFinished` dropped it on the
last read of the parse, on the reasoning that a far seek from there on is a scrub. It
is not: after a trailing-index parse the anchored connection sits at the END of the
file, so playback's FIRST read is a backward one, and it lands at the head on every
layout measured (48 and 263303 under aetherctl, 5752 in the field trace, where it cost
a fresh connection whose first byte took 865 ms). No probe-based measurement could see
this, because `probe` exits at exactly that call, which is why the PR #285 matrix
reported the layouts as settled. Measured against a 300 ms origin on a fragmented
fixture, playback's first read moves from a new connection at 263303 to a copy out of
the head, and the next connection is deferred to 4194304: one round trip off the first
frame and 3.93 MB that were already resident no longer re-fetched. The head is now
released by the first post-open read it cannot answer instead.

The reporter's origin answers `bytes=-65536` with a 200 and the whole file, on every
open. The delegate still hangs up at the response header, so no body is taken, but the
request was being paid per open: a second connection opened at the same instant as the
data connection whose first byte IS the cold start, sharing that uplink, against a
server that had already shown it cannot serve it. Its answer is now remembered per
origin for the session, and the log says so rather than going quiet.

Only the origin's own answer latches on the first occurrence; a transport failure takes
two, since a link bad enough to lose this request loses others.

Verification lines for the field: `retained file head served playback's first read at
<offset>` and `tail prefetch skipped: this origin declined suffix ranges earlier this
session (<reason>)`, both of which exist before being named this time.

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