5 // Atsushi Enomoto <atsushi@ximian.com>
6 // Ankit Jain <JAnkit@novell.com>
13 using System.Collections;
14 using System.Collections.Generic;
15 using System.Security.Permissions;
16 using System.Runtime.Serialization;
17 using System.Threading;
19 namespace System.Transactions
22 public class Transaction : IDisposable, ISerializable
25 static Transaction ambient;
28 TransactionInformation info;
30 ArrayList dependents = new ArrayList ();
32 /* Volatile enlistments */
33 List <IEnlistmentNotification> volatiles = new List <IEnlistmentNotification> ();
35 /* Durable enlistments
36 Durable RMs can also have 2 Phase commit but
37 not in LTM, and that is what we are supporting
40 List <ISinglePhaseNotification> durables = new List <ISinglePhaseNotification> ();
42 delegate void AsyncCommit ();
44 AsyncCommit asyncCommit = null;
46 bool committed = false;
48 TransactionScope scope = null;
50 Exception innerException;
52 internal Transaction ()
54 info = new TransactionInformation ();
55 level = IsolationLevel.Serializable;
58 internal Transaction (Transaction other)
62 dependents = other.dependents;
66 void ISerializable.GetObjectData (SerializationInfo info,
67 StreamingContext context)
69 throw new NotImplementedException ();
72 public event TransactionCompletedEventHandler TransactionCompleted;
74 public static Transaction Current {
76 EnsureIncompleteCurrentScope ();
77 return CurrentInternal;
80 EnsureIncompleteCurrentScope ();
81 CurrentInternal = value;
85 internal static Transaction CurrentInternal {
86 get { return ambient; }
87 set { ambient = value; }
90 public IsolationLevel IsolationLevel {
92 EnsureIncompleteCurrentScope ();
97 public TransactionInformation TransactionInformation {
99 EnsureIncompleteCurrentScope ();
104 public Transaction Clone ()
106 return new Transaction (this);
110 public void Dispose ()
112 throw new NotImplementedException ();
116 public DependentTransaction DependentClone (
117 DependentCloneOption option)
119 DependentTransaction d =
120 new DependentTransaction (this, option);
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)
131 throw new NotImplementedException ("Only SinglePhase commit supported for durable resource managers.");
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)
140 if (durables.Count == 1)
141 throw new NotImplementedException ("Only LTM supported. Cannot have more than 1 durable resource per transaction.");
143 EnsureIncompleteCurrentScope ();
145 if (options != EnlistmentOptions.None)
146 throw new NotImplementedException ("Implement me");
148 durables.Add (notification);
150 /* FIXME: Enlistment ?? */
151 return new Enlistment ();
155 public bool EnlistPromotableSinglePhase (
156 IPromotableSinglePhaseNotification notification)
158 throw new NotImplementedException ();
161 [MonoTODO ("EnlistmentOptions being ignored")]
162 public Enlistment EnlistVolatile (
163 IEnlistmentNotification notification,
164 EnlistmentOptions options)
166 return EnlistVolatileInternal (notification, options);
169 [MonoTODO ("EnlistmentOptions being ignored")]
170 public Enlistment EnlistVolatile (
171 ISinglePhaseNotification notification,
172 EnlistmentOptions options)
174 /* FIXME: Anything extra reqd for this? */
175 return EnlistVolatileInternal (notification, options);
178 private Enlistment EnlistVolatileInternal (
179 IEnlistmentNotification notification,
180 EnlistmentOptions options)
182 EnsureIncompleteCurrentScope ();
183 /* FIXME: Handle options.EnlistDuringPrepareRequired */
184 volatiles.Add (notification);
186 /* FIXME: Enlistment.. ? */
187 return new Enlistment ();
190 public override bool Equals (object obj)
192 return Equals (obj as Transaction);
195 // FIXME: Check whether this is correct (currently, GetHashCode() uses 'dependents' but this doesn't)
196 private bool Equals (Transaction t)
198 if (ReferenceEquals (t, this))
200 if (ReferenceEquals (t, null))
202 return this.level == t.level &&
206 public static bool operator == (Transaction x, Transaction y)
208 if (ReferenceEquals (x, null))
209 return ReferenceEquals (y, null);
213 public static bool operator != (Transaction x, Transaction y)
218 public override int GetHashCode ()
220 return (int) level ^ info.GetHashCode () ^ dependents.GetHashCode ();
223 public void Rollback ()
228 public void Rollback (Exception ex)
230 EnsureIncompleteCurrentScope ();
234 internal void Rollback (Exception ex, IEnlistmentNotification enlisted)
239 /* See test ExplicitTransaction7 */
240 if (info.Status == TransactionStatus.Committed)
241 throw new TransactionException ("Transaction has already been committed. Cannot accept any new work.");
244 Enlistment e = new Enlistment ();
245 foreach (IEnlistmentNotification prep in volatiles)
246 if (prep != enlisted)
249 if (durables.Count > 0 && durables [0] != enlisted)
250 durables [0].Rollback (e);
256 get { return aborted; }
260 info.Status = TransactionStatus.Aborted;
264 internal TransactionScope Scope {
265 get { return scope; }
266 set { scope = value; }
269 protected IAsyncResult BeginCommitInternal (AsyncCallback callback)
271 if (committed || committing)
272 throw new InvalidOperationException ("Commit has already been called for this transaction.");
274 this.committing = true;
276 asyncCommit = new AsyncCommit (DoCommit);
277 return asyncCommit.BeginInvoke (callback, null);
280 protected void EndCommitInternal (IAsyncResult ar)
282 asyncCommit.EndInvoke (ar);
285 internal void CommitInternal ()
287 if (committed || committing)
288 throw new InvalidOperationException ("Commit has already been called for this transaction.");
290 this.committing = true;
295 private void DoCommit ()
297 /* Scope becomes null in TransactionScope.Dispose */
299 /* See test ExplicitTransaction8 */
300 Rollback (null, null);
304 if (volatiles.Count == 1 && durables.Count == 0) {
306 ISinglePhaseNotification single = volatiles [0] as ISinglePhaseNotification;
307 if (single != null) {
308 DoSingleCommit (single);
314 if (volatiles.Count > 0)
317 if (durables.Count > 0)
318 DoSingleCommit (durables [0]);
320 if (volatiles.Count > 0)
326 private void Complete ()
332 info.Status = TransactionStatus.Committed;
335 internal void InitScope (TransactionScope scope)
337 /* See test NestedTransactionScope10 */
340 /* See test ExplicitTransaction6a */
342 throw new InvalidOperationException ("Commit has already been called on this transaction.");
347 void DoPreparePhase ()
349 PreparingEnlistment pe;
350 foreach (IEnlistmentNotification enlisted in volatiles) {
351 pe = new PreparingEnlistment (this, enlisted);
353 enlisted.Prepare (pe);
355 /* FIXME: Where should this timeout value come from?
357 Wait after all Prepare()'s are sent
358 pe.WaitHandle.WaitOne (new TimeSpan (0,0,5), true); */
360 if (!pe.IsPrepared) {
361 /* FIXME: if not prepared & !aborted as yet, then
362 this is inDoubt ? . For now, setting aborted = true */
368 /* Either InDoubt(tmp) or Prepare failed and
373 void DoCommitPhase ()
375 foreach (IEnlistmentNotification enlisted in volatiles) {
376 Enlistment e = new Enlistment ();
378 /* Note: e.Done doesn't matter for volatile RMs */
382 void DoSingleCommit (ISinglePhaseNotification single)
387 SinglePhaseEnlistment enlistment = new SinglePhaseEnlistment (this, single);
388 single.SinglePhaseCommit (enlistment);
395 throw new TransactionAbortedException ("Transaction has aborted", innerException);
398 static void EnsureIncompleteCurrentScope ()
400 if (CurrentInternal == null)
402 if (CurrentInternal.Scope != null && CurrentInternal.Scope.IsComplete)
403 throw new InvalidOperationException ("The current TransactionScope is already complete");