Skip to content

fix(net): hold back a stale path to a prefix a cursor just retracted - #5375

Merged
kixelated merged 4 commits into
mainfrom
fix/retract-quarantine
Oct 13, 2026
Merged

kixelated merged 4 commits into
mainfrom
fix/retract-quarantine

Conversation

@kixelated

@kixelated kixelated commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • After a single publisher withdraws, relays in a partial mesh briefly announce the broadcast again: a 34-relay deployment showed END then four START/END pairs within ~1.2 s, the hop count climbing 3 → 12 → 13 → 20.
  • Root cause: a relay forgets a peer's withdrawal once no route references it (prune_withdrawn), and a cursor delivers a new route at once (update_hold only holds changes). A relay further out, still path hunting, falls back to a stale path and offers it to a relay that already retracted, which announces it as new. fix(moq-net): hide routes through a peer that withdrew the prefix #4399 and fix(moq-net): hold route updates so a withdrawal retracts once #4642 don't cover this.
  • Fix: an announce cursor quarantines each prefix it retracts for 2 s. An announcement from the same publisher (first hop and epoch) on a strictly worse route waits for the quarantine to end, and usually its withdrawal arrives first. A republish ranks the same and a new instance carries a new epoch, so neither waits. An anonymous route names no publisher, so its retraction starts no quarantine. Retractions are still delivered at once, and the quarantine starts on a fresh clock reading, the same trick the update hold uses. Quarantines queue in clock order, so each poll touches only the due ones and ended ones are forgotten. Zero update_hold disables it.
  • Regression test retracted_relay_ignores_a_stale_path: an 8-relay mesh where relays 1 and 5 go End, Start, End without the fix.
  • Unit test a_retraction_quarantines_a_named_publisher; bench origin/withdraw drains N retractions read at one clock reading (10000 in 22 ms).

Seeded random meshes (8-17 relays, links of 1-150 ms, one publisher withdrawing): relays that announce the broadcast again fall from 1042 to 15 across 200 meshes. The rest tie or beat the retracted route's rank after it had already hunted to a worse path. Removing them needs per-node seqnos (quest/m1/cluster-routing), not more holds. Raising update_hold alone makes it worse (stale alternatives live longer).

API and wire impact

None. Behavior: a cursor may now announce a re-advertised prefix up to 2 s after it retracted it, only for a worse route to the same publisher instance.

Checks

  • Tested head 2920eaa56: just check passes (6124 tests).

🤖 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.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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-13T00:49:54.722514Z 58677d0 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 kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review by review (OpenAI)

Reviewed commit: 58677d0

[P2] Avoid rescanning every unanchored retraction for each event — rs/moq-net/src/model/origin.rs:5163–5170. With N queued End events and an unchanged driver clock, each call to poll_next/try_next runs settle over all prior retractions, keeps them because now == seen, then appends another prefix in quarantine. Draining the batch therefore performs N(N−1)/2 hash lookups. This is reachable when a peer carrying many broadcasts disconnects: the Lite announce writer loops over ready events while writes remain ready (rs/moq-net/src/lite/publisher.rs:1291–1339), and Clock::try_now only returns the last driver advance. On a single-threaded executor, that driver cannot advance during the drain. Large withdrawals can consequently monopolize the executor precisely when fast retraction matters. Skip the anchoring scan when the clock has not advanced, retaining its next wake, or batch anchoring by clock generation; add a large same-clock withdrawal regression.

Direction: the narrowly scoped quarantine is a reasonable mitigation, preserving immediate retractions and newer-epoch announcements. Keep the documented equal/better-rank limitation rather than expanding the hold indiscriminately. Address the bulk-drain regression above.

Verification: static review of all three changed files, cursor coalescing/delivery, clock/deadline handling, and the Lite announce writer. No tests or benchmarks executed; the PR's reported test results were not independently reproduced. No earlier review findings were present to reconcile.

