File: System\Transactions\VolatileEnlistmentState.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
{
    internal delegate void FinishVolatileDelegate(InternalEnlistment enlistment);
 
    // Base class for all volatile enlistment states
    internal abstract class VolatileEnlistmentState : EnlistmentState
    {
        private static VolatileEnlistmentActive? s_volatileEnlistmentActive;
        private static VolatileEnlistmentPreparing? s_volatileEnlistmentPreparing;
        private static VolatileEnlistmentPrepared? s_volatileEnlistmentPrepared;
        private static VolatileEnlistmentSPC? s_volatileEnlistmentSPC;
        private static VolatileEnlistmentPreparingAborting? s_volatileEnlistmentPreparingAborting;
        private static VolatileEnlistmentAborting? s_volatileEnlistmentAborting;
        private static VolatileEnlistmentCommitting? s_volatileEnlistmentCommitting;
        private static VolatileEnlistmentInDoubt? s_volatileEnlistmentInDoubt;
        private static VolatileEnlistmentEnded? s_volatileEnlistmentEnded;
        private static VolatileEnlistmentDone? s_volatileEnlistmentDone;
 
        // Object for synchronizing access to the entire class( avoiding lock( typeof( ... )) )
        private static object? s_classSyncObject;
 
        internal static VolatileEnlistmentActive VolatileEnlistmentActive =>
            LazyInitializer.EnsureInitialized(ref s_volatileEnlistmentActive, ref s_classSyncObject, () => new VolatileEnlistmentActive());
 
 
        protected static VolatileEnlistmentPreparing VolatileEnlistmentPreparing =>
            LazyInitializer.EnsureInitialized(ref s_volatileEnlistmentPreparing, ref s_classSyncObject, () => new VolatileEnlistmentPreparing());
 
 
        protected static VolatileEnlistmentPrepared VolatileEnlistmentPrepared =>
            LazyInitializer.EnsureInitialized(ref s_volatileEnlistmentPrepared, ref s_classSyncObject, () => new VolatileEnlistmentPrepared());
 
 
        protected static VolatileEnlistmentSPC VolatileEnlistmentSPC =>
            LazyInitializer.EnsureInitialized(ref s_volatileEnlistmentSPC, ref s_classSyncObject, () => new VolatileEnlistmentSPC());
 
 
        protected static VolatileEnlistmentPreparingAborting VolatileEnlistmentPreparingAborting =>
            LazyInitializer.EnsureInitialized(ref s_volatileEnlistmentPreparingAborting, ref s_classSyncObject, () => new VolatileEnlistmentPreparingAborting());
 
 
        protected static VolatileEnlistmentAborting VolatileEnlistmentAborting =>
            LazyInitializer.EnsureInitialized(ref s_volatileEnlistmentAborting, ref s_classSyncObject, () => new VolatileEnlistmentAborting());
 
 
        protected static VolatileEnlistmentCommitting VolatileEnlistmentCommitting =>
            LazyInitializer.EnsureInitialized(ref s_volatileEnlistmentCommitting, ref s_classSyncObject, () => new VolatileEnlistmentCommitting());
 
 
        protected static VolatileEnlistmentInDoubt VolatileEnlistmentInDoubt =>
            LazyInitializer.EnsureInitialized(ref s_volatileEnlistmentInDoubt, ref s_classSyncObject, () => new VolatileEnlistmentInDoubt());
 
 
        protected static VolatileEnlistmentEnded VolatileEnlistmentEnded =>
            LazyInitializer.EnsureInitialized(ref s_volatileEnlistmentEnded, ref s_classSyncObject, () => new VolatileEnlistmentEnded());
 
 
        protected static VolatileEnlistmentDone VolatileEnlistmentDone =>
            LazyInitializer.EnsureInitialized(ref s_volatileEnlistmentDone, ref s_classSyncObject, () => new VolatileEnlistmentDone());
 
 
        // Override of get_RecoveryInformation to be more specific with the exception string.
        internal override byte[] RecoveryInformation(InternalEnlistment enlistment)
        {
            throw TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceLtm,
                SR.VolEnlistNoRecoveryInfo, null, enlistment == null ? Guid.Empty : enlistment.DistributedTxId);
        }
    }
 
    // Active state for a volatile enlistment indicates that the enlistment has been created
    // but no one has begun committing or aborting the transaction.  From this state the enlistment
    // can abort the transaction or call read only to indicate that it does not want to
    // participate further in the transaction.
    internal sealed class VolatileEnlistmentActive : VolatileEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
 
            // Yeah it's active.
        }
 
        #region IEnlistment Related Events
 
        internal override void EnlistmentDone(InternalEnlistment enlistment)
        {
            // End this enlistment
            VolatileEnlistmentDone.EnterState(enlistment);
 
            // Note another enlistment finished.
            enlistment.FinishEnlistment();
        }
 
        #endregion
 
        #region State Change Events
 
        internal override void ChangeStatePreparing(InternalEnlistment enlistment)
        {
            VolatileEnlistmentPreparing.EnterState(enlistment);
        }
 
 
        internal override void ChangeStateSinglePhaseCommit(InternalEnlistment enlistment)
        {
            VolatileEnlistmentSPC.EnterState(enlistment);
        }
 
 
        #endregion
 
        #region Internal Events
 
        internal override void InternalAborted(InternalEnlistment enlistment)
        {
            // Change the enlistment state to aborting.
            VolatileEnlistmentAborting.EnterState(enlistment);
        }
 
        #endregion
    }
 
    // Preparing state is the time after prepare has been called but no response has been received.
    internal sealed class VolatileEnlistmentPreparing : VolatileEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
 
            Monitor.Exit(enlistment.Transaction);
            try // Don't hold this lock while calling into the application code.
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.Prepare);
                }
 
                Debug.Assert(enlistment.EnlistmentNotification != null);
                enlistment.EnlistmentNotification.Prepare(enlistment.PreparingEnlistment);
            }
            finally
            {
                Monitor.Enter(enlistment.Transaction);
            }
        }
 
        internal override void EnlistmentDone(InternalEnlistment enlistment)
        {
            VolatileEnlistmentDone.EnterState(enlistment);
 
            // Process Finished InternalEnlistment
            enlistment.FinishEnlistment();
        }
 
        internal override void Prepared(InternalEnlistment enlistment)
        {
            // Change the enlistments state to prepared.
            VolatileEnlistmentPrepared.EnterState(enlistment);
 
            // Process Finished InternalEnlistment
            enlistment.FinishEnlistment();
        }
 
        // The enlistment says to abort start the abort sequence.
        internal override void ForceRollback(InternalEnlistment enlistment, Exception? e)
        {
            // Change enlistment state to aborting
            VolatileEnlistmentEnded.EnterState(enlistment);
 
            Debug.Assert(enlistment.Transaction.State != null);
            // Start the transaction aborting
            enlistment.Transaction.State.ChangeStateTransactionAborted(enlistment.Transaction, e);
 
            // Process Finished InternalEnlistment
            enlistment.FinishEnlistment();
        }
 
        internal override void ChangeStatePreparing(InternalEnlistment enlistment)
        {
            // If the transaction promotes during phase 0 then the transition to
            // the promoted phase 0 state for the transaction may cause this
            // notification to be delivered again.  So in this case it should be
            // ignored.
        }
 
        internal override void InternalAborted(InternalEnlistment enlistment)
        {
            VolatileEnlistmentPreparingAborting.EnterState(enlistment);
        }
    }
 
    // SPC state for a volatile enlistment is the point at which there is exactly 1 enlisment
    // and it supports SPC.  The TM will send a single phase commit to the enlistment and wait
    // for the response from the TM.
    internal sealed class VolatileEnlistmentSPC : VolatileEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            bool spcCommitted = false;
            // Set the enlistment state
            enlistment.State = this;
 
            // Send Single Phase Commit to the enlistment
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.SinglePhaseCommit);
            }
 
            Monitor.Exit(enlistment.Transaction);
            try // Don't hold this lock while calling into the application code.
            {
                Debug.Assert(enlistment.SinglePhaseNotification != null);
                enlistment.SinglePhaseNotification.SinglePhaseCommit(enlistment.SinglePhaseEnlistment);
                spcCommitted = true;
            }
            finally
            {
                if (!spcCommitted)
                {
                    //If we have an exception thrown in SPC, we don't know the if the enlistment is committed or not
                    //reply indoubt
                    enlistment.SinglePhaseEnlistment.InDoubt();
                }
                Monitor.Enter(enlistment.Transaction);
            }
        }
 
        internal override void EnlistmentDone(InternalEnlistment enlistment)
        {
            VolatileEnlistmentEnded.EnterState(enlistment);
            Debug.Assert(enlistment.Transaction.State != null);
            enlistment.Transaction.State.ChangeStateTransactionCommitted(enlistment.Transaction);
        }
 
        internal override void Committed(InternalEnlistment enlistment)
        {
            VolatileEnlistmentEnded.EnterState(enlistment);
            Debug.Assert(enlistment.Transaction.State != null);
            enlistment.Transaction.State.ChangeStateTransactionCommitted(enlistment.Transaction);
        }
 
        internal override void Aborted(InternalEnlistment enlistment, Exception? e)
        {
            VolatileEnlistmentEnded.EnterState(enlistment);
 
            Debug.Assert(enlistment.Transaction.State != null);
            enlistment.Transaction.State.ChangeStateTransactionAborted(enlistment.Transaction, e);
        }
 
        internal override void InDoubt(InternalEnlistment enlistment, Exception? e)
        {
            VolatileEnlistmentEnded.EnterState(enlistment);
 
            enlistment.Transaction._innerException ??= e;
 
            Debug.Assert(enlistment.Transaction.State != null);
            enlistment.Transaction.State.InDoubtFromEnlistment(enlistment.Transaction);
        }
    }
 
    // Prepared state for a volatile enlistment is the point at which prepare has been called
    // and the enlistment has responded prepared.  No enlistment operations are valid at this
    // point.  The RM must wait for the TM to take the next action.
    internal sealed class VolatileEnlistmentPrepared : VolatileEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
 
            // Wait for Committed
        }
 
        internal override void InternalAborted(InternalEnlistment enlistment)
        {
            VolatileEnlistmentAborting.EnterState(enlistment);
        }
 
        internal override void InternalCommitted(InternalEnlistment enlistment)
        {
            VolatileEnlistmentCommitting.EnterState(enlistment);
        }
 
        internal override void InternalIndoubt(InternalEnlistment enlistment)
        {
            // Change the enlistment state to InDoubt.
            VolatileEnlistmentInDoubt.EnterState(enlistment);
        }
 
        internal override void ChangeStatePreparing(InternalEnlistment enlistment)
        {
            // This would happen in the second pass of a phase 0 wave.
        }
    }
 
    // Aborting state is when Rollback has been sent to the enlistment.
    internal sealed class VolatileEnlistmentPreparingAborting : VolatileEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
        }
 
        internal override void EnlistmentDone(InternalEnlistment enlistment)
        {
            // Move this enlistment to the ended state
            VolatileEnlistmentEnded.EnterState(enlistment);
        }
 
        internal override void Prepared(InternalEnlistment enlistment)
        {
            // The enlistment has respondend so changes it's state to aborting.
            VolatileEnlistmentAborting.EnterState(enlistment);
 
            // Process Finished InternalEnlistment
            enlistment.FinishEnlistment();
        }
 
        // The enlistment says to abort start the abort sequence.
        internal override void ForceRollback(InternalEnlistment enlistment, Exception? e)
        {
            // Change enlistment state to aborting
            VolatileEnlistmentEnded.EnterState(enlistment);
 
            // Record the exception in the transaction
            if (enlistment.Transaction._innerException == null)
            {
                // Arguably this is the second call to ForceRollback and not the call that
                // aborted the transaction but just in case.
                enlistment.Transaction._innerException = e;
            }
 
            // Process Finished InternalEnlistment
            enlistment.FinishEnlistment();
        }
 
        internal override void InternalAborted(InternalEnlistment enlistment)
        {
            // If this event comes from multiple places just ignore it.  Continue
            // waiting for the enlistment to respond so that we can respond to it.
        }
    }
 
    // Aborting state is when Rollback has been sent to the enlistment.
    internal sealed class VolatileEnlistmentAborting : VolatileEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
 
            Monitor.Exit(enlistment.Transaction);
            try // Don't hold this lock while calling into the application code.
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.Rollback);
                }
 
                Debug.Assert(enlistment.EnlistmentNotification != null);
                enlistment.EnlistmentNotification.Rollback(enlistment.SinglePhaseEnlistment);
            }
            finally
            {
                Monitor.Enter(enlistment.Transaction);
            }
        }
 
        internal override void ChangeStatePreparing(InternalEnlistment enlistment)
        {
            // This enlistment was told to abort before being told to prepare
        }
 
        internal override void EnlistmentDone(InternalEnlistment enlistment)
        {
            // Move this enlistment to the ended state
            VolatileEnlistmentEnded.EnterState(enlistment);
        }
 
        internal override void InternalAborted(InternalEnlistment enlistment)
        {
            // Already working on it.
        }
    }
 
    // Committing state is when Commit has been sent to the enlistment.
    internal sealed class VolatileEnlistmentCommitting : VolatileEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
 
            Monitor.Exit(enlistment.Transaction);
            try // Don't hold this lock while calling into the application code.
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.Commit);
                }
 
                Debug.Assert(enlistment.EnlistmentNotification != null);
                // Forward the notification to the enlistment
                enlistment.EnlistmentNotification.Commit(enlistment.Enlistment);
            }
            finally
            {
                Monitor.Enter(enlistment.Transaction);
            }
        }
 
        internal override void EnlistmentDone(InternalEnlistment enlistment)
        {
            // Move this enlistment to the ended state
            VolatileEnlistmentEnded.EnterState(enlistment);
        }
    }
 
    // InDoubt state is for an enlistment that has sent indoubt but has not been responeded to.
    internal sealed class VolatileEnlistmentInDoubt : VolatileEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
 
            Monitor.Exit(enlistment.Transaction);
            try // Don't hold this lock while calling into the application code.
            {
                TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
                if (etwLog.IsEnabled())
                {
                    etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.InDoubt);
                }
 
                Debug.Assert(enlistment.EnlistmentNotification != null);
                // Forward the notification to the enlistment
                enlistment.EnlistmentNotification.InDoubt(enlistment.PreparingEnlistment);
            }
            finally
            {
                Monitor.Enter(enlistment.Transaction);
            }
        }
 
        internal override void EnlistmentDone(InternalEnlistment enlistment)
        {
            // Move this enlistment to the ended state
            VolatileEnlistmentEnded.EnterState(enlistment);
        }
    }
 
    // Ended state is the state that is entered when the transaction has committed,
    // aborted, or said read only for an enlistment.  At this point there are no valid
    // operations on the enlistment.
    internal class VolatileEnlistmentEnded : VolatileEnlistmentState
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
 
            // Nothing to do.
        }
 
        internal override void ChangeStatePreparing(InternalEnlistment enlistment)
        {
            // This enlistment was told to abort before being told to prepare
        }
 
        internal override void InternalAborted(InternalEnlistment enlistment)
        {
            // Ignore this in case the enlistment gets here before
            // the transaction tells it to do so
        }
 
        internal override void InternalCommitted(InternalEnlistment enlistment)
        {
            // Ignore this in case the enlistment gets here before
            // the transaction tells it to do so
        }
 
        internal override void InternalIndoubt(InternalEnlistment enlistment)
        {
            // Ignore this in case the enlistment gets here before
            // the transaction tells it to do so
        }
 
        internal override void InDoubt(InternalEnlistment enlistment, Exception? e)
        {
            // Ignore this in case the enlistment gets here before
            // the transaction tells it to do so
        }
    }
 
    // At some point either early or late the enlistment responded ReadOnly
    internal sealed class VolatileEnlistmentDone : VolatileEnlistmentEnded
    {
        internal override void EnterState(InternalEnlistment enlistment)
        {
            // Set the enlistment state
            enlistment.State = this;
 
            // Nothing to do.
        }
 
        internal override void ChangeStatePreparing(InternalEnlistment enlistment)
        {
            enlistment.CheckComplete();
        }
    }
}