Skip to content

Conversation

jamescrosswell
Copy link
Collaborator

@jamescrosswell jamescrosswell changed the base branch from main to version6 October 13, 2025 00:34
Copy link

codecov bot commented Oct 13, 2025

Codecov Report

❌ Patch coverage is 85.71429% with 1 line in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (version6@6377b49). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/Sentry/Internal/NoOpSpan.cs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             version6    #4627   +/-   ##
===========================================
  Coverage            ?   73.10%           
===========================================
  Files               ?      479           
  Lines               ?    17388           
  Branches            ?     3431           
===========================================
  Hits                ?    12711           
  Misses              ?     3819           
  Partials            ?      858           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jamescrosswell
Copy link
Collaborator Author

@Flash0ver I'm trying to force an analyzer warning for the CA2000 (so that I can build and test a way to suppress this). I've added this to the csproj file for samples/Sentry.Samples.Console.Basic/Sentry.Samples.Console.Basic.csproj:

<WarningsAsErrors>CA2000</WarningsAsErrors>

However I can't seem to get it to work:
image

The warning does appear for the undisposed use of Foo (which I added to test the warning was enabled)... but we're not seeing it for our newly disposable ISpan classes. StartTransaction returns an ITransactionTracer that descends from ISpan, which implements IDisposable.

Any ideas/thoughts?

@Flash0ver
Copy link
Member

Flash0ver commented Oct 13, 2025

Oh ... perhaps I totally misunderstood CA2000 😟

DisposeObjectsBeforeLosingScope.cs

Hmmm ... it seems that the CA2000-DiagnosticAnalyzer does some Control-Flow-Analysis, that either tries to "see through" the implementation and does not report when it can't statically figure it out, or just does not report for some usage patterns.

var instance1 = new MyDisposable(); //CA2000
var instance2 = new MyFactory().CreateInstance(); //CA2000
var instance3 = MyFactory.CreateStatic(); //CA2000
var instance4 = MyFactory.Instance.CreateInstance(); // does not report CA2000

public sealed class MyDisposable : IDisposable
{
    public void Dispose()
    {
    }
}
public interface IMyFactory
{
    IDisposable CreateInstance();
}
public sealed class MyFactory : IMyFactory
{
    public static IMyFactory Instance { get; } = new MyFactory();
    public static IDisposable CreateStatic() => new MyDisposable();
    public IDisposable CreateInstance() => new MyDisposable();
}

Not sure if that analysis behavior is intended or not.
Perhaps the Analyzer cannot "see through" our rather complex control flow from SentrySdk, via the IHub, to some extension-methods.


Now, as a follow-up, I'm wondering:
We do have 2 public types that inherit ISpan, and with that IDisposable:

var transaction = new TransactionTracer(null!, null!); //CA2000
var span = new SpanTracer(null!, transaction, default, default, null!); //CA2000
span.Finish();
transaction.Finish();

Are there valid/intended usage scenarios to instantiate these types directly from user code, which would report CA2000, even when properly Finished?
Or ... perhaps we do not need a DiagnosticSuppressor for CA2000, since it's not reported for our implementation ... which may change in the future 🤔

@jamescrosswell
Copy link
Collaborator Author

Are there valid/intended usage scenarios to instantiate these types directly from user code, which would report CA2000,

I can't think of any scenario in which people would want to construct those manually... so I suspect not.

Or ... perhaps we do not need a DiagnosticSuppressor for CA2000, since it's not reported for our implementation ...

That's possibly the case yes... and without being able to reproduce the warning that it's supposed to suppress, it's relatively difficult to test. I think this PR might be ready for review then...

/// Transaction span tracer.
/// </summary>
public class SpanTracer : IBaseTracer, ISpan
public sealed class SpanTracer : IBaseTracer, ISpan
Copy link
Member

Choose a reason for hiding this comment

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

question: Is it worth a consideration to make SpanTracer internal, too? (in a potential follow-up)

So that users cannot run into CA2000

var span = new SpanTracer(null!, transaction, default, default, null!); //CA2000
span.Finish();

Or is it a potential user code scenario to try to cast?

if (SentrySdk.StartSpan("operation", "description") is SpanTracer span)

But this could have quite a large impact.


/// <summary>
/// Automatically finishes the span at the end of a <c>using</c> block. This is a convenience method only. Disposing
/// is not required (and analyser warnings are suppressed).
Copy link
Member

Choose a reason for hiding this comment

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

suggestion:

  • shall we remove the note about (and analyser warnings are suppressed) ... as it seems we cannot reproduce CA2000, unless a SpanTracer is directly newed.
  • should we mention that
    • Dispose finishes the span as "OK" / Successful
    • is equivalent to <see cref="Finish()"/>
    • and to use any of these overloads to finish as error / unsuccessfully
      • <see cref="Finish(SpanStatus)"/>
      • <see cref="Finish(Exception)"/>
      • <see cref="Finish(Exception, SpanStatus)"/>

/// Transaction tracer.
/// </summary>
public class TransactionTracer : IBaseTracer, ITransactionTracer
public sealed class TransactionTracer : IBaseTracer, ITransactionTracer
Copy link
Member

Choose a reason for hiding this comment

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

question: Is it worth a consideration to make TransactionTracer internal, too? (in a potential follow-up)

So that users cannot run into CA2000

var transaction = new TransactionTracer(null!, null!); //CA2000
transaction.Finish();

Or is it a potential user code scenario to try to cast?

if (SentrySdk.StartTransaction("name", "operation") is TransactionTracer transaction)

But this could have quite a large impact.


/// <summary>
/// Automatically finishes the span at the end of a <c>using</c> block. This is a convenience method only. Disposing
/// is not required (and analyser warnings are suppressed).
Copy link
Member

Choose a reason for hiding this comment

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

suggestion:

  • shall we remove the note about (and analyser warnings are suppressed) ... as it seems we cannot reproduce CA2000, unless a TransactionTracer is directly newed.
  • should we mention that
    • Dispose finishes the transaction as "OK" / Successful
    • is equivalent to <see cref="Finish()"/>
    • and to use any of these overloads to finish as error / unsuccessfully
      • <see cref="Finish(SpanStatus)"/>
      • <see cref="Finish(Exception)"/>
      • <see cref="Finish(Exception, SpanStatus)"/>

}

[Fact]
public void Dispose_Finished_Finishes()
Copy link
Member

Choose a reason for hiding this comment

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

question: Are part of the names here inverted from what each test case actually tests?

-public void Dispose_Finished_Finishes()
+public void Dispose_Unfinished_Finishes()

-public void Dispose_Unfinished_DoesNothing()
+public void Dispose_Finished_DoesNothing()

Or am I reading this wrongly?

/// <inheritdoc />
public void Finish()
{
// TODO: Replace with InterlockedBoolean once this has been merged into version6
Copy link
Member

Choose a reason for hiding this comment

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

TODO (as a "resolvable" comment)

public void Finish()
{
// TODO: Replace with InterlockedBoolean once this has been merged into version6
if (Interlocked.Exchange(ref _hasFinished, 1) == 1)
Copy link
Member

Choose a reason for hiding this comment

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

question: Shall we create a dedicated test for double-Disposal / double-Finishing?

Or is it actually sufficiently covered already by TransactionTracerTests.Dispose_Finished_DoesNothing?
I can imagine it is, as this new test drove this guard, right?

### BREAKING CHANGES

- SentryOptions.IsEnvironmentUser now defaults to false on MAUI. The means the User.Name will no longer be set, by default, to the name of the device ([#4606](https://github.com/getsentry/sentry-dotnet/pull/4606))
- Spans and Transactions now implement `IDispose` so that they can be used with `using` statements that will automatically finish the span when it passes out of scope, to be consistent with `Activity` classes when using OpenTelemetry ([#4627](https://github.com/getsentry/sentry-dotnet/pull/4627))
Copy link
Member

Choose a reason for hiding this comment

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

suggestion:

  • IDispose -> IDisposable
  • also mention using-declarations alongside using-statements
  • specify "Successfully"
Suggested change
- Spans and Transactions now implement `IDispose` so that they can be used with `using` statements that will automatically finish the span when it passes out of scope, to be consistent with `Activity` classes when using OpenTelemetry ([#4627](https://github.com/getsentry/sentry-dotnet/pull/4627))
- Spans and Transactions now implement `IDisposable` so that they can be used with `using` statements/declarations that will automatically finish the span successfully when it passes out of scope, to be consistent with `Activity` classes when using OpenTelemetry ([#4627](https://github.com/getsentry/sentry-dotnet/pull/4627))

/// Transaction span tracer.
/// </summary>
public class SpanTracer : IBaseTracer, ISpan
public sealed class SpanTracer : IBaseTracer, ISpan
Copy link
Member

Choose a reason for hiding this comment

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

question: CHANGELOG

Is it worth a note in the CHANGELOG that both

  • SpanTracer
  • TransactionTracer

are no longer inheritable?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ITransactionTracer should implement IDisposable

2 participants