File: System\Transactions\TransactionScope.cs
Web Access
Project: src\src\libraries\System.Transactions.Local\src\System.Transactions.Local.csproj (System.Transactions.Local)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Runtime.Versioning;
using System.Threading;
 
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
    }
 
    [UnsupportedOSPlatform("browser")]
    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
            )
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
            }
 
            ValidateAndSetAsyncFlowOption(asyncFlowOption);
 
            if (NeedToCreateTransaction(scopeOption))
            {
                _committableTransaction = new CommittableTransaction();
                _expectedCurrent = _committableTransaction.Clone();
            }
 
            if (null == _expectedCurrent)
            {
                if (etwLog.IsEnabled())
                {
                    etwLog.TransactionScopeCreated(TransactionTraceIdentifier.Empty, TransactionScopeResult.NoTransaction);
                }
            }
            else
            {
                TransactionScopeResult scopeResult;
 
                if (null == _committableTransaction)
                {
                    scopeResult = TransactionScopeResult.UsingExistingCurrent;
                }
                else
                {
                    scopeResult = TransactionScopeResult.CreatedTransaction;
                }
 
                if (etwLog.IsEnabled())
                {
                    etwLog.TransactionScopeCreated(_expectedCurrent.TransactionTraceId, scopeResult);
                }
            }
 
            PushScope();
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
            }
        }
 
        public TransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout)
            : this(scopeOption, scopeTimeout, TransactionScopeAsyncFlowOption.Suppress)
        {
        }
 
        public TransactionScope(
            TransactionScopeOption scopeOption,
            TimeSpan scopeTimeout,
            TransactionScopeAsyncFlowOption asyncFlowOption
            )
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
            }
 
            ValidateScopeTimeout(nameof(scopeTimeout), scopeTimeout);
            TimeSpan txTimeout = TransactionManager.ValidateTimeout(scopeTimeout);
 
            ValidateAndSetAsyncFlowOption(asyncFlowOption);
 
            if (NeedToCreateTransaction(scopeOption))
            {
                _committableTransaction = new CommittableTransaction(txTimeout);
                _expectedCurrent = _committableTransaction.Clone();
            }
 
            if ((null != _expectedCurrent) && (null == _committableTransaction) && (TimeSpan.Zero != scopeTimeout))
            {
                // BUGBUG: Scopes should not use individual timers
                _scopeTimer = new Timer(
                    TimerCallback,
                    this,
                    scopeTimeout,
                    TimeSpan.Zero);
            }
 
            if (null == _expectedCurrent)
            {
                if (etwLog.IsEnabled())
                {
                    etwLog.TransactionScopeCreated(TransactionTraceIdentifier.Empty, TransactionScopeResult.NoTransaction);
                }
            }
            else
            {
                TransactionScopeResult scopeResult;
 
                if (null == _committableTransaction)
                {
                    scopeResult = TransactionScopeResult.UsingExistingCurrent;
                }
                else
                {
                    scopeResult = TransactionScopeResult.CreatedTransaction;
                }
 
                if (etwLog.IsEnabled())
                {
                    etwLog.TransactionScopeCreated(_expectedCurrent.TransactionTraceId, scopeResult);
                }
            }
 
            PushScope();
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
            }
        }
 
        public TransactionScope(TransactionScopeOption scopeOption, TransactionOptions transactionOptions)
            : this(scopeOption, transactionOptions, TransactionScopeAsyncFlowOption.Suppress)
        {
        }
 
        public TransactionScope(
            TransactionScopeOption scopeOption,
            TransactionOptions transactionOptions,
            TransactionScopeAsyncFlowOption asyncFlowOption
            )
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
            }
 
            ValidateScopeTimeout("transactionOptions.Timeout", transactionOptions.Timeout);
            TimeSpan scopeTimeout = transactionOptions.Timeout;
 
            transactionOptions.Timeout = TransactionManager.ValidateTimeout(transactionOptions.Timeout);
            TransactionManager.ValidateIsolationLevel(transactionOptions.IsolationLevel);
 
            ValidateAndSetAsyncFlowOption(asyncFlowOption);
 
            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.TransactionScopeIsolationLevelDifferentFromTransaction, nameof(transactionOptions));
                    }
                }
            }
 
            if ((null != _expectedCurrent) && (null == _committableTransaction) && (TimeSpan.Zero != scopeTimeout))
            {
                // BUGBUG: Scopes should use a shared timer
                _scopeTimer = new Timer(
                    TimerCallback,
                    this,
                    scopeTimeout,
                    TimeSpan.Zero);
            }
 
            if (null == _expectedCurrent)
            {
                if (etwLog.IsEnabled())
                {
                    etwLog.TransactionScopeCreated(TransactionTraceIdentifier.Empty, TransactionScopeResult.NoTransaction);
                }
            }
            else
            {
                TransactionScopeResult scopeResult;
 
                if (null == _committableTransaction)
                {
                    scopeResult = TransactionScopeResult.UsingExistingCurrent;
                }
                else
                {
                    scopeResult = TransactionScopeResult.CreatedTransaction;
                }
 
                if (etwLog.IsEnabled())
                {
                    etwLog.TransactionScopeCreated(_expectedCurrent.TransactionTraceId, scopeResult);
                }
            }
 
            PushScope();
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
            }
        }
 
        public TransactionScope(
            TransactionScopeOption scopeOption,
            TransactionOptions transactionOptions,
            EnterpriseServicesInteropOption interopOption)
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
            }
 
            ValidateScopeTimeout("transactionOptions.Timeout", transactionOptions.Timeout);
            TimeSpan scopeTimeout = transactionOptions.Timeout;
 
            transactionOptions.Timeout = TransactionManager.ValidateTimeout(transactionOptions.Timeout);
            TransactionManager.ValidateIsolationLevel(transactionOptions.IsolationLevel);
 
            ValidateInteropOption(interopOption);
            _interopModeSpecified = true;
            _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.TransactionScopeIsolationLevelDifferentFromTransaction, nameof(transactionOptions));
                    }
                }
            }
 
            if ((null != _expectedCurrent) && (null == _committableTransaction) && (TimeSpan.Zero != scopeTimeout))
            {
                // BUGBUG: Scopes should use a shared timer
                _scopeTimer = new Timer(
                    TimerCallback,
                    this,
                    scopeTimeout,
                    TimeSpan.Zero);
            }
 
            if (null == _expectedCurrent)
            {
                if (etwLog.IsEnabled())
                {
                    etwLog.TransactionScopeCreated(TransactionTraceIdentifier.Empty, TransactionScopeResult.NoTransaction);
                }
            }
            else
            {
                TransactionScopeResult scopeResult;
 
                if (null == _committableTransaction)
                {
                    scopeResult = TransactionScopeResult.UsingExistingCurrent;
                }
                else
                {
                    scopeResult = TransactionScopeResult.CreatedTransaction;
                }
 
                if (etwLog.IsEnabled())
                {
                    etwLog.TransactionScopeCreated(_expectedCurrent.TransactionTraceId, scopeResult);
                }
            }
 
            PushScope();
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
            }
        }
 
        public TransactionScope(Transaction transactionToUse)
            : this(transactionToUse, TransactionScopeAsyncFlowOption.Suppress)
        {
        }
 
        public TransactionScope(
            Transaction transactionToUse,
            TransactionScopeAsyncFlowOption asyncFlowOption
            )
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
            }
 
            ValidateAndSetAsyncFlowOption(asyncFlowOption);
 
            Initialize(
                transactionToUse,
                TimeSpan.Zero,
                false);
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
            }
        }
 
        public TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout)
            : this(transactionToUse, scopeTimeout, TransactionScopeAsyncFlowOption.Suppress)
        {
        }
 
        public TransactionScope(
            Transaction transactionToUse,
            TimeSpan scopeTimeout,
            TransactionScopeAsyncFlowOption asyncFlowOption)
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
            }
 
            ValidateAndSetAsyncFlowOption(asyncFlowOption);
 
            Initialize(
                transactionToUse,
                scopeTimeout,
                false);
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
            }
        }
 
        public TransactionScope(
            Transaction transactionToUse,
            TimeSpan scopeTimeout,
            EnterpriseServicesInteropOption interopOption
        )
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
            }
 
            ValidateInteropOption(interopOption);
            _interopOption = interopOption;
 
            Initialize(
                transactionToUse,
                scopeTimeout,
                true);
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
            }
        }
 
        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 = _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(nameof(scopeOption));
            }
 
            return retVal;
        }
 
        private void Initialize(
            Transaction transactionToUse,
            TimeSpan scopeTimeout,
            bool interopModeSpecified)
        {
            ArgumentNullException.ThrowIfNull(transactionToUse);
 
            ValidateScopeTimeout(nameof(scopeTimeout), scopeTimeout);
 
            CommonInitialize();
 
            if (TimeSpan.Zero != scopeTimeout)
            {
                _scopeTimer = new Timer(
                    TimerCallback,
                    this,
                    scopeTimeout,
                    TimeSpan.Zero
                    );
            }
 
            _expectedCurrent = transactionToUse;
            _interopModeSpecified = interopModeSpecified;
 
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.TransactionScopeCreated(_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.
 
        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;
 
                Debug.Assert(_threadContextData != null);
                // 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 = (Transaction?)_committableTransaction ?? _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);
            }
        }
 
        private void InternalDispose()
        {
            // Set this if it is called internally.
            _disposed = true;
 
            try
            {
                PopScope();
 
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (null == _expectedCurrent)
                {
                    if (etwLog.IsEnabled())
                    {
                        etwLog.TransactionScopeDisposed(TransactionTraceIdentifier.Empty);
                    }
                }
                else
                {
                    if (etwLog.IsEnabled())
                    {
                        etwLog.TransactionScopeDisposed(_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 != _expectedCurrent)
                {
                    if (!_complete)
                    {
                        if (etwLog.IsEnabled())
                        {
                            etwLog.TransactionScopeIncomplete(_expectedCurrent.TransactionTraceId);
                        }
 
                        //
                        // Note: Rollback is not called on expected current because someone could conceiveably
                        //       dispose expectedCurrent out from under the transaction scope.
                        //
                        Transaction? rollbackTransaction = (Transaction?)_committableTransaction ?? _dependentTransaction;
                        Debug.Assert(rollbackTransaction != null);
                        rollbackTransaction.Rollback();
                    }
                    else
                    {
                        // If we are supposed to commit on dispose, cast to CommittableTransaction and commit it.
                        if (null != _committableTransaction)
                        {
                            _committableTransaction.Commit();
                        }
                        else
                        {
                            Debug.Assert(null != _dependentTransaction, "null != this.dependentTransaction");
                            _dependentTransaction.Complete();
                        }
                    }
                }
            }
            finally
            {
                _scopeTimer?.Dispose();
 
                if (null != _committableTransaction)
                {
                    _committableTransaction.Dispose();
 
                    Debug.Assert(_expectedCurrent != null);
                    // If we created the committable transaction then we placed a clone in expectedCurrent
                    // and it needs to be disposed as well.
                    _expectedCurrent.Dispose();
                }
 
                _dependentTransaction?.Dispose();
            }
        }
 
        public void Complete()
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceBase, this);
            }
            ObjectDisposedException.ThrowIf(_disposed, this);
 
            if (_complete)
            {
                throw TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceBase, SR.DisposeScope, null);
            }
 
            _complete = true;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceBase, this);
            }
        }
 
        private static void TimerCallback(object? state)
        {
            TransactionScope? scope = state as TransactionScope;
            if (null == scope)
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.InternalError("TransactionScopeTimerObjectInvalid");
                }
 
                throw TransactionException.Create(SR.InternalError + SR.TransactionScopeTimerObjectInvalid, null);
            }
 
            scope.Timeout();
        }
 
        private void Timeout()
        {
            if ((!_complete) && (null != _expectedCurrent))
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.TransactionScopeTimeout(_expectedCurrent.TransactionTraceId);
                }
                try
                {
                    _expectedCurrent.Rollback();
                }
                catch (ObjectDisposedException ex)
                {
                    // Tolerate the fact that the transaction has already been disposed.
                    if (etwLog.IsEnabled())
                    {
                        etwLog.ExceptionConsumed(TraceSourceType.TraceSourceBase, ex);
                    }
                }
                catch (TransactionException txEx)
                {
                    // Tolerate transaction exceptions
                    if (etwLog.IsEnabled())
                    {
                        etwLog.ExceptionConsumed(TraceSourceType.TraceSourceBase, txEx);
                    }
                }
            }
        }
 
        private void CommonInitialize()
        {
            ContextKey = new ContextKey();
            _complete = false;
            _dependentTransaction = null;
            _disposed = false;
            _committableTransaction = null;
            _expectedCurrent = null;
            _scopeTimer = null;
            _scopeThread = Thread.CurrentThread;
 
            Transaction.GetCurrentTransactionAndScope(
                            AsyncFlowEnabled ? TxLookup.DefaultCallContext : TxLookup.DefaultTLS,
                            out _savedCurrent,
                            out _savedCurrentScope,
                            out _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 (!_interopModeSpecified)
            {
                // Transaction.InteropMode will take the interop mode on
                // for the scope in currentScope into account.
                _interopOption = Transaction.InteropMode(_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 (AsyncFlowEnabled)
            {
                Debug.Assert(ContextKey != null);
                // Async Flow is enabled and CallContext will be used for ambient transaction.
                _threadContextData = CallContextCurrentData.CreateOrGetCurrentData(ContextKey);
 
                if (_savedCurrentScope == null && _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.
                _threadContextData = ContextData.TLSCurrentData;
                CallContextCurrentData.ClearCurrentData(ContextKey, false);
            }
 
            // This call needs to be done first
            SetCurrent(_expectedCurrent);
            _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 (AsyncFlowEnabled)
            {
                CallContextCurrentData.ClearCurrentData(ContextKey, true);
            }
 
            if (_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 (_savedCurrentScope != null)
            {
                if (_savedCurrentScope.AsyncFlowEnabled)
                {
                    Debug.Assert(_savedCurrentScope.ContextKey != null);
                    _threadContextData = CallContextCurrentData.CreateOrGetCurrentData(_savedCurrentScope.ContextKey);
                }
                else
                {
                    if (_savedCurrentScope._scopeThread != Thread.CurrentThread)
                    {
                        // Clear TLS data so that transaction doesn't leak from current thread.
                        shouldRestoreContextData = false;
                        ContextData.TLSCurrentData = null;
                    }
                    else
                    {
                        _threadContextData = ContextData.TLSCurrentData;
                    }
 
                    CallContextCurrentData.ClearCurrentData(_savedCurrentScope.ContextKey, false);
                }
            }
            else
            {
                // No parent TransactionScope present
 
                // Clear any CallContext data
                CallContextCurrentData.ClearCurrentData(null, false);
 
                if (_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 = _threadContextData;
                }
            }
 
            // prevent restoring the context in an unexpected thread due to thread switch during TransactionScope's Dispose
            if (shouldRestoreContextData)
            {
                Debug.Assert(_threadContextData != null);
                _threadContextData.CurrentScope = _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 (_dependentTransaction == null && _committableTransaction == null)
            {
                if (newCurrent != null)
                {
                    _dependentTransaction = newCurrent.DependentClone(DependentCloneOption.RollbackIfNotComplete);
                }
            }
 
            switch (_interopOption)
            {
                case EnterpriseServicesInteropOption.None:
                    _threadContextData!.CurrentTransaction = newCurrent;
                    break;
 
                case EnterpriseServicesInteropOption.Automatic:
                    EnterpriseServices.VerifyEnterpriseServicesOk();
                    if (EnterpriseServices.UseServiceDomainForCurrent())
                    {
                        EnterpriseServices.PushServiceDomain();
                    }
                    else
                    {
                        _threadContextData!.CurrentTransaction = newCurrent;
                    }
                    break;
 
                case EnterpriseServicesInteropOption.Full:
                    EnterpriseServices.VerifyEnterpriseServicesOk();
                    EnterpriseServices.PushServiceDomain();
                    break;
            }
        }
 
        private void SaveTLSContextData()
        {
            _savedTLSContextData ??= new ContextData(false);
 
            _savedTLSContextData.CurrentScope = ContextData.TLSCurrentData.CurrentScope;
            _savedTLSContextData.CurrentTransaction = ContextData.TLSCurrentData.CurrentTransaction;
            _savedTLSContextData.DefaultComContextState = ContextData.TLSCurrentData.DefaultComContextState;
            _savedTLSContextData.WeakDefaultComContext = ContextData.TLSCurrentData.WeakDefaultComContext;
        }
 
        private void RestoreSavedTLSContextData()
        {
            if (_savedTLSContextData != null)
            {
                ContextData.TLSCurrentData.CurrentScope = _savedTLSContextData.CurrentScope;
                ContextData.TLSCurrentData.CurrentTransaction = _savedTLSContextData.CurrentTransaction;
                ContextData.TLSCurrentData.DefaultComContextState = _savedTLSContextData.DefaultComContextState;
                ContextData.TLSCurrentData.WeakDefaultComContext = _savedTLSContextData.WeakDefaultComContext;
            }
        }
 
        // RestoreCurrent
        //
        // Restore current to it's previous value depending on how it was changed for this scope.
        private void RestoreCurrent()
        {
            if (EnterpriseServices.CreatedServiceDomain)
            {
                EnterpriseServices.LeaveServiceDomain();
            }
 
            // Only restore the value that was actually in the context.
            _threadContextData!.CurrentTransaction = _contextTransaction;
        }
 
 
        // ValidateInteropOption
        //
        // Validate a given interop Option
        private static void ValidateInteropOption(EnterpriseServicesInteropOption interopOption)
        {
            if (interopOption < EnterpriseServicesInteropOption.None || interopOption > EnterpriseServicesInteropOption.Full)
            {
                throw new ArgumentOutOfRangeException(nameof(interopOption));
            }
        }
 
 
        // ValidateScopeTimeout
        //
        // Scope timeouts are not governed by MaxTimeout and therefore need a special validate function
        private static void ValidateScopeTimeout(string? paramName, TimeSpan scopeTimeout)
        {
            ArgumentOutOfRangeException.ThrowIfLessThan(scopeTimeout, TimeSpan.Zero, paramName);
        }
 
        private void ValidateAndSetAsyncFlowOption(TransactionScopeAsyncFlowOption asyncFlowOption)
        {
            if (asyncFlowOption < TransactionScopeAsyncFlowOption.Suppress || asyncFlowOption > TransactionScopeAsyncFlowOption.Enabled)
            {
                throw new ArgumentOutOfRangeException(nameof(asyncFlowOption));
            }
 
            if (asyncFlowOption == TransactionScopeAsyncFlowOption.Enabled)
            {
                AsyncFlowEnabled = true;
            }
        }
 
        // The validate method assumes that the existing parent ambient transaction scope is already looked up.
        private void ValidateAsyncFlowOptionAndESInteropOption()
        {
            if (AsyncFlowEnabled)
            {
                EnterpriseServicesInteropOption currentInteropOption = _interopOption;
                if (!_interopModeSpecified)
                {
                    // Transaction.InteropMode will take the interop mode on
                    // for the scope in currentScope into account.
                    currentInteropOption = Transaction.InteropMode(_savedCurrentScope);
                }
 
                if (currentInteropOption != EnterpriseServicesInteropOption.None)
                {
                    throw new NotSupportedException(SR.AsyncFlowAndESInteropNotSupported);
                }
            }
        }
 
        // Denotes the action to take when the scope is disposed.
        private bool _complete;
        internal bool ScopeComplete
        {
            get
            {
                return _complete;
            }
        }
 
        // Storage location for the previous current transaction.
        private 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.
        private Transaction? _contextTransaction;
 
        // Storage for the value to restore to current
        private TransactionScope? _savedCurrentScope;
 
        // Store a reference to the context data object for this scope.
        private ContextData? _threadContextData;
 
        private ContextData? _savedTLSContextData;
 
        // Store a reference to the value that this scope expects for current
        private Transaction? _expectedCurrent;
 
        // Store a reference to the committable form of this transaction if
        // the scope made one.
        private CommittableTransaction? _committableTransaction;
 
        // Store a reference to the scopes transaction guard.
        private DependentTransaction? _dependentTransaction;
 
        // Note when the scope is disposed.
        private bool _disposed;
 
        // BUGBUG: A shared timer should be used.
        // Individual timer for this scope.
        private 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.
        private Thread? _scopeThread;
 
        // Store the interop mode for this transaction scope.
        private bool _interopModeSpecified;
        private EnterpriseServicesInteropOption _interopOption;
        internal EnterpriseServicesInteropOption InteropMode
        {
            get
            {
                return _interopOption;
            }
        }
 
        internal ContextKey? ContextKey
        {
            get;
            private set;
        }
 
        internal bool AsyncFlowEnabled
        {
            get;
            private set;
        }
    }
}