Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

CancellationToken #5239

Closed
freshe opened this issue Sep 6, 2016 · 27 comments
Closed

CancellationToken #5239

freshe opened this issue Sep 6, 2016 · 27 comments

Comments

@freshe
Copy link

freshe commented Sep 6, 2016

Sorry if this has been asked before but i cant find any documentation on it. Is it possible in MVC6 to get a CancellationToken triggered when aborting an ajax request for example. IsCancellationRequested is always false. From what i understand this was possible in MVC4 and WebAPI, not MVC 5+6?

public async Task<IActionResult> CancelTest(int id, CancellationToken cToken)
{
    Debug.WriteLine("Started");

    for(var i = 1; i <= 10; i++)
    {
        await Task.Delay(3000, cToken);

        if (cToken.IsCancellationRequested)
        {
            Debug.WriteLine("Canceled");
            break;
        }
    }

    return Json(new { data = "CancellationTest", id = id });
}
@benaadams
Copy link
Contributor

Use the httpContext.RequestAborted token?

@freshe
Copy link
Author

freshe commented Sep 6, 2016

Thank you. Same behaviour. I believe if you put a CancellationToken in your action method the model binder binds it to HttpContext.RequestAborted though.

@khellang
Copy link
Contributor

khellang commented Sep 6, 2016

Yeah, using httpContext.RequestAborted is the same thing as taking it as a parameter;

var model = (object)bindingContext.HttpContext.RequestAborted;

@rynowak
Copy link
Member

rynowak commented Sep 6, 2016

some background here..

/cc @Tratcher in case he wants to add more

The RequestAborted token is triggered server-side, by your code. It's intended to be used when the server wants to cancel pending work based on a timeout or some other criteria.

In old webapi, we used to provide a token that fired when the client disconnected, and this is what we model-bound by default. This led to the assumption that the right thing to do was to always use that token, and pass it into every API call that could accept it.

This led to a lot of exception noise in logs for cases that you really can't troubleshoot.

This also led to the wrong semantics in a lot of cases. Would you abort a database transaction if the client disconnects while it's processing?

This is why we bind RequestAborted and why aborts are now triggered in server-side code rather than by the client hanging up.

Use it in good health with this knowledge.

@freshe
Copy link
Author

freshe commented Sep 6, 2016

Thank you for background and clarification. Makes sense to me now.

@barynov
Copy link

barynov commented Sep 12, 2016

Just to clarify. I understood that now CancellationToken is not triggered by a client. But is there any other way to be notified that the client aborted the connection? In my application this is a desired behavior to abandon any further server-side processing when a request is aborted.

@rynowak
Copy link
Member

rynowak commented Sep 12, 2016

/cc @Tratcher @davidfowl - where did we put the client disconnected token?

@khellang
Copy link
Contributor

aspnet/HttpAbstractions#197 ?

@Tratcher
Copy link
Member

@rynowak RequestAborted is ClientDisconnected, we haven't made any effort to separate them. That said, client disconnects can be difficult to detect. If the client disconnects gracefully (FIN), Kestrel won't detect it unless you send multiple writes and the client sends a RST in response. If the client disconnects with a RST first Kestrel should detect that and fire RequestAborted. WebListener currently triggers RequestAborted for FIN or RST.

@rynowak
Copy link
Member

rynowak commented Sep 12, 2016

So is all the information that I've ever given out on this topic false then?

@rynowak rynowak reopened this Sep 12, 2016
@davidfowl
Copy link
Member

@Tratcher We should do the same in Kestrel (i.e. fire the RequestAborted token for both FIN and RST). It's affecting SignalR right now.

@halter73
Copy link
Member

halter73 commented Oct 3, 2016

