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 a text/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 like Connection 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 is ECANCELED 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
public class ServerSentEventController : Controller
{
[Route("/event-stream")]
public async Task MyEventStream()
{
HttpContext.Response.Headers.Add("Content-Type", "text/event-stream");
var ct = HttpContext.RequestAborted;
while (!ct.IsCancellationRequested)
{
try
{
await HttpContext.Response.WriteAsync($"data: Hello ({DateTime.Now})\r\r", ct);
await Task.Delay(1000, ct);
}
catch (TaskCanceledException)
{
// If the client aborted the request, don't regard it as an error
}
}
}
}
BTW I know there have been other issues reported previously about EPIPE broken pipe (OS X) and ECANCELED operation canceled (Windows), but those are closed and the above is still a simple repro.