List:Commits« Previous MessageNext Message »
From:rburnett Date:August 7 2007 8:29pm
Subject:Connector/NET commit: r838 - in branches/5.1: . Driver/Properties Driver/Source TestSuite/Source
View as plain text  
Modified:
   branches/5.1/CHANGES
   branches/5.1/Driver/Properties/Resources.Designer.cs
   branches/5.1/Driver/Properties/Resources.resx
   branches/5.1/Driver/Source/Connection.cs
   branches/5.1/Driver/Source/Driver.cs
   branches/5.1/Driver/Source/MySqlPromotableTransaction.cs
   branches/5.1/Driver/Source/command.cs
   branches/5.1/Driver/Source/transaction.cs
   branches/5.1/TestSuite/Source/BaseTest.cs
   branches/5.1/TestSuite/Source/Transactions.cs
Log:
Added code to implement better TransactionScope support.  This code is brand new and will
be heavily refactored in 5.2. (bug #28709)

The approach in this change is to take advantage of the fact that all work under a single
txn must happen on the same physical connection so we just keep a hash of active driver
connections and reuse them when necessary.

Modified: branches/5.1/CHANGES
===================================================================
--- branches/5.1/CHANGES	2007-08-03 17:44:45 UTC (rev 837)
+++ branches/5.1/CHANGES	2007-08-07 18:29:20 UTC (rev 838)
@@ -24,6 +24,8 @@
     foreign keys and foreign key columns
   - Fixed index and foreign key enumerators inside the DDEX provider to work
     with the new binary respect behavior of 5.1
+  - Added code to implement better TransactionScope support.  This code is brand new and
will be
+    heavily refactored in 5.2. (bug #28709)
 
 Version 5.1.2 - 6/12/2007
   - Fixed integration with the Website Administration Tool.  Before this fix, the test
link

Modified: branches/5.1/Driver/Properties/Resources.Designer.cs
===================================================================
--- branches/5.1/Driver/Properties/Resources.Designer.cs	2007-08-03 17:44:45 UTC (rev 837)
+++ branches/5.1/Driver/Properties/Resources.Designer.cs	2007-08-07 18:29:20 UTC (rev 838)
@@ -343,7 +343,14 @@
                 return ResourceManager.GetString("MixingUpdatedRowSource",
resourceCulture);
             }
         }
-        
+
+        internal static string MultipleConnectionsInTransactionNotSupported
+        {
+            get {
+                return
ResourceManager.GetString("MultipleConnectionsInTransactionNotSupported",
resourceCulture);
+            }
+        }
+
         /// <summary>
         ///   Looks up a localized string similar to NamedPipeStream does not support
seeking.
         /// </summary>

Modified: branches/5.1/Driver/Properties/Resources.resx
===================================================================
--- branches/5.1/Driver/Properties/Resources.resx	2007-08-03 17:44:45 UTC (rev 837)
+++ branches/5.1/Driver/Properties/Resources.resx	2007-08-07 18:29:20 UTC (rev 838)
@@ -324,4 +324,7 @@
   <data name="ObjectDisposed" xml:space="preserve">
     <value>The object is not open or has been disposed.</value>
   </data>
-</root>
+  <data name="MultipleConnectionsInTransactionNotSupported" xml:space="preserve">
+    <value>Multiple simultaneous connections or connections with different
connection strings inside the same transaction are not currently supported.</value>
+  </data>
+</root>
\ No newline at end of file

Modified: branches/5.1/Driver/Source/Connection.cs
===================================================================
--- branches/5.1/Driver/Source/Connection.cs	2007-08-03 17:44:45 UTC (rev 837)
+++ branches/5.1/Driver/Source/Connection.cs	2007-08-07 18:29:20 UTC (rev 838)
@@ -30,6 +30,7 @@
 using System.Text;
 using IsolationLevel=System.Data.IsolationLevel;
 using MySql.Data.Common;
