List:Commits« Previous MessageNext Message »
From:rburnett Date:July 24 2007 10:34pm
Subject:Connector/NET commit: r811 - in trunk: . Driver/Source TestSuite/Source
View as plain text  
Modified:
   trunk/CHANGES
   trunk/Driver/Source/command.cs
   trunk/TestSuite/Source/TimeoutAndCancel.cs
Log:
Fixed some serious issues with command timeout and cancel that could present as exceptions about thread ownership.  The issue was that not all queries cancel the same.  Some produce resultsets while others don't.  ExecuteReader had to be changed to check for this.

Modified: trunk/CHANGES
===================================================================
--- trunk/CHANGES	2007-07-24 22:31:42 UTC (rev 810)
+++ trunk/CHANGES	2007-07-24 22:34:25 UTC (rev 811)
@@ -83,6 +83,10 @@
   - Fixed problem where an attempt to open a connection max pool size times while
     the server is down will prevent any further attempts due to the pool semaphore
     being full. (Bug #29409)    
+  - Fixed some serious issues with command timeout and cancel that could present
+    as exceptions about thread ownership.  The issue was that not all queries 
+    cancel the same.  Some produce resultsets while others don't.  ExecuteReader
+    had to be changed to check for this.
     
 Version 5.0.7 5/16/2007
 

Modified: trunk/Driver/Source/command.cs
===================================================================
--- trunk/Driver/Source/command.cs	2007-07-24 22:31:42 UTC (rev 810)
+++ trunk/Driver/Source/command.cs	2007-07-24 22:34:25 UTC (rev 811)
@@ -54,8 +54,7 @@
 		private PreparableStatement statement;
 		private int commandTimeout;
 		private bool canCancel;
-		private bool timedOut;
-        private AutoResetEvent querySent;
+		private bool timedOut, canceled;
         private bool resetSqlSelect;
 
 		/// <include file='docs/mysqlcommand.xml' path='docs/ctor1/*'/>
@@ -71,7 +70,6 @@
 			commandTimeout = 30;
 			canCancel = false;
 			timedOut = false;
-            querySent = new AutoResetEvent(false);
 		}
 
 		/// <include file='docs/mysqlcommand.xml' path='docs/ctor2/*'/>
@@ -239,13 +237,17 @@
 			if (!connection.driver.Version.isAtLeast(5, 0, 0))
 				throw new NotSupportedException(Resources.CancelNotSupported);
 
