unosquare / embedio

A tiny, cross-platform, module based web server for .NET
http://unosquare.github.io/embedio
Other
1.47k stars 176 forks source link

Unhandled exception while handling exception #398

Closed rocketraman closed 4 years ago

rocketraman commented 5 years ago

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:

  1. Create an Embed.IO server with a controller that returns a stream of data.
  2. Run a siege test.

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):

rocketraman commented 5 years ago

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
  }
}
geoperez commented 5 years ago

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).

rocketraman commented 5 years ago

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()
geoperez commented 5 years ago

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.

stale[bot] commented 4 years ago

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.

stale[bot] commented 4 years ago

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.

rocketraman commented 4 years ago

Thanks for the information about WebServer.Listener.IgnoreWriteException.