Skip to content

Commit 2e3fd52

Browse files
committed
Use CommandTimeoutExpired consistently. Fixes #939
1 parent b9b0215 commit 2e3fd52

File tree

6 files changed

+46
-8
lines changed

6 files changed

+46
-8
lines changed

src/MySqlConnector/Core/ICancellableCommand.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ internal interface ICancellableCommand
1515
MySqlConnection? Connection { get; }
1616
IDisposable? RegisterCancel(CancellationToken cancellationToken);
1717
void SetTimeout(int milliseconds);
18+
bool IsTimedOut { get; }
1819
}
1920

2021
internal static class ICancellableCommandExtensions

src/MySqlConnector/Core/ResultSet.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,8 @@ public async Task<bool> ReadAsync(IOBehavior ioBehavior, CancellationToken cance
248248
resultSet.BufferState = resultSet.State = ResultSetState.NoMoreData;
249249
if (ex.ErrorCode == MySqlErrorCode.QueryInterrupted && token.IsCancellationRequested)
250250
throw new OperationCanceledException(ex.Message, ex, token);
251+
if (ex.ErrorCode == MySqlErrorCode.QueryInterrupted && resultSet.Command.CancellableCommand.IsTimedOut)
252+
throw MySqlException.CreateForTimeout(ex);
251253
throw;
252254
}
253255
return ScanRowAsyncRemainder(resultSet, payloadData, row);

src/MySqlConnector/MySqlBatch.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,16 +173,26 @@ public void Dispose()
173173

174174
void ICancellableCommand.SetTimeout(int milliseconds)
175175
{
176+
Volatile.Write(ref m_commandTimedOut, false);
177+
176178
if (m_cancelTimerId != 0)
177179
TimerQueue.Instance.Remove(m_cancelTimerId);
178180

179181
if (milliseconds != Constants.InfiniteTimeout)
180182
{
181-
m_cancelAction ??= Cancel;
182-
m_cancelTimerId = TimerQueue.Instance.Add(milliseconds, m_cancelAction);
183+
m_cancelForCommandTimeoutAction ??= CancelCommandForTimeout;
184+
m_cancelTimerId = TimerQueue.Instance.Add(milliseconds, m_cancelForCommandTimeoutAction);
183185
}
184186
}
185187

188+
bool ICancellableCommand.IsTimedOut => Volatile.Read(ref m_commandTimedOut);
189+
190+
private void CancelCommandForTimeout()
191+
{
192+
Volatile.Write(ref m_commandTimedOut, true);
193+
Cancel();
194+
}
195+
186196
private async Task<int> ExecuteNonQueryAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
187197
{
188198
((ICancellableCommand) this).ResetCommandTimeout();
@@ -309,6 +319,8 @@ private bool IsPrepared
309319
readonly int m_commandId;
310320
bool m_isDisposed;
311321
Action? m_cancelAction;
322+
Action? m_cancelForCommandTimeoutAction;
312323
uint m_cancelTimerId;
324+
bool m_commandTimedOut;
313325
}
314326
}

src/MySqlConnector/MySqlCommand.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,16 +363,20 @@ public override ValueTask DisposeAsync()
363363

364364
void ICancellableCommand.SetTimeout(int milliseconds)
365365
{
366+
Volatile.Write(ref m_commandTimedOut, false);
367+
366368
if (m_cancelTimerId != 0)
367369
TimerQueue.Instance.Remove(m_cancelTimerId);
368370

369371
if (milliseconds != Constants.InfiniteTimeout)
370372
{
371-
m_cancelAction ??= Cancel;
372-
m_cancelTimerId = TimerQueue.Instance.Add(milliseconds, m_cancelAction);
373+
m_cancelForCommandTimeoutAction ??= CancelCommandForTimeout;
374+
m_cancelTimerId = TimerQueue.Instance.Add(milliseconds, m_cancelForCommandTimeoutAction);
373375
}
374376
}
375377

378+
bool ICancellableCommand.IsTimedOut => Volatile.Read(ref m_commandTimedOut);
379+
376380
int ICancellableCommand.CommandId => m_commandId;
377381

378382
int ICancellableCommand.CancelAttemptCount { get; set; }
@@ -381,6 +385,12 @@ void ICancellableCommand.SetTimeout(int milliseconds)
381385

382386
private IOBehavior AsyncIOBehavior => Connection?.AsyncIOBehavior ?? IOBehavior.Asynchronous;
383387