+using System.Diagnostics;
 
 namespace MySql.Data.MySqlClient
 {
@@ -51,7 +52,6 @@
         private ProcedureCache procedureCache;
 #if !CF
         private PerformanceMonitor perfMonitor;
-        private MySqlPromotableTransaction currentTransaction;
 #endif
         private bool isExecutingBuggyQuery;
         private string database;
@@ -84,12 +84,6 @@
         #region Interal Methods & Properties
 
 #if !CF
-        internal MySqlPromotableTransaction CurrentTransaction
-        {
-            get { return currentTransaction; }
-            set { currentTransaction = value; }
-        }
-
         internal PerformanceMonitor PerfMonitor
         {
             get { return perfMonitor; }
@@ -135,6 +129,17 @@
             get { return isExecutingBuggyQuery; }
             set { isExecutingBuggyQuery = value; }
         }
+        internal bool SoftClosed
+        {
+            get 
+            {
+#if !CF
+                return (State == ConnectionState.Closed) &&
driver.CurrentTransaction != null;
+#else
+                return false;            
+#endif
+            }
+        }
 
         #endregion
 
@@ -286,20 +291,50 @@
         /// </param>
         public override void EnlistTransaction(Transaction transaction)
         {
+            // enlisting in the null transaction is a noop
             if (transaction == null)
                 return;
 
-            if (currentTransaction != null)
+            // guard against trying to enlist in more than one transaction
+            if (driver.CurrentTransaction != null)
             {
-                if (currentTransaction.BaseTransaction == transaction)
+                if (driver.CurrentTransaction.BaseTransaction == transaction)
                     return;
 
                 throw new MySqlException("Already enlisted");
             }
 
-            MySqlPromotableTransaction t = new MySqlPromotableTransaction(this,
transaction);
-            transaction.EnlistPromotableSinglePhase(t);
-            currentTransaction = t;
+            // now see if we need to swap out drivers.  We would need to do this since
+            // we have to make sure all ops for a given transaction are done on the
+            // same physical connection.
+            Driver existingDriver =
DriverTransactionManager.GetDriverInTransaction(transaction);
+            if (existingDriver != null)
+            {
+                // we can't allow more than one driver to contribute to the same
connection
+                if (existingDriver.IsInActiveUse)
+                    throw new
NotSupportedException(Resources.MultipleConnectionsInTransactionNotSupported);
+
+                // there is an existing driver and it's not being currently used.
+                // now we need to see if it is using the same connection string
+                string text1 = existingDriver.Settings.GetConnectionString(true);
+                string text2 = Settings.GetConnectionString(true);
+                if (String.Compare(text1, text2, true) != 0)
+                    throw new
NotSupportedException(Resources.MultipleConnectionsInTransactionNotSupported);
+
+                // close existing driver
+                // set this new driver as our existing driver
+                CloseDriver();
+                driver = existingDriver;
+            }
+
+            if (driver.CurrentTransaction == null)
+            {
+                MySqlPromotableTransaction t = new MySqlPromotableTransaction(this,
transaction);
+                transaction.EnlistPromotableSinglePhase(t);
+                driver.CurrentTransaction = t;
+                DriverTransactionManager.SetDriverInTransaction(driver);
+                driver.IsInActiveUse = true;
+            }
         }
 #endif
 
@@ -395,17 +430,30 @@
 
             SetState(ConnectionState.Connecting);
 
+#if !CF
+                // if we are auto enlisting in a current transaction, then we will be
+                // treating the connection as pooled
+                if (settings.AutoEnlist && Transaction.Current != null)
+                {
+                    driver =
DriverTransactionManager.GetDriverInTransaction(Transaction.Current);
+                    if (driver != null && driver.IsInActiveUse)
+                        throw new
NotSupportedException(Resources.MultipleConnectionsInTransactionNotSupported);
+                }
+#endif
+
             try
             {
                 if (settings.Pooling)
                 {
                     MySqlPool pool = MySqlPoolManager.GetPool(settings);
-                    driver = pool.GetConnection();
+                    if (driver == null)
+                        driver = pool.GetConnection();
                     procedureCache = pool.ProcedureCache;
                 }
                 else
                 {
-                    driver = Driver.Create(settings);
+                    if (driver == null)
+                        driver = Driver.Create(settings);
                     procedureCache = new ProcedureCache((int)
settings.ProcedureCacheSize);
                 }
             }
@@ -505,15 +553,12 @@
             SetState(ConnectionState.Closed);
         }
 
-        /// <include file='docs/MySqlConnection.xml' path='docs/Close/*'/>
-        public override void Close()
+        internal void CloseDriver()
         {
-            //TODO: rollback any pending transaction
-            if (State == ConnectionState.Closed) return;
+#if !CF
+            driver.CurrentTransaction = null;
+#endif
 
-            if (dataReader != null)
-                dataReader.Close();
-
             if (settings.Pooling)
             {
                 // if we are in a transaction, roll it back
@@ -527,7 +572,24 @@
             }
             else
                 driver.Close();
+        }
 
+        /// <include file='docs/MySqlConnection.xml' path='docs/Close/*'/>
+        public override void Close()
+        {
+            if (State == ConnectionState.Closed) return;
+
+            if (dataReader != null)
+                dataReader.Close();
+#if !CF
+            if (driver.CurrentTransaction == null)
+#endif
+                CloseDriver();
+#if !CF
+            else
+                driver.IsInActiveUse = false;
+#endif
+
             SetState(ConnectionState.Closed);
         }
 

Modified: branches/5.1/Driver/Source/Driver.cs
===================================================================
--- branches/5.1/Driver/Source/Driver.cs	2007-08-03 17:44:45 UTC (rev 837)
+++ branches/5.1/Driver/Source/Driver.cs	2007-08-07 18:29:20 UTC (rev 838)
@@ -47,6 +47,10 @@
         protected Hashtable charSets;
         protected bool hasWarnings;
         protected long maxPacketSize;
+#if !CF
+        protected MySqlPromotableTransaction currentTransaction;
+        protected bool inActiveUse;
+#endif
 
         public Driver(MySqlConnectionStringBuilder settings)
         {
@@ -97,6 +101,20 @@
             get { return hasWarnings; }
         }
 
+#if !CF
+        public MySqlPromotableTransaction CurrentTransaction
+        {
+            get { return currentTransaction; }
+            set { currentTransaction = value; }
+        }
+
+        public bool IsInActiveUse
+        {
+            get { return inActiveUse; }
+            set { inActiveUse = value; }
+        }
+#endif
+
         #endregion
 
         public string Property(string key)

Modified: branches/5.1/Driver/Source/MySqlPromotableTransaction.cs
===================================================================
--- branches/5.1/Driver/Source/MySqlPromotableTransaction.cs	2007-08-03 17:44:45 UTC (rev
837)
+++ branches/5.1/Driver/Source/MySqlPromotableTransaction.cs	2007-08-07 18:29:20 UTC (rev
838)
@@ -20,6 +20,7 @@
 
 using System;
 using System.Transactions;
+using System.Collections;
 
 namespace MySql.Data.MySqlClient
 {
@@ -49,14 +50,16 @@
         {
             simpleTransaction.Rollback();
             singlePhaseEnlistment.Aborted();
-            connection.CurrentTransaction = null;
+            DriverTransactionManager.RemoveDriverInTransaction(baseTransaction);
+            connection.CloseDriver();
         }
 
         void IPromotableSinglePhaseNotification.SinglePhaseCommit(SinglePhaseEnlistment
singlePhaseEnlistment)
         {
             simpleTransaction.Commit();
             singlePhaseEnlistment.Committed();
-            connection.CurrentTransaction = null;
+            DriverTransactionManager.RemoveDriverInTransaction(baseTransaction);
+            connection.CloseDriver();
         }
 
         byte[] ITransactionPromoter.Promote()
@@ -64,5 +67,35 @@
             throw new NotSupportedException();
         }
     }
+
+    internal class DriverTransactionManager
+    {
+        private static Hashtable driversInUse = new Hashtable();
+
+        public static Driver GetDriverInTransaction(Transaction transaction)
+        {
+            lock (driversInUse.SyncRoot)
+            {
+                Driver d = (Driver)driversInUse[transaction.GetHashCode()];
+                return d;
+            }
+        }
+
+        public static void SetDriverInTransaction(Driver driver)
+        {
+            lock (driversInUse.SyncRoot)
+            {
+                driversInUse[driver.CurrentTransaction.BaseTransaction.GetHashCode()] =
driver;
+            }
+        }
+
+        public static void RemoveDriverInTransaction(Transaction transaction)
+        {
+            lock (driversInUse.SyncRoot)
+            {
+                driversInUse.Remove(transaction.GetHashCode());
+            }
+        }
+    }
 }
 

Modified: branches/5.1/Driver/Source/command.cs
===================================================================
--- branches/5.1/Driver/Source/command.cs	2007-08-03 17:44:45 UTC (rev 837)
+++ branches/5.1/Driver/Source/command.cs	2007-08-07 18:29:20 UTC (rev 838)
@@ -273,7 +273,8 @@
 		private void CheckState()
 		{
 			// There must be a valid and open connection.
-			if (connection == null || connection.State != ConnectionState.Open)
+			if ((connection == null || connection.State != ConnectionState.Open) && 
+                !connection.SoftClosed)
 				throw new InvalidOperationException("Connection must be valid and open");
 
 			// Data readers have to be closed first

Modified: branches/5.1/Driver/Source/transaction.cs
===================================================================
--- branches/5.1/Driver/Source/transaction.cs	2007-08-03 17:44:45 UTC (rev 837)
+++ branches/5.1/Driver/Source/transaction.cs	2007-08-07 18:29:20 UTC (rev 838)
@@ -80,7 +80,7 @@
         /// <include file='docs/MySqlTransaction.xml' path='docs/Commit/*'/>
         public override void Commit()
         {
-            if (conn == null || conn.State != ConnectionState.Open)
+            if (conn == null || (conn.State != ConnectionState.Open &&
!conn.SoftClosed))
                 throw new InvalidOperationException("Connection must be valid and open to
commit transaction");
             if (!open)
                 throw new InvalidOperationException("Transaction has already been
committed or is not pending");
@@ -92,8 +92,8 @@
         /// <include file='docs/MySqlTransaction.xml' path='docs/Rollback/*'/>
         public override void Rollback()
         {
-            if (conn == null || conn.State != ConnectionState.Open)
-                throw new InvalidOperationException("Connection must be valid and open to
commit transaction");
+            if (conn == null || (conn.State != ConnectionState.Open &&
!conn.SoftClosed))
+                throw new InvalidOperationException("Connection must be valid and open to
rollback transaction");
             if (!open)
                 throw new InvalidOperationException("Transaction has already been rolled
back or is not pending");
             MySqlCommand cmd = new MySqlCommand("ROLLBACK", conn);

Modified: branches/5.1/TestSuite/Source/BaseTest.cs
===================================================================
--- branches/5.1/TestSuite/Source/BaseTest.cs	2007-08-03 17:44:45 UTC (rev 837)
+++ branches/5.1/TestSuite/Source/BaseTest.cs	2007-08-07 18:29:20 UTC (rev 838)
@@ -44,12 +44,12 @@
         protected string rootUser;
         protected string rootPassword;
         protected Version version;
+        protected bool pooling;
 
         public BaseTest()
         {
             databases = new string[2];
 
-            csAdditions = ";pooling=false;";
             user = "test";
             password = "test";
             host = "localhost";
@@ -113,16 +113,25 @@
             return String.Format("protocol=sockets;port={0}", port);
         }
 
-        protected string GetConnectionString(bool includedb)
+        protected string GetConnectionStringBasic(bool includedb)
         {
             string connStr = String.Format("server={0};user id={1};password={2};" +
-                 "persist security info=true;{3}", host, user, password, csAdditions);
+                 "persist security info=true;", host, user, password);
             if (includedb)
                 connStr += String.Format("database={0};", databases[0]);
+            if (!pooling)
+                connStr += ";pooling=false;";
             connStr += GetConnectionInfo();
             return connStr;
         }
 
+        protected string GetConnectionString(bool includedb)
+        {
+            string connStr = String.Format("{0};{1}", 
+                GetConnectionStringBasic(includedb), csAdditions);
+            return connStr;
+        }
+
         protected string GetConnectionStringEx(string user, string pw, bool includedb)
         {
             string connStr = String.Format("server={0};user id={1};" +
@@ -186,6 +195,7 @@
         {
             try
             {
+                pooling = true;
                 IDataReader reader = execReader("SHOW TABLES LIKE 'Test'");
                 bool exists = reader.Read();
                 reader.Close();
@@ -265,5 +275,12 @@
             return cmd.ExecuteReader();
         }
 
+        protected int CountProcesses()
+        {
+            MySqlDataAdapter da = new MySqlDataAdapter("SHOW PROCESSLIST", conn);
+            DataTable dt = new DataTable();
+            da.Fill(dt);
+            return dt.Rows.Count;
+        }
     }
 }

Modified: branches/5.1/TestSuite/Source/Transactions.cs
===================================================================
--- branches/5.1/TestSuite/Source/Transactions.cs	2007-08-03 17:44:45 UTC (rev 837)
+++ branches/5.1/TestSuite/Source/Transactions.cs	2007-08-07 18:29:20 UTC (rev 838)
@@ -33,7 +33,6 @@
         [TestFixtureSetUp]
         public void FixtureSetup()
         {
-            csAdditions += ";pooling=true;";
             Open();
         }
 
@@ -284,54 +283,158 @@
             Assert.AreEqual(1, cmd2.ExecuteScalar());
         }
 
+        private void ManuallyEnlistingInitialConnection(bool complete)
+        {
+            using (TransactionScope ts = new TransactionScope())
+            {
+                string connStr = GetConnectionStringBasic(true) + ";auto enlist=false";
 
-/*        [Test]
-        public void XATransaction1Rollback()
-        {
-            XATransaction1(false);
+                using (MySqlConnection c1 = new MySqlConnection(connStr))
+                {
+                    c1.Open();
+                    c1.EnlistTransaction(Transaction.Current);
+                    MySqlCommand cmd1 = new MySqlCommand("INSERT INTO test (key2) VALUES
('a')", c1);
+                    cmd1.ExecuteNonQuery();
+                }
+
+                using (MySqlConnection c2 = new MySqlConnection(connStr))
+                {
+                    c2.Open();
+                    c2.EnlistTransaction(Transaction.Current);
+                    MySqlCommand cmd2 = new MySqlCommand("INSERT INTO test (key2) VALUES
('b')", c2);
+                    cmd2.ExecuteNonQuery();
+                }
+                if (complete)
+                    ts.Complete();
+            }
         }
 
         [Test]
-        public void XATransaction1Commit()
+        public void ManuallyEnlistingInitialConnection()
         {
-            XATransaction1(true);
+            ManuallyEnlistingInitialConnection(true);
+            ManuallyEnlistingInitialConnection(false);
         }
 
-        private void XATransaction1(bool commit)
+        [Test]
+        public void ManualEnlistmentWithActiveConnection()
         {
-            try
+            using (TransactionScope ts = new TransactionScope())
             {
-                using (TransactionScope ts = new TransactionScope())
+                string connStr = GetConnectionStringBasic(true);
+
+                using (MySqlConnection c1 = new MySqlConnection(connStr))
                 {
-                    using (MySqlConnection c = new
MySqlConnection(GetConnectionString(true)))
+                    c1.Open();
+
+                    connStr += "; auto enlist=false";
+                    using (MySqlConnection c2 = new MySqlConnection(connStr))
                     {
-                        c.Open();
-
-                        MySqlCommand cmd = new MySqlCommand("INSERT INTO test VALUES
('a', 'name', 'name2')", c);
-                        cmd.ExecuteNonQuery();
+                        c2.Open();
+                        try
+                        {
+                            c2.EnlistTransaction(Transaction.Current);
+                        }
+                        catch (NotSupportedException)
+                        {
+                        }
                     }
+                }
+            }
+        }
 
-                    using (MySqlConnection c2 = new
MySqlConnection(GetConnectionString(true)))
+        [Test]
+        public void AttemptToEnlistTwoConnections()
+        {
+            using (TransactionScope ts = new TransactionScope())
+            {
+                string connStr = GetConnectionStringBasic(true);
+
+                using (MySqlConnection c1 = new MySqlConnection(connStr))
+                {
+                    c1.Open();
+
+                    using (MySqlConnection c2 = new MySqlConnection(connStr))
                     {
-                        c2.Open();
-                        MySqlCommand cmd2 = new MySqlCommand("INSERT INTO test VALUES
('b', 'name', 'name2')", c2);
-                        cmd2.ExecuteNonQuery();
+                        try
+                        {
+                            c2.Open();
+                        }
+                        catch (NotSupportedException)
+                        {
+                        }
                     }
+                }
+            }
+        }
 
-                    if (commit)
+        private void ReusingSameConnection(bool pooling, bool complete)
+        {
+            execSQL("TRUNCATE TABLE test");
+            using (TransactionScope ts = new
TransactionScope(TransactionScopeOption.RequiresNew, TimeSpan.MaxValue))
+            {
+                string connStr = GetConnectionStringBasic(true);
+                if (!pooling)
+                    connStr += ";pooling=false";
+
+                using (MySqlConnection c1 = new MySqlConnection(connStr))
+                {
+                    c1.Open();
+                    MySqlCommand cmd1 = new MySqlCommand("INSERT INTO test (key2) VALUES
('a')", c1);
+                    cmd1.ExecuteNonQuery();
+                }
+
+                using (MySqlConnection c2 = new MySqlConnection(connStr))
+                {
+                    c2.Open();
+                    MySqlCommand cmd2 = new MySqlCommand("INSERT INTO test (key2) VALUES
('b')", c2);
+                    cmd2.ExecuteNonQuery();
+                }
+
+                try
+                {
+                    if (complete)
                         ts.Complete();
                 }
+                catch (Exception ex)
+                {
+                    Assert.Fail(ex.Message);
+                }
+            }
 
-                MySqlCommand cmd3 = new MySqlCommand("SELECT COUNT(*) FROM test", conn);
-                object count = cmd3.ExecuteScalar();
-                Assert.AreEqual(commit ? 2 : 0, count);
+            MySqlDataAdapter da = new MySqlDataAdapter("SELECT * FROM test", conn);
+            DataTable dt = new DataTable();
+            da.Fill(dt);
+            if (complete)
+            {
+                Assert.AreEqual(2, dt.Rows.Count);
+                Assert.AreEqual("a", dt.Rows[0][0]);
+                Assert.AreEqual("b", dt.Rows[1][0]);
             }
-            catch (Exception ex)
+            else
             {
-                Assert.Fail(ex.Message);
+                Assert.AreEqual(0, dt.Rows.Count);
             }
         }
-        */
+
+        [Test]
+        public void ReusingSameConnection()
+        {
+            int processes = CountProcesses();
+
+            ReusingSameConnection(true, true);
+            Assert.AreEqual(processes + 1, CountProcesses());
+
+            ReusingSameConnection(true, false);
+            Assert.AreEqual(processes + 1, CountProcesses());
+
+            ReusingSameConnection(false, true);
+            Assert.AreEqual(processes + 1, CountProcesses());
+
+            ReusingSameConnection(false, false);
+            Assert.AreEqual(processes + 1, CountProcesses());
+        }
+
 #endif
 
     }

Thread
Connector/NET commit: r838 - in branches/5.1: . Driver/Properties Driver/Source TestSuite/Sourcerburnett7 Aug