1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //----------------------------------------------------------------
4 
5 namespace System.ServiceModel.Activities.Dispatcher
6 {
7     using System.Runtime;
8     using System.Transactions;
9 
10     //1) On Tx.Prepare
11     //    Persist the instance.
12     //          When Persist completes Tx.Prepared called.
13     //          When Persist fails Tx.ForceRollback called.
14     //2) On Tx.Commit
15     //     DurableInstance.OnTransactionCompleted().
16     //3) On Tx.Abort
17     //     DurableInstance.OnTransactionAborted()
18     class TransactionContext : IEnlistmentNotification
19     {
20         static AsyncCallback handleEndPrepare = Fx.ThunkCallback(new AsyncCallback(HandleEndPrepare));
21         Transaction currentTransaction;
22         WorkflowServiceInstance durableInstance;
23 
TransactionContext(WorkflowServiceInstance durableInstance, Transaction currentTransaction)24         public TransactionContext(WorkflowServiceInstance durableInstance, Transaction currentTransaction)
25         {
26             Fx.Assert(durableInstance != null, "Null DurableInstance passed to TransactionContext.");
27             Fx.Assert(currentTransaction != null, "Null Transaction passed to TransactionContext.");
28 
29             this.currentTransaction = currentTransaction.Clone();
30             this.durableInstance = durableInstance;
31             this.currentTransaction.EnlistVolatile(this, EnlistmentOptions.EnlistDuringPrepareRequired);
32         }
33 
34         public Transaction CurrentTransaction
35         {
36             get
37             {
38                 return this.currentTransaction;
39             }
40         }
41 
IEnlistmentNotification.Commit(Enlistment enlistment)42         void IEnlistmentNotification.Commit(Enlistment enlistment)
43         {
44             enlistment.Done();
45             this.durableInstance.TransactionCommitted();
46         }
47 
IEnlistmentNotification.InDoubt(Enlistment enlistment)48         void IEnlistmentNotification.InDoubt(Enlistment enlistment)
49         {
50             enlistment.Done();
51             Fx.Assert(this.currentTransaction.TransactionInformation.Status == TransactionStatus.InDoubt, "Transaction state should be InDoubt at this point");
52             TransactionException exception = this.GetAbortedOrInDoubtTransactionException();
53 
54             Fx.Assert(exception != null, "Need a valid TransactionException at this point");
55             this.durableInstance.OnTransactionAbortOrInDoubt(exception);
56         }
57 
IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)58         void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
59         {
60             bool success = false;
61             try
62             {
63                 IAsyncResult result = new PrepareAsyncResult(this, TransactionContext.handleEndPrepare, preparingEnlistment);
64                 if (result.CompletedSynchronously)
65                 {
66                     PrepareAsyncResult.End(result);
67                     preparingEnlistment.Prepared();
68                 }
69                 success = true;
70             }
71             //we need to swollow the TransactionException as it could because another party aborting it
72             catch (TransactionException)
73             {
74             }
75             finally
76             {
77                 if (!success)
78                 {
79                     preparingEnlistment.ForceRollback();
80                 }
81             }
82         }
83 
IEnlistmentNotification.Rollback(Enlistment enlistment)84         void IEnlistmentNotification.Rollback(Enlistment enlistment)
85         {
86             enlistment.Done();
87             Fx.Assert(this.currentTransaction.TransactionInformation.Status == TransactionStatus.Aborted, "Transaction state should be Aborted at this point");
88             TransactionException exception = this.GetAbortedOrInDoubtTransactionException();
89 
90             Fx.Assert(exception != null, "Need a valid TransactionException at this point");
91             this.durableInstance.OnTransactionAbortOrInDoubt(exception);
92         }
93 
GetAbortedOrInDoubtTransactionException()94         TransactionException GetAbortedOrInDoubtTransactionException()
95         {
96             try
97             {
98                 TransactionHelper.ThrowIfTransactionAbortedOrInDoubt(this.currentTransaction);
99             }
100             catch (TransactionException exception)
101             {
102                 return exception;
103             }
104             return null;
105         }
106 
HandleEndPrepare(IAsyncResult result)107         static void HandleEndPrepare(IAsyncResult result)
108         {
109             PreparingEnlistment preparingEnlistment = (PreparingEnlistment)result.AsyncState;
110             bool success = false;
111             try
112             {
113                 if (!result.CompletedSynchronously)
114                 {
115                     PrepareAsyncResult.End(result);
116                     preparingEnlistment.Prepared();
117                 }
118                 success = true;
119             }
120             //we need to swollow the TransactionException as it could because another party aborting it
121             catch (TransactionException)
122             {
123             }
124             finally
125             {
126                 if (!success)
127                 {
128                     preparingEnlistment.ForceRollback();
129                 }
130             }
131         }
132 
133         class PrepareAsyncResult : TransactedAsyncResult
134         {
135             static readonly AsyncCompletion onEndPersist = new AsyncCompletion(OnEndPersist);
136 
137             readonly TransactionContext context;
138 
PrepareAsyncResult(TransactionContext context, AsyncCallback callback, object state)139             public PrepareAsyncResult(TransactionContext context, AsyncCallback callback, object state)
140                 : base(callback, state)
141             {
142                 this.context = context;
143 
144                 IAsyncResult result = null;
145                 using (PrepareTransactionalCall(this.context.currentTransaction))
146                 {
147                     result = this.context.durableInstance.BeginPersist(TimeSpan.MaxValue, PrepareAsyncCompletion(PrepareAsyncResult.onEndPersist), this);
148                 }
149                 if (SyncContinue(result))
150                 {
151                     Complete(true);
152                 }
153             }
154 
End(IAsyncResult result)155             public static void End(IAsyncResult result)
156             {
157                 AsyncResult.End<PrepareAsyncResult>(result);
158             }
159 
OnEndPersist(IAsyncResult result)160             static bool OnEndPersist(IAsyncResult result)
161             {
162                 PrepareAsyncResult thisPtr = (PrepareAsyncResult)result.AsyncState;
163                 thisPtr.context.durableInstance.EndPersist(result);
164                 thisPtr.context.durableInstance.OnTransactionPrepared();
165                 return true;
166             }
167         }
168     }
169 }
170