Controller timeout on dotnet core - c#

I have an web api on dotnet core 3.1 and I want to set different timeout specific controller action.I try to create an actionfilter something like below
public class TimeOutAttribute : ActionFilterAttribute
{
private readonly int _timeout;
public TimeOutAttribute(int timeout)
{
_timeout = timeout;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
try
{
var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(_timeout));
await Task.Run(async () => await next(), cts.Token);
}
catch (TaskCanceledException)
{
var request = context.HttpContext.Request;
var message = $"Action exceeded the set timeout limit {_timeout} milisecond for {request.PathBase}{request.Path}";
throw new ActionTimeOutException(message);
}
}
}
and I use it on controller method
[TimeOut(100)]
public async Task<IActionResult> Get()
{
}
Although Get method takes more than 100 ms I can not get exception.Could you see any problem on code or If you have a another options for controller timeout Im ready to try it

Could you see any problem on code
Yes; passing a cancellation token to Task.Run isn't going to work. The token for that method only cancels the scheduling of the task to the thread pool, not the delegate itself.
The only way to cancel your delegate code is to have your delegate take a CancellationToken and observe that (usually by passing it to other methods). I have a blog post series on the subject of cancellation.
If you have a another options for controller timeout Im ready to try it
So, that's a harder problem.
There is built-in support for CancellationToken in ASP.NET Core; you can add a CancellationToken argument to any controller action method. However, this token doesn't have anything to do with timeouts; it cancels if the user request is aborted (e.g., the user closes their browser).
One approach is to add a CancellationToken parameter to your action method and have your filter modify the model binding results, replacing the provided CancellationToken. Something like this should work:
public sealed class TimeoutAttribute : ActionFilterAttribute
{
private readonly TimeSpan _timeout;
public TimeoutAttribute(int timeoutMilliseconds) => _timeout = TimeSpan.FromMilliseconds(timeoutMilliseconds);
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Find the CancellationToken argument passed to the action
var cancellationTokenArgument = context.ActionArguments.FirstOrDefault(x => x.Value is CancellationToken);
if (cancellationTokenArgument.Key == null || cancellationTokenArgument.Value == null)
throw new InvalidOperationException("TimeoutAttribute must be used on an action with a CancellationToken");
// Create a new CancellationToken that will be cancelled if *either* the user disconnects *or* a timeout
using var cts = CancellationTokenSource.CreateLinkedTokenSource((CancellationToken)cancellationTokenArgument.Value);
cts.CancelAfter(_timeout);
// Replace the action's CancellationToken argument with our own
context.ActionArguments[cancellationTokenArgument.Key] = cts.Token;
await next();
}
}
This will work - to an extent. Your host (i.e., IIS) likely has its own timeout, and this timeout is completely separate from that one.

Related

What to return in StartAsync and StopAsync of a hosted service for .NET Core 5?

