@@ -1085,76 +1085,61 @@ public async Task Reports_command_diagnostic_on_cancellation()
10851085 }
10861086
10871087 [ ConditionalFact ]
1088- public void HandleTransactionCompleted_with_concurrent_ClearTransactions_is_thread_safe( )
1088+ public async Task HandleTransactionCompleted_with_concurrent_ClearTransactions_is_thread_safe( )
10891089 {
10901090 // This test verifies the fix for the race condition where HandleTransactionCompleted
10911091 // could be called on a different thread while ClearTransactions is executing.
1092- var exceptions = new List< Exception > ( ) ;
1092+
1093+ // Test with scope.Complete() before ResetState()
10931094 for ( var i = 0 ; i < Environment . ProcessorCount ; i++ )
10941095 {
10951096 var connection = new FakeRelationalConnection (
10961097 CreateOptions ( new FakeRelationalOptionsExtension ( ) . WithConnectionString ( "Database=ConcurrencyTest" ) ) ) ;
10971098
1098- using var scope = new TransactionScope ( ) ;
1099+ var scope = new TransactionScope ( ) ;
10991100 connection . Open ( ) ;
1101+ scope . Complete ( ) ;
11001102
1101- var random = new Random ( ) ;
1102- var resetFirst = random. Next( 0 , 1 ) == 0 ;
1103- var tasks = new Task [ 2 ] ;
1104- tasks [ 0 ] = Task . Run ( async ( ) =>
1103+ // Start the reset task first, which will yield and then try to reset
1104+ var resetTask = Task . Run ( ( ) =>
11051105 {
1106- try
1107- {
1108- // Small delay to increase chance of race condition
1109- await Task . Yield ( ) ;
1110-
1111- if ( resetFirst )
1112- {
1113- ( ( IResettableService ) connection ) . ResetState ( ) ;
1114- }
1115- else
1116- {
1117- scope . Complete ( ) ;
1118- }
1119- }
1120- catch ( Exception ex )
1121- {
1122- lock ( exceptions )
1123- {
1124- exceptions . Add ( ex ) ;
1125- }
1126- }
1106+ // ResetState calls ClearTransactions which might race with HandleTransactionCompleted
1107+ ( ( IResettableService ) connection ) . ResetState ( ) ;
11271108 } ) ;
11281109
1129- tasks[ 1 ] = Task . Run ( async ( ) =>
1110+ // Dispose the scope on the main thread, which will trigger the TransactionCompleted event
1111+ // The event handler (HandleTransactionCompleted) may execute on a different thread and race
1112+ // with the ClearTransactions call in resetTask
1113+ scope . Dispose ( ) ;
1114+
1115+ await resetTask ;
1116+ }
1117+
1118+ // Test with ResetState() before scope.Complete()
1119+ for ( var i = 0 ; i < Environment . ProcessorCount ; i ++ )
1120+ {
1121+ var connection = new FakeRelationalConnection (
1122+ CreateOptions ( new FakeRelationalOptionsExtension ( ) . WithConnectionString ( "Database=ConcurrencyTest" ) ) ) ;
1123+
1124+ var scope = new TransactionScope ( ) ;
1125+ connection . Open ( ) ;
1126+
1127+ // Start the reset task first
1128+ var resetTask = Task . Run ( async ( ) =>
11301129 {
1131- try
1132- {
1133- // Small delay to increase chance of race condition
1134- await Task . Yield ( ) ;
1135-
1136- if ( resetFirst )
1137- {
1138- scope . Complete ( ) ;
1139- }
1140- else
1141- {
1142- ( ( IResettableService ) connection ) . ResetState ( ) ;
1143- }
1144- }
1145- catch ( Exception ex )
1146- {
1147- lock ( exceptions )
1148- {
1149- exceptions . Add ( ex ) ;
1150- }
1151- }
1130+ // Small delay to increase chance of race condition
1131+ await Task . Yield ( ) ;
1132+
1133+ // ResetState calls ClearTransactions which might race with HandleTransactionCompleted
1134+ ( ( IResettableService ) connection ) . ResetState ( ) ;
11521135 } ) ;
11531136
1154- Task. WaitAll ( tasks , TimeSpan . FromSeconds ( 10 ) ) ;
1155- }
1137+ // Complete and dispose the scope, which will trigger the TransactionCompleted event
1138+ scope . Complete ( ) ;
1139+ scope . Dispose ( ) ;
11561140
1157- Assert. Empty ( exceptions ) ;
1141+ await resetTask ;
1142+ }
11581143 }
11591144
11601145 private static IDbContextOptions CreateOptions ( params RelationalOptionsExtension [ ] optionsExtensions )
0 commit comments