File: System\Transactions\Enlistment.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.Globalization;
using System.Threading;
 
namespace System.Transactions
{
    internal interface IPromotedEnlistment
    {
        void EnlistmentDone();
 
        void Prepared();
 
        void ForceRollback();
 
        void ForceRollback(Exception? e);
 
        void Committed();
 
        void Aborted();
 
        void Aborted(Exception? e);
 
        void InDoubt();
 
        void InDoubt(Exception? e);
 
        byte[] GetRecoveryInformation();
 
        InternalEnlistment? InternalEnlistment
        {
            get;
            set;
        }
    }
 
    //
    // InternalEnlistment by itself can support a Phase0 volatile enlistment.
    // There are derived classes to support durable, phase1 volatile & PSPE
    // enlistments.
    //
    internal class InternalEnlistment : ISinglePhaseNotificationInternal
    {
        // Storage for the state of the enlistment.
        internal EnlistmentState? _twoPhaseState;
 
        // Interface implemented by the enlistment owner for notifications
        protected IEnlistmentNotification? _twoPhaseNotifications;
 
        // Store a reference to the single phase notification interface in case
        // the enlisment supports it.
        protected ISinglePhaseNotification? _singlePhaseNotifications;
 
        // Reference to the containing transaction.
        protected InternalTransaction _transaction = null!;
 
        // Reference to the lightweight transaction.
        private readonly Transaction? _atomicTransaction;
 
        // The EnlistmentTraceIdentifier for this enlistment.
        private EnlistmentTraceIdentifier _traceIdentifier;
 
        // Unique value amongst all enlistments for a given internal transaction.
        private readonly int _enlistmentId;
 
        internal Guid DistributedTxId
        {
            get
            {
                Guid returnValue = Guid.Empty;
 
                if (Transaction != null)
                {
                    returnValue = Transaction.DistributedTxId;
                }
                return returnValue;
            }
        }
 
        // Parent Enlistment Object
        private readonly Enlistment _enlistment;
        private PreparingEnlistment? _preparingEnlistment;
        private SinglePhaseEnlistment? _singlePhaseEnlistment;
 
        // If this enlistment is promoted store the object it delegates to.
        private IPromotedEnlistment? _promotedEnlistment;
 
        // For Recovering Enlistments
        protected InternalEnlistment(Enlistment enlistment, IEnlistmentNotification twoPhaseNotifications)
        {
            Debug.Assert(this is RecoveringInternalEnlistment);
            _enlistment = enlistment;
            _twoPhaseNotifications = twoPhaseNotifications;
            _enlistmentId = 1;
            _traceIdentifier = EnlistmentTraceIdentifier.Empty;
        }
 
        // For Promotable Enlistments
        protected InternalEnlistment(Enlistment enlistment, InternalTransaction transaction, Transaction atomicTransaction)
        {
            Debug.Assert(this is PromotableInternalEnlistment);
            _enlistment = enlistment;
            _transaction = transaction;
            _atomicTransaction = atomicTransaction;
            _enlistmentId = transaction._enlistmentCount++;
            _traceIdentifier = EnlistmentTraceIdentifier.Empty;
        }
 
        internal InternalEnlistment(
            Enlistment enlistment,
            InternalTransaction transaction,
            IEnlistmentNotification twoPhaseNotifications,
            ISinglePhaseNotification? singlePhaseNotifications,
            Transaction atomicTransaction)
        {
            _enlistment = enlistment;
            _transaction = transaction;
            _twoPhaseNotifications = twoPhaseNotifications;
            _singlePhaseNotifications = singlePhaseNotifications;
            _atomicTransaction = atomicTransaction;
            _enlistmentId = transaction._enlistmentCount++;
            _traceIdentifier = EnlistmentTraceIdentifier.Empty;
        }
 
        internal InternalEnlistment(
            Enlistment enlistment,
            IEnlistmentNotification twoPhaseNotifications,
            InternalTransaction transaction,
            Transaction atomicTransaction)
        {
            _enlistment = enlistment;
            _twoPhaseNotifications = twoPhaseNotifications;
            _transaction = transaction;
            _atomicTransaction = atomicTransaction;
        }
 
