Skip to content

fix(net): keep a quiet track's latest group past rejoin and takeover - #5430

Merged
kixelated merged 2 commits into
releasefrom
quest/m0/release-22/quiet-edge
Oct 13, 2026
Merged

kixelated merged 2 commits into
releasefrom
quest/m0/release-22/quiet-edge

Conversation

@kixelated

@kixelated kixelated commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #5413.

Quest: quest/m0/release-22/quiet-edge (backport to the 0.17 line; main deleted this code in #4741).

Problem

On release, the cache exempts only a live track's latest group from expiry. When a parked track re-splices, the front dropped its WarmCopy. That finished the warm track and demoted its newest group out of protection, while io.head still held the group as the live edge. The new upstream copy resumes past that group, so nothing re-sends it, and the pool expires it. A late reader of a quiet track (a catalog published once) then gets nothing. TrackIo::retire returned early on a finished held group, so a publisher takeover between groups lost it the same way.

Fix

Uses the approach settled in the quest plan, not the reporter's cache::Protection fallback. Keeping the copy alive was not invasive.

  • At splice, the warm copy is kept unfinished in TrackIo::prior with its edge's sequence. The existing rule (a live track's latest group is protected) then covers the edge.
  • TrackIo::hold (used by Step::Held and bury) releases prior once the spliced copy delivers a newer group, so a superseded group still expires.
  • retire no longer skips a finished held group. It warms it the same way, and the following splice moves it into prior.
  • Park, end, and track removal drop prior as before.

Tests

In rs/moq-net/tests/rejoin.rs, over lite-05/06:

  • rejoined_quiet_catalog_survives_cache_expiry: the reporter's test, lightly restyled.
  • taken_over_quiet_catalog_survives_cache_expiry: the takeover variant. The publisher reconnects while a reader holds the catalog, and the old connection drops.

Both check that the late reader gets the catalog after 2x the cache expiry, and that the superseded group expires once a newer one arrives.

Before the fix, both fail with "late reader lost the latest catalog". With only the splice change, the rejoin test passes and the takeover test still fails, so each half is covered. just check origin/release passes.

The takeover variant waits 100ms on the paused clock before dropping the old upstream connection. An equal-cost second route changes no announcement, so there is nothing to wait on. The paused clock only advances once every task is idle, so this is deterministic. Without it, lite-06 sometimes dropped the old route before the replacement's announce landed.

Impact

  • Public API: none. All changes are private to origin.rs.
  • Wire: none.
  • Memory: a quiet track now keeps one extra local track handle (the warm copy, whose groups it already shares) until a newer group arrives or the track parks.

Follow-ups

  • Delete quest/m0/release-22/quiet-edge.md and its Required entry in quest/m0/release-22/README.md on main once this merges, as quest: finish the FIRST_OBJECT backport #5075 did for the FIRST_OBJECT backport.
  • main gets its own regression guard via quest/m1/quiet-edge-test.md. The takeover variant here can port with it.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

kixelated and others added 2 commits October 13, 2026 10:58
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-splicing a parked track dropped its warm copy, finishing the track and
demoting its newest group out of cache protection, while the new upstream
copy resumed past that group. Nothing re-sent it, so the cache expired it
and a late reader of a quiet track (a catalog published once) got nothing.
A takeover between groups lost the finished group the same way, since
retire skipped warming it.

Keep the warm copy alive and unfinished until the spliced copy delivers a
newer group, so the existing rule (a live track's latest group is
protected) covers it, and let retire warm a finished held group too.

Closes #5413.

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

Copy link
Copy Markdown
Collaborator Author

Quest outcome: implemented as planned. The WarmCopy stays alive until a newer group arrives, and retire warms a finished held group. The cache::Protection fallback was not needed. Both regression tests fail on release without the fix and pass with it.

Open for the maintainer:

  1. The takeover test waits 100ms on the paused clock so the replacement's announce lands, because an equal-cost route emits no announce update to wait on. Recommendation: accept it, since it is deterministic under paused time. The alternative is a test-only route-count hook, which isn't worth adding on release.
  2. Quest file deletion on main. Recommendation: a small main PR after this merges, matching quest: finish the FIRST_OBJECT backport #5075.

(Written by Claude Opus 5.5)

@kixelated
kixelated marked this pull request as ready for review October 13, 2026 18:33
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 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-13T18:36:03.645587Z 7246c4f Draft marked ready
ℹ️ 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

Copy link
Copy Markdown
Collaborator Author

Merge summary for 7246c4ffe48009c9c920585c285aef277d996c94:

  • Backport of the moq-relay 0.17.3: quiet catalog lost after reader rejoin and cache expiry #5413 fix onto release. At splice, the warm copy stays alive and unfinished until the new upstream copy delivers a newer group, so the existing "a live track's latest group is protected" rule covers the edge. retire now warms a finished held group the same way, covering publisher takeover. No public API or wire change.
  • Regression tests in rs/moq-net/tests/rejoin.rs (rejoin and takeover, lite-05/06) fail on release without the fix and pass with it.

Decisions confirmed by the maintainer:

  1. The takeover test's 100ms sleep on tokio's paused clock is accepted on release; it is deterministic under paused time.
  2. A follow-up PR onto main deletes quest/m0/release-22/quiet-edge.md and its Required entry, and notes in quest/m1/quiet-edge-test.md that the main-line takeover test should await a real signal rather than a sleep, porting this PR's takeover variant.

Review: Codex approved the final head with no findings. CI green except macOS, which auto-merge waits on.

(Written by Claude Opus 5.5)

@kixelated
kixelated merged commit d68eaa1 into release Oct 13, 2026
6 of 7 checks passed
@kixelated
kixelated deleted the quest/m0/release-22/quiet-edge branch October 13, 2026 19:14
@blacksmith-sh blacksmith-sh Bot mentioned this pull request Oct 13, 2026
@moq-bot moq-bot Bot mentioned this pull request Oct 14, 2026
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