Skip to content

HTTP based transports don't abort an in progress request if the reply has already started #5885

@mconnew

Description

@mconnew

If you are using an Http based binding, and you call Abort() on the client channel proxy after the call to HttpClient.SendAsync has returned an HttpResponseMessage, it doesn't propagate aborting of the WCF channel to the code that's receiving the response body. This means if the response body is being read, and something happens where the server stops responding, calling Abort() on the channel doesn't cancel the ReadAsync call on the response stream.

Relying on the binding SendTimeout or the channel OperationTimeout still works as we create a CancellationToken based on the remaining time left in the operation and that will be honored.

Calling Abort() while the request is being sent does get honored as the cancellation token we create for timing out calling HttpClient.SendAsync isn't directly passed, but instead a callback is registered against it which cancels the request wide CancellationTokenSource, the CancellationToken for which is then passed to the SendAsync call. That code looks similar to this:

var timeoutToken = await _timeoutHelper.GetCancellationTokenAsync();

using (timeoutToken.UnsafeRegister(s_cancelCts, _httpSendCts))
{
    _httpResponseMessage = await _httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, _httpSendCts.Token);
}

The callback s_cancelCts will cancel _httpSendCts. The CancellationTokenSource _httpSendCts gets cancelled when Abort is called on the request, so this pattern enables cancelling the call when the timeout hits, or when Abort() is manually called. This code is in System.ServiceModel.Channels.HttpChannelFactory<TChannel>.HttpClientRequestChannel.HttpClientChannelAsyncRequest which is in the file src\System.ServiceModel.Http\src\System\ServiceModel\Channels\HttpChannelFactory.cs

In the ReceiveReplyAsync method, a timeoutHelper is being created and passed along to a helper class/method HttpResponseMessageHelper.ParseIncomingResponse. This needs to be checked/validated, but I believe we could use the same registering of the callback with the CancellationToken from the TimeoutHelper, and pass _httpSendCts.Token to HttpResponseMessageHelper.ParseIncomingResponse instead. The potential things which could cause a problem are if the remaining or original timeout need to be retrieved from the TimeoutHelper in a transitive call inside ParseIncomingResponse, e.g. for formatting a string used in an exception message.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions