Skip to content

GC doesn't run when using TransactionScope + async #50683

Open
@karlra

Description

@karlra

Description

I have an application that uses the TransactionScope class to control SQL transactions. We also use the same library to do mathematical simulations, but in this case it injects a dummy repository to stop the persistence to SQL. When doing this I noticed that performance became progressively worse, and eventually the app crashed with OOM errors.

I had previously created a bug report here: #1419 but it was closed because it was determined that this is a GC issue and not a TransactionScope issue.

Take this code:

using System;
using System.Diagnostics;
using System.Threading;
using System.Transactions;

namespace TestTrxScope
{
    class Program
    {
        static void Main(string[] args)
        {
            int i = 0;
            double lastElapsed = 0;
            Stopwatch sw = new Stopwatch();
            sw.Start();
            while(true)
            {
                using(var scope = CreateTransactionScope())
                {
                    scope.Complete();
                    i++;
                    if(i % 1_000_000 == 0)
                    {
                        Console.WriteLine($"1m took {sw.Elapsed.TotalSeconds - lastElapsed}");
                        lastElapsed = sw.Elapsed.TotalSeconds;
                        //GC.Collect(2);
                    }
                }
            }
        }

        public static TransactionScope CreateTransactionScope()
        {
            //Memory grows constantly
            return new TransactionScope(TransactionScopeOption.Suppress, new TransactionOptions() { }, TransactionScopeAsyncFlowOption.Enabled);

            //This version doesn't constantly grow in memory usage
            //return new TransactionScope();
        }
    }
}

This program's memory usage will grow forever, and the time it takes to complete 1 million TransactionScopes will also grow forever.

image

image

image

Uncommenting the GC.Collect(2) fixes the ever increasing memory issue, but for some reason the GC does not run on its own in this program. You might think that this is a contrived example but I found this issue in production code, where we have console apps that do extremely heavy calculations.

Note that even if GC.Collect(2) is called manually, the performance per 1 million rounds still gets worse by 75% or so until it evens out. This suggests that our data access code is impacted by this even in situations when the GC might run level 2 collections on its own due to other allocations causing it.

Configuration

.net core 3.1, Windows 10 x64

Regression?

This does not seem to happen on .net framework.

Other information

The situation improves somewhat with .net 5 as I haven't managed to get OOM errors but it still gets massively progressively worse.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions