Skip to content

Commit 0a1e7af

Browse files
CopilotcincuranetAndriySvyryd
authored
Fix flaky test by creating proper race condition between HandleTransactionCompleted and ClearTransactions (#37101)
Fixes #37086 Co-authored-by: cincuranet <4540597+cincuranet@users.noreply.github.com> Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
1 parent eeaf988 commit 0a1e7af

File tree

1 file changed

+38
-53
lines changed

1 file changed

+38
-53
lines changed

test/EFCore.Relational.Tests/RelationalConnectionTest.cs

Lines changed: 38 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)