File: System\Transactions\Oletx\OletxEnlistment.cs
Project: ndp\cdf\src\NetFx20\System.Transactions\System.Transactions.csproj (System.Transactions)
using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Threading;
using System.Transactions;
using System.Transactions.Diagnostics;
 
namespace System.Transactions.Oletx
{
    [Serializable]
    internal class OletxRecoveryInformation
    {
        internal byte[] proxyRecoveryInformation;
 
        internal OletxRecoveryInformation(
            byte[] proxyRecoveryInformation
            )
        {
            this.proxyRecoveryInformation = proxyRecoveryInformation;
        }
    }
    
    class OletxEnlistment : OletxBaseEnlistment,
        IPromotedEnlistment
    {
        internal enum OletxEnlistmentState
        {
            Active,
            Phase0Preparing,
            Preparing,
            SinglePhaseCommitting,
            Prepared,
            Committing,
            Committed,
            Aborting,
            Aborted,
            InDoubt,
            Done
        }
 
        IEnlistmentShim enlistmentShim;
        IPhase0EnlistmentShim phase0Shim;
        bool canDoSinglePhase;
        IEnlistmentNotificationInternal iEnlistmentNotification;
        // The information that comes from/goes to the proxy.
        byte[] proxyPrepareInfoByteArray = null;
 
        OletxEnlistmentState state = OletxEnlistmentState.Active;
 
        bool isSinglePhase = false;
        Guid transactionGuid = Guid.Empty;
 
        // We need to keep track of the handle for the phase 1 notifications
        // so that if the enlistment terminates the conversation due for instance
        // to a force rollback the handle can be cleaned up.
        internal IntPtr phase1Handle = IntPtr.Zero;
 
        // Set to true if we receive an AbortRequest while we still have
        // another notification, like prepare, outstanding.  It indicates that
        // we need to fabricate a rollback to the app after it responds to Prepare.
        bool fabricateRollback = false;
 
        bool tmWentDown = false;
        bool aborting = false;
 
        byte[] prepareInfoByteArray;
 
        internal Guid TransactionIdentifier
        {
            get
            {
                return transactionGuid;
            }
        }
        
        #region Constructor
        
        internal OletxEnlistment(
            bool canDoSinglePhase,
            IEnlistmentNotificationInternal enlistmentNotification,
            Guid transactionGuid,
            EnlistmentOptions enlistmentOptions,
            OletxResourceManager oletxResourceManager,
            OletxTransaction oletxTransaction
            ) : base( oletxResourceManager, oletxTransaction )
        {
            Guid myGuid = Guid.Empty;
 
            // This will get set later by the creator of this object after it
            // has enlisted with the proxy.
            this.enlistmentShim = null;
            this.phase0Shim = null;
 
            this.canDoSinglePhase = canDoSinglePhase;
            this.iEnlistmentNotification = enlistmentNotification;
            this.state = OletxEnlistmentState.Active;
            this.transactionGuid = transactionGuid;
 
            this.proxyPrepareInfoByteArray = null;
 
            if ( DiagnosticTrace.Information )
            {
                EnlistmentTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    this.InternalTraceIdentifier,
                    EnlistmentType.Durable,
                    enlistmentOptions
                    );
            }
 
            // Always do this last incase anything earlier fails.
            AddToEnlistmentTable();
        }
 
 
        internal OletxEnlistment(
            IEnlistmentNotificationInternal enlistmentNotification,
            OletxTransactionStatus xactStatus,
            byte[] prepareInfoByteArray,
            OletxResourceManager oletxResourceManager
            ) : base( oletxResourceManager, null )
        {
            Guid myGuid = Guid.Empty;
 
            // This will get set later by the creator of this object after it
            // has enlisted with the proxy.
            this.enlistmentShim = null;
            this.phase0Shim = null;
 
            this.canDoSinglePhase = false;
            this.iEnlistmentNotification = enlistmentNotification;
            this.state = OletxEnlistmentState.Active;
 
            // Do this before we do any tracing because it will affect the trace identifiers that we generate.
            Debug.Assert( ( null != prepareInfoByteArray ),  
                "OletxEnlistment.ctor - null oletxTransaction without a prepareInfoByteArray" );
 
            int prepareInfoLength = prepareInfoByteArray.Length;
            this.proxyPrepareInfoByteArray = new byte[prepareInfoLength];
            Array.Copy(prepareInfoByteArray, proxyPrepareInfoByteArray, prepareInfoLength);
 
            byte[] txGuidByteArray = new byte[16];
            Array.Copy(proxyPrepareInfoByteArray, txGuidByteArray, 16);
 
            this.transactionGuid = new Guid( txGuidByteArray );
            this.transactionGuidString = this.transactionGuid.ToString();
            
            // If this is being created as part of a Reenlist and we already know the
            // outcome, then tell the application.
            switch (xactStatus)
            {
                case OletxTransactionStatus.OLETX_TRANSACTION_STATUS_ABORTED:
                {
                    this.state = OletxEnlistmentState.Aborting;
                    if ( DiagnosticTrace.Verbose )
                    {
                        EnlistmentNotificationCallTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            this.InternalTraceIdentifier,
                            NotificationCall.Rollback
                            );
                    }
 
                    iEnlistmentNotification.Rollback( this );
                    break;
                }
 
                case OletxTransactionStatus.OLETX_TRANSACTION_STATUS_COMMITTED:
                {
                    this.state = OletxEnlistmentState.Committing;
                    // We are going to send the notification to the RM.  We need to put the
                    // enlistment on the reenlistPendingList.  We lock the reenlistList because
                    // we have decided that is the lock that protects both lists.  The entry will
                    // be taken off the reenlistPendingList when the enlistment has
                    // EnlistmentDone called on it.  The enlistment will call
                    // RemoveFromReenlistPending.
                    lock ( oletxResourceManager.reenlistList )
                    {
                        oletxResourceManager.reenlistPendingList.Add( this );
                    }
 
                    if ( DiagnosticTrace.Verbose )
                    {
                        EnlistmentNotificationCallTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            this.InternalTraceIdentifier,
                            NotificationCall.Commit
                            );
                    }
 
                    iEnlistmentNotification.Commit( this );
                    break;
                }
 
