-
Notifications
You must be signed in to change notification settings - Fork 857
[api] Fix memory leaks in TracerProvider.GetTracer API #4906
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
5725dda
0bd778c
87a1bbc
31b045f
8afee1b
6d84dec
5951223
988af80
8b4895d
ca19d28
7c1ec50
404df8b
83e550b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| override OpenTelemetry.Trace.TracerProvider.Dispose(bool disposing) -> void |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| override OpenTelemetry.Trace.TracerProvider.Dispose(bool disposing) -> void |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| override OpenTelemetry.Trace.TracerProvider.Dispose(bool disposing) -> void |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,10 @@ | |
|
|
||
| #nullable enable | ||
|
|
||
| using System.Diagnostics; | ||
| using System.Collections.Concurrent; | ||
| #if NET6_0_OR_GREATER | ||
| using System.Diagnostics.CodeAnalysis; | ||
| #endif | ||
|
|
||
| namespace OpenTelemetry.Trace; | ||
|
|
||
|
|
@@ -25,6 +28,8 @@ namespace OpenTelemetry.Trace; | |
| /// </summary> | ||
| public class TracerProvider : BaseProvider | ||
| { | ||
| private ConcurrentDictionary<TracerKey, Tracer>? tracers = new(); | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="TracerProvider"/> class. | ||
| /// </summary> | ||
|
|
@@ -43,6 +48,83 @@ protected TracerProvider() | |
| /// <param name="name">Name identifying the instrumentation library.</param> | ||
| /// <param name="version">Version of the instrumentation library.</param> | ||
| /// <returns>Tracer instance.</returns> | ||
| public Tracer GetTracer(string name, string? version = null) | ||
| => new(new ActivitySource(name ?? string.Empty, version)); | ||
| public Tracer GetTracer( | ||
| #if NET6_0_OR_GREATER | ||
| [AllowNull] | ||
| #endif | ||
| string name, | ||
| string? version = null) | ||
| { | ||
| var tracers = this.tracers; | ||
| if (tracers == null) | ||
| { | ||
| // Note: Returns a no-op Tracer once dispose has been called. | ||
| return new(activitySource: null); | ||
| } | ||
|
|
||
| var key = new TracerKey(name, version); | ||
|
|
||
| Retry: | ||
| if (!tracers.TryGetValue(key, out var tracer)) | ||
| { | ||
| lock (tracers) | ||
| { | ||
| if (this.tracers == null) | ||
| { | ||
| // Note: We check here for a race with Dispose and return a | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe we need to set
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just checked it a couple times. I think it is good! Could be I'm not seeing something though. Can you write out a flow for me that you think is flawed? Here are a couple flows I'm imagining. Case where Dispose runs in the middle of the writer and gets the lock...
Case where Dispose runs in the middle of the writer and waits on the lock...
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For case 2,
I think this is more of design choice. Yes, it doesn't care that I was thinking we could offer a stronger guarantee that we would never return a
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I merged the PR because I think what's there will work well enough. I'll circle back to this comment when I have a sec to see if I can simplify it or clean it up in a way that doesn't introduce a bunch of contention. |
||
| // no-op Tracer in that case. | ||
| return new(activitySource: null); | ||
| } | ||
|
|
||
| tracer = new(new(key.Name, key.Version)); | ||
| if (!tracers.TryAdd(key, tracer)) | ||
| { | ||
| // Note: This should really never happen due to the | ||
CodeBlanch marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // outer lock. | ||
| tracer.ActivitySource!.Dispose(); | ||
| goto Retry; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return tracer; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override void Dispose(bool disposing) | ||
| { | ||
| if (disposing) | ||
| { | ||
| var tracers = Interlocked.CompareExchange(ref this.tracers, null, this.tracers); | ||
CodeBlanch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (tracers != null) | ||
| { | ||
| lock (tracers) | ||
| { | ||
| foreach (var kvp in tracers) | ||
| { | ||
| var tracer = kvp.Value; | ||
| var activitySource = tracer.ActivitySource; | ||
| tracer.ActivitySource = null; | ||
| activitySource?.Dispose(); | ||
| } | ||
|
|
||
| tracers.Clear(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| base.Dispose(disposing); | ||
| } | ||
|
|
||
| private readonly record struct TracerKey | ||
| { | ||
| public readonly string Name; | ||
| public readonly string? Version; | ||
|
|
||
| public TracerKey(string? name, string? version) | ||
| { | ||
| this.Name = name ?? string.Empty; | ||
| this.Version = version; | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.