Skip to content

Commit e2514f7

Browse files
committed
Ports Fix deadlock in transaction (dotnet#1242) to .NET
1 parent c1984b8 commit e2514f7

File tree

2 files changed

+49
-45
lines changed

2 files changed

+49
-45
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs

+49-44
Original file line numberDiff line numberDiff line change
@@ -348,31 +348,36 @@ public void SinglePhaseCommit(SinglePhaseEnlistment enlistment)
348348
#endif
349349
try
350350
{
351-
Exception commitException = null;
352-
353-
lock (connection)
351+
// If the connection is doomed, we can be certain that the
352+
// transaction will eventually be rolled back or has already been aborted externally, and we shouldn't
353+
// attempt to commit it.
354+
if (connection.IsConnectionDoomed)
354355
{
355-
// If the connection is doomed, we can be certain that the
356-
// transaction will eventually be rolled back or has already been aborted externally, and we shouldn't
357-
// attempt to commit it.
358-
if (connection.IsConnectionDoomed)
356+
lock (connection)
359357
{
360358
_active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done.
361359
_connection = null;
362-
363-
enlistment.Aborted(SQL.ConnectionDoomed());
364360
}
365-
else
361+
362+
enlistment.Aborted(SQL.ConnectionDoomed());
363+
}
364+
else
365+
{
366+
Exception commitException = null;
367+
lock (connection)
366368
{
367369
try
368370
{
369371
// Now that we've acquired the lock, make sure we still have valid state for this operation.
370372
ValidateActiveOnConnection(connection);
371373

372-
_active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done.
373-
_connection = null; // Set prior to ExecuteTransaction call in case this initiates a TransactionEnd event
374+
_active =
375+
false; // set to inactive first, doesn't matter how the rest completes, this transaction is done.
376+
_connection =
377+
null; // Set prior to ExecuteTransaction call in case this initiates a TransactionEnd event
374378

375-
connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true);
379+
connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null,
380+
System.Data.IsolationLevel.Unspecified, _internalTransaction, true);
376381
}
377382
catch (SqlException e)
378383
{
@@ -391,42 +396,42 @@ public void SinglePhaseCommit(SinglePhaseEnlistment enlistment)
391396
ADP.TraceExceptionWithoutRethrow(e);
392397
connection.DoomThisConnection();
393398
}
394-
if (commitException != null)
399+
}
400+
if (commitException != null)
401+
{
402+
// connection.ExecuteTransaction failed with exception
403+
if (_internalTransaction.IsCommitted)
395404
{
396-
// connection.ExecuteTransaction failed with exception
397-
if (_internalTransaction.IsCommitted)
398-
{
399-
// Even though we got an exception, the transaction
400-
// was committed by the server.
401-
enlistment.Committed();
402-
}
403-
else if (_internalTransaction.IsAborted)
404-
{
405-
// The transaction was aborted, report that to
406-
// SysTx.
407-
enlistment.Aborted(commitException);
408-
}
409-
else
410-
{
411-
// The transaction is still active, we cannot
412-
// know the state of the transaction.
413-
enlistment.InDoubt(commitException);
414-
}
415-
416-
// We eat the exception. This is called on the SysTx
417-
// thread, not the applications thread. If we don't
418-
// eat the exception an UnhandledException will occur,
419-
// causing the process to FailFast.
405+
// Even though we got an exception, the transaction
406+
// was committed by the server.
407+
enlistment.Committed();
408+
}
409+
else if (_internalTransaction.IsAborted)
410+
{
411+
// The transaction was aborted, report that to
412+
// SysTx.
413+
enlistment.Aborted(commitException);
414+
}
415+
else
416+
{
417+
// The transaction is still active, we cannot
418+
// know the state of the transaction.
419+
enlistment.InDoubt(commitException);
420420
}
421421

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

426-
if (commitException == null)
427-
{
428-
// connection.ExecuteTransaction succeeded
429-
enlistment.Committed();
428+
connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction);
429+
430+
if (commitException == null)
431+
{
432+
// connection.ExecuteTransaction succeeded
433+
enlistment.Committed();
434+
}
430435
}
431436
}
432437
catch (System.OutOfMemoryException e)

src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionEnlistmentTest.cs

-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ public static void TestManualEnlistment_Enlist_TxScopeComplete()
4747
RunTestSet(TestCase_ManualEnlistment_Enlist_TxScopeComplete);
4848
}
4949

50-
[SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp)]
5150
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))]
5251
public static void TestEnlistmentPrepare_TxScopeComplete()
5352
{

0 commit comments

Comments
 (0)