Skip to content

Fail TestServer requests as soon as the client cancels - #69106

Open
georgehotca wants to merge 1 commit into
dotnet:mainfrom
georgehotca:georgehotca/fix-5938-testhost-cancellation
Open

Fail TestServer requests as soon as the client cancels#69106
georgehotca wants to merge 1 commit into
dotnet:mainfrom
georgehotca:georgehotca/fix-5938-testhost-cancellation

Conversation

@georgehotca

@georgehotca georgehotca commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fail TestServer requests as soon as the client cancels

  • You've read the Contributor Guide and Code of Conduct.
  • You've included unit or integration tests for your change, where applicable.
  • You've included inline docs for your change, where applicable.
  • There's an open issue for the PR that you are making.

TestServer clients now fail at cancellation instead of waiting for the app.

Description

HttpContextBuilder.SendAsync registers the client's cancellation token, but the registration aborted the server side and nothing else: it fired RequestAborted and aborted the response stream. The task returned to the client stayed pending until the application delegate finished. An application that never checks RequestAborted kept the client waiting past its own cancellation, which is the repro in #5938. Against a real server, HttpClient throws TaskCanceledException the moment the token fires.

The registration now also completes the response task with an OperationCanceledException that carries the token. HttpClient surfaces it as TaskCanceledException, the same as against Kestrel. The application keeps running in the background and continues to see the abort through RequestAborted.

Scope, measured against the issue thread

@Tratcher's rule from the thread: the token governs the duration of SendAsync only, and under ResponseHeadersRead it must never reach the response stream. The change keeps to that. It touches the response task alone, which is what SendAsync awaits; once that task has a result the registration has nothing left to do.

The thread also pointed at content buffering. On current main, HttpClient already passes its token into LoadIntoBufferAsync, so that phase honors cancellation without any change here. I measured four cases with a 1-second token against a 2-second app, on main and with this change:

Case Before After
App delays 2 s, then writes; ResponseContentRead (the issue's repro) TaskCanceledException after 2062 ms TaskCanceledException after 1004 ms
Same app; ResponseHeadersRead returned 200 after 1999 ms, body read then threw TaskCanceledException after 998 ms
Headers flushed at once, body streamed over 2 s; ResponseContentRead TaskCanceledException after 1001 ms unchanged
Same streaming app; ResponseHeadersRead returned 200 at once, full body read later unchanged

Only the pending-response phase changes. Buffering already honored the token, and ResponseHeadersRead never applied it to later reads.

Verification

  • New test ClientCancellationThrowsWithoutWaitingForApplication in TestClientTests. The app signals it has started, then awaits a task it never completes. The client cancels, and the request task must throw OperationCanceledException. Without the fix the test failed at the 5-second guard with TimeoutException; with the fix it passes.
  • The existing ClientCancellationAbortsRequest covers the case where the application observes the token. It is unchanged and still passes.
  • Full Microsoft.AspNetCore.TestHost.Tests run: 150 passed, 0 failed, 0 build warnings.

No public API changes.

Fixes #5938

HttpContextBuilder registered the client's cancellation token only to
abort the server side. The task handed back to the client stayed pending
until the application finished, so an application that never checks
RequestAborted kept the client waiting past its own cancellation. A real
server fails the client's request the moment the token fires. TestServer
now does the same: the cancellation registration completes the response
task with an OperationCanceledException. The application keeps running
and still sees the abort through RequestAborted.

Fixes dotnet#5938
@github-actions github-actions Bot added the area-hosting Includes Hosting label Sep 7, 2026
@georgehotca

Copy link
Copy Markdown
Contributor Author

@dotnet-policy-service agree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-hosting Includes Hosting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TestServer.CreateClient : the HttpClient created didn't support well the cancellation.

1 participant