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