Repository navigation
perf(moq-net): store large frames as sealed segments - #6628
Conversation
Quest-Claim: 28bd1223e73ff93c
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A frame larger than one 1 MiB segment that arrives in pieces is kept as a chain of fixed-size sealed segments instead of one buffer of its declared size. Receiving it allocates at most the segment being written, and the wire ingest returns receive credit as each segment seals, so a frame never needs a matching connection window. Frames within a segment, and whole writes of any size, keep their single buffer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ports segmented payloads onto the lock-free group log: a log Entry stores a Cached frame, and the prefetch sets chains aside on both fill paths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 3 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: 7ed616d
No actionable bugs found in this review. I checked segment allocation/sealing and reader offsets, receive-credit release boundaries, whole/streamed reads through prefetch, compression adaptation, and the lite/IETF egress changes against the current base. There are no prior published findings to carry forward.
Direction: keeping small and whole-written frames as Bytes, with chains only for large incremental writes, is a reasonable way to bound receive-side allocation without changing the public API or wire. The prefetch side table adds complexity, but preserves the common small-frame path; making every frame a chain would impose that cost universally. Coalescing only at whole-frame/compression boundaries is a justified compatibility tradeoff.
Verification limits: static GitHub code and test inspection only; I did not run builds, tests, benchmarks, or concurrency checks. The added segment-boundary, mixed-prefetch, protocol round-trip, and multi-window tests cover the principal changed paths, but their execution and reported performance remain unverified here.
|
@codex review (Written by Claude Opus 5.5) |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
Landing summary: merged main onto the lock-free group log (log entries store the segmented (Written by Claude Opus 5.5) |
Finishes quest/m1/unbounded-groups/segmented-frames.md.
Problem
A completed frame was one contiguous
Bytes, and a declared-size frame preallocated its whole declared size (up to the sessionBudget). Its QUIC receive credit came back only once the whole frame landed, so the connection window had to fit the largest frame. Front eviction and byte holes need frame storage that the cache can account for and evict in pieces.Approach
frame::SEGMENT(1 MiB, crate-private) is the storage unit. A frame larger than one segment that arrives in pieces is stored asPayload::Chain: sealed segments of exactlySEGMENTbytes (the last shorter), so segmentialways holds bytes fromi * SEGMENT. Smaller frames, and any frame written whole, stay oneBytes(Payload::Whole, same size asBytes).Budgetreservesmin(size, SEGMENT)per frame.coding::Readerstops each lease chunk at the open segment's seal and releases the lease's credit there, so an in-flight frame holds at most one segment of the receive window. The frame header is still read as one unit.poll_read_chunk, wire egress throughPayload: Buf). A whole-frame read (read_frame) copies a chain into one buffer, out of line. The group prefetch keeps chains in a separate side table, so reading small frames does the same work as before.Entrystores theCachedframe, and both prefetch fill paths (the lock-free log and the locked state) set chains aside.frame_recv_largesweeps 4 to 32 MiB frames over 1 and 4 streams.memory.rsadds a 4 MiB size and skips cases that would write more than 512 MiB per round.Measured on a loaded shared host (load ~20 to 100), min of 2 runs:
frame_recv_large(before the log merge), budget mode: 4 MiB x 1 stream 2.26 ms -> 0.48 ms, 16 MiB x 1 10.4 -> 5.8 ms, 32 MiB x 4 111 -> 61 ms.frame_recvat 16 to 256 KiB stayed within noise.group_read_frames(after the log merge) against main: 512 to 8192 frames per group sit at 0.81 to 1.12x, within noise on this host.memoryallocations per frame: unchanged count, +8 bytes.Impact
frame::SEGMENT,Payload,Chain, andCachedare crate-private, andtransport::MIN_RECEIVE_WINDOWkeeps its value: the 64 MiB announcement term already exceeded the old 32 MiB frame term, and now outweighs the 1 MiB segment term.lite_frame_of_many_windows_completes), provided the window fits one segment.Choices made without asking
frame.rssweep. Smaller segments mean more allocations per byte, larger ones a bigger allocation and more credit held ahead of the data. Most media frames fit in one segment, so a whole read of them never copies.write_frame, or one write or transport chunk carrying all of it) stays oneBytesat any size instead of being split. Byte holes will have to slice or copy those frames; that is recorded in byte-holes.md.frame::Frame::payloadis a publicBytes. A public chunked payload type would be an API change, so it is out of scope here.Alternatives
Arc<[Bytes]>per frame on the common path.frame::Frame: avoids the copy on a whole read of a huge frame, but breaks the public API in every binding.Checks
just cipasses locally. It was run with the schedule-bench fix from moq-dev/moq#6824 applied but not committed, becausebenches/schedule.rsis broken on main after moq-dev/moq#6809.Follow-ups
just checkon moq-net to pass on main.Requiredsection is now empty.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code