From: Date: August 7 2007 8:29pm
Subject: Connector/NET commit: r838 - in branches/5.1: . Driver/Properties Driver/Source TestSuite/Source
List-Archive: http://lists.mysql.com/commits/32216
X-Bug: 28709
Message-Id: <200708071829.l77ITLYR007941@bk-internal.mysql.com>
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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);
+ }
+ }
+
///
/// Looks up a localized string similar to NamedPipeStream does not support seeking.
///
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 @@
The object is not open or has been disposed.
-
+
+ Multiple simultaneous connections or connections with different connection strings inside the same transaction are not currently supported.
+
+
\ 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 @@
///
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);
}
- ///
- 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();
+ }
+ ///
+ 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 @@
///
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 @@
///
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
}