| File: System\Transactions\InternalTransaction.cs | Web Access |
| Project: src\runtime\src\libraries\System.Transactions.Local\src\System.Transactions.Local.csproj (System.Transactions.Local) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.Diagnostics; using System.Globalization; using System.Threading; using System.Transactions.Oletx; using OletxTransaction = System.Transactions.Oletx.OletxTransaction; namespace System.Transactions { // InternalTransaction // // This class holds the state and all data common to a transaction instance internal sealed class InternalTransaction : IDisposable { // This variable manages the state of the transaction it should be one of the // static elements of TransactionState derived from TransactionState. private 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 readonly TransactionState[] _stateHistory = new TransactionState[MaxStateHist]; internal int _currentStateHist; #endif // Finalized object see class definition for the use of this object internal FinalizedObject? _finalizedObject; internal readonly int _transactionHash; internal int TransactionHash => _transactionHash; internal static int _nextHash; // timeout stores a relative timeout for the transaction. absoluteTimeout stores // the actual time in ticks. private readonly long _absoluteTimeout; internal long AbsoluteTimeout => _absoluteTimeout; // record the current number of ticks active when the transaction is created. private long _creationTime; internal long CreationTime { get { return _creationTime; } set { _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 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 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 OletxTransaction? _promotedTransaction; internal OletxTransaction? PromotedTransaction { get => _promotedTransaction; set { Debug.Assert(_promotedTransaction == null, "A transaction can only be promoted once!"); _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; // Note the number of Transaction objects supported by this object internal int _cloneCount; // The number of enlistments on this transaction. internal int _enlistmentCount; // 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 readonly CommittableTransaction? _committableTransaction; // Store a reference to the outcome source internal readonly Transaction _outcomeSource; // Object for synchronizing access to the entire class( avoiding lock( typeof( ... )) ) private static object? s_classSyncObject; internal Guid DistributedTxId => State!.get_Identifier(this); private static string? s_instanceIdentifier; internal static string InstanceIdentifier => LazyInitializer.EnsureInitialized(ref s_instanceIdentifier, ref s_classSyncObject, () => $"{Guid.NewGuid()}:"); // Double-checked locking pattern requires volatile for read/write synchronization private volatile bool _traceIdentifierInited; // The trace identifier for the internal transaction. private TransactionTraceIdentifier _traceIdentifier; internal TransactionTraceIdentifier TransactionTraceId { get { if (!_traceIdentifierInited) { lock (this) { if (!_traceIdentifierInited) { TransactionTraceIdentifier temp = new TransactionTraceIdentifier( string.Create(CultureInfo.InvariantCulture, $"{InstanceIdentifier}{_transactionHash}"), 0); _traceIdentifier = temp; _traceIdentifierInited = true; } } } return _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; // 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 ((_promoterType != Guid.Empty) && (_promoterType != TransactionInterop.PromoterTypeDtc)) { throw new InvalidOperationException(SR.PromoterTypeInvalid); } _promoterType = TransactionInterop.PromoterTypeDtc; } // Throws a TransactionPromotionException if the promoterType is NOT // Guid.Empty AND NOT TransactionInterop.PromoterTypeDtc. internal void ThrowIfPromoterTypeIsNotMSDTC() { if ((_promoterType != Guid.Empty) && (_promoterType != TransactionInterop.PromoterTypeDtc)) { throw new TransactionPromotionException(SR.Format(SR.PromoterTypeUnrecognized, _promoterType.ToString()), _innerException); } } // Construct an internal transaction internal InternalTransaction(TimeSpan timeout, CommittableTransaction committableTransaction) { // Calculate the absolute timeout for this transaction _absoluteTimeout = TransactionManager.TransactionTable.TimeoutTicks(timeout); // Start the transaction off as active TransactionState.TransactionStateActive.EnterState(this); // Until otherwise noted this transaction uses normal promotion. _promoteState = TransactionState.TransactionStatePromoted; // Keep a reference to the commitable transaction _committableTransaction = committableTransaction; _outcomeSource = committableTransaction; // Initialize the hash _transactionHash = TransactionManager.TransactionTable.Add(this); } // Construct an internal transaction internal InternalTransaction(Transaction outcomeSource, OletxTransaction distributedTx) { _promotedTransaction = distributedTx; _absoluteTimeout = long.MaxValue; // Store the initial creater as it will be the source of outcome events _outcomeSource = outcomeSource; // Initialize the hash _transactionHash = TransactionManager.TransactionTable.Add(this); // Start the transaction off as active TransactionState.TransactionStateNonCommittablePromoted.EnterState(this); // Until otherwise noted this transaction uses normal promotion. _promoteState = TransactionState.TransactionStateNonCommittablePromoted; } // Construct an internal transaction internal InternalTransaction(Transaction outcomeSource, ITransactionPromoter promoter) { _absoluteTimeout = long.MaxValue; // Store the initial creater as it will be the source of outcome events _outcomeSource = outcomeSource; // Initialize the hash _transactionHash = TransactionManager.TransactionTable.Add(this); // Save the transaction promoter. _promoter = promoter; // This transaction starts in a special state. TransactionState.TransactionStateSubordinateActive.EnterState(this); // This transaction promotes through delegation _promoteState = TransactionState.TransactionStateDelegatedSubordinate; } internal static void DistributedTransactionOutcome(InternalTransaction tx, TransactionStatus status) { FinalizedObject? fo = null; lock (tx) { if (null == tx._innerException) { Debug.Assert(tx.PromotedTransaction != null); tx._innerException = tx.PromotedTransaction.InnerException; } Debug.Assert(tx.State! != null); 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.Fail("InternalTransaction.DistributedTransactionOutcome - Unexpected TransactionStatus"); TransactionException.CreateInvalidOperationException(TraceSourceType.TraceSourceLtm, "", null, tx.DistributedTxId ); break; } } fo = tx._finalizedObject; } fo?.Dispose(); } #region Outcome Events // Signal Waiters anyone waiting for transaction outcome. internal void SignalAsyncCompletion() { _asyncResultEvent?.Set(); if (_asyncCallback != null) { Monitor.Exit(this); // Don't hold a lock calling user code. try { Debug.Assert(_committableTransaction != null); _asyncCallback(_committableTransaction); } finally { Monitor.Enter(this); } } } // Fire completion to anyone registered for outcome internal void FireCompletion() { TransactionCompletedEventHandler? eventHandlers = _transactionCompletedDelegate; if (eventHandlers != null) { TransactionEventArgs args = new TransactionEventArgs(); args._transaction = _outcomeSource.InternalClone(); eventHandlers(args._transaction, args); } } #endregion #region IDisposable Members public void 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. internal sealed class FinalizedObject : IDisposable { // Keep the identifier separate. Since it is a struct it won't be finalized out from under // this object. private readonly Guid _identifier; private readonly InternalTransaction _internalTransaction; internal FinalizedObject(InternalTransaction internalTransaction, Guid identifier) { _internalTransaction = internalTransaction; _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[_identifier]; if (null != weakRef) { if (weakRef.Target != null) { weakRef.Target = null; } } promotedTransactionTable.Remove(_identifier); } } public void Dispose() { Dispose(true); } ~FinalizedObject() { Dispose(false); } } }