1 //----------------------------------------------------------------------------- 2 // Copyright (c) Microsoft Corporation. All rights reserved. 3 //----------------------------------------------------------------------------- 4 5 namespace System.Runtime 6 { 7 using System; 8 using System.Diagnostics; 9 using System.Diagnostics.CodeAnalysis; 10 using System.Threading; 11 using System.Transactions; 12 13 // AsyncResult starts acquired; Complete releases. 14 [Fx.Tag.SynchronizationPrimitive(Fx.Tag.BlocksUsing.ManualResetEvent, SupportsAsync = true, ReleaseMethod = "Complete")] 15 abstract class TransactedAsyncResult : AsyncResult 16 { 17 IAsyncResult deferredTransactionalResult; 18 TransactionSignalScope transactionContext; 19 TransactedAsyncResult(AsyncCallback callback, object state)20 protected TransactedAsyncResult(AsyncCallback callback, object state) 21 : base(callback, state) 22 { 23 SetBeforePrepareAsyncCompletionAction(BeforePrepareAsyncCompletion); 24 SetCheckSyncValidationFunc(CheckSyncValidation); 25 } 26 OnContinueAsyncCompletion(IAsyncResult result)27 protected override bool OnContinueAsyncCompletion(IAsyncResult result) 28 { 29 if (this.transactionContext != null && !this.transactionContext.Signal(result)) 30 { 31 // The TransactionScope isn't cleaned up yet and can't be done on this thread. Must defer 32 // the callback (which is likely to attempt to commit the transaction) until later. 33 return false; 34 } 35 36 this.transactionContext = null; 37 return true; 38 } 39 CheckSyncValidation(IAsyncResult result)40 bool CheckSyncValidation(IAsyncResult result) 41 { 42 if (result.CompletedSynchronously) 43 { 44 // Once we pass the check, we know that we own forward progress, so transactionContext is correct. Verify its state. 45 if (this.transactionContext != null) 46 { 47 if (this.transactionContext.State != TransactionSignalState.Completed) 48 { 49 ThrowInvalidAsyncResult("Check/SyncContinue cannot be called from within the PrepareTransactionalCall using block."); 50 } 51 else if (this.transactionContext.IsSignalled) 52 { 53 // This is most likely to happen when result.CompletedSynchronously registers differently here and in the callback, which 54 // is the fault of 'result'. 55 ThrowInvalidAsyncResult(result); 56 } 57 } 58 } 59 else if (object.ReferenceEquals(result, this.deferredTransactionalResult)) 60 { 61 // The transactionContext may not be current if forward progress has been made via the callback. Instead, 62 // use deferredTransactionalResult to see if we are supposed to execute a post-transaction callback. 63 // 64 // Once we pass the check, we know that we own forward progress, so transactionContext is correct. Verify its state. 65 if (this.transactionContext == null || !this.transactionContext.IsSignalled) 66 { 67 ThrowInvalidAsyncResult(result); 68 } 69 this.deferredTransactionalResult = null; 70 } 71 else 72 { 73 return false; 74 } 75 76 this.transactionContext = null; 77 return true; 78 } 79 BeforePrepareAsyncCompletion()80 void BeforePrepareAsyncCompletion() 81 { 82 if (this.transactionContext != null) 83 { 84 // It might be an old, leftover one, if an exception was thrown within the last using (PrepareTransactionalCall()) block. 85 if (this.transactionContext.IsPotentiallyAbandoned) 86 { 87 this.transactionContext = null; 88 } 89 else 90 { 91 this.transactionContext.Prepared(); 92 } 93 } 94 } 95 PrepareTransactionalCall(Transaction transaction)96 protected IDisposable PrepareTransactionalCall(Transaction transaction) 97 { 98 if (this.transactionContext != null && !this.transactionContext.IsPotentiallyAbandoned) 99 { 100 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."); 101 } 102 103 return this.transactionContext = transaction == null ? null : new TransactionSignalScope(this, transaction); 104 } 105 106 enum TransactionSignalState 107 { 108 Ready = 0, 109 Prepared, 110 Completed, 111 Abandoned, 112 } 113 114 class TransactionSignalScope : SignalGate<IAsyncResult>, IDisposable 115 { 116 TransactionScope transactionScope; 117 TransactedAsyncResult parent; 118 TransactionSignalScope(TransactedAsyncResult result, Transaction transaction)119 public TransactionSignalScope(TransactedAsyncResult result, Transaction transaction) 120 { 121 Fx.Assert(transaction != null, "Null Transaction provided to AsyncResult.TransactionSignalScope."); 122 this.parent = result; 123 this.transactionScope = TransactionHelper.CreateTransactionScope(transaction); 124 } 125 126 public TransactionSignalState State { get; private set; } 127 128 public bool IsPotentiallyAbandoned 129 { 130 get 131 { 132 return State == TransactionSignalState.Abandoned || (State == TransactionSignalState.Completed && !IsSignalled); 133 } 134 } 135 Prepared()136 public void Prepared() 137 { 138 if (State != TransactionSignalState.Ready) 139 { 140 AsyncResult.ThrowInvalidAsyncResult("PrepareAsyncCompletion should only be called once per PrepareTransactionalCall."); 141 } 142 State = TransactionSignalState.Prepared; 143 } 144 IDisposable.Dispose()145 void IDisposable.Dispose() 146 { 147 if (State == TransactionSignalState.Ready) 148 { 149 State = TransactionSignalState.Abandoned; 150 } 151 else if (State == TransactionSignalState.Prepared) 152 { 153 State = TransactionSignalState.Completed; 154 } 155 else 156 { 157 AsyncResult.ThrowInvalidAsyncResult("PrepareTransactionalCall should only be called in a using. Dispose called multiple times."); 158 } 159 160 try 161 { 162 TransactionHelper.CompleteTransactionScope(ref this.transactionScope); 163 } 164 catch (Exception exception) 165 { 166 if (Fx.IsFatal(exception)) 167 { 168 throw; 169 } 170 171 // Complete and Dispose are not expected to throw. If they do it can mess up the AsyncResult state machine. 172 throw Fx.Exception.AsError(new InvalidOperationException(SRCore.AsyncTransactionException)); 173 } 174 175 // This will release the callback to run, or tell us that we need to defer the callback to Check/SyncContinue. 176 // 177 // It's possible to avoid this Interlocked when CompletedSynchronously is true, but we have no way of knowing that 178 // from here, and adding a way would add complexity to the AsyncResult transactional calling pattern. This 179 // unnecessary Interlocked only happens when: PrepareTransactionalCall is called with a non-null transaction, 180 // PrepareAsyncCompletion is reached, and the operation completes synchronously or with an exception. 181 IAsyncResult result; 182 if (State == TransactionSignalState.Completed && Unlock(out result)) 183 { 184 if (this.parent.deferredTransactionalResult != null) 185 { 186 AsyncResult.ThrowInvalidAsyncResult(this.parent.deferredTransactionalResult); 187 } 188 this.parent.deferredTransactionalResult = result; 189 } 190 } 191 } 192 } 193 } 194