1 //-----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //-----------------------------------------------------------------------------
4 
5 namespace System.ServiceModel.Activities.Dispatcher
6 {
7     using System.Collections.Generic;
8     using System.Runtime;
9     using System.Threading;
10     using System.Transactions;
11 
12     sealed class PersistenceContextEnlistment : IEnlistmentNotification
13     {
14         PreparingEnlistment preparingEnlistment;
15         Enlistment enlistment;
16 
17         // This will be true if we have received either a Prepare or Rollback
18         // notification from the transaction manager. If this is true, it is too
19         // late to try to add more entries to the undo collection.
20         bool tooLateForMoreUndo;
21         Transaction transaction;
22         object ThisLock = new object();
23 
24         List<PersistenceContext> enlistedContexts;
25 
26         static Action<object> prepareCallback;
27         static Action<object> commitCallback;
28         static Action<object> rollbackCallback;
29         static Action<object> indoubtCallback;
30 
PersistenceContextEnlistment(PersistenceContext context, Transaction transaction)31         internal PersistenceContextEnlistment(PersistenceContext context, Transaction transaction)
32         {
33             this.transaction = transaction;
34 
35             this.enlistedContexts = new List<PersistenceContext>();
36             this.enlistedContexts.Add(context);
37         }
38 
AddToEnlistment(PersistenceContext context)39         internal void AddToEnlistment(PersistenceContext context)
40         {
41             lock (this.ThisLock)
42             {
43                 if (tooLateForMoreUndo)
44                 {
45                     throw FxTrace.Exception.AsError(new InvalidOperationException(SR.PersistenceTooLateToEnlist));
46                 }
47 
48                 this.enlistedContexts.Add(context);
49             }
50         }
51 
52         internal static Action<object> PrepareCallback
53         {
54             get
55             {
56                 if (prepareCallback == null)
57                 {
58                     prepareCallback = new Action<object>(DoPrepare);
59                 }
60                 return prepareCallback;
61             }
62         }
63 
64         internal static Action<object> CommitCallback
65         {
66             get
67             {
68                 if (commitCallback == null)
69                 {
70                     commitCallback = new Action<object>(DoCommit);
71                 }
72                 return commitCallback;
73             }
74         }
75 
76         internal static Action<object> RollbackCallback
77         {
78             get
79             {
80                 if (rollbackCallback == null)
81                 {
82                     rollbackCallback = new Action<object>(DoRollback);
83                 }
84                 return rollbackCallback;
85             }
86         }
87 
88         internal static Action<object> IndoubtCallback
89         {
90             get
91             {
92                 if (indoubtCallback == null)
93                 {
94                     indoubtCallback = new Action<object>(DoIndoubt);
95                 }
96                 return indoubtCallback;
97             }
98         }
99 
IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)100         void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
101         {
102             // We don't want to try to grab one of our locks while executing on the
103             // System.Transactions notification thread because that will block all
104             // the other notifications that need to be made. So schedule the
105             // processing of this on another thread. If we decide that the locks
106             // aren't necessary, we can get rid of this.
107             this.preparingEnlistment = preparingEnlistment;
108             ActionItem.Schedule(PrepareCallback, this);
109         }
110 
IEnlistmentNotification.Commit(Enlistment enlistment)111         void IEnlistmentNotification.Commit(Enlistment enlistment)
112         {
113             // We don't want to try to grab one of our locks while executing on the
114             // System.Transactions notification thread because that will block all
115             // the other notifications that need to be made. So schedule the
116             // processing of this on another thread. If we decide that the locks
117             // aren't necessary, we can get rid of this.
118             this.enlistment = enlistment;
119             ActionItem.Schedule(CommitCallback, this);
120         }
121 
IEnlistmentNotification.Rollback(Enlistment enlistment)122         void IEnlistmentNotification.Rollback(Enlistment enlistment)
123         {
124             // We don't want to try to grab one of our locks while executing on the
125             // System.Transactions notification thread because that will block all
126             // the other notifications that need to be made. So schedule the
127             // processing of this on another thread. If we decide that the locks
128             // aren't necessary, we can get rid of this.
129             this.enlistment = enlistment;
130             ActionItem.Schedule(RollbackCallback, this);
131         }
132 
IEnlistmentNotification.InDoubt(Enlistment enlistment)133         void IEnlistmentNotification.InDoubt(Enlistment enlistment)
134         {
135             // We don't want to try to grab one of our locks while executing on the
136             // System.Transactions notification thread because that will block all
137             // the other notifications that need to be made. So schedule the
138             // processing of this on another thread. If we decide that the locks
139             // aren't necessary, we can get rid of this.
140             this.enlistment = enlistment;
141             ActionItem.Schedule(IndoubtCallback, this);
142         }
143 
DoPrepare(object state)144         internal static void DoPrepare(object state)
145         {
146             PersistenceContextEnlistment pcEnlist = state as PersistenceContextEnlistment;
147             Fx.Assert(null != pcEnlist, "PersistenceContextEnlistment.DoPrepare called with an object that is not a PersistenceContext.");
148 
149             lock (pcEnlist.ThisLock)
150             {
151                 pcEnlist.tooLateForMoreUndo = true;
152             }
153 
154             // This needs to be done outside of the lock because it could induce System.Transactions
155             // to do a whole bunch of other work inline, including issuing the Commit call and doing
156             // Completion notifications for hte transaction. We don't want to be holding the lock
157             // during all of that.
158             pcEnlist.preparingEnlistment.Prepared();
159         }
160 
DoCommit(object state)161         internal static void DoCommit(object state)
162         {
163             PersistenceContextEnlistment pcEnlist = state as PersistenceContextEnlistment;
164             Fx.Assert(null != pcEnlist, "PersistenceContextEnlistment.DoCommit called with an object that is not a PersistenceContext.");
165 
166             lock (pcEnlist.ThisLock)
167             {
168                 // Wake up the next waiter for the pc, if any.
169                 foreach (PersistenceContext context in pcEnlist.enlistedContexts)
170                 {
171                     context.ScheduleNextTransactionWaiter();
172                 }
173             }
174             lock (PersistenceContext.Enlistments)
175             {
176                 PersistenceContext.Enlistments.Remove(pcEnlist.transaction.GetHashCode());
177             }
178             // This needs to be outside the lock because SysTx might do other stuff on the thread.
179             pcEnlist.enlistment.Done();
180         }
181 
DoRollback(object state)182         internal static void DoRollback(object state)
183         {
184             PersistenceContextEnlistment pcEnlist = state as PersistenceContextEnlistment;
185             Fx.Assert(null != pcEnlist, "PersistenceContextEnlistment.DoRollback called with an object that is not a PersistenceContext.");
186 
187             lock (pcEnlist.ThisLock)
188             {
189                 pcEnlist.tooLateForMoreUndo = true;
190 
191                 foreach (PersistenceContext context in pcEnlist.enlistedContexts)
192                 {
193                     context.Abort();
194                     context.ScheduleNextTransactionWaiter();
195                 }
196             }
197             lock (PersistenceContext.Enlistments)
198             {
199                 PersistenceContext.Enlistments.Remove(pcEnlist.transaction.GetHashCode());
200             }
201             // This needs to be outside the lock because SysTx might do other stuff on the thread.
202             pcEnlist.enlistment.Done();
203         }
204 
DoIndoubt(object state)205         internal static void DoIndoubt(object state)
206         {
207             PersistenceContextEnlistment pcEnlist = state as PersistenceContextEnlistment;
208             Fx.Assert(null != pcEnlist, "PersistenceContextEnlistment.DoIndoubt called with an object that is not a PersistenceContext.");
209 
210             lock (pcEnlist.ThisLock)
211             {
212                 pcEnlist.tooLateForMoreUndo = true;
213 
214                 foreach (PersistenceContext context in pcEnlist.enlistedContexts)
215                 {
216                     context.Abort();
217                     context.ScheduleNextTransactionWaiter();
218                 }
219             }
220             lock (PersistenceContext.Enlistments)
221             {
222                 PersistenceContext.Enlistments.Remove(pcEnlist.transaction.GetHashCode());
223             }
224             // This needs to be outside the lock because SysTx might do other stuff on the thread.
225             pcEnlist.enlistment.Done();
226         }
227     }
228 }
229