Skip to content

Commit 13cfd38

Browse files
committed
Move "KILL QUERY" fix into MySqlSesssion.
Track waiting for a pending cancellation as part of MySqlSession.m_state (instead of a separate property on MySqlCommand) and execute "DO SLEEP(0);" directly on the session.
1 parent fee93b1 commit 13cfd38

File tree

4 files changed

+48
-14
lines changed

4 files changed

+48
-14
lines changed

src/MySqlConnector/MySqlClient/MySqlCommand.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ 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; }
7069

7170
public override CommandType CommandType
7271
{

src/MySqlConnector/MySqlClient/MySqlDataReader.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -288,15 +288,6 @@ private void DoClose()
288288
if (!connection.BufferResultSets)
289289
connection.Session.FinishQuerying();
290290

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("DO SLEEP(0);", connection);
297-
killClearCommand.ExecuteNonQuery();
298-
}
299-
300291
Command.ReaderClosed();
301292
if ((m_behavior & CommandBehavior.CloseConnection) != 0)
302293
{

src/MySqlConnector/Serialization/MySqlSession.cs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,6 @@ 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;
8987
}
9088
}
9189

@@ -125,9 +123,30 @@ public void SetActiveReader(MySqlDataReader dataReader)
125123

126124
public void FinishQuerying()
127125
{
126+
bool clearConnection = false;
128127
lock (m_lock)
129128
{
130-
VerifyState(State.Querying, State.CancelingQuery);
129+
if (m_state == State.CancelingQuery)
130+
{
131+
m_state = State.ClearingPendingCancellation;
132+
clearConnection = true;
133+
}
134+
}
135+
136+
if (clearConnection)
137+
{
138+
// KILL QUERY will kill a subsequent query if the command it was intended to cancel has already completed.
139+
// In order to handle this case, we issue a dummy query that will consume the pending cancellation.
140+
// See https://bugs.mysql.com/bug.php?id=45679
141+
var payload = new PayloadData(new ArraySegment<byte>(PayloadUtilities.CreateEofStringPayload(CommandKind.Query, "DO SLEEP(0);")));
142+
SendAsync(payload, IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult();
143+
payload = ReceiveReplyAsync(IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult();
144+
OkPayload.Create(payload);
145+
}
146+
147+
lock (m_lock)
148+
{
149+
VerifyState(State.Querying, State.ClearingPendingCancellation);
131150
m_state = State.Connected;
132151
m_activeReader = null;
133152
m_activeCommand = null;
@@ -313,7 +332,7 @@ private void VerifyConnected()
313332
{
314333
if (m_state == State.Closed)
315334
throw new ObjectDisposedException(nameof(MySqlSession));
316-
if (m_state != State.Connected && m_state != State.Querying && m_state != State.CancelingQuery && m_state != State.Closing)
335+
if (m_state != State.Connected && m_state != State.Querying && m_state != State.CancelingQuery && m_state != State.ClearingPendingCancellation && m_state != State.Closing)
317336
throw new InvalidOperationException("MySqlSession is not connected.");
318337
}
319338
}
@@ -669,6 +688,9 @@ private enum State
669688
// The session is connected to a server and the active query is being cancelled.
670689
CancelingQuery,
671690

691+
// A cancellation is pending on the server and needs to be cleared.
692+
ClearingPendingCancellation,
693+
672694
// The session is closing.
673695
Closing,
674696

tests/SideBySide/CancelTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,28 @@ value varchar(45)
251251

252252
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
253253
}
254+
255+
using (var cmd = m_database.Connection.CreateCommand())
256+
{
257+
cmd.CommandText = "select value from cancel_completed_command where id = 1;";
258+
var value = (string) await cmd.ExecuteScalarAsync();
259+
Assert.Equal("value", value);
260+
}
261+
}
262+
263+
[Fact]
264+
public void ImplicitCancelWithDapper()
265+
{
266+
m_database.Connection.Execute(@"drop table if exists cancel_completed_command;
267+
create table cancel_completed_command(id integer not null primary key, value text null);");
268+
269+
// a query that returns 0 fields will cause Dapper to cancel the command
270+
m_database.Connection.Query<int>("insert into cancel_completed_command(id, value) values (1, null);");
271+
272+
m_database.Connection.Execute("update cancel_completed_command set value = 'value' where id = 1;");
273+
274+
var value = m_database.Connection.Query<string>(@"select value from cancel_completed_command where id = 1").FirstOrDefault();
275+
Assert.Equal("value", value);
254276
}
255277

256278
[UnbufferedResultSetsFact]

0 commit comments

Comments
 (0)