|
//-----------------------------------------------------------------------------
// <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);
}
}
}
|