Skip to content

Memory leak: never-ended Activities accumulate in SentrySpanProcessor._map (sibling of #3197) #5392

Description

@Ermabo

Package

Sentry.OpenTelemetry

.NET Flavor / Version

.NET 10.0.7 (also reproduced on .NET 8 and 9)

OS

Linux (production). Reproduces on any OS.

SDK Version

6.6.0

Summary

SentrySpanProcessor leaks spans whose Activity never receives OnEnd, for example aborted HTTP/2 streams or abandoned gRPC server streams. Such spans accumulate in SentrySpanProcessor._map and are never removed, so a long running service grows in memory until it runs out.

This is a sibling of #3197 / #3198. #3198 fixed the filtered span case, and its description mentions holding weak references to Activities so PruneFilteredSpans could also evict collected ones. But the reference is stored via SetFused, which is backed by a ConditionalWeakTable and holds its values strongly (only keys are weak). So _map -> span -> Activity pins the Activity alive, the activity is null branch in PruneFilteredSpans can never run, and recorded spans that never end stay in the map forever.

UseSentry / SentrySdk.Init call

builder.WebHost.UseSentry(o =>
{
    o.Dsn = "<redacted>";
    o.UseOpenTelemetry();
    o.DisableSentryHttpMessageHandler = true;
    o.TracesSampler = ctx => 0.1; // sample 10% of root traces
});

builder.Services.AddOpenTelemetry().WithTracing(t => t
    .AddAspNetCoreInstrumentation()
    .AddSentry());

Steps to Reproduce

Minimal repro: https://gist.github.com/Ermabo/41e0301e0afabfe4d28cff799d4e79b6

  1. dotnet run -c Release -- --close true (normal spans): memory stays flat.
  2. dotnet run -c Release -- --close false (Activities started but never stopped, i.e. an aborted request): memory grows without bound, about 1.9 GB over 300k iterations.

Expected Result

Spans whose Activity never ends are eventually evicted from _map, and memory stays bounded.

Actual Result

They are retained forever. In our production gRPC service we observed about 91k Sentry.Internal.UnsampledTransaction retained with _isFinished == false, gcroot terminating at SentrySpanProcessor._map. Managed gen2 grew from about 190 MB to 2.4 GB over roughly 6 days before an out of memory crash, repeating roughly every 4 days.

A fix PR is on the way. cc @jamescrosswell (author of #3198).

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETPull requests that update .net codeBugSomething isn't workingSpans

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions