Skip to content

Frame known-length cache bodies with Content-Length - #743

Open
zshenker wants to merge 3 commits into
fastly:mainfrom
zshenker:cache-body-content-length
Open

zshenker wants to merge 3 commits into
fastly:mainfrom
zshenker:cache-body-content-length

Conversation

@zshenker

Copy link
Copy Markdown

When a guest streams a Core Cache entry to the client with found.to_stream() or to_stream_from_range(..) in automatic framing mode, production Compute sends Content-Length if the entry's length is known. Viceroy sent Transfer-Encoding: chunked even for entries inserted with known_length that had finished filling. Because of this, guests can't assert Content-Length on cache hits when testing locally.

Cause

CollectingBody::read_range always returns a Chunk::Channel, and Body::size_hint gives up as soon as it sees one. In automatic mode the guest's Content-Length is stripped, so hyper frames the body as chunked.

Change

  • Chunk::Channel gets an Option<u64>: the exact number of bytes the channel still has to yield. It goes down as chunks are read. Body::len and Body::size_hint both use it. Because it's stored per chunk, appending other bodies keeps the total correct.
  • CollectingBody now keeps the expected length from the insert. Each read gets an exact length when the body's length is known, either from known_length or because the body is complete. For a range, that's min(end, len) - start. If the length isn't known yet, the read stays chunked, as before.
  • If a body is truncated, the reader still fails, so hyper cuts off the connection. The client never sees a clean end of response.

Behaviour change to check

Body::known_length() in the guest now returns Some(n) for these cache bodies, because the hostcalls use Body::len(). I haven't checked what production returns here. If production returns None, we can limit this change to size_hint only.

Tests

  • Unit tests for Body: exact hint, the count going down as chunks are read, and append.
  • Unit tests for CollectingBody: full, range and open-ended lengths; unknown length staying unknown until the body completes; an underfilled known-length body failing.
  • New cache-framing fixture and integration tests, core wasm and component, sent through hyper:
    • full read, range 100..=199, open-ended range from 4990, and a finished insert without known_length: each sends Content-Length;
    • a known-length entry read while it's still being written: Content-Length: 5000;
    • an unknown-length entry read while it's still being written: chunked;
    • a truncated known-length insert: the client gets an error;
    • an in-memory prefix with a cache body appended: Content-Length is the sum of both;
    • the guest's known_length().
  • cargo test --all and clippy -D warnings pass. The existing framing tests (framing_headers_are_overridden, manual_framing_preserves_transfer_encoding, content_length_is_computed_correctly) are unchanged.

🤖 Generated with Claude Code

A Core Cache body read via `to_stream()` or `to_stream_from_range(..)` was always
a channel chunk with no size hint, so hyper framed it as chunked even when the
entry's length was known. Production Compute sends `Content-Length` there.

Channel chunks now carry an optional count of the bytes they have left to
yield, which feeds `Body::size_hint` and `Body::len`. `CollectingBody` keeps the
insert's expected length and attaches the exact length of each read when the
body's length is known up front or the body is complete. A body that then
comes up short still fails, so hyper cuts off the connection instead of ending
a chunked response cleanly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zshenker
zshenker requested a review from a team as a code owner October 15, 2026 03:08
zshenker and others added 2 commits October 14, 2026 21:08
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without a known length, the host learns an entry's length only after its
collector task has read the whole body, which happens asynchronously after the
writer finishes. On slower CI runners the guest's lookup won that race, so the
body was still chunked. Re-look-up until the length is known, as the cache
fixture's poll_known_length does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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