5.1.2 + System.Transactions with volatile resource manager gives "The operation is not valid for the current state of the enlistment" exceptions under concurrent load #2262
Description
Describe the bug
After upgrading to EFCore 8 from EFCore 6 we started seeing "System.InvalidOperationException: The operation is not valid for the current state of the enlistment." errors in our logfiles.
A bit of research has isolated the issue to the increased version of the SqlClient transitive dependency, specifically the update from 5.0.1 to 5.0.2. The issue is present on the latest 5.1.2 version also. Included below is a sample application that reproduces the issue.
In low volume testing the issue didn't appear, but under moderate load appeared reliably.
The full exception we are seeing is below:
System.InvalidOperationException: The operation is not valid for the current state of the enlistment.
at System.Transactions.EnlistmentState.Committed(InternalEnlistment enlistment)
at System.Transactions.SinglePhaseEnlistment.Committed()
at Microsoft.Data.SqlClient.SqlDelegatedTransaction.SinglePhaseCommit(SinglePhaseEnlistment enlistment)
at System.Transactions.DurableEnlistmentCommitting.EnterState(InternalEnlistment enlistment)
at System.Transactions.PreparingEnlistment.Prepared()
at MyEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment) in E:\Src\_scratch\ConsoleApp3\Program.cs:line 36
at System.Transactions.VolatileEnlistmentPreparing.EnterState(InternalEnlistment enlistment)
at System.Transactions.TransactionStateVolatilePhase1.EnterState(InternalTransaction tx)
at System.Transactions.CommittableTransaction.Commit()
at System.Transactions.TransactionScope.InternalDispose()
at System.Transactions.TransactionScope.Dispose()
at Program.<>c.<<<Main>$>b__0_0>d.MoveNext() in E:\Src\_scratch\ConsoleApp3\Program.cs:line 18
--- End of stack trace from previous location ---
at Program.<Main>$(String[] args) in E:\Src\_scratch\ConsoleApp3\Program.cs:line 24
at Program.<Main>(String[] args)
To reproduce
A complete console app is shown below that reproduces the issue reliably. Swapping between the two referenced versions of Microsoft.Data.SqlClient should demonstrate it working or failing in 5.0.1 and 5.0.2 repectively.
using System.Transactions;
using Microsoft.Data.SqlClient;
List<Task> tasks = [];
for (int i = 0; i < 1000; ++i)
{
var task = Task.Run(async () =>
{
using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
SqlConnection conn = new("...");
await conn.OpenAsync();
conn.Close();
Transaction.Current.EnlistVolatile(new MyEnlistmentNotification(), EnlistmentOptions.None);
tx.Complete();
}
});
tasks.Add(task);
}
await Task.WhenAll(tasks);
internal class MyEnlistmentNotification : IEnlistmentNotification
{
public void Commit(Enlistment enlistment)
{
Thread.Sleep(500);
enlistment.Done();
}
public void InDoubt(Enlistment enlistment) => enlistment.Done();
public void Prepare(PreparingEnlistment preparingEnlistment) => preparingEnlistment.Prepared();
public void Rollback(Enlistment enlistment) => enlistment.Done();
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<!--Broke-->
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.0.2" />
<!--Works-->
<!--<PackageReference Include="Microsoft.Data.SqlClient" Version="5.0.1" />-->
</ItemGroup>
</Project>
Expected behavior
The same behaviour in 5.0.2 and greater as in 5.0.1.
Further technical details
Microsoft.Data.SqlClient version: 5.0.2 / 5.1.2
.NET target: net8
SQL Server version: SQL Server 2016/2022
Operating system: net8 debian docker image & Windows 11
Additional context
Currently this prevents us migrating to any EFCore versions > 7.0.5 since they all require a SqlClient dependency > 5.0.1
Metadata
Assignees
Labels
Type
Projects
Status
Closed
Activity