-
Notifications
You must be signed in to change notification settings - Fork 10k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AspNetCore Mvc Controller keeps serializing IAsyncEnumerable after connection is closed #35330
Comments
The way to do this is to pass in the CancellationToken into GetInfiniteInts method and decorate it with EnumeratorCancellation attribute. using System.Runtime.CompilerServices;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", (CancellationToken cancellationToken) => GetNumbersAsync(cancellationToken));
app.Run();
static async IAsyncEnumerable<int> GetNumbersAsync([EnumeratorCancellation]CancellationToken cancellationToken)
{
for (int i = 0; i < int.MaxValue; i++)
{
if (cancellationToken.IsCancellationRequested)
{
break;
}
await Task.Delay(TimeSpan.FromMilliseconds(100));
Console.WriteLine($"{DateTime.Now}: {i}");
yield return i;
}
} |
We should fix this by passing the RequestAborted token to the call to SerializeAsync |
|
Yep, we should fix it in both places. |
Passing the RequestAborted token to WriteAsJsonAsync ends up in an error for me when the request is cancelled: var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", (HttpResponse res, CancellationToken cancellationToken) => res.WriteAsJsonAsync(GetNumbersAsync(), cancellationToken)); ;
app.Run();
static async IAsyncEnumerable<int> GetNumbersAsync()
{
for (int i = 0; i < int.MaxValue; i++)
{
await Task.Delay(TimeSpan.FromMilliseconds(100));
Console.WriteLine($"{DateTime.Now}: {i}");
yield return i;
}
}
|
Here's the pattern we use for sending files aspnetcore/src/Http/Http.Extensions/src/SendFileResponseExtensions.cs Lines 142 to 153 in ff51fd7
|
So a empty catch block? 😊 |
The exception is only caught if it's an implicit cancellation, that is, if the token you passed in isn't the one that fired. |
Ah... Got it; if you pass the CancellationToken you need to handle the OperationCanceledException, else this (framework) handles it. |
I have no idea why that is, it looks like a bug... |
Related to dotnet/runtime#51176 (comment). Compiler-generated async enumerators will throw NotSupportedException if an attempt is made to dispose them while a |
Thanks everyone for looking into this! |
Describe the bug
In #32483 (see also) a breaking change regarding the processing of
IAsyncEnumerable
was announced. I wanted to test the new behavior using preview version 6.0.100-preview.6.21355.2.Observed behavior
At first glance, it works as expected and data is returned to the client even before the processing is terminated. However, adding
Console.WriteLine
reveals that the ASP keeps iterating overIAsyncEnumerable
for serialization even a long time after the client has closed the connection. This puts unnecessary load on the server because the result of that processing will never be fetched.Expected behavior
After the client closed the connection the server should stop processing and hence stop iterating over
IAsyncEnumerable
To Reproduce
Server
The server exposes an endpoint "Loop" which returns a list of 100000 ints. Each time a single int is fetched it will be logged in the console with a counter and the current timestamp.
Output
Please note that the server is processing for about a minute when this endpoint is called.
Client
The Python client calls the server's exposed "Loop" endpoint, but only fetches the first 100 bytes and closes the connection afterwards. The client code terminates after about 100-200ms.
Output
Please not the the client closes the connection after less than a second while the server before was processing for about a minute.
Exceptions (if any)
There are no exceptions. The logs reveal the unexpected bahvior.
Further technical details
.NET SDK (reflecting any global.json):
Version: 6.0.100-preview.6.21355.2
Commit: 7f8e0d76c0
Runtime Environment:
OS Name: ubuntu
OS Version: 20.04
OS Platform: Linux
RID: ubuntu.20.04-x64
Base Path: /home/USER/.dotnet/sdk/6.0.100-preview.6.21355.2/
Host (useful for support):
Version: 6.0.0-preview.6.21352.12
Commit: 770d630b28
.NET SDKs installed:
5.0.205 [/home/USER/.dotnet/sdk]
6.0.100-preview.6.21355.2 [/home/USER/.dotnet/sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 5.0.8 [/home/USER/.dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.0-preview.6.21355.2 [/home/USER/.dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 5.0.8 [/home/USER/.dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.0-preview.6.21352.12 [/home/USER/.dotnet/shared/Microsoft.NETCore.App]
The text was updated successfully, but these errors were encountered: