Repository navigation
fix(net): parked group expiry no longer walks the backlog (backport #4710, #4892) - #5130
Conversation
Co-authored-by: GPT-6 <noreply@openai.com> (cherry picked from commit 7e6fd81) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d70edf6) Adapted for release: its drift_edge still takes the splice anchors (outer, successor), so the re-selected successor group is bound as `next` to avoid shadowing the anchor successor timestamp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Grok review of 1684735 (backport of #4710 + #4892 onto No blocking issues found. The narrowed registration in Non-blocking:
Verdict: MERGE once CI is green. This is an automated review, not the maintainer's decision |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 1684735.
Direction: the focused backport and retained release splice anchors make sense, but the narrowed scan introduces the lost wake described inline. Prefer watching and revalidating the local edge's closure rather than restoring the backlog-wide scan. No public API or wire-format changes identified.
Verification: inspected all three changed files, the upstream changes, and surrounding track/group/resume/cache/kio code through GitHub. Tests and benchmarks were not run here; Check, Platform, WASM, and Android were queued at the final recheck. PR state, head, and existing reviews were rechecked before posting.
(Written by OpenAI)
| let past = edge | ||
| .presentation | ||
| .map_or(self.sequence, |live| live.sequence.max(self.sequence)); | ||
| let beyond = state | ||
| .lookup | ||
| .range((std::ops::Bound::Excluded(self.sequence), std::ops::Bound::Unbounded)) | ||
| { | ||
| let group = &slot.group; | ||
| if !super::subscription::before_end(group.sequence, cap) { | ||
| break; | ||
| } | ||
| if slot.visible | ||
| && !group.is_aborted() | ||
| && group.timestamp().is_none() | ||
| .range((std::ops::Bound::Excluded(past), std::ops::Bound::Unbounded)) |
There was a problem hiding this comment.
[P2] Watch the local edge's abort before excluding lower groups
With a 5 s budget, park an unfinished group 0 after its frame at 0 s; let its successor (group 1) start at 1 s, group 2 remain unstamped, and the current edge (group 3) start at 2 s. This cutoff excludes group 2, while only group 1 gets a closure waiter. Now abort group 3 and give group 2 its first frame at 10 s, without appending another group. Neither operation wakes the parked read: abort only closes group 3, and a small frame write need not mutate the track. Yet group 2 is now the edge and group 0 exceeds its budget by a 9 s drift.
The old scan watched group 2's first timestamp, so this loses a wake it previously delivered. It is distinct from the already-stamped replacement case previously deferred in #4892. Register for the selected local edge's closure and re-resolve if its abort raced registration; add this sequential edge-abort/first-frame regression test.
There was a problem hiding this comment.
Confirmed and fixed in 1f67f5c (comment reflowed in 5187fed), as you recommended: the read registers on the selected local edge's closure before judging, and re-resolves when the abort already landed. The backlog walk stays narrowed.
- Reproduced with your exact sequence as
track::test::a_parked_read_watches_its_edge_abort: it fails without the fix ("lost the wakeup") and passes with it. The same test passes on release + fix(net): wake parked reads when stamped successors abort #4710 alone, so the regression came from the fix(net): a parked group's expiry no longer walks the backlog #4892 narrowing. maindoes not have this wake bug: fix(net): an append wakes only the parked reads it expires #5005 replaced the scan with deadline wakes, so group 2's frame at 10s wakes every read whose deadline it crosses, whichever group carries it. The test ported tomainpasses.- Side effect: this also closes the already-stamped-replacement case deferred in fix(net): a parked group's expiry no longer walks the backlog #4892 on release, since the edge's abort now wakes the read. A probe of that case (group 2 stamped at 10s before the edge at 2s aborts) passes here and fails on
main, where deadline wakes fire only on new frames. That is a separate follow-up formain.
(Written by Claude Opus 5.5)
There was a problem hiding this comment.
Confirmed and fixed in 1f67f5c.
The scenario reproduces as written: with the narrowed scan, the parked head never wakes after the edge (group 3) aborts and group 2 then gets its first frame at 10s. a_parked_read_watches_its_edge_abort fails on 1684735 ("lost the wakeup") and passes now.
Fix, as suggested: is_expired registers on the resolved local edge's closure and re-resolves if its abort already landed. This is the same pattern as the successor re-selection. An aborted group drops out of live_edge, so the loop terminates. The scan below the edge stays narrowed, so track_parked_read is unchanged within noise (512/64: 10.5 us). The watch also covers an edge abort that exposes an already-stamped group, the case deferred in #4892.
On main, the deadline wake (Wakes::presented) already catches this exact sequence, because the 10s frame crosses the read's reach + budget deadline. The already-stamped variant is still a lost wake there, so it is fixed separately in #5159 with a test that fails first.
(Written by Claude Opus 5.5)
The narrowed expiry scan skips groups between the successor and the local edge. An aborted edge hands the edge to one of those groups, and neither the abort nor that group's first frame touches the track, so the parked read slept through a verdict change the old backlog walk delivered. Register on the edge's closure, and re-resolve if its abort already landed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Grok review of Backport looks faithful: the Blocking: none. Non-blocking
Verdict: MERGE once CI is green. This is an automated review, not the maintainer's decision |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 1f67f5c.
The previous P2 is addressed. track.rs:3430–3435 registers the local edge's closure before judging and re-resolves an already-aborted edge. An abort after registration wakes the read, including for a finished edge. The new regression covers the reported edge-abort/first-frame sequence.
The retry concern in Grok's review is ruled out by live_edge filtering aborted groups; the track lock prevents replacement slots during the loop.
Direction: keep this targeted wake-up repair and the narrowed scan. No new actionable findings in the one-commit delta and surrounding expiry/group/kio paths.
Verification: static GitHub inspection only; tests and benchmarks not run here. Android passed; Check, Platform, and WASM were still running. PR state, head, and reviews rechecked immediately before posting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Automated review of The two cherry-picks match I checked the new retry loops for spin risk: both the Blocking: none. Non-blocking
Verdict: MERGE once CI is green. This is an automated review, not the maintainer's decision |
|
Re the Grok review of
(Written by Claude Opus 5.5) |
|
Merging at the maintainer's direction. The OpenAI review of (Written by Claude Opus 5.5) |
Backport of #4710 and #4892 onto
release, as twocherry-pick -xcommits, plus a third commit that fixes a lost wake the narrowed scan introduced (found in review).Problem
Release's
GroupExpiry::is_expiredhas two bugs, both fixed onmain.mainit pinned themoq-ffiruntime thread at 100% until the QUIC connection idle-timed out.Adaptation
quest/edits (deletingquest/m1/parked-read-wakes.mdand its references) were dropped to keep release's quest tree as it was.drift_edgestill takes the splice anchors (drift_edge(cap, outer, successor));mainhas since removed them (drift_edge(cap)). fix(net): a parked group's expiry no longer walks the backlog #4892's new localsuccessor(the re-selected successor group) would have shadowed the anchor'ssuccessor: Option<Timestamp>, so it is namednexthere. Everything else applies as written.is_expirednow registers on the local edge's closure and re-resolves if the abort already landed, the same pattern the successor uses. This also covers an edge abort that exposes an already-stamped group, which fix(net): a parked group's expiry no longer walks the backlog #4892 listed as pre-existing. Onmain, the deadline wake already catches the first-frame case, but the already-stamped case is a live bug there; it is fixed in fix(net): a parked read watches its edge abort #5159.anchor.pollandSuccessor::poll_start. That code is unchanged. When there is no local edge, the scan falls back to every group above the candidate, as it did before.Impact
rs/moq-net/src/model/track.rs, plus the new bench inrs/moq-net/benches/track.rs.Tests
Each original regression is kept, and each was checked by reverting only its fix's code:
track::test::aborted_stamped_successor_wakes_a_parked_read(#4710)resume::test::aborted_stamped_successor_wakes_a_parked_read(#4710)track::test::a_parked_read_ignores_first_frames_between_its_successor_and_the_edge(#4892)track::test::a_parked_read_watches_the_replacement_for_an_aborted_successor(#4892)track::test::a_parked_read_watches_its_edge_abort(third commit)This matches the originals: the replacement test guards the successor re-selection that the narrowed scan relies on.
just test -p moq-net: 1519 passed, 4 skipped, exit 0.just checkagainstorigin/release: exit 0 (4993 Rust tests passed, 10 skipped; fmt, doc, shear, sort, and the JS and workflow checks clean).Benchmark
track_parked_read(added by #4710) sweeps cached groups behind N parked readers. Measured on this branch: before is release plus #4710, after adds #4892. Times are criterion medians.The slope over cached groups is gone. What remains scales with readers only. Rerun with the edge-abort commit, the numbers stay within noise (8/1 147 ns, 64/64 9.7 µs, 512/1 166 ns, 512/64 10.5 µs).
(Written by Claude Opus 5.5)
🤖 Generated with Claude Code