File: System\Transactions\InternalTransaction.cs
Project: ndp\cdf\src\NetFx20\System.Transactions\System.Transactions.csproj (System.Transactions)
//-----------------------------------------------------------------------------
// <copyright file="InternalTransaction.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------
 
namespace System.Transactions
{
    using System;
    using System.Collections;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.Serialization;
    using System.Threading;
    using System.Transactions.Diagnostics;
 
 
    // InternalTransaction 
    //
    // This class holds the state and all data common to a transaction instance
    class InternalTransaction : IDisposable
    {
        // This variable manages the state of the transaction it should be one of the 
        // static elements of TransactionState derived from TransactionState.
        protected TransactionState transactionState;
        internal TransactionState State
        {
            get
            {
                return transactionState;
            }
            set
            {
                transactionState = value;
            }
        }
 
        // This variable holds the state that the transaction will promote to.  By
        // default it uses the straight forward TransactionStatePromoted.  If the
        // transaction has a promotable single phase enlistment however it must use
        // a different state so that it is promoted correctly.
        internal TransactionState promoteState;
 
        // The PromoterType for the transaction.
        // This is set when a PSPE enlistment is created via Transaction.EnlistPromotableSinglePhase.
        // It is also set when a transaction promotes without a PSPE enlistment.
        internal Guid promoterType = Guid.Empty;
 
        // The promoted token for the transaction.
        // This is set when the transaction is promoted. For an MSDTC transaction, it is the 
        // same as the DTC propagation token.
        internal byte[] promotedToken;
 
        // This is only used if the promoter type is different than TransactionInterop.PromoterTypeDtc.
        // The promoter is supposed to tell us what the distributed transaction id after promoting it.
        // We store the value here.
        internal Guid distributedTransactionIdentifierNonMSDTC = Guid.Empty;
 
#if DEBUG
        // Keep a history of th transaction states
        internal const int MaxStateHist = 20;
        internal TransactionState[] stateHistory = new TransactionState[MaxStateHist];
        internal int currentStateHist;
#endif
 
        // Finalized object see class definition for the use of this object
        internal FinalizedObject finalizedObject;
 
        internal int transactionHash;
        internal int TransactionHash
        {
            get
            {
                return this.transactionHash;
            }
        }
 
 
        internal static int nextHash;
 
        // timeout stores a relative timeout for the transaction.  absoluteTimeout stores
        // the actual time in ticks.
        private long absoluteTimeout;
        internal long AbsoluteTimeout
        {
            get
            {
                return this.absoluteTimeout;
            }
        }
 
        // record the current number of ticks active when the transaction is created.
        private Int64 creationTime;
        internal Int64 CreationTime
        {
            get
            {
                return this.creationTime;
            }
            set
            {
                this.creationTime = value;
            }
        }
 
        // The goal for the LTM is to only allocate as few heap objects as possible for a given 
        // transaction and all of its enlistments.  To accomplish this, enlistment objects are 
        // held in system arrays.  The transaction contains one enlistment for the single durable 
        // enlistment it can handle and a small array of volatile enlistments.  If the number of 
        // enlistments for a given transaction exceeds the capacity of the current array a new 
        // larger array will be created and the contents of the old array will be copied into it.  
        // Heuristic data based on TransactionType can be created to avoid this sort of copy 
        // operation repeatedly for a given type of transaction.  So if a transaction of a specific 
        // type continually causes the array size to be increased the LTM could start 
        // allocating a larger array initially for transactions of that type. 
        internal InternalEnlistment durableEnlistment;
        internal VolatileEnlistmentSet phase0Volatiles;
        internal VolatileEnlistmentSet phase1Volatiles;
 
        // This member stores the number of phase 0 volatiles for the last wave
        internal int phase0VolatileWaveCount;
 
        // These members are used for promoted waves of dependent blocking clones.  The Ltm
        // does not register individually for each blocking clone created in phase 0.  Instead
        // it multiplexes a single phase 0 blocking clone only created after phase 0 has started.
        internal Oletx.OletxDependentTransaction phase0WaveDependentClone;
        internal int phase0WaveDependentCloneCount;
 
        // These members are used for keeping track of aborting dependent clones if we promote
        // BEFORE we get an aborting dependent clone or a Ph1 volatile enlistment.  If we
        // promote before we get either of these, then we never create a Ph1 volatile enlistment
        // on the distributed TM.  If we promote AFTER an aborting dependent clone or Ph1 volatile
        // enlistment is created, then we create a Ph1 volatile enlistment on the distributed TM
        // as part of promotion, so these won't be used.  In that case, the Ph1 volatile enlistment
        // on the distributed TM takes care of checking to make sure all the aborting dependent
        // clones have completed as part of its Prepare processing.  These are used in conjunction with
        // phase1volatiles.dependentclones.
        internal Oletx.OletxDependentTransaction abortingDependentClone;
        internal int abortingDependentCloneCount;
 
        // When the size of the volatile enlistment array grows increase it by this amount.
        internal const int volatileArrayIncrement = 8;
 
        // Data maintained for TransactionTable participation
        internal Bucket tableBucket;
        internal int bucketIndex;
 
        // Delegate to fire on transaction completion
        internal TransactionCompletedEventHandler transactionCompletedDelegate;
 
        // If this transaction get's promoted keep a reference to the promoted transaction
        private Oletx.OletxTransaction promotedTransaction;
        internal Oletx.OletxTransaction PromotedTransaction
        {
            get
            {
                return this.promotedTransaction;
            }
            set
            {
                Debug.Assert( this.promotedTransaction == null, "A transaction can only be promoted once!" );
                this.promotedTransaction = value;
            }
        }
 
 
        // If there was an exception that happened during promotion save that exception so that it
        // can be used as an inner exception to the transaciton aborted exception.
        internal Exception innerException = null;
 
        // Note the number of Transaction objects supported by this object
        internal int cloneCount;
 
        // The number of enlistments on this transaction.
        internal int enlistmentCount = 0;
 
        // Double-checked locking pattern requires volatile for read/write synchronization
        // Manual Reset event for IAsyncResult support
        internal volatile ManualResetEvent asyncResultEvent;
 
        // Store the callback and state for the caller of BeginCommit
        internal bool asyncCommit;
        internal AsyncCallback asyncCallback;
        internal object asyncState;
 
        // Flag to indicate if we need to be pulsed for tx completion
        internal bool needPulse;
 
        // Store the transaction information object
        internal TransactionInformation transactionInformation;
 
        // Store a reference to the owning Committable Transaction
        internal CommittableTransaction committableTransaction;
 
        // Store a reference to the outcome source
        internal Transaction outcomeSource;
 
        // Object for synchronizing access to the entire class( avoiding lock( typeof( ... )) )
        private static object classSyncObject;
        internal static object ClassSyncObject
        {
            get
            {
                if ( classSyncObject == null )
                {
                    object o = new object();
                    Interlocked.CompareExchange( ref classSyncObject, o, null );
                }
                return classSyncObject;
            }
        }
 
        internal Guid DistributedTxId
        {
            get
            {
                return this.State.get_Identifier(this);
            }
        }
 
 
        // Double-checked locking pattern requires volatile for read/write synchronization
        static volatile string instanceIdentifier;
        static internal string InstanceIdentifier
        {
            get
            {
                if ( instanceIdentifier == null )
                {
                    lock ( ClassSyncObject )
                    {
                        if ( instanceIdentifier == null )
                        {
                            string temp = Guid.NewGuid().ToString() + ":";
                            instanceIdentifier = temp;
                        }
                    }
                }
                return instanceIdentifier;
            }
        }
 
        // Double-checked locking pattern requires volatile for read/write synchronization
        private volatile bool traceIdentifierInited = false;
 
        // The trace identifier for the internal transaction.
        private TransactionTraceIdentifier traceIdentifier;
        internal TransactionTraceIdentifier TransactionTraceId
        {
            get
            {
                if (!traceIdentifierInited)
                {
                    lock ( this )
                    {
                        if (!traceIdentifierInited)
                        {
                            TransactionTraceIdentifier temp = new TransactionTraceIdentifier( 
                                InstanceIdentifier + Convert.ToString( this.transactionHash, CultureInfo.InvariantCulture ),
                                0 );
                            this.traceIdentifier = temp;
                            traceIdentifierInited = true;
                        }
                    }
                }
                return this.traceIdentifier;
            }
        }
 
        internal ITransactionPromoter promoter;
 
