aspnetcore
aspnetcore copied to clipboard
RequestTimeoutsMiddleware doesn't seem to work outside of me manually throwing an OperationCancelledException
Is there an existing issue for this?
- [X] I have searched the existing issues
Describe the bug
If I create a default ASP.NET Core 8 Web API project in Visual Studio and add the Request Timeouts Middleware with a default policy of 10 seconds I don't get an HTTP504 even if I put a Thread.Sleep(30000) in the API endpoint to induce a timeout.
Looking at RequestTimeoutsMiddleware.cs it seems that the only scenario in which I'd get back an empty response and the defined HTTP error code is if my code manually throws an OperationCancelledException.
I started a long-running API and then attached a debugger (to get past the if (Debugger.IsAttached) { return _next(context); }
at line #31 in RequestTimeoutsMiddleware.cs) and if I step backward from a breakpoint at the finally{}
block I land at await _next(context);
(line #107)...
Importantly my WriteTimeoutResponse
delegate specified in the timeout policy is never hit.
In other words it seems like the middleware may possibly trigger a timeout like it should (though in my testing it doesn't seem to do that either) but it definitely doesn't set the HTTP result code when an induced timeout occurs. If I had to guess I'm not getting the expected HTTP result code specifically because it's not actually inducing the timeout in the first place...using the provided sample code the configured 10-second timeout isn't honored.
It seems to me like what would be required is to call context.RequestAborted.Register()
within the try block at line #101 and either move or duplicate the content of the catch()when()
block to the delegate registered on the linkedCts.Token
cancellaton token???
Expected Behavior
If my operation exceeds the configured timeout then the configured HTTP status code should be set.
Steps To Reproduce
- Create an ASP.NET Core Web API project in Visual Studio using the below code for the program.cs file (no other changes needed to the out-of-box project) using .NET 8.0
- Start the project without a debugger (else the middleware is bypassed)
- Invoke the test API (ex: using PowerShell
Invoke-WebRequest -Uri 'https://localhost:44339/{weatherforecast}?timeout=30000'
)
using Microsoft.AspNetCore.Http.Timeouts;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddRequestTimeouts(options =>
{
options.DefaultPolicy = new RequestTimeoutPolicy
{
Timeout = TimeSpan.FromSeconds(10),
TimeoutStatusCode = StatusCodes.Status504GatewayTimeout,
WriteTimeoutResponse = async (HttpContext context) => {
Thread.Sleep(120000);
System.Diagnostics.Debug.WriteLine("Entered the writetimeoutresponse delegate");
}
};
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseRequestTimeouts();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", (int timeout) =>
{
// Generate something interesting for a response body JSON
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
// Recreate a "sleeping thread" behavior to trigger the demo
var forevertask = new Task(async () =>
{
Thread.Sleep(timeout);
});
forevertask.Start();
forevertask.Wait();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.Run();
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Exceptions (if any)
No response
.NET Version
8
Anything else?
Microsoft Visual Studio Enterprise 2022
Version 17.8.7
VisualStudio.17.Release/17.8.7+34601.278
Microsoft .NET Framework
Version 4.8.09032
Installed Version: Enterprise
ADL Tools Service Provider 1.0
This package contains services used by Data Lake tools
ASA Service Provider 1.0
ASP.NET and Web Tools 17.8.358.6298
ASP.NET and Web Tools
Azure App Service Tools v3.0.0 17.8.358.6298
Azure App Service Tools v3.0.0
Azure Data Lake Tools for Visual Studio 2.6.5000.0
Microsoft Azure Data Lake Tools for Visual Studio
Azure Functions and Web Jobs Tools 17.8.358.6298
Azure Functions and Web Jobs Tools
Azure Stream Analytics Tools for Visual Studio 2.6.5000.0
Microsoft Azure Stream Analytics Tools for Visual Studio
C# Tools 4.8.0-7.23572.1+7b75981cf3bd520b86ec4ed00ec156c8bc48e4eb
C# components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.
Common Azure Tools 1.10
Provides common services for use by Azure Mobile Services and Microsoft Azure Tools.
Microsoft Azure Hive Query Language Service 2.6.5000.0
Language service for Hive query
Microsoft Azure Stream Analytics Language Service 2.6.5000.0
Language service for Azure Stream Analytics
Microsoft Azure Tools for Visual Studio 2.9
Support for Azure Cloud Services projects
Microsoft JVM Debugger 1.0
Provides support for connecting the Visual Studio debugger to JDWP compatible Java Virtual Machines
NuGet Package Manager 6.8.1
NuGet Package Manager in Visual Studio. For more information about NuGet, visit https://docs.nuget.org/
Razor (ASP.NET Core) 17.8.3.2405201+d135dd8d2ec1c2fbdee220e8656b308694e17a4b
Provides languages services for ASP.NET Core Razor.
SQL Server Data Tools 17.8.120.1
Microsoft SQL Server Data Tools
ToolWindowHostedEditor 1.0
Hosting json editor into a tool window
TypeScript Tools 17.0.20920.2001
TypeScript Tools for Microsoft Visual Studio
Visual Basic Tools 4.8.0-7.23572.1+7b75981cf3bd520b86ec4ed00ec156c8bc48e4eb
Visual Basic components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.
Visual F# Tools 17.8.0-beta.23475.2+10f956e631a1efc0f7f5e49c626c494cd32b1f50
Microsoft Visual F# Tools
Visual Studio IntelliCode 2.2
AI-assisted development for Visual Studio.
.NET SDK:
Version: 8.0.102
Commit: 64f1bc458e
Workload version: 8.0.100-manifests.8a11730e
Runtime Environment:
OS Name: Windows
OS Version: 10.0.22621
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\8.0.102\
.NET workloads installed:
Workload version: 8.0.100-manifests.8a11730e
There are no installed workloads to display.
Host:
Version: 8.0.2
Architecture: x64
Commit: 1381d5ebd2
.NET SDKs installed:
6.0.419 [C:\Program Files\dotnet\sdk]
8.0.102 [C:\Program Files\dotnet\sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 6.0.27 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 6.0.27 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 6.0.27 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.16 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
The middleware is designed for cooperative timeouts, not forceful ones. E.g. when the timeout fires it's not going to call Thread.Abort or anything, that ends up being too destabilizing. What it does is signal HttpContext.RequestAborted and it's up to observers to stop their work.
Replace forevertask with something like this:
await Task.Delay(timeout, httpContext.RequestAborted);
By flowing the RequestAborted token to the long running operations they know when the timeout happens and can stop themselves.
This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.
See our Issue Management Policies for more information.