Transaction now has limited support for PromotableSinglePhaseEnlistment
authorShay Rojansky <roji@eaglets.co.il>
Tue, 23 Oct 2012 00:17:41 +0000 (02:17 +0200)
committerShay Rojansky <roji@eaglets.co.il>
Wed, 24 Oct 2012 17:03:26 +0000 (19:03 +0200)
System.Transactions.Transaction is only partially implemented, and was lacking support for enlistments of type PromotableSinglePhaseEnlistment.
Specifically, this prevented usage of Npgsql with the ambient transactions feature (TransactionScope).

mcs/class/System.Transactions/System.Transactions/SinglePhaseEnlistment.cs
mcs/class/System.Transactions/System.Transactions/Transaction.cs
mcs/class/System.Transactions/Test/EnlistTest.cs
mcs/class/System.Transactions/Test/IntResourceManager.cs

index a386a6967e2bcd1ecc0b9b475fe02f502a325ac5..072c3e99710ca41fd454c65b756653b775eaf49b 100644 (file)
@@ -17,12 +17,18 @@ namespace System.Transactions
        {
 //             bool committed;
                Transaction tx;
-               ISinglePhaseNotification enlisted;
+               object abortingEnlisted;
                
-               internal SinglePhaseEnlistment (Transaction tx, ISinglePhaseNotification enlisted)
+               /// <summary>
+               /// The empty ctor is used only for enlistments passed to resource managers when rolling back a transaction that
+               ///  has already been aborted by another resource manager; no need to retrigger (another) rollback.
+               /// </summary>
+               internal SinglePhaseEnlistment () {}
+
+               internal SinglePhaseEnlistment (Transaction tx, object abortingEnlisted)
                {
                        this.tx = tx;
-                       this.enlisted = enlisted;
+                       this.abortingEnlisted = abortingEnlisted;
                }
 
                public void Aborted ()
@@ -32,7 +38,8 @@ namespace System.Transactions
 
                public void Aborted (Exception e)
                {
-                       tx.Rollback (e, enlisted);
+                       if (tx != null)
+                               tx.Rollback (e, abortingEnlisted);
                }
 
                [MonoTODO]
index dc32c9a76d719c3c8c65ae2023f4740181795ce3..e1c094599aa7064af0c654662096f053bef30c26 100644 (file)
@@ -39,6 +39,8 @@ namespace System.Transactions
                 */
                List <ISinglePhaseNotification> durables;
 
+               IPromotableSinglePhaseNotification pspe = null;
+
                delegate void AsyncCommit ();
                
                AsyncCommit asyncCommit = null;
@@ -50,7 +52,7 @@ namespace System.Transactions
                Exception innerException;
                Guid tag = Guid.NewGuid ();
 
-               List <IEnlistmentNotification> Volatiles {
+               internal List <IEnlistmentNotification> Volatiles {
                        get {
                                if (volatiles == null)
                                        volatiles = new List <IEnlistmentNotification> ();
@@ -58,14 +60,16 @@ namespace System.Transactions
                        }
                }
 
-               List <ISinglePhaseNotification> Durables {
+               internal List <ISinglePhaseNotification> Durables {
                        get {
                                if (durables == null)
                                        durables = new List <ISinglePhaseNotification> ();
                                return durables;
                        }
                }
-               
+
+               internal IPromotableSinglePhaseNotification Pspe { get { return pspe; } }
+
                internal Transaction ()
                {
                        info = new TransactionInformation ();
@@ -79,6 +83,7 @@ namespace System.Transactions
                        dependents = other.dependents;
                        volatiles = other.Volatiles;
                        durables = other.Durables;
+                       pspe = other.Pspe;
                }
 
                [MonoTODO]
@@ -147,7 +152,7 @@ namespace System.Transactions
                        IEnlistmentNotification notification,
                        EnlistmentOptions options)
                {
-                       throw new NotImplementedException ("Only SinglePhase commit supported for durable resource managers.");
+                       throw new NotImplementedException ("DTC unsupported, only SinglePhase commit supported for durable resource managers.");
                }
 
                [MonoTODO ("Only Local Transaction Manager supported. Cannot have more than 1 durable resource per transaction. Only EnlistmentOptions.None supported yet.")]
@@ -156,26 +161,33 @@ namespace System.Transactions
                        ISinglePhaseNotification notification,
                        EnlistmentOptions options)
                {
-                       var durables = Durables;
-                       if (durables.Count == 1)
-                               throw new NotImplementedException ("Only LTM supported. Cannot have more than 1 durable resource per transaction.");
-
                        EnsureIncompleteCurrentScope ();
+                       if (pspe != null || Durables.Count > 0)
+                               throw new NotImplementedException ("DTC unsupported, multiple durable resource managers aren't supported.");
 
                        if (options != EnlistmentOptions.None)
-                               throw new NotImplementedException ("Implement me");
+                               throw new NotImplementedException ("EnlistmentOptions other than None aren't supported");
 
-                       durables.Add (notification);
+                       Durables.Add (notification);
 
                        /* FIXME: Enlistment ?? */
                        return new Enlistment ();
                }
 
-               [MonoTODO]
                public bool EnlistPromotableSinglePhase (
                        IPromotableSinglePhaseNotification notification)
                {
-                       throw new NotImplementedException ();
+                       EnsureIncompleteCurrentScope ();
+
+                       // The specs aren't entirely clear on whether we can have volatile RMs along with a PSPE, but
+                       // I'm assuming that yes based on: http://social.msdn.microsoft.com/Forums/br/windowstransactionsprogramming/thread/3df6d4d3-0d82-47c4-951a-cd31140950b3
+                       if (pspe != null || Durables.Count > 0)
+                               return false;
+
+                       pspe = notification;
+                       pspe.Initialize();
+
+                       return true;
                }
 
                [MonoTODO ("EnlistmentOptions being ignored")]
@@ -251,7 +263,7 @@ namespace System.Transactions
                        Rollback (ex, null);
                }
 
