Skip to content

Commit a73c7ee

Browse files
[3.1.6] | Fix | Invalid transaction exception against the connections and distributed transactions (#2301) (#2434)
1 parent 254d76d commit a73c7ee

File tree

4 files changed

+24
-15
lines changed

4 files changed

+24
-15
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ internal abstract partial class DbConnectionInternal
1616
{
1717
private static int _objectTypeCount;
1818
internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount);
19+
private TransactionCompletedEventHandler _transactionCompletedEventHandler = null;
1920

2021
private bool _isInStasis;
2122

@@ -437,15 +438,19 @@ internal void DetachTransaction(Transaction transaction, bool isExplicitlyReleas
437438
// potentially a multi-threaded event, so lock the connection to make sure we don't enlist in a new
438439
// transaction between compare and assignment. No need to short circuit outside of lock, since failed comparisons should
439440
// be the exception, not the rule.
440-
lock (this)
441+
// locking on anything other than the transaction object would lead to a thread deadlock with sys.Transaction.TransactionCompleted event.
442+
lock (transaction)
441443
{
442444
// Detach if detach-on-end behavior, or if outer connection was closed
443445
DbConnection owner = (DbConnection)Owner;
444-
if (isExplicitlyReleasing || UnbindOnTransactionCompletion || null == owner)
446+
if (isExplicitlyReleasing || UnbindOnTransactionCompletion || owner is null)
445447
{
446448
Transaction currentEnlistedTransaction = _enlistedTransaction;
447449
if (currentEnlistedTransaction != null && transaction.Equals(currentEnlistedTransaction))
448450
{
451+
// We need to remove the transaction completed event handler to cease listening for the transaction to end.
452+
currentEnlistedTransaction.TransactionCompleted -= _transactionCompletedEventHandler;
453+
449454
EnlistedTransaction = null;
450455

451456
if (IsTxRootWaitingForTxEnd)
@@ -479,7 +484,8 @@ void TransactionCompletedEvent(object sender, TransactionEventArgs e)
479484

480485
private void TransactionOutcomeEnlist(Transaction transaction)
481486
{
482-
transaction.TransactionCompleted += new TransactionCompletedEventHandler(TransactionCompletedEvent);
487+
_transactionCompletedEventHandler ??= new TransactionCompletedEventHandler(TransactionCompletedEvent);
488+
transaction.TransactionCompleted += _transactionCompletedEventHandler;
483489
}
484490

485491
internal void SetInStasis()

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,17 +155,17 @@ public byte[] Promote()
155155
ValidateActiveOnConnection(connection);
156156

157157
connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true);
158-
returnValue = _connection.PromotedDTCToken;
158+
returnValue = connection.PromotedDTCToken;
159159

160160
// For Global Transactions, we need to set the Transaction Id since we use a Non-MSDTC Promoter type.
161-
if (_connection.IsGlobalTransaction)
161+
if (connection.IsGlobalTransaction)
162162
{
163163
if (SysTxForGlobalTransactions.SetDistributedTransactionIdentifier == null)
164164
{
165165
throw SQL.UnsupportedSysTxForGlobalTransactions();
166166
}
167167

168-
if (!_connection.IsGlobalTransactionsEnabledForServer)
168+
if (!connection.IsGlobalTransactionsEnabledForServer)
169169
{
170170
throw SQL.GlobalTransactionsNotEnabled();
171171
}

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ namespace Microsoft.Data.ProviderBase
1818
using SysTx = System.Transactions;
1919

2020
internal abstract class DbConnectionInternal
21-
{ // V1.1.3300
22-
23-
21+
{
2422
private static int _objectTypeCount;
2523
internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount);
24+
private SysTx.TransactionCompletedEventHandler _transactionCompletedEventHandler = null;
2625

2726
internal static readonly StateChangeEventArgs StateChangeClosed = new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed);
2827
internal static readonly StateChangeEventArgs StateChangeOpen = new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open);
@@ -899,15 +898,18 @@ internal void DetachTransaction(SysTx.Transaction transaction, bool isExplicitly
899898
// potentially a multi-threaded event, so lock the connection to make sure we don't enlist in a new
900899
// transaction between compare and assignment. No need to short circuit outside of lock, since failed comparisons should
901900
// be the exception, not the rule.
902-
lock (this)
901+
// locking on anything other than the transaction object would lead to a thread deadlock with sys.Transaction.TransactionCompleted event.
902+
lock (transaction)
903903
{
904904
// Detach if detach-on-end behavior, or if outer connection was closed
905905
DbConnection owner = (DbConnection)Owner;
906-
if (isExplicitlyReleasing || UnbindOnTransactionCompletion || null == owner)
906+
if (isExplicitlyReleasing || UnbindOnTransactionCompletion || owner is null)
907907
{
908908
SysTx.Transaction currentEnlistedTransaction = _enlistedTransaction;
909909
if (currentEnlistedTransaction != null && transaction.Equals(currentEnlistedTransaction))
910910
{
911+
// We need to remove the transaction completed event handler to cease listening for the transaction to end.
912+
currentEnlistedTransaction.TransactionCompleted -= _transactionCompletedEventHandler;
911913

912914
EnlistedTransaction = null;
913915

@@ -946,7 +948,8 @@ void TransactionCompletedEvent(object sender, SysTx.TransactionEventArgs e)
946948
[SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode)]
947949
private void TransactionOutcomeEnlist(SysTx.Transaction transaction)
948950
{
949-
transaction.TransactionCompleted += new SysTx.TransactionCompletedEventHandler(TransactionCompletedEvent);
951+
_transactionCompletedEventHandler ??= new SysTx.TransactionCompletedEventHandler(TransactionCompletedEvent);
952+
transaction.TransactionCompleted += _transactionCompletedEventHandler;
950953
}
951954

952955
internal void SetInStasis()

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,17 +192,17 @@ public Byte[] Promote()
192192
ValidateActiveOnConnection(connection);
193193

194194
connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, IsolationLevel.Unspecified, _internalTransaction, true);
195-
returnValue = _connection.PromotedDTCToken;
195+
returnValue = connection.PromotedDTCToken;
196196

197197
// For Global Transactions, we need to set the Transaction Id since we use a Non-MSDTC Promoter type.
198-
if (_connection.IsGlobalTransaction)
198+
if (connection.IsGlobalTransaction)
199199
{
200200
if (SysTxForGlobalTransactions.SetDistributedTransactionIdentifier == null)
201201
{
202202
throw SQL.UnsupportedSysTxForGlobalTransactions();
203203
}
204204

205-
if (!_connection.IsGlobalTransactionsEnabledForServer)
205+
if (!connection.IsGlobalTransactionsEnabledForServer)
206206
{
207207
throw SQL.GlobalTransactionsNotEnabled();
208208
}

0 commit comments

Comments
 (0)