question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

AspNetCore Mvc Controller keeps serializing IAsyncEnumerable after connection is closed

See original GitHub issue

Describe the bug

In https://github.com/dotnet/aspnetcore/issues/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 over IAsyncEnumerable 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.

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System;

namespace dot_net_api_streaming.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class LoopController : ControllerBase
    {

        [HttpGet]
        public IAsyncEnumerable<int> Get()
        {
            return GetInfiniteInts();
        }

        private async IAsyncEnumerable<int> GetInfiniteInts()
        {
            int index = 0;
            while (index <100000)
            {
                Console.WriteLine($"{DateTime.Now}: {index}");
                yield return index++;
            }            

        }
    }
}
Output

Please note that the server is processing for about a minute when this endpoint is called.

8/13/2021 11:51:18 AM: 1
8/13/2021 11:51:18 AM: 2
[...]
8/13/2021 11:52:19 AM: 99998
8/13/2021 11:52:19 AM: 99999

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.

import urllib.request
import time
import ssl

request_url = 'https://localhost:5001/Loop'
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
print(f"Start request {request_url}")
start = time.time()
f = urllib.request.urlopen(request_url, context=ctx)
print(f.read(100).decode('utf-8'))
end = time.time()
print("Completed in {}s".format(round(end - start, 3)))
Output

Please not the the client closes the connection after less than a second while the server before was processing for about a minute.

Start request https://localhost:5001/Loop
[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,3
Completed in 0.107s

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]

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:12 (7 by maintainers)

github_iconTop GitHub Comments

3reactions
eiriktsarpaliscommented, Aug 13, 2021

Related to https://github.com/dotnet/runtime/issues/51176#issuecomment-818866190. Compiler-generated async enumerators will throw NotSupportedException if an attempt is made to dispose them while a MoveNextAsync() task is pending completion. It would seem like STJ is doing this when serialization is cancelled due to a cancellation token firing. It’s a bug and we need to fix it.

1reaction
davidfowlcommented, Aug 13, 2021
Read more comments on GitHub >

github_iconTop Results From Across the Web

Clarification on how IAsyncEnumerable works with ASP. ...
Once the framework code and the serialiser get all data, it will be serialised and served to the client as a single response....
Read more >
System.Text.Json IAsyncEnumerable serialization - .NET
Asynchronous serialization methods now enumerate any IAsyncEnumerable<T> instances in an object graph and then serialize them as JSON arrays ...
Read more >
ASP.NET Core Best Practices
Returning IEnumerable<T> from an action results in synchronous collection iteration by the serializer. The result is the blocking of calls and a ...
Read more >
Async streams in C# – Deep Dive
It starts by comparing the new IAsyncEnumerable<T> interface to the options that were previously available for making access to items in ...
Read more >
ASP.NET Core 6 and IAsyncEnumerable - Async Streamed ...
NET 6 is support for async streaming of IAsyncEnumerable . In .NET 6, System.Text.Json can serialize incoming IAsyncEnumerable in asynchronous ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found