388+
private void CancelCommandForTimeout()
389+
{
390+
Volatile.Write(ref m_commandTimedOut, true);
391+
Cancel();
392+
}
393+
384394
private bool IsValid([NotNullWhen(false)] out Exception? exception)
385395
{
386396
exception = null;
@@ -413,6 +423,8 @@ private bool IsValid([NotNullWhen(false)] out Exception? exception)
413423
CommandType m_commandType;
414424
CommandBehavior m_commandBehavior;
415425
Action? m_cancelAction;
426+
Action? m_cancelForCommandTimeoutAction;
416427
uint m_cancelTimerId;
428+
bool m_commandTimedOut;
417429
}
418430
}

src/MySqlConnector/MySqlDataReader.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ private void ActivateResultSet(CancellationToken cancellationToken)
127127
if (mySqlException?.ErrorCode == MySqlErrorCode.QueryInterrupted && cancellationToken.IsCancellationRequested)
128128
throw new OperationCanceledException(mySqlException.Message, mySqlException, cancellationToken);
129129

130+
if (mySqlException?.ErrorCode == MySqlErrorCode.QueryInterrupted && Command!.CancellableCommand.IsTimedOut)
131+
throw MySqlException.CreateForTimeout(mySqlException);
132+
130133
if (mySqlException is not null)
131134
throw new MySqlException(mySqlException.ErrorCode, mySqlException.SqlState, mySqlException.Message, mySqlException);
132135

tests/MySqlConnector.Tests/CancellationTests.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ public void Test(int step, int method)
4040
var stopwatch = Stopwatch.StartNew();
4141
var ex = Assert.Throws<MySqlException>(() => s_executeMethods[method](command));
4242
Assert.InRange(stopwatch.ElapsedMilliseconds, 900, 1500);
43-
Assert.Equal(MySqlErrorCode.QueryInterrupted, ex.ErrorCode);
43+
Assert.Equal(MySqlErrorCode.CommandTimeoutExpired, ex.ErrorCode);
44+
var inner = Assert.IsType<MySqlException>(ex.InnerException);
45+
Assert.Equal(MySqlErrorCode.QueryInterrupted, inner.ErrorCode);
4446

4547
// connection should still be usable
4648
Assert.Equal(ConnectionState.Open, connection.State);
@@ -63,7 +65,9 @@ public async Task Test(int step, int method)
6365
var stopwatch = Stopwatch.StartNew();
6466
var ex = await Assert.ThrowsAsync<MySqlException>(async () => await s_executeAsyncMethods[method](command, default));
6567
Assert.InRange(stopwatch.ElapsedMilliseconds, 900, 1500);
66-
Assert.Equal(MySqlErrorCode.QueryInterrupted, ex.ErrorCode);
68+
Assert.Equal(MySqlErrorCode.CommandTimeoutExpired, ex.ErrorCode);
69+
var inner = Assert.IsType<MySqlException>(ex.InnerException);
70+
Assert.Equal(MySqlErrorCode.QueryInterrupted, inner.ErrorCode);
6771
}
6872
}
6973

@@ -147,7 +151,9 @@ public void Timeout(int method)
147151
var stopwatch = Stopwatch.StartNew();
148152
var ex = Assert.Throws<MySqlException>(() => s_executeMethods[method](command));
149153
Assert.InRange(stopwatch.ElapsedMilliseconds, 900, 1500);
150-
Assert.Equal(MySqlErrorCode.QueryInterrupted, ex.ErrorCode);
154+
Assert.Equal(MySqlErrorCode.CommandTimeoutExpired, ex.ErrorCode);
155+
var inner = Assert.IsType<MySqlException>(ex.InnerException);
156+
Assert.Equal(MySqlErrorCode.QueryInterrupted, inner.ErrorCode);
151157
}
152158

153159
[SkipCITheory]
@@ -181,7 +187,9 @@ public async Task Timeout(int method)
181187
var stopwatch = Stopwatch.StartNew();
182188
var ex = await Assert.ThrowsAsync<MySqlException>(async () => await s_executeAsyncMethods[method](command, default));
183189
Assert.InRange(stopwatch.ElapsedMilliseconds, 900, 1500);
184-
Assert.Equal(MySqlErrorCode.QueryInterrupted, ex.ErrorCode);
190+
Assert.Equal(MySqlErrorCode.CommandTimeoutExpired, ex.ErrorCode);
191+
var inner = Assert.IsType<MySqlException>(ex.InnerException);
192+
Assert.Equal(MySqlErrorCode.QueryInterrupted, inner.ErrorCode);
185193
}
186194

187195
[SkipCITheory]

0 commit comments

Comments
 (0)