Skip to content

Long-suspended data tasks holding full receive windows starve every Network.framework flow in the app process on tvOS — close-and-rerequest at high water would avoid the dormant state (field dose-response inside) #310

Description

@rrgomes

Context. tvOS 26 (AppleTV14,1 ×2), engine 6.7.0, persistent reader, direct-play VOD over LAN. Follow-on to #220's "suspend is advisory": this is what we now believe the long-suspended state does to the whole process.

Finding. During low-bitrate playback we see recurring 10–80 s episodes in which EVERY Network.framework-based flow in the app process goes deaf at once — established WebSockets stop receiving (dying of ETIMEDOUT ~30 s in, their sends unACKed), and no NEW nw flow completes a handshake (URLSession and bare NWConnection fail identically, -1001 at deadline, SYN-ACKs arriving on the wire unprocessed) — while in the same seconds raw BSD sockets from the same process complete TCP handshakes AND full plaintext HTTP exchanges 100% (229/229 in one evening), other processes on the box are unaffected, and the process is idle (4–18% CPU, ~16 threads parked, ~30 fds). On tvOS, TCP for nw flows runs in-process (user-space networking); everything observed is consistent with the process's nw inbound path stalling as a unit — beneath CFNetwork, above the kernel-socket path.

The dose-response tying it to reader dormancy. The reader suspends its data task at high water and can then sit dormant for long stretches holding a full unread receive window (a closed TCP window on an idle established flow). Dormant-stretch length is set by media bitrate (≈ window size / rate). Same fleet, same wire, three runs in 30 hours:

media rate dormant stretch episodes
~1.2 Mbps h264 1080p ~100 s ~11 in 80 min
~14 Mbps HEVC 4K ~8.5 s 1 in 54 min
~88 Mbps HEVC 4K ~1.4 s 0 in 118 min

A paused player is the limiting case (indefinite dormancy) and is where we've recorded the worst episode (11 unbroken minutes). The effect switches back on at will by returning to low-bitrate content.

Why the fix belongs at the reader even though the fault looks like Apple's. Nothing a session owner does dodges the stall once it starts — it hits fresh sessions and bare NWConnection equally. The escape is not entering the dormant state: the engine already ENDS the connection at the hard cap and re-requests on drain (#220's machinery); at low bitrates, though, suspend() engages first and the flow then sits dormant-established ~100 s per cycle. If the reader closed-and-rerequested at high water instead of suspending (or bounded how long a task may stay suspended before the conn is ended), the pathological state never forms — one extra range request per drain cycle (every ~100 s at 1 Mbps; ~2 ms connects on LAN), and a paused player would hold no dormant flow at all.

Happy to run any instrumented build — we have per-second probe arms (URLSession / NWConnection / raw-BSD) that make episodes and their absence directly measurable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions