[bcl] Enable tests for the monodroid profile.
[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 using System.Collections;
13 using System.Collections.Generic;
14 using System.Security.Permissions;
15 using System.Runtime.Serialization;
16 using System.Threading;
17
18 namespace System.Transactions
19 {
20         [Serializable]
21         public class Transaction : IDisposable, ISerializable
22         {
23                 [ThreadStatic]
24                 static Transaction ambient;
25
26                 IsolationLevel level;
27                 TransactionInformation info;
28
29                 ArrayList dependents = new ArrayList ();
30
31                 /* Volatile enlistments */
32                 List <IEnlistmentNotification> volatiles;
33
34                 /* Durable enlistments 
35                    Durable RMs can also have 2 Phase commit but
36                    not in LTM, and that is what we are supporting
37                    right now   
38                  */
39                 List <ISinglePhaseNotification> durables;
40
41                 IPromotableSinglePhaseNotification pspe = null;
42
43                 delegate void AsyncCommit ();
44                 
45                 AsyncCommit asyncCommit = null;
46                 bool committing;
47                 bool committed = false;
48                 bool aborted = false;
49                 TransactionScope scope = null;
50
51                 Exception innerException;
52                 Guid tag = Guid.NewGuid ();
53
54                 internal List <IEnlistmentNotification> Volatiles {
55                         get {
56                                 if (volatiles == null)
57                                         volatiles = new List <IEnlistmentNotification> ();
58                                 return volatiles;
59                         }
60                 }
61
62                 internal List <ISinglePhaseNotification> Durables {
63                         get {
64                                 if (durables == null)
65                                         durables = new List <ISinglePhaseNotification> ();
66                                 return durables;
67                         }
68                 }
69
70                 internal IPromotableSinglePhaseNotification Pspe { get { return pspe; } }
71
72                 internal Transaction ()
73                 {
74                         info = new TransactionInformation ();
75                         level = IsolationLevel.Serializable;
76                 }
77
78                 internal Transaction (Transaction other)
79                 {
80                         level = other.level;
81                         info = other.info;
82                         dependents = other.dependents;
83                         volatiles = other.Volatiles;
84                         durables = other.Durables;
85                         pspe = other.Pspe;
86                 }
87
88                 [MonoTODO]
89                 void ISerializable.GetObjectData (SerializationInfo info,
90                         StreamingContext context)
91                 {
92                         throw new NotImplementedException ();
93                 }
94
95                 public event TransactionCompletedEventHandler TransactionCompleted;
96
97                 public static Transaction Current {
98                         get { 
99                                 EnsureIncompleteCurrentScope ();
100                                 return CurrentInternal;
101                         }
102                         set { 
103                                 EnsureIncompleteCurrentScope ();
104                                 CurrentInternal = value;
105                         }
106                 }
107
108                 internal static Transaction CurrentInternal {
109                         get { return ambient; }
110                         set { ambient = value; }
111                 }
112
113                 public IsolationLevel IsolationLevel {
114                         get { 
115                                 EnsureIncompleteCurrentScope ();
116                                 return level; 
117                         }
118                 }
119
120                 public TransactionInformation TransactionInformation {
121                         get { 
122                                 EnsureIncompleteCurrentScope ();
123                                 return info; 
124                         }
125                 }
126
127                 public Transaction Clone ()
128                 {
129                         return new Transaction (this);
130                 }
131
132                 public void Dispose ()
133                 {
134                         if (TransactionInformation.Status == TransactionStatus.Active)
135                                 Rollback();
136                 }
137
138                 [MonoTODO]
139                 public DependentTransaction DependentClone (
140                         DependentCloneOption option)
141                 {
142                         DependentTransaction d = 
143                                 new DependentTransaction (this, option);
144                         dependents.Add (d);
145                         return d;
146                 }
147
148                 [MonoTODO ("Only SinglePhase commit supported for durable resource managers.")]
149                 [PermissionSetAttribute (SecurityAction.LinkDemand)]
150                 public Enlistment EnlistDurable (Guid manager,
151                         IEnlistmentNotification notification,
152                         EnlistmentOptions options)
153                 {
154                         throw new NotImplementedException ("DTC unsupported, only SinglePhase commit supported for durable resource managers.");
155                 }
156
157                 [MonoTODO ("Only Local Transaction Manager supported. Cannot have more than 1 durable resource per transaction. Only EnlistmentOptions.None supported yet.")]
158                 [PermissionSetAttribute (SecurityAction.LinkDemand)]
159                 public Enlistment EnlistDurable (Guid manager,
160                         ISinglePhaseNotification notification,
161                         EnlistmentOptions options)
162                 {
163                         EnsureIncompleteCurrentScope ();
164                         if (pspe != null || Durables.Count > 0)
165                                 throw new NotImplementedException ("DTC unsupported, multiple durable resource managers aren't supported.");
166
167                         if (options != EnlistmentOptions.None)
168                                 throw new NotImplementedException ("EnlistmentOptions other than None aren't supported");
169
170                         Durables.Add (notification);
171
172                         /* FIXME: Enlistment ?? */
173                         return new Enlistment ();
174                 }
175
176                 public bool EnlistPromotableSinglePhase (
177                         IPromotableSinglePhaseNotification notification)
178                 {
179                         EnsureIncompleteCurrentScope ();
180
181                         // The specs aren't entirely clear on whether we can have volatile RMs along with a PSPE, but
182                         // I'm assuming that yes based on: http://social.msdn.microsoft.com/Forums/br/windowstransactionsprogramming/thread/3df6d4d3-0d82-47c4-951a-cd31140950b3
183                         if (pspe != null || Durables.Count > 0)
184                                 return false;
185
186                         pspe = notification;
187                         pspe.Initialize();
188
189                         return true;
190                 }
191
192                 public void SetDistributedTransactionIdentifier (IPromotableSinglePhaseNotification promotableNotification, Guid distributedTransactionIdentifier)
193                 {
194                         throw new NotImplementedException ();
195                 }
196
197                 public bool EnlistPromotableSinglePhase (IPromotableSinglePhaseNotification promotableSinglePhaseNotification, Guid promoterType)
198                 {
199                         throw new NotImplementedException ();
200                 }
201
202                 public byte[] GetPromotedToken ()
203                 {
204                         throw new NotImplementedException ();
205                 }
206
207                 public Guid PromoterType
208                 {
209                         get { throw new NotImplementedException (); }
210                 }
211
212                 [MonoTODO ("EnlistmentOptions being ignored")]
213                 public Enlistment EnlistVolatile (
214                         IEnlistmentNotification notification,
215                         EnlistmentOptions options)
216                 {
217                         return EnlistVolatileInternal (notification, options);
218                 }
219
220                 [MonoTODO ("EnlistmentOptions being ignored")]
221                 public Enlistment EnlistVolatile (
222                         ISinglePhaseNotification notification,
223                         EnlistmentOptions options)
224                 {
225                         /* FIXME: Anything extra reqd for this? */
226                         return EnlistVolatileInternal (notification, options);
227                 }
228
229                 private Enlistment EnlistVolatileInternal (
230                         IEnlistmentNotification notification,
231                         EnlistmentOptions options)
232                 {
233                         EnsureIncompleteCurrentScope (); 
234                         /* FIXME: Handle options.EnlistDuringPrepareRequired */
235                         Volatiles.Add (notification);
236
237                         /* FIXME: Enlistment.. ? */
238                         return new Enlistment ();
239                 }
240
241                 [MonoTODO ("Only Local Transaction Manager supported. Cannot have more than 1 durable resource per transaction.")]
242                 [PermissionSetAttribute (SecurityAction.LinkDemand)]
243                 public Enlistment PromoteAndEnlistDurable (
244                         Guid manager,
245                         IPromotableSinglePhaseNotification promotableNotification,
246                         ISinglePhaseNotification notification,
247                         EnlistmentOptions options)
248                 {
249                         throw new NotImplementedException ("DTC unsupported, multiple durable resource managers aren't supported.");
250                 }
251
252                 public override bool Equals (object obj)
253                 {
254                         return Equals (obj as Transaction);
255                 }
256
257                 // FIXME: Check whether this is correct (currently, GetHashCode() uses 'dependents' but this doesn't)
258                 private bool Equals (Transaction t)
259                 {
260                         if (ReferenceEquals (t, this))
261                                 return true;
262                         if (ReferenceEquals (t, null))
263                                 return false;
264                         return this.level == t.level &&
265                                 this.info == t.info;
266                 }
267
268                 public static bool operator == (Transaction x, Transaction y)
269                 {
270                         if (ReferenceEquals (x, null))
271                                 return ReferenceEquals (y, null);
272                         return x.Equals (y);
273                 }
274
275                 public static bool operator != (Transaction x, Transaction y)
276                 {
277                         return !(x == y);
278                 }
279
280                 public override int GetHashCode ()
281                 {
282                         return (int) level ^ info.GetHashCode () ^ dependents.GetHashCode ();
283                 }
284
285                 public void Rollback ()
286                 {
287                         Rollback (null);
288                 }
289
290                 public void Rollback (Exception ex)
291                 {
292                         EnsureIncompleteCurrentScope ();
293                         Rollback (ex, null);
294                 }
295
296                 internal void Rollback (Exception ex, object abortingEnlisted)
297                 {
298                         if (aborted)
299                         {
300                                 FireCompleted ();
301                                 return;
302                         }
303
304                         /* See test ExplicitTransaction7 */
305                         if (info.Status == TransactionStatus.Committed)
306                                 throw new TransactionException ("Transaction has already been committed. Cannot accept any new work.");
307
308                         // Save thrown exception as 'reason' of transaction's abort.
309                         innerException = ex;
310
311                         SinglePhaseEnlistment e = new SinglePhaseEnlistment();
312                         foreach (IEnlistmentNotification prep in Volatiles)
313                                 if (prep != abortingEnlisted)
314                                         prep.Rollback (e);
315
316                         var durables = Durables;
317                         if (durables.Count > 0 && durables [0] != abortingEnlisted)
318                                 durables [0].Rollback (e);
319
320                         if (pspe != null && pspe != abortingEnlisted)
321                                 pspe.Rollback (e);
322
323                         Aborted = true;
324
325                         FireCompleted ();
326                 }
327
328                 bool Aborted {
329                         get { return aborted; }
330                         set {
331                                 aborted = value;
332                                 if (aborted)
333                                         info.Status = TransactionStatus.Aborted;
334                         }
335                 }
336                 
337                 internal TransactionScope Scope {
338                         get { return scope; }
339                         set { scope = value; }
340                 }
341
342                 protected IAsyncResult BeginCommitInternal (AsyncCallback callback)
343                 {
344                         if (committed || committing)
345                                 throw new InvalidOperationException ("Commit has already been called for this transaction.");
346
347                         this.committing = true;
348
349                         asyncCommit = new AsyncCommit (DoCommit);
350                         return asyncCommit.BeginInvoke (callback, null);
351                 }
352
353                 protected void EndCommitInternal (IAsyncResult ar)
354                 {
355                         asyncCommit.EndInvoke (ar);
356                 }
357
358                 internal void CommitInternal ()
359                 {
360                         if (committed || committing)
361                                 throw new InvalidOperationException ("Commit has already been called for this transaction.");
362
363                         this.committing = true;
364
365                         try {
366                                 DoCommit ();
367                         }
368                         catch (TransactionException)
369                         {
370                                 throw;
371                         }
372                         catch (Exception ex)
373                         {
374                                 throw new TransactionAbortedException("Transaction failed", ex);
375                         }
376                 }
377                 
378                 private void DoCommit ()
379                 {
380                         /* Scope becomes null in TransactionScope.Dispose */
381                         if (Scope != null) {
382                                 /* See test ExplicitTransaction8 */
383                                 Rollback (null, null);
384                                 CheckAborted ();
385                         }
386
387                         var volatiles = Volatiles;
388                         var durables = Durables;
389                         if (volatiles.Count == 1 && durables.Count == 0)
390                         {
391                                 /* Special case */
392                                 ISinglePhaseNotification single = volatiles[0] as ISinglePhaseNotification;
393                                 if (single != null)
394                                 {
395                                         DoSingleCommit(single);
396                                         Complete();
397                                         return;
398                                 }
399                         }
400
401                         if (volatiles.Count > 0)
402                                 DoPreparePhase();
403
404                         if (durables.Count > 0)
405                                 DoSingleCommit(durables[0]);
406
407                         if (pspe != null)
408                                 DoSingleCommit(pspe);
409
410                         if (volatiles.Count > 0)
411                                 DoCommitPhase();
412                         
413                         Complete();
414                 }
415
416                 private void Complete ()
417                 {
418                         committing = false;
419                         committed = true;
420
421                         if (!aborted)
422                                 info.Status = TransactionStatus.Committed;
423
424                         FireCompleted ();
425                 }
426
427                 internal void InitScope (TransactionScope scope)
428                 {
429                         /* See test NestedTransactionScope10 */
430                         CheckAborted ();
431
432                         /* See test ExplicitTransaction6a */
433                         if (committed)
434                                 throw new InvalidOperationException ("Commit has already been called on this transaction."); 
435
436                         Scope = scope;  
437                 }
438
439                 static void PrepareCallbackWrapper(object state)
440                 {
441                         PreparingEnlistment enlist = state as PreparingEnlistment;
442
443                         try
444                         {
445                                 enlist.EnlistmentNotification.Prepare(enlist);
446                         }
447                         catch (Exception ex)
448                         {
449                                 // Oops! Unhandled exception.. we should notify
450                                 // to our caller thread that preparing has failed.
451                                 // This usually happends when an exception is
452                                 // thrown by code enlistment.Rollback() methods
453                                 // executed inside prepare.ForceRollback(ex).
454                                 enlist.Exception = ex;
455
456                                 // Just in case enlistment did not call Prepared()
457                                 // we need to manually set WH to avoid transaction
458                                 // from failing due to transaction timeout.
459                                 if (!enlist.IsPrepared)
460                                         ((ManualResetEvent)enlist.WaitHandle).Set();
461                         }
462                 }
463
464                 void DoPreparePhase ()
465                 {
466                         // Call prepare on all volatile managers.
467                         foreach (IEnlistmentNotification enlist in Volatiles)
468                         {
469                                 PreparingEnlistment pe = new PreparingEnlistment (this, enlist);
470                                 ThreadPool.QueueUserWorkItem (new WaitCallback(PrepareCallbackWrapper), pe);
471
472                                 /* Wait (with timeout) for manager to prepare */
473                                 TimeSpan timeout = Scope != null ? Scope.Timeout : TransactionManager.DefaultTimeout;
474
475                                 // FIXME: Should we managers in parallel or on-by-one?
476                                 if (!pe.WaitHandle.WaitOne(timeout, true))
477                                 {
478                                         this.Aborted = true;
479                                         throw new TimeoutException("Transaction timedout");
480                                 }
481
482                                 if (pe.Exception != null)
483                                 {
484                                         innerException = pe.Exception;
485                                         Aborted = true;
486                                         break;
487                                 }
488
489                                 if (!pe.IsPrepared)
490                                 {
491                                         /* FIXME: if not prepared & !aborted as yet, then 
492                                                 this is inDoubt ? . For now, setting aborted = true */
493                                         Aborted = true;
494                                         break;
495                                 }
496                         }                       
497                         
498                         /* Either InDoubt(tmp) or Prepare failed and
499                            Tx has rolledback */
500                         CheckAborted ();
501                 }
502
503                 void DoCommitPhase ()
504                 {
505                         foreach (IEnlistmentNotification enlisted in Volatiles) {
506                                 Enlistment e = new Enlistment ();
507                                 enlisted.Commit (e);
508                                 /* Note: e.Done doesn't matter for volatile RMs */
509                         }
510                 }
511
512                 void DoSingleCommit (ISinglePhaseNotification single)
513                 {
514                         if (single == null)
515                                 return;
516
517                         single.SinglePhaseCommit (new SinglePhaseEnlistment (this, single));
518                         CheckAborted ();
519                 }
520
521                 void DoSingleCommit (IPromotableSinglePhaseNotification single)
522                 {
523                         if (single == null)
524                                 return;
525
526                         single.SinglePhaseCommit (new SinglePhaseEnlistment (this, single));
527                         CheckAborted ();
528                 }
529
530                 void CheckAborted ()
531                 {
532                         if (aborted)
533                                 throw new TransactionAbortedException ("Transaction has aborted", innerException);
534                 }
535
536                 void FireCompleted ()
537                 {
538                         if (TransactionCompleted != null)
539                                 TransactionCompleted (this, new TransactionEventArgs(this));
540                 }
541
542                 static void EnsureIncompleteCurrentScope ()
543                 {
544                         if (CurrentInternal == null)
545                                 return;
546                         if (CurrentInternal.Scope != null && CurrentInternal.Scope.IsComplete)
547                                 throw new InvalidOperationException ("The current TransactionScope is already complete");
548                 }
549   }
550 }
551