                case OletxTransactionStatus.OLETX_TRANSACTION_STATUS_PREPARED:
                {
                    this.state = OletxEnlistmentState.Prepared;
                    lock ( oletxResourceManager.reenlistList )
                    {
                        oletxResourceManager.reenlistList.Add( this );
                        oletxResourceManager.StartReenlistThread();
                    }
                    break;
                }
 
                default:
                {
                    if ( DiagnosticTrace.Critical )
                    {
                        InternalErrorTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            SR.GetString( SR.OletxEnlistmentUnexpectedTransactionStatus )
                            );
                    }
 
                    throw TransactionException.Create(SR.GetString(SR.TraceSourceOletx), SR.GetString(SR.OletxEnlistmentUnexpectedTransactionStatus), null, this.DistributedTxId);
                }
            }
 
            if ( DiagnosticTrace.Information )
            {
                EnlistmentTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    this.InternalTraceIdentifier,
                    EnlistmentType.Durable,
                    EnlistmentOptions.None
                    );
            }
 
            // Always do this last in case anything prior to this fails.
            AddToEnlistmentTable();
        }
        #endregion
        
 
        internal IEnlistmentNotificationInternal EnlistmentNotification
        {
            get
            {
                return iEnlistmentNotification;
            }
        }
 
 
        internal IEnlistmentShim EnlistmentShim
        {
            get { return this.enlistmentShim; }
            set { this.enlistmentShim = value; }
        }
        
 
        internal IPhase0EnlistmentShim Phase0EnlistmentShim
        {
            get { return this.phase0Shim; }
            set
            { 
                lock ( this )
                {
                    // If this.aborting is set to true, then we must have already received a
                    // Phase0Request.  This could happen if the transaction aborts after the
                    // enlistment is made, but before we are given the shim.
                    if ( ( null != value ) && ( this.aborting || this.tmWentDown ) )
                    {
                        value.Phase0Done( false );
                    }
                    this.phase0Shim = value; 
                }
            }
        }
        
 
        internal OletxEnlistmentState State
        {
            get { return state; }
            set { state = value; }
        }
 
        internal byte[] ProxyPrepareInfoByteArray
        {
            get { return proxyPrepareInfoByteArray; }
        }
        
       
        internal void FinishEnlistment()
        {
            lock ( this )
            {
                // If we don't have a wrappedTransactionEnlistmentAsync, we may
                // need to remove ourselves from the reenlistPendingList in the
                // resource manager.
                if ( null == this.enlistmentShim )
                {
                    oletxResourceManager.RemoveFromReenlistPending( this );
                }
                this.iEnlistmentNotification = null;
 
                RemoveFromEnlistmentTable();
            }
        }
 
        internal void TMDownFromInternalRM( OletxTransactionManager oletxTm )
        {
            lock ( this )
            {
                // If we don't have an oletxTransaction or the passed oletxTm matches that of my oletxTransaction, the TM went down.
                if ( ( null == this.oletxTransaction ) || ( oletxTm == this.oletxTransaction.realOletxTransaction.OletxTransactionManagerInstance ) )
                {
                    this.tmWentDown = true;
                }
            }
 
            return;
        }
 
 
        #region ITransactionResourceAsync methods
        
        // ITranactionResourceAsync.PrepareRequest
        public bool PrepareRequest(
            bool singlePhase,
            byte[] prepareInfo
            )
        {
            IEnlistmentShim localEnlistmentShim = null;
            OletxEnlistmentState localState = OletxEnlistmentState.Active;
            IEnlistmentNotificationInternal localEnlistmentNotification = null;
            OletxRecoveryInformation oletxRecoveryInformation = null;
            bool enlistmentDone;
 
            lock ( this )
            {
                if ( OletxEnlistmentState.Active == state )
                {
                    localState = state = OletxEnlistmentState.Preparing;
                }
                else
                {
                    // We must have done the prepare work in Phase0, so just remember what state we are
                    // in now.
                    localState = state;
                }
 
                localEnlistmentNotification = iEnlistmentNotification;
 
                localEnlistmentShim = this.EnlistmentShim;
 
                this.oletxTransaction.realOletxTransaction.TooLateForEnlistments = true;
            }
 
            // If we went to Preparing state, send the app
            // a prepare request.
            if ( OletxEnlistmentState.Preparing == localState )
            {
                oletxRecoveryInformation = new OletxRecoveryInformation( prepareInfo );
                this.isSinglePhase = singlePhase;
 
                // Store the prepare info we are given.
                Debug.Assert(this.proxyPrepareInfoByteArray == null, "Unexpected value in this.proxyPrepareInfoByteArray");
                long arrayLength = prepareInfo.Length;
                this.proxyPrepareInfoByteArray = new Byte[arrayLength];
                Array.Copy(prepareInfo, this.proxyPrepareInfoByteArray, arrayLength);
 
                if ( this.isSinglePhase && this.canDoSinglePhase )
                {
                    ISinglePhaseNotificationInternal singlePhaseNotification = (ISinglePhaseNotificationInternal) localEnlistmentNotification;
                    state = OletxEnlistmentState.SinglePhaseCommitting;
                    // We don't call DecrementUndecidedEnlistments for Phase1 enlistments.
                    if ( DiagnosticTrace.Verbose )
                    {
                        EnlistmentNotificationCallTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            this.InternalTraceIdentifier,
                            NotificationCall.SinglePhaseCommit
                            );
                    }
 
                    singlePhaseNotification.SinglePhaseCommit( this );
                    enlistmentDone = true;
                }
                else
                {
                    // We need to turn the oletxRecoveryInformation into a byte array.
                    byte[] oletxRecoveryInformationByteArray = TransactionManager.ConvertToByteArray( oletxRecoveryInformation );
                        
                    state = OletxEnlistmentState.Preparing;
                        
                    // 
                    this.prepareInfoByteArray = TransactionManager.GetRecoveryInformation(
                        oletxResourceManager.oletxTransactionManager.CreationNodeName,
                        oletxRecoveryInformationByteArray
                        );
 
                    if ( DiagnosticTrace.Verbose )
                    {
                        EnlistmentNotificationCallTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            this.InternalTraceIdentifier,
                            NotificationCall.Prepare
                            );
                    }
 
                    localEnlistmentNotification.Prepare(
                        this
                        );
                    enlistmentDone = false;
                }
            }
            else if ( OletxEnlistmentState.Prepared == localState )
            {
                // We must have done our prepare work during Phase0 so just vote Yes.
                try
                {
                    localEnlistmentShim.PrepareRequestDone( OletxPrepareVoteType.Prepared );
                    enlistmentDone = false;
                }
                catch ( COMException comException )
                {
                    OletxTransactionManager.ProxyException( comException );
                    throw;
                }
            }
            else if ( OletxEnlistmentState.Done == localState )
            {
                try
                {
                    // This was an early vote.  Respond ReadOnly
                    try
                    {
                        localEnlistmentShim.PrepareRequestDone( OletxPrepareVoteType.ReadOnly );
                        enlistmentDone = true;
                    }
                    finally
                    {
                        FinishEnlistment();
                    }
                }
                catch ( COMException comException )
                {
                    OletxTransactionManager.ProxyException( comException );
                    throw;
                }
            }
            else
            {
                // Any other state means we should vote NO to the proxy.
                try
                {
                    localEnlistmentShim.PrepareRequestDone( OletxPrepareVoteType.Failed );
                }
                catch ( COMException ex )
                {
                    // No point in rethrowing this.  We are not on an app thread and we have already told
                    // the app that the transaction is aborting.  When the app calls EnlistmentDone, we will
                    // do the final release of the ITransactionEnlistmentAsync.
                    if ( DiagnosticTrace.Verbose )
                    {
                        ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            ex );
                    }
                }
                enlistmentDone = true;
            }
 
            return enlistmentDone;
        }
            
 
        public void CommitRequest()
        {
            OletxEnlistmentState localState = OletxEnlistmentState.Active;
            IEnlistmentNotificationInternal localEnlistmentNotification = null;
            IEnlistmentShim localEnlistmentShim = null;
            bool finishEnlistment = false;
 
            lock ( this )
            {
                if ( OletxEnlistmentState.Prepared == state )
                {
                    localState = state = OletxEnlistmentState.Committing;
                    localEnlistmentNotification = iEnlistmentNotification;
                }
                else
                {
                    // We must have received an EnlistmentDone already.
                    localState = state;
                    localEnlistmentShim = this.EnlistmentShim;
                    finishEnlistment = true;
                }
            }
 
            if ( null != localEnlistmentNotification )
            {
                if ( DiagnosticTrace.Verbose )
                {
                    EnlistmentNotificationCallTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                        this.InternalTraceIdentifier,
                        NotificationCall.Commit
                        );
                }
 
                localEnlistmentNotification.Commit( this );
            }
            else if ( null != localEnlistmentShim )
            {
                // We need to respond to the proxy now.
                try
                {
                    localEnlistmentShim.CommitRequestDone();
                }
                catch ( COMException ex )
                {
                    // If the TM went down during our call, there is nothing special we have to do because
                    // the App doesn't expect any more notifications.  We do want to mark the enlistment
                    // to finish, however.
                    if ( ( NativeMethods.XACT_E_CONNECTION_DOWN == ex.ErrorCode ) ||
                        ( NativeMethods.XACT_E_TMNOTAVAILABLE == ex.ErrorCode )
                        )
                    {
                        finishEnlistment = true;
                        if ( DiagnosticTrace.Verbose )
                        {
                            ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                                ex );
                        }
                    }
                    else
                    {
                        throw;
                    }
                }
                finally
                {
                    if ( finishEnlistment )
                    {
                        FinishEnlistment();
                    }
                }
            }
            return;
        }
 
        public void AbortRequest()
        {
            OletxEnlistmentState localState = OletxEnlistmentState.Active;
            IEnlistmentNotificationInternal localEnlistmentNotification = null;
            IEnlistmentShim localEnlistmentShim = null;
            bool finishEnlistment = false;
 
            lock ( this )
            {
                if ( ( OletxEnlistmentState.Active == state ) ||
                     ( OletxEnlistmentState.Prepared == state )
                   )
                {
                    localState = state = OletxEnlistmentState.Aborting;
                    localEnlistmentNotification = iEnlistmentNotification;
                }
                else
                {
                    // We must have received an EnlistmentDone already or we have
                    // a notification outstanding (Phase0 prepare).
                    localState = state;
                    if ( OletxEnlistmentState.Phase0Preparing == state )
                    {
                        this.fabricateRollback = true;
                    }
                    else
                    {
                        finishEnlistment = true;
                    }
 
                    localEnlistmentShim = this.EnlistmentShim;
                }
            }
 
            if ( null != localEnlistmentNotification )
            {
                if ( DiagnosticTrace.Verbose )
                {
                    EnlistmentNotificationCallTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                        this.InternalTraceIdentifier,
                        NotificationCall.Rollback
                        );
                }
 
                localEnlistmentNotification.Rollback( this );
            }
            else if ( null != localEnlistmentShim )
            {
                // We need to respond to the proxy now.
                try
                {
                    localEnlistmentShim.AbortRequestDone();
                }
                catch ( COMException ex )
                {
                    // If the TM went down during our call, there is nothing special we have to do because
                    // the App doesn't expect any more notifications.  We do want to mark the enlistment
                    // to finish, however.
                    if ( ( NativeMethods.XACT_E_CONNECTION_DOWN == ex.ErrorCode ) ||
                        ( NativeMethods.XACT_E_TMNOTAVAILABLE == ex.ErrorCode )
                        )
                    {
                        finishEnlistment = true;
                        if ( DiagnosticTrace.Verbose )
                        {
                            ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                                ex );
                        }
                    }
                    else
                    {
                        throw;
                    }
                }
                finally
                {
                    if ( finishEnlistment )
                    {
                        FinishEnlistment();
                    }
                }
            }
            return;
        }
 
        public void TMDown()
        {
            // We aren't telling our enlistments about TMDown, only
            // resource managers.
            // Put this enlistment on the Reenlist list.  The Reenlist thread will get
            // started when the RMSink gets the TMDown notification.
            lock ( oletxResourceManager.reenlistList )
            {
                lock ( this )
                {
                    // Remember that we got the TMDown in case we get a Phase0Request after so we
                    // can avoid doing a Prepare to the app.
                    this.tmWentDown = true;
 
                    // Only move Prepared and Committing enlistments to the ReenlistList.  All others
                    // do not require a Reenlist to figure out what to do.  We save off Committing
                    // enlistments because the RM has not acknowledged the commit, so we can't
                    // call RecoveryComplete on the proxy until that has happened.  The Reenlist thread
                    // will loop until the reenlist list is empty and it will leave a Committing
                    // enlistment on the list until it is done, but will NOT call Reenlist on the proxy.
                    if ( ( OletxEnlistmentState.Prepared == state ) ||
                         ( OletxEnlistmentState.Committing == state )
                       )
                    {
                        oletxResourceManager.reenlistList.Add( this );
                    }
                }
            }
 
            return;
        }
 
        #endregion
        
        #region ITransactionPhase0NotifyAsync methods
        
        // ITransactionPhase0NotifyAsync
        public void Phase0Request(
            bool abortingHint
            )
        {
            IEnlistmentNotificationInternal localEnlistmentNotification = null;
            OletxEnlistmentState localState = OletxEnlistmentState.Active;
            OletxCommittableTransaction committableTx = null;
            bool commitNotYetCalled = false;
 
            if ( DiagnosticTrace.Verbose )
            {
                MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxEnlistment.Phase0Request"
                    );
            }
 
            committableTx = this.oletxTransaction.realOletxTransaction.committableTransaction;
            if ( null != committableTx )
            {
                // We are dealing with the committable transaction.  If Commit or BeginCommit has NOT been
                // called, then we are dealing with a situation where the TM went down and we are getting
                // a bogus Phase0Request with abortHint = false (COMPlus bug 36760/36758).  This is an attempt
                // to not send the app a Prepare request when we know the transaction is going to abort.
                if (!committableTx.CommitCalled )
                {
                    commitNotYetCalled = true;
                }
            }
 
            lock ( this )
            {
                this.aborting = abortingHint;
 
                // The app may have already called EnlistmentDone.  If this occurs, don't bother sending
                // the notification to the app and we don't need to tell the proxy.
                if ( OletxEnlistmentState.Active == state )
                {
                    // If we got an abort hint or we are the committable transaction and Commit has not yet been called or the TM went down,
                    // we don't want to do any more work on the transaction.  The abort notifications will be sent by the phase 1
                    // enlistment
                    if ( ( this.aborting ) || ( commitNotYetCalled ) || ( this.tmWentDown ) )
                    {
                        // There is a possible ---- where we could get the Phase0Request before we are given the
                        // shim.  In that case, we will vote "no" when we are given the shim.
                        if ( null != this.phase0Shim )
                        {
                            try
                            {
                                this.phase0Shim.Phase0Done( false );
                            }
                            // I am not going to check for XACT_E_PROTOCOL here because that check is a workaround for a bug
                            // that only shows up if abortingHint is false.
                            catch ( COMException ex )
                            {
                                if ( DiagnosticTrace.Verbose )
                                {
                                    ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                                        ex );
                                }
                            }
                        }
                    }
                    else
                    {
                        localState = state = OletxEnlistmentState.Phase0Preparing;
                        localEnlistmentNotification = iEnlistmentNotification;
                    }
                }
            }
 
            // Tell the application to do the work.
            if ( null != localEnlistmentNotification )
            {
                if ( OletxEnlistmentState.Phase0Preparing == localState )
                {
                    byte[] txGuidArray = transactionGuid.ToByteArray();
                    byte[] rmGuidArray = oletxResourceManager.resourceManagerIdentifier.ToByteArray();
                    
                    byte[] temp = new byte[txGuidArray.Length + rmGuidArray.Length];
                    Thread.MemoryBarrier();
                    this.proxyPrepareInfoByteArray = temp;
                    int index = 0;
                    for ( index = 0; index < txGuidArray.Length; index++ )
                    {
                        proxyPrepareInfoByteArray[index] = 
                            txGuidArray[index];
                    }
 
                    for ( index = 0; index < rmGuidArray.Length; index++ )
                    {
                        proxyPrepareInfoByteArray[txGuidArray.Length + index] = 
                            rmGuidArray[index];
                    }
 
                    OletxRecoveryInformation oletxRecoveryInformation = new OletxRecoveryInformation(
                                                                            proxyPrepareInfoByteArray
                                                                            );
                    byte[] oletxRecoveryInformationByteArray = TransactionManager.ConvertToByteArray( oletxRecoveryInformation );
 
                    // 
                    this.prepareInfoByteArray = TransactionManager.GetRecoveryInformation(
                        oletxResourceManager.oletxTransactionManager.CreationNodeName,
                        oletxRecoveryInformationByteArray
                        );
 
                    if ( DiagnosticTrace.Verbose )
                    {
                        EnlistmentNotificationCallTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            this.InternalTraceIdentifier,
                            NotificationCall.Prepare
                            );
                    }
 
                    localEnlistmentNotification.Prepare( this );
                }
                else
                {
                    // We must have had a ---- between EnlistmentDone and the proxy telling
                    // us Phase0Request.  Just return.
                    if ( DiagnosticTrace.Verbose )
                    {
                        MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            "OletxEnlistment.Phase0Request"
                            );
                    }
 
                    return;
                }
 
            }
            if ( DiagnosticTrace.Verbose )
            {
                MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxEnlistment.Phase0Request"
                    );
            }
 
        }
 
        #endregion
        
        public void EnlistmentDone()
        {
            if ( DiagnosticTrace.Verbose )
            {
                MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxEnlistment.EnlistmentDone"
                    );
                EnlistmentCallbackPositiveTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    this.InternalTraceIdentifier,
                    EnlistmentCallback.Done
                    );
            }
 
            IEnlistmentShim localEnlistmentShim = null;
            IPhase0EnlistmentShim localPhase0Shim = null;
            OletxEnlistmentState localState = OletxEnlistmentState.Active;
            bool finishEnlistment;
            bool localFabricateRollback = false;
 
            lock ( this )
            {
                localState = state;
                if ( OletxEnlistmentState.Active == state )
                {
                    // Early vote.  If we are doing Phase0, we need to unenlist.  Otherwise, just
                    // remember.
                    localPhase0Shim = this.Phase0EnlistmentShim;
                    if ( null != localPhase0Shim )
                    {
                        // We are a Phase0 enlistment and we have a vote - decrement the undecided enlistment count.
                        // We only do this for Phase0 because we don't count Phase1 durable enlistments.
                        oletxTransaction.realOletxTransaction.DecrementUndecidedEnlistments();
                    }
                    finishEnlistment = false;
                }
                else if ( OletxEnlistmentState.Preparing == state )
                {
                    // Read only vote.  Tell the proxy and go to the Done state.
                    localEnlistmentShim = this.EnlistmentShim;
                    // We don't decrement the undecided enlistment count for Preparing because we only count
                    // Phase0 enlistments and we are in Phase1 in Preparing state.
                    finishEnlistment = true;
                }
                else if ( OletxEnlistmentState.Phase0Preparing == state )
                {
                    // Read only vote to Phase0.  Tell the proxy okay and go to the Done state.
                    localPhase0Shim = this.Phase0EnlistmentShim;
                    // We are a Phase0 enlistment and we have a vote - decrement the undecided enlistment count.
                    // We only do this for Phase0 because we don't count Phase1 durable enlistments.
                    oletxTransaction.realOletxTransaction.DecrementUndecidedEnlistments();
 
                    // If we would have fabricated a rollback then we have already received an abort request
                    // from proxy and will not receive any more notifications.  Otherwise more notifications
                    // will be coming.
                    if ( this.fabricateRollback )
                    {
                        finishEnlistment = true;
                    }
                    else
                    {
                        finishEnlistment = false;
                    }
                }
                else if ( ( OletxEnlistmentState.Committing == state ) ||
                    ( OletxEnlistmentState.Aborting == state ) ||
                    ( OletxEnlistmentState.SinglePhaseCommitting == state )
                    )
                {
                    localEnlistmentShim = this.EnlistmentShim;
                    finishEnlistment = true;
                    // We don't decrement the undecided enlistment count for SinglePhaseCommitting because we only
                    // do it for Phase0 enlistments.
                }
                else
                {
                    throw TransactionException.CreateEnlistmentStateException(SR.GetString(SR.TraceSourceOletx), null, this.DistributedTxId);
                }
 
                // If this.fabricateRollback is true, it means that we are fabricating this
                // AbortRequest, rather than having the proxy tell us.  So we don't need
                // to respond to the proxy with AbortRequestDone.
                localFabricateRollback = this.fabricateRollback;
 
                state = OletxEnlistmentState.Done;
            }
 
            try
            {
                if ( null != localEnlistmentShim )
                {
                    if ( OletxEnlistmentState.Preparing == localState )
                    {
                        try
                        {
                            localEnlistmentShim.PrepareRequestDone( OletxPrepareVoteType.ReadOnly );
                        }
                        finally
                        {
                            HandleTable.FreeHandle( this.phase1Handle );
                        }
                    }
                    else if ( OletxEnlistmentState.Committing == localState )
                    {
                        localEnlistmentShim.CommitRequestDone();
                    }
                    else if ( OletxEnlistmentState.Aborting == localState )
                    {
                        // If localFabricatRollback is true, it means that we are fabricating this
                        // AbortRequest, rather than having the proxy tell us.  So we don't need
                        // to respond to the proxy with AbortRequestDone.
                        if ( ! localFabricateRollback )
                        {
                            localEnlistmentShim.AbortRequestDone();
                        }
                    }
                    else if ( OletxEnlistmentState.SinglePhaseCommitting == localState )
                    {
                        localEnlistmentShim.PrepareRequestDone( OletxPrepareVoteType.SinglePhase );
                    }
                    else
                    {
                        throw TransactionException.CreateEnlistmentStateException(SR.GetString(SR.TraceSourceOletx), null, this.DistributedTxId);
                    }
                }
                else if ( null != localPhase0Shim )
                {
                    if ( OletxEnlistmentState.Active == localState )
                    {
                        localPhase0Shim.Unenlist();
                    }
                    else if ( OletxEnlistmentState.Phase0Preparing == localState )
                    {
                        localPhase0Shim.Phase0Done( true );
                    }
                    else
                    {
                        throw TransactionException.CreateEnlistmentStateException(SR.GetString(SR.TraceSourceOletx), null, this.DistributedTxId);
                    }
                }
 
            }
            catch ( COMException ex )
            {
                // If we get an error talking to the proxy, there is nothing special we have to do because
                // the App doesn't expect any more notifications.  We do want to mark the enlistment
                // to finish, however.
                finishEnlistment = true;
 
                if ( DiagnosticTrace.Verbose )
                {
                    ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                        ex );
                }
            }
            finally
            {
                if ( finishEnlistment )
                {
                    FinishEnlistment();
                }
            }
            if ( DiagnosticTrace.Verbose )
            {
                MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxEnlistment.EnlistmentDone"
                    );
            }
        }
 
 
        public EnlistmentTraceIdentifier EnlistmentTraceId
        {
            get
            {
                if ( DiagnosticTrace.Verbose )
                {
                    MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                        "OletxEnlistment.get_TraceIdentifier"
                        );
                    MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                        "OletxEnlistment.get_TraceIdentifier"
                        );
                }
 
                return this.InternalTraceIdentifier;
            }
        }
        
        public void Prepared()
        {
            int hrResult = NativeMethods.S_OK;
            IEnlistmentShim localEnlistmentShim = null;
            IPhase0EnlistmentShim localPhase0Shim = null;
            bool localFabricateRollback = false;
 
            if ( DiagnosticTrace.Verbose )
            {
                MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxPreparingEnlistment.Prepared"
                    );
                EnlistmentCallbackPositiveTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    this.InternalTraceIdentifier,
                    EnlistmentCallback.Prepared
                    );
            }
 
            lock ( this )
            {
                if ( OletxEnlistmentState.Preparing == state )
                {
                    localEnlistmentShim = this.EnlistmentShim;
                }
                else if ( OletxEnlistmentState.Phase0Preparing == state )
                {
                    // If the transaction is doomed or we have fabricateRollback is true because the
                    // transaction aborted while the Phase0 Prepare request was outstanding,
                    // release the WrappedTransactionPhase0EnlistmentAsync and remember that
                    // we have a pending rollback.
                    localPhase0Shim = this.Phase0EnlistmentShim;
                    if ( ( oletxTransaction.realOletxTransaction.Doomed ) ||
                         ( this.fabricateRollback )
                       )
                    {
                        // Set fabricateRollback in case we got here because the transaction is doomed.
                        this.fabricateRollback = true;
                        localFabricateRollback = this.fabricateRollback;
                    }
                }
                else
                {
                    throw TransactionException.CreateEnlistmentStateException(SR.GetString(SR.TraceSourceOletx), null, this.DistributedTxId);
                }
 
                state = OletxEnlistmentState.Prepared;
 
            }
 
            try
            {
                if ( null != localEnlistmentShim )
                {
                    localEnlistmentShim.PrepareRequestDone( OletxPrepareVoteType.Prepared );
                }
                else if ( null != localPhase0Shim )
                {
                    // We have a vote - decrement the undecided enlistment count.  We do
                    // this after checking Doomed because ForceRollback will decrement also.
                    // We also do this only for Phase0 enlistments.
                    oletxTransaction.realOletxTransaction.DecrementUndecidedEnlistments();
 
                    localPhase0Shim.Phase0Done( !localFabricateRollback );
                }
 
                else
                    // The TM must have gone down, thus causing our interface pointer to be
                    // invalidated.  So we need to drive abort of the enlistment as if we
                    // received an AbortRequest.
                {
                    localFabricateRollback = true;
                }
                
                if ( localFabricateRollback )
                {
                    AbortRequest();
                }
            }
            catch ( COMException ex )
            {
                // If the TM went down during our call, the TMDown notification to the enlistment
                // and RM will put this enlistment on the ReenlistList, if appropriate.  The outcome
                // will be obtained by the ReenlistThread.
                if ( ( NativeMethods.XACT_E_CONNECTION_DOWN == ex.ErrorCode ) ||
                    ( NativeMethods.XACT_E_TMNOTAVAILABLE == ex.ErrorCode )
                    )
                {
                    if ( DiagnosticTrace.Verbose )
                    {
                        ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            ex );
                    }
                }
                // In the case of Phase0, there is a bug in the proxy that causes an XACT_E_PROTOCOL
                // error if the TM goes down while the enlistment is still active.  The Phase0Request is
                // sent out with abortHint false, but the state of the proxy object is not changed, causing
                // Phase0Done request to fail with XACT_E_PROTOCOL.
                // For Prepared, we want to make sure the proxy aborts the transaction.  We don't need
                // to drive the abort to the application here because the Phase1 enlistment will do that.
                // In other words, treat this as if the proxy said Phase0Request( abortingHint = true ).
                else if ( NativeMethods.XACT_E_PROTOCOL == ex.ErrorCode )
                {
                    this.Phase0EnlistmentShim = null;
                    if ( DiagnosticTrace.Verbose )
                    {
                        ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            ex );
                    }
                }
                else
                {
                    throw;
                }
            }
            if ( DiagnosticTrace.Verbose )
            {
                MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxPreparingEnlistment.Prepared"
                    );
            }
        }
 
 
        public void ForceRollback()
        {
            ForceRollback( null );
        }
 
        public void ForceRollback( Exception e )
        {
            IEnlistmentShim localEnlistmentShim = null;
            IPhase0EnlistmentShim localPhase0Shim = null;
 
            if ( DiagnosticTrace.Verbose )
            {
                MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxPreparingEnlistment.ForceRollback"
                    );
            }
 
            if ( DiagnosticTrace.Warning )
            {
                EnlistmentCallbackNegativeTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    this.InternalTraceIdentifier,
                    EnlistmentCallback.ForceRollback
                    );
            }
 
            lock ( this )
            {
                if ( OletxEnlistmentState.Preparing == state )
                {
                    localEnlistmentShim = this.EnlistmentShim;
                }
                else if ( OletxEnlistmentState.Phase0Preparing == state )
                {
                    localPhase0Shim = this.Phase0EnlistmentShim;
                    if ( null != localPhase0Shim )
                    {
 
                        // We have a vote - decrement the undecided enlistment count.  We only do this
                        // if we are Phase0 enlistment.
                        oletxTransaction.realOletxTransaction.DecrementUndecidedEnlistments();
                    }
 
                }
                else
                {
                    throw TransactionException.CreateEnlistmentStateException(SR.GetString(SR.TraceSourceOletx), null, this.DistributedTxId);
                }
 
                state = OletxEnlistmentState.Aborted;
            }
 
            Interlocked.CompareExchange<Exception>( ref this.oletxTransaction.realOletxTransaction.innerException, e, null );
 
            try
            {
                if ( null != localEnlistmentShim )
                {
                    try
                    {
                        localEnlistmentShim.PrepareRequestDone( OletxPrepareVoteType.Failed );
                    }
                    finally
                    {
                        HandleTable.FreeHandle( this.phase1Handle );
                    }
                }
 
                if ( null != localPhase0Shim )
                {
                    localPhase0Shim.Phase0Done( false );
                }
//                else
                    // The TM must have gone down, thus causing our interface pointer to be
                    // invalidated.  The App doesn't expect any more notifications, so we can
                    // just finish the enlistment.
            }
            catch ( COMException ex )
            {
                // If the TM went down during our call, there is nothing special we have to do because
                // the App doesn't expect any more notifications.
                if ( ( NativeMethods.XACT_E_CONNECTION_DOWN == ex.ErrorCode ) ||
                    ( NativeMethods.XACT_E_TMNOTAVAILABLE == ex.ErrorCode )
                    )
                {
                    if ( DiagnosticTrace.Verbose )
                    {
                        ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            ex );
                    }
                }
                else
                {
                    throw;
                }
            }
            finally
            {
                FinishEnlistment();
            }
            if ( DiagnosticTrace.Verbose )
            {
                MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxPreparingEnlistment.ForceRollback"
                    );
            }
        }
        
        public void Committed()
        {
            IEnlistmentShim localEnlistmentShim = null;
            if ( DiagnosticTrace.Verbose )
            {
                MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxSinglePhaseEnlistment.Committed"
                    );
                EnlistmentCallbackPositiveTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    this.InternalTraceIdentifier,
                    EnlistmentCallback.Committed
                    );
            }
 
            lock ( this )
            {
                if (!isSinglePhase || (OletxEnlistmentState.SinglePhaseCommitting != state))
                {
                    throw TransactionException.CreateEnlistmentStateException(SR.GetString(SR.TraceSourceOletx), null, this.DistributedTxId);
                }
                state = OletxEnlistmentState.Committed;
                localEnlistmentShim = this.EnlistmentShim;
            }
 
            try
            {
                // This may be the result of a reenlist, which means we don't have a
                // reference to the proxy.
                if ( null != localEnlistmentShim )
                {
                    localEnlistmentShim.PrepareRequestDone( OletxPrepareVoteType.SinglePhase );
                }
            }
            catch ( COMException ex )
            {
                // If the TM went down during our call, there is nothing special we have to do because
                // the App doesn't expect any more notifications.
                if ( ( NativeMethods.XACT_E_CONNECTION_DOWN == ex.ErrorCode ) ||
                    ( NativeMethods.XACT_E_TMNOTAVAILABLE == ex.ErrorCode )
                    )
                {
                    if ( DiagnosticTrace.Verbose )
                    {
                        ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            ex );
                    }
                }
                else
                {
                    throw;
                }
            }
            finally
            {
                FinishEnlistment();
            }
            if ( DiagnosticTrace.Verbose )
            {
                MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxSinglePhaseEnlistment.Committed"
                    );
            }
        }
 
 
        public void Aborted()
        {
            Aborted( null );
        }
 
    
        public void Aborted(Exception e)
        {
            IEnlistmentShim localEnlistmentShim = null;
            if ( DiagnosticTrace.Verbose )
            {
                MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxSinglePhaseEnlistment.Aborted"
                    );
            }
 
            if ( DiagnosticTrace.Warning )
            {
                EnlistmentCallbackNegativeTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    this.InternalTraceIdentifier,
                    EnlistmentCallback.Aborted
                    );
            }
 
            lock ( this )
            {
                if (!isSinglePhase || (OletxEnlistmentState.SinglePhaseCommitting != state))
                {
                    throw TransactionException.CreateEnlistmentStateException(SR.GetString(SR.TraceSourceOletx), null, this.DistributedTxId);
                }
                state = OletxEnlistmentState.Aborted;
                
                localEnlistmentShim = this.EnlistmentShim;
            }
 
            Interlocked.CompareExchange<Exception>( ref this.oletxTransaction.realOletxTransaction.innerException, e, null );
 
            try
            {
                if ( null != localEnlistmentShim )
                {
                    localEnlistmentShim.PrepareRequestDone( OletxPrepareVoteType.Failed );
                }
            }
            catch ( COMException ex )
            {
                // If the TM went down during our call, there is nothing special we have to do because
                // the App doesn't expect any more notifications.
                if ( ( NativeMethods.XACT_E_CONNECTION_DOWN == ex.ErrorCode ) ||
                    ( NativeMethods.XACT_E_TMNOTAVAILABLE == ex.ErrorCode )
                    )
                {
                    if ( DiagnosticTrace.Verbose )
                    {
                        ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            ex );
                    }
                }
                else
                {
                    throw;
                }
            }
            finally
            {
                FinishEnlistment();
            }
            if ( DiagnosticTrace.Verbose )
            {
                MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxSinglePhaseEnlistment.Aborted"
                    );
            }
        }
 
 
        public void InDoubt()
        {
            InDoubt( null );
        }
 
 
        public void InDoubt(Exception e)
        {
            IEnlistmentShim localEnlistmentShim = null;
            if ( DiagnosticTrace.Verbose )
            {
                MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxSinglePhaseEnlistment.InDoubt"
                    );
            }
 
            if ( DiagnosticTrace.Warning )
            {
                EnlistmentCallbackNegativeTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    this.InternalTraceIdentifier,
                    EnlistmentCallback.InDoubt
                    );
            }
 
            lock ( this )
            {
                if (!isSinglePhase || (OletxEnlistmentState.SinglePhaseCommitting != state))
                {
                    throw TransactionException.CreateEnlistmentStateException(SR.GetString(SR.TraceSourceOletx), null, this.DistributedTxId);
                }
                state = OletxEnlistmentState.InDoubt;
                localEnlistmentShim = this.EnlistmentShim;
            }
 
            lock ( this.oletxTransaction.realOletxTransaction )
            {
                if ( this.oletxTransaction.realOletxTransaction.innerException == null )
                {
                    this.oletxTransaction.realOletxTransaction.innerException = e;
                }
            }
 
            try
            {
                if ( null != localEnlistmentShim )
                {
                    localEnlistmentShim.PrepareRequestDone( OletxPrepareVoteType.InDoubt );
                }
            }
            catch ( COMException ex )
            {
                // If the TM went down during our call, there is nothing special we have to do because
                // the App doesn't expect any more notifications.
                if ( ( NativeMethods.XACT_E_CONNECTION_DOWN == ex.ErrorCode ) ||
                    ( NativeMethods.XACT_E_TMNOTAVAILABLE == ex.ErrorCode )
                    )
                {
                    if ( DiagnosticTrace.Verbose )
                    {
                        ExceptionConsumedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                            ex );
                    }
                }
                else
                {
                    throw;
                }
            }
            finally
            {
                FinishEnlistment();
            }
            if ( DiagnosticTrace.Verbose )
            {
                MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceOletx ),
                    "OletxSinglePhaseEnlistment.InDoubt"
                    );
            }
        }
 
        public byte[] GetRecoveryInformation()
        {
            if ( this.prepareInfoByteArray == null )
            {
                Debug.Assert( false, string.Format( null, "this.prepareInfoByteArray == null in RecoveryInformation()" ));
                throw TransactionException.CreateEnlistmentStateException(SR.GetString(SR.TraceSourceOletx), null, this.DistributedTxId);
            }
            return this.prepareInfoByteArray;
        }
 
 
        public InternalEnlistment InternalEnlistment
        {
            get
            {
                return this.internalEnlistment;
            }
            
            set
            {
                this.internalEnlistment = value;
            }
        }
    }
}