tokio-quiche: size H3 body_recv_buf to readable length#2547
Open
LynnLi100 wants to merge 2 commits into
Open
Conversation
LynnLi100
force-pushed
the
linlin/body-recv-buf-size
branch
2 times, most recently
from
July 20, 2026 07:10
2cb3c57 to
f748dd7
Compare
H3Driver eagerly allocated a 64 KiB body receive buffer per connection at construction. Make it Option<Limit<BytesMut>>, allocate it lazily on the first body read, and release it once no streams or flows remain, so idle connections hold no receive buffer.
Follow-up to the lazy body_recv_buf allocation: previously the buffer, once first needed, was always allocated at a fixed 64 KiB (BufFactory::MAX_BUF_SIZE). A connection whose body is 10 bytes still paid for 64 KiB on the first body read. Size the allocation to the amount actually readable on the stream, capped at MAX_BUF_SIZE: - Add quiche::Connection::stream_readable_len(stream_id), an O(1) upper bound of unread bytes (recv.max_off() - recv.off_front()), returning 0 when the stream is absent. - In H3Driver::process_h3_data, size both the lazy allocation and the exhausted-buffer reallocation to body_recv_buf_size(stream_readable_len(stream_id)), which clamps to [1, MAX_BUF_SIZE]. Capacity is kept equal to the limit so the split() invariant (spare capacity == remaining_mut) still holds. stream_readable_len is an upper bound on the buffered body bytes (it also counts H3 framing), so we never under-allocate; the cap bounds the worst case exactly as before. Idle/small-body connections now allocate proportionally to their data; large streamed bodies allocate once per driver read-cycle (capped at 64 KiB) instead of reusing a single fixed 64 KiB buffer. Tests: unit test for stream_readable_len; unit tests for the body_recv_buf_size clamp (floor, tracking, cap); driver test asserts the allocated buffer tracks a small body rather than 64 KiB.
LynnLi100
force-pushed
the
linlin/body-recv-buf-size
branch
from
July 20, 2026 14:50
f748dd7 to
08f5fd8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to the lazy
body_recv_bufallocation. Previously, once the buffer was first needed it was always allocated at a fixed 64 KiB (BufFactory::MAX_BUF_SIZE), so a connection with a 10-byte body still paid for 64 KiB on its first body read.This PR sizes the allocation to the amount actually readable on the stream, capped at
MAX_BUF_SIZE.Changes
quiche::Connection::stream_readable_len(stream_id), anO(1)upper bound of unread bytes (recv.max_off() - recv.off_front()), returning 0 when the stream is absent.H3Driver::process_h3_data, size both the lazy allocation and the exhausted-buffer reallocation tobody_recv_buf_size(stream_readable_len(stream_id)), which clamps to[1, MAX_BUF_SIZE]. Capacity is kept equal to the limit so thesplit()invariant (spare capacity == remaining_mut) still holds.Rationale
stream_readable_lenis an upper bound on the buffered body bytes (also counts H3 framing), so we never under-allocate; the cap bounds the worst case exactly as before. Idle/small-body connections now allocate proportionally to their data; large streamed bodies allocate once per driver read-cycle (capped at 64 KiB) instead of reusing a single fixed 64 KiB buffer.Testing
stream_readable_len.body_recv_buf_sizeclamp (floor, tracking, cap).