2

My application is running ASP.Net MVC 5.2.2 and hosted on the IIS 7 integrated pipeline. I am looking to timeout requests when the duration of the request is over 3 minutes. Setting the httpRuntime.executionTimeout does not help.

I have other ASP.Net applications running in IIS 6 classic mode. Asp.Net automatically throws a ThreadAbortException when the request duration exceeds the httpRuntime.executionTimeout value.

I tried to look into the ASP.Net source code and found RequestTimeoutManager. It basically keeps track of all the HttpRequests initialized and a background task aborts the requests exceeding the time out duration. Is RequestTimeoutManager applicable for ASP.Net MVC Web Api? If not how can something similar be implemented.

Stack Trace for a typical Http Request in my application

   at MyApp.Api.Controllers.Controller.GetDetails(String Id) 
   at lambda_method(Closure , Object , Object[] )
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass13.<GetExecutor>b__c(Object instance, Object[] methodParameters)
   at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)
   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)
   at System.Web.Http.Controllers.ApiControllerActionInvoker.<>c__DisplayClass3.<InvokeActionAsync>b__0()
   at System.Threading.Tasks.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)
   at System.Web.Http.Controllers.ApiControllerActionInvoker.InvokeActionAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
   at System.Web.Http.Filters.ActionFilterAttribute.System.Web.Http.Filters.IActionFilter.ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func`1 continuation)
   at System.Web.Http.Filters.ActionFilterAttribute.System.Web.Http.Filters.IActionFilter.ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func`1 continuation)
   at System.Threading.Tasks.TaskHelpersExtensions.ThenImpl[TTask,TOuterResult](TTask task, Func`2 continuation, CancellationToken cancellationToken, Boolean runSynchronously)
   at System.Threading.Tasks.TaskHelpersExtensions.Then[TOuterResult](Task task, Func`1 continuation, CancellationToken cancellationToken, Boolean runSynchronously)
   at System.Web.Http.ApiController.<>c__DisplayClass3.<ExecuteAsync>b__0()
   at System.Web.Http.Filters.AuthorizationFilterAttribute.System.Web.Http.Filters.IAuthorizationFilter.ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func`1 continuation)
   at System.Web.Http.ApiController.ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
   at System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncInternal(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpMessageInvoker.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Web.Http.Dispatcher.HttpRoutingDispatcher.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.DelegatingHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at MyApp.Common.MyAppExceptionHandler.<>n__0(HttpRequestMessage request, CancellationToken cancellationToken)
   at MyApp.Common.MyAppExceptionHandler.<SendAsync>d__1.MoveNext() in C:\MyApp_source\Components\MyAppExceptionHandler.cs
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)
   at MyApp.Common.MyAppExceptionHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.DelegatingHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Web.Http.HttpServer.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpMessageInvoker.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Web.Http.WebHost.HttpControllerHandler.BeginProcessRequest(HttpContextBase httpContextBase, AsyncCallback callback, Object state)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
   at System.Web.HttpApplication.PipelineStepManager.ResumeSteps(Exception error)
   at System.Web.HttpApplication.BeginProcessRequestNotification(HttpContext context, AsyncCallback cb)
   at System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context)
   at System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)
   at System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)
   at System.Web.Hosting.UnsafeIISMethods.MgdIndicateCompletion(IntPtr pHandler, RequestNotificationStatus& notificationStatus)
   at System.Web.Hosting.UnsafeIISMethods.MgdIndicateCompletion(IntPtr pHandler, RequestNotificationStatus& notificationStatus)
   at System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)
   at System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)
Praveen Reddy
  • 7,295
  • 2
  • 21
  • 43
  • 1
    If using async-await then you can always use a cancellation token with your async code, you could also short circuit the request from a middleware as well. – Nkosi Sep 06 '19 at 02:27

2 Answers2

0

You can use Custom attribute to override timeout value in AsyncManager

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAsyncTimeoutAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException(nameof(filterContext));
        }

        if (!(filterContext.Controller is IAsyncManagerContainer managerContainer))
        {
            throw new InvalidOperationException("Action Filter failed");
        }

        managerContainer.AsyncManager.Timeout = 180000;
        base.OnActionExecuting(filterContext);
    }
}

Or why can't you use [AsyncTimeout(180000)] ?

cdev
  • 5,043
  • 2
  • 33
  • 32
  • Setting the ScriptTimeout, executionTimeout, requestTimeout values does not work in MVC async pipelines. https://stackoverflow.com/a/27342836/1219543 – Praveen Reddy Sep 05 '19 at 19:39
  • Interesting. I changed answer. Maybe it will help you. Or are you looking for IIS side example – cdev Sep 06 '19 at 02:36
0

You can create a race between 2 running tasks, one for the main operation, and another for preferred timeout (e.g 3 * 60,000 ms = 180,000 ms) by the following code :

public async Task<HttpResponseMessage> Get() 
{
    var _task = this.DoTask(2000); //your operation required time
    var _timeout = this.SetTimeout(180000); //equal to 3 minutes
    var _finishedTask = await Task.WhenAny(_timeout, _task);
    if (_finishedTask == _timeout)
        return this.Request.CreateResponse(HttpStatusCode.RequestTimeout); //timeout
    else
        return this.Request.CreateResponse(HttpStatusCode.OK, _task.Result); //Ok
}

private async Task<string> DoTask(int duration)
{
    await Task.Delay(duration);
    return "work results";
}

private async Task SetTimeout(int ms)
{
    await Task.Delay(ms);
}

I have tested with ApiController as base class and works fine. The task which is finished earlier win the race and determines the Api result.

Amirhossein Mehrvarzi
  • 18,024
  • 7
  • 45
  • 70