Skip to content

test(server): Assert upload cancellation on the server side - #54

Merged
korya merged 1 commit into
masterfrom
korya-fix-budget-cancel-test
Sep 11, 2026
Merged

korya merged 1 commit into
masterfrom
korya-fix-budget-cancel-test

Conversation

@korya

@korya korya commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Why

Master CI failed twice on TestBudgetCancellationOverHTTP/h2=false/upload=true with "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/http deliberately 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"
Loading

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.

Assertion Before After
Client Do on the aborted upload must return an error (racy) awaited only
Server body read for the aborted upload not observed must end with a non-EOF error
Slot released, other transfer undisturbed, next request admitted asserted unchanged

The 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, since net/http prefers a delivered response over a cancel.

Related:

🤖 Generated with Claude Code

https://claude.ai/code/session_01B4xBvbUJ5Ci1b99X5x8LJL

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
@korya
korya marked this pull request as ready for review September 11, 2026 15:12
@korya
korya merged commit 69e945e into master Sep 11, 2026
25 of 26 checks passed
@korya
korya deleted the korya-fix-budget-cancel-test branch September 11, 2026 15:13
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