@@ -6,6 +6,29 @@ public class TransactionTracerTests
66{
77 private static readonly TimeSpan AnyTimeout = TimeSpan . FromSeconds ( 30 ) ;
88
9+ private sealed class ThrowOnDisposedTimer : ISentryTimer
10+ {
11+ private bool _isDisposed ;
12+
13+ public ThrowOnDisposedTimer ( Action _ )
14+ {
15+ }
16+
17+ public void Start ( TimeSpan timeout )
18+ {
19+ if ( _isDisposed )
20+ {
21+ throw new ObjectDisposedException ( nameof ( ThrowOnDisposedTimer ) ) ;
22+ }
23+ }
24+
25+ public void Cancel ( )
26+ {
27+ }
28+
29+ public void Dispose ( ) => _isDisposed = true ;
30+ }
31+
932 private static ( TransactionTracer transaction , MockTimer timer ) CreateIdleTransaction (
1033 IHub hub , string name = "name" , string op = "op" )
1134 {
@@ -122,13 +145,39 @@ public void IdleTimeout_NoChildSpans_TransactionIsDiscarded()
122145 {
123146 // Given an auto-generated UI event transaction with no child spans
124147 var hub = Substitute . For < IHub > ( ) ;
125- var ( _ , timer ) = CreateIdleTransaction ( hub ) ;
148+ var ( transaction , timer ) = CreateIdleTransaction ( hub ) ;
126149
127150 // When the idleTimeout fires
128151 timer . Fire ( ) ;
129152
130153 // Then the SDK discards the transaction (does not capture it)
131154 hub . DidNotReceive ( ) . CaptureTransaction ( Arg . Any < SentryTransaction > ( ) ) ;
155+ transaction . IsFinished . Should ( ) . BeTrue ( ) ;
156+ transaction . EndTimestamp . Should ( ) . NotBeNull ( ) ;
157+ }
158+
159+ [ Fact ]
160+ public void ChildSpanFinished_DisposedIdleTimer_DoesNotThrow ( )
161+ {
162+ // Given an idle transaction whose timer has been disposed after the finished-state check
163+ var hub = Substitute . For < IHub > ( ) ;
164+ ThrowOnDisposedTimer ? disposedTimer = null ;
165+ var transaction = new TransactionTracer (
166+ hub ,
167+ new TransactionContext ( "name" , "op" ) ,
168+ idleTimeout : AnyTimeout ,
169+ timerFactory : callback =>
170+ {
171+ disposedTimer = new ThrowOnDisposedTimer ( callback ) ;
172+ return disposedTimer ;
173+ } ) ;
174+ disposedTimer ! . Dispose ( ) ;
175+
176+ // When ChildSpanFinished attempts to restart the disposed timer
177+ var action = ( ) => transaction . ChildSpanFinished ( ) ;
178+
179+ // Then no ObjectDisposedException escapes
180+ action . Should ( ) . NotThrow ( ) ;
132181 }
133182
134183 [ Fact ]
0 commit comments