I have a very weird case where my ASP.NET Core Web API endpoint fails randomly due to CancellationToken being cancelled. I have that problem only when I run integration tests, the API works completely fine when I call it from Postman.
In order to understand what's going on, I'd like to know what causes the CancellationToken to have its state set to cancelled. Is there any way to enable some logging for that or any other solution?
The CancellationToken comes from the ASP.Net Core Web API action, it's created by the framework iteself.
// EDIT
Here's my action:
public async Task<ActionResult<TokenResponse>> GetToken(
[Required][FromBody] Parameters parameters,
ApiVersion apiVersion,
CancellationToken cancellationToken) { ... }
My application just passes that token through various layers.
From pure technical standpoint you can leverage cancelation callback (if you are the owner of CancellationTokenSource do not forget to call TryReset on it in case it is reused). For example:
CancellationToken cancellationToken = ...;
cancellationToken.Register(() => throw new Exception("Here")); // exception with stack trace will be thrown
Though not sure that this information will be as useful as you possibly expect.
Related
When I cancel my request from browser to a HTTP Trigger it does not cancel and continues execution when hosted on Azure.
My function example:
[FunctionName("Test")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
CancellationToken cancellationToken,
ILogger log)
{
var allCancellationTokens = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, req.HttpContext.RequestAborted);
await Task.Delay(30000, allCancellationTokens.Token);
if (allCancellationTokens.IsCancellationRequested) {
log.LogInformation("Request was cancelled!");
return new StatusCodeResult(499);
}
return new OkObjectResult("Complete");
}
I have been testing the cancellation through Postman and axios cancel tokens.
It works when I run the project locally and cancel it, but does not seem to cancel once it's published to azure.
My expected result would be that it throws an OperationCanceledException if I cancel the request during the await Task.Delay(30000, allCancellationTokens.Token); However when checking logs on Azure Functions, just seems to carry on execution and complete the function.
Steps to reproduce:
Create a HTTP Trigger like the one defined above.
Publish the app to Azure.
Open the functions logs on Azure in the monitor section.
Using Postman, send a request to this function and cancel the request (within the timeout period of 30 seconds).
Won't cancel the request and will still execute.
CancellationToken is helpful for implementing graceful shutdown of your Functions runtime host when it is notified by the Operating System that something has gone wrong to terminate it, please see the following answer for more: https://stackoverflow.com/a/63439515/528779
I have a .NET core dependency that is scoped to each REST API request. It is added in Startup.ConfigureServices with a call to AddScoped.
I want to add cancellation support to this. If I add a CancellationToken cancellationToken to any controller action's parameters I can get a token that is cancelled if the client-side request is. I can then pass that token to all the methods on my dependency.
However, the dependency is scoped to the request, so passing the token down through the action to the methods feels unnecessary - could I just add the CancellationToken to the scoped dependency somehow?
could I just add the CancellationToken to the scoped dependency somehow?
Well, technically yes. i.e. by injecting IHttpAccessorand accessing HttpContext.RequestAborted property, which is the same cancellation token you usually get passed into the controllers action if defined.
But using the action parameter overload is actually kinda discouraged as in every controller action you can access the cancellation token via HttpContext.RequestAborted and having it in controllers action kinda makes the token public, i.e. when creating Swagger scheme (at least was the case back in 2017), where as using the HttpContext itself didn't expose it to the public.
The only exception to that seems to be, when using "Poco Controllers" which don't inherit from Controller or ControllerBase and not injecting IHttpAccessor into this controller.
But injecting cancellation tokens into arbitrary services is problematic as you get a hard dependency on the web framework (IHttpAccessor/HttpContext).
It's best and cleanest to keep having a CancellationToken parameter on your methods which can be cancelled in a meaningful way and make the token optional, so you can only pass the parameter in situation where you have a request or a situation that can be cancelled
public Task<Result> ProcessSomething(string param1, CancellationToken cancellationToken = default)
{
}
On my controllers actions, I can insert a CancellationToken parameter and the platform will fill it so I can implement cooperative cancellation. How can I configure this token? I would like to set its timeout to 10sec for example (pref in a global middleware)
I'm trying to do impersonation in a .NET Core 2.1 Web-API. So this Web-API calls another Web-API using HttpClient and I need the user that called the first one to also be the one who is executing the second one.
The same scenario does work from another Web-API running with the full framework with this call:
((WindowsIdentity)_httpContextAccessor.HttpContext.User.Identity).Impersonate()
Since Impersonate() is not available in .NET Core 2.1 I searched for some samples with WindowsIdentity.RunImpersonated and tried different versions of code similar to this:
WindowsIdentity identity = (WindowsIdentity)m_contextAccessor.HttpContext.User.Identity;
HttpClient client = new HttpClient(new HttpClientHandler { UseDefaultCredentials = true });
await WindowsIdentity.RunImpersonated(identity.AccessToken, async () =>
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
var response = await client.SendAsync(request);
});
This throws an error at client.SendAsync and the error message is this:
A call to WSALookupServiceEnd was made while this call was still
processing. The call has been canceled --->
System.Net.Http.HttpRequestException
Start of the Stack Trace:
at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port,
CancellationToken cancellationToken) --- End of inner exception
stack trace --- at
System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port,
CancellationToken cancellationToken) at
System.Threading.Tasks.ValueTask`1.get_Result() at
System.Net.Http.HttpConnectionPool.CreateConnectionAsync(HttpRequestMessage
request, CancellationToken cancellationToken)
Has anyone else seen this error or has any insight on how to solve this? I tried different versions of code for calling RunImpersonated with the HttpContext user and all lead to the same error.
Thanks for any input
Starting with .NET Core 2.1, the SocketsHttpHandler class provides the implementation used by higher-level HTTP networking classes such as HttpClient.
try to disable this feature and see if the exception is gone.
Even though this error is super vague, I'm pretty sure that this issue is related to the server refusing to connect to the endpoint, OR it can't find the endpoint. My issue was that the endpoint was not visible by the webserver - and FYI, don't use "localhost:xxxx" to point to a local port. Instead, use the server full IP address in your config. localhost doesn't always resolve in local DNS.
TLDR: make sure your webserver can ping the endpoint server and port with authority.
await WindowsIdentity.RunImpersonated(identity.AccessToken, async () =>
{
//this should give you the impersonated user
var impersonatedUser = WindowsIdentity.GetCurrent().Name;
var client = new HttpClient(new HttpClientHandler { UseDefaultCredentials = true });
var request = new HttpRequestMessage(HttpMethod.Get, url);
var response = await client.SendAsync(request);
});
I am trying to access the Request property in my ApiController-derived class.
For some reason, Request is null in ExecuteAsync method. I've seen the other questions, so before you ask:
I am not initializing the controller by calling the constructor, it's a regular HTTP POST API call from an external device.
I've tried the same request locally with Fiddler, the behavior is identical.
I am not unit testing.
Before hitting the ExecuteAsync method, my request passes through a delegating handler, and in the delegating handler, my request object exists (I even add some properties without any problem).
At the last line of delegating handler, I call return await base.SendAsync(request, cancellationToken); without a problem and request exists.
Right after than in API controller, HttpContext.Current.Request is not null and accessible without problem.
In API controller, RequestContext is not null and accessible without problem.
In that same line, Request is null.
Why would this occur? I'm on Web API 2.2 (and MVC 5, if relevant).
This is probably due to the fact that you're trying to access HttpContext while working with async/await.
So, you have two options:
Access the request via the ExecuteAsync method 'HttpControllerContext' parameter - controllerContext.Request.
Make sure your web.config is targeting .NET 4.5 and update appSettings with aspnet:UseTaskFriendlySynchronizationContext set to true.
You can read more here - Using HttpContext Safely After Async in ASP.NET MVC Applications.
To better understand what's going on under the hood, I'd recommend:
Understand what is SynchronizationContext - ExecutionContext vs SynchronizationContext
Understand how it is related to ASP.NET - Understanding the SynchronizationContext in ASP.NET.
In a very very high level: in a synchronous server implementations, where the entire request is processed by the same thread, the execution context is stored using TLS (thread local storage), means HttpContext is available anywhere in your code.
In an asynchronous server implementation (async/await), the request may be processed by several threads, and there's a need to pass the execution context between those threads.