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 contextTransaction = null;
Transaction current = Transaction.FastGetTransaction(actualCurrentScope, _threadContextData, out 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);
}
}