File: System\Runtime\TransactedAsyncResult.cs
Project: ndp\cdf\src\NetFx40\System.Runtime.DurableInstancing\System.Runtime.DurableInstancing.csproj (System.Runtime.DurableInstancing)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
 
namespace System.Runtime
{
    using System;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Threading;
    using System.Transactions;
 
    // AsyncResult starts acquired; Complete releases.
    [Fx.Tag.SynchronizationPrimitive(Fx.Tag.BlocksUsing.ManualResetEvent, SupportsAsync = true, ReleaseMethod = "Complete")]
    abstract class TransactedAsyncResult : AsyncResult
    {
        IAsyncResult deferredTransactionalResult;
        TransactionSignalScope transactionContext;
 
        protected TransactedAsyncResult(AsyncCallback callback, object state)
            : base(callback, state)
        {
            SetBeforePrepareAsyncCompletionAction(BeforePrepareAsyncCompletion);
            SetCheckSyncValidationFunc(CheckSyncValidation);
        }
 
        protected override bool OnContinueAsyncCompletion(IAsyncResult result)
        {
            if (this.transactionContext != null && !this.transactionContext.Signal(result))
            {
                // The TransactionScope isn't cleaned up yet and can't be done on this thread.  Must defer
                // the callback (which is likely to attempt to commit the transaction) until later.
                return false;
            }
 
            this.transactionContext = null;
            return true;
        }
 
        bool CheckSyncValidation(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
            {
                // Once we pass the check, we know that we own forward progress, so transactionContext is correct. Verify its state.
                if (this.transactionContext != null)
                {
                    if (this.transactionContext.State != TransactionSignalState.Completed)
                    {
                        ThrowInvalidAsyncResult("Check/SyncContinue cannot be called from within the PrepareTransactionalCall using block.");
                    }
                    else if (this.transactionContext.IsSignalled)
                    {
                        // This is most likely to happen when result.CompletedSynchronously registers differently here and in the callback, which
                        // is the fault of 'result'.
                        ThrowInvalidAsyncResult(result);
                    }
                }
            }
            else if (object.ReferenceEquals(result, this.deferredTransactionalResult))
            {
                // The transactionContext may not be current if forward progress has been made via the callback. Instead,
                // use deferredTransactionalResult to see if we are supposed to execute a post-transaction callback.
                //
                // Once we pass the check, we know that we own forward progress, so transactionContext is correct. Verify its state.
                if (this.transactionContext == null || !this.transactionContext.IsSignalled)
                {
                    ThrowInvalidAsyncResult(result);
                }
                this.deferredTransactionalResult = null;
            }
            else
            {
                return false;
            }
 
            this.transactionContext = null;
            return true;
        }
 
        void BeforePrepareAsyncCompletion()
        {
            if (this.transactionContext != null)
            {
                // It might be an old, leftover one, if an exception was thrown within the last using (PrepareTransactionalCall()) block.
                if (this.transactionContext.IsPotentiallyAbandoned)
                {
                    this.transactionContext = null;
                }
                else
                {
                    this.transactionContext.Prepared();
                }
            }
        }
 
        protected IDisposable PrepareTransactionalCall(Transaction transaction)
        {
            if (this.transactionContext != null && !this.transactionContext.IsPotentiallyAbandoned)
            {
                ThrowInvalidAsyncResult("PrepareTransactionalCall should only be called as the object of non-nested using statements. If the Begin succeeds, Check/SyncContinue must be called before another PrepareTransactionalCall.");
            }
 
            return this.transactionContext = transaction == null ? null : new TransactionSignalScope(this, transaction);
        }
 
        enum TransactionSignalState
        {
            Ready = 0,
            Prepared,
            Completed,
            Abandoned,
        }
 
        class TransactionSignalScope : SignalGate<IAsyncResult>, IDisposable
        {
            TransactionScope transactionScope;
            TransactedAsyncResult parent;
 
            public TransactionSignalScope(TransactedAsyncResult result, Transaction transaction)
            {
                Fx.Assert(transaction != null, "Null Transaction provided to AsyncResult.TransactionSignalScope.");
                this.parent = result;
                this.transactionScope = TransactionHelper.CreateTransactionScope(transaction);
            }
 
            public TransactionSignalState State { get; private set; }
 
            public bool IsPotentiallyAbandoned
            {
                get
                {
                    return State == TransactionSignalState.Abandoned || (State == TransactionSignalState.Completed && !IsSignalled);
                }
            }
 
            public void Prepared()
            {
                if (State != TransactionSignalState.Ready)
                {
                    AsyncResult.ThrowInvalidAsyncResult("PrepareAsyncCompletion should only be called once per PrepareTransactionalCall.");
                }
                State = TransactionSignalState.Prepared;
            }
 
            void IDisposable.Dispose()
            {
                if (State == TransactionSignalState.Ready)
                {
                    State = TransactionSignalState.Abandoned;
                }
                else if (State == TransactionSignalState.Prepared)
                {
                    State = TransactionSignalState.Completed;
                }
                else
                {
                    AsyncResult.ThrowInvalidAsyncResult("PrepareTransactionalCall should only be called in a using. Dispose called multiple times.");
                }
 
                try
                {
                    TransactionHelper.CompleteTransactionScope(ref this.transactionScope);
                }
                catch (Exception exception)
                {
                    if (Fx.IsFatal(exception))
                    {
                        throw;
                    }
 
                    // Complete and Dispose are not expected to throw.  If they do it can mess up the AsyncResult state machine.
                    throw Fx.Exception.AsError(new InvalidOperationException(SRCore.AsyncTransactionException));
                }
 
                // This will release the callback to run, or tell us that we need to defer the callback to Check/SyncContinue.
                //
                // It's possible to avoid this Interlocked when CompletedSynchronously is true, but we have no way of knowing that
                // from here, and adding a way would add complexity to the AsyncResult transactional calling pattern. This
                // unnecessary Interlocked only happens when: PrepareTransactionalCall is called with a non-null transaction,
                // PrepareAsyncCompletion is reached, and the operation completes synchronously or with an exception.
                IAsyncResult result;
                if (State == TransactionSignalState.Completed && Unlock(out result))
                {
                    if (this.parent.deferredTransactionalResult != null)
                    {
                        AsyncResult.ThrowInvalidAsyncResult(this.parent.deferredTransactionalResult);
                    }
                    this.parent.deferredTransactionalResult = result;
                }
            }
        }
    }
}