File: System\Transactions\Transaction.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.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Threading;
using System.Transactions.Oletx;
 
namespace System.Transactions
{
    public class TransactionEventArgs : EventArgs
    {
        internal Transaction? _transaction;
        public Transaction? Transaction => _transaction;
    }
 
    public delegate void TransactionCompletedEventHandler(object? sender, TransactionEventArgs e);
 
    public enum IsolationLevel
    {
        Serializable = 0,
        RepeatableRead = 1,
        ReadCommitted = 2,
        ReadUncommitted = 3,
        Snapshot = 4,
        Chaos = 5,
        Unspecified = 6,
    }
 
    public enum TransactionStatus
    {
        Active = 0,
        Committed = 1,
        Aborted = 2,
        InDoubt = 3
    }
 
    public enum DependentCloneOption
    {
        BlockCommitUntilComplete = 0,
        RollbackIfNotComplete = 1,
    }
 
    [Flags]
    public enum EnlistmentOptions
    {
        None = 0x0,
        EnlistDuringPrepareRequired = 0x1,
    }
 
    // When we serialize a Transaction, we specify the type DistributedTransaction, so a Transaction never
    // actually gets deserialized.
    public class Transaction : IDisposable, ISerializable
    {
        // UseServiceDomain
        //
        // Property tells parts of system.transactions if it should use a
        // service domain for current.
        [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
        internal static bool UseServiceDomainForCurrent() => false;
 
        // InteropMode
        //
        // This property figures out the current interop mode based on the
        // top of the transaction scope stack as well as the default mode
        // from config.
        internal static EnterpriseServicesInteropOption InteropMode(TransactionScope? currentScope)
        {
            if (currentScope != null)
            {
#pragma warning disable CA1416 // Validate platform compatibility, the property is not platform-specific, safe to suppress
                return currentScope.InteropMode;
#pragma warning restore CA1416
            }
 
            return EnterpriseServicesInteropOption.None;
        }
 
        internal static Transaction? FastGetTransaction(TransactionScope? currentScope, ContextData contextData, out Transaction? contextTransaction)
        {
            Transaction? current = null;
 
            contextTransaction = contextData.CurrentTransaction;
 
            switch (InteropMode(currentScope))
            {
                case EnterpriseServicesInteropOption.None:
 
                    current = contextTransaction;
 
                    // If there is a transaction in the execution context or if there is a current transaction scope
                    // then honer the transaction context.
                    if (current == null && currentScope == null)
                    {
                        // Otherwise check for an external current.
                        if (TransactionManager.s_currentDelegateSet)
                        {
                            current = TransactionManager.s_currentDelegate!();
                        }
                        else
                        {
                            current = EnterpriseServices.GetContextTransaction();
                        }
                    }
                    break;
 
                case EnterpriseServicesInteropOption.Full:
                    current = EnterpriseServices.GetContextTransaction();
                    break;
 
                case EnterpriseServicesInteropOption.Automatic:
                    if (EnterpriseServices.UseServiceDomainForCurrent())
                    {
                        current = EnterpriseServices.GetContextTransaction();
                    }
                    else
                    {
                        current = contextData.CurrentTransaction;
                    }
                    break;
            }
 
            return current;
        }
 
 
        // GetCurrentTransactionAndScope
        //
        // Returns both the current transaction and scope.  This is implemented for optimizations
        // in TransactionScope because it is required to get both of them in several cases.
        internal static void GetCurrentTransactionAndScope(
            TxLookup defaultLookup,
            out Transaction? current,
            out TransactionScope? currentScope,
            out Transaction? contextTransaction)
        {
            current = null;
            currentScope = null;
            contextTransaction = null;
 
            ContextData contextData = ContextData.LookupContextData(defaultLookup);
            if (contextData != null)
            {
                currentScope = contextData.CurrentScope;
                current = FastGetTransaction(currentScope, contextData, out contextTransaction);
            }
        }
 
        public static Transaction? Current
        {
            get
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodEnter(TraceSourceType.TraceSourceBase, "Transaction.get_Current");
                }
 
                GetCurrentTransactionAndScope(TxLookup.Default, out Transaction? current, out TransactionScope? currentScope, out _);
 
                if (currentScope != null)
                {
#pragma warning disable CA1416 // Validate platform compatibility, the property is not platform-specific, safe to suppress
                    if (currentScope.ScopeComplete)
#pragma warning restore CA1416
                    {
                        throw new InvalidOperationException(SR.TransactionScopeComplete);
                    }
                }
 
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodExit(TraceSourceType.TraceSourceBase, "Transaction.get_Current");
                }
 
