Skip to content

5.24.0 - Bounded range requests

Choose a tag to compare

@superuser404notfound superuser404notfound released this 28 Jul 08:10

Minor release. The correction behind the #220 bound shipped in 5.23.12, with the reporter's field measurements throughout.

Changed

The persistent reader asks for a bounded range at a time, instead of the rest of the file. (#220)

5.23.12 bounded the resident window by ending the connection once it passed 48 MB. That caught the damage and left the cause in place: the reader requested bytes=X-, the entire remainder of the source, and then tried to regulate the resulting flow by suspending the URLSession task. suspend() is advisory, so on a link that never saturates the socket the transport keeps delivering into a window nobody is draining.

It now requests 32 MB and re-requests at the frontier once the consumer drains below the 8 MB low water. The origin cannot send more than was asked for, so the window is bounded by construction, at low water plus one range, and no delivered byte is discarded or re-fetched at a boundary.

Three details this depends on:

The refill is issued at the low-water crossing rather than on an empty window, so a range boundary does not become a stall.

A full range delivery is recognised as a planned end and takes its own path: no backoff, no unproductive-reconnect charge, no .reconnecting phase, no lastUnplannedReconnectAt. Nothing failed, and spending the give-up budget on range boundaries would kill the reader on a healthy link.

All persistent connections now share one URLSession with a per-task delegate, the pattern the chunk path has used since the task-pool leak was fixed. Without it a range boundary would be a TLS handshake every few seconds, which is worse than what it replaces. Releasing a connection is task.cancel() rather than session teardown.

Live sources keep the open-ended form, as does any source whose total size is not yet resolved. The first connection is bounded even though fileSize is still unknown when it opens, since a server clamps a range that overruns the file and 416 requires the start to be past the end; waiting for a resolved size would have left the connection that reads from byte zero unbounded.

Verification

Against a real 52.7 GB 4K HEVC remux with PGS, native path, over a link shaped to 1.46x media rate, which is the band the defect actually lives in:

                    5.23.12        5.24.0
  post-suspend      29-124 MB      0
  cap events        8              0
  malloc            142-201 MB     92-151 MB
  window peak       ~51 MB         21 MB

Unthrottled over the same LAN: malloc 114-128 MB, windows 8-21 MB, no cap events. Better than the previous build on the identical run.

Keep-Alive was the risk the design turned on, and it holds: 90 range requests over the run were served on 3 connections, one long-lived per reader. Playback, subtitle counts, stall counts and unproductive reconnects are unchanged from the previous build in every run, including a 20-seek churn test.

Notes

The 48 MB cap and the task suspend both stay. Neither should ever engage now, which makes a firing cap a signal rather than the normal ceiling, and it seemed unwise to remove the net in the same release that introduces the new path.

Thanks again to rrgomes, whose server-side per-connection accounting is what made this measurable in the first place.