|
//-----------------------------------------------------------------------------
// <copyright file="TransactionScope.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------
using System;
using System.Diagnostics;
using SysES = System.EnterpriseServices;
using System.Runtime.CompilerServices;
using System.Runtime.Remoting.Messaging;
using System.Runtime.InteropServices;
using System.Threading;
using System.Transactions.Diagnostics;
namespace System.Transactions
{
public enum TransactionScopeOption
{
Required,
RequiresNew,
Suppress,
}
//
// The legacy TransactionScope uses TLS to store the ambient transaction. TLS data doesn't flow across thread continuations and hence legacy TransactionScope does not compose well with
// new .Net async programming model constructs like Tasks and async/await. To enable TransactionScope to work with Task and async/await, a new TransactionScopeAsyncFlowOption
// is introduced. When users opt-in the async flow option, ambient transaction will automatically flow across thread continuations and user can compose TransactionScope with Task and/or
// async/await constructs.
//
public enum TransactionScopeAsyncFlowOption
{
Suppress, // Ambient transaction will be stored in TLS and will not flow across thread continuations.
Enabled, // Ambient transaction will be stored in CallContext and will flow across thread continuations. This option will enable TransactionScope to compose well with Task and async/await.
}
public enum EnterpriseServicesInteropOption
{
None = 0,
Automatic = 1,
Full = 2
}
public sealed class TransactionScope : IDisposable
{
public TransactionScope() : this( TransactionScopeOption.Required )
{
}
public TransactionScope(TransactionScopeOption scopeOption)
: this(scopeOption, TransactionScopeAsyncFlowOption.Suppress)
{
}
public TransactionScope(TransactionScopeAsyncFlowOption asyncFlowOption)
: this(TransactionScopeOption.Required, asyncFlowOption)
{
}
public TransactionScope(
TransactionScopeOption scopeOption,
TransactionScopeAsyncFlowOption asyncFlowOption
)
{
if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( TransactionScopeOption )"
);
}
ValidateAndSetAsyncFlowOption(asyncFlowOption);
if ( NeedToCreateTransaction( scopeOption ) )
{
committableTransaction = new CommittableTransaction();
expectedCurrent = committableTransaction.Clone();
}
if ( DiagnosticTrace.Information )
{
if ( null == expectedCurrent )
{
TransactionScopeCreatedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
TransactionTraceIdentifier.Empty,
TransactionScopeResult.NoTransaction
);
}
else
{
TransactionScopeResult scopeResult;
if ( null == committableTransaction )
{
scopeResult = TransactionScopeResult.UsingExistingCurrent;
}
else
{
scopeResult = TransactionScopeResult.CreatedTransaction;
}
TransactionScopeCreatedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
expectedCurrent.TransactionTraceId,
scopeResult
);
}
}
PushScope();
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( TransactionScopeOption )"
);
}
}
public TransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout)
: this(scopeOption, scopeTimeout, TransactionScopeAsyncFlowOption.Suppress)
{
}
public TransactionScope(
TransactionScopeOption scopeOption,
TimeSpan scopeTimeout,
TransactionScopeAsyncFlowOption asyncFlowOption
)
{
if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( TransactionScopeOption, TimeSpan )"
);
}
ValidateScopeTimeout( "scopeTimeout", scopeTimeout );
TimeSpan txTimeout = TransactionManager.ValidateTimeout( scopeTimeout );
ValidateAndSetAsyncFlowOption(asyncFlowOption);
if ( NeedToCreateTransaction( scopeOption ))
{
this.committableTransaction = new CommittableTransaction( txTimeout );
this.expectedCurrent = committableTransaction.Clone();
}
if ( (null != this.expectedCurrent) && (null == this.committableTransaction) && (TimeSpan.Zero != scopeTimeout) )
{
//
scopeTimer = new Timer(
TransactionScope.TimerCallback,
this,
scopeTimeout,
TimeSpan.Zero
);
}
if ( DiagnosticTrace.Information )
{
if ( null == expectedCurrent )
{
TransactionScopeCreatedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
TransactionTraceIdentifier.Empty,
TransactionScopeResult.NoTransaction
);
}
else
{
TransactionScopeResult scopeResult;
if ( null == committableTransaction )
{
scopeResult = TransactionScopeResult.UsingExistingCurrent;
}
else
{
scopeResult = TransactionScopeResult.CreatedTransaction;
}
TransactionScopeCreatedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
expectedCurrent.TransactionTraceId,
scopeResult
);
}
}
PushScope();
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( TransactionScopeOption, TimeSpan )"
);
}
}
public TransactionScope(TransactionScopeOption scopeOption, TransactionOptions transactionOptions)
: this(scopeOption, transactionOptions, TransactionScopeAsyncFlowOption.Suppress)
{
}
public TransactionScope(
TransactionScopeOption scopeOption,
TransactionOptions transactionOptions,
TransactionScopeAsyncFlowOption asyncFlowOption
)
{
if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( TransactionScopeOption, TransactionOptions )"
);
}
ValidateScopeTimeout( "transactionOptions.Timeout", transactionOptions.Timeout );
TimeSpan scopeTimeout = transactionOptions.Timeout;
transactionOptions.Timeout = TransactionManager.ValidateTimeout( transactionOptions.Timeout );
TransactionManager.ValidateIsolationLevel( transactionOptions.IsolationLevel );
ValidateAndSetAsyncFlowOption(asyncFlowOption);
if ( NeedToCreateTransaction( scopeOption ) )
{
this.committableTransaction = new CommittableTransaction( transactionOptions );
this.expectedCurrent = committableTransaction.Clone();
}
else
{
if ( null != this.expectedCurrent )
{
// If the requested IsolationLevel is stronger than that of the specified transaction, throw.
if ( (IsolationLevel.Unspecified != transactionOptions.IsolationLevel) && ( expectedCurrent.IsolationLevel != transactionOptions.IsolationLevel ) )
{
throw new ArgumentException( SR.GetString( SR.TransactionScopeIsolationLevelDifferentFromTransaction ), "transactionOptions.IsolationLevel" );
}
}
}
if ( (null != this.expectedCurrent) && (null == this.committableTransaction) && (TimeSpan.Zero != scopeTimeout) )
{
//
scopeTimer = new Timer(
TransactionScope.TimerCallback,
this,
scopeTimeout,
TimeSpan.Zero
);
}
if ( DiagnosticTrace.Information )
{
if ( null == expectedCurrent )
{
TransactionScopeCreatedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
TransactionTraceIdentifier.Empty,
TransactionScopeResult.NoTransaction
);
}
else
{
TransactionScopeResult scopeResult;
if ( null == committableTransaction )
{
scopeResult = TransactionScopeResult.UsingExistingCurrent;
}
else
{
scopeResult = TransactionScopeResult.CreatedTransaction;
}
TransactionScopeCreatedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
expectedCurrent.TransactionTraceId,
scopeResult
);
}
}
PushScope();
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( TransactionScopeOption, TransactionOptions )"
);
}
}
[System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")]
public TransactionScope(
TransactionScopeOption scopeOption,
TransactionOptions transactionOptions,
EnterpriseServicesInteropOption interopOption
)
{
if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( TransactionScopeOption, TransactionOptions, EnterpriseServicesInteropOption )"
);
}
ValidateScopeTimeout( "transactionOptions.Timeout", transactionOptions.Timeout );
TimeSpan scopeTimeout = transactionOptions.Timeout;
transactionOptions.Timeout = TransactionManager.ValidateTimeout( transactionOptions.Timeout );
TransactionManager.ValidateIsolationLevel( transactionOptions.IsolationLevel );
ValidateInteropOption( interopOption );
this.interopModeSpecified = true;
this.interopOption = interopOption;
if ( NeedToCreateTransaction( scopeOption ) )
{
committableTransaction = new CommittableTransaction( transactionOptions );
expectedCurrent = committableTransaction.Clone();
}
else
{
if ( null != expectedCurrent )
{
// If the requested IsolationLevel is stronger than that of the specified transaction, throw.
if ( (IsolationLevel.Unspecified != transactionOptions.IsolationLevel) && ( expectedCurrent.IsolationLevel != transactionOptions.IsolationLevel ) )
{
throw new ArgumentException( SR.GetString( SR.TransactionScopeIsolationLevelDifferentFromTransaction ), "transactionOptions.IsolationLevel" );
}
}
}
if ( (null != this.expectedCurrent) && (null == this.committableTransaction) && (TimeSpan.Zero != scopeTimeout) )
{
//
scopeTimer = new Timer(
TransactionScope.TimerCallback,
this,
scopeTimeout,
TimeSpan.Zero
);
}
if ( DiagnosticTrace.Information )
{
if ( null == expectedCurrent )
{
TransactionScopeCreatedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
TransactionTraceIdentifier.Empty,
TransactionScopeResult.NoTransaction
);
}
else
{
TransactionScopeResult scopeResult;
if ( null == committableTransaction )
{
scopeResult = TransactionScopeResult.UsingExistingCurrent;
}
else
{
scopeResult = TransactionScopeResult.CreatedTransaction;
}
TransactionScopeCreatedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
expectedCurrent.TransactionTraceId,
scopeResult
);
}
}
PushScope();
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( TransactionScopeOption, TransactionOptions, EnterpriseServicesInteropOption )"
);
}
}
public TransactionScope(Transaction transactionToUse)
: this(transactionToUse, TransactionScopeAsyncFlowOption.Suppress)
{
}
public TransactionScope(
Transaction transactionToUse,
TransactionScopeAsyncFlowOption asyncFlowOption
)
{
if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( Transaction )"
);
}
ValidateAndSetAsyncFlowOption(asyncFlowOption);
Initialize(
transactionToUse,
TimeSpan.Zero,
false
);
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( Transaction )"
);
}
}
public TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout)
: this(transactionToUse, scopeTimeout, TransactionScopeAsyncFlowOption.Suppress)
{
}
public TransactionScope(
Transaction transactionToUse,
TimeSpan scopeTimeout,
TransactionScopeAsyncFlowOption asyncFlowOption
)
{
if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( Transaction, TimeSpan )"
);
}
ValidateAndSetAsyncFlowOption(asyncFlowOption);
Initialize(
transactionToUse,
scopeTimeout,
false
);
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( Transaction, TimeSpan )"
);
}
}
[System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")]
public TransactionScope(
Transaction transactionToUse,
TimeSpan scopeTimeout,
EnterpriseServicesInteropOption interopOption
)
{
if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( Transaction, TimeSpan, EnterpriseServicesInteropOption )"
);
}
ValidateInteropOption( interopOption );
this.interopOption = interopOption;
Initialize(
transactionToUse,
scopeTimeout,
true
);
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.ctor( Transaction, TimeSpan, EnterpriseServicesInteropOption )"
);
}
}
private bool NeedToCreateTransaction(
TransactionScopeOption scopeOption
)
{
bool retVal = false;
CommonInitialize();
// If the options specify NoTransactionNeeded, that trumps everything else.
switch ( scopeOption )
{
case TransactionScopeOption.Suppress:
expectedCurrent = null;
retVal = false;
break;
case TransactionScopeOption.Required:
expectedCurrent = this.savedCurrent;
// If current is null, we need to create one.
if ( null == expectedCurrent )
{
retVal = true;
}
break;
case TransactionScopeOption.RequiresNew:
retVal = true;
break;
default:
throw new ArgumentOutOfRangeException( "scopeOption" );
}
return retVal;
}
private void Initialize(
Transaction transactionToUse,
TimeSpan scopeTimeout,
bool interopModeSpecified
)
{
if ( null == transactionToUse )
{
throw new ArgumentNullException( "transactionToUse" );
}
ValidateScopeTimeout( "scopeTimeout", scopeTimeout );
CommonInitialize();
if ( TimeSpan.Zero != scopeTimeout )
{
scopeTimer = new Timer(
TransactionScope.TimerCallback,
this,
scopeTimeout,
TimeSpan.Zero
);
}
this.expectedCurrent = transactionToUse;
this.interopModeSpecified = interopModeSpecified;
if ( DiagnosticTrace.Information )
{
TransactionScopeCreatedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
expectedCurrent.TransactionTraceId,
TransactionScopeResult.TransactionPassed
);
}
PushScope();
}
// 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.
// FXCop wants us to dispose savedCurrent, which is a Transaction, and thus disposable. But we don't
// want to do that here. We want to restore Current to that value. We do dispose the expected current.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed")]
public void Dispose()
{
bool successful = false;
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.Dispose"
);
}
if ( this.disposed )
{
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.Dispose"
);
}
return;
}
// Dispose for a scope can only be called on the thread where the scope was created.
if ((this.scopeThread != Thread.CurrentThread) && !this.AsyncFlowEnabled)
{
if ( DiagnosticTrace.Error )
{
InvalidOperationExceptionTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
SR.GetString( SR.InvalidScopeThread )
);
}
throw new InvalidOperationException( SR.GetString( SR.InvalidScopeThread ));
}
Exception exToThrow = null;
try
{
// Single threaded from this point
this.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 = this.threadContextData.CurrentScope;
Transaction contextTransaction = null;
Transaction current = Transaction.FastGetTransaction( actualCurrentScope, this.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 = this.committableTransaction;
if ( rollbackTransaction == null )
{
rollbackTransaction = this.dependentTransaction;
}
Debug.Assert( rollbackTransaction != null );
rollbackTransaction.Rollback();
successful = true;
throw TransactionException.CreateInvalidOperationException( SR.GetString( SR.TraceSourceBase ),
SR.GetString(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 ) )
)
{
if ( DiagnosticTrace.Warning )
{
TransactionTraceIdentifier myId;
TransactionTraceIdentifier currentId;
if ( null == current )
{
currentId = TransactionTraceIdentifier.Empty;
}
else
{
currentId = current.TransactionTraceId;
}
if ( null == this.expectedCurrent )
{
myId = TransactionTraceIdentifier.Empty;
}
else
{
myId = this.expectedCurrent.TransactionTraceId;
}
TransactionScopeCurrentChangedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
myId,
currentId
);
}
exToThrow = TransactionException.CreateInvalidOperationException(SR.GetString(SR.TraceSourceBase), SR.GetString(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( SR.GetString( SR.TraceSourceBase ), SR.GetString( SR.TransactionScopeInvalidNesting ), null,
current == null ? Guid.Empty : current.DistributedTxId );
}
if ( DiagnosticTrace.Warning )
{
if ( null == actualCurrentScope.expectedCurrent )
{
TransactionScopeNestedIncorrectlyTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
TransactionTraceIdentifier.Empty
);
}
else
{
TransactionScopeNestedIncorrectlyTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
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 = this.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.
this.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 == this.interopOption )
{
if ((( null != this.expectedCurrent ) && ( ! this.expectedCurrent.Equals( current )))
|| (( null != current ) && ( null == this.expectedCurrent ))
)
{
if ( DiagnosticTrace.Warning )
{
TransactionTraceIdentifier myId;
TransactionTraceIdentifier currentId;
if ( null == current )
{
currentId = TransactionTraceIdentifier.Empty;
}
else
{
currentId = current.TransactionTraceId;
}
if ( null == this.expectedCurrent )
{
myId = TransactionTraceIdentifier.Empty;
}
else
{
myId = this.expectedCurrent.TransactionTraceId;
}
TransactionScopeCurrentChangedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
myId,
currentId
);
}
if ( null == exToThrow )
{
exToThrow = TransactionException.CreateInvalidOperationException(SR.GetString(SR.TraceSourceBase), SR.GetString(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.
this.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 ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.Dispose"
);
}
}
private void InternalDispose()
{
// Set this if it is called internally.
this.disposed = true;
try
{
PopScope();
if ( DiagnosticTrace.Information )
{
if ( null == this.expectedCurrent )
{
TransactionScopeDisposedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
TransactionTraceIdentifier.Empty
);
}
else
{
TransactionScopeDisposedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
this.expectedCurrent.TransactionTraceId
);
}
}
// If Transaction.Current is not null, we have work to do. Otherwise, we don't, except to replace
// the previous value.
if ( null != this.expectedCurrent )
{
if ( !this.complete )
{
if ( DiagnosticTrace.Warning )
{
TransactionScopeIncompleteTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
this.expectedCurrent.TransactionTraceId
);
}
//
// Note: Rollback is not called on expected current because someone could conceiveably
// dispose expectedCurrent out from under the transaction scope.
//
Transaction rollbackTransaction = this.committableTransaction;
if ( rollbackTransaction == null )
{
rollbackTransaction = this.dependentTransaction;
}
Debug.Assert( rollbackTransaction != null );
rollbackTransaction.Rollback();
}
else
{
// If we are supposed to commit on dispose, cast to CommittableTransaction and commit it.
if ( null != this.committableTransaction )
{
this.committableTransaction.Commit();
}
else
{
Debug.Assert( null != this.dependentTransaction, "null != this.dependentTransaction" );
this.dependentTransaction.Complete();
}
}
}
}
finally
{
if ( null != scopeTimer )
{
scopeTimer.Dispose();
}
if ( null != this.committableTransaction )
{
this.committableTransaction.Dispose();
// If we created the committable transaction then we placed a clone in expectedCurrent
// and it needs to be disposed as well.
this.expectedCurrent.Dispose();
}
if ( null != this.dependentTransaction )
{
this.dependentTransaction.Dispose();
}
}
}
public void Complete()
{
if ( DiagnosticTrace.Verbose )
{
MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.Complete"
);
}
if ( this.disposed )
{
throw new ObjectDisposedException( "TransactionScope" );
}
if ( this.complete )
{
throw TransactionException.CreateInvalidOperationException( SR.GetString( SR.TraceSourceBase ), SR.GetString(SR.DisposeScope), null);
}
this.complete = true;
if ( DiagnosticTrace.Verbose )
{
MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
"TransactionScope.Complete"
);
}
}
private static void TimerCallback(
object state
)
{
TransactionScope scope = state as TransactionScope;
if ( null == scope )
{
if ( DiagnosticTrace.Critical )
{
InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
SR.GetString( SR.TransactionScopeTimerObjectInvalid )
);
}
throw TransactionException.Create( SR.GetString( SR.TraceSourceBase ), SR.GetString( SR.InternalError) + SR.GetString( SR.TransactionScopeTimerObjectInvalid ), null );
}
scope.Timeout();
}
private void Timeout()
{
if ( ( !this.complete ) && ( null != this.expectedCurrent ) )
{
if ( DiagnosticTrace.Warning )
{
TransactionScopeTimeoutTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
this.expectedCurrent.TransactionTraceId
);
}
try
{
this.expectedCurrent.Rollback();
}
catch ( ObjectDisposedException ex )
{
// Tolerate the fact that the transaction has already been disposed.
if ( DiagnosticTrace.Verbose )
{
ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
ex );
}
}
catch ( TransactionException txEx )
{
// Tolerate transaction exceptions - VSWhidbey 466868.
if ( DiagnosticTrace.Verbose )
{
ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
txEx );
}
}
}
}
private void CommonInitialize()
{
this.ContextKey = new ContextKey();
this.complete = false;
this.dependentTransaction = null;
this.disposed = false;
this.committableTransaction = null;
this.expectedCurrent = null;
this.scopeTimer = null;
this.scopeThread = Thread.CurrentThread;
Transaction.GetCurrentTransactionAndScope(
this.AsyncFlowEnabled ? TxLookup.DefaultCallContext : TxLookup.DefaultTLS,
out this.savedCurrent,
out this.savedCurrentScope,
out this.contextTransaction
);
// Calling validate here as we need to make sure the existing parent ambient transaction scope is already looked up to see if we have ES interop enabled.
ValidateAsyncFlowOptionAndESInteropOption();
}
// PushScope
//
// Push a transaction scope onto the stack.
private void PushScope()
{
// Fixup the interop mode before we set current.
if ( !this.interopModeSpecified )
{
// Transaction.InteropMode will take the interop mode on
// for the scope in currentScope into account.
this.interopOption = Transaction.InteropMode(this.savedCurrentScope);
}
// async function yield at await points and main thread can continue execution. We need to make sure the TLS data are restored appropriately.
SaveTLSContextData();
if (this.AsyncFlowEnabled)
{
// Async Flow is enabled and CallContext will be used for ambient transaction.
this.threadContextData = CallContextCurrentData.CreateOrGetCurrentData(this.ContextKey);
if (this.savedCurrentScope == null && this.savedCurrent == null)
{
// Clear TLS data so that transaction doesn't leak from current thread.
ContextData.TLSCurrentData = null;
}
}
else
{
// Legacy TransactionScope. Use TLS to track ambient transaction context.
this.threadContextData = ContextData.TLSCurrentData;
CallContextCurrentData.ClearCurrentData(this.ContextKey, false);
}
// This call needs to be done first
SetCurrent( expectedCurrent );
this.threadContextData.CurrentScope = this;
}
// PopScope
//
// Pop the current transaction scope off the top of the stack
private void PopScope()
{
bool shouldRestoreContextData = true;
// Clear the current TransactionScope CallContext data
if (this.AsyncFlowEnabled)
{
CallContextCurrentData.ClearCurrentData(this.ContextKey, true);
}
if (this.scopeThread == Thread.CurrentThread)
{
// async function yield at await points and main thread can continue execution. We need to make sure the TLS data are restored appropriately.
// Restore the TLS only if the thread Ids match.
RestoreSavedTLSContextData();
}
// Restore threadContextData to parent CallContext or TLS data
if (this.savedCurrentScope != null)
{
if (this.savedCurrentScope.AsyncFlowEnabled)
{
this.threadContextData = CallContextCurrentData.CreateOrGetCurrentData(this.savedCurrentScope.ContextKey);
}
else
{
if (this.savedCurrentScope.scopeThread != Thread.CurrentThread)
{
// Clear TLS data so that transaction doesn't leak from current thread.
shouldRestoreContextData = false;
ContextData.TLSCurrentData = null;
}
else
{
this.threadContextData = ContextData.TLSCurrentData;
}
CallContextCurrentData.ClearCurrentData(this.savedCurrentScope.ContextKey, false);
}
}
else
{
// No parent TransactionScope present
// Clear any CallContext data
CallContextCurrentData.ClearCurrentData(null, false);
if (this.scopeThread != Thread.CurrentThread)
{
// Clear TLS data so that transaction doesn't leak from current thread.
shouldRestoreContextData = false;
ContextData.TLSCurrentData = null;
}
else
{
// Restore the current data to TLS.
ContextData.TLSCurrentData = this.threadContextData;
}
}
// prevent restoring the context in an unexpected thread due to thread switch during TransactionScope's Dispose
if (shouldRestoreContextData)
{
this.threadContextData.CurrentScope = this.savedCurrentScope;
RestoreCurrent();
}
}
// SetCurrent
//
// Place the given value in current by whatever means necessary for interop mode.
private void SetCurrent( Transaction newCurrent )
{
// Keep a dependent clone of current if we don't have one and we are not committable
if ( this.dependentTransaction == null && this.committableTransaction == null )
{
if ( newCurrent != null )
{
this.dependentTransaction = newCurrent.DependentClone( DependentCloneOption.RollbackIfNotComplete );
}
}
switch ( this.interopOption )
{
case EnterpriseServicesInteropOption.None:
this.threadContextData.CurrentTransaction = newCurrent;
break;
case EnterpriseServicesInteropOption.Automatic:
Transaction.VerifyEnterpriseServicesOk();
if ( Transaction.UseServiceDomainForCurrent() )
{
PushServiceDomain( newCurrent );
}
else
{
this.threadContextData.CurrentTransaction = newCurrent;
}
break;
case EnterpriseServicesInteropOption.Full:
Transaction.VerifyEnterpriseServicesOk();
PushServiceDomain( newCurrent );
break;
}
}
private void SaveTLSContextData()
{
if (this.savedTLSContextData == null)
{
this.savedTLSContextData = new ContextData(false);
}
this.savedTLSContextData.CurrentScope = ContextData.TLSCurrentData.CurrentScope;
this.savedTLSContextData.CurrentTransaction = ContextData.TLSCurrentData.CurrentTransaction;
this.savedTLSContextData.DefaultComContextState = ContextData.TLSCurrentData.DefaultComContextState;
this.savedTLSContextData.WeakDefaultComContext = ContextData.TLSCurrentData.WeakDefaultComContext;
}
private void RestoreSavedTLSContextData()
{
if (this.savedTLSContextData != null)
{
ContextData.TLSCurrentData.CurrentScope = this.savedTLSContextData.CurrentScope;
ContextData.TLSCurrentData.CurrentTransaction = this.savedTLSContextData.CurrentTransaction;
ContextData.TLSCurrentData.DefaultComContextState = this.savedTLSContextData.DefaultComContextState;
ContextData.TLSCurrentData.WeakDefaultComContext = this.savedTLSContextData.WeakDefaultComContext;
}
}
// PushServiceDomain
//
// Create a new service domain with the given transaction.
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
// PushServiceDomain will only be called if a transaction scope is created with an
// EnterpriseServicesInteropOption. TransactionScope constructors that take a ESIO are protected by
// a FullTrust link demand.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2116:AptcaMethodsShouldOnlyCallAptcaMethods")]
private void PushServiceDomain( Transaction newCurrent )
{
// If we are not changing the transaction for the current ServiceDomain then
// don't call CoEnterServiceDomain
if ((newCurrent != null && newCurrent.Equals( SysES.ContextUtil.SystemTransaction )) ||
(newCurrent == null && SysES.ContextUtil.SystemTransaction == null ))
{
return;
}
SysES.ServiceConfig serviceConfig = new SysES.ServiceConfig();
try
{
// If a transaction is specified place it in BYOT. Otherwise the
// default transaction option for ServiceConfig is disabled. So
// if this is a not supported scope it will be cleared.
if ( newCurrent != null )
{
// To work around an SWC bug in Com+ we need to create 2
// service domains.
// Com+ will by default try to inherit synchronization from
// an existing context. Turn that off.
serviceConfig.Synchronization = SysES.SynchronizationOption.RequiresNew;
SysES.ServiceDomain.Enter( serviceConfig );
this.createdDoubleServiceDomain = true;
serviceConfig.Synchronization = SysES.SynchronizationOption.Required;
serviceConfig.BringYourOwnSystemTransaction = newCurrent;
}
SysES.ServiceDomain.Enter( serviceConfig );
this.createdServiceDomain = true;
}
catch ( COMException e )
{
if ( System.Transactions.Oletx.NativeMethods.XACT_E_NOTRANSACTION == e.ErrorCode )
{
throw TransactionException.Create(SR.GetString(SR.TraceSourceBase),
SR.GetString(SR.TransactionAlreadyOver),
e,
newCurrent == null ? Guid.Empty : newCurrent.DistributedTxId);
}
throw TransactionException.Create(SR.GetString(SR.TraceSourceBase), e.Message, e, newCurrent == null ? Guid.Empty : newCurrent.DistributedTxId);
}
finally
{
if ( !this.createdServiceDomain )
{
// If we weren't successful in creating both service domains then
// make sure to exit one of them.
if ( this.createdDoubleServiceDomain )
{
SysES.ServiceDomain.Leave();
}
}
}
}
[System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
// PushServiceDomain will only be called if a transaction scope is created with an
// EnterpriseServicesInteropOption. TransactionScope constructors that take a ESIO are protected by
// a FullTrust link demand. Therefore JitSafeLeaveServiceDomain is not exposed directly to a partial
// trust caller.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2116:AptcaMethodsShouldOnlyCallAptcaMethods")]
private void JitSafeLeaveServiceDomain()
{
if ( this.createdDoubleServiceDomain )
{
// This is an ugly work around to a bug in Com+ that prevents the use of BYOT and SWC with
// anything other than required synchronization.
SysES.ServiceDomain.Leave();
}
SysES.ServiceDomain.Leave();
}
// RestoreCurrent
//
// Restore current to it's previous value depending on how it was changed for this scope.
private void RestoreCurrent()
{
if ( this.createdServiceDomain )
{
JitSafeLeaveServiceDomain();
}
// Only restore the value that was actually in the context.
this.threadContextData.CurrentTransaction = this.contextTransaction;
}
// ValidateInteropOption
//
// Validate a given interop Option
private void ValidateInteropOption( EnterpriseServicesInteropOption interopOption )
{
if ( interopOption < EnterpriseServicesInteropOption.None || interopOption > EnterpriseServicesInteropOption.Full )
{
throw new ArgumentOutOfRangeException( "interopOption" );
}
}
// ValidateScopeTimeout
//
// Scope timeouts are not governed by MaxTimeout and therefore need a special validate function
private void ValidateScopeTimeout( string paramName, TimeSpan scopeTimeout )
{
if ( scopeTimeout < TimeSpan.Zero )
{
throw new ArgumentOutOfRangeException( paramName );
}
}
private void ValidateAndSetAsyncFlowOption(TransactionScopeAsyncFlowOption asyncFlowOption)
{
if (asyncFlowOption < TransactionScopeAsyncFlowOption.Suppress || asyncFlowOption > TransactionScopeAsyncFlowOption.Enabled)
{
throw new ArgumentOutOfRangeException( "asyncFlowOption" );
}
if (asyncFlowOption == TransactionScopeAsyncFlowOption.Enabled)
{
this.AsyncFlowEnabled = true;
}
}
// The validate method assumes that the existing parent ambient transaction scope is already looked up.
private void ValidateAsyncFlowOptionAndESInteropOption()
{
if (this.AsyncFlowEnabled)
{
EnterpriseServicesInteropOption currentInteropOption = this.interopOption;
if ( !this.interopModeSpecified )
{
// Transaction.InteropMode will take the interop mode on
// for the scope in currentScope into account.
currentInteropOption = Transaction.InteropMode(this.savedCurrentScope);
}
if (currentInteropOption != EnterpriseServicesInteropOption.None)
{
throw new NotSupportedException(SR.GetString(SR.AsyncFlowAndESInteropNotSupported));
}
}
}
// Denotes the action to take when the scope is disposed.
bool complete;
internal bool ScopeComplete
{
get
{
return this.complete;
}
}
// Storage location for the previous current transaction.
Transaction savedCurrent;
// To ensure that we don't restore a value for current that was
// returned to us by an external entity keep the value that was actually
// in TLS when the scope was created.
Transaction contextTransaction;
// Storage for the value to restore to current
TransactionScope savedCurrentScope;
// Store a reference to the context data object for this scope.
ContextData threadContextData;
ContextData savedTLSContextData;
// Store a reference to the value that this scope expects for current
Transaction expectedCurrent;
// Store a reference to the committable form of this transaction if
// the scope made one.
CommittableTransaction committableTransaction;
// Store a reference to the scopes transaction guard.
DependentTransaction dependentTransaction;
// Note when the scope is disposed.
bool disposed;
//
Timer scopeTimer;
// Store a reference to the thread on which the scope was created so that we can
// check to make sure that the dispose pattern for scope is being used correctly.
Thread scopeThread;
// Store a member to let us know if a new service domain has been created for this
// scope.
bool createdServiceDomain;
bool createdDoubleServiceDomain;
// Store the interop mode for this transaction scope.
bool interopModeSpecified = false;
EnterpriseServicesInteropOption interopOption;
internal EnterpriseServicesInteropOption InteropMode
{
get
{
return this.interopOption;
}
}
internal ContextKey ContextKey
{
get;
private set;
}
internal bool AsyncFlowEnabled
{
get;
private set;
}
}
}
|