Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Fixed Monitor duration calculation ([#3420]https://github.com/getsentry/sentry-dotnet/pull/3420)
- Fixed null IServiceProvider in anonymous routes with OpenTelemetry ([#3401](https://github.com/getsentry/sentry-dotnet/pull/3401))
- Fixed Trim warnings in Sentry.DiagnosticSource and WinUIUnhandledException integrations ([#3410](https://github.com/getsentry/sentry-dotnet/pull/3410))
- Fix memory leak when tracing is enabled ([#3432](https://github.com/getsentry/sentry-dotnet/pull/3432))

### Dependencies

Expand Down
25 changes: 25 additions & 0 deletions src/Sentry/TransactionTracer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,11 @@ public IReadOnlyList<string> Fingerprint
/// <inheritdoc />
public IReadOnlyDictionary<string, string> Tags => _tags;

#if NETSTANDARD2_1_OR_GREATER
private readonly ConcurrentBag<ISpan> _spans = new();
#else
private ConcurrentBag<ISpan> _spans = new();
#endif

/// <inheritdoc />
public IReadOnlyCollection<ISpan> Spans => _spans;
Expand Down Expand Up @@ -337,6 +341,14 @@ public void Push(ISpan span)
return null;
}
}

public void Clear()
{
lock (_lock)
{
TrackedSpans.Clear();
}
}
}
private readonly LastActiveSpanTracker _activeSpanTracker = new LastActiveSpanTracker();

Expand Down Expand Up @@ -373,6 +385,9 @@ public void Finish()

// Client decides whether to discard this transaction based on sampling
_hub.CaptureTransaction(new SentryTransaction(this));

// Release tracked spans
ReleaseSpans();
Comment on lines +389 to +390
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know why this is required? Is something holding the reference to the TransactionTracer? Does that reference need to be released too?

Copy link
Collaborator Author

@jamescrosswell jamescrosswell Jun 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I fully understand yet, no. The stack overflow thread linked above seems to imply ConcurrentBag<T> stores stuff in a thread local and leaves it there, unless you remove it. It seems improbable that we wouldn't have picked up on something so obvious years ago if that was the case though. That would mean massive memory leaks all over our SDK (we use ConcurrentBag<T> in a number of places).

I'll merge this PR, create a release, and then do a bit more digging on potentially replacing ConcurrentBag<T> with a light weight custom type that isn't so mysterious. That will require some benchmarking and testing though, as it would be replacing something quite foundational.

}

/// <inheritdoc />
Expand All @@ -395,4 +410,14 @@ public void Finish(Exception exception) =>

/// <inheritdoc />
public SentryTraceHeader GetTraceHeader() => new(TraceId, SpanId, IsSampled);

private void ReleaseSpans()
{
#if NETSTANDARD2_1_OR_GREATER
_spans.Clear();
#else
_spans = new ConcurrentBag<ISpan>();
#endif
_activeSpanTracker.Clear();
}
}
3 changes: 2 additions & 1 deletion test/Sentry.Tests/Protocol/SentryTransactionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ public void Redact_Redacts_Urls()
child2.SetExtra("f222", "p111");
child2.Finish(SpanStatus.OutOfRange);

txTracer.Finish(SpanStatus.Aborted);
// Don't finish the tracer - that would cause the spans to be released
// txTracer.Finish(SpanStatus.Aborted);

// Act
var transaction = new SentryTransaction(txTracer);
Expand Down