Skip to content

fix(net): apply a resumed reader's ranges and cursor before each read - #7869

Merged
moq-bot[bot] merged 4 commits into
mainfrom
quest/m1/js-resume-model-12345
Nov 4, 2026
Merged

moq-bot[bot] merged 4 commits into
mainfrom
quest/m1/js-resume-model-12345

Conversation

@kixelated

@kixelated kixelated commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m1/js-resume-model-12345.md: MOQ_RESUME_SEED=12345 MOQ_RESUME_CASES=400 bun test src/resume.model.test.ts (in js/net) passes, fixed at the cause.

Problem

A resumed reader (js/net/src/resume.ts, Reader) applied its subscriber's preferences and group cursor to its route copies only from signal callbacks, which notify a microtask after the change. A read in between judged the copies by the old values:

  • Seed 12345 (Up, Join [0,1), Write g0, Ranges none): the read after the update called exhausted(), which judged the serving copy still bounded at [0,1). It found the window fully read and ended the track cleanly, though the reader now follows live.
  • Default seed and seed 7, 400 cases (… Down, Ranges [1,2)): the narrowed reader handed out buffered group 0 from a copy still bounded at [0,1). This also fails on main.

Approach

Reader.sync(), which every read calls first, now applies both signals to the copies through one #follow(). It mirrors the subscription when it changed, and re-bounds the copies when the cursor changed: backfilling a lowered start and letting go once ended, exactly as the cursor callback did. Both signal callbacks call the same #follow(), so a change is applied once, whichever comes first.

This matches the Rust Reader::sync. That applies the reader's preferences on every poll, and its with_range reaches the reader synchronously under the lock.

Why the cursor too

Mirroring only the subscription, as moq-dev/moq#7736's resume.ts hunk does, fixes seed 12345 but breaks another case, which passes on main (seed 8 and the default seed, 400 cases): an ordered reader whose range widens from [1,2) to [0,1), below group 0 its copy already passed, ends cleanly without group 0. The copy took the new ranges at once, but its local floor (set by #bound from the cursor) stayed at 1. Lowering that floor is what re-offers group 0 from the producer's cache (withRange → reofferCached), so the copy judged [0,1) drained with nothing held. Applying the cursor in the same step closes this.

Tests

resume.model.test.ts regressions gains three minimal cases:

  • seed 12345's: fails on main ("the track ended cleanly though no route declared an end");
  • the narrowing case: fails on main ("group 0 outside cursor 1,2");
  • the widening case: passes on main but fails with only the subscription mirrored ("missing groups 0"), guarding the cursor half.

Validation

  • The quest's command passes: MOQ_RESUME_SEED=12345 MOQ_RESUME_CASES=400 bun test src/resume.model.test.ts in js/net.
  • MOQ_RESUME_CASES=400 passes on seeds 1 to 16, 777, 4242, 12345 and the default 0x6d6f71. MOQ_RESUME_CASES=2000 passes on 12345, 8 and the default.
  • bun test in js/net: 2888 pass, 0 fail.
  • nix develop --command just check origin/main (biome, JS typecheck and tests, markdown, gh, shell, text, toml, nix, justfile) and quest check --base origin/main pass.
  • One failure remains outside this quest, and fails identically on main: seed 7 at 2000 cases, Up i1s1, GiveUp i1s1 g2, Expire, Up i1s2, Join, Write i1s1, Write i1s2 g2, Down i1s2, Leave → "missing groups 1". It involves no range or cursor change. See follow-ups.

Impact

  • Public API: none. Wire: none. A resumed (routed) reader now applies a Subscriber.update() or withRange() to its copies at its next read, rather than a microtask later.

Choices made without asking

Suggested follow-ups

  • The JS resume model's seed 7 at 2000 cases (above): a group given up on one route, cache expiry, then a route failure leave group 1 never handed out. This is pre-existing and unrelated to ranges. It's worth a quest (and a Rust cross-check).

Review

mechanical fix by quest land watch: merged main at ba130ea3d864fd346e6cf0836fb216b18e460794 with the quest merge driver (head 0477d0c)

  • Reviewer: Codex (quest review), gpt-6.1-sol, medium effort, session 01a1a6ff
  • Reviewed head: c3921614d2ced8a47e6aff75b5da6e62e4dbcb39, round 1
  • Findings: none

