Skip to content

Commit ef4f7ee

Browse files
committed
Handle QueryInterrupted exceptions when canceling completed queries.
KILL QUERY will interrupt a subsequent query if the command it was intended to cancel has already completed, and the connection is idle. In order to handle this case, we issue a dummy query to catch the QueryInterrupted exception. See https://bugs.mysql.com/bug.php?id=45679.
1 parent d9780e4 commit ef4f7ee

File tree

4 files changed

+33
-0
lines changed

4 files changed

+33
-0
lines changed

src/MySqlConnector/MySqlClient/MySqlCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public override void Prepare()
6666

6767
public override string CommandText { get; set; }
6868
public override int CommandTimeout { get; set; }
69+
public bool IsCanceled { get; internal set; }
6970

7071
public override CommandType CommandType
7172
{

src/MySqlConnector/MySqlClient/MySqlDataReader.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,24 @@ private void DoClose()
287287
var connection = Command.Connection;
288288
if (!connection.BufferResultSets)
289289
connection.Session.FinishQuerying();
290+
291+
if (Command.IsCanceled)
292+
{
293+
// KILL QUERY will kill a subsequent query if the command it was intended to cancel has already completed.
294+
// In order to handle this case, we issue a dummy query to catch the QueryInterrupted exception.
295+
// See https://bugs.mysql.com/bug.php?id=45679
296+
var killClearCommand = new MySqlCommand("SELECT * FROM fake_table LIMIT 0;", connection);
297+
try
298+
{
299+
killClearCommand.ExecuteReader();
300+
}
301+
catch (MySqlException ex)
302+
{
303+
if (ex.Number != (int) MySqlErrorCode.QueryInterrupted && ex.Number != (int) MySqlErrorCode.NoSuchTable)
304+
throw;
305+
}
306+
}
307+
290308
Command.ReaderClosed();
291309
if ((m_behavior & CommandBehavior.CloseConnection) != 0)
292310
{

src/MySqlConnector/Serialization/MySqlSession.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public void DoCancel(MySqlCommand commandToCancel, MySqlCommand killCommand)
8484
// blocking the other thread for an extended duration.
8585
killCommand.CommandTimeout = 3;
8686
killCommand.ExecuteNonQuery();
87+
88+
commandToCancel.IsCanceled = true;
8789
}
8890
}
8991

tests/SideBySide/CancelTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,18 @@ public async Task CancelCommandWithTokenBeforeExecuteReader()
228228
}
229229
}
230230

231+
[Fact]
232+
public async Task CancelCompletedCommand()
233+
{
234+
using (var cmd = m_database.Connection.CreateCommand())
235+
{
236+
cmd.CommandText = @"select 1;";
237+
238+
using (await cmd.ExecuteReaderAsync().ConfigureAwait(false))
239+
cmd.Cancel();
240+
}
241+
}
242+
231243
[UnbufferedResultSetsFact]
232244
public async Task CancelHugeQueryWithTokenAfterExecuteReader()
233245
{

0 commit comments

Comments
 (0)