-
Notifications
You must be signed in to change notification settings - Fork 527
"EPIPE broken pipe" and "ECANCELED operation canceled" on event stream requests (+ ObjectDisposedException with HTTPS) #1103
Comments
I tried reproing with a simple non-MVC app and don't get the same results as you. The token fires as soon as I abort the connection on the client side. I'll try with an MVC app next; there might be something up the stack affecting this. |
Same with MVC app - token fires right after I abort the connection on the client side. Can you share more info on your app? Are you using any middleware? |
It's definitely not MVC-specific. See below for a complete repro app that doesn't use MVC.
Might it be that you're just not logging the Complete repro app without MVC using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace WebApplication
{
public class Program
{
public static void Main(string[] args)
{
new WebHostBuilder().UseKestrel().UseStartup<Startup>().Build().Run();
}
}
public class Startup
{
public Startup(IHostingEnvironment env)
{
Configuration = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddEnvironmentVariables()
.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services) {}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole().AddDebug();
app.Map("/event-stream", builder => {
builder.Run(async ctx => {
ctx.Response.Headers.Add("Content-Type", "text/event-stream");
var ct = ctx.RequestAborted;
ct.Register(() => Console.WriteLine("Client disconnected"));
while (!ct.IsCancellationRequested)
{
try
{
await ctx.Response.WriteAsync($"data: Hello ({DateTime.Now})\r\r");
await Task.Delay(1000, ct);
}
catch (TaskCanceledException)
{
// If the client aborted the request, don't regard it as an error
}
}
});
});
}
}
} |
Note that it's worse still if you're using HTTPS, as it throws a To repro, use the code from my previous comment, but change the public static void Main(string[] args)
{
new WebHostBuilder()
.UseUrls("https://*:44300")
.UseKestrel(opts => {
opts.UseHttps(new X509Certificate2("ssl.pfx"));
})
.UseStartup<Startup>().Build().Run();
} ... and put a valid (e.g., self-signed) PFX file called Now when the client aborts the request, the server still doesn't recognise the disconnection soon enough and will continue to try writing to the response, and then you get this
|
Ah, this is 1.0. I should have asked, sorry. I was testing with what we have in dev, which will be 1.1. A few points:
I still can't repro the 5-10 second delay you see between aborting the connection on the client and the |
@SteveSandersonMS This doesn't fix the major issue, but if you want ctrrl+c to kill long running requests sooner, you can lower then ShutdownTimeout. In the case of the client resetting the connection mid chunked request, I think we will always log something (but in 1.1 we've changed this from warning to info level and reduced it to 1 log entry). We'll look into the ObjectDisposedException. |
I might have some more information on issue 1. Tried it on both commit 1.0.0 (07e958d) and the current Dev commit (1a273f5). Sample code:
Now, when I fire a request from the browser to http://localhost:5000, and immediately abort the request in the browser, the console has the following output:
Note that the cancellation isn't noticed until the start of the 3rd loop. The delay value in Task.Delay() does not matter. 1000 ms and 10.000 ms give the same result. |
Thanks for the info @CesarBS / @halter73 / @nphmuller! @CesarBS - the client I'm using is Chome, and by 'aborting request' I mean 'pressing escape'. I think the delay is probably explained (or at least demonstrated) by @nphmuller's comment. @halter73 Cool - thanks for letting me know. By "1 log entry", do you mean "per server process lifetime" or "per aborted request"? The latter would be a bit awkward if there was no way to suppress it, because it would happen for every EventStream connection. What's SignalR going to do about this? |
Per aborted request. That's why it's informational, so hopefully it can be filtered in that way. Bad requests also have a special logging id (17) in the "Microsoft.AspNetCore.Server.Kestrel" logging category. It's possible do do what SignalR does, and have the client send an "abort" message to the server, that can then cause the server to close the event source connection gracefully by completing the Task returned by the middleware. The abort by the client can be triggered by window.on(before)unload. |
Did some more research, and it seems the delay comes all the way from libuv. |
@CesarBS Investigate/close as needed :) |
I was wrong when I thought the The problem is that |
|
Just tested 3104110 with the sample I posted above for issue 1: HttpContext.RequestAborted is only set to cancelled after the 3rd WriteAsync() call to libuv, after a browser (Chrome) initiated abort. The issue still occurs. Should I make a seperated issue for it? Since this issue was used for 2 seperate issues from the beginning it should be at least easier to track. |
@nphmuller Yes, please file another issue. It indeed would make it easier to track. Thank you! |
Is there a correct, supported way to implement long-running requests that are intended to be closed by the client, e.g., for
EventStream
?See the (possibly naive) implementation of
ServerSentEventController
below. This produces atext/event-stream
response. The client-side code keeps open a request to this until the user navigates away, which aborts the request.Issue 1: Client-initiated aborts
Expected: When the client aborts the request, the
HttpContext.RequestAborted
token should fire immediately, and the response closes gracefully.Actual: When the client aborts the request,
HttpContext.RequestAborted
does not fire immediately. After 5-10 seconds, in the console I get an error likeConnection id "0HKUROLPTMNFF" communication error Microsoft.AspNetCore.Server.Kestrel.Internal.Networking.UvException: Error -32 EPIPE broken pipe
on OS X, with no stack trace. On Windows, the error isECANCELED operation canceled
, again with no stack trace. Immediately after this error,HttpContext.RequestAborted
does fire.Note that the errors show up regardless of whether the cancellation token is passed to
WriteAsync
or not.Issue 2: Server-initiated aborts
If, while the request is ongoing, you ctrl+c in the console to terminate the server app, I'd expect it really to terminate (closing the TCP connection to the client at once). However, the app keeps running for another 5-10 seconds, sending more messages to the client during that period.
Appendix: ServerSentEventController
BTW I know there have been other issues reported previously about
EPIPE broken pipe
(OS X) andECANCELED operation canceled
(Windows), but those are closed and the above is still a simple repro.The text was updated successfully, but these errors were encountered: