File: System\Transactions\DurableEnlistmentState.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.Threading;
 
namespace System.Transactions
{
    // Base class for all durable enlistment states
    internal abstract class DurableEnlistmentState : EnlistmentState
    {
        private static DurableEnlistmentActive? s_durableEnlistmentActive;
        private static DurableEnlistmentAborting? s_durableEnlistmentAborting;
        private static DurableEnlistmentCommitting? s_durableEnlistmentCommitting;
        private static DurableEnlistmentDelegated? s_durableEnlistmentDelegated;
        private static DurableEnlistmentEnded? s_durableEnlistmentEnded;
 
        // Object for synchronizing access to the entire class( avoiding lock( typeof( ... )) )
        private static object? s_classSyncObject;
 
        internal static DurableEnlistmentActive DurableEnlistmentActive =>
            LazyInitializer.EnsureInitialized(ref s_durableEnlistmentActive, ref s_classSyncObject, () => new DurableEnlistmentActive());
 
        protected static DurableEnlistmentAborting DurableEnlistmentAborting =>
            LazyInitializer.EnsureInitialized(ref s_durableEnlistmentAborting, ref s_classSyncObject, () => new DurableEnlistmentAborting());
 
        protected static DurableEnlistmentCommitting DurableEnlistmentCommitting =>
            LazyInitializer.EnsureInitialized(ref s_durableEnlistmentCommitting, ref s_classSyncObject, () => new DurableEnlistmentCommitting());
 
        protected static DurableEnlistmentDelegated DurableEnlistmentDelegated =>
            LazyInitializer.EnsureInitialized(ref s_durableEnlistmentDelegated, ref s_classSyncObject, () => new DurableEnlistmentDelegated());
 
        protected static DurableEnlistmentEnded DurableEnlistmentEnded =>
            LazyInitializer.EnsureInitialized(ref s_durableEnlistmentEnded, ref s_classSyncObject, () => new DurableEnlistmentEnded());
    }
 
    // Active state for a durable enlistment.  In this state the transaction can be aborted
    // asynchronously by calling abort.
    internal sealed class DurableEnlistmentActive : DurableEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
 