        internal EnlistmentState State
        {
            get {
                Debug.Assert(_twoPhaseState != null);
                return _twoPhaseState;
            }
            set { _twoPhaseState = value; }
        }
 
        internal Enlistment Enlistment => _enlistment;
 
        internal PreparingEnlistment PreparingEnlistment =>
            // If there is a race here one of the objects would simply be garbage collected.
            _preparingEnlistment ??= new PreparingEnlistment(this);
 
        internal SinglePhaseEnlistment SinglePhaseEnlistment =>
            // If there is a race here one of the objects would simply be garbage collected.
            _singlePhaseEnlistment ??= new SinglePhaseEnlistment(this);
 
        internal InternalTransaction Transaction => _transaction;
 
        internal virtual object SyncRoot
        {
            get
            {
                Debug.Assert(_transaction != null, "this.transaction != null");
                return _transaction;
            }
        }
 
        internal IEnlistmentNotification? EnlistmentNotification => _twoPhaseNotifications;
 
        internal ISinglePhaseNotification? SinglePhaseNotification => _singlePhaseNotifications;
 
        internal virtual IPromotableSinglePhaseNotification PromotableSinglePhaseNotification
        {
            get
            {
                Debug.Fail("PromotableSinglePhaseNotification called for a non promotable enlistment.");
                throw new NotImplementedException();
            }
        }
 
        internal IPromotedEnlistment? PromotedEnlistment
        {
            get { return _promotedEnlistment; }
            set { _promotedEnlistment = value; }
        }
 
        internal EnlistmentTraceIdentifier EnlistmentTraceId
        {
            get
            {
                if (_traceIdentifier == EnlistmentTraceIdentifier.Empty)
                {
                    lock (SyncRoot)
                    {
                        if (_traceIdentifier == EnlistmentTraceIdentifier.Empty)
                        {
                            EnlistmentTraceIdentifier temp;
                            if (null != _atomicTransaction)
                            {
                                temp = new EnlistmentTraceIdentifier(
                                    Guid.Empty,
                                    _atomicTransaction.TransactionTraceId,
                                    _enlistmentId);
                            }
                            else
                            {
                                temp = new EnlistmentTraceIdentifier(
                                    Guid.Empty,
                                    new TransactionTraceIdentifier(
                                        string.Create(CultureInfo.InvariantCulture, $"{InternalTransaction.InstanceIdentifier}{Interlocked.Increment(ref InternalTransaction._nextHash)}"),
                                        0),
                                    _enlistmentId);
                            }
                            Interlocked.MemoryBarrier();
                            _traceIdentifier = temp;
                        }
                    }
                }
                return _traceIdentifier;
            }
        }
 
        internal virtual void FinishEnlistment()
        {
            // Note another enlistment finished.
            Transaction._phase0Volatiles._preparedVolatileEnlistments++;
            CheckComplete();
        }
 
        internal virtual void CheckComplete()
        {
            // Make certain we increment the right list.
            Debug.Assert(Transaction._phase0Volatiles._preparedVolatileEnlistments <=
                Transaction._phase0Volatiles._volatileEnlistmentCount + Transaction._phase0Volatiles._dependentClones);
 
            // Check to see if all of the volatile enlistments are done.
            if (Transaction._phase0Volatiles._preparedVolatileEnlistments ==
                Transaction._phase0VolatileWaveCount + Transaction._phase0Volatiles._dependentClones)
            {
                Transaction.State!.Phase0VolatilePrepareDone(Transaction);
            }
        }
 
        internal virtual Guid ResourceManagerIdentifier
        {
            get
            {
                Debug.Fail("ResourceManagerIdentifier called for non durable enlistment");
                throw new NotImplementedException();
            }
        }
 
