Skip to content

fix(relay): keep only finished groups warm when a track goes idle - #3977

Merged
kixelated merged 2 commits into
mainfrom
claude/unruffled-goldstine-428939
Sep 24, 2026
Merged

kixelated merged 2 commits into
mainfrom
claude/unruffled-goldstine-428939

Conversation

@kixelated

@kixelated kixelated commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Found while chasing the Smoke "audio/video sync: after rejoin" flake (player-side fix in #3973).

When the last reader leaves a relayed track, the origin front parks it: it copies the cached groups to a warm local track, then drops the source copy. That cancels the upstream subscription and resets the group still in flight. The warm copy had adopted that open group, so the logical track's resume boundary pointed mid-group ((18, N)) into a head that was now aborted.

On rejoin the relay re-spliced from that boundary and sent upstream SUBSCRIBE { Group Start: 18, Frame Start: N }. It only got the tail, and nobody could read the head, so group 18 never reached the returning reader. The player got 17 (warm) and then 19.

The cache itself is not at fault. create_group already evicts an aborted incarnation (claim_sequence), the same way writeGroup does in JS.

The repro needs lite-06 in the browser because pre-lite-06 peers widen a mid-group boundary to frame 0. In Rust-to-Rust relaying the group is lost on lite-05 as well.

Approach

warm_copy now adopts only finished groups. An open group is left to the re-splice, which resumes at its first frame and delivers it whole.

Regression test rs/moq-net/tests/rejoin.rs (mock transport, lite-05 and lite-06): leave mid-group, the publisher cuts the group, rejoin, and group 1 must come back with every frame. It fails on both versions without the fix.

Impact

  • No public API change. Adds a crate-private group::Producer::is_finished.
  • No wire change. A rejoin now sends Group Start = <open group>, Frame Start = 0 instead of a mid-group frame.
  • A returning reader re-fetches the open group from upstream instead of reading it from the warm copy.

Alternatives

  • Make track::resume_position ignore an aborted group's resume_frame. That would break failover, where readers still hold the aborted head and want only the tail.

Follow-ups

  • A lite-06 join after an idle stretch still gets the latest group, even when it's stale in wall-clock time. That is intended: Max Age is measured from the latest group, and a subscriber that doesn't want a group again sets Group Start to last+1. No draft change.

🤖 Generated with Claude Code

(written by Claude Opus 5.5)

When the last reader leaves, the origin front parks the track: it copies the
cached groups to a warm local track, then drops the source copy, which cancels
the upstream subscription and resets the group still in flight. The warm copy
had adopted that open group, so its resume boundary pointed mid-group into a
now-dead head. A rejoin then subscribed upstream from that frame and the group
never reached the returning reader.

Adopt only finished groups. The re-splice resumes at the open group's first
frame and delivers it whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review September 23, 2026 19:31
@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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.

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

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: ba69e2a9-155a-4d63-adae-9a570a5cdc76

📥 Commits

Reviewing files that changed from the base of the PR and between db73903 and a7e7d9d.

📒 Files selected for processing (4)
  • doc/lib/c/index.md
  • rs/moq-net/src/model/group.rs
  • rs/moq-net/src/model/origin.rs
  • rs/moq-net/tests/rejoin.rs

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 commented Sep 23, 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-09-24T01:28:13.506286Z a7e7d9d 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.

@kixelated
kixelated merged commit 5056a96 into main Sep 24, 2026
5 checks passed
@kixelated
kixelated deleted the claude/unruffled-goldstine-428939 branch September 24, 2026 01:40

Copy link
Copy Markdown
Collaborator Author

Merged (squash, 5056a96).

  • Merged origin/main into the branch (36 commits, no conflicts, none touching the changed files).
  • Locally: cargo test -p moq-net --test rejoin passes, all 1188 moq-net lib tests pass, clippy (--all-targets -D warnings) and rustfmt are clean.
  • CI green on a7e7d9d (Check, Test, WASM, Android).
  • Reviews: Codex gave a thumbs up with no findings. CodeRabbit was rate-limited.
  • The doc/lib/c/index.md edit belongs here: it updates the demand note to say only finished groups stay warm.

(Written by Claude)


Generated by Claude Code

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.

2 participants