Trying to roughly follow MSDN, I've added a hosted service after my scoped services in StartUp class.
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IUtilityService, UtilityService>();
services.AddHostedService<StartupService>();
...
}
I've implemented StartAsync like this.
public class StartupService : IHostedService
{
private IServiceProvider Provider { get; }
public StartupService(IServiceProvider provider)
{
Provider = provider;
}
public Task StartAsync(CancellationToken cancellationToken)
{
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
service.Seed();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
I've read a number of articles and blogs but it's above my ability to understand what should be returned at the end of the methods. It seems to work for now but I can clearly see that I'm breaching the idea by not using asynchronous calls and returninig a dummy (not even that at stop!) so I can safely conclude that I'm doing it wrong (although not apparently but I'm sure it's coming to bite my behind in the future).
What should I return in the implementation to ensure I'm "working with" not agains the framework?
StartAsync needs to return a Task, which may or may not be running (but ideally it should be running, thats the point of a HostedService - an operation/task that runs for the lifetime of the application, or just for some extended period of time longer than normal).
It looks like you are trying to perform extra startup items using a HostedService, instead of just trying to run a task/operation that will last for the entire lifetime of the application.
If this is the case, you can have a pretty simple setup. The thing you want to return from your StartAsync() method is a Task. When you return a Task.CompletedTask, you are saying that the work is already done and there is no code executing - the task is completed. What you want to return is your code that is doing your extra startup items that is running inside of a Task object. The good thing about the HostedService in asp.net is that it does not matter how long the task runs for (since it is meant to run tasks for the entire lifetime of the app).
One important note before the code example - if you are using a Scoped service in your task, then you need to generate a scope with the IServiceScopeFactory, read about that in this StackOverflow post
If you refactor your service method to return a task, you could just return that:
public Task StartAsync(CancellationToken)
{
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
// If Seed returns a Task
return service.Seed();
}
If you have multiple service methods that all return a task, you could return a task that is waiting for all of the tasks to finish
public Task StartAsync(CancellationToken)
{
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
ISomeOtherService someOtherService = scope.ServiceProvider
.GetRequiredService<ISomeOtherService>();
var tasks = new List<Task>();
tasks.Add(service.Seed());
tasks.Add(someOtherService.SomeOtherStartupTask());
return Task.WhenAll(tasks);
}
If your startup tasks do alot of CPU bound work, just return a Task.Run(() => {});
public Task StartAsync(CancellationToken)
{
// Return a task which represents my long running cpu startup work...
return Task.Run(() => {
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
service.LongRunningCpuStartupMethod1();
service.LongRunningCpuStartupMethod2();
}
}
To use your cancellation token, some of the example code below shows how it can be done, by Catching a TaskCanceledException in a Try/Catch, and forcefully exiting our running loop.
Then we move on to tasks that will run for the entire application lifetime.
Heres the base class that I use for all of my HostedService implementations that are designed to never stop running until the application shuts down.
public abstract class HostedService : IHostedService
{
// Example untested base class code kindly provided by David Fowler: https://gist.github.com/davidfowl/a7dd5064d9dcf35b6eae1a7953d615e3
private Task _executingTask;
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
// Create a linked token so we can trigger cancellation outside of this token's cancellation
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
// Store the task we're executing
_executingTask = ExecuteAsync(_cts.Token);
// If the task is completed then return it, otherwise it's running
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
// Signal cancellation to the executing method
_cts.Cancel();
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
// Throw if cancellation triggered
cancellationToken.ThrowIfCancellationRequested();
}
// Derived classes should override this and execute a long running method until
// cancellation is requested
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
}
In this Base Class, you will see that when StartAsync is called, we invoke our ExecuteAsync() method which returns a Task that contains a while loop - the Task will not stop running until our cancellation token is triggered, or the application gracefully/forcefully stops.
The ExecuteAsync() method needs to be implemented by any class inheriting from this base class, which should be all of your HostedService's.
Here is an example HostedService implementation that inherits from this Base class designed to checkin every 30 seconds. You will notice that the ExecuteAsync() method enters into a while loop and never exits - it will 'tick' once every second, and this is where you can invoke other methods such as checking in to another server on some regular interval. All of the code in this loop is returned in the Task to StartAsync() and returned to the caller. The task will not die until the while loop exits or the application dies, or the cancellation token is triggered.
public class CounterHostedService : HostedService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILog _logger;
public CounterHostedService(IServiceScopeFactory scopeFactory, ILog logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
}
// Checkin every 30 seconds
private int CheckinFrequency = 30;
private DateTime CheckedIn;
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
int counter = 0;
var runningTasks = new List<Task>();
while (true)
{
// This loop will run for the lifetime of the application.
// Time since last checkin is checked every tick. If time since last exceeds the frequency, we perform the action without breaking the execution of our main Task
var timeSinceCheckin = (DateTime.UtcNow - CheckedIn).TotalSeconds;
if (timeSinceCheckin > CheckinFrequency)
{
var checkinTask = Checkin();
runningTasks.Add(checkinTask);
}
try
{
// The loop will 'tick' every second.
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
catch (TaskCanceledException)
{
// Break out of the long running task because the Task was cancelled externally
break;
}
counter++;
}
}
// Custom override of StopAsync. This is only triggered when the application
// GRACEFULLY shuts down. If it is not graceful, this code will not execute. Neither will the code for StopAsync in the base method.
public override async Task StopAsync(CancellationToken cancellationToken)
{
_logger.Info($"HostedService Gracefully Shutting down");
// Perform base StopAsync
await base.StopAsync(cancellationToken);
}
// Creates a task that performs a checkin, and returns the running task
private Task Checkin()
{
return Task.Run(async () =>
{
// await DoTheThingThatWillCheckin();
});
}
}
Notice you can also override the StopAsync() method to do some logging, and anything else needed for your shutdown events. Try to avoid critical logic in StopAsync, as its not guaranteed to be called.
I have a service framework that contains many services and I can see each service's status in a web panel as well. So, in my solution:
In StartAsync method, I initialize and start all jobs, so the system waits for the jobs to be finished, after finishing, I return Task.CompletedTask
In StopAsync, I try to stop all jobs and ensure they're stopped successfully, then return Task.CompletedTask

How to cancel the query sent by MediatR?

I'm using MediatR in .Net core 3.1 Blazor application. The following are the query and its handler.
public class GetSaleQuery : IRequest<SaleVm>
{
public GetSaleQuery(string id)
{
Id = id;
}
public string Id { get; }
}
public class GetSaleQueryHandler : IRequestHandler<GetaQuery, SaleVm>
{
public async Task<SaleVm> Handle(GetSaleQuery request, CancellationToken cancellationToken)
{
var q = await _context.Table1
.ToListAsync(cancellationToken).ConfigureAwait(false);
return ...;
}
}
And in the UI part, the following is used to send query request.
async Task SearchClicked()
{
sendResult = await mediator.Send(new GetSaleQuery{ Id = id });
// page will use sendRest to display the result .....
}
Now I need to add a Cancel button to let user to cancel the long running query. How to pass the cancellation token to the query handler GetSaleQueryHandler.Handle()?
async Task CancelButtonClicked()
{
// ?????
}
This is essentially what the cancellation token is there for, if you look at the mediatr Send method you'll see tha it has a cancellation token as an optional parameter:
Task<object> Send(object request, CancellationToken cancellationToken = default (CancellationToken));
You can read more about them here: https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=netframework-4.8
A CancellationToken enables cooperative cancellation between threads, thread pool work items, or Task objects. You create a cancellation token by instantiating a CancellationTokenSource object, which manages cancellation tokens retrieved from its CancellationTokenSource.Token property. You then pass the cancellation token to any number of threads, tasks, or operations that should receive notice of cancellation. The token cannot be used to initiate cancellation. When the owning object calls CancellationTokenSource.Cancel, the IsCancellationRequested property on every copy of the cancellation token is set to true. The objects that receive the notification can respond in whatever manner is appropriate.
So to do what you are asking to do when you run your query you want to return a cancelation token:
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
var result = await _mediator.Send(new Operation(), token);
return source ;
Then when you Cancel you would need to use that cancellation token to, well cancel the operation:
void Cancel(CancellationTokenSource token)
{
token.Cancel();
}
Hope this helps.

Using Custom IHttpActionInvoker in WebAPI for TransactionScope in Async actions

I want to apply TransactionScope for every async controller actions. Rather than doing it in every action I want to do it in a central place so that it would be applicable for all actions. I tried creating a custom IHttpActionInvoker which inherits from ApiControllerActionInvoker
public class ControllerActionTransactionInvoker : ApiControllerActionInvoker
{
public override Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
Task<HttpResponseMessage> result;
result = base.InvokeActionAsync(actionContext, cancellationToken);
scope.Complete();
return result;
}
}
}
then in the Startup class of Web Api replaced default IHttpActionInvoker with the newly created one
config.Services.Replace(typeof(IHttpActionInvoker), new ControllerActionTransactionInvoker());
Now, I can call a controller action and get result but I manually raised exceptions for a series of Db operations and the desired Transaction process does not work. So there is partial work done in DB.
And it does not work at all after hosting the api in Azure api app. It says the controller action was not found.
How to resolve this?
Your scope is completing and being disposed before the controller action is finished executing, as you do not await the response. Try it like this instead:
public class ControllerActionTransactionInvoker : ApiControllerActionInvoker
{
public override async Task<HttpResponseMessage> InvokeActionAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
HttpResponseMessage result = await base.InvokeActionAsync(actionContext, cancellationToken);
scope.Complete();
return result;
}
}
}
While this might well work, you might also want to consider doing this higher up the Web API stack - perhaps in a handler - as you might miss other transactional activity in e.g. handlers, filters, etc. Depends on what you want in and out of scope.

