Skip to content

6.1.2 - Bounded chunk-fetch body

Choose a tag to compare

@superuser404notfound superuser404notfound released this 30 Jul 07:58

A crash fix on the FFmpeg/AVIO source path. Anyone opening large progressive HTTP sources on iOS or tvOS wants this one.

Fixed

  • Opening a large progressive HTTP source no longer dies in the allocator before the first frame. ChunkFetchDelegate reserved whatever the response declared, with no ceiling: body.reserveCapacity(Int(http.expectedContentLength)). One of the requests that reaches that line is the HEAD size probe, and a HEAD declares the entire source while delivering zero body bytes, so opening a 12.4 GB MKV asked malloc for 12.4 GB in order to buffer nothing. On a 6 GB iPhone the allocation returned NULL and __DataStorage.init(capacity:) force-unwrapped it: EXC_BREAKPOINT on the URLSession delegate queue, before a frame played. It traps rather than throwing, so a host app could neither catch it nor degrade. Reported by dlev02 from a symbolicated crash report and register state (#255).

    The surface was wider than the reported case. probeFileSize() launches the HEAD fallback 0.75 s after the range probes and runs it whenever they have not resolved a size yet, so any non-prefetch open (still extraction, one-shot seekable) against a slow origin could take the crash, not only an origin that rejects Range outright.

    Both halves of the fix come from the request instead of the response. The reservation is the declared length clamped to what the request itself can deliver: the span of a bounded Range: bytes=a-b, nothing at all for a HEAD, a flat 8 MB ceiling when the request is open-ended. Data grows on demand, so the cap costs at most one reallocation on a legitimately larger body.

    The body is bounded by that same span. An origin that ignores Range and answers a bounded chunk request with 200 plus the whole source's length used to be buffered in full and only then rejected by the caller's status check; the reader now keeps the prefix it asked for and hangs up. Measured against a scripted origin, that path pulled 134 MB over the wire and then failed the read outright; it now writes single-digit MB, and the truncated chunk serves the read.

    Not affected: the persistent and streaming reader paths, whose resident window is bounded at 48 MB by the reader's own backpressure cap whatever the origin does, and HTTPDiscIOReader, which never reserves from a declared length and requires working Range support at init.

Upgrading

.package(url: "https://github.com/superuser404notfound/AetherEngine", from: "6.1.2")

No API change and no source change for consumers.