Skip to content

Commit

Permalink
Merged PR 4120: [5.1.4] Backport #2161 - Fix deadlock in transaction …
Browse files Browse the repository at this point in the history
…against .NET 7

Backport [GH PR #2161](#1242) - Fix deadlock in transaction against .NET 7

Related work items: #1242
  • Loading branch information
David-Engel committed Nov 3, 2023
1 parent b77f09e commit 5cc6ca8
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -348,31 +348,34 @@ public void SinglePhaseCommit(SinglePhaseEnlistment enlistment)
#endif
try
{
Exception commitException = null;

lock (connection)
// If the connection is doomed, we can be certain that the
// transaction will eventually be rolled back or has already been aborted externally, and we shouldn't
// attempt to commit it.
if (connection.IsConnectionDoomed)
{
// If the connection is doomed, we can be certain that the
// transaction will eventually be rolled back or has already been aborted externally, and we shouldn't
// attempt to commit it.
if (connection.IsConnectionDoomed)
lock (connection)
{
_active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done.
_connection = null;

enlistment.Aborted(SQL.ConnectionDoomed());
}
else

enlistment.Aborted(SQL.ConnectionDoomed());
}
else
{
Exception commitException;
lock (connection)
{
try
{
// Now that we've acquired the lock, make sure we still have valid state for this operation.
ValidateActiveOnConnection(connection);

_active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done.
_connection = null; // Set prior to ExecuteTransaction call in case this initiates a TransactionEnd event
_connection = null; // Set prior to ExecuteTransaction call in case this initiates a TransactionEnd event

connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true);
commitException = null;
}
catch (SqlException e)
{
Expand All @@ -391,42 +394,41 @@ public void SinglePhaseCommit(SinglePhaseEnlistment enlistment)
ADP.TraceExceptionWithoutRethrow(e);
connection.DoomThisConnection();
}
if (commitException != null)
}
if (commitException != null)
{
// connection.ExecuteTransaction failed with exception
if (_internalTransaction.IsCommitted)
{
// connection.ExecuteTransaction failed with exception
if (_internalTransaction.IsCommitted)
{
// Even though we got an exception, the transaction
// was committed by the server.
enlistment.Committed();
}
else if (_internalTransaction.IsAborted)
{
// The transaction was aborted, report that to
// SysTx.
enlistment.Aborted(commitException);
}
else
{
// The transaction is still active, we cannot
// know the state of the transaction.
enlistment.InDoubt(commitException);
}

// We eat the exception. This is called on the SysTx
// thread, not the applications thread. If we don't
// eat the exception an UnhandledException will occur,
// causing the process to FailFast.
// Even though we got an exception, the transaction
// was committed by the server.
enlistment.Committed();
}
else if (_internalTransaction.IsAborted)
{
// The transaction was aborted, report that to
// SysTx.
enlistment.Aborted(commitException);
}
else
{
// The transaction is still active, we cannot
// know the state of the transaction.
enlistment.InDoubt(commitException);
}

connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction);
// We eat the exception. This is called on the SysTx
// thread, not the applications thread. If we don't
// eat the exception an UnhandledException will occur,
// causing the process to FailFast.
}
}

if (commitException == null)
{
// connection.ExecuteTransaction succeeded
enlistment.Committed();
connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction);
if (commitException == null)
{
// connection.ExecuteTransaction succeeded
enlistment.Committed();
}
}
}
catch (System.OutOfMemoryException e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,6 @@ public void SinglePhaseCommit(SysTx.SinglePhaseEnlistment enlistment)
Debug.Assert(null != enlistment, "null enlistment?");
SqlInternalConnection connection = GetValidConnection();


if (null != connection)
{
SqlConnection usersConnection = connection.Connection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,17 @@ public static string GetUniqueNameForSqlServer(string prefix, bool withBracket =
return name;
}

public static bool IsSupportingDistributedTransactions()
{
#if NET7_0_OR_GREATER
return OperatingSystem.IsWindows() && System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture != System.Runtime.InteropServices.Architecture.X86 && IsNotAzureServer();
#elif NETFRAMEWORK
return IsNotAzureServer();
#else
return false;
#endif
}

public static void DropTable(SqlConnection sqlConnection, string tableName)
{
ResurrectConnection(sqlConnection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,10 @@ public static void TestManualEnlistment_Enlist_TxScopeComplete()
RunTestSet(TestCase_ManualEnlistment_Enlist_TxScopeComplete);
}

[SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp)]
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))]
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSupportingDistributedTransactions))]
public static void TestEnlistmentPrepare_TxScopeComplete()
{
try
Assert.Throws<TransactionAbortedException>(() =>
{
using TransactionScope txScope = new(TransactionScopeOption.RequiresNew, new TransactionOptions()
{
Expand All @@ -64,11 +63,7 @@ public static void TestEnlistmentPrepare_TxScopeComplete()
System.Transactions.Transaction.Current.EnlistDurable(EnlistmentForPrepare.s_id, new EnlistmentForPrepare(), EnlistmentOptions.None);
txScope.Complete();
Assert.False(true, "Expected exception not thrown.");
}
catch (Exception e)
{
Assert.True(e is TransactionAbortedException);
}
});
}

private static void TestCase_AutoEnlistment_TxScopeComplete()
Expand Down

0 comments on commit 5cc6ca8

Please sign in to comment.