Closed
Description
Background and Motivation
A common customer request is to be able to apply timeouts to their requests. AspNetCore servers don't do this by default since request times vary widely by scenario and we don't have good ways to predict that. E.g. WebSockets, static files, expensive APIs, etc..
- RequestTimeout in ASP.NET Core 3.1 is not availablewhen using InProcess Hosting #23160 - Out-of-proc had request timeouts.
- Kestrel execution timeout support #10079
- [Needs Tests] Add timeouts aspnet/KestrelHttpServer#485 (comment)
- How to specify / modify request timeout aspnet/KestrelHttpServer#611
To provide more control we can provide timeouts via middleware that are configured per-endpoint, as well as a global timeout if desired. These timeouts would link to the RequestAborted CancellationToken and be entirely cooperative. E.g. we won't call Abort when the timeout fires. It's up to the application logic to consume RequestAborted and decide how to handle the cancellation.
Proposed API
Project/Assembly: Microsoft.AspNetCore.Http
namespace Microsoft.Extensions.DependencyInjection;
+ public static class RequestTimeoutsIServiceCollectionExtensions
+ {
+ public static IServiceCollection AddRequestTimeouts(this IServiceCollection services) { }
+ public static IServiceCollection AddRequestTimeouts(this IServiceCollection services, Action<RequestTimeoutOptions> configure) { }
+ // We need to consider how these policies would integrate with IConfiguration, but that may be substantially different from this API.
+ // public static IServiceCollection AddRequestTimeouts(this IServiceCollection services, IConfigurationSection section) { }
+ }
namespace Microsoft.AspNetCore.Builder;
+ public static class RequestTimeoutsIApplicationBuilderExtensions
+ {
+ public static IApplicationBuilder UseRequestTimeouts(this IApplicationBuilder builder) { }
+ }
+ public static class RequestTimeoutsIEndpointConventionBuilderExtensions
+ {
+ public static IEndpointConventionBuilder WithRequestTimeout(this IEndpointConventionBuilder builder, TimeSpan timeout) { }
+ public static IEndpointConventionBuilder WithRequestTimeout(this IEndpointConventionBuilder builder, string policyName) { }
+ public static IEndpointConventionBuilder WithRequestTimeout(this IEndpointConventionBuilder builder, RequestTimeoutPolicy policy) { }
+ public static IEndpointConventionBuilder DisableRequestTimeout(this IEndpointConventionBuilder builder) { }
+ }
+ namespace Microsoft.AspNetCore.Http.Timeouts;
+ public class RequestTimeoutOptions
+ {
+ // Applied to any request without a policy set. No value by default.
+ public TimeSpan? DefaultTimeout { get; set; }
+ public RequestTimeoutOptions AddPolicy(string policyName, TimeSpan timeout) { }
+ public RequestTimeoutOptions AddPolicy(string policyName, RequestTimeoutPolicy policy) { }
+ public bool TryGetPolicy(string policyName, out RequestTimeoutPolicy policy) { }
+ public void RemovePolicy(string policyName) { }
+ }
+ public class RequestTimeoutPolicy
+ {
+ public TimeSpan? Timeout { get; }
+ public RequestTimeoutPolicy(TimeSpan? timeout) { }
+ }
+ // Overrides the global timeout, if any.
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
+ public sealed class RequestTimeoutAttribute : Attribute
+ {
+ public TimeSpan? Timeout { get; }
+ public string? PolicyName { get; }
+ public RequestTimeoutAttribute(int seconds) { }
+ public RequestTimeoutAttribute(string policyName) { }
+ }
+ // Disables all timeouts, even the global one.
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
+ public sealed class DisableRequestTimeoutAttribute : Attribute
+ {
+ public DisableRequestTimeoutAttribute() { }
+ }
Usage Examples
services.AddRequestTimeout(options =>
{
options.DefaultTimeout = TimeSpan.FromSeconds(10);
options.DefaultTimeoutLocator = context => TimeSpan.Parse(context.Request.Query["delay"]);
options.AddPolicy("MyTimeoutPolicy", TimeSpan.FromSeconds(1));
options.AddPolicy("DynamicPolicy", new RequestTimeoutPolicy(context => TimeSpan.Parse(context.Request.Query["timeout"])));
});
...
app.UseRequestTimeouts();
...
app.UseEndpoints(endpoints => {
endpoints
.MapGet("/", (context) => { ... })
.WithRequestTimeout(TimeSpan.FromSeconds(1));
app.UseEndpoints(endpoints => {
endpoints
.MapGet("/", (context) => { ... })
.WithRequestTimeout("MyTimeoutPolicy");
...
[RequestTimeout(seconds: 1)]
public ActionResult<TValue> GetHello()
{
return "Hello";
}
[RequestTimeout("MyTimeoutPolicy")]
public ActionResult<TValue> GetHello()
{
return "Hello";
}
[DisableRequestTimeout]
public ActionResult<TValue> ImVerySlow()
{
Thread.Sleep(TimeSpan.FromHours(1);
return "Hello";
}
Alternative Designs
Risks
- We need to ensure this system is flexible enough to cover a wide variety of scenarios, while being easy enough for people to not mis-configure.
- What about components that don't use endpoints?
- What if the middleware is placed in the wrong location?