dotnet / dotnet/runtime

System.Transactions hangs when non-MSDTC PSPE promotion occurs during EnlistDuringPrepareRequired Prepare

Open
#132,460 1 comment 0 reactions 0 assignees View on GitHub
area-System.Transactions
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Description

A non-MSDTC promotion triggered from inside an EnlistDuringPrepareRequired phase-0 callback leaves the System.Transactions commit state machine in TransactionStateDelegatedNonMSDTC instead of restarting commit in the promoted transaction states.

### Reproduction Steps

The problem occurs when calling this
```
for (var index = 1; index <= 2; index++)
{
var connection = new SqlConnection(connectionString);
this._connections.Add(connection);
connection.Open();
connection.EnlistTransaction(this._transactionToUse);
}
```
inside a `IEnlistmentNotification.Prepare()` when the transaction is not yet promoted before that.
See reproduction in [EnlistDuringPrepared.zip](https://github.com/user-attachments/files/31177153/EnlistDuringPrepared.zip): run the application and wait 1 minute for the exception. (setting openDuringPrepare to false, will enlist the connection before complete is called, which works)

### Expected behavior

Disposing the transaction should not run into a timeout

### Actual behavior

After `IEnlistmentNotification.Prepare()` is called, the transaction hangs for 60 seconds and then throws
```
System.Transactions.TransactionAbortedException: The transaction has aborted.
---> System.TimeoutException: Transaction Timeout
--- End of inner exception stack trace ---
at System.Transactions.TransactionStateAborted.EndCommit(InternalTransaction tx)
at System.Transactions.CommittableTransaction.Commit()
at System.Transactions.TransactionScope.InternalDispose()
at System.Transactions.TransactionScope.Dispose()
```

### Regression?

_No response_

### Known Workarounds

Ensure that the transaction is already promoted before Complete() is called.

### Configuration

.NET 10 (can also be reproduced with .NET 472)
Windows 11 x64 (I currently don't have another machine available to test)

### Other information

I assume that the code used to work with a MsSql server and broke after switching to AzureSql (it appeared in an old unit test which was originally written for an MsSql server that hasn't run for several years).

Contributor guide

Open the contributing guide

Research direction

Run the EnlistDuringPrepared.zip reproduction on .NET 10 and observe the timeout during TransactionScope disposal. Start with the System.Transactions commit path around IEnlistmentNotification.Prepare(), comparing promotion during the callback with the openDuringPrepare=false workaround. Done means a non-MSDTC promotion during phase-0 prepare completes without the 60-second Transaction Timeout.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, csharp, sql
Domain
backend, databases, distributed-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.