IActionFilter returning a new Task<HttpResponseMessage> never returns

In an ASP.NET Web API project, I have an action filter which checks for model state errors and returns the Bad Request status code if there are any. It looks like this:
public class ValidationFilter : IActionFilter
{
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext context,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
if(!actionContext.ModelState.IsValid)
{
return new Task<HttpResponseMessage>(() =>
actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,
actionContext.ModelState);
}
return continuation();
}
}
Now, for some reason, any request with a model state error never returns. It just hangs there. If I debug, I get to the next filter in the pipeline, which starts with
var result = await continuation();
If I "Step Over" that line, the debugger sort of drops out to "waiting" mode, but no more code seems to be run.
I assume all of this is because I've somehow misunderstood how all these things interact, but despite hours of googling and reading, I still can't figure out how to make this work properly. Any help - both for understanding and bugfixing - is deeply appreciated.
You never start your task. You need to call Start when you use the Task constructor. Instead of calling the constructor and then Start a better option would be to use Task.Run:
if(!actionContext.ModelState.IsValid)
{
return Task.Run(() =>
actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,
actionContext.ModelState);
}
In your case there's nothing really asynchronous about your operation, so you can simply use Task.FromResult to create a task with the result you get synchronously:
public Task<HttpResponseMessage> ExecuteActionFilterAsync(
HttpActionContext context,
CancellationToken cancellationToken,
Func<Task<HttpResponseMessage>> continuation)
{
if(!actionContext.ModelState.IsValid)
{
return Task.FromResult(actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest,
actionContext.ModelState);
}
return continuation();
}