-               internal void Rollback (Exception ex, IEnlistmentNotification enlisted)
+               internal void Rollback (Exception ex, object abortingEnlisted)
                {
                        if (aborted)
                        {
@@ -264,15 +276,18 @@ namespace System.Transactions
                                throw new TransactionException ("Transaction has already been committed. Cannot accept any new work.");
 
                        innerException = ex;
-                       Enlistment e = new Enlistment ();
+                       SinglePhaseEnlistment e = new SinglePhaseEnlistment();
                        foreach (IEnlistmentNotification prep in Volatiles)
-                               if (prep != enlisted)
+                               if (prep != abortingEnlisted)
                                        prep.Rollback (e);
 
                        var durables = Durables;
-                       if (durables.Count > 0 && durables [0] != enlisted)
+                       if (durables.Count > 0 && durables [0] != abortingEnlisted)
                                durables [0].Rollback (e);
 
+                       if (pspe != null && pspe != abortingEnlisted)
+                               pspe.Rollback (e);
+
                        Aborted = true;
 
                        FireCompleted ();
@@ -316,7 +331,7 @@ namespace System.Transactions
                        this.committing = true;
 
                        try {
-                               DoCommit ();    
+                               DoCommit ();
                        }
                        catch (TransactionException)
                        {
@@ -357,6 +372,9 @@ namespace System.Transactions
                        if (durables.Count > 0)
                                DoSingleCommit(durables[0]);
 
+                       if (pspe != null)
+                               DoSingleCommit(pspe);
+
                        if (volatiles.Count > 0)
                                DoCommitPhase();
                        
@@ -438,8 +456,16 @@ namespace System.Transactions
                        if (single == null)
                                return;
 
-                       SinglePhaseEnlistment enlistment = new SinglePhaseEnlistment (this, single);
-                       single.SinglePhaseCommit (enlistment);
+                       single.SinglePhaseCommit (new SinglePhaseEnlistment (this, single));
+                       CheckAborted ();
+               }
+
+               void DoSingleCommit (IPromotableSinglePhaseNotification single)
+               {
+                       if (single == null)
+                               return;
+
+                       single.SinglePhaseCommit (new SinglePhaseEnlistment (this, single));
                        CheckAborted ();
                }
 
@@ -462,7 +488,7 @@ namespace System.Transactions
                        if (CurrentInternal.Scope != null && CurrentInternal.Scope.IsComplete)
                                throw new InvalidOperationException ("The current TransactionScope is already complete");
                }
-       }
+  }
 }
 
 #endif
index b06d0b896f2cdf873dddad07501095e47139670d..4066a702da5ab6a5c45fe2983f30c2a6ff9c6226 100644 (file)
@@ -58,7 +58,7 @@ namespace MonoTests.System.Transactions {
                                scope.Complete ();*/
                        }
 
-                       irm.Check ( 0, 0, 0, 1, 0, "irm" );
+                       irm.Check ( 0, 0, 0, 1, 0, 0, 0, "irm" );
                }
 
                [Test]
@@ -122,7 +122,7 @@ namespace MonoTests.System.Transactions {
                public void Vol0_Dur1 ()
                {
                        IntResourceManager irm = new IntResourceManager (1);
-                       irm.Volatile = false;
+                       irm.Type = ResourceManagerType.Durable;
                        irm.UseSingle = true;
 
                        using (TransactionScope scope = new TransactionScope ()) {
@@ -146,7 +146,7 @@ namespace MonoTests.System.Transactions {
                        /* Durable resource enlisted with a IEnlistedNotification
                         * object
                         */
-                       irm.Volatile = false;
+                       irm.Type = ResourceManagerType.Durable;
 
                        using (TransactionScope scope = new TransactionScope ()) {
                                irm.Value = 2;
@@ -163,7 +163,7 @@ namespace MonoTests.System.Transactions {
                        /* Durable resource enlisted with a IEnlistedNotification
                         * object
                         */
-                       irm.Volatile = false;
+                       irm.Type = ResourceManagerType.Durable;
                        irm.FailSPC = true;
                        irm.UseSingle = true;
                        try {
@@ -174,7 +174,7 @@ namespace MonoTests.System.Transactions {
                                }
                        }
                        catch (TransactionAbortedException) {
-                               irm.Check ( 1, 0, 0, 0, 0, "irm" );
+                               irm.Check ( 1, 0, 0, 0, 0, 0, 0, "irm" );
                                return;
                        }
 
@@ -193,7 +193,7 @@ namespace MonoTests.System.Transactions {
                        irm [2] = new IntResourceManager ( 5 );
                        irm [3] = new IntResourceManager ( 7 );
 
-                       irm [0].Volatile = false;
+                       irm [0].Type = ResourceManagerType.Durable;
                        for ( int i = 0; i < 4; i++ )
                                irm [i].UseSingle = true;
 
@@ -225,7 +225,7 @@ namespace MonoTests.System.Transactions {
                        irm [2] = new IntResourceManager (5);
                        irm [3] = new IntResourceManager (7);
 
-                       irm [0].Volatile = false;
+                       irm [0].Type = ResourceManagerType.Durable;
                        irm [0].FailSPC = true;
 
                        for ( int i = 0; i < 4; i++ )
@@ -247,7 +247,7 @@ namespace MonoTests.System.Transactions {
                                irm [0].CheckSPC ( "irm [0]" );
                                /* Volatile RMs get 2PC Prepare, and then get rolled back */
                                for (int i = 1; i < 4; i++)
-                                       irm [i].Check ( 0, 1, 0, 1, 0, "irm [" + i + "]" );
+                                       irm [i].Check ( 0, 1, 0, 1, 0, 0, 0, "irm [" + i + "]" );
                        }
                }
 
@@ -265,7 +265,7 @@ namespace MonoTests.System.Transactions {
                        irm [2] = new IntResourceManager (5);
                        irm [3] = new IntResourceManager (7);
 
-                       irm [0].Volatile = false;
+                       irm [0].Type = ResourceManagerType.Durable;
                        irm [0].IgnoreSPC = true;
 
                        for ( int i = 0; i < 4; i++ )
@@ -288,7 +288,7 @@ namespace MonoTests.System.Transactions {
 
                                /* Volatile RMs get 2PC Prepare, and then get rolled back */
                                for (int i = 1; i < 4; i++)
-                                       irm [i].Check ( 0, 1, 0, 1, 0, "irm [" + i + "]" );
+                                       irm [i].Check ( 0, 1, 0, 1, 0, 0, 0, "irm [" + i + "]" );
 
                                exception = ex;
                        }
@@ -311,7 +311,7 @@ namespace MonoTests.System.Transactions {
                        irm[3] = new IntResourceManager(7);
 
                        irm[0].IgnoreSPC = true;
-                       irm[1].Volatile = false;
+                       irm[1].Type = ResourceManagerType.Durable;
 
                        for (int i = 0; i < 4; i++)
                                irm[i].UseSingle = true;
@@ -336,7 +336,7 @@ namespace MonoTests.System.Transactions {
 
                                /* Volatile RMs get 2PC Prepare, and then get rolled back */
                                for (int i = 1; i < 4; i++)
-                                       irm[i].Check(0, 1, 0, 1, 0, "irm [" + i + "]");
+                                       irm[i].Check(0, 1, 0, 1, 0, 0, 0, "irm [" + i + "]");
 
                                exception = ex;
                        }
@@ -358,7 +358,7 @@ namespace MonoTests.System.Transactions {
                        irm [2] = new IntResourceManager ( 5 );
                        irm [3] = new IntResourceManager ( 7 );
 
-                       irm [0].Volatile = false;
+                       irm [0].Type = ResourceManagerType.Durable;
                        irm [2].FailPrepare = true;
 
                        for ( int i = 0; i < 4; i++ )
@@ -377,15 +377,15 @@ namespace MonoTests.System.Transactions {
                                }
                        }
                        catch (TransactionAbortedException) {
-                               irm [0].Check ( 0, 0, 0, 1, 0, "irm [0]");
+                               irm [0].Check ( 0, 0, 0, 1, 0, 0, 0, "irm [0]");
 
                                /* irm [1] & [2] get prepare,
                                 * [2] -> ForceRollback,
                                 * [1] & [3] get rollback,
                                 * [0](durable) gets rollback */
-                               irm [1].Check ( 0, 1, 0, 1, 0, "irm [1]" );
-                               irm [2].Check ( 0, 1, 0, 0, 0, "irm [2]" );
-                               irm [3].Check ( 0, 0, 0, 1, 0, "irm [3]" );
+                               irm [1].Check ( 0, 1, 0, 1, 0, 0, 0, "irm [1]" );
+                               irm [2].Check ( 0, 1, 0, 0, 0, 0, 0, "irm [2]" );
+                               irm [3].Check ( 0, 0, 0, 1, 0, 0, 0, "irm [3]" );
 
                                return;
                        }
@@ -400,7 +400,7 @@ namespace MonoTests.System.Transactions {
                        irm [0] = new IntResourceManager ( 1 );
                        irm [1] = new IntResourceManager ( 3 );
 
-                       irm [0].Volatile = false;
+                       irm [0].Type = ResourceManagerType.Durable;
                        irm [0].FailSPC = true;
                        irm [0].FailWithException = true;
 
@@ -421,8 +421,8 @@ namespace MonoTests.System.Transactions {
                                Assert.IsNotNull ( e.InnerException, "Expected e.InnerException == NotSupportedException, but got None");
                                Assert.AreEqual ( typeof ( NotSupportedException ), e.InnerException.GetType (), "Expected e.InnerException == NotSupportedException, but got " + e.GetType () );
 
-                               irm [0].Check ( 1, 0, 0, 0, 0, "irm [0]" );
-                               irm [1].Check ( 0, 1, 0, 1, 0, "irm [1]" );
+                               irm [0].Check ( 1, 0, 0, 0, 0, 0, 0, "irm [0]" );
+                               irm [1].Check ( 0, 1, 0, 1, 0, 0, 0, "irm [1]" );
                                return;
                        }
 
@@ -438,7 +438,7 @@ namespace MonoTests.System.Transactions {
                        irm [1] = new IntResourceManager ( 3 );
 
                        Transaction.Current = ct;
-                       irm [0].Volatile = false;
+                       irm [0].Type = ResourceManagerType.Durable;
                        irm [0].FailSPC = true;
                        irm [0].FailWithException = true;
 
@@ -462,8 +462,8 @@ namespace MonoTests.System.Transactions {
                                Assert.IsNotNull ( e.InnerException, "Expected e.InnerException == NotSupportedException, but got None" );
                                Assert.AreEqual ( typeof ( NotSupportedException ), e.InnerException.GetType (), "Expected e.InnerException == NotSupportedException, but got " + e.GetType () );
 
-                               irm [0].Check ( 1, 0, 0, 0, 0, "irm [0]" );
-                               irm [1].Check ( 0, 1, 0, 1, 0, "irm [1]" );
+                               irm [0].Check ( 1, 0, 0, 0, 0, 0, 0, "irm [0]" );
+                               irm [1].Check ( 0, 1, 0, 1, 0, 0, 0, "irm [1]" );
                                try {
                                        ct.Commit ();
                                }
@@ -480,6 +480,65 @@ namespace MonoTests.System.Transactions {
 
                #endregion
 
+               #region Promotable Single Phase Enlistment
+               [Test]
+               public void Vol0_Dur0_Pspe1 ()
+               {
+                       IntResourceManager irm = new IntResourceManager (1);
+                       irm.Type = ResourceManagerType.Promotable;
+                       using (TransactionScope scope = new TransactionScope ()) {
+                               irm.Value = 2;
+
+                               scope.Complete ();
+                       }
+                       irm.Check ( 1, 0, 0, 0, 0, 1, 0, "irm" );
+               }
+
+               [Test]
+               public void Vol1_Dur0_Pspe1 ()
+               {
+                       IntResourceManager irm0 = new IntResourceManager (1);
+                       IntResourceManager irm1 = new IntResourceManager (1);
+                       irm1.Type = ResourceManagerType.Promotable;
+                       using (TransactionScope scope = new TransactionScope ()) {
+                               irm0.Value = 2;
+                               irm1.Value = 8;
+
+                               scope.Complete ();
+                       }
+                       irm1.Check ( 1, 0, 0, 0, 0, 1, 0, "irm1" );
+               }
+
+               [Test]
+               public void Vol0_Dur1_Pspe1 ()
+               {
+                       IntResourceManager irm0 = new IntResourceManager (1);
+                       IntResourceManager irm1 = new IntResourceManager (1);
+                       irm0.Type = ResourceManagerType.Durable;
+                       irm0.UseSingle = true;
+                       irm1.Type = ResourceManagerType.Promotable;
+                       using (TransactionScope scope = new TransactionScope ()) {
+                               irm0.Value = 8;
+                               irm1.Value = 2;
+                               Assert.AreEqual(0, irm1.NumEnlistFailed, "PSPE enlist did not fail although durable RM was already enlisted");
+                       }
+               }
+
+               [Test]
+               public void Vol0_Dur0_Pspe2 ()
+               {
+                       IntResourceManager irm0 = new IntResourceManager (1);
+                       IntResourceManager irm1 = new IntResourceManager (1);
+                       irm0.Type = ResourceManagerType.Promotable;
+                       irm1.Type = ResourceManagerType.Promotable;
+                       using (TransactionScope scope = new TransactionScope ()) {
+                               irm0.Value = 8;
+                               irm1.Value = 2;
+                               Assert.AreEqual(0, irm1.NumEnlistFailed, "PSPE enlist did not fail although PSPE RM was already enlisted");
+                       }
+               }
+               #endregion
+
                #region Others
                /* >1vol  
                 * > 1 durable, On .net this becomes a distributed transaction
@@ -493,8 +552,8 @@ namespace MonoTests.System.Transactions {
                        irm [0] = new IntResourceManager ( 1 );
                        irm [1] = new IntResourceManager ( 3 );
 
-                       irm [0].Volatile = false;
-                       irm [1].Volatile = false;
+                       irm [0].Type = ResourceManagerType.Durable;
+                       irm [1].Type = ResourceManagerType.Durable;
 
                        for ( int i = 0; i < 2; i++ )
                                irm [i].UseSingle = true;
@@ -512,7 +571,7 @@ namespace MonoTests.System.Transactions {
                {
                        CommittableTransaction ct = new CommittableTransaction ();
                        IntResourceManager irm = new IntResourceManager (1);
-                       irm.Volatile = false;
+                       irm.Type = ResourceManagerType.Durable;
 
                        ct.Dispose ();
                        irm.Check  (0, 0, 0, 0, "Dispose transaction");
index af41c9f3cba9aded4e1253072266c4762193e26c..0f42e56629a4620c01143708df81a55fa5be3d4c 100644 (file)
@@ -32,12 +32,15 @@ namespace MonoTests.System.Transactions
         public int NumCommit = 0;
         public int NumInDoubt = 0;
         public int NumSingle = 0;
+        
+        public int NumInitialize = 0;
+        public int NumPromote = 0;
+        public int NumEnlistFailed = 0;
 
+        public ResourceManagerType Type = ResourceManagerType.Volatile;        
         public bool FailPrepare = false;
         public bool FailWithException = false;
         public bool IgnorePrepare = false;
-
-        public bool Volatile = true;
         public bool IgnoreSPC = false;
         public bool FailSPC = false;
         public bool FailCommit = false;
@@ -64,16 +67,18 @@ namespace MonoTests.System.Transactions
 
                 if (transaction != Transaction.Current) {
                     transaction = Transaction.Current;
-                    
-                    if (UseSingle) {
+
+                    if ( Type == ResourceManagerType.Promotable ) {
+                        transaction.EnlistPromotableSinglePhase(new PromotableSinglePhaseNotification ( this ));
+                    } else if (UseSingle) {
                         SinglePhaseNotification enlistment = new SinglePhaseNotification ( this );
-                        if ( Volatile )
+                        if ( Type == ResourceManagerType.Volatile )
                             transaction.EnlistVolatile ( enlistment, EnlistmentOptions.None );
                         else
                             transaction.EnlistDurable ( guid, enlistment, EnlistmentOptions.None );
                     } else {
                         EnlistmentNotification enlistment = new EnlistmentNotification ( this );
-                        if ( Volatile )
+                        if ( Type == ResourceManagerType.Volatile )
                             transaction.EnlistVolatile ( enlistment, EnlistmentOptions.None );
                         else
                             transaction.EnlistDurable ( guid, enlistment, EnlistmentOptions.None );
@@ -96,27 +101,29 @@ namespace MonoTests.System.Transactions
 
         public  void CheckSPC ( string msg )
         {
-            Check ( 1, 0, 0, 0, 0, msg );
+            Check ( 1, 0, 0, 0, 0, 0, 0, msg );
         }
 
         public void Check2PC ( string msg)
         {
-            Check ( 0, 1, 1, 0, 0, msg );
+            Check ( 0, 1, 1, 0, 0, 0, 0, msg );
         }
 
-        public void Check ( int s, int p, int c, int r, int d, string msg )
+        public void Check ( int s, int p, int c, int r, int d, int i, int pr, string msg )
         {
             Assert.AreEqual ( s, NumSingle, msg + ": NumSingle" );
             Assert.AreEqual ( p, NumPrepare, msg + ": NumPrepare" );
             Assert.AreEqual ( c, NumCommit, msg + ": NumCommit" );
             Assert.AreEqual ( r, NumRollback, msg + ": NumRollback" );
             Assert.AreEqual ( d, NumInDoubt, msg + ": NumInDoubt" );
+            Assert.AreEqual ( i, NumInitialize, msg + ": NumRollback" );
+            Assert.AreEqual ( pr, NumPromote, msg + ": NumInDoubt" );
         }
        
         /* Used for volatile RMs */
         public void Check ( int p, int c, int r, int d, string msg )
         {
-            Check ( 0, p, c, r, d, msg );
+            Check ( 0, p, c, r, d, 0, 0, msg );
         }
     }
 
@@ -165,7 +172,6 @@ namespace MonoTests.System.Transactions
             resource.NumInDoubt++;
             throw new Exception ( "IntResourceManager.InDoubt is not implemented." );
         }
-
     }
 
     public class SinglePhaseNotification : EnlistmentNotification, ISinglePhaseNotification 
@@ -191,9 +197,34 @@ namespace MonoTests.System.Transactions
                 resource.Commit ();
                 enlistment.Committed ();
             }
+        }
+    }
+
+    public class PromotableSinglePhaseNotification : SinglePhaseNotification, IPromotableSinglePhaseNotification
+    {
+        public PromotableSinglePhaseNotification ( IntResourceManager resource )
+            : base( resource )
+        {
+        }
 
+        public void Initialize ()
+        {
+            resource.NumInitialize++;
         }
 
+        public void Rollback ( SinglePhaseEnlistment enlistment )
+        {
+            resource.NumRollback++;
+            resource.Rollback ();
+        }
+
+        public byte [] Promote ()
+        {
+            resource.NumPromote++;
+            return new byte[0];
+        }
     }
+
+    public enum ResourceManagerType { Volatile, Durable, Promotable };
 }