-			MySqlConnection c = new MySqlConnection(connection.Settings.GetConnectionString(true));
-			try
+            // if we are not inside ExecuteReader, then cancel will not do
+            // anything
+            if (!canCancel) return;
+
+			using(MySqlConnection c = new MySqlConnection(connection.Settings.GetConnectionString(true)))
 			{
                 c.Open();
                 MySqlCommand cmd = new MySqlCommand(String.Format("KILL QUERY {0}",
                      connection.ServerThread), c);
                 cmd.ExecuteNonQuery();
+                canceled = true;
 			}
 			catch (Exception)
 			{
@@ -327,8 +329,6 @@
 		private void TimeoutExpired(object commandObject)
 		{
 			MySqlCommand cmd = (commandObject as MySqlCommand);
-            // wait for the query to be sent
-            querySent.WaitOne();
 
             if (cmd.canCancel)
 			{
@@ -391,33 +391,43 @@
 
 			updatedRowCount = -1;
 
+            Timer timer = null;
             try
             {
                 MySqlDataReader reader = new MySqlDataReader(this, statement, behavior);
 
                 // start a threading timer on our command timeout 
                 timedOut = false;
-                Timer t = null;
-                querySent.Reset();
-#if !DEBUG
+                canceled = false;
+
+                // execute the statement
+                statement.Execute();
+
+                // indicate that we can now be canceled
+                canCancel = true;
+
+                // start a timeout timer
                 if (connection.driver.Version.isAtLeast(5, 0, 0) &&
                      commandTimeout > 0)
                 {
                     TimerCallback timerDelegate =
                          new TimerCallback(TimeoutExpired);
-                    t = new Timer(timerDelegate, this, this.CommandTimeout * 1000, Timeout.Infinite);
+                    timer = new Timer(timerDelegate, this, this.CommandTimeout * 1000, Timeout.Infinite);
                 }
-#endif
 
-                // execute the statement
-                statement.Execute();
-                querySent.Set();
-
-                canCancel = true;
+                // wait for data to return
                 reader.NextResult();
-                if (t != null)
-                    t.Dispose();
+                
+                // if we get here, then we have started receiving data and
+                // can't cancel anymore
                 canCancel = false;
+
+                // if we were canceled or timed out and an exception has not 
+                // yet been thrown, then we need to consume the reader and
+                // that will throw the exception
+                if (canceled || timedOut)
+                    reader.Close();
+
                 connection.Reader = reader;
                 return reader;
             }
@@ -434,8 +444,9 @@
             }
             finally
             {
-                querySent.Reset();
                 canCancel = false;
+                if (timer != null)
+                    timer.Dispose();
             }
 		}
 

Modified: trunk/TestSuite/Source/TimeoutAndCancel.cs
===================================================================
--- trunk/TestSuite/Source/TimeoutAndCancel.cs	2007-07-24 22:31:42 UTC (rev 810)
+++ trunk/TestSuite/Source/TimeoutAndCancel.cs	2007-07-24 22:34:25 UTC (rev 811)
@@ -62,11 +62,14 @@
             if (version < new Version(5, 0)) return;
 
             // first we need a routine that will run for a bit
-            execSQL("CREATE PROCEDURE spTest() BEGIN SET @start=NOW()+0; REPEAT SET @end=NOW()-@start; " +
-                "UNTIL @end >= 5000 END REPEAT; SELECT @start, @end; END");
+            execSQL(@"CREATE PROCEDURE spTest(duration INT) 
+                BEGIN 
+                    SELECT SLEEP(duration);
+                END");
 
             MySqlCommand cmd = new MySqlCommand("spTest", conn);
             cmd.CommandType = CommandType.StoredProcedure;
+            cmd.Parameters.AddWithValue("duration", 60);
 
             // now we start execution of the command
             CommandInvokerDelegate d = new CommandInvokerDelegate(CommandRunner);
@@ -126,22 +129,25 @@
             if (version < new Version(5, 0)) return;
 
             // first we need a routine that will run for a bit
-            execSQL("CREATE PROCEDURE spTest() BEGIN SET @start=UNIX_TIMESTAMP(NOW()); " +
-                "REPEAT SET @end=UNIX_TIMESTAMP(NOW())-@start; " +
-                "UNTIL @end >= 60 END REPEAT; SELECT @start, @end; END");
+            execSQL(@"CREATE PROCEDURE spTest(duration INT) 
+                BEGIN 
+                    SELECT SLEEP(duration);
+                END");
 
             DateTime start = DateTime.Now;
             try
             {
                 MySqlCommand cmd = new MySqlCommand("spTest", conn);
+                cmd.Parameters.AddWithValue("duration", 60);
                 cmd.CommandType = CommandType.StoredProcedure;
-                cmd.CommandTimeout = 10;
+                cmd.CommandTimeout = 5;
                 cmd.ExecuteNonQuery();
                 Assert.Fail("Should not get to this point");
             }
             catch (MySqlException ex)
             {
                 TimeSpan ts = DateTime.Now.Subtract(start);
+                Assert.IsTrue(ts.TotalSeconds <= 10);
                 Assert.IsTrue(ex.Message.StartsWith("Timeout expired"), "Message is wrong");
             }
         }
@@ -153,12 +159,15 @@
             if (version < new Version(5, 0)) return;
 
             // first we need a routine that will run for a bit
-            execSQL("CREATE PROCEDURE spTest() BEGIN SET @start=NOW()+0; REPEAT SET @end=NOW()-@start; " +
-                "UNTIL @end >= 5 END REPEAT; SELECT @start, @end; END");
+            execSQL(@"CREATE PROCEDURE spTest(duration INT) 
+                BEGIN 
+                    SELECT SLEEP(duration);
+                END");
 
             try
             {
                 MySqlCommand cmd = new MySqlCommand("spTest", conn);
+                cmd.Parameters.AddWithValue("duration", 10);
                 cmd.CommandType = CommandType.StoredProcedure;
                 cmd.CommandTimeout = 15;
                 cmd.ExecuteNonQuery();
@@ -168,5 +177,33 @@
                 Assert.Fail(ex.Message);
             }
         }
+
+        [Category("5.0")]
+        [Test]
+        public void TimeoutDuringBatch()
+        {
+            execSQL(@"CREATE PROCEDURE spTest(duration INT) 
+                BEGIN 
+                    SELECT SLEEP(duration);
+                END");
+
+            execSQL("DROP TABLE IF EXISTS test");
+            execSQL("CREATE TABLE test (id INT)");
+
+            MySqlCommand cmd = new MySqlCommand(
+                "call spTest(10);INSERT INTO test VALUES(4)", conn);
+            cmd.CommandTimeout = 5;
+            try
+            {
+                cmd.ExecuteNonQuery();
+            }
+            catch (MySqlException ex)
+            {
+                Assert.IsTrue(ex.Message.StartsWith("Timeout expired"), "Message is wrong");
+            }
+
+            cmd.CommandText = "SELECT COUNT(*) FROM test";
+            Assert.AreEqual(0, cmd.ExecuteScalar());
+        }
     }
 }

Thread
Connector/NET commit: r811 - in trunk: . Driver/Source TestSuite/Sourcerburnett25 Jul