        void ISinglePhaseNotificationInternal.SinglePhaseCommit(IPromotedEnlistment singlePhaseEnlistment)
        {
            bool spcCommitted = false;
            _promotedEnlistment = singlePhaseEnlistment;
            try
            {
                Debug.Assert(_singlePhaseNotifications != null);
                _singlePhaseNotifications.SinglePhaseCommit(SinglePhaseEnlistment);
                spcCommitted = true;
            }
            finally
            {
                if (!spcCommitted)
                {
                    SinglePhaseEnlistment.InDoubt();
                }
            }
        }
 
        void IEnlistmentNotificationInternal.Prepare(IPromotedEnlistment preparingEnlistment)
        {
            Debug.Assert(_twoPhaseNotifications != null);
            _promotedEnlistment = preparingEnlistment;
            _twoPhaseNotifications.Prepare(PreparingEnlistment);
        }
 
        void IEnlistmentNotificationInternal.Commit(IPromotedEnlistment enlistment)
        {
            Debug.Assert(_twoPhaseNotifications != null);
            _promotedEnlistment = enlistment;
            _twoPhaseNotifications.Commit(Enlistment);
        }
 
        void IEnlistmentNotificationInternal.Rollback(IPromotedEnlistment enlistment)
        {
            Debug.Assert(_twoPhaseNotifications != null);
            _promotedEnlistment = enlistment;
            _twoPhaseNotifications.Rollback(Enlistment);
        }
 
        void IEnlistmentNotificationInternal.InDoubt(IPromotedEnlistment enlistment)
        {
            Debug.Assert(_twoPhaseNotifications != null);
            _promotedEnlistment = enlistment;
            _twoPhaseNotifications.InDoubt(Enlistment);
        }
    }
 
    internal class DurableInternalEnlistment : InternalEnlistment
    {
        // Resource Manager Identifier for this enlistment if it is durable
        internal Guid _resourceManagerIdentifier;
 
        internal DurableInternalEnlistment(
            Enlistment enlistment,
            Guid resourceManagerIdentifier,
            InternalTransaction transaction,
            IEnlistmentNotification twoPhaseNotifications,
            ISinglePhaseNotification? singlePhaseNotifications,
            Transaction atomicTransaction) :
            base(enlistment, transaction, twoPhaseNotifications, singlePhaseNotifications, atomicTransaction)
        {
            _resourceManagerIdentifier = resourceManagerIdentifier;
        }
 
        protected DurableInternalEnlistment(Enlistment enlistment, IEnlistmentNotification twoPhaseNotifications) :
            base(enlistment, twoPhaseNotifications)
        {
        }
 
        internal override Guid ResourceManagerIdentifier => _resourceManagerIdentifier;
    }
 
    //
    // Since RecoveringInternalEnlistment does not have a transaction it must take
    // a separate object as its sync root.
    //
    internal sealed class RecoveringInternalEnlistment : DurableInternalEnlistment
    {
        private readonly object _syncRoot;
 
        internal RecoveringInternalEnlistment(Enlistment enlistment, IEnlistmentNotification twoPhaseNotifications, object syncRoot) :
            base(enlistment, twoPhaseNotifications)
        {
            _syncRoot = syncRoot;
        }
 
        internal override object SyncRoot => _syncRoot;
    }
 
    internal sealed class PromotableInternalEnlistment : InternalEnlistment
    {
        // This class acts as the durable single phase enlistment for a
        // promotable single phase enlistment.
        private readonly IPromotableSinglePhaseNotification _promotableNotificationInterface;
 
        internal PromotableInternalEnlistment(
            Enlistment enlistment,
            InternalTransaction transaction,
            IPromotableSinglePhaseNotification promotableSinglePhaseNotification,
            Transaction atomicTransaction) :
            base(enlistment, transaction, atomicTransaction)
        {
            _promotableNotificationInterface = promotableSinglePhaseNotification;
        }
 
        internal override IPromotableSinglePhaseNotification PromotableSinglePhaseNotification => _promotableNotificationInterface;
    }
 
 
    // This class supports volatile enlistments
    //
    internal sealed class Phase1VolatileEnlistment : InternalEnlistment
    {
        public Phase1VolatileEnlistment(
            Enlistment enlistment,
            InternalTransaction transaction,
            IEnlistmentNotification twoPhaseNotifications,
            ISinglePhaseNotification? singlePhaseNotifications,
            Transaction atomicTransaction)
            : base(enlistment, transaction, twoPhaseNotifications, singlePhaseNotifications, atomicTransaction)
        {
        }
 
