Repository navigation
fix(net): a JS copy fills no group its lowered demand floor asks the feed for - #8643
Conversation
…feed for The copy's feed start follows the demand's floor once it moves (`#feedStart`), but `#landing` still clamped what the feed owes by the stale START. A reader whose range lowered the floor below that START then had its groups asked of a lite-07 fill subscription as well as the widened live one, so the publisher served them on two streams. That made copy_fill's yielded-group test see a second open stream about 1 run in 75. 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 16 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. |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
I reviewed the one-commit fix to how a received JS copy decides which groups the live feed still owes, plus its two test changes.
#landingclamps by#feedStart():Producer.#landing(behindmayLand,owedRunsandowedGroup) now uses#feedStart()instead of the rawserved.start. This matches the rule#owesand#fillFlooralready follow: a START bounds the feed only while the demand keeps the floor it answered. So a lowered floor no longer sends a duplicate lite-07 fill for groups the SUBSCRIBE_UPDATE already asked the feed for.- Deterministic regression test:
fill.test.tsrebuilds the exact state from the trace through hooks, with no session and no timing involved. - Flaky test settles its stream list: in
copy_fill.test.ts, the live reader now drains group 1 to its FIN before the history reader starts. These are the same lines #8610 adds.
One side effect isn't mentioned in the description. When the START resolved empty (served.start === null), #landing now clamps to the requested floor, where it used to clamp to 0n. That is the same thing #owes and #fillFloor already do with a null START, so I think it's correct. Just note that "a pending or matching START behaves as before" doesn't fully describe the null case.
claude-opus-5-5 | 𝕏
… a cancelled recovery Answers CodeRabbit's review of #8616: - A recovery the first poll opens asks with the held fills' priority, not the empty ask's lowest (Rust and JS). - A refusal the source did not name lost ends once no held fill covers its groups, so a later request asks again (Rust and JS). - JS: a recovery cancelled after its FIN stops waiting for the publisher's, and a response error resets its stream. - drop_record test: excuse only a single-group duplicate drop, not skips or NotFound. - copy_fill test: exactly one stream serves the archived group now that the fill rides the copy's subscription (maintainer, after #8643). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Problem
js/net/src/lite/copy_fill.test.ts›copy fill: a yielded group the fill's drop names endssometimes saw 2 open streams atexpect(open.length).toBe(1). That brokemain's merge gate (moq-dev/moq#8597, moq-dev/moq#8632). The test was right. The bug is in the code: a received JS copy downloaded the same group twice.In the test, a live reader starts the copy's feed at group 1, so its START is 1. A history reader then asks for
[0, 1). That lowers the demand's floor to 0, and the SUBSCRIBE_UPDATE asks the live subscription for group 0.#feedStart()already says a START only holds while the demand keeps the floor it answered. ButProducer.#landing(behindowedRuns/mayLand) still clamped what the feed owes by the staleserved.start. So the history reader decided the feed did not owe group 0, and asked a lite-07 fill subscription for it too. The publisher was right to serve group 0 on both subscriptions. Whether the live one's stream opened before the test read the list was a race.Trace at the moment the reader asked the fill:
Approach
#landingnow clamps by#feedStart(), the same rule#owesand#fillFlooruse. A START bounds the feed only while the demand keeps its floor. The only case that changes is a moved floor; a pending or matching START behaves as before.fill: a group a lowered floor asks the feed for is not asked of a fillinfill.test.ts. It sets up exactly the state from the trace above, with no session and no timing. It fails every run without the fix.git merge-treeagainst fix(moq-net): bound paused groups and fix the yield and resume review findings #8610's head is clean).Rust needs no change. Its lite-07 copy asks for ordinary fills as ranges on the same SUBSCRIBE (
RangeFillsinrs/moq-net/src/lite/subscriber/fills.rs), so a misjudged fill can't open a second stream.lite/publisher.rsis correct to serve a group on every subscription whose ranges include it.Impact
Validation
main: 2 failures in 150 runs of the test alone, and 17 in 200 with tracing added, which widened the window.copy_fill.test.ts, run 6 at a time to add contention.bun test srcinjs/net: 3238 pass, 0 fail.just ci: pass.Alternatives
RangeFills. That removes this whole class of duplicates, but it is a large redesign and out of scope for a gate fix (see Follow-ups).Follow-ups
toBeGreaterThan(0), preempts every open stream, and says the group "may be served on both the fill and the copy's expanded feed". After this PR, exactly one stream serves it (the live subscription's), so fix(moq-net): bound paused groups and fix the yield and resume review findings #8610 can go back totoBe(1)and drop that comment.copy_fill.test.ts's header says JS fills "by run on a SUBSCRIBE of its own, as itsRecoverydoes". In Rust, only stale-drop recovery gets its own SUBSCRIBE; ordinary fills ride the copy's subscription. Aligning JS with that would rule out duplicate streams by construction.rangescombined with a history range gave a demand of[{}]in a scratch test, which seems to lose the history floor.Caused by
served.startclamp to#mayLand(now#landing) without the stale-START rule#owesalready applied. moq-dev/moq#7793 later routed fill judgment through it.Models
Size: S
Implementer: Claude, claude-opus-5-5, unknown effort
🤖 Generated with Claude Code
(Written by Claude Opus 5.5)
Review
Rewritten by
quest reviewafter each round.Head: 91ee8ca
Base: origin/main at 6aa4f9c
Reviewer: Codex, gpt-6.1-sol, medium effort
Session:
01a1d520-425f-7542-9545-eae3afaf9028, round 1Score: 9/10,
The focused fix consistently uses the current feed floor to prevent duplicate fill requests after demand widens, with sound regression coverage, though tests were not run as instructed.Rounds: 1; findings 0: fixed 0, refuted 0, open 0
Findings: none