The order the RST comes in is irrelevant. If a RST is received with or without a prior FIN, RequestAborted will fire. RequestAborted will not fire with just a FIN from the client since the TCP connection is in a half-open CLOSE_WAIT state (i.e. it's still valid to write to the response). Maybe for HTTP we should treat CLOSE_WAIT as disconnected/aborted, but we should investigate to make sure this wouldn't break real clients.

@alanprot
Copy link

Is there any update on this issue? This is really important especially when you are dealing with disaster recovery situations where the majority of the request should not be processed (due to client disconnection). We have a microservice archuiteture and this can actually prevent a "snowball" effect.

@halter73
Copy link
Member

We are currently looking at changing the RequestAborted token for the next release. There's an open PR for this change at aspnet/KestrelHttpServer#1218.

The RequestAborted token will still fire today given failed writes or a RST from the client. In some scenarios, it's possible to send keep alive messages to the client and check the RequestAborted token to more reliably ensure the connection is still alive.

@davidfowl
Copy link
Member

This was fixed in Kestrel.

@RehanSaeed
Copy link

This definately used to work as I tried it when David Paquette wrote a blog post blog post about it. What release was this fixed in, I'm trying the following code with ASP.NET Core 1.1.1 and can't get this to work as expected.

public class Model { public class CancellationToken { get; set; }

[HttpGet("foo")]
public async Task<IActionResult> Foo(Model model, CancellationToken cancellationToken)
{
    await Task.Delay(10000);
    var a = cancellationToken.IsCancellationRequested;
    var b = this.HttpContext.RequestAborted.IsCancellationRequested;
    var c = model.CancellationToken.IsCancellationRequested;
    return this.Ok(); // Set breakpoint here
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
var request = $.get(
  "http://localhost:5000/Bar/foo",
  function(msg) {
    alert("done");
  });
setTimeout(function() {
  request.abort();
  alert('aborted');
}, 
1000);
</script>

@rynowak
Copy link
Member

rynowak commented Apr 29, 2017

@RehanSaeed - can you provide more information about what you mean by used to work? How do you expect the system to behave vs what is it doing?

@RehanSaeed
Copy link

"In MVC 6, uses RequestAborted which from the HttpContext which a CancellationToken so it does work as expected."
David Paquette

However, my code above does not show true for this.HttpContext.RequestAborted.IsCancellationRequested using 1.1.1.

@davidfowl
Copy link
Member

This only works in 2.0 not in 1.x

@madrianr
Copy link

madrianr commented May 2, 2017

Hello,
Please clarify: is it true that cancel a ajax call to a async action in ASP.NET Core is not possible in Version 1.x?

robert

@davidfowl
Copy link
Member

It really has nothing to do with Ajax, there's currently no cancellation token that represents the lifetime of the client. If you want to cancel some sever side operation then you need to create a cancellation token with a timeout and pass that to whatever APIs you call.

@madrianr
Copy link

madrianr commented May 3, 2017

Hi David,

I have the following async Action in my ASP.NET MVC Controller (see below) where I get some SQL as a parameter and execute that query and the user should have the possibility to press a button to cancel the running query if it takes to Long...

can you give me a short example how to cancel the async Action from the client if I press a button please?

public async System.Threading.Tasks.Task<ActionResult> ReadAsync(string queryJson, string optionsJson, [DataSourceRequest] DataSourceRequest request, CancellationToken cancelToken)
        {
            try
            {

                var query = eqService.GetQueryByJsonDict(queryJson.ToJsonDict());
                var sql = eqService.BuildQuery(query, optionsJson.ToJsonDict());

                var result = await Db.QueryAsync<dynamic>(sql, commandType: System.Data.CommandType.Text,cancellationToken: cts.Token);

                return Json(result.ToDataSourceResult(request));
            }
            catch (Exception ex)
            {
                return Json(ex);
            }

        }

@BrianBergh
Copy link

BrianBergh commented Aug 20, 2017

I have just installed VS2017 15.3 and the CORE 2.0 SDK. Created a new ASP.NET CORE 2.0 (CORE) APP, and implemented the CancellationToken in a method, and it still doesn't get triggered. I can even close the browser (Edge), and even then, the HttpContext.RequestAborted (CancellationToken) is not triggered. I've tried with IE, Chrome and Edge, no luck so far!? Any ideas?

## "EDIT!!" - I just figured out, that it actually works with the KESTREL server!
So, this is just an IIS Express issue!

public async Task<IActionResult> About(CancellationToken token)
    {
      try
      {
        ViewData["Message"] = "Your application description page.";
        DateTime time = DateTime.Now;
        await Task.Run(() =>
        {
          double s = -1;
          while ((DateTime.Now - time).TotalSeconds < 10)
          {
            if (HttpContext.RequestAborted.IsCancellationRequested)
              break; // NEVER GETS HIT!
            token.ThrowIfCancellationRequested();
            if (s != DateTime.Now.Second)
            {
              System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss") + " task running....");
              s = DateTime.Now.Second;
            }
          }
        }, token);
        System.Diagnostics.Debug.Print("Task done"); // GETS HIT, EVEN WHEN EDGE IS CLOSED 1 SECOND AFTER THE REQUEST STARTED
      }
      catch (Exception ex)
      {
        System.Diagnostics.Debug.Print(ex.Message); // NEVER GETS HIT
      }
      return View();
    }

@davidfowl
Copy link
Member

Ah! You're using IIS right? There's still an issue there aspnet/AspNetCoreModule#38 (unfortunately)

@BrianBergh
Copy link

Exactly... yes, i was using IIS Express, and yes, it works like a charm with the Kestrel server :)

@Slacquer
Copy link

Slacquer commented Oct 6, 2017

I am using kestrel and the example code above does not work.

Seems FIDDLER was my problem (Im on Win10), not sure what it's doing in the background to mess with this but once it was off, i now get TaskCanceledException, which is just fine.

@mkArtakMSFT
Copy link
Contributor

Thanks for contacting us. We believe that the question you've raised have been answered. If you still feel a need to continue the discussion, feel free to reopen it and add your comments.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests