-
Notifications
You must be signed in to change notification settings - Fork 574
Fix HTTP streaming Abort() not canceling response reception and add unit tests #5886
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: mconnew <8648390+mconnew@users.noreply.github.com>
Co-authored-by: mconnew <8648390+mconnew@users.noreply.github.com>
Co-authored-by: mconnew <8648390+mconnew@users.noreply.github.com>
|
@copilot, you didn't make any changes to address the problem described in the issue. You only wrote the test that was described in the additional instructions. |
Co-authored-by: mconnew <8648390+mconnew@users.noreply.github.com>
Co-authored-by: mconnew <8648390+mconnew@users.noreply.github.com>
Co-authored-by: mconnew <8648390+mconnew@users.noreply.github.com>
You're absolutely right - I misunderstood the original instructions. I only created the unit test but didn't implement the actual fix for the issue. I'm now implementing the fix to propagate Abort() to the response stream reading operations. |
- Added CancellationToken parameter to HttpResponseMessageHelper constructor - Created GetCombinedCancellationTokenAsync helper to combine timeout and abort tokens - Updated all stream reading operations to use combined cancellation token - Pass _httpSendCts.Token from HttpClientChannelAsyncRequest to enable abort during response reception Co-authored-by: mconnew <8648390+mconnew@users.noreply.github.com>
- Added detailed comments explaining why the linked CTS is not explicitly disposed - Clarified that HttpResponseMessageHelper has short lifetime and CTS will be garbage collected - Fixed CancellationToken default comparison to use CanBeCanceled check Co-authored-by: mconnew <8648390+mconnew@users.noreply.github.com>
- Added _linkedCts and _cachedCombinedToken fields to HttpResponseMessageHelper - Modified GetCombinedCancellationTokenAsync to cache the combined token on first call - This avoids creating multiple linked CancellationTokenSource instances - Improves resource management by creating only one CTS per helper instance Co-authored-by: mconnew <8648390+mconnew@users.noreply.github.com>
|
@copilot, don't create a linked cancellation token source. Do something more like this: var timeoutToken = await _timeoutHelper.GetCancellationTokenAsync();
using (timeoutToken.UnsafeRegister(s_cancelCts, _httpSendCts))
{
_httpResponseMessage = await _httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, _httpSendCts.Token);
}There's no need to create a linked token as we have a callback which will cancel _httpSendCts which can be registered against a new CancellationToken. |
…TokenSource - Changed HttpResponseMessageHelper to accept CancellationTokenSource instead of CancellationToken - Added s_cancelCts callback in HttpResponseMessageHelper (same pattern as in HttpChannelFactory) - Register timeout token callback in ParseIncomingResponse using UnsafeRegister - Removed linked CancellationTokenSource creation and caching logic - Simplified GetCancellationTokenAsync to just return _httpSendCts.Token or timeout token - This matches the pattern already used in SendRequestAsync and avoids resource management issues Co-authored-by: mconnew <8648390+mconnew@users.noreply.github.com>
... Refactored to use the UnsafeRegister pattern as suggested. Changed HttpResponseMessageHelper to accept CancellationTokenSource instead of CancellationToken, and register the timeout token callback in ParseIncomingResponse using |
- Enhanced ObjectDisposedException comment to explain race condition - Renamed 'registration' to 'timeoutCancellationRegistration' for clarity - Improved comment explaining why registration disposal is necessary Co-authored-by: mconnew <8648390+mconnew@users.noreply.github.com>
Co-authored-by: mconnew <8648390+mconnew@users.noreply.github.com>
Fixes the issue where calling
Abort()on HTTP-based WCF channels doesn't cancel in-progress stream reads afterHttpClient.SendAsyncreturns. The abort signal now properly propagates to the response body reader.Problem
HTTP-based transports don't abort an in-progress request if the reply has already started. After
HttpClient.SendAsyncreturns anHttpResponseMessage, callingAbort()on the WCF channel doesn't propagate to the code receiving the response body, causing stream reads to hang until timeout.Solution
The fix follows the exact same pattern already used for the send phase in
SendRequestAsync:Modified
HttpResponseMessageHelper:CancellationTokenSource httpSendCtsparameter instead ofCancellationTokens_cancelCtsstatic callback (same pattern as inHttpChannelFactory)ParseIncomingResponse, registers the timeout token callback usingtimeoutToken.UnsafeRegister(s_cancelCts, _httpSendCts)_httpSendCts_httpSendCts.TokenGetStreamAsync()ReadChunkedBufferedMessageAsync()ReadBufferedMessageAsync()DecodeBufferedMessageAsync()Modified
HttpClientChannelAsyncRequest.ReceiveReplyAsync():_httpSendCts(the CancellationTokenSource) toHttpResponseMessageHelperconstructorAbort()is called,_httpSendCtsis cancelled via the existingCleanup()method_httpSendCtsPattern used (matching SendRequestAsync):
No linked CancellationTokenSource is created, avoiding resource management issues.
Test Implementation
HttpStreamingAbortTests.4.1.0.cswith sync and async variantsCustomBindingwithTransferMode.StreamedResponse, reads partial data (1KB from 500KB stream), callsAbort(), then attempts continued readingCommunicationObjectAbortedException,IOException,CommunicationException, orOperationCanceledException. If broken, hangs untilReceiveTimeoutexpires.Tests use existing
CustomTextEncoderStreamedendpoint. Both synchronous and async patterns covered.Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.