Skip to content

6.5.5 - The retained head lives to serve playback's first read

Choose a tag to compare

@superuser404notfound superuser404notfound released this 03 Aug 07:52

Reported by @digilearn-dev, from the 6.5.2 retest traces on #281. Two findings, one of which the previous round could not have seen: the whole PR #285 layout matrix was measured with aetherctl probe, and probe exits at the exact call this release moves.

Fixed

  • The retained file head now survives to serve playback's first read. It was released the moment the demuxer finished parsing, on the reasoning that a far seek from there on is a scrub whose landing zone the head cannot serve. That is true of a scrub and wrong about the read that actually comes next. 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: measured landings of 48 and 263303 against a latency-carrying origin here, and 5752 in the reporter's trace, where it opened a fresh connection whose first byte took 865 ms. The head is now released by the first post-open read it cannot answer instead, which is either playback moving past what it holds or a resume position nowhere near it.

  • An origin that declines suffix ranges is asked once per session, not once per open. Some origins, this reporter's among them, answer bytes=-65536 with a 200 and the whole file, or sit on it until it times out. The body was already refused at the response header, so nothing was ever downloaded, but the request itself was re-issued on every single open: a second connection opened at the same instant as the data connection whose first byte IS the cold start, sharing the same uplink, against a server that had already demonstrated it cannot serve it. That answer is now remembered per origin (scheme, host, port) for the process lifetime. Only the origin's own answer latches on the first occurrence; a transport failure takes two, because a link bad enough to lose this request loses others, and a timeout is the network's verdict rather than the server's.

Measurement

aetherctl play against a Range-logging origin with 300 ms of latency per request and one shared 50 Mbit/s pipe, on a 64 MB fragmented MP4 whose parse seeks to the trailing mfra:

playback's first read already-resident bytes re-fetched
6.5.4 new connection at 263303 3.93 MB
6.5.5 served from the retained head; next connection deferred to 4194304 none

On a non-fragmented file with a 147 KB trailing moov, playback's first read at offset 48 is served the same way. The four layouts whose parse never leaves the data connection (faststart MP4, small trailing moov, MKV with Cues at either end) open in two requests before and after, unchanged.

The regression test for this is red against 6.5.4 with four requests where three are enough. Full suite: 1451 tests.

Verification lines

Both exist in the code before being named here, which is the second lesson this thread taught:

[AVIOReader] pump retained file head served playback's first read at <offset>; no reconnect for it
[AVIOReader] pump tail prefetch rejected after <n>ms: <reason>; not asking this origin again this session
[AVIOReader] pump tail prefetch skipped: this origin declined suffix ranges earlier this session (<reason>)

The first is deliberately distinct from retained file head served the return to <offset>, which 6.5.2 already printed for the parse's return trip. One line could not say which of the two a reader was looking at.

Notes

The suffix fetch stays at 64 KB and stays speculative. On an origin that does implement it, it removes a round trip on two of six container layouts and costs exactly the link time of 64 KB on the rest, which is the trade measured for 6.5.2 and is unchanged here. What changes is only that an origin which has refused the form is not asked again.

The deep trailing read on a fragmented source is still its own request, on purpose: it is a media fragment rather than an index, its distance from the end scales with the fragment size, and no suffix window covers that class.

Compatibility

No API changes.