runtime: Stream returned by HttpResponseMessage.Content.ReadAsStream does not respect readtimeout or cancellationtoken .net framework
Hi This issue is found in net framework .net452.
I have the following sample code for reading data from server:
using(var webresponse = httpclient.SendAsync(request,HttpCompletionOption.ResponseHeadersRead,cancelToken).Configureawait(false)){
using (Stream opStream = await webResponse.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
try
{
opStream.ReadTimeout = 60 * 1000;
do{
await opstream.ReadAsync(buffer,offset,count,cancelToken).ConfigureAwait(false);
}while(...)
}catch(IOException e){//this is where the exception is thrown}
}
}
For some requests it gives following error “Unable to read data from the transport connection: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond”.
After debugging I figured out that this line: “await opstream.ReadAsync” throws this ioexception. Now this error is not the problem.
Problem is it throws this error after 300 seconds which is the default readtimeout of the “WebExceptionWrapperStream.ReadTimeout” even though I have set it as 60 seconds (as shown above).
The cancelToken passed to the ReadAsync is from a cancellationtokensource which is set to cancel after 60 seconds.
Still it timesout after 300 seconds
Is there a way to set the timeout of ReadAsync?
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 1
- Comments: 16 (7 by maintainers)
Closing since this is not a .NET Core issue and the behavior on .NET Framework is currently by-design.
I looked at the repro code that you sent. Thank you for providing that.
The short answer is the .NET Framework does not properly support the cancellation token that is passed into the ReadAsync() method for the response stream that you obtained from the HttpClient APIs. That is why the repro doesn’t stop the I/O after 10 seconds. However, .NET Core does support the cancellation token.
The reason is because the .NET Framework HTTP stack was originally written long before Tasks/Async/CancellationToken semantics. The underlying networking stack is using HttpWebRequest and Begin/End patterns for async operations.
The stream,
opStreamyou obtained from this code:is actually implemented as a System.Net.ConnectStream.
Reference source of System.Net.ConnectStream from .NET Framework: https://github.com/Microsoft/referencesource/blob/master/System/net/System/Net/_ConnectStream.cs
That stream object doesn’t have any actual ReadAsync() method on it. It only has BeginRead() and EndRead(). So, the implementation of that virtual method falls to the base System.IO.Stream class. The default ReadAsync() method on System.IO.Stream() will wrap calls to the stream’s actual BeingRead() / EndRead() methods. The implementation of that code is that it ignores the cancellation token passed into the ReadAsync() method.
The only way to fix this would be for .NET Framework to change to actually implement the overridden ReadAsync() method on the System.Net.ConnectStream class implementation.
So, using a cancellation token won’t work for doing reads on the response body stream in this scenario on .NET Framework. However, the same scenario on .NET Core will work since ReadAsync() is fully implemented with cancellation token support.
@karelz I run a test from a VM where I have 100 threads and on each thread I am downloading 1 1GB file (so across 100 threads i am downloading 100 1 gb files) from our storage. For this case my storage account and the VM are in cross region, that is why I encounter these timeouts. Each request is a read of 4MB, so we send multiple read requests for reading 1 1GB . Although my current SDK is in public, but the httpclient changes are in still in private repo. Let me write up a repro and try to share it. If I cannot share here, I will share it internally.