Repository navigation
fix(net): an aborted live copy doesn't hold a track's end open - #7149
Conversation
Quest-Claim: dfc62673f9de2a77
A track's declared end now judges each slot below it by the copy serving its end: a fetched head that finished settles the end over a live copy that was cut or evicted, and an aborted copy counts as nothing in flight. A later abort then keeps the clean end instead of turning it into an error. The outcome poll watches every copy, so a serving head aborting hands the wait back to the live group. js/net mirrors the aborted-copy rule; it caches one copy per group, so it has no fetched-head case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Treating every aborted copy as settled let a session that died mid-group end its subscription cleanly, which the subscription integrity test rejects. Narrow the rule to the copy serving the slot: a fetched head serving the group to its end settles it over a cut, evicted, or still-written live copy, and an aborted live copy with no head left still holds the end open. js/net caches one copy per group, so it keeps its check. 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 26 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. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 0a36262
No actionable bugs found. Judging settlement through the slot's existing serving-copy selection is a focused fix: a completed fetched head can settle the end without waiting on the abandoned live copy, while an aborted head falls back to the live copy. Watching both copies before rechecking settlement also preserves wakeups across that handoff. The added tests cover live cancellation/eviction, a still-open live copy, head abortion, and clean-outcome latching with and without a preceding poll; no API or wire expansion is needed.
Verification: static review of the diff and surrounding track/group settlement and polling code only. I did not run the tests or exercise concurrent transport delivery.
|
Merge summary:
(Written by Claude Opus 5.5) |
Problem
TrackState::is_settled(rs/moq-net/src/model/track.rs) decides whether a track's declared end has settled, so that a later abort keeps the clean end. It checked only each slot's live copy,slot.group. Suppose the live copy of a group was cut or evicted while a fetched head still served that group to its end. When the head finished, the end stayed unsettled, and a later track abort turned the clean end into an error.poll_outcomehad the same gap: it waited onslot.group.poll_closedand treated an aborted live copy as never closing.Found while landing moq-dev/moq#7053.
Approach
Slot::is_settled: the copy serving the slot (Slot::serving(), the same choiceserves_endandcut()make) finished. A finished copy counts even if a transport bound cut it short.TrackState::is_settlednow usesSlot::is_settled.poll_outcomenow waits on the same predicate. It registers on every copy, because a serving head that aborts hands the slot back to the live group, and then judges again, so a copy that closes in the meantime is not missed.js/netis unchanged. Its track cache holds one copy per group with no fetched head, so the head case cannot happen there. Its aborted-group rule already matches Rust's.Tests (in
track.rs). Both head tests fail onmain:a_fetched_head_settles_the_end_once_it_finishes: the live copy is cut (Cancel), evicted, or finished. The end settles when the head finishes, and a later abort ends cleanly. Covered both with and withoutclosed()seeing the end first.a_finished_head_settles_the_end_over_an_open_live_copy: the end settles while the live copy is still being written, and that copy aborting later keeps the end clean.an_aborted_head_hands_the_end_back_to_the_live_copy: the head aborts. A finished live copy settles the end. A cut one does not, so the track abort still errors.Choices made without asking
subscription_end_integrity::a_subscription_cut_by_the_session_close_does_not_end_clean: when a session dies mid-group, it aborts the live copy, and the subscription then endedOk(None)short of the final group. The moq-lite rule is that a subscription ends cleanly only once every group is accounted for. So an aborted copy counts as nothing in flight only when another copy (the fetched head) serves the group's end.a_fetched_head_holds_the_largest_open. An abandoned fetch aborts its head, which hands the slot back to the finished live copy, so this cannot hang.Impact
Suggested follow-ups
DropCursor::cutstill reports a cut that a fetched head covers. This was raised in moq-dev/moq#7053 and is not addressed here.Validation
just check origin/main: pass on the branch before merging main (9023 Rust tests, plus clippy, fmt, quest, and shell checks).cargo test -p moq-net --all-featuresafter merging main: 2649 lib tests and 491 integration tests pass.quest check: pass. The finished quest and its link inquest/m1/README.mdare removed.🤖 Generated with Claude Code
(Written by Claude Opus 5.5)