Repository navigation
feat(json): a public snapshot group budget, refusing an oversized snapshot before parsing - #7072
Conversation
|
Design change: at the maintainer's request this PR is widened. (Written by Claude Opus 5.5) |
|
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 44 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 5887398a3b941047da5bc92fe7cace8af32b7850.
No actionable bugs found in the inspected changes. Rust rejects oversized snapshots before baseline parsing/replacement, and both implementations check restated snapshots as well as new ones. Delta admission retains the group budget and rollover behavior. The stats test-only limit still exercises the production FrameTooLarge abort path rather than replacing it with a test-specific error.
Direction: exposing the existing snapshot budget consistently is a reasonable, small alternative to constructing transport-sized test payloads or threading a test-only setting through every stats task. The public limit remains capped by the transport cache ceiling.
Verification: GitHub-only static review of Rust/JS encoders, producer integration and changed tests, including an independent Rust-side pass. I did not execute tests, type checks or benchmarks, and did not reproduce the reported CPU savings.
|
Landing via
(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>
* chore(quest): claim quest/m1/quic-api/streams.md
Quest-Claim: 3067217ff870abc5
* wip(quic): namespace moq-quic streams as send, recv, and stream
send::{Stream, Offset, End, Options, Group, Groups, Error}, recv::{Stream,
Lease, Chunks, Error}, and stream::{Id, Dir}. Writes take Buf, a lease moves
its chunks out and holds no borrow, send ends stay queryable until released,
and datagrams chain chunks without copying.
* wip(qmux): use moq-quic send ends and Buf writes
* wip(tokio): namespace the QUIC driver as send, recv, and stream
send::{Stream, Finished, Group, Options, Offset, Error} with finish(self)
typestate and consuming resets, recv::{Stream, Lease, Error} with a lease
that owns its bytes and holds no lock, Buf writes and BufMut reads, and
datagrams that chain a header. quic::web collapses onto one error per
direction. moq-quic only reruns the stuck pass on restore when an out lease
held it off.
* wip(uring): namespace the QUIC driver as send, recv, and stream
* wip(uring): port tests and moq-relay to the namespaced driver
* wip(uring): format
* wip(quic): name the namespaced stream API in the fork notes
* wip(quic): fix a stale doc link
* chore(quest): finish quic-api/streams
* fix(quic): keep terminal stream state across the review's lifecycle gaps
- moq-tokio and moq-uring cache a reset `received_reset` reports, since
moq-quic frees the stream then, so later reads and leases fail with it
instead of panicking.
- moq-uring's web `poll_closed` observes a clean FIN again through an empty
read, instead of a reset-only query that waits forever.
- moq-uring's web `finish` keeps a finished stream's handle when called again.
- qmux resets an unfinished stream on drop before releasing its ID, which
refused the reset.
* fix(quic): size a datagram before taking its payload
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… on moq-dev pairs (#7073) * chore(quest): claim quest/m0/smoke-released.md Quest-Claim: 1a9318c095d636e0 * fix(smoke): hold the Cloudflare probe's track reader The probe dropped its TrackReader, so when the relay rejected an early SUBSCRIBE with DoesNotExist, cloudflare/moq-rs failed to close the track and resolved the subscription as done. The harness then failed the cell instead of retrying the readiness race. Also rewrite the smoke-released quest to the classified runner failures. * fix(smoke): gate the upstream runner on moq-dev pairs only Only moq-dev-rs and moq-dev-js against this checkout's relay fail the Upstream Docker interoperability job; every other pair is reported, as relays.sh treats non-moq-dev relays. A required pair the registry stops pairing fails too. With the Cloudflare probe fix this finishes the quest, so delete it. * fix(smoke): annotate optional runner failures; record the moq-rs close bug Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore(quest): claim quest/m0/nightly/quic-rustdoc.md Quest-Claim: 0a0d799823f82f7f * fix(quic): unbreak moq-quic's all-features rustdoc The fuzz-gated bench map linked to the private SentPackets type, which fails rustdoc under -D warnings. Nightly's features lane already builds workspace rustdoc with --all-features, so no new lane is needed. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore(quest): claim quest/m0/ci/tracing-filter.md Quest-Claim: a26a0b73687228f9 * wip: stop forcing tracing-test's no-env-filter * chore(quest): finish quest/m0/ci/tracing-filter.md * test: keep captured spans enabled beside parallel tests With one dispatcher registered, tracing-core caches a callsite's interest from the thread that first hits it. A parallel test without a subscriber then disabled the `srt` ingest span for the capturing test, so `publisher_reports_a_silent_pid` failed about two runs in three under `cargo test`. A second, no-op dispatcher makes every callsite ask the current thread's subscriber. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore: point quest and rust2 references at moq-dev Both repositories moved from the kixelated account to the moq-dev organization. The flake input keeps the same revision and hash. Generated quest skills refresh with the next quest bump. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore(quest): claim quest/m0/ci/mbx-r2-verify.md Quest-Claim: 9a556a77ba203b39 * quest(m0/ci): record the first mbx 1.28 Nightly in the R2 verify quest The bump's checks passed: every hosted Nightly job writes, macOS refused only an already oversized manifest, and the merge queue shows no E0463. Reuse is still absent on every platform; record the lockfile-identity evidence and narrow the quest to what remains. * ci(platform): install uniffi-bindgen-cpp after the Rust build The Nightly Windows build loaded the install's 124-prediction mbx manifest under its own identity on 2026-10-27 and 2026-10-28, which blocked inheriting the previous lockfile's manifest. Installing after the build lets the build inherit first. Reported upstream as jdx/mr-boxington#869. * quest(m0/ci): record upstream's CRLF cause for the Windows cache miss jdx/mr-boxington#870 traced the Windows miss to CRLF lockfile digests, not the uniffi-bindgen-cpp install, and the macOS build did inherit. * quest(m0/ci): revert the uniffi-bindgen-cpp step move, record settled decisions #870 showed the Windows miss comes from CRLF lockfile digests, not the install order, so platform.yml keeps main's order. Windows now waits on an mbx release with #870 and a pin bump; Linux full history waits on Windows hitting; lockfile churn past 8 states counts under the rebuild item. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…pshot before parsing (#7072) * chore(quest): claim quest/m0/ci/stats-oversize.md Quest-Claim: 4a6d2df5874a8c48 * perf(json): refuse an oversized snapshot before parsing or compressing it * chore(quest): finish quest/m0/ci/stats-oversize.md * feat(json): a public snapshot group budget, refusing a snapshot past it before parsing snapshot::Config gains max_group_bytes (JS maxGroupBytes, no longer internal), clamped to moq-net's cache limit. The encoder refuses a snapshot past it before the baseline parse, so moq-stats' oversize tests lower it to 64 KiB under test instead of building 32 MiB of stats. * chore(json): regenerate the API report for maxGroupBytes Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2353028 to
652ba00
Compare
Problem
quest/m0/ci/stats-oversize.md, found by the profile in moq-dev/moq#6987. The moq-stats oversize tests publish a stats frame past moq-net's 32 MiB group cache and expectFrameTooLarge. Most of their cost was building and serializing 32 MiB of stats in a debug build, before the encoder ever saw the bytes. The snapshot encoder then parsed the oversized snapshot back into its baseline before anything refused it.Changes
This PR was widened at the maintainer's request (see Decisions). It was also rebuilt on main after moq-dev/moq#7046 removed per-record compression, which most of its first version was about.
moq_json::snapshot::Config::max_group_bytes(Rust)with_max_group_bytesbuilder. It defaults tomoq_net::group::MAX_CACHE_BYTES, andEncoder::newclamps it to that limit, since moq-net refuses a larger group anyway.max_group_bytesfield, which only in-crate tests could set.FrameTooLargebefore parsing it into the baseline. This coversupdateand thesnapshot()restate. Before, it parsed first and left the refusal to moq-net on write.@moq/jsonsnapshotConfig.maxGroupBytes(JS): the existing option is no longer@internal. It is clamped the same way, and a snapshot past it throwsFrameTooLarge. The API report is regenerated.moq-stats
config()builds every stats track's snapshot config. Undercfg(test)it lowers the budget totests::MAX_GROUP_BYTES(64 KiB).oversized_pathssizes itself to that budget, so it builds about 17 paths instead of about 8,200.Snapshot::updateis removed. The encoder now refuses past the budget itself, and there is no compression left for the guard to cover. Two comments about compressed frames are updated.Tests:
a_snapshot_past_the_budget_is_refused(Rust and JS, with deltas on and off), andthe_budget_is_clamped_to_the_cache_limit(Rust).Decisions
Chosen without asking, listed for review:
snapshot::Configfield.Configis#[non_exhaustive]and already carriesdeltaswith awith_builder, and JS already hadmaxGroupByteson its snapshotConfig. So this is the API that matches both. The window and stream encoders have no budget knob on main (moq-dev/moq#6987, which adds a private one, is still open), so there was nothing there to match.#[doc(hidden)]or@internal. A caller may want a smaller group budget for reasons beyond tests, and the semantics are simple.MAX_CACHE_BYTES, not rejected. A larger budget would let deltas grow a group past what moq-net holds.cfg(test)constant, not a field threaded throughTask. Threading one through would touch everyTaskliteral and the producer constructors for a test-only value. Every moq-stats test runs under the 64 KiB budget, and the whole suite passes with it.Producerkeeps its own payload check. It also guards frames that did not come from this encoder's budget logic, so it is left as is.Result
User+sys CPU of the debug moq-stats test binary, three standalone runs each:
oversized_frame_ends_the_trackan_oversize_group_leaves_other_groups_readableThat saves about 3.2 s of CPU, close to the quest Goal's 4.5 s estimate for the whole moq-stats cohort.
Impact
snapshot::Config::max_group_bytesandConfig::with_max_group_bytes. JSSnapshot.Config.maxGroupBytesmoves from internal to public.snapshot::Encoder::updateandsnapshot()(Rust and JS) now return or throwFrameTooLargefor a snapshot past the budget. Before, they handed the frame back and moq-net refused it on write. The error and the published state are the same: the previous value stays readable.Validation
just check origin/mainpassed.just ci origin/mainpassed: 4,945 Rust tests plus the JS, lint and docs gates.cargo test -p moq-json -p moq-stats --libpassed (182 + 74 tests).bun test src/snapshotinjs/jsonpassed (69 tests).Review
5887398a3b941047da5bc92fe7cace8af32b7850.Configcallers, and comments.2353028885mergesmaininto the reviewed head without conflicts, so it needs no new review.just check origin/mainpassed on it.Follow-ups
MoqJsonSnapshotConfigdoes not exposemax_group_bytes. Add it if an FFI caller needs a smaller budget.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code