File: System\Transactions\CommittableTransaction.cs
Web Access
Project: src\src\libraries\System.Transactions.Local\src\System.Transactions.Local.csproj (System.Transactions.Local)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Runtime.Versioning;
using System.Threading;
 
#pragma warning disable CS1591
 
namespace System.Transactions
{
    [UnsupportedOSPlatform("browser")]
    public sealed class CommittableTransaction : Transaction, IAsyncResult
    {
        // Create a transaction with defaults
        public CommittableTransaction() : this(TransactionManager.DefaultIsolationLevel, TransactionManager.DefaultTimeout)
        {
        }
 
        // Create a transaction with the given info
        public CommittableTransaction(TimeSpan timeout) : this(TransactionManager.DefaultIsolationLevel, timeout)
        {
        }
 
        // Create a transaction with the given options
        public CommittableTransaction(TransactionOptions options) : this(options.IsolationLevel, options.Timeout)
        {
        }
 
        internal CommittableTransaction(IsolationLevel isoLevel, TimeSpan timeout) : base(isoLevel, (InternalTransaction?)null)
        {
            // object to use for synchronization rather than locking on a public object
            _internalTransaction = new InternalTransaction(timeout, this);
 
            // Because we passed null for the internal transaction to the base class, we need to
            // fill in the traceIdentifier field here.
            _internalTransaction._cloneCount = 1;
            _cloneId = 1;
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.TransactionCreated(TraceSourceType.TraceSourceLtm, TransactionTraceId, "CommittableTransaction");
            }
        }
 
        public IAsyncResult BeginCommit(AsyncCallback? asyncCallback, object? asyncState)
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
                etwLog.TransactionCommit(TraceSourceType.TraceSourceLtm, TransactionTraceId, "CommittableTransaction");
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            lock (_internalTransaction)
            {
                if (_complete)
                {
                    throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
                }
 
                Debug.Assert(_internalTransaction.State != null);
                // this.complete will get set to true when the transaction enters a state that is
                // beyond Phase0.
                _internalTransaction.State.BeginCommit(_internalTransaction, true, asyncCallback, asyncState);
            }
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
            }
 
            return this;
        }
 
        // Forward the commit to the state machine to take the appropriate action.
        //
        public void Commit()
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
                etwLog.TransactionCommit(TraceSourceType.TraceSourceLtm, TransactionTraceId, "CommittableTransaction");
            }
 
            ObjectDisposedException.ThrowIf(Disposed, this);
 
            lock (_internalTransaction)
            {
                if (_complete)
                {
                    throw TransactionException.CreateTransactionCompletedException(DistributedTxId);
                }
 
                Debug.Assert(_internalTransaction.State != null);
                _internalTransaction.State.BeginCommit(_internalTransaction, false, null, null);
 
                // now that commit has started wait for the monitor on the transaction to know
                // if the transaction is done.
                do
                {
                    if (_internalTransaction.State.IsCompleted(_internalTransaction))
                    {
                        break;
                    }
                } while (Monitor.Wait(_internalTransaction));
 
                _internalTransaction.State.EndCommit(_internalTransaction);
            }
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
            }
        }
 
        internal override void InternalDispose()
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
            }
 
            if (Interlocked.Exchange(ref _disposed, Transaction._disposedTrueValue) == Transaction._disposedTrueValue)
            {
                return;
            }
 
            Debug.Assert(_internalTransaction.State != null);
            if (_internalTransaction.State.get_Status(_internalTransaction) == TransactionStatus.Active)
            {
                lock (_internalTransaction)
                {
                    // Since this is the root transaction do state based dispose.
                    _internalTransaction.State.DisposeRoot(_internalTransaction);
                }
            }
 
            // Attempt to clean up the internal transaction
            long remainingITx = Interlocked.Decrement(ref _internalTransaction._cloneCount);
            if (remainingITx == 0)
            {
                _internalTransaction.Dispose();
            }
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
            }
        }
 
        public void EndCommit(IAsyncResult asyncResult)
        {
            TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
            if (etwLog.IsEnabled())
            {
                etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this);
            }
 
            if (asyncResult != ((object)this))
            {
                throw new ArgumentException(SR.BadAsyncResult, nameof(asyncResult));
            }
 
            lock (_internalTransaction)
            {
                do
                {
                    Debug.Assert(_internalTransaction.State != null);
                    if (_internalTransaction.State.IsCompleted(_internalTransaction))
                    {
                        break;
                    }
                } while (Monitor.Wait(_internalTransaction));
 
                _internalTransaction.State.EndCommit(_internalTransaction);
            }
 
            if (etwLog.IsEnabled())
            {
                etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this);
            }
        }
 
        object? IAsyncResult.AsyncState => _internalTransaction._asyncState;
 
        bool IAsyncResult.CompletedSynchronously => false;
 
        WaitHandle IAsyncResult.AsyncWaitHandle
        {
            get
            {
                if (_internalTransaction._asyncResultEvent == null)
                {
                    lock (_internalTransaction)
                    {
                        if (_internalTransaction._asyncResultEvent == null)
                        {
                            Debug.Assert(_internalTransaction.State != null);
                            // Demand create an event that is already signaled if the transaction has completed.
                            ManualResetEvent temp = new ManualResetEvent(
                                _internalTransaction.State.get_Status(_internalTransaction) != TransactionStatus.Active);
 
                            _internalTransaction._asyncResultEvent = temp;
                        }
                    }
                }
 
                return _internalTransaction._asyncResultEvent;
            }
        }
 
        bool IAsyncResult.IsCompleted
        {
            get
            {
                lock (_internalTransaction)
                {
                    Debug.Assert(_internalTransaction.State != null);
                    return _internalTransaction.State.get_Status(_internalTransaction) != TransactionStatus.Active;
                }
            }
        }
    }
}