            // Yeah it's active
        }
 
        internal override void EnlistmentDone(InternalEnlistment enlistment)
        {
            // Mark the enlistment as done.
            DurableEnlistmentEnded.EnterState(enlistment);
        }
 
        internal override void InternalAborted(InternalEnlistment enlistment)
        {
            // Transition to the aborting state
            DurableEnlistmentAborting.EnterState(enlistment);
        }
 
        internal override void ChangeStateCommitting(InternalEnlistment enlistment)
        {
            // Transition to the committing state
            DurableEnlistmentCommitting.EnterState(enlistment);
        }
 
        internal override void ChangeStatePromoted(InternalEnlistment enlistment, IPromotedEnlistment promotedEnlistment)
        {
            // Save the promoted enlistment because future notifications must be sent here.
            enlistment.PromotedEnlistment = promotedEnlistment;
 
            // The transaction is being promoted promote the enlistment as well
            EnlistmentStatePromoted.EnterState(enlistment);
        }
 
        internal override void ChangeStateDelegated(InternalEnlistment enlistment)
        {
            // This is a valid state transition.
            DurableEnlistmentDelegated.EnterState(enlistment);
        }
    }
 
    // Aborting state for a durable enlistment.  In this state the transaction has been aborted,
    // by someone other than the enlistment.
    //
    internal sealed class DurableEnlistmentAborting : DurableEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
 
            Monitor.Exit(enlistment.Transaction);
            try
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.Rollback);
                }
 
                // Send the Rollback notification to the enlistment
                if (enlistment.SinglePhaseNotification != null)
                {
                    enlistment.SinglePhaseNotification.Rollback(enlistment.SinglePhaseEnlistment);
                }
                else
                {
                    enlistment.PromotableSinglePhaseNotification.Rollback(enlistment.SinglePhaseEnlistment);
                }
            }
            finally
            {
                Monitor.Enter(enlistment.Transaction);
            }
        }
 
        internal override void Aborted(InternalEnlistment enlistment, Exception? e)
        {
            enlistment.Transaction._innerException ??= e;
 
            // Transition to the ended state
            DurableEnlistmentEnded.EnterState(enlistment);
        }
 
        internal override void EnlistmentDone(InternalEnlistment enlistment)
        {
            // Transition to the ended state
            DurableEnlistmentEnded.EnterState(enlistment);
        }
    }
 
    // Committing state is when SPC has been sent to an enlistment but no response
    // has been received.
    //
    internal sealed class DurableEnlistmentCommitting : DurableEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            bool spcCommitted = false;
            // Set the enlistment state
            enlistment.State = this;
 
            Monitor.Exit(enlistment.Transaction);
            try
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.SinglePhaseCommit);
                }
 
                // Send the Commit notification to the enlistment
                if (enlistment.SinglePhaseNotification != null)
                {
                    enlistment.SinglePhaseNotification.SinglePhaseCommit(enlistment.SinglePhaseEnlistment);
                }
                else
                {
                    enlistment.PromotableSinglePhaseNotification.SinglePhaseCommit(enlistment.SinglePhaseEnlistment);
                }
                spcCommitted = true;
            }
            finally
            {
                if (!spcCommitted)
                {
                    enlistment.SinglePhaseEnlistment.InDoubt();
                }
                Monitor.Enter(enlistment.Transaction);
            }
        }
 
        internal override void EnlistmentDone(InternalEnlistment enlistment)
        {
            // EnlistmentDone should be treated the same as Committed from this state.
            // This eliminates a race between the SPC call and the EnlistmentDone call.
 
            // Transition to the ended state
            DurableEnlistmentEnded.EnterState(enlistment);
 
            Debug.Assert(enlistment.Transaction.State != null);
            // Make the transaction commit
            enlistment.Transaction.State.ChangeStateTransactionCommitted(enlistment.Transaction);
        }
 
        internal override void Committed(InternalEnlistment enlistment)
        {
            // Transition to the ended state
            DurableEnlistmentEnded.EnterState(enlistment);
 
            Debug.Assert(enlistment.Transaction.State != null);
            // Make the transaction commit
            enlistment.Transaction.State.ChangeStateTransactionCommitted(enlistment.Transaction);
        }
 
        internal override void Aborted(InternalEnlistment enlistment, Exception? e)
        {
            // Transition to the ended state
            DurableEnlistmentEnded.EnterState(enlistment);
 
            Debug.Assert(enlistment.Transaction.State != null);
            // Start the transaction aborting
            enlistment.Transaction.State.ChangeStateTransactionAborted(enlistment.Transaction, e);
        }
 
        internal override void InDoubt(InternalEnlistment enlistment, Exception? e)
        {
            // Transition to the ended state
            DurableEnlistmentEnded.EnterState(enlistment);
 
            enlistment.Transaction._innerException ??= e;
 
            Debug.Assert(enlistment.Transaction.State != null);
            // Make the transaction in dobut
            enlistment.Transaction.State.InDoubtFromEnlistment(enlistment.Transaction);
        }
    }
 
    // Delegated state for a durable enlistment represents an enlistment that was
    // originally a PromotableSinglePhaseEnlisment that where promotion has happened.
    // These enlistments don't need to participate in the commit process anymore.
    internal sealed class DurableEnlistmentDelegated : DurableEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
 
            // At this point the durable enlistment should have someone to forward to.
            Debug.Assert(enlistment.PromotableSinglePhaseNotification != null);
        }
 
        internal override void Committed(InternalEnlistment enlistment)
        {
            // Transition to the ended state
            DurableEnlistmentEnded.EnterState(enlistment);
 
            Debug.Assert(enlistment.Transaction.State != null);
            // Change the transaction to committed.
            enlistment.Transaction.State.ChangeStatePromotedCommitted(enlistment.Transaction);
        }
 
        internal override void Aborted(InternalEnlistment enlistment, Exception? e)
        {
            // Transition to the ended state
            DurableEnlistmentEnded.EnterState(enlistment);
 
            enlistment.Transaction._innerException ??= e;
 
            Debug.Assert(enlistment.Transaction.State != null);
            // Start the transaction aborting
            enlistment.Transaction.State.ChangeStatePromotedAborted(enlistment.Transaction);
        }
 
        internal override void InDoubt(InternalEnlistment enlistment, Exception? e)
        {
            // Transition to the ended state
            DurableEnlistmentEnded.EnterState(enlistment);
 
            enlistment.Transaction._innerException ??= e;
 
            Debug.Assert(enlistment.Transaction.State != null);
            // Tell the transaction that the enlistment is InDoubt.  Note that
            // for a transaction that has been delegated and then promoted there
            // are two chances to get a better answer than indoubt.  So it may be that
            // the TM will have a better answer.
            enlistment.Transaction.State.InDoubtFromEnlistment(enlistment.Transaction);
        }
    }
 
    // Ended state is the state that is entered when the durable enlistment has committed,
    // aborted, or said read only for an enlistment.  At this point there are no valid
    // operations on the enlistment.
    internal sealed class DurableEnlistmentEnded : DurableEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
        }
 
        internal override void InternalAborted(InternalEnlistment enlistment)
        {
            // From the Aborting state the transaction may tell the enlistment to abort.  At this point
            // it already knows.  Eat this message.
        }
 
        internal override void InDoubt(InternalEnlistment enlistment, Exception? e)
        {
            // Ignore this in case the enlistment gets here before
            // the transaction tells it to do so
        }
    }
}