Skip to content

fix(mux): start a live export at the publisher's largest group - #5947

Merged
kixelated merged 5 commits into
mainfrom
quest/m1/test-flakes-2/ts-mp2-join-hole
Oct 19, 2026
Merged

kixelated merged 5 commits into
mainfrom
quest/m1/test-flakes-2/ts-mp2-join-hole

Conversation

@kixelated

@kixelated kixelated commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

TS validation's "TS full multiplex at its own rate" step still flaked with an MP2 hole right after the export's live join, even with #5854's budget fix: run 39042472016 logged media skipped upstream ... track="2.mp2" at=2.389977s skipped=120ms. The capture has the first frame of one PES (2.390 s), then nothing until the next PES (2.510 s).

The cause is the live latch from #5854. It starts at the newest group announced when the first frame arrives. Behind a backlog, the publisher serves the budget's groups oldest first, so the oldest in-budget group can land alone. The export starts there and reads it. Then the next PES moves the live edge a full budget (450 ms) past the groups after it, and the budget skips them: a gap after the first frame.

The CI timings fit this. The source's PCR puts PES 2.39 right at the edge of the 450 ms budget when the export joined, and PES 2.87 arrived about 20 ms later. That expires exactly 2.414 to 2.486 and keeps 2.510, which are the four missing frames.

Reproduced deterministically. A memory session holds back every group stream after the first, the way a network can deliver one stream ahead of the rest. On main, the export reads [1656000, 1800000, ...]: the same one frame followed by a 96 ms hole.

Approach

  • moq-mux: the live latch also reads the track's largest (track::Subscriber::poll_largest). On lite-07 that comes from SUBSCRIBE_START, before the newest group's stream lands. The latch starts at whichever is newer: that largest group or the newest announced. Nothing is read below it, so dropping the backlog is not a gap.
  • moq-net: a relay's SUBSCRIBE_START reported only the newest group the relay held (poll_live). Right after its upstream subscribes, that can be the same lone oldest group. The relay already records its source's answer as the cache's largest hint (report_largest), so TrackRun::start now reports the larger of the two. A new crate-private track::Subscriber::peek_largest reads that hint.
  • js/net: the same relay forwarding. A lite-07 SUBSCRIBE_START's Largest was bufferedLargest, which only sees arrived groups. It is now the later of that and the track's largest, which already holds the source's report; a new internal peekLargest hook reads it.
  • draft: the Largest definition now says it can name a frame a relay's source reported that has not arrived yet.

This also removes #5854's trade-off, where some joins started one video GOP back. Locally, just test ts --full now starts video, MP2 and AC-3 at the newest group (3.08 s) in all three runs, with no holes.

Tests:

  • a_live_start_behind_a_backlog_starts_at_the_newest_group (moq-mux) covers a direct link and a relay. Without the consumer change the direct case fails. Without the relay change the relay case fails.
  • lite07_start_reports_the_sources_largest (moq-net) fails without the relay change (largest: 0.0 instead of 4.0).
  • lite draft-07 reports the source's largest position past what a relay holds (js/net) is its JS mirror, and fails without the js/net change (group 0 instead of 4).
  • The session plumbing moved into container::test_util::Link, which test(mux): reproduce the live export's burst loss over a session #5857's a_live_start_loses_no_group_the_publisher_bursts now uses too. Link::held gates the uni streams a link accepts.

Impact

  • Public API: none. track::Subscriber::peek_largest is pub(crate), and the JS hooks.peekLargest is package-internal.
  • Wire: no format change. A lite-07 relay's (Rust or JS) SUBSCRIBE_START Largest now carries its source's reported largest when that is past what the relay holds. The draft's Largest definition says so.

Decision

The relay forwards its source's largest in SUBSCRIBE_START rather than only the newest group its cache holds. The cache is a performance optimization, so a relay reports and behaves the same warm or cold; rs/moq-net/AGENTS.md now says so. The rejected options were latching only the export on the hint (the publisher, relay, export topology keeps flaking) and splitting the moq-net change into its own PR.

Alternatives

  • Hold the latch until the budget settles. No local signal says the newest group has arrived, and waiting on a timer is a workaround.
  • Re-latch after a budget skip. The first group has already been exported by then, so the hole stays.

