Copy from 72246 to trunk
[mono.git] / mcs / class / System.Transactions / System.Transactions / Transaction.cs
1 //
2 // Transaction.cs
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //      Ankit Jain       <JAnkit@novell.com>
7 //
8 // (C)2005 Novell Inc,
9 // (C)2006 Novell Inc,
10 //
11
12 #if NET_2_0
13 using System.Collections;
14 using System.Collections.Generic;
15 using System.Security.Permissions;
16 using System.Runtime.Serialization;
17 using System.Threading;
18
19 namespace System.Transactions
20 {
21         [Serializable]
22         public class Transaction : IDisposable, ISerializable
23         {
24                 [ThreadStatic]
25                 static Transaction ambient;
26
27                 IsolationLevel level;
28                 TransactionInformation info;
29
30                 ArrayList dependents = new ArrayList ();
31
32                 /* Volatile enlistments */
33                 List <IEnlistmentNotification> volatiles = new List <IEnlistmentNotification> ();
34
35                 /* Durable enlistments 
36                    Durable RMs can also have 2 Phase commit but
37                    not in LTM, and that is what we are supporting
38                    right now   
39                  */
40                 List <ISinglePhaseNotification> durables = new List <ISinglePhaseNotification> ();
41
42                 delegate void AsyncCommit ();
43                 
44                 AsyncCommit asyncCommit = null;
45                 bool committing;
46                 bool committed = false;
47                 bool aborted = false;
48                 TransactionScope scope = null;
49                 
50                 Exception innerException;
51
52                 internal Transaction ()
53                 {
54                         info = new TransactionInformation ();
55                         level = IsolationLevel.Serializable;
56                 }
57
58                 internal Transaction (Transaction other)
59                 {
60                         level = other.level;
61                         info = other.info;
62                         dependents = other.dependents;
63                 }
64
65                 [MonoTODO]
66                 void ISerializable.GetObjectData (SerializationInfo info,
67                         StreamingContext context)
68                 {
69                         throw new NotImplementedException ();
70                 }
71
72                 public event TransactionCompletedEventHandler TransactionCompleted;
73
74                 public static Transaction Current {
75                         get { 
76                                 EnsureIncompleteCurrentScope ();
77                                 return CurrentInternal;
78                         }
79                         set { 
80                                 EnsureIncompleteCurrentScope ();
81                                 CurrentInternal = value;
82                         }
83                 }
84
85                 internal static Transaction CurrentInternal {
86                         get { return ambient; }
87                         set { ambient = value; }
88                 }
89
90                 public IsolationLevel IsolationLevel {
91                         get { 
92                                 EnsureIncompleteCurrentScope ();
93                                 return level; 
94                         }
95                 }
96
97                 public TransactionInformation TransactionInformation {
98                         get { 
99                                 EnsureIncompleteCurrentScope ();
100                                 return info; 
101                         }
102                 }
103
104                 public Transaction Clone ()
105                 {
106                         return new Transaction (this);
107                 }
108
109                 [MonoTODO]
110                 public void Dispose ()
111                 {
112                         throw new NotImplementedException ();
113                 }
114
115                 [MonoTODO]
116                 public DependentTransaction DependentClone (
117                         DependentCloneOption option)
118                 {
119                         DependentTransaction d = 
120                                 new DependentTransaction (this, option);
121                         dependents.Add (d);
122                         return d;
123                 }
124
125                 [MonoTODO ("Only SinglePhase commit supported for durable resource managers.")]
126                 [PermissionSetAttribute (SecurityAction.LinkDemand)]
127                 public Enlistment EnlistDurable (Guid manager,
128                         IEnlistmentNotification notification,
129                         EnlistmentOptions options)
130                 {
131                         throw new NotImplementedException ("Only SinglePhase commit supported for durable resource managers.");
132                 }
133
134                 [MonoTODO ("Only Local Transaction Manager supported. Cannot have more than 1 durable resource per transaction. Only EnlistmentOptions.None supported yet.")]
135                 [PermissionSetAttribute (SecurityAction.LinkDemand)]
136                 public Enlistment EnlistDurable (Guid manager,
137                         ISinglePhaseNotification notification,
138                         EnlistmentOptions options)
139                 {
140                         if (durables.Count == 1)
141                                 throw new NotImplementedException ("Only LTM supported. Cannot have more than 1 durable resource per transaction.");
142
143                         EnsureIncompleteCurrentScope ();
144
145                         if (options != EnlistmentOptions.None)
146                                 throw new NotImplementedException ("Implement me");
147
148                         durables.Add (notification);
149
150                         /* FIXME: Enlistment ?? */
151                         return new Enlistment ();
152                 }
153
154                 [MonoTODO]
155                 public bool EnlistPromotableSinglePhase (
156                         IPromotableSinglePhaseNotification notification)
157                 {
158                         throw new NotImplementedException ();
159                 }
160
161                 [MonoTODO ("EnlistmentOptions being ignored")]
162                 public Enlistment EnlistVolatile (
163                         IEnlistmentNotification notification,
164                         EnlistmentOptions options)
165                 {
166                         return EnlistVolatileInternal (notification, options);
167                 }
168
169                 [MonoTODO ("EnlistmentOptions being ignored")]
170                 public Enlistment EnlistVolatile (
171                         ISinglePhaseNotification notification,
172                         EnlistmentOptions options)
173                 {
174                         /* FIXME: Anything extra reqd for this? */
175                         return EnlistVolatileInternal (notification, options);
176                 }
177
178                 private Enlistment EnlistVolatileInternal (
179                         IEnlistmentNotification notification,
180                         EnlistmentOptions options)
181                 {
182                         EnsureIncompleteCurrentScope (); 
183                         /* FIXME: Handle options.EnlistDuringPrepareRequired */
184                         volatiles.Add (notification);
185
186                         /* FIXME: Enlistment.. ? */
187                         return new Enlistment ();
188                 }
189
190                 public override bool Equals (object obj)
191                 {
192                         return Equals (obj as Transaction);
193                 }
194
195                 // FIXME: Check whether this is correct (currently, GetHashCode() uses 'dependents' but this doesn't)
196                 private bool Equals (Transaction t)
197                 {
198                         if (ReferenceEquals (t, this))
199                                 return true;
200                         if (ReferenceEquals (t, null))
201                                 return false;
202                         return this.level == t.level &&
203                                 this.info == t.info;
204                 }
205
206                 public static bool operator == (Transaction x, Transaction y)
207                 {
208                         if (ReferenceEquals (x, null))
209                                 return ReferenceEquals (y, null);
210                         return x.Equals (y);
211                 }
212
213                 public static bool operator != (Transaction x, Transaction y)
214                 {
215                         return !(x == y);
216                 }
217
218                 public override int GetHashCode ()
219                 {
220                         return (int) level ^ info.GetHashCode () ^ dependents.GetHashCode ();
221                 }
222
223                 public void Rollback ()
224                 {
225                         Rollback (null);
226                 }
227
228                 public void Rollback (Exception ex)
229                 {
230                         EnsureIncompleteCurrentScope ();
231                         Rollback (ex, null);
232                 }
233
234                 internal void Rollback (Exception ex, IEnlistmentNotification enlisted)
235                 {
236                         if (aborted)
237                                 return;
238
239                         /* See test ExplicitTransaction7 */
240                         if (info.Status == TransactionStatus.Committed)
241                                 throw new TransactionException ("Transaction has already been committed. Cannot accept any new work.");
242
243                         innerException = ex;
244                         Enlistment e = new Enlistment ();
245                         foreach (IEnlistmentNotification prep in volatiles)
246                                 if (prep != enlisted)
247                                         prep.Rollback (e);
248
249                         if (durables.Count > 0 && durables [0] != enlisted)
250                                 durables [0].Rollback (e);
251
252                         Aborted = true;
253                 }
254
255                 bool Aborted {
256                         get { return aborted; }
257                         set {
258                                 aborted = value;
259                                 if (aborted)
260                                         info.Status = TransactionStatus.Aborted;
261                         }
262                 }
263                 
264                 internal TransactionScope Scope {
265                         get { return scope; }
266                         set { scope = value; }
267                 }
268
269                 protected IAsyncResult BeginCommitInternal (AsyncCallback callback)
270                 {
271                         if (committed || committing)
272                                 throw new InvalidOperationException ("Commit has already been called for this transaction.");
273
274                         this.committing = true;
275
276                         asyncCommit = new AsyncCommit (DoCommit);
277                         return asyncCommit.BeginInvoke (callback, null);
278                 }
279
280                 protected void EndCommitInternal (IAsyncResult ar)
281                 {
282                         asyncCommit.EndInvoke (ar);
283                 }
284
285                 internal void CommitInternal ()
286                 {
287                         if (committed || committing)
288                                 throw new InvalidOperationException ("Commit has already been called for this transaction.");
289
290                         this.committing = true;
291
292                         DoCommit ();            
293                 }
294                 
295                 private void DoCommit ()
296                 {
297                         /* Scope becomes null in TransactionScope.Dispose */
298                         if (Scope != null) {
299                                 /* See test ExplicitTransaction8 */
300                                 Rollback (null, null);
301                                 CheckAborted ();
302                         }
303
304                         if (volatiles.Count == 1 && durables.Count == 0) {
305                                 /* Special case */
306                                 ISinglePhaseNotification single = volatiles [0] as ISinglePhaseNotification;
307                                 if (single != null) {
308                                         DoSingleCommit (single);
309                                         Complete ();
310                                         return;
311                                 }
312                         } 
313
314                         if (volatiles.Count > 0)
315                                 DoPreparePhase ();
316
317                         if (durables.Count > 0)
318                                 DoSingleCommit (durables [0]);
319
320                         if (volatiles.Count > 0)
321                                 DoCommitPhase ();
322
323                         Complete ();
324                 }
325
326                 private void Complete ()
327                 {
328                         committing = false;
329                         committed = true;
330
331                         if (!aborted)
332                                 info.Status = TransactionStatus.Committed;
333                 }
334
335                 internal void InitScope (TransactionScope scope)
336                 {
337                         /* See test NestedTransactionScope10 */
338                         CheckAborted ();
339
340                         /* See test ExplicitTransaction6a */
341                         if (committed)
342                                 throw new InvalidOperationException ("Commit has already been called on this transaction."); 
343
344                         Scope = scope;  
345                 }
346
347                 void DoPreparePhase ()
348                 {
349                         PreparingEnlistment pe;
350                         foreach (IEnlistmentNotification enlisted in volatiles) {
351                                 pe = new PreparingEnlistment (this, enlisted);
352
353                                 enlisted.Prepare (pe);
354
355                                 /* FIXME: Where should this timeout value come from? 
356                                    current scope?
357                                    Wait after all Prepare()'s are sent
358                                 pe.WaitHandle.WaitOne (new TimeSpan (0,0,5), true); */
359
360                                 if (!pe.IsPrepared) {
361                                         /* FIXME: if not prepared & !aborted as yet, then 
362                                            this is inDoubt ? . For now, setting aborted = true */
363                                         Aborted = true;
364                                         break;
365                                 }
366                         }
367                         
368                         /* Either InDoubt(tmp) or Prepare failed and
369                            Tx has rolledback */
370                         CheckAborted ();
371                 }
372
373                 void DoCommitPhase ()
374                 {
375                         foreach (IEnlistmentNotification enlisted in volatiles) {
376                                 Enlistment e = new Enlistment ();
377                                 enlisted.Commit (e);
378                                 /* Note: e.Done doesn't matter for volatile RMs */
379                         }
380                 }
381
382                 void DoSingleCommit (ISinglePhaseNotification single)
383                 {
384                         if (single == null)
385                                 return;
386
387                         SinglePhaseEnlistment enlistment = new SinglePhaseEnlistment (this, single);
388                         single.SinglePhaseCommit (enlistment);
389                         CheckAborted ();
390                 }
391
392                 void CheckAborted ()
393                 {
394                         if (aborted)
395                                 throw new TransactionAbortedException ("Transaction has aborted", innerException);
396                 }
397
398                 static void EnsureIncompleteCurrentScope ()
399                 {
400                         if (CurrentInternal == null)
401                                 return;
402                         if (CurrentInternal.Scope != null && CurrentInternal.Scope.IsComplete)
403                                 throw new InvalidOperationException ("The current TransactionScope is already complete");
404                 }
405         }
406 }
407
408 #endif