New test.
[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                 static Transaction ambient;
25
26                 IsolationLevel level;
27                 TransactionInformation info;
28
29                 ArrayList dependents = new ArrayList ();
30
31                 /* Volatile enlistments */
32                 List <IEnlistmentNotification> volatiles = new List <IEnlistmentNotification> ();
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 = new List <ISinglePhaseNotification> ();
40
41                 delegate void AsyncCommit ();
42                 
43                 AsyncCommit asyncCommit = null;
44                 bool committing;
45                 bool committed = false;
46                 bool aborted = false;
47                 TransactionScope scope = null;
48                 
49                 Exception innerException;
50
51                 internal Transaction ()
52                 {
53                         info = new TransactionInformation ();
54                         level = IsolationLevel.Serializable;
55                 }
56
57                 internal Transaction (Transaction other)
58                 {
59                         level = other.level;
60                         info = other.info;
61                         dependents = other.dependents;
62                 }
63
64                 [MonoTODO]
65                 void ISerializable.GetObjectData (SerializationInfo info,
66                         StreamingContext context)
67                 {
68                         throw new NotImplementedException ();
69                 }
70
71                 public event TransactionCompletedEventHandler TransactionCompleted;
72
73                 public static Transaction Current {
74                         get { 
75                                 EnsureIncompleteCurrentScope ();
76                                 return CurrentInternal;
77                         }
78                         set { 
79                                 EnsureIncompleteCurrentScope ();
80                                 CurrentInternal = value;
81                         }
82                 }
83
84                 internal static Transaction CurrentInternal {
85                         get { return ambient; }
86                         set { ambient = value; }
87                 }
88
89                 public IsolationLevel IsolationLevel {
90                         get { 
91                                 EnsureIncompleteCurrentScope ();
92                                 return level; 
93                         }
94                 }
95
96                 public TransactionInformation TransactionInformation {
97                         get { 
98                                 EnsureIncompleteCurrentScope ();
99                                 return info; 
100                         }
101                 }
102
103                 [MonoTODO]
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]
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]
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]
162                 public Enlistment EnlistVolatile (
163                         IEnlistmentNotification notification,
164                         EnlistmentOptions options)
165                 {
166                         return EnlistVolatileInternal (notification, options);
167                 }
168
169                 [MonoTODO]
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                 [MonoTODO]
191                 public override bool Equals (object obj)
192                 {
193                         Transaction t = obj as Transaction;
194                         if (t == null)
195                                 return false;
196                         return this.level == t.level &&
197                                 this.info == t.info;
198                 }
199
200                 [MonoTODO]
201                 public static bool op_Inequality (Transaction x, Transaction y)
202                 {
203                         if (x == null && y == null)
204                                 return false;
205                         if (x == null || y == null)
206                                 return true;
207
208                         return ! x.Equals (y);
209                 }
210
211                 public override int GetHashCode ()
212                 {
213                         return (int) level ^ info.GetHashCode () ^ dependents.GetHashCode ();
214                 }
215
216                 public void Rollback ()
217                 {
218                         Rollback (null);
219                 }
220
221                 public void Rollback (Exception ex)
222                 {
223                         EnsureIncompleteCurrentScope ();
224                         Rollback (ex, null);
225                 }
226
227                 internal void Rollback (Exception ex, IEnlistmentNotification enlisted)
228                 {
229                         if (aborted)
230                                 return;
231
232                         /* See test ExplicitTransaction7 */
233                         if (info.Status == TransactionStatus.Committed)
234                                 throw new TransactionException ("Transaction has already been committed. Cannot accept any new work.");
235
236                         innerException = ex;
237                         Enlistment e = new Enlistment ();
238                         foreach (IEnlistmentNotification prep in volatiles)
239                                 if (prep != enlisted)
240                                         prep.Rollback (e);
241
242                         if (durables.Count > 0 && durables [0] != enlisted)
243                                 durables [0].Rollback (e);
244
245                         Aborted = true;
246                 }
247
248                 bool Aborted {
249                         get { return aborted; }
250                         set {
251                                 aborted = value;
252                                 if (aborted)
253                                         info.Status = TransactionStatus.Aborted;
254                         }
255                 }
256                 
257                 internal TransactionScope Scope {
258                         get { return scope; }
259                         set { scope = value; }
260                 }
261
262                 protected IAsyncResult BeginCommitInternal (AsyncCallback callback)
263                 {
264                         if (committed || committing)
265                                 throw new InvalidOperationException ("Commit has already been called for this transaction.");
266
267                         this.committing = true;
268
269                         asyncCommit = new AsyncCommit (DoCommit);
270                         return asyncCommit.BeginInvoke (callback, null);
271                 }
272
273                 protected void EndCommitInternal (IAsyncResult ar)
274                 {
275                         asyncCommit.EndInvoke (ar);
276                 }
277
278                 internal void CommitInternal ()
279                 {
280                         if (committed || committing)
281                                 throw new InvalidOperationException ("Commit has already been called for this transaction.");
282
283                         this.committing = true;
284
285                         DoCommit ();            
286                 }
287                 
288                 private void DoCommit ()
289                 {
290                         /* Scope becomes null in TransactionScope.Dispose */
291                         if (Scope != null) {
292                                 /* See test ExplicitTransaction8 */
293                                 Rollback (null, null);
294                                 CheckAborted ();
295                         }
296
297                         if (volatiles.Count == 1 && durables.Count == 0) {
298                                 /* Special case */
299                                 ISinglePhaseNotification single = volatiles [0] as ISinglePhaseNotification;
300                                 if (single != null) {
301                                         DoSingleCommit (single);
302                                         Complete ();
303                                         return;
304                                 }
305                         } 
306
307                         if (volatiles.Count > 0)
308                                 DoPreparePhase ();
309
310                         if (durables.Count > 0)
311                                 DoSingleCommit (durables [0]);
312
313                         if (volatiles.Count > 0)
314                                 DoCommitPhase ();
315
316                         Complete ();
317                 }
318
319                 private void Complete ()
320                 {
321                         committing = false;
322                         committed = true;
323
324                         if (!aborted)
325                                 info.Status = TransactionStatus.Committed;
326                 }
327
328                 internal void InitScope (TransactionScope scope)
329                 {
330                         /* See test NestedTransactionScope10 */
331                         CheckAborted ();
332
333                         /* See test ExplicitTransaction6a */
334                         if (committed)
335                                 throw new InvalidOperationException ("Commit has already been called on this transaction."); 
336
337                         Scope = scope;  
338                 }
339
340                 void DoPreparePhase ()
341                 {
342                         PreparingEnlistment pe;
343                         foreach (IEnlistmentNotification enlisted in volatiles) {
344                                 pe = new PreparingEnlistment (this, enlisted);
345
346                                 enlisted.Prepare (pe);
347
348                                 /* FIXME: Where should this timeout value come from? 
349                                    current scope?
350                                    Wait after all Prepare()'s are sent
351                                 pe.WaitHandle.WaitOne (new TimeSpan (0,0,5), true); */
352
353                                 if (!pe.IsPrepared) {
354                                         /* FIXME: if not prepared & !aborted as yet, then 
355                                            this is inDoubt ? . For now, setting aborted = true */
356                                         Aborted = true;
357                                         break;
358                                 }
359                         }
360                         
361                         /* Either InDoubt(tmp) or Prepare failed and
362                            Tx has rolledback */
363                         CheckAborted ();
364                 }
365
366                 void DoCommitPhase ()
367                 {
368                         foreach (IEnlistmentNotification enlisted in volatiles) {
369                                 Enlistment e = new Enlistment ();
370                                 enlisted.Commit (e);
371                                 /* Note: e.Done doesn't matter for volatile RMs */
372                         }
373                 }
374
375                 void DoSingleCommit (ISinglePhaseNotification single)
376                 {
377                         if (single == null)
378                                 return;
379
380                         SinglePhaseEnlistment enlistment = new SinglePhaseEnlistment (this, single);
381                         single.SinglePhaseCommit (enlistment);
382                         CheckAborted ();
383                 }
384
385                 void CheckAborted ()
386                 {
387                         if (aborted)
388                                 throw new TransactionAbortedException ("Transaction has aborted", innerException);
389                 }
390
391                 static void EnsureIncompleteCurrentScope ()
392                 {
393                         if (CurrentInternal == null)
394                                 return;
395                         if (CurrentInternal.Scope != null && CurrentInternal.Scope.IsComplete)
396                                 throw new InvalidOperationException ("The current TransactionScope is already complete");
397                 }
398         }
399 }
400
401 #endif