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

[Fixes #2044] Implement an equivalent to AsyncTimeOutAttribute #2083

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/Microsoft.AspNet.Mvc.Core/Filters/AsyncTimeoutAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.Framework.Internal;

namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Used to set the timeout value, in milliseconds,
/// which when elapsed will cause the current request to be aborted.
/// </summary>
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Method,
AllowMultiple = false,
Inherited = true)]
public class AsyncTimeoutAttribute : Attribute, IAsyncResourceFilter
{
/// <summary>
/// Initializes a new instance of <see cref="AsyncTimeoutAttribute"/>.
/// </summary>
/// <param name="durationInMilliseconds">The duration in milliseconds.</param>
public AsyncTimeoutAttribute(int durationInMilliseconds)
{
if (durationInMilliseconds <= 0)
{
throw new ArgumentOutOfRangeException(
nameof(durationInMilliseconds),
durationInMilliseconds,
Resources.AsyncTimeoutAttribute_InvalidTimeout);
}

DurationInMilliseconds = durationInMilliseconds;
}

/// <summary>
/// The timeout duration in milliseconds.
/// </summary>
public int DurationInMilliseconds { get; }

public async Task OnResourceExecutionAsync(
[NotNull] ResourceExecutingContext context,
[NotNull] ResourceExecutionDelegate next)
{
var httpContext = context.HttpContext;

// Get a task that will complete after a time delay.
var timeDelayTask = Task.Delay(DurationInMilliseconds, cancellationToken: httpContext.RequestAborted);

// Task representing later stages of the pipeline.
var pipelineTask = next();

// Get the first task which completed.
var completedTask = await Task.WhenAny(pipelineTask, timeDelayTask);

if (completedTask == pipelineTask)
{
// Task completed within timeout, but it could be in faulted or canceled state.
// Allow the following line to throw exception and be handled somewhere else.
await completedTask;
}
else
{
// Pipeline task did not complete within timeout, so abort the request.
httpContext.Abort();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// A delegate which asyncronously returns a <see cref="ResourceExecutedContext"/>.
/// A delegate which asynchronously returns a <see cref="ResourceExecutedContext"/>.
/// </summary>
/// <returns>A <see cref="ResourceExecutedContext"/>.</returns>
public delegate Task<ResourceExecutedContext> ResourceExecutionDelegate();
Expand Down
26 changes: 25 additions & 1 deletion src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/Microsoft.AspNet.Mvc.Core/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -448,4 +448,7 @@
<data name="ModelType_WrongType" xml:space="preserve">
<value>The model's runtime type '{0}' is not assignable to the type '{1}'.</value>
</data>
<data name="AsyncTimeoutAttribute_InvalidTimeout" xml:space="preserve">
<value>The timeout value must be non-negative.</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#if ASPNET50
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing;
using Moq;
using Xunit;

namespace Microsoft.AspNet.Mvc.Test
{
public class AsyncTimeoutAttributeTest
{
[Fact]
public async Task RequestIsAborted_AfterTimeoutDurationElapses()
{
// Arrange
var httpContext = new Mock<HttpContext>();
var asyncTimeoutAttribute = new AsyncTimeoutAttribute(1 * 1000); // 1 second
var resourceExecutingContext = GetResourceExecutingContext(httpContext.Object);

// Act
await asyncTimeoutAttribute.OnResourceExecutionAsync(
resourceExecutingContext,
async () =>
{
// Imagine here the rest of pipeline(ex: model-binding->action filters-action) being executed
await Task.Delay(10 * 1000); // 10 seconds
return null;
});

// Assert
httpContext.Verify(hc => hc.Abort(), Times.Once);
}

[Fact]
public async Task RequestIsNotAborted_BeforeTimeoutDurationElapses()
{
// Arrange
var httpContext = new Mock<HttpContext>();
var asyncTimeoutAttribute = new AsyncTimeoutAttribute(10 * 1000); // 10 seconds
var resourceExecutingContext = GetResourceExecutingContext(httpContext.Object);

// Act
await asyncTimeoutAttribute.OnResourceExecutionAsync(
resourceExecutingContext,
async () =>
{
// Imagine here the rest of pipeline(ex: model-binding->action filters-action) being executed
await Task.Delay(1 * 1000); // 1 second
return null;
});

// Assert
httpContext.Verify(hc => hc.Abort(), Times.Never);
}

private ResourceExecutingContext GetResourceExecutingContext(HttpContext httpContext)
{
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());

return new ResourceExecutingContext(actionContext, new IFilter[] { });
}
}
}
#endif