Skip to content

Memory is not released when using TransactionScope with async option #108447

@frencsi

Description

@frencsi

Sample code

while (true)
{
    using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
    {
        scope.Complete();
    }
}

snapshot1

Memory usage grows, and response times increase until the web api halts. Removing the async option resolves the issue, but async is required in my case. 😞

Workaround (not perfect, see comment)

I created a factory (singleton) around it to manually trigger GC, something like this simplified:

public class TransactionScopeFactory
{
    private const int GcThreshold = 200_000;
    private static int _createdScopes = 0;

    public TransactionScope CreateScope()
    {
        int currentCreatedScopes = Interlocked.Increment(ref _createdScopes);

        if (currentCreatedScopes >= GcThreshold)
        {
            if (Interlocked.CompareExchange(ref _createdScopes, 0, GcThreshold) >= GcThreshold)
            {
                GC.Collect(2);
            }
        }

        return new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
    }
}

Questions

  • Is the workaround okay? I'm a bit hesitant about manual GC, as the docs advises against it without a clear purpose and the TransactionScope docs doesn't mention anything about manually invoking GC.
  • Why does the workaround only work in release mode? It doesn't seem to work in debug mode.

Used versions: .NET 8, 9

Thank you 😄

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions