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                 public Transaction Clone ()
104                 {
105                         return new Transaction (this);
106                 }
107
108                 [MonoTODO]
109                 public void Dispose ()
110                 {
111                         throw new NotImplementedException ();
112                 }
113
114                 [MonoTODO]
115                 public DependentTransaction DependentClone (
116                         DependentCloneOption option)
117                 {
118                         DependentTransaction d = 
119                                 new DependentTransaction (this, option);
120                         dependents.Add (d);
121                         return d;
122                 }
123
124                 [MonoTODO ("Only SinglePhase commit supported for durable resource managers.")]
125                 [PermissionSetAttribute (SecurityAction.LinkDemand)]
126                 public Enlistment EnlistDurable (Guid manager,
127                         IEnlistmentNotification notification,
128                         EnlistmentOptions options)
129                 {
130                         throw new NotImplementedException ("Only SinglePhase commit supported for durable resource managers.");
131                 }
132
133                 [MonoTODO ("Only Local Transaction Manager supported. Cannot have more than 1 durable resource per transaction. Only EnlistmentOptions.None supported yet.")]
134                 [PermissionSetAttribute (SecurityAction.LinkDemand)]
135                 public Enlistment EnlistDurable (Guid manager,
136                         ISinglePhaseNotification notification,
137                         EnlistmentOptions options)
138                 {
139                         if (durables.Count == 1)
140                                 throw new NotImplementedException ("Only LTM supported. Cannot have more than 1 durable resource per transaction.");
141
142                         EnsureIncompleteCurrentScope ();
143
144                         if (options != EnlistmentOptions.None)
145                                 throw new NotImplementedException ("Implement me");
146
147                         durables.Add (notification);
148
149                         /* FIXME: Enlistment ?? */
150                         return new Enlistment ();
151                 }
152
153                 [MonoTODO]
154                 public bool EnlistPromotableSinglePhase (
155                         IPromotableSinglePhaseNotification notification)
156                 {
157                         throw new NotImplementedException ();
158                 }
159
160                 [MonoTODO ("EnlistmentOptions being ignored")]
161                 public Enlistment EnlistVolatile (
162                         IEnlistmentNotification notification,
163                         EnlistmentOptions options)
164                 {
165                         return EnlistVolatileInternal (notification, options);
166                 }
167
168                 [MonoTODO ("EnlistmentOptions being ignored")]
169                 public Enlistment EnlistVolatile (
170                         ISinglePhaseNotification notification,
171                         EnlistmentOptions options)
172                 {
173                         /* FIXME: Anything extra reqd for this? */
174                         return EnlistVolatileInternal (notification, options);
175                 }
176
177                 private Enlistment EnlistVolatileInternal (
178                         IEnlistmentNotification notification,
179                         EnlistmentOptions options)
180                 {
181                         EnsureIncompleteCurrentScope (); 
182                         /* FIXME: Handle options.EnlistDuringPrepareRequired */
183                         volatiles.Add (notification);
184
185                         /* FIXME: Enlistment.. ? */
186                         return new Enlistment ();
187                 }
188
189                 public override bool Equals (object obj)
190                 {
191                         return Equals (obj as Transaction);
192                 }
193
194                 // FIXME: Check whether this is correct (currently, GetHashCode() uses 'dependents' but this doesn't)
195                 private bool Equals (Transaction t)
196                 {
197                         if (ReferenceEquals (t, this))
198                                 return true;
199                         if (ReferenceEquals (t, null))
200                                 return false;
201                         return this.level == t.level &&
202                                 this.info == t.info;
203                 }
204
205                 public static bool operator == (Transaction x, Transaction y)
206                 {
207                         if (ReferenceEquals (x, null))
208                                 return ReferenceEquals (y, null);
209                         return x.Equals (y);
210                 }
211
212                 public static bool operator != (Transaction x, Transaction y)
213                 {
214                         return !(x == y);
215                 }
216
217                 public override int GetHashCode ()
218                 {
219                         return (int) level ^ info.GetHashCode () ^ dependents.GetHashCode ();
220                 }
221
222                 public void Rollback ()
223                 {
224                         Rollback (null);
225                 }
226
227                 public void Rollback (Exception ex)
228                 {
229                         EnsureIncompleteCurrentScope ();
230                         Rollback (ex, null);
231                 }
232
233                 internal void Rollback (Exception ex, IEnlistmentNotification enlisted)
234                 {
235                         if (aborted)
236                                 return;
237
238                         /* See test ExplicitTransaction7 */
239                         if (info.Status == TransactionStatus.Committed)
240                                 throw new TransactionException ("Transaction has already been committed. Cannot accept any new work.");
241
242                         innerException = ex;
243                         Enlistment e = new Enlistment ();
244                         foreach (IEnlistmentNotification prep in volatiles)
245                                 if (prep != enlisted)
246                                         prep.Rollback (e);
247
248                         if (durables.Count > 0 && durables [0] != enlisted)
249                                 durables [0].Rollback (e);
250
251                         Aborted = true;
252                 }
253
254                 bool Aborted {
255                         get { return aborted; }
256                         set {
257                                 aborted = value;
258                                 if (aborted)
259                                         info.Status = TransactionStatus.Aborted;
260                         }
261                 }
262                 
263                 internal TransactionScope Scope {
264                         get { return scope; }
265                         set { scope = value; }
266                 }
267
268                 protected IAsyncResult BeginCommitInternal (AsyncCallback callback)
269                 {
270                         if (committed || committing)
271                                 throw new InvalidOperationException ("Commit has already been called for this transaction.");
272
273                         this.committing = true;
274
275                         asyncCommit = new AsyncCommit (DoCommit);
276                         return asyncCommit.BeginInvoke (callback, null);
277                 }
278
279                 protected void EndCommitInternal (IAsyncResult ar)
280                 {
281                         asyncCommit.EndInvoke (ar);
282                 }
283
284                 internal void CommitInternal ()
285                 {
286                         if (committed || committing)
287                                 throw new InvalidOperationException ("Commit has already been called for this transaction.");
288
289                         this.committing = true;
290
291                         DoCommit ();            
292                 }
293                 
294                 private void DoCommit ()
295                 {
296                         /* Scope becomes null in TransactionScope.Dispose */
297                         if (Scope != null) {
298                                 /* See test ExplicitTransaction8 */
299                                 Rollback (null, null);
300                                 CheckAborted ();
301                         }
302
303                         if (volatiles.Count == 1 && durables.Count == 0) {
304                                 /* Special case */
305                                 ISinglePhaseNotification single = volatiles [0] as ISinglePhaseNotification;
306                                 if (single != null) {
307                                         DoSingleCommit (single);
308                                         Complete ();
309                                         return;
310                                 }
311                         } 
312
313                         if (volatiles.Count > 0)
314                                 DoPreparePhase ();
315
316                         if (durables.Count > 0)
317                                 DoSingleCommit (durables [0]);
318
319                         if (volatiles.Count > 0)
320                                 DoCommitPhase ();
321
322                         Complete ();
323                 }
324
325                 private void Complete ()
326                 {
327                         committing = false;
328                         committed = true;
329
330                         if (!aborted)
331                                 info.Status = TransactionStatus.Committed;
332                 }
333
334                 internal void InitScope (TransactionScope scope)
335                 {
336                         /* See test NestedTransactionScope10 */
337                         CheckAborted ();
338
339                         /* See test ExplicitTransaction6a */
340                         if (committed)
341                                 throw new InvalidOperationException ("Commit has already been called on this transaction."); 
342
343                         Scope = scope;  
344                 }
345
346                 void DoPreparePhase ()
347                 {
348                         PreparingEnlistment pe;
349                         foreach (IEnlistmentNotification enlisted in volatiles) {
350                                 pe = new PreparingEnlistment (this, enlisted);
351
352                                 enlisted.Prepare (pe);
353
354                                 /* FIXME: Where should this timeout value come from? 
355                                    current scope?
356                                    Wait after all Prepare()'s are sent
357                                 pe.WaitHandle.WaitOne (new TimeSpan (0,0,5), true); */
358
359                                 if (!pe.IsPrepared) {
360                                         /* FIXME: if not prepared & !aborted as yet, then 
361                                            this is inDoubt ? . For now, setting aborted = true */
362                                         Aborted = true;
363                                         break;
364                                 }
365                         }
366                         
367                         /* Either InDoubt(tmp) or Prepare failed and
368                            Tx has rolledback */
369                         CheckAborted ();
370                 }
371
372                 void DoCommitPhase ()
373                 {
374                         foreach (IEnlistmentNotification enlisted in volatiles) {
375                                 Enlistment e = new Enlistment ();
376                                 enlisted.Commit (e);
377                                 /* Note: e.Done doesn't matter for volatile RMs */
378                         }
379                 }
380
381                 void DoSingleCommit (ISinglePhaseNotification single)
382                 {
383                         if (single == null)
384                                 return;
385
386                         SinglePhaseEnlistment enlistment = new SinglePhaseEnlistment (this, single);
387                         single.SinglePhaseCommit (enlistment);
388                         CheckAborted ();
389                 }
390
391                 void CheckAborted ()
392                 {
393                         if (aborted)
394                                 throw new TransactionAbortedException ("Transaction has aborted", innerException);
395                 }
396
397                 static void EnsureIncompleteCurrentScope ()
398                 {
399                         if (CurrentInternal == null)
400                                 return;
401                         if (CurrentInternal.Scope != null && CurrentInternal.Scope.IsComplete)
402                                 throw new InvalidOperationException ("The current TransactionScope is already complete");
403                 }
404         }
405 }
406
407 #endif