Skip to content

Commit 67b1f46

Browse files
committed
MySqlCommand can be changed while another is executing. Fixes #867
1 parent d6153e8 commit 67b1f46

File tree

4 files changed

+32
-10
lines changed

4 files changed

+32
-10
lines changed

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public ServerSession(ConnectionPool? pool, int poolGeneration, int id)
5151

5252
public string Id { get; }
5353
public ServerVersion ServerVersion { get; set; }
54+
public int ActiveCommandId { get; private set; }
5455
public int ConnectionId { get; set; }
5556
public byte[]? AuthPluginData { get; set; }
5657
public uint CreatedTicks { get; }
@@ -90,7 +91,7 @@ public bool TryStartCancel(ICancellableCommand command)
9091
{
9192
lock (m_lock)
9293
{
93-
if (m_activeCommandId != command.CommandId)
94+
if (ActiveCommandId != command.CommandId)
9495
return false;
9596
VerifyState(State.Querying, State.CancelingQuery, State.Failed);
9697
if (m_state != State.Querying)
@@ -109,11 +110,11 @@ public void DoCancel(ICancellableCommand commandToCancel, MySqlCommand killComma
109110
Log.Info("Session{0} canceling CommandId {1}: CommandText: {2}", m_logArguments[0], commandToCancel.CommandId, (commandToCancel as MySqlCommand)?.CommandText);
110111
lock (m_lock)
111112
{
112-
if (m_activeCommandId != commandToCancel.CommandId)
113+
if (ActiveCommandId != commandToCancel.CommandId)
113114
return;
114115

115116
// NOTE: This command is executed while holding the lock to prevent race conditions during asynchronous cancellation.
116-
// For example, if the lock weren't held, the current command could finish and the other thread could set m_activeCommandId
117+
// For example, if the lock weren't held, the current command could finish and the other thread could set ActiveCommandId
117118
// to zero, then start executing a new command. By the time this "KILL QUERY" command reached the server, the wrong
118119
// command would be killed (because "KILL QUERY" specifies the connection whose command should be killed, not
119120
// a unique identifier of the command itself). As a mitigation, we set the CommandTimeout to a low value to avoid
@@ -127,7 +128,7 @@ public void AbortCancel(ICancellableCommand command)
127128
{
128129
lock (m_lock)
129130
{
130-
if (m_activeCommandId == command.CommandId && m_state == State.CancelingQuery)
131+
if (ActiveCommandId == command.CommandId && m_state == State.CancelingQuery)
131132
m_state = State.Querying;
132133
}
133134
}
@@ -253,7 +254,7 @@ public void StartQuerying(ICancellableCommand command)
253254
m_state = State.Querying;
254255

255256
command.CancelAttemptCount = 0;
256-
m_activeCommandId = command.CommandId;
257+
ActiveCommandId = command.CommandId;
257258
}
258259
}
259260

@@ -289,7 +290,7 @@ public void FinishQuerying()
289290
m_state = State.Connected;
290291
else
291292
VerifyState(State.Failed);
292-
m_activeCommandId = 0;
293+
ActiveCommandId = 0;
293294
}
294295
}
295296

@@ -1622,7 +1623,6 @@ private enum State
16221623
SslStream? m_sslStream;
16231624
X509Certificate2? m_clientCertificate;
16241625
IPayloadHandler? m_payloadHandler;
1625-
int m_activeCommandId;
16261626
bool m_useCompression;
16271627
bool m_isSecureConnection;
16281628
bool m_supportsComMulti;

src/MySqlConnector/MySqlCommand.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ private bool NeedsPrepare(out Exception? exception)
157157
else if (string.IsNullOrWhiteSpace(CommandText))
158158
exception = new InvalidOperationException("CommandText must be specified");
159159
else if (Connection?.HasActiveReader ?? false)
160-
exception = new InvalidOperationException("Cannot call Prepare when there is an open DataReader for this command; it must be closed first.");
160+
exception = new InvalidOperationException("Cannot call Prepare when there is an open DataReader for this command's connection; it must be closed first.");
161161

162162
if (exception is not null || Connection!.IgnorePrepare)
163163
return false;
@@ -179,7 +179,7 @@ public override string CommandText
179179
get => m_commandText;
180180
set
181181
{
182-
if (m_connection?.HasActiveReader ?? false)
182+
if (m_connection?.ActiveCommandId == m_commandId)
183183
throw new InvalidOperationException("Cannot set MySqlCommand.CommandText when there is an open DataReader for this command; it must be closed first.");
184184
m_commandText = value ?? "";
185185
}
@@ -194,7 +194,7 @@ public override string CommandText
194194
get => m_connection;
195195
set
196196
{
197-
if (m_connection?.HasActiveReader ?? false)
197+
if (m_connection?.ActiveCommandId == m_commandId)
198198
throw new InvalidOperationException("Cannot set MySqlCommand.Connection when there is an open DataReader for this command; it must be closed first.");
199199
m_connection = value;
200200
}

src/MySqlConnector/MySqlConnection.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,8 @@ internal void Cancel(ICancellableCommand command)
765765

766766
internal MySqlSslMode SslMode => GetInitializedConnectionSettings().SslMode;
767767

768+
internal int? ActiveCommandId => m_session?.ActiveCommandId;
769+
768770
internal bool HasActiveReader => m_activeReader is not null;
769771

770772
internal void SetActiveReader(MySqlDataReader dataReader)

tests/SideBySide/CommandTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,26 @@ public void CommandsAreIndependent()
300300
Assert.True(reader.Read());
301301
}
302302

303+
[Fact]
304+
public void ExecutingCommandsAreIndependent()
305+
{
306+
using var connection = new MySqlConnection(AppConfig.ConnectionString);
307+
connection.Open();
308+
309+
using var cmd1 = connection.CreateCommand();
310+
cmd1.CommandText = "SELECT 1;";
311+
using var reader1 = cmd1.ExecuteReader();
312+
313+
using var cmd2 = connection.CreateCommand();
314+
cmd2.CommandText = "SELECT 'abc';";
315+
316+
#if BASELINE
317+
Assert.Throws<MySqlException>(() => cmd2.ExecuteReader());
318+
#else
319+
Assert.Throws<InvalidOperationException>(() => cmd2.ExecuteReader());
320+
#endif
321+
}
322+
303323
private static string GetIgnoreCommandTransactionConnectionString()
304324
{
305325
#if BASELINE

0 commit comments

Comments
 (0)