OdeToCode IC Logo

CancellationTokens and Aborted ASP.NET Core Requests

Wednesday, September 12, 2018

When a client closes a connection during a long running web operation, it could be beneficial for some systems to take notice and stop work on creating the response.

There are two techniques you can use to detect an aborted request in ASP.NET Core. The first approach is to look at the RequestAborted property of HttpContext.

if (HttpContext.RequestAborted.IsCancellationRequested)
{
    // can stop working now
}

RequestAborted is a CancellationToken. Another approach is to allow model binding to pass this CancellationToken as an action parameter.

[HttpGet]
public async Task<ActionResult> GetHardWork(CancellationToken cancellationToken)
{
    // ...

    if (cancellationToken.IsCancellationRequested)
    {
        // stop!
    }
    
    // ...
}

And yes, both approaches work with the exact same object.

if(cancellationToken == HttpContext.RequestAborted)
{
    // this is true!
}

But, Why Am I Not Seeing Any Disconnects?

If you are hosted in IIS or IIS Express, the ASP.NET Core Module (ANCM) doesn’t tell ASP.NET Core to abort a request when the client disconnects. We know the ANCM is having some work done for the 2.2 release and moving to a faster in-process hosting model. Hopefully the 2.2 work will fix this issue, too.


Comments
Gravatar Kirk Larkin Wednesday, September 12, 2018
You can also see where the model-binding value is taken from the `HttpContext.RequestAborted` in the source code: https://github.com/aspnet/Mvc/blob/release/2.1/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinder.cs#L28.
Gravatar Wade Walker Wednesday, September 12, 2018
So if on IIS or IIS Express, this is basically useless, at this point? Am I missing something?
Gravatar scott Wednesday, September 12, 2018
@Wade: Yes.
Gravatar Admir Hodžić Wednesday, September 12, 2018
If we host behind Apache or nginx , Will then CancellationTokens catch disconect or abort connection ?
Gravatar scott Wednesday, September 12, 2018
@Admir: Yes, those work as expected.
Stefan Thursday, September 13, 2018
It's not true that the cancellation token is not cancelled properly when running behind IIS. It works just as expected since ASP.NET Core 2.0.
Gravatar scott Thursday, September 13, 2018
@stefan - I have repo steps and the linked GitHub issue that say otherwise.
Gravatar Pure.Krome Monday, September 17, 2018
Heya Scott :) I was having a similar conversation with Andrew Lock about ASPNET.Core + CancellationTokens last year and about this :) ref: https://andrewlock.net/using-cancellationtokens-in-asp-net-core-mvc-controllers/ What was interesting in Andrew's post was how he considered handling the exception that is thrown when a cancellation occurs. Sure, the client might not see it but it's nice to keep things 'under controlled / handled'. Cheers and keep up the awesome work! -PK-
Stefan Wednesday, September 19, 2018
@scott: I wrote a longer comment with examples but it seems to have been lost over the wire (probably due to me moving around the office while on wifi). I'll try to comment again and see if it sticks. Sorry if the short comment seemed rude :) We run code on ASP.NET Core 2.1 (netcoreapp2.1 and before that 2.0 and 1.1) in production and have for some time. The system handles large uploads (500 MB+) and makes heavy use of cancellation tokens to do some cleanup if the client disconnects. After reading your post I had to investigate how it can work for us and not for you. Turns out that the cancellation token is actually cancelled if you read straight from HttpContext.Request.Body and the client disconnects while the read is ongoing. As we handle large files, this is what we do. To make it easier to work with we have a wrapper for doing so called "ClientDisconnectGuard". The exception thrown inside our ClientDisconnectGuard is a System.OperationCanceledException with stack trace: at System.Threading.CancellationToken.ThrowOperationCanceledException() at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.ValidateState(CancellationToken cancellationToken) After this exception is thrown the cancellation token is flagged as cancelled. I have added a POC here: https://github.com/smatsson/ClientDisconnectPoc To test it, clone the repo, start the site and do a POST request to /home/index with a large file as body (I used a file that was ~150 MB). On line 28 in HomeController.cs "token.IsCancellationRequested" will be true. The code will also write log the current datetime and if the token was cancelled or not to C:\temp\log.txt I have tested the POC in IISExpress (VS 15.8.4), IIS (on Windows 10, not sure on version) and Azure app services.
Stefan Wednesday, September 19, 2018
Just awesome formatting above :D Sorry about that.
Gravatar scott Wednesday, September 19, 2018
@Stefan: Awesome find! Thank you for including the level of detail. The formatting is the fault of this blog - apologies.
Comments are closed.