Skip to content

Async Code Leaks in 2.1 Runtime, but Not 2.0 Runtime #11189

Closed
@kriskalish

Description

@kriskalish

I have encountered an issue a specific instance of my code leaks when the .NET Core runtime is upgraded from 2.0 to 2.1. While the original code is too complex to post, I was able to reproduce the issue with a distilled example.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MemoryTester
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Task<long>> tasks = new List<Task<long>>();

            for (var i = 0; i < 2000; i++)
            {
                var task = MockWork(new byte[1024 * 1024]);
                tasks.Add(task);
                task.Wait();

                GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
                GC.WaitForPendingFinalizers();
            }

            var all = Task.WhenAll(tasks).GetAwaiter().GetResult();

            Console.WriteLine($"Finished working on {all.Sum()} bytes.");
        }

        static async Task<long> MockWork(byte[] bytes)
        {
            await Task.Delay(10);
            await Task.Yield();
            return bytes.Length;
        }
    }
}

I can see how it would leak, if MockWork is compiled into a state machine with a property representing bytes, then the fact that Main holds a reference to all the tasks would prevent it from garbage collecting those byte arrays. However, I find it curious that this style of code works in 2.0 which leads me to believe that it nulls out unused fields in the state machine when the task is complete.

All in all, my question is this leak expected behavior?

For what it's worth, I suspect that the change in behavior may be related to these two PRs for 2.1:
dotnet/coreclr#13105, dotnet/coreclr#14178

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions