Describe the bug
I upgraded to Embed.IO 3.1.1 from 2.9.2. When running a test in which I interrupt the client, or a stress test via siege for a controller that returns a stream of data, Embed.IO always writes errors like this:
08:47:31.766 ERR >> [WebServer] [jS5t0ASWu0mxlcYT4GOeVQ] Unhandled exception.
$type : System.IO.IOException
Message :
Unable to write data to the transport connection: An established connection was aborted by the software in your host machine.
InnerException : object
$type : System.Net.Sockets.SocketException
ErrorCode : 10053
Message : An established connection was aborted by the software in your host machine
SocketErrorCode : 10053
NativeErrorCode : 10053
TargetSite : Int32 Send(Byte[], Int32, Int32, System.Net.Sockets.SocketFlags)
StackTrace :
at System.Net.Sockets.Socket.Send(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags)
at System.Net.Sockets.NetworkStream.Write(Byte[] buffer, Int32 offset, Int32 size)
Source : System
HResult : -2147467259
TargetSite : Void Write(Byte[], Int32, Int32)
StackTrace :
at System.Net.Sockets.NetworkStream.Write(Byte[] buffer, Int32 offset, Int32 size)
at EmbedIO.Net.Internal.ResponseStream.InternalWrite(Byte[] buffer, Int32 offset, Int32 count)
at EmbedIO.Net.Internal.ResponseStream.Write(Byte[] buffer, Int32 offset, Int32 count)
at System.IO.Compression.DeflateStream.PurgeBuffers(Boolean disposing)
at System.IO.Compression.DeflateStream.Dispose(Boolean disposing)
at System.IO.Stream.Close()
at System.IO.Compression.GZipStream.Dispose(Boolean disposing)
at System.IO.Stream.Close()
at System.IO.StreamWriter.Dispose(Boolean disposing)
at System.IO.TextWriter.Dispose()
at EmbedIO.ResponseSerializer.<Json>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at EmbedIO.WebApi.WebApiModuleBase.<SerializeResultAsync>d__24`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at EmbedIO.Routing.RouteResolverBase`1.<ResolveAsync>d__8.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at EmbedIO.Routing.RouteResolverCollectionBase`2.<ResolveAsync>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at EmbedIO.Routing.RoutingModuleBase.<OnRequestAsync>d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at EmbedIO.WebModuleBase.<HandleRequestAsync>d__17.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at EmbedIO.ExceptionHandler.<Handle>d__16.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at EmbedIO.WebModuleBase.<HandleRequestAsync>d__17.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at EmbedIO.Internal.WebModuleCollection.<DispatchRequestAsync>d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
at EmbedIO.WebServerBase`1.<DoHandleContextAsync>d__41.MoveNext()
Source : System
HResult : -2146232800
08:47:31.767 ERR >> [WebServer] [jS5t0ASWu0mxlcYT4GOeVQ] Unhandled exception while handling exception.
$type : System.ObjectDisposedException
Message :
Cannot access a disposed object.
Object name: 'EmbedIO.Net.Internal.HttpListenerResponse'.
ObjectName : EmbedIO.Net.Internal.HttpListenerResponse
TargetSite : Void set_StatusCode(Int32)
StackTrace :
at EmbedIO.Net.Internal.HttpListenerResponse.set_StatusCode(Int32 value)
at EmbedIO.HttpResponseExtensions.SetEmptyResponse(IHttpResponse this, Int32 statusCode)
at EmbedIO.ExceptionHandler.<Handle>d__16.MoveNext()
Source : EmbedIO
HResult : -2146232798
To Reproduce
Steps to reproduce the behavior:
The test does not even have to be cancelled early for these errors to show up. I don't know what Siege is doing, but the same test with Embed.IO 2.x does not produce these errors. On the client side siege does not report any errors, so this appears to be harmless, but annoying.
Expected behavior
No errors in the logs.
Desktop (please complete the following information):
As I noted in Slack, these errors seem to occur when the client has disconnected while there are active connections.
This wasn't required in Embed.IO v2, but in v3, it looks like I need to use a pattern like this to catch these errors and ignore them:
try {
...
var responseStream = HttpContext.OpenResponseStream();
await resultStream.CopyToAsync(responseStream);
}
catch (BusinessException)
{
throw HttpException ...
}
catch (IOException e)
{
// ignore it, client probably disconnected or other network problem, client will retry if necessary
// we can re-throw an InternalServerError just in case this is something else, but if the client is gone,
// this throws an error itself, so ignore that too
try
{
throw HttpException.InternalServerError(data: ...);
}
catch (Exception)
{
// ignore
}
}
Can you post the code that you use for EmbedIO v2?
Usually, the ResponseStream is handling the IOException here (https://github.com/unosquare/embedio/blob/master/src/EmbedIO/Net/Internal/ResponseStream.cs#L158).
Same code without that extra catch block.
The code you pointed out only handles IOException inside a Dispose call. As you can see from the stack I posted, this exception is occurring outside of that call -- note the two EmbedIO.Net.Internal.ResponseStream frames here:
at System.Net.Sockets.NetworkStream.Write(Byte[] buffer, Int32 offset, Int32 size)
at EmbedIO.Net.Internal.ResponseStream.InternalWrite(Byte[] buffer, Int32 offset, Int32 count)
at EmbedIO.Net.Internal.ResponseStream.Write(Byte[] buffer, Int32 offset, Int32 count)
at System.IO.Compression.DeflateStream.PurgeBuffers(Boolean disposing)
at System.IO.Compression.DeflateStream.Dispose(Boolean disposing)
at System.IO.Stream.Close()
I found the reason for the change in this behavior.
There is a setting, WebServer.Listener.IgnoreWriteExceptions, to ignore any Write exception from the ResponseStream. EmbedIO v2 by default set this flag to true, but v3 doesn't.
Your solution is to set this flag to true before starting the WebServer.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Thanks for the information about WebServer.Listener.IgnoreWriteException.