test(server): Assert upload cancellation on the server side - #54
Merged
Merged
Conversation
TestBudgetCancellationOverHTTP/h2=false/upload=true required client.Do to fail after cancellation. Over HTTP/1.1 that is not guaranteed: the client's TLS close sends close_notify before the socket closes, the upload handler reads that as body EOF and answers 200 in the gap, and net/http returns a response that races cancellation. Commit 1456257 closed the clean-EOF route to the same outcome and left this one; master CI failed twice on it. Wait for the client to settle without asserting how, and assert instead that the aborted upload's body read on the server ended with an error, never EOF, which holds in every interleaving because the producer is closed with an error and the transport then never writes the terminating chunk. Record the rule in docs/guidelines.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B4xBvbUJ5Ci1b99X5x8LJL
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.
Why
Master CI failed twice on
TestBudgetCancellationOverHTTP/h2=false/upload=truewith "canceled request succeeded", and the earlier patch to the same test did not stop it.The assertion demanded that a client-canceled HTTP/1.1 upload always return an error. The stack does not promise that. The client's TLS close sends a close_notify alert before closing the socket; the upload handler reads that as end of body and, as the spec requires, replies 200 with the byte count;
net/httpdeliberately returns a response that races cancellation. Commit 1456257 removed one route to a complete response (a clean EOF from the test's pipe) and left this one.sequenceDiagram participant C as "client" participant S as "server" C->>S: "POST /nq/upload (chunked), chunk x" Note over S: "handler blocks on the next chunk" Note over C: "ctx canceled → tls.Conn.Close" C->>S: "TLS close_notify" Note over S: "body read → unexpected EOF, ignored" S->>C: "200 OK, body 1" C->>S: "TCP close" Note over C: "roundTrip: response ready vs ctx done → uses the response"Only this subtest is exposed: downloads already hold the 200 and see a short body on cancel, and HTTP/2 cancels the stream with RST_STREAM.
What & how
Assert cancellation on the server: the aborted upload's body read must end with an error, and stop asserting how the client's request settled.
Doon the aborted uploadThe server-side check holds in every interleaving because the test closes the upload producer with an error, and the transport writes the terminating chunk only when the body copy succeeds. A mutation check confirms it: switching the abort to a clean
pw.Close()fails with "aborted upload ended with EOF, want a read error".Production code is left alone. Replying 4xx to a truncated upload would remove the raceable response at the source, but real clients abort uploads at every phase end and never read that reply, so it would trade a test race for client-side error noise. That is a product decision, not part of this fix.
The failure could not be reproduced on demand locally (about 4,000 runs on macOS, with and without the race detector). The mechanism was verified step by step: with the window between close_notify and the TCP close widened by 20 ms, the client transport was observed reading the 200 before its own socket closed, and one probe run returned the 200 with a nil error while the server logged the body read as unexpected EOF.
Other changes
docs/guidelines.md: new quality-bar bullet stating that cancellation is asserted through server-side effects, sincenet/httpprefers a delivered response over a cancel.Related:
🤖 Generated with Claude Code
https://claude.ai/code/session_01B4xBvbUJ5Ci1b99X5x8LJL