I’ve done some digging through the .NET source (both .NET 4 and 8) and this is what I’ve found. Note that this is based solely upon code review.
Both .NET 4 and 8 trigger a cancellation token when the HttpClient
is disposed. This is meant to cancel pending requests. For example, if the request stream was still being sent to the server it would cancel that.
In .NET 4, the request is eventually handled by the legacy WebRequest
infrastructure. This code registers a callback on the cancellation token passed to it which in turn closes the socket when it’s canceled. This could then cause the socket errors we were seeing if the dispose happened before the request body was read in full.
In .NET 8 we use the SocketsHttpHandler
since the WebRequest
infrastructure is deprecated. It appears that the cancellation token is unused past the point of sending the request body buried within the SocketsHttpHandler implementation, and the token being canceled while streaming the response body would have no effect. I also think it’s safe to conclude that .NET 6 behaves similarly.
All of this is to say that I believe @mdegroux theory is correct. If we delay disposing of the HttpClient
until after the response body is fully streamed then we should avoid the socket exceptions we observed earlier.
However, there are other concerns as well, particularly around connection leaks. In .NET 4 using HttpCompletionOption.RequestHeadersRead
absolutely requires that the underlying HttpResponseMessage
or Stream
be disposed when done. Failure to do so will “leak” the HTTP connection and not allow it’s reuse. When this is combined with the default .NET 4 behavior of limiting to 2 active HTTP connections per HTTP server this could have very detrimental effects. This would occur if the SDK consumer either doesn’t enumerate or otherwise dispose of the IQueryResult<T>
instance that we return.
This is still an issue in .NET 6/8. However, it is mitigated in those versions for two reasons. First, the NET team added a finalizer so that GC will free the connection eventually. Second, the default is unlimited HTTP connections per server.
This means that making the change to .NET 4 is not without a risk of negatively impacting consumers. There may need to be some consideration given to making it opt-in for consumers who have ensured they are disposing correctly.