        // This member is used to allow a PSPE enlistment to call Transaction.PSPEPromoteAndConvertToEnlistDurable when it is
        // asked to promote a transaction. The value is set to true in TransactionStatePSPEOperation.PSPEPromote before the
        // Promote call is made and set back to false after the call returns (or an exception is thrown). The value is
        // checked for true in TransactionStatePSPEOperation.PSPEPromoteAndConvertToEnlistDurable to make sure the transaction
        // is in the process of promoting via a PSPE enlistment.
        internal bool attemptingPSPEPromote = false;
 
        // This is called from TransactionStatePromoted.EnterState. We assume we are promoting to MSDTC.
        internal void SetPromoterTypeToMSDTC()
        {
            // The promoter type should either not yet be set or should already be TransactionInterop.PromoterTypeDtc in this case.
            if ((this.promoterType != Guid.Empty) && (this.promoterType != TransactionInterop.PromoterTypeDtc))
            {
                throw new InvalidOperationException(SR.GetString(SR.PromoterTypeInvalid));
            }
            this.promoterType = TransactionInterop.PromoterTypeDtc;
        }
 
        // Throws a TransactionPromotionException if the promoterType is NOT
        // Guid.Empty AND NOT TransactionInterop.PromoterTypeDtc.
        internal void ThrowIfPromoterTypeIsNotMSDTC()
        {
            if ((this.promoterType != Guid.Empty) && (this.promoterType != TransactionInterop.PromoterTypeDtc))
            {
                throw new TransactionPromotionException(string.Format(CultureInfo.CurrentCulture,
                    SR.GetString(SR.PromoterTypeUnrecognized), this.promoterType.ToString()),
                    this.innerException);
            }
        }
        
        static InternalTransaction()
        {
            try
            {
                // Emit a telemetry event to indicate that System.Transactions is being used on the first time 
                // InternalTransaction is used.
                using (TelemetryEventSource eventSource = new TelemetryEventSource())
                {
                    eventSource.InternalTransaction();
                }
            }
            catch
            {
            }
        }
 
        // Construct an internal transaction
        internal InternalTransaction( TimeSpan timeout, CommittableTransaction committableTransaction )
        {
            if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
            
            // Calculate the absolute timeout for this transaction
            this.absoluteTimeout = TransactionManager.TransactionTable.TimeoutTicks( timeout );
 
            // Start the transaction off as active
            TransactionState._TransactionStateActive.EnterState( this );
            
            // Until otherwise noted this transaction uses normal promotion.
            this.promoteState = TransactionState._TransactionStatePromoted;
 
            // Keep a reference to the commitable transaction
            this.committableTransaction = committableTransaction;
            this.outcomeSource = committableTransaction;
 
            // Initialize the hash
            this.transactionHash = TransactionManager.TransactionTable.Add( this );
        }
 
 
        // Construct an internal transaction
        internal InternalTransaction( Transaction outcomeSource, Oletx.OletxTransaction distributedTx )
        {
            if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
 
            this.promotedTransaction = distributedTx;
 
            this.absoluteTimeout = long.MaxValue;
 
            // Store the initial creater as it will be the source of outcome events
            this.outcomeSource = outcomeSource;
 
            // Initialize the hash
            this.transactionHash = TransactionManager.TransactionTable.Add( this );
 
            // Start the transaction off as active
            TransactionState._TransactionStateNonCommittablePromoted.EnterState( this );
            
            // Until otherwise noted this transaction uses normal promotion.
            this.promoteState = TransactionState._TransactionStateNonCommittablePromoted;
        }
 
 
        // Construct an internal transaction
        internal InternalTransaction( Transaction outcomeSource, ITransactionPromoter promoter )
        {
            if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
 
            this.absoluteTimeout = long.MaxValue;
 
            // Store the initial creater as it will be the source of outcome events
            this.outcomeSource = outcomeSource;
 
            // Initialize the hash
            this.transactionHash = TransactionManager.TransactionTable.Add( this );
 
            // Save the transaction promoter.
            this.promoter = promoter;
 
            // This transaction starts in a special state.
            TransactionState._TransactionStateSubordinateActive.EnterState( this );
 
            // This transaction promotes through delegation
            this.promoteState = TransactionState._TransactionStateDelegatedSubordinate;
         }
 
 
        internal static void DistributedTransactionOutcome( InternalTransaction tx, TransactionStatus status )
        {
            FinalizedObject fo = null;
            
            lock ( tx )
            {
                if ( null == tx.innerException )
                {
                    tx.innerException = tx.PromotedTransaction.InnerException;
                }
 
                switch (status)
                {
                    case TransactionStatus.Committed:
                    {
                        tx.State.ChangeStatePromotedCommitted( tx );
                        break;
                    }
 
                    case TransactionStatus.Aborted:
                    {
                        tx.State.ChangeStatePromotedAborted( tx );
                        break;
                    }
 
                    case TransactionStatus.InDoubt:
                    {
                        tx.State.InDoubtFromDtc( tx );
                        break;
                    }
 
                    default:
                    {
                        Debug.Assert( false, "InternalTransaction.DistributedTransactionOutcome - Unexpected TransactionStatus" );
                        TransactionException.CreateInvalidOperationException( SR.GetString( SR.TraceSourceLtm ), 
                            "",
                            null,
                            tx.DistributedTxId
                            );
                        break;
                    }
 
                }
 
                fo = tx.finalizedObject;
            }
 
            if ( null != fo )
            {
                fo.Dispose();
            }
        }
 