(Written by Claude Opus 5.5)

kixelated and others added 3 commits November 4, 2026 06:26
A resumed reader's preferences and group cursor reached its route copies
only when their signals notified, a microtask after the change. A read in
between judged the copies by the old ones: a fully read range replaced by
none read as exhausted and ended the track cleanly, and a narrowed range
still handed out a buffered group it left out.

Every read now applies both first, as the Rust Reader::sync does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title The JS resume model passes seed 12345 fix(net): apply a resumed reader's ranges and cursor before each read Nov 4, 2026
@kixelated
kixelated marked this pull request as ready for review November 4, 2026 14:40
@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

Reader now applies subscription and cursor changes to its copies through a shared method, including when the track has ended. Three regression cases cover range changes while a track remains live. The related quest entry and planning document were removed.

Merge Risk: Minimal · up to 0477d

No concrete merge-blocking issue is identified in the reader synchronization change or its regression cases. Merge after normal checks pass.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: applying a resumed reader's ranges and cursor before each read.
Description check Passed The description directly explains the stale-signal problem, the shared #follow() fix, regression coverage, validation, and scope of the changes.
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 2 files.

Finishing Touches
Simplify code
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

Biome (2.5.15)
js/net/src/resume.model.test.ts

Biome could not lint this file: configuration resulted in errors. Check the repository's Biome configuration and plugins.

js/net/src/resume.ts

Biome could not lint this file: configuration resulted in errors. Check the repository's Biome configuration and plugins.

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.

Share
Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 89ad7a66-d0cb-4d78-be56-cbed7d1ee747
Commits

Reviewing files that changed from the base of the PR and between ba130ea and 0477d0c.

Files selected for processing: 4
  • js/net/src/resume.model.test.ts
  • js/net/src/resume.ts
  • quest/m1/README.md
  • quest/m1/js-resume-model-12345.md
Files with no reviewable changes: 1
  • quest/m1/js-resume-model-12345.md

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • Reader.#follow() applies signals at read time — sync() now calls #follow() before judging a copy, mirroring the reader's subscription and cursor synchronously instead of waiting for the signal microtask; both signal callbacks call the same helper, so a change is applied once whichever fires first.
  • Cursor re-bounding shares the helper — a lowered cursor start still backfills the newly covered groups and lets go once ended, matching the old cursor callback's behavior.
  • Three model regressions — pin the seed 12345 clean-end case, the narrowing case, and the widening case that fails with only the subscription mirrored.
  • Quest bookkeeping — deletes the finished quest and its README entry.

I verified MOQ_RESUME_SEED=12345 MOQ_RESUME_CASES=400, resume.test.ts, and track.test.ts pass at c3921614. I also checked the documented seed 7 failure at 2000 cases: it still fails ("missing groups 1") and involves no ranges/cursor update, so #follow is a no-op there; the base fails earlier on a case this PR fixes. It is correctly left as a pre-existing follow-up.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed head c392161 against base 68c72cc. No actionable correctness findings; applying both preferences and cursor bounds before each read is the right direction.

The new regressions exercise the immediate-read window for unbounding, narrowing and backward widening, including the backfill case. The separate seed 7 failure described in the PR remains outside this change.

Verification: GitHub-only static review of the reader, signal lifecycle, model cases and Rust parity. I did not run the tests or independently reproduce the reported seeds.

(Written by review (OpenAI))

@kixelated
kixelated added this pull request to the merge queue Nov 4, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Nov 4, 2026
@moq-bot

moq-bot Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

quest land watch: the merge queue removed this pull request at c392161 because it conflicts with main (merge_conflict).

The land fixer works on it in the background, and the watcher re-enqueues it at the back of the queue once it is fixed or judged flaky, or hands it back here with what it needs.

(written by quest land watch)

@moq-bot
moq-bot Bot enabled auto-merge November 4, 2026 20:00
@moq-bot

moq-bot Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

quest land watch: armed auto-merge on this pull request at 0477d0c, so it joins the back of the merge queue once its required checks pass, with main at ba130ea merged in by the quest merge driver.

(written by quest land watch)

@moq-bot
moq-bot Bot added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit 56b28d2 Nov 4, 2026
8 of 9 checks passed
@moq-bot
moq-bot Bot deleted the quest/m1/js-resume-model-12345 branch November 4, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant