Skip to content

Make AVIOReader.connStallTimeout configurable — 20 s is a long time to hold a frozen frame when the origin is on the same LAN #272

Description

@rrgomes

AVIOReader.connStallTimeout is a private static let fixed at 20 s. The comment
calls it a CDN stall threshold, and for a CDN that seems right: a distant origin
can legitimately go quiet for a while, and tearing down a connection that was
about to deliver is worse than waiting.

On a LAN origin the trade runs the other way, and I would like to be able to say
so per-load rather than take the 20 s.

What it looks like from here

A 95-minute session against a Plex server on the same wired LAN produced nine
reads that each sat almost exactly at the timeout:

slow read: 20075ms  stallWaits=10(20072ms,9signaled)  reconnects=1
slow read: 20040ms  stallWaits=9(20037ms,9signaled)   reconnects=1
slow read: 20133ms  stallWaits=10(20130ms,9signaled)  reconnects=1
slow read: 20092ms  stallWaits=7(20089ms,6signaled)   reconnects=1
...

Every one is the same shape: no bytes at all, the full timeout elapses, one
reconnect, and then data immediately:

22:29:04.965  pump stall at offset 65673024, reconnecting
22:29:04.969  pump conn start gen=9 offset=65673024 len=26MB
22:29:05.037  pump gen=9 first data after 70ms
22:29:05.053  slow read: 20092ms ... reconnects=1 connect=2ms

The reconnect costs 70 ms and resumes at full rate. So the connection was dead
for essentially the whole 20 s and the only thing that established that was the
timeout expiring. A shorter threshold would have recovered in a fraction of the
time, and the evidence that it would have succeeded is that it did succeed, at
full rate, the moment it was tried.

To be clear about whose problem the underlying fault is: mine. Something on
my LAN is dropping TCP connections without an RST — the same minutes also kill an
unrelated WebSocket to a different service on the same host, so it is not
AetherEngine and I am not asking you to fix it. What I am asking is that the
recovery time not be fixed at a value chosen for a very different network.

I have seen the same shape in an earlier session on a different box (nine
reconnect-and-stall pairs interleaved the same way), so this is not a one-off
capture.

Why 20 s specifically hurts here

Two things make the wait more expensive than a plain 20 s gap in delivery would
suggest.

The content is AV1 and decodes in software (codec=225 → software,
libdav1d, no Apple TV has an AV1 hardware decoder). After the reconnect the
decoder picks up mid-temporal-unit:

[error] Missing reference frame needed for show_existing_frame (frame_to_show_map_idx = 3)
[error] Failed to read unit 0 (type 3): Invalid data found when processing input
[error] Failed to parse temporal unit

so the recovery is not instant either — it costs a further resync on top of the
20 s.

This is a synchronised-playback client. Two people watch the same title in
step over a relay. A 20 s hold is not just a long buffer on one screen; it is 20
seconds of one viewer sitting on a frozen frame while the room's clock keeps
moving, which then has to be reconciled. Something in the 3–5 s range would fit
that context far better, and I would happily take a few more reconnects for it.

The ask

Expose it on LoadOptions, alongside the existing probesize /
maxAnalyzeDuration optionals, defaulting to today's 20 s so nothing changes for
anyone who does not set it:

/// No bytes for this long triggers a reconnect. nil keeps the default,
/// chosen for CDN origins that can legitimately go quiet.
public var connStallTimeout: TimeInterval?

A host that knows its origin is one hop away can then choose a threshold that
matches. I have no view on whether the default should move — you have far more
visibility into the CDN case than I do, and the argument for 20 s there looks
sound to me.

Happy to test whatever you land on; I can reproduce the stall class on demand at
the moment, which is not a sentence I expected to be glad about.

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