5 // Atsushi Enomoto <atsushi@ximian.com>
6 // Ankit Jain <JAnkit@novell.com>
12 using System.Collections;
13 using System.Collections.Generic;
14 using System.Security.Permissions;
15 using System.Runtime.Serialization;
16 using System.Threading;
18 namespace System.Transactions
21 public class Transaction : IDisposable, ISerializable
24 static Transaction ambient;
27 TransactionInformation info;
29 ArrayList dependents = new ArrayList ();
31 /* Volatile enlistments */
32 List <IEnlistmentNotification> volatiles;
34 /* Durable enlistments
35 Durable RMs can also have 2 Phase commit but
36 not in LTM, and that is what we are supporting
39 List <ISinglePhaseNotification> durables;
41 IPromotableSinglePhaseNotification pspe = null;
43 delegate void AsyncCommit ();
45 AsyncCommit asyncCommit = null;
47 bool committed = false;
49 TransactionScope scope = null;
51 Exception innerException;
52 Guid tag = Guid.NewGuid ();
54 internal List <IEnlistmentNotification> Volatiles {
56 if (volatiles == null)
57 volatiles = new List <IEnlistmentNotification> ();
62 internal List <ISinglePhaseNotification> Durables {
65 durables = new List <ISinglePhaseNotification> ();
70 internal IPromotableSinglePhaseNotification Pspe { get { return pspe; } }
72 internal Transaction ()
74 info = new TransactionInformation ();
75 level = IsolationLevel.Serializable;
78 internal Transaction (Transaction other)
82 dependents = other.dependents;
83 volatiles = other.Volatiles;
84 durables = other.Durables;
89 void ISerializable.GetObjectData (SerializationInfo info,
90 StreamingContext context)
92 throw new NotImplementedException ();
95 public event TransactionCompletedEventHandler TransactionCompleted;
97 public static Transaction Current {
99 EnsureIncompleteCurrentScope ();
100 return CurrentInternal;
103 EnsureIncompleteCurrentScope ();
104 CurrentInternal = value;
108 internal static Transaction CurrentInternal {
109 get { return ambient; }
110 set { ambient = value; }
113 public IsolationLevel IsolationLevel {
115 EnsureIncompleteCurrentScope ();
120 public TransactionInformation TransactionInformation {
122 EnsureIncompleteCurrentScope ();
127 public Transaction Clone ()
129 return new Transaction (this);
132 public void Dispose ()
134 if (TransactionInformation.Status == TransactionStatus.Active)
139 public DependentTransaction DependentClone (
140 DependentCloneOption option)
142 DependentTransaction d =
143 new DependentTransaction (this, option);
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)
154 throw new NotImplementedException ("DTC unsupported, only SinglePhase commit supported for durable resource managers.");
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)
163 EnsureIncompleteCurrentScope ();
164 if (pspe != null || Durables.Count > 0)
165 throw new NotImplementedException ("DTC unsupported, multiple durable resource managers aren't supported.");
167 if (options != EnlistmentOptions.None)
168 throw new NotImplementedException ("EnlistmentOptions other than None aren't supported");
170 Durables.Add (notification);
172 /* FIXME: Enlistment ?? */
173 return new Enlistment ();
176 public bool EnlistPromotableSinglePhase (
177 IPromotableSinglePhaseNotification notification)
179 EnsureIncompleteCurrentScope ();
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)
192 public void SetDistributedTransactionIdentifier (IPromotableSinglePhaseNotification promotableNotification, Guid distributedTransactionIdentifier)
194 throw new NotImplementedException ();
197 public bool EnlistPromotableSinglePhase (IPromotableSinglePhaseNotification promotableSinglePhaseNotification, Guid promoterType)
199 throw new NotImplementedException ();
202 public byte[] GetPromotedToken ()
204 throw new NotImplementedException ();
207 public Guid PromoterType
209 get { throw new NotImplementedException (); }
212 [MonoTODO ("EnlistmentOptions being ignored")]
213 public Enlistment EnlistVolatile (
214 IEnlistmentNotification notification,
215 EnlistmentOptions options)
217 return EnlistVolatileInternal (notification, options);
220 [MonoTODO ("EnlistmentOptions being ignored")]
221 public Enlistment EnlistVolatile (
222 ISinglePhaseNotification notification,
223 EnlistmentOptions options)
225 /* FIXME: Anything extra reqd for this? */
226 return EnlistVolatileInternal (notification, options);
229 private Enlistment EnlistVolatileInternal (
230 IEnlistmentNotification notification,
231 EnlistmentOptions options)
233 EnsureIncompleteCurrentScope ();
234 /* FIXME: Handle options.EnlistDuringPrepareRequired */
235 Volatiles.Add (notification);
237 /* FIXME: Enlistment.. ? */
238 return new Enlistment ();
241 [MonoTODO ("Only Local Transaction Manager supported. Cannot have more than 1 durable resource per transaction.")]
242 [PermissionSetAttribute (SecurityAction.LinkDemand)]
243 public Enlistment PromoteAndEnlistDurable (
245 IPromotableSinglePhaseNotification promotableNotification,
246 ISinglePhaseNotification notification,
247 EnlistmentOptions options)
249 throw new NotImplementedException ("DTC unsupported, multiple durable resource managers aren't supported.");
252 public override bool Equals (object obj)
254 return Equals (obj as Transaction);
257 // FIXME: Check whether this is correct (currently, GetHashCode() uses 'dependents' but this doesn't)
258 private bool Equals (Transaction t)
260 if (ReferenceEquals (t, this))
262 if (ReferenceEquals (t, null))
264 return this.level == t.level &&
268 public static bool operator == (Transaction x, Transaction y)
270 if (ReferenceEquals (x, null))
271 return ReferenceEquals (y, null);
275 public static bool operator != (Transaction x, Transaction y)
280 public override int GetHashCode ()
282 return (int) level ^ info.GetHashCode () ^ dependents.GetHashCode ();
285 public void Rollback ()
290 public void Rollback (Exception ex)
292 EnsureIncompleteCurrentScope ();
296 internal void Rollback (Exception ex, object abortingEnlisted)
304 /* See test ExplicitTransaction7 */
305 if (info.Status == TransactionStatus.Committed)
306 throw new TransactionException ("Transaction has already been committed. Cannot accept any new work.");
308 // Save thrown exception as 'reason' of transaction's abort.
311 SinglePhaseEnlistment e = new SinglePhaseEnlistment();
312 foreach (IEnlistmentNotification prep in Volatiles)
313 if (prep != abortingEnlisted)
316 var durables = Durables;
317 if (durables.Count > 0 && durables [0] != abortingEnlisted)
318 durables [0].Rollback (e);
320 if (pspe != null && pspe != abortingEnlisted)
329 get { return aborted; }
333 info.Status = TransactionStatus.Aborted;
337 internal TransactionScope Scope {
338 get { return scope; }
339 set { scope = value; }
342 protected IAsyncResult BeginCommitInternal (AsyncCallback callback)
344 if (committed || committing)
345 throw new InvalidOperationException ("Commit has already been called for this transaction.");
347 this.committing = true;
349 asyncCommit = new AsyncCommit (DoCommit);
350 return asyncCommit.BeginInvoke (callback, null);
353 protected void EndCommitInternal (IAsyncResult ar)
355 asyncCommit.EndInvoke (ar);
358 internal void CommitInternal ()
360 if (committed || committing)
361 throw new InvalidOperationException ("Commit has already been called for this transaction.");
363 this.committing = true;
368 catch (TransactionException)
374 throw new TransactionAbortedException("Transaction failed", ex);
378 private void DoCommit ()
380 /* Scope becomes null in TransactionScope.Dispose */
382 /* See test ExplicitTransaction8 */
383 Rollback (null, null);
387 var volatiles = Volatiles;
388 var durables = Durables;
389 if (volatiles.Count == 1 && durables.Count == 0)
392 ISinglePhaseNotification single = volatiles[0] as ISinglePhaseNotification;
395 DoSingleCommit(single);
401 if (volatiles.Count > 0)
404 if (durables.Count > 0)
405 DoSingleCommit(durables[0]);
408 DoSingleCommit(pspe);
410 if (volatiles.Count > 0)
416 private void Complete ()
422 info.Status = TransactionStatus.Committed;
427 internal void InitScope (TransactionScope scope)
429 /* See test NestedTransactionScope10 */
432 /* See test ExplicitTransaction6a */
434 throw new InvalidOperationException ("Commit has already been called on this transaction.");
439 static void PrepareCallbackWrapper(object state)
441 PreparingEnlistment enlist = state as PreparingEnlistment;
445 enlist.EnlistmentNotification.Prepare(enlist);
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;
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();
464 void DoPreparePhase ()
466 // Call prepare on all volatile managers.
467 foreach (IEnlistmentNotification enlist in Volatiles)
469 PreparingEnlistment pe = new PreparingEnlistment (this, enlist);
470 ThreadPool.QueueUserWorkItem (new WaitCallback(PrepareCallbackWrapper), pe);
472 /* Wait (with timeout) for manager to prepare */
473 TimeSpan timeout = Scope != null ? Scope.Timeout : TransactionManager.DefaultTimeout;
475 // FIXME: Should we managers in parallel or on-by-one?
476 if (!pe.WaitHandle.WaitOne(timeout, true))
479 throw new TimeoutException("Transaction timedout");
482 if (pe.Exception != null)
484 innerException = pe.Exception;
491 /* FIXME: if not prepared & !aborted as yet, then
492 this is inDoubt ? . For now, setting aborted = true */
498 /* Either InDoubt(tmp) or Prepare failed and
503 void DoCommitPhase ()
505 foreach (IEnlistmentNotification enlisted in Volatiles) {
506 Enlistment e = new Enlistment ();
508 /* Note: e.Done doesn't matter for volatile RMs */
512 void DoSingleCommit (ISinglePhaseNotification single)
517 single.SinglePhaseCommit (new SinglePhaseEnlistment (this, single));
521 void DoSingleCommit (IPromotableSinglePhaseNotification single)
526 single.SinglePhaseCommit (new SinglePhaseEnlistment (this, single));
533 throw new TransactionAbortedException ("Transaction has aborted", innerException);
536 void FireCompleted ()
538 if (TransactionCompleted != null)
539 TransactionCompleted (this, new TransactionEventArgs(this));
542 static void EnsureIncompleteCurrentScope ()
544 if (CurrentInternal == null)
546 if (CurrentInternal.Scope != null && CurrentInternal.Scope.IsComplete)
547 throw new InvalidOperationException ("The current TransactionScope is already complete");