diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs index acfe827b48..6a4ede69fd 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs @@ -137,79 +137,92 @@ public byte[] Promote() // from an operational standpoint (i.e. logging connection's ObjectID should be fine, // but the PromotedDTCToken can change over calls. so that must be protected). SqlInternalConnection connection = GetValidConnection(); - Exception promoteException; byte[] returnValue = null; - SqlConnection usersConnection = connection.Connection; - SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, promoting transaction.", ObjectID, connection.ObjectID); - RuntimeHelpers.PrepareConstrainedRegions(); - try + + if (null != connection) { - lock (connection) + SqlConnection usersConnection = connection.Connection; + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, promoting transaction.", ObjectID, connection.ObjectID); + RuntimeHelpers.PrepareConstrainedRegions(); + try { - try + lock (connection) { - // Now that we've acquired the lock, make sure we still have valid state for this operation. - ValidateActiveOnConnection(connection); + try + { + // Now that we've acquired the lock, make sure we still have valid state for this operation. + ValidateActiveOnConnection(connection); - connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true); - returnValue = _connection.PromotedDTCToken; + connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true); + returnValue = _connection.PromotedDTCToken; - // For Global Transactions, we need to set the Transaction Id since we use a Non-MSDTC Promoter type. - if (_connection.IsGlobalTransaction) - { - if (SysTxForGlobalTransactions.SetDistributedTransactionIdentifier == null) + // For Global Transactions, we need to set the Transaction Id since we use a Non-MSDTC Promoter type. + if (_connection.IsGlobalTransaction) { - throw SQL.UnsupportedSysTxForGlobalTransactions(); - } + if (SysTxForGlobalTransactions.SetDistributedTransactionIdentifier == null) + { + throw SQL.UnsupportedSysTxForGlobalTransactions(); + } - if (!_connection.IsGlobalTransactionsEnabledForServer) - { - throw SQL.GlobalTransactionsNotEnabled(); + if (!_connection.IsGlobalTransactionsEnabledForServer) + { + throw SQL.GlobalTransactionsNotEnabled(); + } + + SysTxForGlobalTransactions.SetDistributedTransactionIdentifier.Invoke(_atomicTransaction, new object[] { this, GetGlobalTxnIdentifierFromToken() }); } - SysTxForGlobalTransactions.SetDistributedTransactionIdentifier.Invoke(_atomicTransaction, new object[] { this, GetGlobalTxnIdentifierFromToken() }); + promoteException = null; } + catch (SqlException e) + { + promoteException = e; - promoteException = null; + // Doom the connection, to make sure that the transaction is + // eventually rolled back. + // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event + connection.DoomThisConnection(); + } + catch (InvalidOperationException e) + { + promoteException = e; + connection.DoomThisConnection(); + } } - catch (SqlException e) - { - promoteException = e; + } + catch (System.OutOfMemoryException e) + { + usersConnection.Abort(e); + throw; + } + catch (System.StackOverflowException e) + { + usersConnection.Abort(e); + throw; + } + catch (System.Threading.ThreadAbortException e) + { + usersConnection.Abort(e); + throw; + } - // Doom the connection, to make sure that the transaction is - // eventually rolled back. - // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event - connection.DoomThisConnection(); - } - catch (InvalidOperationException e) - { - promoteException = e; - connection.DoomThisConnection(); - } + //Throw exception only if Transaction is till active and not yet aborted. + if (promoteException != null && Transaction.TransactionInformation.Status != TransactionStatus.Aborted) + { + throw SQL.PromotionFailed(promoteException); + } + else + { + // The transaction was aborted externally, since it's already doomed above, we only log the same. + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, aborted during promotion.", ObjectID, connection.ObjectID); } } - catch (System.OutOfMemoryException e) - { - usersConnection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - usersConnection.Abort(e); - throw; - } - catch (System.Threading.ThreadAbortException e) - { - usersConnection.Abort(e); - throw; - } - - if (promoteException != null) + else { - throw SQL.PromotionFailed(promoteException); + // The transaction was aborted externally, doom the connection to make sure it's eventually rolled back and log the same. + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, aborted before promoting.", ObjectID, connection.ObjectID); } - return returnValue; } @@ -217,72 +230,81 @@ public byte[] Promote() public void Rollback(SinglePhaseEnlistment enlistment) { Debug.Assert(null != enlistment, "null enlistment?"); - SqlInternalConnection connection = GetValidConnection(); - SqlConnection usersConnection = connection.Connection; - SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, aborting transaction.", ObjectID, connection.ObjectID); - RuntimeHelpers.PrepareConstrainedRegions(); - try + + if (null != connection) { - lock (connection) + SqlConnection usersConnection = connection.Connection; + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, rolling back transaction.", ObjectID, connection.ObjectID); + RuntimeHelpers.PrepareConstrainedRegions(); + try { - try + lock (connection) { - // 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 execute completes, this transaction is done. - _connection = null; // Set prior to ExecuteTransaction call in case this initiates a TransactionEnd event + 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 execute completes, this transaction is done. + _connection = null; // Set prior to ExecuteTransaction call in case this initiates a TransactionEnd event - // If we haven't already rolled back (or aborted) then tell the SQL Server to roll back - if (!_internalTransaction.IsAborted) + // If we haven't already rolled back (or aborted) then tell the SQL Server to roll back + if (!_internalTransaction.IsAborted) + { + connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Rollback, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true); + } + } + catch (SqlException) { - connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Rollback, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true); + // Doom the connection, to make sure that the transaction is + // eventually rolled back. + // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event + connection.DoomThisConnection(); + + // Unlike SinglePhaseCommit, a rollback is a rollback, regardless + // of how it happens, so SysTx won't throw an exception, and we + // don't want to throw an exception either, because SysTx isn't + // handling it and it may create a fail fast scenario. In the end, + // there is no way for us to communicate to the consumer that this + // failed for more serious reasons than usual. + // + // This is a bit like "should you throw if Close fails", however, + // it only matters when you really need to know. In that case, + // we have the tracing that we're doing to fallback on for the + // investigation. + } + catch (InvalidOperationException) + { + connection.DoomThisConnection(); } } - catch (SqlException) - { - // Doom the connection, to make sure that the transaction is - // eventually rolled back. - // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event - connection.DoomThisConnection(); - - // Unlike SinglePhaseCommit, a rollback is a rollback, regardless - // of how it happens, so SysTx won't throw an exception, and we - // don't want to throw an exception either, because SysTx isn't - // handling it and it may create a fail fast scenario. In the end, - // there is no way for us to communicate to the consumer that this - // failed for more serious reasons than usual. - // - // This is a bit like "should you throw if Close fails", however, - // it only matters when you really need to know. In that case, - // we have the tracing that we're doing to fallback on for the - // investigation. - } - catch (InvalidOperationException) - { - connection.DoomThisConnection(); - } - } - // it doesn't matter whether the rollback succeeded or not, we presume - // that the transaction is aborted, because it will be eventually. - connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); - enlistment.Aborted(); - } - catch (System.OutOfMemoryException e) - { - usersConnection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - usersConnection.Abort(e); - throw; + // it doesn't matter whether the rollback succeeded or not, we presume + // that the transaction is aborted, because it will be eventually. + connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); + enlistment.Aborted(); + } + catch (System.OutOfMemoryException e) + { + usersConnection.Abort(e); + throw; + } + catch (System.StackOverflowException e) + { + usersConnection.Abort(e); + throw; + } + catch (System.Threading.ThreadAbortException e) + { + usersConnection.Abort(e); + throw; + } } - catch (System.Threading.ThreadAbortException e) + else { - usersConnection.Abort(e); - throw; + // The transaction was aborted, report that to SysTx and log the same. + enlistment.Aborted(); + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, aborted before rollback.", ObjectID, connection.ObjectID); } } @@ -290,107 +312,116 @@ public void Rollback(SinglePhaseEnlistment enlistment) public void SinglePhaseCommit(SinglePhaseEnlistment enlistment) { Debug.Assert(null != enlistment, "null enlistment?"); - SqlInternalConnection connection = GetValidConnection(); - SqlConnection usersConnection = connection.Connection; - SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, committing transaction.", ObjectID, connection.ObjectID); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - // If the connection is dooomed, we can be certain that the - // transaction will eventually be rolled back, 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 + if (null != connection) + { + SqlConnection usersConnection = connection.Connection; + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, committing transaction.", ObjectID, connection.ObjectID); + RuntimeHelpers.PrepareConstrainedRegions(); + try { - Exception commitException; - lock (connection) + // If the connection is dooomed, we can be certain that the + // transaction will eventually be rolled back, and we shouldn't + // attempt to commit it. + if (connection.IsConnectionDoomed) { - try + lock (connection) { - // 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.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true); - commitException = null; + _connection = null; } - catch (SqlException e) - { - commitException = e; - // Doom the connection, to make sure that the transaction is - // eventually rolled back. - // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event - connection.DoomThisConnection(); - } - catch (InvalidOperationException e) - { - commitException = e; - connection.DoomThisConnection(); - } + enlistment.Aborted(SQL.ConnectionDoomed()); } - if (commitException != null) + else { - // connection.ExecuteTransaction failed with exception - if (_internalTransaction.IsCommitted) + Exception commitException; + lock (connection) { - // Even though we got an exception, the transaction - // was committed by the server. - enlistment.Committed(); + 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.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true); + commitException = null; + } + catch (SqlException e) + { + commitException = e; + + // Doom the connection, to make sure that the transaction is + // eventually rolled back. + // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event + connection.DoomThisConnection(); + } + catch (InvalidOperationException e) + { + commitException = e; + connection.DoomThisConnection(); + } } - else if (_internalTransaction.IsAborted) + if (commitException != null) { - // The transaction was aborted, report that to - // SysTx. - enlistment.Aborted(commitException); + // 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. } - else + + connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); + if (commitException == null) { - // The transaction is still active, we cannot - // know the state of the transaction. - enlistment.InDoubt(commitException); + // connection.ExecuteTransaction succeeded + enlistment.Committed(); } - - // 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. - } - - connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); - if (commitException == null) - { - // connection.ExecuteTransaction succeeded - enlistment.Committed(); } } + catch (System.OutOfMemoryException e) + { + usersConnection.Abort(e); + throw; + } + catch (System.StackOverflowException e) + { + usersConnection.Abort(e); + throw; + } + catch (System.Threading.ThreadAbortException e) + { + usersConnection.Abort(e); + throw; + } } - catch (System.OutOfMemoryException e) - { - usersConnection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - usersConnection.Abort(e); - throw; - } - catch (System.Threading.ThreadAbortException e) + else { - usersConnection.Abort(e); - throw; + // The transaction was aborted before we could commit, report that to SysTx and log the same. + enlistment.Aborted(); + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, aborted before commit.", ObjectID, connection.ObjectID); } } @@ -413,6 +444,8 @@ internal void TransactionEnded(Transaction transaction) _active = false; _connection = null; } + // Safest approach is to doom this connection, whose transaction has been aborted externally. + connection.DoomThisConnection(); } } } @@ -421,7 +454,7 @@ internal void TransactionEnded(Transaction transaction) private SqlInternalConnection GetValidConnection() { SqlInternalConnection connection = _connection; - if (null == connection) + if (null == connection && Transaction.TransactionInformation.Status != TransactionStatus.Aborted) { throw ADP.ObjectDisposed(this); } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs index 21a5966f3e..d80a7a685a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs @@ -36,7 +36,6 @@ internal int ObjectID private SqlInternalConnection _connection; // the internal connection that is the root of the transaction private IsolationLevel _isolationLevel; // the IsolationLevel of the transaction we delegated to the server private SqlInternalTransaction _internalTransaction; // the SQL Server transaction we're delegating to - private SysTx.Transaction _atomicTransaction; private bool _active; // Is the transaction active? @@ -167,97 +166,110 @@ public Byte[] Promote() Exception promoteException; byte[] returnValue = null; - SqlConnection usersConnection = connection.Connection; - SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, promoting transaction.", ObjectID, connection.ObjectID); - RuntimeHelpers.PrepareConstrainedRegions(); - - try + if (null != connection) { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - + SqlConnection usersConnection = connection.Connection; + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, promoting transaction.", ObjectID, connection.ObjectID); RuntimeHelpers.PrepareConstrainedRegions(); + try { - tdsReliabilitySection.Start(); +#if DEBUG + TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + tdsReliabilitySection.Start(); #else { #endif //DEBUG - lock (connection) - { - try + lock (connection) { - // Now that we've acquired the lock, make sure we still have valid state for this operation. - ValidateActiveOnConnection(connection); + try + { + // Now that we've acquired the lock, make sure we still have valid state for this operation. + ValidateActiveOnConnection(connection); - connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, IsolationLevel.Unspecified, _internalTransaction, true); - returnValue = _connection.PromotedDTCToken; + connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, IsolationLevel.Unspecified, _internalTransaction, true); + returnValue = _connection.PromotedDTCToken; - // For Global Transactions, we need to set the Transaction Id since we use a Non-MSDTC Promoter type. - if (_connection.IsGlobalTransaction) - { - if (SysTxForGlobalTransactions.SetDistributedTransactionIdentifier == null) + // For Global Transactions, we need to set the Transaction Id since we use a Non-MSDTC Promoter type. + if (_connection.IsGlobalTransaction) { - throw SQL.UnsupportedSysTxForGlobalTransactions(); - } + if (SysTxForGlobalTransactions.SetDistributedTransactionIdentifier == null) + { + throw SQL.UnsupportedSysTxForGlobalTransactions(); + } - if (!_connection.IsGlobalTransactionsEnabledForServer) - { - throw SQL.GlobalTransactionsNotEnabled(); + if (!_connection.IsGlobalTransactionsEnabledForServer) + { + throw SQL.GlobalTransactionsNotEnabled(); + } + + SysTxForGlobalTransactions.SetDistributedTransactionIdentifier.Invoke(_atomicTransaction, new object[] { this, GetGlobalTxnIdentifierFromToken() }); } - SysTxForGlobalTransactions.SetDistributedTransactionIdentifier.Invoke(_atomicTransaction, new object[] { this, GetGlobalTxnIdentifierFromToken() }); + promoteException = null; } + catch (SqlException e) + { + promoteException = e; - promoteException = null; - } - catch (SqlException e) - { - promoteException = e; - - ADP.TraceExceptionWithoutRethrow(e); + ADP.TraceExceptionWithoutRethrow(e); - // Doom the connection, to make sure that the transaction is - // eventually rolled back. - // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event - connection.DoomThisConnection(); - } - catch (InvalidOperationException e) - { - promoteException = e; - ADP.TraceExceptionWithoutRethrow(e); - connection.DoomThisConnection(); + // Doom the connection, to make sure that the transaction is + // eventually rolled back. + // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event + connection.DoomThisConnection(); + } + catch (InvalidOperationException e) + { + promoteException = e; + ADP.TraceExceptionWithoutRethrow(e); + connection.DoomThisConnection(); + } } } - } #if DEBUG - finally + finally + { + tdsReliabilitySection.Stop(); + } +#endif //DEBUG + } + catch (System.OutOfMemoryException e) { - tdsReliabilitySection.Stop(); + usersConnection.Abort(e); + throw; + } + catch (System.StackOverflowException e) + { + usersConnection.Abort(e); + throw; + } + catch (System.Threading.ThreadAbortException e) + { + usersConnection.Abort(e); + throw; } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - usersConnection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - usersConnection.Abort(e); - throw; - } - catch (System.Threading.ThreadAbortException e) - { - usersConnection.Abort(e); - throw; - } - if (promoteException != null) + //Throw exception only if Transaction is till active and not yet aborted. + if (promoteException != null && Transaction.TransactionInformation.Status != SysTx.TransactionStatus.Aborted) + { + throw SQL.PromotionFailed(promoteException); + } + else + { + // The transaction was aborted externally, since it's already doomed above, we only log the same. + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, aborted during promote.", ObjectID, connection.ObjectID); + } + } + else { - throw SQL.PromotionFailed(promoteException); + // The transaction was aborted externally, doom the connection to make sure it's eventually rolled back and log the same. + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, aborted before promote.", ObjectID, connection.ObjectID); } - return returnValue; } @@ -265,24 +277,19 @@ public Byte[] Promote() public void Rollback(SysTx.SinglePhaseEnlistment enlistment) { Debug.Assert(null != enlistment, "null enlistment?"); - SqlInternalConnection connection = GetValidConnection(); - SqlConnection usersConnection = connection.Connection; - SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, aborting transaction.", ObjectID, connection.ObjectID); - RuntimeHelpers.PrepareConstrainedRegions(); - try + if (null != connection) { #if DEBUG TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - + tdsReliabilitySection.Start(); +#endif //DEBUG + SqlConnection usersConnection = connection.Connection; RuntimeHelpers.PrepareConstrainedRegions(); + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, rolling back transaction.", ObjectID, connection.ObjectID); try { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG lock (connection) { try @@ -331,6 +338,21 @@ public void Rollback(SysTx.SinglePhaseEnlistment enlistment) connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); enlistment.Aborted(); } + catch (System.OutOfMemoryException e) + { + usersConnection.Abort(e); + throw; + } + catch (System.StackOverflowException e) + { + usersConnection.Abort(e); + throw; + } + catch (System.Threading.ThreadAbortException e) + { + usersConnection.Abort(e); + throw; + } #if DEBUG finally { @@ -338,20 +360,11 @@ public void Rollback(SysTx.SinglePhaseEnlistment enlistment) } #endif //DEBUG } - catch (System.OutOfMemoryException e) - { - usersConnection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - usersConnection.Abort(e); - throw; - } - catch (System.Threading.ThreadAbortException e) + else { - usersConnection.Abort(e); - throw; + // The transaction was aborted, report that to SysTx and log the same. + enlistment.Aborted(); + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, aborted before rollback.", ObjectID, connection.ObjectID); } } @@ -359,128 +372,136 @@ public void Rollback(SysTx.SinglePhaseEnlistment enlistment) public void SinglePhaseCommit(SysTx.SinglePhaseEnlistment enlistment) { Debug.Assert(null != enlistment, "null enlistment?"); - SqlInternalConnection connection = GetValidConnection(); - SqlConnection usersConnection = connection.Connection; - SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, committing transaction.", ObjectID, connection.ObjectID); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + if (null != connection) + { + SqlConnection usersConnection = connection.Connection; + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, committing transaction.", ObjectID, connection.ObjectID); RuntimeHelpers.PrepareConstrainedRegions(); + try { - tdsReliabilitySection.Start(); +#if DEBUG + TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + try + { + tdsReliabilitySection.Start(); #else { #endif //DEBUG - // If the connection is dooomed, we can be certain that the - // transaction will eventually be rolled back, and we shouldn't - // attempt to commit it. - if (connection.IsConnectionDoomed) - { - lock (connection) + // If the connection is dooomed, we can be certain that the + // transaction will eventually be rolled back, and we shouldn't + // attempt to commit it. + if (connection.IsConnectionDoomed) { - _active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done. - _connection = null; - } + 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 - { - Exception commitException; - lock (connection) + enlistment.Aborted(SQL.ConnectionDoomed()); + } + else { - try + Exception commitException; + lock (connection) { - // Now that we've acquired the lock, make sure we still have valid state for this operation. - ValidateActiveOnConnection(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 + _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.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, IsolationLevel.Unspecified, _internalTransaction, true); - commitException = null; - } - catch (SqlException e) - { - commitException = e; + connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, IsolationLevel.Unspecified, _internalTransaction, true); + commitException = null; + } + catch (SqlException e) + { + commitException = e; - ADP.TraceExceptionWithoutRethrow(e); + ADP.TraceExceptionWithoutRethrow(e); - // Doom the connection, to make sure that the transaction is - // eventually rolled back. - // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event - connection.DoomThisConnection(); + // Doom the connection, to make sure that the transaction is + // eventually rolled back. + // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event + connection.DoomThisConnection(); + } + catch (InvalidOperationException e) + { + commitException = e; + ADP.TraceExceptionWithoutRethrow(e); + connection.DoomThisConnection(); + } } - catch (InvalidOperationException e) + if (commitException != null) { - commitException = e; - ADP.TraceExceptionWithoutRethrow(e); - connection.DoomThisConnection(); + // 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. } - } - if (commitException != null) - { - // connection.ExecuteTransaction failed with exception - if (_internalTransaction.IsCommitted) + + connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); + if (commitException == null) { - // Even though we got an exception, the transaction - // was committed by the server. + // connection.ExecuteTransaction succeeded 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. - } - - connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); - if (commitException == null) - { - // connection.ExecuteTransaction succeeded - enlistment.Committed(); } } - } #if DEBUG - finally + finally + { + tdsReliabilitySection.Stop(); + } +#endif //DEBUG + } + catch (System.OutOfMemoryException e) { - tdsReliabilitySection.Stop(); + usersConnection.Abort(e); + throw; + } + catch (System.StackOverflowException e) + { + usersConnection.Abort(e); + throw; + } + catch (System.Threading.ThreadAbortException e) + { + usersConnection.Abort(e); + throw; } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - usersConnection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - usersConnection.Abort(e); - throw; } - catch (System.Threading.ThreadAbortException e) + else { - usersConnection.Abort(e); - throw; + // The transaction was aborted before we could commit, report that to SysTx and log the same. + enlistment.Aborted(); + SqlClientEventSource.Log.TraceEvent(" {0}, Connection {1}, aborted before commit.", ObjectID, connection.ObjectID); } } @@ -504,6 +525,8 @@ internal void TransactionEnded(SysTx.Transaction transaction) _active = false; _connection = null; } + // Safest approach is to doom this connection, whose transaction has been aborted externally. + connection.DoomThisConnection(); } } } @@ -512,7 +535,7 @@ internal void TransactionEnded(SysTx.Transaction transaction) private SqlInternalConnection GetValidConnection() { SqlInternalConnection connection = _connection; - if (null == connection) + if (null == connection && _atomicTransaction.TransactionInformation.Status != SysTx.TransactionStatus.Aborted) { throw ADP.ObjectDisposed(this); }