You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
moq export ts loses most of the feed under random packet loss since #4001 #4613
Since #4001 (order TS export by media time), moq export ts loses most of a live feed when the path
has random packet loss. Before it, the same path delivers the full rate.
Setup: two network namespaces on one Linux host, relay + moq import ts on one side, moq export ts --max-age 2s on the other. netem adds 10% uniform loss and 25 ms delay on the relay → subscriber
leg, 25 ms delay on the return leg, and no rate limit. The source is a real ~10 Mb/s broadcast TS
(H.264 + MP2 + SI + several data PIDs), paced with tsp -P regulate. Each run lasts 120 s over QUIC
(noq).
I bisected with the relay and the importer held at an older build (84b34f54) and only the
subscriber's moq binary changed:
It isn't the controller, the relay or the wire version:
With CUBIC in place of BBR, main still collapses: 0.80 Mb/s against 6.87 for the older build.
A main relay feeding an old subscriber delivers 10.15 Mb/s.
The old relay caps the session at moq-lite-05, and main's subscriber still collapses on it.
At 0% loss main and the old build both deliver about 9.8 Mb/s. Delivery doesn't settle; it decays
through the run.
Still there on 6f1a9e33, all roles on that build: 1.77 and 1.52 Mb/s at 10% loss against 9.84 at 0%.
My guess, from reading the change rather than measuring it: pick_next_track now holds the earliest
frame for up to max_age until every track has shown it can't precede it. Under loss some track is
nearly always behind, so the exporter spends the same budget that the consumer then enforces when it
evicts groups (current group evicted; skipping to next buffered group). Each hold becomes skipped
content. If that's right, the hold should be capped well below max_age, or charged against it.
Happy to rerun against a branch. The rig is a single shell script plus netem.
Found it. My guess above was half right. The hold does spend the sources' budget, but the collapse comes from rewind() renewing it.
Under loss a source now and then skips a group, and skipping over a hole bumps its discontinuity(). The exporter takes that as a new generation, and rewind() resets stall, refreshes the held frame's arrived and clears every track's timeline. The sparse data PIDs, and video while it waits on a retransmit, then can't show, so the next frame waits the full max_age again. That's the 2 s the sources skip on. After the hold they're a budget behind the newest content, the next stall skips at once, and that's another rewind.
I added logging to pick_next_track and the consumer's skip paths on main and reran the 10% arm. 56 holds in 120 s: 2 ended with the lagging track catching up, and a rewind restarted the rest at the full 2 s. The sources fell steadily behind the newest content, to 25 s after about 35 s and 105 s by the end.
Same build and path, only the hold changed:
mean
second half
hold as on main
1.13 Mb/s
0.36
no hold
9.80
9.95
hold capped at 200 ms
8.37
8.54
hold kept across rewinds
9.70, 9.87
10.47, 10.13
At 0% loss, main and the fix both give about 9.85 Mb/s, with one hold each.
A cap only shrinks it, because each rewind still renews the hold. #4618 keeps the stall and the held frame's wait across a rewind instead, which reverses the two rewind_restarts_* tests from #4001.
A correction to my figures here and in #4618. The Mb/s were egress bytes, and the export pads to the
mux rate, so they measured how much it wrote, not how much programme it carried.
Graded on content (video access units against a 0 % run, same rig: two namespaces, 10 % netem loss
and 25 ms each way on the relay → subscriber leg, the ~10 Mb/s broadcast clip, 120 s):
So #4618 fixed a real stall: the hold no longer compounds and the exporter keeps writing at the mux
rate. It did not bring back the programme lost under loss, which goes before the exporter. The
passthrough export (#5349) on the same rig, which has no interleave, keeps 6 % of the video, and its log shows
the relay ending groups as Old while their streams wait on the transport. The title's symptom
(most of the feed lost at 10 % loss) still holds on main. Happy to open a fresh issue for that part
if you'd rather keep this one closed.
Activity
t0ms commentedon Sep 30, 2026
Found it. My guess above was half right. The hold does spend the sources' budget, but the collapse comes from
rewind()renewing it.Under loss a source now and then skips a group, and skipping over a hole bumps its
discontinuity(). The exporter takes that as a new generation, andrewind()resetsstall, refreshes the held frame'sarrivedand clears every track'stimeline. The sparse data PIDs, and video while it waits on a retransmit, then can't show, so the next frame waits the fullmax_ageagain. That's the 2 s the sources skip on. After the hold they're a budget behind the newest content, the next stall skips at once, and that's another rewind.I added logging to
pick_next_trackand the consumer's skip paths on main and reran the 10% arm. 56 holds in 120 s: 2 ended with the lagging track catching up, and a rewind restarted the rest at the full 2 s. The sources fell steadily behind the newest content, to 25 s after about 35 s and 105 s by the end.Same build and path, only the hold changed:
At 0% loss, main and the fix both give about 9.85 Mb/s, with one hold each.
A cap only shrinks it, because each rewind still renews the hold. #4618 keeps the stall and the held frame's wait across a rewind instead, which reverses the two
rewind_restarts_*tests from #4001.t0ms commentedon Oct 12, 2026
A correction to my figures here and in #4618. The Mb/s were egress bytes, and the export pads to the
mux rate, so they measured how much it wrote, not how much programme it carried.
Graded on content (video access units against a 0 % run, same rig: two namespaces, 10 %
netemlossand 25 ms each way on the relay → subscriber leg, the ~10 Mb/s broadcast clip, 120 s):
mainat83ce47fe(with #4618)mainat83ce47fe(with #4618)84b34f54So #4618 fixed a real stall: the hold no longer compounds and the exporter keeps writing at the mux
rate. It did not bring back the programme lost under loss, which goes before the exporter. The
passthrough export (#5349) on the same rig, which has no interleave, keeps 6 % of the video, and its log shows
the relay ending groups as
Oldwhile their streams wait on the transport. The title's symptom(most of the feed lost at 10 % loss) still holds on
main. Happy to open a fresh issue for that partif you'd rather keep this one closed.
(Written by Claude Opus 5.5)