override protected void CleanupTransactionOnCompletion(SysTx.Transaction transaction) { // Note: unlocked, potentially multi-threaded code, so pull delegate to local to // ensure it doesn't change between test and call. SqlDelegatedTransaction delegatedTransaction = DelegatedTransaction; if (null != delegatedTransaction) { delegatedTransaction.TransactionEnded(transaction); } }
private void TransactionEndedByServer(long transactionId, TransactionState transactionState) { // Some extra steps required when the server initiates the ending of a transaction unilaterally // as opposed to the client initiating it. // Basically, we have to make the delegated transaction (if there is one) aware of the situation. SqlDelegatedTransaction delegatedTransaction = DelegatedTransaction; if (null != delegatedTransaction) { delegatedTransaction.Transaction.Rollback(); // just to make sure... DelegatedTransaction = null; // He's dead, Jim. } // Now handle the standard transaction-ended stuff. TransactionEnded(transactionId, transactionState); }
private void EnlistNonNull(SysTx.Transaction tx) { Debug.Assert(null != tx, "null transaction?"); SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnection.EnlistNonNull|ADV> {0}, transaction {1}.", ObjectID, tx.GetHashCode()); bool hasDelegatedTransaction = false; if (Is2005OrNewer) { SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnection.EnlistNonNull|ADV> {0}, attempting to delegate", ObjectID); // Promotable transactions are only supported on 2005 // servers or newer. SqlDelegatedTransaction delegatedTransaction = new SqlDelegatedTransaction(this, tx); try { // NOTE: System.Transactions claims to resolve all // potential race conditions between multiple delegate // requests of the same transaction to different // connections in their code, such that only one // attempt to delegate will succeed. // NOTE: PromotableSinglePhaseEnlist will eventually // make a round trip to the server; doing this inside // a lock is not the best choice. We presume that you // aren't trying to enlist concurrently on two threads // and leave it at that -- We don't claim any thread // safety with regard to multiple concurrent requests // to enlist the same connection in different // transactions, which is good, because we don't have // it anyway. // PromotableSinglePhaseEnlist may not actually promote // the transaction when it is already delegated (this is // the way they resolve the race condition when two // threads attempt to delegate the same Lightweight // Transaction) In that case, we can safely ignore // our delegated transaction, and proceed to enlist // in the promoted one. // NOTE: Global Transactions is an Azure SQL DB only // feature where the Transaction Manager (TM) is not // MS-DTC. Sys.Tx added APIs to support Non MS-DTC // promoter types/TM in .NET 4.6.1. Following directions // from .NETFX shiproom, to avoid a "hard-dependency" // (compile time) on Sys.Tx, we use reflection to invoke // the new APIs. Further, the _isGlobalTransaction flag // indicates that this is an Azure SQL DB Transaction // that could be promoted to a Global Transaction (it's // always false for on-prem Sql Server). The Promote() // call in SqlDelegatedTransaction makes sure that the // right Sys.Tx.dll is loaded and that Global Transactions // are actually allowed for this Azure SQL DB. if (_isGlobalTransaction) { if (SysTxForGlobalTransactions.EnlistPromotableSinglePhase == null) { // This could be a local Azure SQL DB transaction. hasDelegatedTransaction = tx.EnlistPromotableSinglePhase(delegatedTransaction); } else { hasDelegatedTransaction = (bool)SysTxForGlobalTransactions.EnlistPromotableSinglePhase.Invoke(tx, new object[] { delegatedTransaction, _globalTransactionTMID }); } } else { // This is an MS-DTC distributed transaction hasDelegatedTransaction = tx.EnlistPromotableSinglePhase(delegatedTransaction); } if (hasDelegatedTransaction) { this.DelegatedTransaction = delegatedTransaction; SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnection.EnlistNonNull|ADV> {0}, delegated to transaction {1} with transactionId=0x{2}", ObjectID, null != CurrentTransaction ? CurrentTransaction.ObjectID : 0, null != CurrentTransaction ? CurrentTransaction.TransactionId : SqlInternalTransaction.NullTransactionId); } } catch (SqlException e) { // we do not want to eat the error if it is a fatal one if (e.Class >= TdsEnums.FATAL_ERROR_CLASS) { throw; } // if the parser is null or its state is not openloggedin, the connection is no longer good. SqlInternalConnectionTds tdsConnection = this as SqlInternalConnectionTds; if (tdsConnection != null) { TdsParser parser = tdsConnection.Parser; if (parser == null || parser.State != TdsParserState.OpenLoggedIn) { throw; } } ADP.TraceExceptionWithoutRethrow(e); // In this case, SqlDelegatedTransaction.Initialize // failed and we don't necessarily want to reject // things -- there may have been a legitimate reason // for the failure. } } if (!hasDelegatedTransaction) { SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnection.EnlistNonNull|ADV> {0}, delegation not possible, enlisting.", ObjectID); byte[] cookie = null; if (_isGlobalTransaction) { if (SysTxForGlobalTransactions.GetPromotedToken == null) { throw SQL.UnsupportedSysTxForGlobalTransactions(); } cookie = (byte[])SysTxForGlobalTransactions.GetPromotedToken.Invoke(tx, null); } else { if (null == _whereAbouts) { byte[] dtcAddress = GetDTCAddress(); if (null == dtcAddress) { throw SQL.CannotGetDTCAddress(); } _whereAbouts = dtcAddress; } cookie = GetTransactionCookie(tx, _whereAbouts); } // send cookie to server to finish enlistment PropagateTransactionCookie(cookie); _isEnlistedInTransaction = true; SqlClientEventSource.Log.TryAdvancedTraceEvent("<sc.SqlInternalConnection.EnlistNonNull|ADV> {0}, enlisted with transaction {1} with transactionId=0x{2}", ObjectID, null != CurrentTransaction ? CurrentTransaction.ObjectID : 0, null != CurrentTransaction ? CurrentTransaction.TransactionId : SqlInternalTransaction.NullTransactionId); } EnlistedTransaction = tx; // Tell the base class about our enlistment // If we're on a 2005 or newer server, and we we delegate the // transaction successfully, we will have done a begin transaction, // which produces a transaction id that we should execute all requests // on. The TdsParser or SmiEventSink will store this information as // the current transaction. // // Likewise, propagating a transaction to a 2005 or newer server will // produce a transaction id that The TdsParser or SmiEventSink will // store as the current transaction. // // In either case, when we're working with a 2005 or newer server // we better have a current transaction by now. Debug.Assert(!Is2005OrNewer || null != CurrentTransaction, "delegated/enlisted transaction with null current transaction?"); }