Enable SocketsHttpHandler cancellation support (dotnet/corefx#27029)
authorStephen Toub <stoub@microsoft.com>
Tue, 13 Feb 2018 21:30:34 +0000 (16:30 -0500)
committerGitHub <noreply@github.com>
Tue, 13 Feb 2018 21:30:34 +0000 (16:30 -0500)
commit8f3ddb7597967ede41801bef7835bec33fc949d9
tree8d0aa8863c4524642e787bdfd6b4f14c9da6c7fd
parente7dc068fac77bac936e50b960ab4d631600635e9
Enable SocketsHttpHandler cancellation support (dotnet/corefx#27029)

* Enable SocketsHttpHandler cancellation support

This change significantly improves the cancellation support in SocketsHttpHandler.  Previously we were passing the CancellationToken around to every method, eventually bottoming out in calls to the underlying Stream which then ends up passing them down to the underlying Socket.  But today Socket's support for cancellation is minimal, only doing up-front checks; if cancellation is requested during the socket operation rather than before, the request will be ignored.  Since HttpClient implements features like timeouts on top of cancellation support, it's important to do better than this.

The change implements cancellation by registering with the CancellationToken to dispose of the connection.  This will cause any reads/writes to wake up.  We then translate resulting exceptions into cancellation exceptions.  When in the main SendAsync method, we register once for the whole body of the operation until the point that we're returning the response message.  For individual operations on the response content stream, we register per operation; however, when feasible we try to avoid the registration costs by only registering if operations don't complete synchronously.  We also account for the case that on Unix, closing the connection may result in read operations waking up not with an exception but rather with EOF, which we also need to translate into cancellation when appropriate.

Along the way I cleaned up a few minor issues as well.

I also added a bunch of cancellation-related tests:
- Test cancellation occurring while sending request content
- Test cancellation occurring while receiving response headers
- Test cancellation occurring while receiving response body and using a buffered operation
- Test that all of the above are triggerable with CancellationTokenSource.Cancel, HttpClient.CancelPendingRequests, and HttpClient.Dispose
- Test cancellation occurring while receiving response body and using an unbuffered operation, either a ReadAsync or CopyToAsync on the response stream
- Test that a CancelPendingRequests doesn't affect unbuffered operations on the response stream

There are deficiencies here in the existing handlers, and tests have been selectively disabled accordingly (I also fixed a couple cases that naturally fell out of the changes I was making for SocketsHttpHandler).  SocketsHttpHandler passes now for all of them.

* Add test that Dispose doesn't cancel response stream

Commit migrated from https://github.com/dotnet/corefx/commit/53be85c2fe473fdea8c001e3d9fd81dd478b858e
27 files changed:
src/libraries/Common/src/System/Net/Http/NoWriteNoSeekStreamContent.cs
src/libraries/Common/src/System/Net/Logging/NetEventSource.Common.cs
src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpResponseParser.cs
src/libraries/System.Net.Http/src/System.Net.Http.csproj
src/libraries/System.Net.Http/src/System/Net/Http/CurlHandler/CurlHandler.CurlResponseMessage.cs
src/libraries/System.Net.Http/src/System/Net/Http/HttpClient.cs
src/libraries/System.Net.Http/src/System/Net/Http/HttpContent.cs
src/libraries/System.Net.Http/src/System/Net/Http/NetEventSource.Http.cs
src/libraries/System.Net.Http/src/System/Net/Http/ReadOnlyMemoryContent.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingReadStream.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingWriteStream.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionCloseReadStream.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthReadStream.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthWriteStream.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/DecompressionHandler.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/EmptyReadStream.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionResponseContent.cs [moved from src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionContent.cs with 70% similarity]
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentDuplexStream.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentReadStream.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentStream.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentWriteStream.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs
src/libraries/System.Net.Http/tests/FunctionalTests/CancellationTest.cs [deleted file]
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Cancellation.cs [new file with mode: 0644]
src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs
src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj