Skip to content

Commit f8520c7

Browse files
author
Javad
authored
Fix | Invalid transaction exception against the connections and distributed transactions (#2301) (#2321)
1 parent b92637e commit f8520c7

File tree

4 files changed

+26
-17
lines changed

4 files changed

+26
-17
lines changed

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

Lines changed: 10 additions & 4 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
443-
DbConnection owner = (DbConnection)Owner;
444-
if (isExplicitlyReleasing || UnbindOnTransactionCompletion || null == owner)
445+
DbConnection owner = 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
@@ -159,17 +159,17 @@ public byte[] Promote()
159159
ValidateActiveOnConnection(connection);
160160

161161
connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true);
162-
returnValue = _connection.PromotedDTCToken;
162+
returnValue = connection.PromotedDTCToken;
163163

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

172-
if (!_connection.IsGlobalTransactionsEnabledForServer)
172+
if (!connection.IsGlobalTransactionsEnabledForServer)
173173
{
174174
throw SQL.GlobalTransactionsNotEnabled();
175175
}

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

Lines changed: 10 additions & 7 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);
@@ -900,15 +899,18 @@ internal void DetachTransaction(SysTx.Transaction transaction, bool isExplicitly
900899
// potentially a multi-threaded event, so lock the connection to make sure we don't enlist in a new
901900
// transaction between compare and assignment. No need to short circuit outside of lock, since failed comparisons should
902901
// be the exception, not the rule.
903-
lock (this)
902+
// locking on anything other than the transaction object would lead to a thread deadlock with sys.Transaction.TransactionCompleted event.
903+
lock (transaction)
904904
{
905905
// Detach if detach-on-end behavior, or if outer connection was closed
906-
DbConnection owner = (DbConnection)Owner;
907-
if (isExplicitlyReleasing || UnbindOnTransactionCompletion || null == owner)
906+
DbConnection owner = Owner;
907+
if (isExplicitlyReleasing || UnbindOnTransactionCompletion || owner is null)
908908
{
909909
SysTx.Transaction currentEnlistedTransaction = _enlistedTransaction;
910910
if (currentEnlistedTransaction != null && transaction.Equals(currentEnlistedTransaction))
911911
{
912+
// We need to remove the transaction completed event handler to cease listening for the transaction to end.
913+
currentEnlistedTransaction.TransactionCompleted -= _transactionCompletedEventHandler;
912914

913915
EnlistedTransaction = null;
914916

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

953956
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)