Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 6, 2025

The test HandleTransactionCompleted_with_concurrent_ClearTransactions_is_thread_safe was failing intermittently due to calling TransactionScope.Complete() concurrently with ResetState(), which violated TransactionScope API constraints and caused ObjectDisposedException.

Root Cause

The original test design mixed user code (scope.Complete()) with internal EF Core operations (ResetState()) in a concurrent manner that the TransactionScope API doesn't support. This was causing ObjectDisposedException when the scope's underlying transaction was being cleared on another thread.

Solution

Restructured the test to properly test the actual race condition without violating TransactionScope API constraints:

  1. Complete and dispose the TransactionScope on the main thread within a using block
  2. Test the race between HandleTransactionCompleted callback (triggered by scope disposal) and ClearTransactions (called by ResetState()) on a background thread

This approach correctly tests the thread-safety between HandleTransactionCompleted and ClearTransactions as originally intended, without hitting TransactionScope API limitations.

Testing

  • ✅ 100 consecutive test runs - all passed
  • ✅ All 48 tests in RelationalConnectionTest class - all passed

Fixes #37086

Original prompt

This section details on the original issue you should resolve

<issue_title>Test Microsoft.EntityFrameworkCore.RelationalConnectionTest.HandleTransactionCompleted_with_concurrent_ClearTransactions_is_thread_safe is flaky</issue_title>
<issue_description>```text
Assert.Empty() Failure: Collection was not empty
Collection: [System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'System.Transactions.TransactionScope'.
at System.Transactions.TransactionScope.Complete()
at Microsoft.EntityFrameworkCore.RelationalConnectionTest.<>c__DisplayClass46_1.<<HandleTransactionCompleted_with_concurrent_ClearTransactions_is_thread_safe>b__1>d.MoveNext() in /_/test/EFCore.Relational.Tests/RelationalConnectionTest.cs:line 1138]


## Comments on the Issue (you are @copilot in this section)

<comments>
<comment_new><author>@cincuranet</author><body>
I guess the dotnet/efcore#36665 was not an entirely correct fix.</body></comment_new>
</comments>


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI changed the title [WIP] Fix flaky test for transaction handling in EF Core Fix flaky test by handling expected ObjectDisposedException from concurrent TransactionScope operations Nov 6, 2025
Copilot finished work on behalf of cincuranet November 6, 2025 12:48
Copilot AI requested a review from cincuranet November 6, 2025 12:48
@cincuranet
Copy link
Contributor

@copilot Show me itemized steps for both tasks that lead to ObjectDisposedException in original code.

Copy link
Contributor Author

Copilot AI commented Nov 6, 2025

@copilot Show me itemized steps for both tasks that lead to ObjectDisposedException in original code.

Here are the itemized steps for both scenarios that lead to ObjectDisposedException in the original code:

Scenario 1: resetFirst = false (Task 0 calls scope.Complete(), Task 1 calls ResetState())

Task 0 (scope.Complete()):

  1. Calls scope.Complete() to mark TransactionScope as committable
  2. This accesses the underlying Transaction.Current
  3. If Task 1 has already cleared the ambient transaction, the Transaction may be in a disposed state
  4. TransactionScope.Complete() throws ObjectDisposedException: Cannot access a disposed object. Object name: 'System.Transactions.TransactionScope'

Task 1 (ResetState()):

  1. Calls ResetState()ClearTransactions(clearAmbient: true)
  2. Sets _resetting = true
  3. Pops all ambient transactions from _ambientTransactions stack
  4. Unsubscribes from TransactionCompleted event for each transaction
  5. Sets _resetting = false
  6. The ambient transaction cleanup affects Transaction.Current that the scope depends on

Scenario 2: resetFirst = true (Task 0 calls ResetState(), Task 1 calls scope.Complete())

Same as Scenario 1 but with tasks reversed - Task 1 gets the exception when it tries to call scope.Complete() after Task 0 has cleared the transactions.

The race condition occurs because TransactionScope.Complete() needs to access the underlying transaction state, which gets affected when ClearTransactions unsubscribes from the event handler and pops the transaction from the stack. The TransactionScope API is not designed to handle this concurrent access pattern.

Copilot finished work on behalf of cincuranet November 6, 2025 13:04
@cincuranet
Copy link
Contributor

@copilot Instead of catching the exception, fix the root cause.

The original test was calling scope.Complete() concurrently with ResetState(),
which caused ObjectDisposedException due to TransactionScope API limitations.

The root cause was that the test was testing the wrong scenario - it was mixing
user code (scope.Complete()) with internal EF Core operations (ResetState()).

Fixed by restructuring the test to:
1. Complete and dispose the TransactionScope on the main thread in a using block
2. Then test the actual race condition: HandleTransactionCompleted callback
   (triggered by scope disposal) vs ClearTransactions (called by ResetState())

This properly tests the thread-safety between HandleTransactionCompleted and
ClearTransactions without hitting TransactionScope API constraints.

Co-authored-by: cincuranet <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 6, 2025

@copilot Instead of catching the exception, fix the root cause.

Fixed the root cause in commit aeaba76. The issue was that the test was calling scope.Complete() concurrently with ResetState(), which violated TransactionScope API constraints.

The test now properly tests the actual race condition (HandleTransactionCompleted vs ClearTransactions) by:

  1. Completing and disposing the TransactionScope on the main thread
  2. Then calling ResetState() on a background thread, which races with the HandleTransactionCompleted callback

Verified with 100 consecutive successful test runs.

Copilot AI changed the title Fix flaky test by handling expected ObjectDisposedException from concurrent TransactionScope operations Fix flaky test by restructuring to avoid concurrent TransactionScope operations Nov 6, 2025
Copilot finished work on behalf of cincuranet November 6, 2025 13:43
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.

Test Microsoft.EntityFrameworkCore.RelationalConnectionTest.HandleTransactionCompleted_with_concurrent_ClearTransactions_is_thread_safe is flaky

2 participants