        internal override void FinishEnlistment()
        {
            // Note another enlistment finished.
            _transaction._phase1Volatiles._preparedVolatileEnlistments++;
            CheckComplete();
        }
 
        internal override void CheckComplete()
        {
            // Make certain we increment the right list.
            Debug.Assert(_transaction._phase1Volatiles._preparedVolatileEnlistments <=
                _transaction._phase1Volatiles._volatileEnlistmentCount +
                _transaction._phase1Volatiles._dependentClones);
 
            // Check to see if all of the volatile enlistments are done.
            if (_transaction._phase1Volatiles._preparedVolatileEnlistments ==
                _transaction._phase1Volatiles._volatileEnlistmentCount +
                _transaction._phase1Volatiles._dependentClones)
            {
                _transaction.State!.Phase1VolatilePrepareDone(_transaction);
            }
        }
    }
 
    public class Enlistment
    {
        // Interface for communicating with the state machine.
        internal InternalEnlistment _internalEnlistment;
 
        internal Enlistment(InternalEnlistment internalEnlistment)
        {
            _internalEnlistment = internalEnlistment;
        }
 
        internal Enlistment(
            Guid resourceManagerIdentifier,
            InternalTransaction transaction,
            IEnlistmentNotification twoPhaseNotifications,
            ISinglePhaseNotification? singlePhaseNotifications,
            Transaction atomicTransaction)
        {
            _internalEnlistment = new DurableInternalEnlistment(
                this,
                resourceManagerIdentifier,
                transaction,
                twoPhaseNotifications,
                singlePhaseNotifications,
                atomicTransaction
                );
        }
 
        internal Enlistment(
            InternalTransaction transaction,
            IEnlistmentNotification twoPhaseNotifications,
            ISinglePhaseNotification? singlePhaseNotifications,
            Transaction atomicTransaction,
            EnlistmentOptions enlistmentOptions)
        {
            if ((enlistmentOptions & EnlistmentOptions.EnlistDuringPrepareRequired) != 0)
            {
                _internalEnlistment = new InternalEnlistment(
                    this,
                    transaction,
                    twoPhaseNotifications,
                    singlePhaseNotifications,
                    atomicTransaction
                    );
            }
            else
            {
                _internalEnlistment = new Phase1VolatileEnlistment(
                this,
                transaction,
                twoPhaseNotifications,
                singlePhaseNotifications,
                atomicTransaction
                );
            }
        }
 
        // This constructor is for a promotable single phase enlistment.
        internal Enlistment(
            InternalTransaction transaction,
            IPromotableSinglePhaseNotification promotableSinglePhaseNotification,
            Transaction atomicTransaction)
        {
            _internalEnlistment = new PromotableInternalEnlistment(
                this,
                transaction,
                promotableSinglePhaseNotification,
                atomicTransaction
                );
        }
 
        internal Enlistment(
            IEnlistmentNotification twoPhaseNotifications,
            InternalTransaction transaction,
            Transaction atomicTransaction)
        {
            _internalEnlistment = new InternalEnlistment(
                this,
                twoPhaseNotifications,
                transaction,
                atomicTransaction
                );
        }
 
        internal Enlistment(IEnlistmentNotification twoPhaseNotifications, object syncRoot)
        {
            _internalEnlistment = new RecoveringInternalEnlistment(
                this,
                twoPhaseNotifications,
                syncRoot
                );
        }
 
        public void Done()
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
                etwLog.EnlistmentDone(_internalEnlistment);
            }
 
            lock (_internalEnlistment.SyncRoot)
            {
                _internalEnlistment.State.EnlistmentDone(_internalEnlistment);
            }
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
            }
        }
 
 
        internal InternalEnlistment InternalEnlistment => _internalEnlistment;
    }
}