Skip to content

fix: surface aborted partial cache writers as errors, not clean EOF - #949

Open
stareezy-1 wants to merge 1 commit into
cloudflare:mainfrom
stareezy-1:fix/932-cancelled-cache-writer
Open

fix: surface aborted partial cache writers as errors, not clean EOF#949
stareezy-1 wants to merge 1 commit into
cloudflare:mainfrom
stareezy-1:fix/932-cancelled-cache-writer

Conversation

@stareezy-1

Copy link
Copy Markdown

Summary

Fixes #932

Problem

A streaming partial-cache reader treats the writer-side sender dropping as clean end-of-body:

// wait for more data
if self.bytes_written.changed().await.is_err() {
    // err: sender dropped, body is finished
    // FIXME: sender could drop because of an error
    return None;
}

But the storage contract says a miss handler dropped without finish() is a failed write. When a cache-miss writer is cancelled before EOF and before finish(), the partial reader reports Ok(None), which the proxy converts to HttpTask::Done — the client receives a truncated response that appears complete.

Reproduction: start a streaming write on MemCache, read the partial body, then drop the writer without finish(). The reader returns Ok(None) (clean EOF) instead of an error.

Fix

  1. PartialHit::read() returns an error when the sender drops while the state is still Partial — the writer never reached EOF, so the admission failed.
  2. MemMissHandler::finish() sends the Complete marker before consuming the handler, so a successful admission still yields a clean EOF even when the final write_body had eof=false. The watch value is copied out before send_replace (which takes the watch's internal write lock) to avoid a self-deadlock with the borrow Ref.

After the fix, the reader state machine is unambiguous: Complete state = real EOF; sender drop while Partial = aborted writer (error → failed response, not truncated success).

Tests

test_partial_reader_errors_on_aborted_writer in memory.rs:

Step Verifies
Writer writes partial data; reader consumes it partial read works
Writer dropped without finish()/EOF
Reader's next read_body() returns Err aborted writer is not clean EOF — fails without the fix
test_write_while_read (existing) finish() after non-EOF writes still yields clean EOF

Full crate suite passes (145 tests), cargo fmt --check clean, clippy clean for the touched file.

A streaming partial-cache reader treated the writer-side sender dropping
as clean end-of-body, even though the storage contract says a miss handler
dropped without finish() is a failed write. A cancelled writer therefore
made a partial response look complete (HttpTask::Done) after only a prefix
of the upstream body.

PartialHit::read() now returns an error when the sender drops while the
state is still Partial. MemMissHandler::finish() sends the Complete marker
(borrowing the watch value before send_replace, which takes the internal
write lock) so a successful admission still yields a clean EOF even when
the final write_body did not set eof=true.
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.

Streaming partial cache reads can treat a cancelled cache writer as clean EOF

1 participant