internal static void DistributedTransactionOutcome(InternalTransaction tx, TransactionStatus status) { FinalizedObject fo = null; lock (tx) { if (null == tx._innerException) { tx._innerException = tx.PromotedTransaction.InnerException; } switch (status) { case TransactionStatus.Committed: { tx.State.ChangeStatePromotedCommitted(tx); break; } case TransactionStatus.Aborted: { tx.State.ChangeStatePromotedAborted(tx); break; } case TransactionStatus.InDoubt: { tx.State.InDoubtFromDtc(tx); break; } default: { Debug.Assert(false, "InternalTransaction.DistributedTransactionOutcome - Unexpected TransactionStatus"); TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceLtm, "", null, tx.DistributedTxId ); break; } } fo = tx._finalizedObject; } if (null != fo) { fo.Dispose(); } }
// Add a transaction to the table. Transactions are added to the end of the list in sorted order based on their // absolute timeout. internal int Add(InternalTransaction txNew) { // Tell the runtime that we are modifying global state. int readerIndex = 0; readerIndex = _rwLock.EnterReadLock(); try { // Start the timer if needed before checking the current time since the current // time can be more efficient with a running timer. if (txNew.AbsoluteTimeout != long.MaxValue) { if (!_timerEnabled) { if (!_timer.Change(_timerInterval, _timerInterval)) { throw TransactionException.CreateInvalidOperationException( TraceSourceType.TraceSourceLtm, SR.UnexpectedTimerFailure, null ); } _lastTimerTime = DateTime.UtcNow.Ticks; _timerEnabled = true; } } txNew.CreationTime = CurrentTime; AddIter(txNew); } finally { _rwLock.ExitReadLock(); } return(readerIndex); }
public void Complete() { TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this); } if (_disposed) { throw new ObjectDisposedException(nameof(TransactionScope)); } if (_complete) { throw TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceBase, SR.DisposeScope, null); } _complete = true; if (etwLog.IsEnabled()) { etwLog.MethodExit(TraceSourceType.TraceSourceBase, this); } }
protected static void PoolablePrepare(object state) { VolatileDemultiplexer demux = (VolatileDemultiplexer)state; // Don't block an enlistment thread (or a thread pool thread). So // try to get the transaction lock but if unsuccessfull give up and // queue this operation to try again later. bool tookLock = false; try { Monitor.TryEnter(demux._transaction, 250, ref tookLock); if (tookLock) { demux.InternalPrepare(); } else { if (!ThreadPool.QueueUserWorkItem(PrepareCallback, demux)) { throw TransactionException.CreateInvalidOperationException( TraceSourceType.TraceSourceLtm, SR.UnexpectedFailureOfThreadPool, null ); } } } finally { if (tookLock) { Monitor.Exit(demux._transaction); } } }
// Override of get_RecoveryInformation to be more specific with the exception string. internal override byte[] RecoveryInformation(InternalEnlistment enlistment) { throw TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceLtm, SR.VolEnlistNoRecoveryInfo, null, enlistment == null ? Guid.Empty : enlistment.DistributedTxId); }
// We don't have a finalizer (~TransactionScope) because all it would be able to do is try to // operate on other managed objects (the transaction), which is not safe to do because they may // already have been finalized. public void Dispose() { bool successful = false; TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this); } if (_disposed) { if (etwLog.IsEnabled()) { etwLog.MethodExit(TraceSourceType.TraceSourceBase, this); } return; } // Dispose for a scope can only be called on the thread where the scope was created. if ((_scopeThread != Thread.CurrentThread) && !AsyncFlowEnabled) { if (etwLog.IsEnabled()) { etwLog.InvalidOperation("TransactionScope", "InvalidScopeThread"); } throw new InvalidOperationException(SR.InvalidScopeThread); } Exception exToThrow = null; try { // Single threaded from this point _disposed = true; // First, lets pop the "stack" of TransactionScopes and dispose each one that is above us in // the stack, making sure they are NOT consistent before disposing them. // Optimize the first lookup by getting both the actual current scope and actual current // transaction at the same time. TransactionScope actualCurrentScope = _threadContextData.CurrentScope; Transaction current = Transaction.FastGetTransaction(actualCurrentScope, _threadContextData, out Transaction contextTransaction); if (!Equals(actualCurrentScope)) { // Ok this is bad. But just how bad is it. The worst case scenario is that someone is // poping scopes out of order and has placed a new transaction in the top level scope. // Check for that now. if (actualCurrentScope == null) { // Something must have gone wrong trying to clean up a bad scope // stack previously. // Make a best effort to abort the active transaction. Transaction rollbackTransaction = _committableTransaction; if (rollbackTransaction == null) { rollbackTransaction = _dependentTransaction; } Debug.Assert(rollbackTransaction != null); rollbackTransaction.Rollback(); successful = true; throw TransactionException.CreateInvalidOperationException( TraceSourceType.TraceSourceBase, SR.TransactionScopeInvalidNesting, null, rollbackTransaction.DistributedTxId); } // Verify that expectedCurrent is the same as the "current" current if we the interopOption value is None. else if (EnterpriseServicesInteropOption.None == actualCurrentScope._interopOption) { if (((null != actualCurrentScope._expectedCurrent) && (!actualCurrentScope._expectedCurrent.Equals(current))) || ((null != current) && (null == actualCurrentScope._expectedCurrent)) ) { TransactionTraceIdentifier myId; TransactionTraceIdentifier currentId; if (null == current) { currentId = TransactionTraceIdentifier.Empty; } else { currentId = current.TransactionTraceId; } if (null == _expectedCurrent) { myId = TransactionTraceIdentifier.Empty; } else { myId = _expectedCurrent.TransactionTraceId; } if (etwLog.IsEnabled()) { etwLog.TransactionScopeCurrentChanged(currentId, myId); } exToThrow = TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceBase, SR.TransactionScopeIncorrectCurrent, null, current == null ? Guid.Empty : current.DistributedTxId); // If there is a current transaction, abort it. if (null != current) { try { current.Rollback(); } catch (TransactionException) { // we are already going to throw and exception, so just ignore this one. } catch (ObjectDisposedException) { // Dito } } } } // Now fix up the scopes while (!Equals(actualCurrentScope)) { if (null == exToThrow) { exToThrow = TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceBase, SR.TransactionScopeInvalidNesting, null, current == null ? Guid.Empty : current.DistributedTxId); } if (null == actualCurrentScope._expectedCurrent) { if (etwLog.IsEnabled()) { etwLog.TransactionScopeNestedIncorrectly(TransactionTraceIdentifier.Empty); } } else { if (etwLog.IsEnabled()) { etwLog.TransactionScopeNestedIncorrectly(actualCurrentScope._expectedCurrent.TransactionTraceId); } } actualCurrentScope._complete = false; try { actualCurrentScope.InternalDispose(); } catch (TransactionException) { // we are already going to throw an exception, so just ignore this one. } actualCurrentScope = _threadContextData.CurrentScope; // We want to fail this scope, too, because work may have been done in one of these other // nested scopes that really should have been done in my scope. _complete = false; } } else { // Verify that expectedCurrent is the same as the "current" current if we the interopOption value is None. // If we got here, actualCurrentScope is the same as "this". if (EnterpriseServicesInteropOption.None == _interopOption) { if (((null != _expectedCurrent) && (!_expectedCurrent.Equals(current))) || ((null != current) && (null == _expectedCurrent)) ) { TransactionTraceIdentifier myId; TransactionTraceIdentifier currentId; if (null == current) { currentId = TransactionTraceIdentifier.Empty; } else { currentId = current.TransactionTraceId; } if (null == _expectedCurrent) { myId = TransactionTraceIdentifier.Empty; } else { myId = _expectedCurrent.TransactionTraceId; } if (etwLog.IsEnabled()) { etwLog.TransactionScopeCurrentChanged(currentId, myId); } if (null == exToThrow) { exToThrow = TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceBase, SR.TransactionScopeIncorrectCurrent, null, current == null ? Guid.Empty : current.DistributedTxId); } // If there is a current transaction, abort it. if (null != current) { try { current.Rollback(); } catch (TransactionException) { // we are already going to throw and exception, so just ignore this one. } catch (ObjectDisposedException) { // Dito } } // Set consistent to false so that the subsequent call to // InternalDispose below will rollback this.expectedCurrent. _complete = false; } } } successful = true; } finally { if (!successful) { PopScope(); } } // No try..catch here. Just let any exception thrown by InternalDispose go out. InternalDispose(); if (null != exToThrow) { throw exToThrow; } if (etwLog.IsEnabled()) { etwLog.MethodExit(TraceSourceType.TraceSourceBase, this); } }
// Process a timer event private void ThreadTimer(Object state) { // // Theory of operation. // // To timeout transactions we must walk down the list starting from the head // until we find a link with an absolute timeout that is greater than our own. // At that point everything further down in the list is elegable to be timed // out. So simply remove that link in the list and walk down from that point // timing out any transaction that is found. // // There could be a race between this callback being queued and the timer // being disabled. If we get here when the timer is disabled, just return. if (!_timerEnabled) { return; } // Increment the number of ticks _ticks++; _lastTimerTime = DateTime.UtcNow.Ticks; // // First find the starting point of transactions that should time out. Every transaction after // that point will timeout so once we've found it then it is just a matter of traversing the // structure. // BucketSet lastBucketSet = null; BucketSet currentBucketSet = _headBucketSet; // The list always has a head. // Acquire a writer lock before checking to see if we should disable the timer. // Adding of transactions acquires a reader lock and might insert a new BucketSet. // If that races with our check for a BucketSet existing, we may not timeout that // transaction that is being added. WeakReference nextWeakSet = null; BucketSet nextBucketSet = null; nextWeakSet = (WeakReference)currentBucketSet.nextSetWeak; if (nextWeakSet != null) { nextBucketSet = (BucketSet)nextWeakSet.Target; } if (nextBucketSet == null) { _rwLock.EnterWriteLock(); try { // Access the nextBucketSet again in writer lock to account for any race before disabling the timeout. nextWeakSet = (WeakReference)currentBucketSet.nextSetWeak; if (nextWeakSet != null) { nextBucketSet = (BucketSet)nextWeakSet.Target; } if (nextBucketSet == null) { // // Special case to allow for disabling the timer. // // If there are no transactions on the timeout list we can disable the // timer. if (!_timer.Change(Timeout.Infinite, Timeout.Infinite)) { throw TransactionException.CreateInvalidOperationException( TraceSourceType.TraceSourceLtm, SR.UnexpectedTimerFailure, null ); } _timerEnabled = false; return; } } finally { _rwLock.ExitWriteLock(); } } // Note it is slightly subtle that we always skip the head node. This is done // on purpose because the head node contains transactions with essentially // an infinite timeout. do { do { nextWeakSet = (WeakReference)currentBucketSet.nextSetWeak; if (nextWeakSet == null) { // Nothing more to do. return; } nextBucketSet = (BucketSet)nextWeakSet.Target; if (nextBucketSet == null) { // Again nothing more to do. return; } lastBucketSet = currentBucketSet; currentBucketSet = nextBucketSet; }while (currentBucketSet.AbsoluteTimeout > _ticks); // // Pinch off the list at this point making sure it is still the correct set. // // Note: We may lose a race with an "Add" thread that is inserting a BucketSet in this location in // the list. If that happens, this CompareExchange will not be performed and the returned abortingSetsWeak // value will NOT equal nextWeakSet. But we check for that and if this condition occurs, this iteration of // the timer thread will simply return, not timing out any transactions. When the next timer interval // expires, the thread will walk the list again, find the appropriate BucketSet to pinch off, and // then time out the transactions. This means that it is possible for a transaction to live a bit longer, // but not much. WeakReference abortingSetsWeak = (WeakReference)Interlocked.CompareExchange(ref lastBucketSet.nextSetWeak, null, nextWeakSet); if (abortingSetsWeak == nextWeakSet) { // Yea - now proceed to abort the transactions. BucketSet abortingBucketSets = null; do { if (abortingSetsWeak != null) { abortingBucketSets = (BucketSet)abortingSetsWeak.Target; } else { abortingBucketSets = null; } if (abortingBucketSets != null) { abortingBucketSets.TimeoutTransactions(); abortingSetsWeak = (WeakReference)abortingBucketSets.nextSetWeak; } }while (abortingBucketSets != null); // That's all we needed to do. break; } // We missed pulling the right transactions off. Loop back up and try again. currentBucketSet = lastBucketSet; }while (true); }