Follow-ups

  • The moq-transport publishers' SUBSCRIBE_OK Largest Location comes from the relay cache (live_edge in Rust, liveEdge in js/net), so they have the same relay limitation. This is low priority, since moq-lite comes first.
  • quest/m2/audio-join-budget.md now points at Link::held for its repro.

Checks

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 2 commits October 19, 2026 00:53
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A live export latched onto the newest group announced when its first frame
arrived. Behind a backlog, the budget serves groups oldest first, so the
backlog's oldest group can land alone: the export read it, then skipped the
groups after it once the next PES moved the live edge a budget past them. That
is TS validation's MP2 hole: one frame, then 96 ms missing.

The latch now takes the publisher's largest (SUBSCRIBE_START on lite-07) when it
names a group past what has arrived. A relay answered with only the newest group
it held, which right after its upstream subscribed can be that same oldest
group, so a lite-07 relay's SUBSCRIBE_START now forwards its source's largest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Quest outcome: I reproduced the MP2 join hole deterministically and fixed it at its cause, the live latch starting at the backlog's oldest group. The fix has two parts: the export latches on the publisher's largest, and lite-07 relays forward their source's largest. All local checks pass, including just test ts --full 3 of 3.

One open decision keeps this a draft: should a relay's SUBSCRIBE_START Largest carry its source's reported largest? I recommend option 1 in the description (yes, as this PR does).

(Written by Claude Opus 5.5)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Decision settled by the maintainer: option 1. A relay's SUBSCRIBE_START proxies its upstream's largest group rather than reporting only what its cache holds.

The cache is a performance optimization, so a relay should report and behave the same with a warm or cold cache. That guideline is now in rs/moq-net/AGENTS.md (800565d).

(Written by Claude Opus 5.5)

@kixelated
kixelated marked this pull request as ready for review October 19, 2026 15:28
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T16:05:53.920491Z 36399c8 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 15 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ce51b41c-7dc1-4ea2-8893-8c5d2faf427b
📥 Commits

Reviewing files that changed from the base of the PR and between ba6a3a6 and 36399c8.

📒 Files selected for processing: 14
  • drafts/draft-lcurley-moq-lite.md
  • js/net/src/internal.ts
  • js/net/src/lite/publisher.test.ts
  • js/net/src/lite/publisher.ts
  • js/net/src/track.ts
  • quest/m1/test-flakes-2/README.md
  • quest/m1/test-flakes-2/ts-mp2-join-hole.md
  • quest/m2/audio-join-budget.md
  • rs/moq-mux/src/container/consumer.rs
  • rs/moq-mux/src/container/source.rs
  • rs/moq-mux/src/container/test_util.rs
  • rs/moq-net/AGENTS.md
  • rs/moq-net/src/lite/publisher.rs
  • rs/moq-net/src/model/track.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 800565dd9e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-net/src/lite/publisher.rs
kixelated and others added 2 commits October 19, 2026 08:41
Mirror the Rust relay: a lite-07 SUBSCRIBE_START names the larger of the newest
buffered frame and the largest the source reported, so a relay holding only the
oldest group of a backlog still points a live joiner at the newest group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Merging with maintainer approval. A relay's lite-07 SUBSCRIBE_START reports its source's largest group, in Rust and JS, and the export's live start uses it, fixing the mp2 join hole; rs/moq-net/AGENTS.md gains the warm/cold cache rule. Codex approved this head; CI green.

(Written by Claude Opus 5.5)

@kixelated
kixelated added this pull request to the merge queue Oct 19, 2026
Merged via the queue into main with commit 493d8ee Oct 19, 2026
16 checks passed
@kixelated
kixelated deleted the quest/m1/test-flakes-2/ts-mp2-join-hole branch October 19, 2026 18:13
kixelated added a commit that referenced this pull request Oct 19, 2026
#5947 added a `peekLargest` hook returning a location, which the lite
publisher reads for a subscriber's start. This branch already had one by
that name returning the largest and whether it can still advance. Keep the
branch's shape, answer it from the serving copy for a resumed reader as Rust
`peek_largest` does, and have the publisher read its `largest`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated added a commit that referenced this pull request Oct 19, 2026
peek_largest answers SUBSCRIBE_START, a wire answer, so it reads the
track's largest regardless of the reader's local bounds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant