// // Transaction.cs // // Author: // Atsushi Enomoto // Ankit Jain // // (C)2005 Novell Inc, // (C)2006 Novell Inc, // #if NET_2_0 using System.Collections; using System.Collections.Generic; using System.Security.Permissions; using System.Runtime.Serialization; using System.Threading; namespace System.Transactions { [Serializable] public class Transaction : IDisposable, ISerializable { static Transaction ambient; IsolationLevel level; TransactionInformation info; ArrayList dependents = new ArrayList (); /* Volatile enlistments */ List volatiles = new List (); /* Durable enlistments Durable RMs can also have 2 Phase commit but not in LTM, and that is what we are supporting right now */ List durables = new List (); delegate void AsyncCommit (); AsyncCommit asyncCommit = null; bool committing; bool committed = false; bool aborted = false; TransactionScope scope = null; Exception innerException; internal Transaction () { info = new TransactionInformation (); level = IsolationLevel.Serializable; } internal Transaction (Transaction other) { level = other.level; info = other.info; dependents = other.dependents; } [MonoTODO] void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context) { throw new NotImplementedException (); } public event TransactionCompletedEventHandler TransactionCompleted; public static Transaction Current { get { EnsureIncompleteCurrentScope (); return CurrentInternal; } set { EnsureIncompleteCurrentScope (); CurrentInternal = value; } } internal static Transaction CurrentInternal { get { return ambient; } set { ambient = value; } } public IsolationLevel IsolationLevel { get { EnsureIncompleteCurrentScope (); return level; } } public TransactionInformation TransactionInformation { get { EnsureIncompleteCurrentScope (); return info; } } [MonoTODO] public Transaction Clone () { return new Transaction (this); } [MonoTODO] public void Dispose () { throw new NotImplementedException (); } [MonoTODO] public DependentTransaction DependentClone ( DependentCloneOption option) { DependentTransaction d = new DependentTransaction (this, option); dependents.Add (d); return d; } [MonoTODO] [PermissionSetAttribute (SecurityAction.LinkDemand)] public Enlistment EnlistDurable (Guid manager, IEnlistmentNotification notification, EnlistmentOptions options) { throw new NotImplementedException ("Only SinglePhase commit supported for durable resource managers."); } [MonoTODO] [PermissionSetAttribute (SecurityAction.LinkDemand)] public Enlistment EnlistDurable (Guid manager, ISinglePhaseNotification notification, EnlistmentOptions options) { if (durables.Count == 1) throw new NotImplementedException ("Only LTM supported. Cannot have more than 1 durable resource per transaction."); EnsureIncompleteCurrentScope (); if (options != EnlistmentOptions.None) throw new NotImplementedException ("Implement me"); durables.Add (notification); /* FIXME: Enlistment ?? */ return new Enlistment (); } [MonoTODO] public bool EnlistPromotableSinglePhase ( IPromotableSinglePhaseNotification notification) { throw new NotImplementedException (); } [MonoTODO] public Enlistment EnlistVolatile ( IEnlistmentNotification notification, EnlistmentOptions options) { return EnlistVolatileInternal (notification, options); } [MonoTODO] public Enlistment EnlistVolatile ( ISinglePhaseNotification notification, EnlistmentOptions options) { /* FIXME: Anything extra reqd for this? */ return EnlistVolatileInternal (notification, options); } private Enlistment EnlistVolatileInternal ( IEnlistmentNotification notification, EnlistmentOptions options) { EnsureIncompleteCurrentScope (); /* FIXME: Handle options.EnlistDuringPrepareRequired */ volatiles.Add (notification); /* FIXME: Enlistment.. ? */ return new Enlistment (); } public override bool Equals (object obj) { return Equals (obj as Transaction); } // FIXME: Check whether this is correct (currently, GetHashCode() uses 'dependents' but this doesn't) private bool Equals (Transaction t) { if (ReferenceEquals (t, this)) return true; if (ReferenceEquals (t, null)) return false; return this.level == t.level && this.info == t.info; } public static bool operator == (Transaction x, Transaction y) { if (ReferenceEquals (x, null)) return ReferenceEquals (y, null); return x.Equals (y); } public static bool operator != (Transaction x, Transaction y) { return !(x == y); } public override int GetHashCode () { return (int) level ^ info.GetHashCode () ^ dependents.GetHashCode (); } public void Rollback () { Rollback (null); } public void Rollback (Exception ex) { EnsureIncompleteCurrentScope (); Rollback (ex, null); } internal void Rollback (Exception ex, IEnlistmentNotification enlisted) { if (aborted) return; /* See test ExplicitTransaction7 */ if (info.Status == TransactionStatus.Committed) throw new TransactionException ("Transaction has already been committed. Cannot accept any new work."); innerException = ex; Enlistment e = new Enlistment (); foreach (IEnlistmentNotification prep in volatiles) if (prep != enlisted) prep.Rollback (e); if (durables.Count > 0 && durables [0] != enlisted) durables [0].Rollback (e); Aborted = true; } bool Aborted { get { return aborted; } set { aborted = value; if (aborted) info.Status = TransactionStatus.Aborted; } } internal TransactionScope Scope { get { return scope; } set { scope = value; } } protected IAsyncResult BeginCommitInternal (AsyncCallback callback) { if (committed || committing) throw new InvalidOperationException ("Commit has already been called for this transaction."); this.committing = true; asyncCommit = new AsyncCommit (DoCommit); return asyncCommit.BeginInvoke (callback, null); } protected void EndCommitInternal (IAsyncResult ar) { asyncCommit.EndInvoke (ar); } internal void CommitInternal () { if (committed || committing) throw new InvalidOperationException ("Commit has already been called for this transaction."); this.committing = true; DoCommit (); } private void DoCommit () { /* Scope becomes null in TransactionScope.Dispose */ if (Scope != null) { /* See test ExplicitTransaction8 */ Rollback (null, null); CheckAborted (); } if (volatiles.Count == 1 && durables.Count == 0) { /* Special case */ ISinglePhaseNotification single = volatiles [0] as ISinglePhaseNotification; if (single != null) { DoSingleCommit (single); Complete (); return; } } if (volatiles.Count > 0) DoPreparePhase (); if (durables.Count > 0) DoSingleCommit (durables [0]); if (volatiles.Count > 0) DoCommitPhase (); Complete (); } private void Complete () { committing = false; committed = true; if (!aborted) info.Status = TransactionStatus.Committed; } internal void InitScope (TransactionScope scope) { /* See test NestedTransactionScope10 */ CheckAborted (); /* See test ExplicitTransaction6a */ if (committed) throw new InvalidOperationException ("Commit has already been called on this transaction."); Scope = scope; } void DoPreparePhase () { PreparingEnlistment pe; foreach (IEnlistmentNotification enlisted in volatiles) { pe = new PreparingEnlistment (this, enlisted); enlisted.Prepare (pe); /* FIXME: Where should this timeout value come from? current scope? Wait after all Prepare()'s are sent pe.WaitHandle.WaitOne (new TimeSpan (0,0,5), true); */ if (!pe.IsPrepared) { /* FIXME: if not prepared & !aborted as yet, then this is inDoubt ? . For now, setting aborted = true */ Aborted = true; break; } } /* Either InDoubt(tmp) or Prepare failed and Tx has rolledback */ CheckAborted (); } void DoCommitPhase () { foreach (IEnlistmentNotification enlisted in volatiles) { Enlistment e = new Enlistment (); enlisted.Commit (e); /* Note: e.Done doesn't matter for volatile RMs */ } } void DoSingleCommit (ISinglePhaseNotification single) { if (single == null) return; SinglePhaseEnlistment enlistment = new SinglePhaseEnlistment (this, single); single.SinglePhaseCommit (enlistment); CheckAborted (); } void CheckAborted () { if (aborted) throw new TransactionAbortedException ("Transaction has aborted", innerException); } static void EnsureIncompleteCurrentScope () { if (CurrentInternal == null) return; if (CurrentInternal.Scope != null && CurrentInternal.Scope.IsComplete) throw new InvalidOperationException ("The current TransactionScope is already complete"); } } } #endif