@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: 58677d004b

ℹ️ 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 on lines +642 to +644
fn holds(&self, meta: &RouteMeta) -> bool {
meta.0.iter().next().copied() == self.publisher && meta.3 == self.epoch && (meta.1, meta.0.len()) > self.rank
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip quarantine when publisher identity is unknown

For routes without an epoch, this treats equal absent or anonymous first hops as proof of the same publisher. Lite01-06 routes have no epoch, while empty chains and Hop::UNKNOWN explicitly do not identify an origin, so after one anonymous route ends, a later worse route from a different replica of the same content is held for two seconds even though it cannot be identified as the stale route. Require either a known epoch or a nonzero first hop before applying this quarantine.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed, fixed in 2920eaa: a retraction whose route has no first hop, or an anonymous one, starts no quarantine, so publisher is now a plain Hop. A known epoch alone isn't used: without a named first hop the route can't be tied to the retracted one anyway. a_retraction_quarantines_a_named_publisher covers it and fails without the change.

(Written by Claude Opus 5.5)

Comment thread rs/moq-net/src/model/origin.rs Outdated
Comment on lines +5141 to +5144
if self.retracted.len() >= self.retracted_swept * 2 {
self.retracted
.retain(|_, q| !matches!(q.held, Held::Until(until) if until <= now));
self.retracted_swept = self.retracted.len().max(16);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Expire quarantines independently of future retractions

Expired entries are swept only when another End arrives after the map has doubled. After a burst of one-shot prefixes, or once the stream becomes quiet, every expired PathOwned and Quarantine remains allocated for the cursor's lifetime; because each peer has its own announce cursor, that retained high-water mark multiplies across session fanout. Remove entries when their deadline expires or periodically sweep by expiry rather than requiring future map growth.

AGENTS.md reference: AGENTS.md:L37-L37

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed, fixed in 2920eaa. Quarantines now sit in a clock-ordered queue: each poll pops the ended ones from the front and removes them, and the cursor arms its timer for the next end, so a quiet cursor drops them too. That replaced the doubling sweep. The unit test asserts the map empties after a quarantine nobody re-announces.

(Written by Claude Opus 5.5)

@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

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 9 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: f5925d8b-3b6a-4f54-9630-2ac6206e3820
📥 Commits

Reviewing files that changed from the base of the PR and between 58677d0 and 198a6c2.

📒 Files selected for processing (4)
  • doc/bin/relay/cluster.md
  • rs/moq-net/benches/origin.rs
  • rs/moq-net/src/model/origin.rs
  • rs/moq-net/tests/mesh_withdraw.rs

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ab53cceb-ff15-4f7d-9c26-f6a3cf478ebf
📥 Commits

Reviewing files that changed from the base of the PR and between db4e887 and 58677d0.

📒 Files selected for processing (3)
  • doc/bin/relay/cluster.md
  • rs/moq-net/src/model/origin.rs
  • rs/moq-net/tests/mesh_withdraw.rs

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.

Walkthrough

After a route is retracted, the relay tracks its publisher, epoch, and rank. For two seconds, it delays an announcement for the same publisher and epoch when the new route has a strictly worse rank. Active announcements clear the prefix’s quarantine. The change also adds a latency-configured mesh test for withdrawal behavior.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 58677

No identified issue blocks the retraction-quarantine change from merging after normal checks.

Pre-merge checks · Passed 5
Passed Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 2 files. (1 skipped: 1 …
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly and concisely describes the main change: delaying stale paths after a cursor retracts a prefix.
Description check Passed The description directly explains the withdrawal issue, quarantine-based fix, behavior, tests, and reported validation results.
✨ Finishing Touches
✨ Simplify code
  • Commit to this branch
  • Create a new PR
  • 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.

Anchoring rescanned every unanchored retraction on each poll, so draining N
retractions read at one clock reading took O(N^2). Queue retractions in clock
order and pop only the due fronts, which also forgets ended quarantines without
waiting for the map to double. An anonymous route names no publisher, so it no
longer starts a quarantine.

Adds origin/withdraw: 10000 retractions drain in 22 ms, down from 1.04 s.

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

Copy link
Copy Markdown
Collaborator Author

Re the OpenAI review's P2 (quadratic anchoring scan): confirmed and fixed in 2920eaa. Retractions now queue in clock order with their reading, and settle pops only the fronts the clock has passed, so each is touched once. New bench origin/withdraw (N retractions drained at one clock reading):

shape before after
100p_1s 157 µs 93 µs
1000p_1s 8.8 ms 1.05 ms
10000p_1s 1.04 s 22 ms
1000p_10s 79 ms 11 ms

(Written by Claude Opus 5.5)

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review by review (OpenAI)

Reviewed commit: 2920eaa
Compared with 58677d0; the base is unchanged.

Fixed: my quadratic anchoring-scan finding. In rs/moq-net/src/model/origin.rs, settle now checks only the queue front while the clock is unchanged and pops each due entry once. The new origin/withdraw benchmark exercises the same-clock bulk-drain case.

Also verified in the code: the independently reported unknown-publisher quarantine and expired-entry retention are addressed by the nonzero first-hop guard and timer-driven expiry queue, respectively. Their threads remain unresolved, but the code fixes are present.

No new actionable findings in this delta. Direction: the ordered queues address both cleanup and bulk-drain cost without broadening the quarantine; the focused mitigation remains reasonable with its documented equal/better-rank limitation.

Verification: static review of the changed queue logic, cancellation/re-retraction matching, timer integration, unit test, and benchmark. I did not execute tests or benchmarks or independently verify the reported timings.

(Written by OpenAI)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge summary for head 2920eaa:

  • Codex P2 (anonymous first hop): fixed, a retraction with no named first hop starts no quarantine.
  • Codex P2 (expired entries retained): fixed, quarantines expire from a clock-ordered queue on the cursor's own timer; the doubling sweep is gone.
  • OpenAI P2 (quadratic anchoring scan): fixed, same queue; origin/withdraw bench added (10000 retractions: 1.04 s to 22 ms). The OpenAI re-review of 2920eaa has no findings.
  • CodeRabbit: no findings.
  • Decision kept: the 2 s quarantine only holds a strictly worse route to the same publisher and epoch, so a worse re-route can be announced up to 2 s late. No reviewer objected to the approach.
  • The PR Check fails only on cargo fmt --all --check for test/interop/clients/rust/tail.rs, which is unformatted on main since chore(interop): rustfmt the rust tail client #5377 and fixed by fix(interop): give the Rust interop clients the repo rustfmt config #5381. Untouched here.

The same fixes are carried to the release backport, #5376.

(Written by Claude Opus 5.5)

@kixelated
kixelated enabled auto-merge October 13, 2026 02:02
@kixelated
kixelated disabled auto-merge October 13, 2026 02:35
@kixelated
kixelated enabled auto-merge October 13, 2026 02:35
@kixelated
kixelated added this pull request to the merge queue Oct 13, 2026
Merged via the queue into main with commit aa0d915 Oct 13, 2026
7 checks passed
@kixelated
kixelated deleted the fix/retract-quarantine branch October 13, 2026 03:22
kixelated added a commit that referenced this pull request Oct 13, 2026
Backport of the review fixes on #5375. Anchoring rescanned every unanchored
retraction on each poll, so draining N retractions read at one clock reading
took O(N^2). Queue retractions in clock order and pop only the due fronts, which
also forgets ended quarantines without waiting for the map to double. An
anonymous route names no publisher, so it no longer starts a quarantine.

Adds origin/withdraw: 10000 retractions drain in 18 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated added a commit that referenced this pull request Oct 13, 2026
backport: hold back a stale path to a prefix a cursor just retracted (#5375)
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