                return current;
            }
 
            set
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodEnter(TraceSourceType.TraceSourceBase, "Transaction.set_Current");
                }
 
                // Bring your own Transaction(BYOT) is supported only for legacy scenarios.
                // This transaction won't be flown across thread continuations.
                if (InteropMode(ContextData.TLSCurrentData.CurrentScope) != EnterpriseServicesInteropOption.None)
                {
                    if (etwLog.IsEnabled())
                    {
                        etwLog.InvalidOperation("Transaction", "Transaction.set_Current");
                    }
 
                    throw new InvalidOperationException(SR.CannotSetCurrent);
                }
 
                // Support only legacy scenarios using TLS.
                ContextData.TLSCurrentData.CurrentTransaction = value;
                // Clear CallContext data.
                CallContextCurrentData.ClearCurrentData(null, false);
 
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodExit(TraceSourceType.TraceSourceBase, "Transaction.set_Current");
                }
            }
        }
 
        // Storage for the transaction isolation level
        internal IsolationLevel _isoLevel;
 
        // Storage for the consistent flag
        internal bool _complete;
 
        // Record an identifier for this clone
        internal int _cloneId;
 
        // Storage for a disposed flag
        internal const int _disposedTrueValue = 1;
        internal int _disposed;
        internal bool Disposed { get { return _disposed == Transaction._disposedTrueValue; } }
 
        internal Guid DistributedTxId
        {
            get
            {
                Guid returnValue = Guid.Empty;
 
                if (_internalTransaction != null)
                {
                    returnValue = _internalTransaction.DistributedTxId;
                }
                return returnValue;
            }
        }
 
        // Internal synchronization object for transactions.  It is not safe to lock on the
        // transaction object because it is public and users of the object may lock it for
        // other purposes.
        internal InternalTransaction _internalTransaction = null!;
 
        // The TransactionTraceIdentifier for the transaction instance.
        internal TransactionTraceIdentifier _traceIdentifier;
 
        // Not used by anyone
        private Transaction() { }
 
        // Create a transaction with the given settings
        //
        internal Transaction(IsolationLevel isoLevel, InternalTransaction? internalTransaction)
        {
            TransactionManager.ValidateIsolationLevel(isoLevel);
 
            _isoLevel = isoLevel;
 
            // Never create a transaction with an IsolationLevel of Unspecified.
            if (IsolationLevel.Unspecified == _isoLevel)
            {
                _isoLevel = TransactionManager.DefaultIsolationLevel;
            }
 
            if (internalTransaction != null)
            {
                _internalTransaction = internalTransaction;
                _cloneId = Interlocked.Increment(ref _internalTransaction._cloneCount);
            }
            else
            {
                // Null is passed from the constructor of a CommittableTransaction.  That
                // constructor will fill in the traceIdentifier because it has allocated the
                // internal transaction.
            }
        }
 
        internal Transaction(OletxTransaction distributedTransaction)
        {
            _isoLevel = distributedTransaction.IsolationLevel;
            _internalTransaction = new InternalTransaction(this, distributedTransaction);
            _cloneId = Interlocked.Increment(ref _internalTransaction._cloneCount);
        }
 
        internal Transaction(IsolationLevel isoLevel, ISimpleTransactionSuperior superior)
        {
            TransactionManager.ValidateIsolationLevel(isoLevel);
            ArgumentNullException.ThrowIfNull(superior);
 
            _isoLevel = isoLevel;
 
            // Never create a transaction with an IsolationLevel of Unspecified.
            if (IsolationLevel.Unspecified == _isoLevel)
            {
                _isoLevel = TransactionManager.DefaultIsolationLevel;
            }
 
            _internalTransaction = new InternalTransaction(this, superior);
            // ISimpleTransactionSuperior is defined to also promote to MSDTC.
            _internalTransaction.SetPromoterTypeToMSDTC();
            _cloneId = 1;
        }
 
        #region System.Object Overrides
 
        // Don't use the identifier for the hash code.
        //
        public override int GetHashCode()
        {
            return _internalTransaction.TransactionHash;
        }
 
 
        // Don't allow equals to get the identifier
        //
        public override bool Equals([NotNullWhen(true)] object? obj)
        {
            // If we can't cast the object as a Transaction, it must not be equal
            // to this, which is a Transaction. Check the internal transaction object for equality.
            return obj is Transaction transaction && _internalTransaction.TransactionHash == transaction._internalTransaction.TransactionHash;
        }
 
        public static bool operator ==(Transaction? x, Transaction? y)
        {
            if (((object?)x) != null)
            {
                return x.Equals(y);
            }
            return ((object?)y) == null;
        }
 
        public static bool operator !=(Transaction? x, Transaction? y)
        {
            if (((object?)x) != null)
            {
                return !x.Equals(y);
            }
            return ((object?)y) != null;
        }
 
 
        #endregion
 
        public TransactionInformation TransactionInformation
        {
            get
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
                }
 
                ObjectDisposedException.ThrowIf(Disposed, this);
 
                TransactionInformation? txInfo = _internalTransaction._transactionInformation;
                if (txInfo == null)
                {
                    // A race would only result in an extra allocation
                    txInfo = new TransactionInformation(_internalTransaction);
                    _internalTransaction._transactionInformation = txInfo;
                }
 
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
                }
 
                return txInfo;
            }
        }
 
 
        // Return the Isolation Level for the given transaction
        //
        public IsolationLevel IsolationLevel
        {
            get
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
                }
 
                ObjectDisposedException.ThrowIf(Disposed, this);
 
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
                }
                return _isoLevel;
            }
        }
 
        /// <summary>
        /// Gets the PromoterType value for the transaction.
        /// </summary>
        /// <value>
        /// If the transaction has not yet been promoted and does not yet have a promotable single phase enlistment,
        /// this property value will be Guid.Empty.
        ///
        /// If the transaction has been promoted or has a promotable single phase enlistment, this will return the
        /// promoter type specified by the transaction promoter.
        ///
        /// If the transaction is, or will be, promoted to MSDTC, the value will be TransactionInterop.PromoterTypeDtc.
        /// </value>
        public Guid PromoterType
        {
            get
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
                }
 
                ObjectDisposedException.ThrowIf(Disposed, this);
 
                lock (_internalTransaction)
                {
                    return _internalTransaction._promoterType;
                }
            }
        }
 
        /// <summary>
        /// Gets the PromotedToken for the transaction.
        ///
        /// If the transaction has not already been promoted, retrieving this value will cause promotion. Before retrieving the
        /// PromotedToken, the Transaction.PromoterType value should be checked to see if it is a promoter type (Guid) that the
        /// caller understands. If the caller does not recognize the PromoterType value, retrieving the PromotedToken doesn't
        /// have much value because the caller doesn't know how to utilize it. But if the PromoterType is recognized, the
        /// caller should know how to utilize the PromotedToken to communicate with the promoting distributed transaction
        /// coordinator to enlist on the distributed transaction.
        ///
        /// If the value of a transaction's PromoterType is TransactionInterop.PromoterTypeDtc, then that transaction's
        /// PromotedToken will be an MSDTC-based TransmitterPropagationToken.
        /// </summary>
        /// <returns>
        /// The byte[] that can be used to enlist with the distributed transaction coordinator used to promote the transaction.
        /// The format of the byte[] depends upon the value of Transaction.PromoterType.
        /// </returns>
        public byte[] GetPromotedToken()
        {
            // We need to ask the current transaction state for the PromotedToken because depending on the state
            // we may need to induce a promotion.
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            // We always make a copy of the promotedToken stored in the internal transaction.
            byte[] internalPromotedToken;
            lock (_internalTransaction)
            {
                Debug.Assert(_internalTransaction.State != null);
                internalPromotedToken = _internalTransaction.State.PromotedToken(_internalTransaction);
            }
 
            byte[] toReturn = new byte[internalPromotedToken.Length];
            Array.Copy(internalPromotedToken, toReturn, toReturn.Length);
            return toReturn;
        }
 
        public Enlistment EnlistDurable(
            Guid resourceManagerIdentifier,
            IEnlistmentNotification enlistmentNotification,
            EnlistmentOptions enlistmentOptions)
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            if (resourceManagerIdentifier == Guid.Empty)
            {
                throw new ArgumentException(SR.BadResourceManagerId, nameof(resourceManagerIdentifier));
            }
 
            ArgumentNullException.ThrowIfNull(enlistmentNotification);
 
            if (enlistmentOptions != EnlistmentOptions.None && enlistmentOptions != EnlistmentOptions.EnlistDuringPrepareRequired)
            {
                throw new ArgumentOutOfRangeException(nameof(enlistmentOptions));
            }
 
            if (_complete)
            {
                throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
            }
 
            lock (_internalTransaction)
            {
                Debug.Assert(_internalTransaction.State != null);
                Enlistment enlistment = _internalTransaction.State.EnlistDurable(_internalTransaction,
                    resourceManagerIdentifier, enlistmentNotification, enlistmentOptions, this);
 
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
                }
 
                return enlistment;
            }
        }
 
 
        // Forward request to the state machine to take the appropriate action.
        //
        public Enlistment EnlistDurable(
            Guid resourceManagerIdentifier,
            ISinglePhaseNotification singlePhaseNotification,
            EnlistmentOptions enlistmentOptions)
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            if (resourceManagerIdentifier == Guid.Empty)
            {
                throw new ArgumentException(SR.BadResourceManagerId, nameof(resourceManagerIdentifier));
            }
 
            ArgumentNullException.ThrowIfNull(singlePhaseNotification);
 
            if (enlistmentOptions != EnlistmentOptions.None && enlistmentOptions != EnlistmentOptions.EnlistDuringPrepareRequired)
            {
                throw new ArgumentOutOfRangeException(nameof(enlistmentOptions));
            }
 
            if (_complete)
            {
                throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
            }
 
            lock (_internalTransaction)
            {
                Debug.Assert(_internalTransaction.State != null);
                Enlistment enlistment = _internalTransaction.State.EnlistDurable(_internalTransaction,
                    resourceManagerIdentifier, singlePhaseNotification, enlistmentOptions, this);
 
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
                }
                return enlistment;
            }
        }
 
 
        public void Rollback()
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
                etwLog.TransactionRollback(TraceSourceType.TraceSourceLtm, TransactionTraceId, "Transaction");
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            lock (_internalTransaction)
            {
                Debug.Assert(_internalTransaction.State != null);
                _internalTransaction.State.Rollback(_internalTransaction, null);
            }
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
            }
        }
 
 
        public void Rollback(Exception? e)
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
                etwLog.TransactionRollback(TraceSourceType.TraceSourceLtm, TransactionTraceId, "Transaction");
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            lock (_internalTransaction)
            {
                Debug.Assert(_internalTransaction.State != null);
                _internalTransaction.State.Rollback(_internalTransaction, e);
            }
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
            }
        }
 
 
        // Forward request to the state machine to take the appropriate action.
        //
        public Enlistment EnlistVolatile(IEnlistmentNotification enlistmentNotification, EnlistmentOptions enlistmentOptions)
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            ArgumentNullException.ThrowIfNull(enlistmentNotification);
 
            if (enlistmentOptions != EnlistmentOptions.None && enlistmentOptions != EnlistmentOptions.EnlistDuringPrepareRequired)
            {
                throw new ArgumentOutOfRangeException(nameof(enlistmentOptions));
            }
 
            if (_complete)
            {
                throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
            }
 
            lock (_internalTransaction)
            {
                Debug.Assert(_internalTransaction.State != null);
                Enlistment enlistment = _internalTransaction.State.EnlistVolatile(_internalTransaction,
                    enlistmentNotification, enlistmentOptions, this);
 
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
                }
                return enlistment;
            }
        }
 
 
        // Forward request to the state machine to take the appropriate action.
        //
        public Enlistment EnlistVolatile(ISinglePhaseNotification singlePhaseNotification, EnlistmentOptions enlistmentOptions)
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            ArgumentNullException.ThrowIfNull(singlePhaseNotification);
 
            if (enlistmentOptions != EnlistmentOptions.None && enlistmentOptions != EnlistmentOptions.EnlistDuringPrepareRequired)
            {
                throw new ArgumentOutOfRangeException(nameof(enlistmentOptions));
            }
 
            if (_complete)
            {
                throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
            }
 
            lock (_internalTransaction)
            {
                Debug.Assert(_internalTransaction.State != null);
                Enlistment enlistment = _internalTransaction.State.EnlistVolatile(_internalTransaction,
                    singlePhaseNotification, enlistmentOptions, this);
 
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
                }
                return enlistment;
            }
        }
 
        // Create a clone of the transaction that forwards requests to this object.
        //
        public Transaction Clone()
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            if (_complete)
            {
                throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
            }
 
            Transaction clone = InternalClone();
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
            }
            return clone;
        }
 
        internal Transaction InternalClone()
        {
            Transaction clone = new Transaction(_isoLevel, _internalTransaction);
 
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.TransactionCloneCreate(clone, "Transaction");
            }
 
            return clone;
        }
 
 
        // Create a dependent clone of the transaction that forwards requests to this object.
        //
        public DependentTransaction DependentClone(
            DependentCloneOption cloneOption
            )
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
            }
 
            if (cloneOption != DependentCloneOption.BlockCommitUntilComplete
                && cloneOption != DependentCloneOption.RollbackIfNotComplete)
            {
                throw new ArgumentOutOfRangeException(nameof(cloneOption));
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            if (_complete)
            {
                throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
            }
 
            DependentTransaction clone = new DependentTransaction(
                _isoLevel, _internalTransaction, cloneOption == DependentCloneOption.BlockCommitUntilComplete);
 
            if (etwLog.IsEnabled())
            {
                etwLog.TransactionCloneCreate(clone, "DependentTransaction");
                etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
            }
            return clone;
        }
 
 
        internal TransactionTraceIdentifier TransactionTraceId
        {
            get
            {
                if (_traceIdentifier == TransactionTraceIdentifier.Empty)
                {
                    lock (_internalTransaction)
                    {
                        if (_traceIdentifier == TransactionTraceIdentifier.Empty)
                        {
                            TransactionTraceIdentifier temp = new TransactionTraceIdentifier(
                                _internalTransaction.TransactionTraceId.TransactionIdentifier,
                                _cloneId);
                            Interlocked.MemoryBarrier();
                            _traceIdentifier = temp;
                        }
                    }
                }
                return _traceIdentifier;
            }
        }
 
        // Forward request to the state machine to take the appropriate action.
        //
        public event TransactionCompletedEventHandler? TransactionCompleted
        {
            add
            {
                ObjectDisposedException.ThrowIf(Disposed, this);
 
                lock (_internalTransaction)
                {
                    Debug.Assert(_internalTransaction.State != null);
                    // Register for completion with the inner transaction
                    _internalTransaction.State.AddOutcomeRegistrant(_internalTransaction, value);
                }
            }
 
            remove
            {
                lock (_internalTransaction)
                {
                    _internalTransaction._transactionCompletedDelegate = (TransactionCompletedEventHandler?)
                        System.Delegate.Remove(_internalTransaction._transactionCompletedDelegate, value);
                }
            }
        }
 
        public void Dispose()
        {
            InternalDispose();
        }
 
        // Handle Transaction Disposal.
        //
        internal virtual void InternalDispose()
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
            }
 
            if (Interlocked.Exchange(ref _disposed, Transaction._disposedTrueValue) == Transaction._disposedTrueValue)
            {
                return;
            }
 
            // Attempt to clean up the internal transaction
            long remainingITx = Interlocked.Decrement(ref _internalTransaction._cloneCount);
            if (remainingITx == 0)
            {
                _internalTransaction.Dispose();
            }
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
            }
        }
 
        // Ask the state machine for serialization info.
        //
        void ISerializable.GetObjectData(
            SerializationInfo serializationInfo,
            StreamingContext context)
        {
            throw new PlatformNotSupportedException();
        }
 
        /// <summary>
        /// Create a promotable single phase enlistment that promotes to MSDTC.
        /// </summary>
        /// <param name="promotableSinglePhaseNotification">The object that implements the IPromotableSinglePhaseNotification interface.</param>
        /// <returns>
        /// True if the enlistment is successful.
        ///
        /// False if the transaction already has a durable enlistment or promotable single phase enlistment or
        /// if the transaction has already promoted. In this case, the caller will need to enlist in the transaction through other
        /// means, such as Transaction.EnlistDurable or retrieve the MSDTC export cookie or propagation token to enlist with MSDTC.
        /// </returns>
        // We apparently didn't spell Promotable like FXCop thinks it should be spelled.
        public bool EnlistPromotableSinglePhase(IPromotableSinglePhaseNotification promotableSinglePhaseNotification)
        {
            return EnlistPromotableSinglePhase(promotableSinglePhaseNotification, TransactionInterop.PromoterTypeDtc);
        }
 
        /// <summary>
        /// Create a promotable single phase enlistment that promotes to a distributed transaction manager other than MSDTC.
        /// </summary>
        /// <param name="promotableSinglePhaseNotification">The object that implements the IPromotableSinglePhaseNotification interface.</param>
        /// <param name="promoterType">
        /// The promoter type Guid that identifies the format of the byte[] that is returned by the ITransactionPromoter.Promote
        /// call that is implemented by the IPromotableSinglePhaseNotificationObject, and thus the promoter of the transaction.
        /// </param>
        /// <returns>
        /// True if the enlistment is successful.
        ///
        /// False if the transaction already has a durable enlistment or promotable single phase enlistment or
        /// if the transaction has already promoted. In this case, the caller will need to enlist in the transaction through other
        /// means.
        ///
        /// If the Transaction.PromoterType matches the promoter type supported by the caller, then the
        /// Transaction.PromotedToken can be retrieved and used to enlist directly with the identified distributed transaction manager.
        ///
        /// How the enlistment is created with the distributed transaction manager identified by the Transaction.PromoterType
        /// is defined by that distributed transaction manager.
        /// </returns>
        public bool EnlistPromotableSinglePhase(IPromotableSinglePhaseNotification promotableSinglePhaseNotification, Guid promoterType)
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            ArgumentNullException.ThrowIfNull(promotableSinglePhaseNotification);
 
            if (promoterType == Guid.Empty)
            {
                throw new ArgumentException(SR.PromoterTypeInvalid, nameof(promoterType));
            }
 
            if (_complete)
            {
                throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
            }
 
            bool succeeded = false;
 
            lock (_internalTransaction)
            {
                Debug.Assert(_internalTransaction.State != null);
                succeeded = _internalTransaction.State.EnlistPromotableSinglePhase(_internalTransaction, promotableSinglePhaseNotification, this, promoterType);
            }
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
            }
 
            return succeeded;
        }
 
        public Enlistment PromoteAndEnlistDurable(Guid resourceManagerIdentifier,
                                                  IPromotableSinglePhaseNotification promotableNotification,
                                                  ISinglePhaseNotification enlistmentNotification,
                                                  EnlistmentOptions enlistmentOptions)
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this);
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            if (resourceManagerIdentifier == Guid.Empty)
            {
                throw new ArgumentException(SR.BadResourceManagerId, nameof(resourceManagerIdentifier));
            }
 
            ArgumentNullException.ThrowIfNull(promotableNotification);
            ArgumentNullException.ThrowIfNull(enlistmentNotification);
 
            if (enlistmentOptions != EnlistmentOptions.None && enlistmentOptions != EnlistmentOptions.EnlistDuringPrepareRequired)
            {
                throw new ArgumentOutOfRangeException(nameof(enlistmentOptions));
            }
 
            if (_complete)
            {
                throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
            }
 
            lock (_internalTransaction)
            {
                Debug.Assert(_internalTransaction.State != null);
                Enlistment enlistment = _internalTransaction.State.PromoteAndEnlistDurable(_internalTransaction,
                    resourceManagerIdentifier, promotableNotification, enlistmentNotification, enlistmentOptions, this);
 
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this);
                }
 
                return enlistment;
            }
        }
 
        public void SetDistributedTransactionIdentifier(IPromotableSinglePhaseNotification promotableNotification,
                                                        Guid distributedTransactionIdentifier)
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            ArgumentNullException.ThrowIfNull(promotableNotification);
 
            if (distributedTransactionIdentifier == Guid.Empty)
            {
                throw new ArgumentException(null, nameof(distributedTransactionIdentifier));
            }
 
            if (_complete)
            {
                throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
            }
 
            lock (_internalTransaction)
            {
                Debug.Assert(_internalTransaction.State != null);
                _internalTransaction.State.SetDistributedTransactionId(_internalTransaction,
                    promotableNotification,
                    distributedTransactionIdentifier);
 
                if (etwLog.IsEnabled())
                {
                    etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
                }
                return;
            }
        }
 
        internal OletxTransaction? Promote()
        {
            lock (_internalTransaction)
            {
                Debug.Assert(_internalTransaction.State != null);
                // This method is only called when we expect to be promoting to MSDTC.
                _internalTransaction.ThrowIfPromoterTypeIsNotMSDTC();
                _internalTransaction.State.Promote(_internalTransaction);
                return _internalTransaction.PromotedTransaction;
            }
        }
    }
 
    //
    // The following code & data is related to management of Transaction.Current
    //
 
    internal enum DefaultComContextState
    {
        Unknown = 0,
        Unavailable = -1,
        Available = 1
    }
 
    //
    //  The TxLookup enum is used internally to detect where the ambient context needs to be stored or looked up.
    //  Default                  - Used internally when looking up Transaction.Current.
    //  DefaultCallContext - Used when TransactionScope with async flow option is enabled. Internally we will use CallContext to store the ambient transaction.
    //  Default TLS            - Used for legacy/synchronous TransactionScope. Internally we will use TLS to store the ambient transaction.
    //
    internal enum TxLookup
    {
        Default,
        DefaultCallContext,
        DefaultTLS,
    }
 
    //
    //  CallContextCurrentData holds the ambient transaction and uses CallContext and ConditionalWeakTable to track the ambient transaction.
    //  For async flow scenarios, we should not allow flowing of transaction across app domains. To prevent transaction from flowing across
    //  AppDomain/Remoting boundaries, we are using ConditionalWeakTable to hold the actual ambient transaction and store only a object reference
    //  in CallContext. When TransactionScope is used to invoke a call across AppDomain/Remoting boundaries, only the object reference will be sent
    //  across and not the actual ambient transaction which is stashed away in the ConditionalWeakTable.
    //
    internal static class CallContextCurrentData
    {
        private static readonly AsyncLocal<ContextKey?> s_currentTransaction = new AsyncLocal<ContextKey?>();
 
        // ConditionalWeakTable is used to automatically remove the entries that are no longer referenced. This will help prevent leaks in async nested TransactionScope
        // usage and when child nested scopes are not synchronized properly.
        private static readonly ConditionalWeakTable<ContextKey, ContextData> s_contextDataTable = new ConditionalWeakTable<ContextKey, ContextData>();
 
        //
        //  Set CallContext data with the given contextKey.
        //  return the ContextData if already present in contextDataTable, otherwise return the default value.
        //
        public static ContextData CreateOrGetCurrentData(ContextKey contextKey)
        {
            s_currentTransaction.Value = contextKey;
            return s_contextDataTable.GetValue(contextKey, (env) => new ContextData(true));
        }
 
        public static void ClearCurrentData(ContextKey? contextKey, bool removeContextData)
        {
            // Get the current ambient CallContext.
            ContextKey? key = s_currentTransaction.Value;
            if (contextKey != null || key != null)
            {
                // removeContextData flag is used for perf optimization to avoid removing from the table in certain nested TransactionScope usage.
                if (removeContextData)
                {
                    // if context key is passed in remove that from the contextDataTable, otherwise remove the default context key.
                    s_contextDataTable.Remove(contextKey ?? key!);
                }
 
                if (key != null)
                {
                    s_currentTransaction.Value = null;
                }
            }
        }
 
        public static bool TryGetCurrentData([NotNullWhen(true)] out ContextData? currentData)
        {
            currentData = null;
            ContextKey? contextKey = s_currentTransaction.Value;
            if (contextKey == null)
            {
                return false;
            }
            else
            {
                return s_contextDataTable.TryGetValue(contextKey, out currentData);
            }
        }
    }
 
    //
    // MarshalByRefObject is needed for cross AppDomain scenarios where just using object will end up with a different reference when call is made across serialization boundary.
    //
    internal sealed class ContextKey // : MarshalByRefObject
    {
    }
 
    internal sealed class ContextData
    {
        internal TransactionScope? CurrentScope;
        internal Transaction? CurrentTransaction;
 
        internal DefaultComContextState DefaultComContextState;
        internal WeakReference? WeakDefaultComContext;
 
        internal bool _asyncFlow;
 
        [ThreadStatic]
        private static ContextData? t_staticData;
 
        internal ContextData(bool asyncFlow)
        {
            _asyncFlow = asyncFlow;
        }
 
        [AllowNull]
        internal static ContextData TLSCurrentData
        {
            get => t_staticData ??= new ContextData(false);
            set
            {
                if (value == null && t_staticData != null)
                {
                    // set each property to null to retain one TLS ContextData copy.
                    t_staticData.CurrentScope = null;
                    t_staticData.CurrentTransaction = null;
                    t_staticData.DefaultComContextState = DefaultComContextState.Unknown;
                    t_staticData.WeakDefaultComContext = null;
                }
                else
                {
                    t_staticData = value;
                }
            }
        }
 
        internal static ContextData LookupContextData(TxLookup defaultLookup)
        {
            ContextData? currentData;
            if (CallContextCurrentData.TryGetCurrentData(out currentData))
            {
                if (currentData.CurrentScope == null && currentData.CurrentTransaction == null && defaultLookup != TxLookup.DefaultCallContext)
                {
                    // Clear Call Context Data
                    CallContextCurrentData.ClearCurrentData(null, true);
                    return TLSCurrentData;
                }
 
                return currentData;
            }
            else
            {
                return TLSCurrentData;
            }
        }
    }
}