        #region Outcome Events
 
        // Signal Waiters anyone waiting for transaction outcome.
        internal void SignalAsyncCompletion()
        {
            if ( this.asyncResultEvent != null )
            {
                this.asyncResultEvent.Set();
            }
 
            if ( this.asyncCallback != null )
            {
                System.Threading.Monitor.Exit( this ); // Don't hold a lock calling user code.
                try
                {
                    this.asyncCallback( this.committableTransaction );
                }
                finally
                {
#pragma warning disable 0618
                    //@
                    System.Threading.Monitor.Enter(this);
#pragma warning restore 0618
                }
            }
        }
 
 
        // Fire completion to anyone registered for outcome
        internal void FireCompletion( )
        {
            TransactionCompletedEventHandler eventHandlers = this.transactionCompletedDelegate;
 
            if ( eventHandlers != null )
            {
                TransactionEventArgs args = new TransactionEventArgs();
                args.transaction = this.outcomeSource.InternalClone();
 
                eventHandlers( args.transaction, args );
            }
        }
        
 
        #endregion
 
 
        #region IDisposable Members
 
        // FXCop wants us to dispose nextLink, which is another InternalTransaction, and thus disposable.  But we don't
        // want to do that here.  That link is for the list of all InternalTransactions.
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed")]
        public void Dispose()
        {
            if ( this.promotedTransaction != null )
            {
                // If there is a promoted transaction dispose it.
                this.promotedTransaction.Dispose();
            }
        }
 
        #endregion
    }
 
 
 
    // Finalized Object
    //
    // This object is created if the InternalTransaction needs some kind of finalization.  An
    // InternalTransaction will only need finalization if it is promoted so having a finalizer
    // would only hurt performance for the unpromoted case.  When the Ltm does promote it creates this
    // object which is finalized and will handle the necessary cleanup.
    sealed class FinalizedObject : IDisposable
    {
        // Keep the identifier separate.  Since it is a struct it wont be finalized out from under
        // this object.
        Guid identifier;
        
        InternalTransaction internalTransaction;
        
        internal FinalizedObject( InternalTransaction internalTransaction, Guid identifier )
        {
            this.internalTransaction = internalTransaction;
            this.identifier = identifier;
        }
 
 
        private void Dispose( bool disposing )
        {
            if ( disposing )
            {
                GC.SuppressFinalize(this);
            }
 
            // We need to remove the entry for the transaction from the static
            // LightweightTransactionManager.PromotedTransactionTable.
            Hashtable promotedTransactionTable = TransactionManager.PromotedTransactionTable;
            lock ( promotedTransactionTable )
            {
                WeakReference weakRef = (WeakReference)promotedTransactionTable[this.identifier];
                if ( null != weakRef )
                {
                    if ( weakRef.Target != null )
                    {
                        weakRef.Target = null;
                    }
                }
 
                promotedTransactionTable.Remove( this.identifier );
            }
        }
        
 
        public void Dispose()
        {
            Dispose(true);
        }
 
        
        ~FinalizedObject()
        {
            Dispose(false);
        }
    }
}