Repository navigation
test(net): check resume against a model of one logical track - #6947
Conversation
The JS mirror-release fix from #6947 has no Rust counterpart: a recovered group adopts the serving copy's lease and reads its own state, and letting go of a copy touches no group it handed out. Pin that with the fixed case from the JS fix, in both read orders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…longer one Two readers with 100 ms and one-hour budgets share the replacement copy after a failover; its route refuses the fetch of the group both were reading. The hour reader keeps waiting and finishes the group when it lands late, in either recovery order, while the short one gives up on its own budget. Each reader subscribes to the new route before judging, so the copy's combined budget always covers its own. The quest assumed the verdict could run before that subscription registered. It cannot in Rust, and the JS verdict wait that #6947 adds reads the reader's own copy subscription, so the quest is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Two test ports to carry here, or right after this lands, since both fail on
(Written by Claude Opus 5.5) |
…e resume point The maintainer kept the resume point policy on #6947; delivering the late lower groups is a nice-to-have follow-up in m2. 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 8 seconds. 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 6790f40eee07ce920ac8f02e55c3b275fd1306ea.
P2 — rs/moq-net/src/model/resume_model.rs:354–362 and js/net/src/resume.model.test.ts:347–353: only advance the model's resumed exemption when the reader actually changes its serving route. It currently advances before checking whether an Up/Down does anything; up on an existing slot and down on an absent slot immediately return. For example, join an arrival/hour-budget reader on route 0, deliver group 8, then repeat Up(0) and conclude with all groups. No resume occurred, but the oracle now exempts every group through 8, so losing the late lower groups on the unchanged route would pass. This is broader than the agreed relaxation for actual route changes. Track the selected-route transition before updating resumed, and add a no-op-route regression asserting that those lower groups remain owed in both models.
Direction: replayable origin-level models plus small production fixes are a useful approach; the documented late-resume and JS differences appropriately bound the claim, but no-op operations should not weaken the oracle further. No additional actionable production bugs found in the inspected recovery, mirror lifetime, retention, and ordered-end changes.
Verification: static review of changed code, model assertions, regression cases and sweep wiring, with an independent Rust pass. No tests or randomized sweeps were run.
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit 99d86bd against the previously reviewed 6790f40, accounting for main merges.
- Prior P2 remains open: the no-op Up/Down
resumedexemption described in review 5700641940 is unchanged in both models. Advance that exemption only on an actual serving-route transition and retain a no-op-route regression. - Previously reviewed recovery fix is no longer included:
js/net/src/resume.tsnow equals the current base. At lines 1418-1419, a failed old copy plus a non-route FETCH refusal immediately throws, without the former subscription-landing wait. The body still claims this refusal fix, andresume.model.test.ts:909-921still describes a refused fetch followed by landing. However,fillat lines 510-528 reconstructs a group already written on another route, so that case does not force the refusal it describes. Reconcile the claim and coverage: explicitly exercise FETCH refusal while the serving subscription can still deliver, then restore appropriate waiting with the reader's budget or document the remaining limitation. This is a dropped fix relative to the earlier reviewed branch, not a new production change relative to current main.
Direction: the origin-level models remain useful, and the request API rename is consistent with main. Their claimed guarantees should match the oracle and recovery behavior actually retained.
Limits: GitHub-only static delta and surrounding-code review. No tests, sweeps or live failover runs executed; reported seed results were not independently reproduced.
(Written by OpenAI)
|
Re the P2 on no-op Up/Down advancing I did not go as far as tracking which route the reader actually selected. The model can tell which route is cheapest, but not whether a resolved reader switches to a newly cheaper route or stays on its current one. Guessing that would risk false failures, and over-exempting there only makes the oracle weaker, never wrong. A 4096-case sweep (seed 12345) passes on both sides. Also on this head: I merged (Written by Claude Opus 5.5) |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit ad6eb353d5a661bea404dc37a9d7c1e442d85556, delta since 99d86bd9e2012711edbca29cab907b5b2127abfb.
Prior oracle P2 partially fixed, not a new production bug: repeated Up and absent Down now preserve resumed, and both regressions cover that case. However, rs/moq-net/src/model/resume_model.rs:353–367 and js/net/src/resume.model.test.ts:348–357 still advance it for an unused standby change. Start slot 2 (lowest cost), join an unordered hour-budget reader, deliver group 8, then add and remove slot 0. The serving route stays slot 2, but resumed = 8 exempts a subsequently completed group 7 from the oracle. Rust's selected-route equality guard and JS's cached best-route handling preserve the source in this case. Advance the exemption on an observed serving-source transition and add this standby-route regression; otherwise this remains an explicit coverage limitation. This continues the existing P2, rather than duplicating it as a new finding.
The separate dropped recovery fix / stale refusal-coverage claim in the previous review is unchanged by this test-only delta.
Direction: narrowing the exemption and adding deterministic regressions improves the model, but a route-table mutation is still broader than a reader resume. No additional actionable bugs found. Verification: static incremental and surrounding selection/oracle review, including an independent Rust pass; no tests or randomized sweeps run.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ad6eb353d5
ℹ️ 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".
| reader.dropped.extend(range.start..range.end.min(GROUPS)); | ||
| } |
There was a problem hiding this comment.
Check expected drops against the reference model
For a reader with drop polling enabled, joining the empty track and giving up group 0 without writing it should produce a drop report. However, this loop only rejects reports for already-delivered groups and records everything else; owed() never checks that an expected drop was reported. Consequently, returning Poll::Pending for every drop poll would pass this scenario, including the new regression intended to verify that a replaced copy stops withholding drops. The TypeScript model has the same omission. Track expected drop dispositions independently and assert their eventual delivery after settling, so the model detects missing reports as well as invalid ones.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed this is a real gap, but asserting expected drops brings its own timing rules (budgets, a drop not being final, late fills), so it is a follow-up listed in the PR body rather than part of this PR.
(Written by Claude Opus 5.5)
|
Addressing the reviews on
(Written by Claude Opus 5.5) |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 17bed1d2fa83214b02a8af84e4fdc825a5148f25, delta since ad6eb353d5a661bea404dc37a9d7c1e442d85556.
The prior Rust oracle P2 is fixed: winner() matches the route-cost ordering, and the expanded regression keeps lower groups owed across standby changes. The JS part of the same finding remains an explicitly documented coverage limitation: adding/removing any route still advances the exemption without observing that reader's serving source. This is not a new finding.
The previously dropped JS FETCH-refusal recovery wait is restored, with group-change listener cleanup. The body now accurately states that the model does not force this refusal. A targeted refusal-then-subscription-arrival regression and the acknowledged per-reader versus aggregate-budget issue remain follow-ups. The independent missing-expected-drop coverage finding (#6947 (comment)) is likewise unchanged, not duplicated here.
Direction: the narrower Rust oracle and restored recovery path are improvements, and explicitly bounding the JS model's claim is appropriate. No additional actionable bugs found in this delta.
Verification: GitHub-only static delta and surrounding-code review, including an independent pass. No tests, randomized sweeps, or live failover experiments run.
|
Landing summary (/quest-merge):
(Written by Claude Opus 5.5) |
* chore(quest): claim quest/m1/resume-model-test.md Quest-Claim: 7bff411939571787 * test(net): check resume against a model of one logical track A seeded proptest drives an origin's fronts through route flips, route failures, give-ups, ranges, cache expiry, a newer epoch, and reader joins and leaves on the simulated clock, and checks every reader's groups, frames, drops, and end against a plain cursor on the logical track. Fixes what it found: - An ordered reader never ended once a finished track left nothing in its range, or the rest of the range was given up. - A drop poll with local bounds below the subscription's floor built a reversed span and panicked. - A resumed reader held a drop back for a replaced copy kept only to back a group still read, and was not woken once that read finished. * test(net): check JS resume against the logical-track model Mirror rs/moq-net's resume model as a fast-check property over the same ops, constants and checks, seeded by MOQ_RESUME_SEED and sized by MOQ_RESUME_CASES (default 64). It found three bugs, each fixed with a regression case: - A replacement copy's refusal ended a group the serving route still delivered; it is a verdict only once that route won't land it either. - A failed copy's cache eviction released a group recovering from another copy; only the copy a mirror reads from reclaims it. - A reported drop was final, so a group a later copy delivered was never handed out; the report is now once and not final. * ci: sweep the resume model nightly from a fresh seed * test(net): answer route fetches in the resume model A real route forwards a fetch for a group it lacks to the publisher. The model's routes refused every fetch, so a group continued across a failover read NotFound although the instance carried it. * test(net): let model routes hold a tail fill beside the live copy A frame-bounded fetch caches a partial copy of a group, so a later whole-group fetch must still be answered. A group a reader stitched from two copies is complete once any copy wrote its last frame. * fix(net): keep a recovered group when its old copy is let go Releasing a copy's subscription released every mirror it cached, including a group recovery had already moved to another copy, which then ended cleanly short of its frames. The JS model's routes now answer fetches, which exposed it. * quest: finish resume-model-test, plan delivering late groups below the resume point The maintainer kept the resume point policy on #6947; delivering the late lower groups is a nice-to-have follow-up in m2. * test(net): request the model's broadcast through the renamed origin API * test(net): resume readers only on a route Up or Down actually changes * test(net): restore Recover's refusal wait, and resume Rust readers only when the cheapest route changes The main merge took main's whole resume.ts and dropped the PR's Recover hunk. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
17bed1d to
a9dc743
Compare
Resume is now checked against a model. A seeded randomized test drives an origin's fronts in Rust (
proptest) and@moq/net(fast-check). It covers route flips, route failures, give-ups, ranges and local bounds, cache expiry, a newer publisher epoch, and readers joining and leaving, all on the simulated clock. Every reader's groups, frames, drops, and end are checked against what one plain cursor on the logical track would see.Quest: quest/m1/resume-model-test.md
What the model checks
MOQ_RESUME_SEEDandMOQ_RESUME_CASESchoose the cases. The merge gate runs a fixed 64 cases (about 0.2 s in Rust, 0.3 s in JS).just test resume-modelsweeps 4096 cases from a fresh seed in both languages and is added to the nightly matrix. Each failure the sweep found is kept as an explicit shrunk regression case. Locally, 51 Rust seeds and 29 JS seeds pass at 4096 cases each.Bugs it found and fixed
Rust (
rs/moq-net):TrackState::poll_next_in_range).poll_dropbuilt a reversed span when local bounds sat below the subscription range's floor, and panicked inBTreeMap::range. A relay forwarding drops for a resumed reader could reach this through a peer's ranges plus a route flip.Copy::feeds).JS (
js/net):Recover). The model does not force this refusal: its routes answer a fetch for any group another route already wrote, so no case reaches it.#evict).track.ts, newreadsMirroredhook).Choices made without asking
requests_route/publishwith epochs, rather thanresume::Producerdirectly, so the epoch rule and the front's park/end decisions are covered too. It uses only epoch routes and lives in its ownmodel/resume_model.rs, so the route-epochs rework does not collide with it.Decisions
resumedexemption advances only when a route change can move the reader: in Rust, when the instance's cheapest live route changes; in JS, when an Up or Down actually adds or removes a route. Repeated, absent, and (in Rust) standby route changes keep lower groups owed, whichunchanged_routes_keep_groups_owedchecks in both languages.resumed). Delivering those late lower groups as a plain cursor would is a nice-to-have follow-up, planned here as Resume delivers late groups below its resume point at low priority in m2.Quests
quest/m1/resume-model-test.md, which this PR finishes, and points the two prose mentions intime-addressing/model.mdandresume-redesign.mdat the model test's files.[M], last in m2's Required list.Impact
track::Consumer::poll_groupand the resume internals are crate-private.moq-netand@moq/netgainproptestandfast-checkas dev-dependencies.Follow-ups
cappedandparkedrules for it.TooFarBehind, and evicted groups answer fetches withNotFound; Rust keeps an open group readable.regressions(), and mirror moq-dev/moq#7097'sa_refused_group_waits_for_its_own_readers_graceinjs/net/src/resume.test.ts.ad6eb353d5). Today a reader that never reports a drop passes both models.Recover's refusal wait with a case that refuses a FETCH while the serving subscription can still deliver the group.origin.ts, overlaps the route-epochs rework).(Written by Claude Opus 5.5)
🤖 Generated with Claude Code