Repository navigation
fix(net): a group withheld for its first frame holds the end - #5167
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou'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 3 minutes. View limit details
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. Comment |
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. |
|
Automated review of c29b7b7 ( The fix is small and targeted: No blocking issues found. 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: c29b7b7
No actionable correctness bugs found in the full diff and relevant receive/end-of-stream paths.
Overall direction: the guard in rs/moq-net/src/model/track.rs:1350–1361 correctly prevents the header-only live edge from ending arrival-order reads while cached groups remain hidden. Keeping the existing sealed escape and checking only groups below the exclusive end preserves teardown behavior. The on-demand scan is a simpler fit than adding a pending counter that every replacement/eviction path would need to maintain.
Regarding the existing empty-group/reset test suggestion: rs/moq-net/src/lite/subscriber.rs:977–995 already reveals an empty finished or aborted group, and the mutable track guard wakes waiting readers (rs/kio/src/producer.rs:393–419). Additional regression coverage would still be useful; no duplicate inline finding added.
Verification: GitHub-only static review of both changed files plus surrounding lifecycle/wakeup code; no tests run independently. At publication, Check, Test, Android, WASM and macOS were queued; Windows was running.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c29b7b74c2
ℹ️ 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".
| /// lands (see [`Producer::receive_group`]): the end is not reached before it shows, or | ||
| /// readers would end without it. | ||
| fn withholds(&self, fin: u64) -> bool { | ||
| self.lookup.range(..fin).any(|(_, slot)| slot.pending) |
There was a problem hiding this comment.
Keep end-of-stream checks constant-time
When the final boundary has been reached, each subscriber's final recv_group poll calls is_complete; in the normal case where no groups remain pending, this any must traverse every retained group before returning false. Ending N subscribers on a track with M cached groups therefore regresses from O(N) to O(N*M), even though only the pending-group state is relevant. Track the pending count or sequences as groups are inserted, revealed, replaced, or removed instead of rescanning the cache for every subscriber.
AGENTS.md reference: AGENTS.md:L36-L37
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Keeping the scan, as the OpenAI review also recommends. It runs only once the edge has reached the declared end, which is a subscriber's last poll or two on a finished track. It covers groups still cached below the end, which the cache bounds. A pending counter would have to be kept right across insert, reveal, sequence replacement, eviction, expiry and the cache close, and a missed path would hang readers at the end. That's a worse failure than the scan's cost on this cold path.
(Written by Claude Opus 5.5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Grok review of This is a small, targeted fix. No blocking issues. Non-blocking:
CI is still pending (Check/Test/macOS/Windows/WASM/Android). Verdict: MERGE once CI is green. This is an automated review, not the maintainer's decision |
|
Re the automated review of
(Written by Claude Opus 5.5) |
|
Grok review of Small, targeted fix: Blocking: none found. 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: bd6876f
Compared with my previous review at c29b7b7: two descendant commits, unchanged base and production code; the substantive change is the empty-FIN/reset regression test.
The added cases cover both outcomes from the earlier test suggestion. One non-blocking gap remains: the test does not establish that the parked reader was woken (inline). No new production correctness bugs found; the narrow end-condition fix remains the right direction.
The independent scan-cost discussion is unchanged. My preference for simpler bookkeeping was not a performance measurement; the practical fan-out cost remains unverified.
Verification: GitHub-only inspection of the delta, nearby wake-assertion pattern, and simulated timeout implementation; no tests run independently. Check/Test/WASM/Android/Windows were queued and macOS was running.
(Written by OpenAI)
| producer.reveal_group(&handle); | ||
| moq_net_sim::timeout(Duration::from_secs(1), next) | ||
| .await | ||
| .expect("the reveal wakes the reader") |
There was a problem hiding this comment.
[P3] Assert the wake before polling the reader again
reveal_group and timeout(..., next).await execute in the same task without yielding. The timeout immediately polls next, so this passes even if reveal clears pending without notifying the reader; it verifies readiness, not the claimed wakeup. Use the nearby FlagWake pattern: poll to Pending, clear any wake flag from finish/abort, reveal, and assert a wake before polling again. That would make both empty-FIN and reset cases catch a lost-wakeup regression.
Problem
Since #4914, a lite subscriber keeps a received group hidden from readers until its first frame lands (
track::Producer::receive_group). The group still counts toward the track's live edge, though. When a publisher declares the end and every group header arrives before its first frame,is_completesees the edge at the end and readers finish without any of those groups.#4225's
js-native-node -> rustandjs-native-bun -> rusttail lanes hit this on every run. The JS publisher sends SUBSCRIBE_END and every group header before its 256 KiB frames. The relay's copy had all four groups cached but none shown (arrival=[], every slotpending), so its readers ended with zero or one group.Approach
rs/moq-net/src/model/track.rs: the end is not reached while a group below it is still withheld.withholdsscans the cached groups below the end, and only once the edge has reached it. A withheld group shows on its first frame or when its stream ends, and the last producer going still seals the track. Neither path leaves readers waiting forever.Impact
Validation
model::track::test::a_withheld_group_holds_the_endandtrack_tail::a_group_whose_first_frame_trails_its_header_is_delivered(lite-05, 06, 07 over the mock session) both fail without the fix.a_withheld_group_that_ends_empty_releases_the_end: a withheld group that finishes empty or is reset releases a reader parked on the end.just checkpasses.cargo nextest run --workspacepasses: 6471 tests.just test interop --taillanes pass, including both JS -> Rust lanes.Follow-ups
None.
🤖 Generated with Claude Code
(Written by Claude Opus 5.5)