6.5.1 - The tail fetch is waited on, so it can pay
6.4.5 added a speculative 64 KB suffix range alongside the open, to overlap the round trip a non-faststart MP4 spends reading its trailing object. The reporter's retest came back with the connection pattern unchanged. It was: the fetch could never have won its race.
Fixed
-
The speculative tail fetch now removes the round trip it was added for. It was issued fire and forget, on the reasoning that a fetch landing late costs no more than the reconnect it failed to save. That reasoning was wrong about the timing. The demuxer reaches the trailing object within microseconds of the data connection's first byte, and the speculative fetch pays the same round trip plus a body, so on any origin whose first byte costs anything it is still on the wire at exactly the moment the read arrives. It never once served the read it exists for, it only added a request.
A read landing in the fetched range now waits for it. The bound is what a round trip against this origin was measured to cost, taken from the data connection's own time to first data: both requests left at the same instant, so once the fetch has been outstanding materially longer than that it is not about to land, and a fresh connection is the better bet. Waiting up to that point can never cost more than the reconnect it replaces, which is what makes this the honest bound rather than a tuned constant. An origin that declines suffix ranges now reports a terminal outcome, so a waiter wakes on the rejection instead of sitting out its budget.
Only a loopback origin, which answers before the race can be lost, made the first version look like it worked: six tests passed while the field trace stayed flat. The regression test therefore models an origin whose first byte costs something, and fails against 6.4.5.
Reported by @digilearn-dev, with three cold-start traces on separate sources (#281).
Changed
-
The cold-start paths say what they did. The advertised way to verify #281 was to look for a
bytes=-65536request, which the engine never printed, so its absence from a log was not evidence of anything:[AVIOReader] pump tail prefetch issued bytes=-65536 [AVIOReader] pump tail prefetch installed 65536B at 4502557916 after 213ms [AVIOReader] pump tail prefetch served the read at 4502622327; no reconnect for it [AVIOReader] pump parked window served the return to 5752; no reconnect for itA declined fetch names the reason it was declined (
status=416 (no suffix range support), aContent-Rangethat does not describe what was asked for, a short body). Slow-read summaries gainedtailWaits=, which is its own counter rather than a stall wait: a stall is the source failing to deliver, this is the reader declining to duplicate a request that is already outstanding.
Compatibility
No API changes. One behaviour change: a read into the last 64 KB of a VOD source may now block briefly on an in-flight speculative fetch instead of opening a connection. The wait is bounded by the measured cost of one round trip against the same origin (floor 250 ms, cap 5 s) and is skipped entirely once the fetch has settled, so it cannot be the slower path.
Not addressed, deliberately
A moov grows with the sample count, and a trailing object further than 64 KB from EOF keeps its own request. Sizing the speculative fetch to cover it would spend real bandwidth on a guess, on exactly the slow links this exists to help.
Upgrading
.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "6.5.1")