Skip to content

tokio-quiche: size H3 body_recv_buf to readable length#2547

Open
LynnLi100 wants to merge 2 commits into
cloudflare:masterfrom
LynnLi100:linlin/body-recv-buf-size
Open

tokio-quiche: size H3 body_recv_buf to readable length#2547
LynnLi100 wants to merge 2 commits into
cloudflare:masterfrom
LynnLi100:linlin/body-recv-buf-size

Conversation

@LynnLi100

@LynnLi100 LynnLi100 commented Jul 20, 2026

Copy link
Copy Markdown

Summary

Follow-up to the lazy body_recv_buf allocation. 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

  • 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.

Rationale

stream_readable_len is 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

  • 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
LynnLi100 requested a review from a team as a code owner July 20, 2026 06:16
@LynnLi100
LynnLi100 force-pushed the linlin/body-recv-buf-size branch 2 times, most recently from 2cb3c57 to f748dd7 Compare July 20, 2026 07:10
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
LynnLi100 force-pushed the linlin/body-recv-buf-size branch from f748dd7 to 08f5fd8 Compare July 20, 2026 14:50
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