embedio icon indicating copy to clipboard operation
embedio copied to clipboard

EmbedIO HttpListenerMode doesn't detect client disconnects

Open ketodiet opened this issue 5 years ago • 8 comments

A quick overview: A number of client apps connect via a rev proxy to a service. The rev proxy is an ASP.NET app and the service is a c# windows service using EmbedIO to handle incoming requests. Client apps have two connections each: one for pushing updates and the one polling for changes. The issue described here is related to the polling.

The Bug: When using Microsoft HttpListenerMode when a client disconnects & the rev proxy removes the connection in response, the next write on the poll connection will fail (i.e. the service will fail to send any updates or heartbeat to the client via the rev proxy).

When using EmbedIO HttpListenerMode in the exact same scenario, the service writes keep succeeding with no errors reported at all. Even if the rev proxy process is terminated (i.e. stopping the IIS server hosting the rev proxy) the writes keep working with no exceptions thrown.

That feels like a bug to me.

ketodiet avatar Feb 18 '20 21:02 ketodiet

Hello @ketodiet, thanks for using EmbedIO!

The behavior you describe feels like a bug to me too.

Which type of module do you use for the polling? Looks like WebSocketModule from what you write, but I'd like to be sure.

rdeago avatar Feb 18 '20 22:02 rdeago

Hi Riccardo, I'm not using websockets. The WebServer setup is:

_PublicAPI = new WebServer(o => o .WithUrlPrefix($"http://*:{port}/") .WithMode(HttpListenerMode.Microsoft) .WithSupportCompressedRequests(true)) .WithModule(new ActionModule(ctx => APIs.PublicAPI.Handle(ctx)));

Changing .WithMode from HttpListenerMode.Microsoft to HttpListenerMode.EmbedIO changes the behaviour as described in the bug.

In the case of HttpListenerMode.Microsoft I always get an System.Net.HttpListenerException exception when attempting to write on a closed connection.

ketodiet avatar Feb 18 '20 22:02 ketodiet

Hi @ketodiet, sorry for the delay. From what I could see from the source code, there are actually some flaws in connection management. I've begun an almost complete refactor of that part of EmbedIO, but in doing so I had to introduce a handful of breaking changes, so I'm afraid you'll have to wait for version 4 to see this issue (hopefully) fixed.

rdeago avatar Apr 23 '20 14:04 rdeago

Hi Riccardo,

We're experiencing the exact same issue. Do you have any idea when we may see v4?

simontuffley avatar Oct 21 '21 20:10 simontuffley

@ketodiet, @simontuffley can you give me an example of code for continuous writes on HTTP connection?

radioegor146 avatar Oct 29 '21 22:10 radioegor146

I confirm that there is such disconnection behavior. the code to repeat the error is simple:

[Route(HttpVerbs.Get, "/test")]
    public async Task Test()
    {
        try {
            byte[] dataBuffer = new byte[512];
            using (var stream = HttpContext.OpenResponseStream()) {
                while (true) {
                     await stream.WriteAsync(dataBuffer, 0, dataBuffer.Length);
                     await Task.Delay(1000);
                }
            }
        } catch (Exception e) { Debug.WriteLine($"{e}"); }
    }

It is convenient to receive data using curl command line, as well as terminate connections using ctl-c.

At the same time, in the debugger during debugging, the occurring exceptions associated with the advice are visible, but they are not returned as exceptions to the code of the method from which it was called.

OffTimers avatar Nov 18 '21 16:11 OffTimers

Small changes in the source codes of the library easily solve this problem. The file /src/packages/EmbedIO/Net/Internal/ResponseStream needs to be modified:

remove: internal class ResponseStream : Stream insert: public class ResponseStream : Stream

remove: private readonly bool _ignoreErrors;

insert and rename all _ignoreErrors to IgnoreErrors public bool IgnoreErrors { get; set; } = true;

next:

[Route(HttpVerbs.Get, "/test")]
    public async Task Test()
    {
        try {
            byte[] dataBuffer = new byte[512];
            using (var stream = HttpContext.OpenResponseStream()) {
                if (stream is EmbedIO.Net.Internal.ResponseStream s)
                    s.IgnoreErrors = false;
                while (true) {
                     await stream.WriteAsync(dataBuffer, 0, dataBuffer.Length);
                     await Task.Delay(1000);
                }
            }
        } catch (Exception e) { Debug.WriteLine($"{e}"); }
    }

OffTimers avatar Nov 19 '21 22:11 OffTimers

Another solution to this issue is globally enabling the write errors by configuring the WebServer's Listener object:

server.Listener.IgnoreWriteExceptions = false;
await server.RunAsync();

This method does not require touching the library internal class.

klasyc avatar Jan 05 '23 13:01 klasyc