DelegatingHandler setting CurrentPrincipal

I am trying to unit test an implementation of DelegateHandler. My simplified implementation:
public class FooHandler
: DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
Thread.CurrentPrincipal = new GenericPrincipal(
new GenericIdentity("Vegard"), new[] { "A", "B" });
return await base.SendAsync(request, cancellationToken);
}
}
When I try to unit test this, I do it like this:
public class TestHandler : DelegatingHandler
{
private readonly Func<HttpRequestMessage,
CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public TestHandler()
{
_handlerFunc = (r, c) => Return(HttpStatusCode.OK);
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
return _handlerFunc(request, cancellationToken);
}
public static Task<HttpResponseMessage> Return(HttpStatusCode status)
{
return Task.Factory.StartNew(
() => new HttpResponseMessage(status));
}
}
[TestMethod]
public async Task SendAsync_CorrectTokens_IsAuthorized()
{
var message = new HttpRequestMessage(HttpMethod.Get, "http://www.test.com");
var handler = new AuthorizationHeaderHandler
{
InnerHandler = new TestHandler()
};
var invoker = new HttpMessageInvoker(handler);
var result = await invoker.SendAsync(message, new CancellationToken());
Assert.AreEqual(HttpStatusCode.OK, result.StatusCode);
Assert.IsTrue(Thread.CurrentPrincipal.Identity.IsAuthenticated); // fails
Assert.AreEqual("Vegard", Thread.CurrentPrincipal.Identity.Name); // fails
}
My guess is that this happens because HttpMessageInvoker runs the DelegateHandler on a separate thread. Can I force these to be on the same thread?
Can I force these to be on the same thread?
You can't.
A better question is "how do I flow Thread.CurrentPrincipal to whatever thread is executing the request"? There is an answer to this question.
Thread.CurrentPrincipal is odd in ASP.NET. In fact, I recommend you don't use it at all; use HttpContext.User instead. But if you want, you can get it to work by understanding these points:
HttpContext.User is flowed by the ASP.NET SynchronizationContext.
Thread.CurrentPrincipal is overwritten by HttpContext.User whenever a thread enters an ASP.NET request SynchronizationContext.
Unfortunately, your current test is flawed in a couple of key points:
After a request is completed, the value of Thread.CurrentPrincipal is undefined.
The current way you're running your tests, there is no HttpContext (or ASP.NET SynchronizationContext), and this interferes with the flowing of the principal user.
To fully test authorization, you'd need an integration test.
Also see my answer to this question.
What you're actually running into is the behavior of await. Await will reset the principal to whatever it was when you entered the await when you exit the await. So since there is no current principal when you call await invoker.SendAsync, there will be no current principal after you await that call.
However, your test handler should see the right principal. What you could do is have your test handler store the current principal in its SendAsync implementation, expose it as a public property, and then have your test assert that the test handler saw the principal it was supposed to. That should work fine, and that should be the behavior you care about.

Categories

Resources