diff --git a/src/SingleStoreConnector/Core/ICancellableCommand.cs b/src/SingleStoreConnector/Core/ICancellableCommand.cs
index eb766dfe7..ab42a87d6 100644
--- a/src/SingleStoreConnector/Core/ICancellableCommand.cs
+++ b/src/SingleStoreConnector/Core/ICancellableCommand.cs
@@ -9,9 +9,10 @@ internal interface ICancellableCommand
{
int CommandId { get; }
int CommandTimeout { get; }
+ int? EffectiveCommandTimeout { get; set; }
int CancelAttemptCount { get; set; }
SingleStoreConnection? Connection { get; }
- IDisposable? RegisterCancel(CancellationToken cancellationToken);
+ CancellationTokenRegistration RegisterCancel(CancellationToken cancellationToken);
void SetTimeout(int milliseconds);
bool IsTimedOut { get; }
}
@@ -23,22 +24,6 @@ internal static class ICancellableCommandExtensions
///
public static int GetNextId() => Interlocked.Increment(ref s_id);
- ///
- /// Returns the time (in seconds) until a command should be canceled, clamping it to the maximum time
- /// allowed including CancellationTimeout.
- ///
- public static int GetCommandTimeUntilCanceled(this ICancellableCommand command)
- {
- var commandTimeout = command.CommandTimeout;
- var session = command.Connection?.Session;
- if (commandTimeout == 0 || session is null)
- return 0;
-
- // the total cancellation period (graphically) is [===CommandTimeout===][=CancellationTimeout=], which can't
- // exceed int.MaxValue/1000 because it has to be multiplied by 1000 to be converted to milliseconds
- return Math.Min(commandTimeout, Math.Max(1, (int.MaxValue / 1000) - session.CancellationTimeout));
- }
-
///
/// Causes the effective command timeout to be reset back to the value specified by
/// plus . This allows for the command to time out, a cancellation to attempt
@@ -53,28 +38,53 @@ public static int GetCommandTimeUntilCanceled(this ICancellableCommand command)
/// method call.
public static void ResetCommandTimeout(this ICancellableCommand command)
{
+ // read value cached on the command
+ var effectiveCommandTimeout = command.EffectiveCommandTimeout;
+
+ // early out if there is no timeout
+ if (effectiveCommandTimeout == Constants.InfiniteTimeout)
+ return;
+
var session = command.Connection?.Session;
- if (session is not null)
+ if (session is null)
+ return;
+
+ // determine the effective command timeout if not already cached
+ if (effectiveCommandTimeout is null)
{
- if (command.CommandTimeout == 0 || session.CancellationTimeout == 0)
+ var commandTimeout = command.CommandTimeout;
+ var cancellationTimeout = session.CancellationTimeout;
+
+ if (commandTimeout == 0 || cancellationTimeout == 0)
{
- session.SetTimeout(Constants.InfiniteTimeout);
+ // if commandTimeout is zero, then cancellation doesn't occur
+ effectiveCommandTimeout = Constants.InfiniteTimeout;
}
else
{
- var commandTimeUntilCanceled = command.GetCommandTimeUntilCanceled() * 1000;
- if (session.CancellationTimeout > 0)
- {
- // try to cancel first, then close socket
- command.SetTimeout(commandTimeUntilCanceled);
- session.SetTimeout(commandTimeUntilCanceled + session.CancellationTimeout * 1000);
- }
- else
- {
- // close socket once the timeout is reached
- session.SetTimeout(commandTimeUntilCanceled);
- }
+ // the total cancellation period (graphically) is [===CommandTimeout===][=CancellationTimeout=], which can't
+ // exceed int.MaxValue/1000 because it has to be multiplied by 1000 to be converted to milliseconds
+ effectiveCommandTimeout = Math.Min(commandTimeout, Math.Max(1, (int.MaxValue / 1000) - Math.Max(0, session.CancellationTimeout))) * 1000;
}
+
+ command.EffectiveCommandTimeout = effectiveCommandTimeout;
+ }
+
+ if (effectiveCommandTimeout == Constants.InfiniteTimeout)
+ {
+ // for no timeout, we set an infinite timeout once (then early out above)
+ session.SetTimeout(Constants.InfiniteTimeout);
+ }
+ else if (session.CancellationTimeout > 0)
+ {
+ // try to cancel first, then close socket
+ command.SetTimeout(effectiveCommandTimeout.Value);
+ session.SetTimeout(effectiveCommandTimeout.Value + (session.CancellationTimeout * 1000));
+ }
+ else
+ {
+ // close socket once the timeout is reached
+ session.SetTimeout(effectiveCommandTimeout.Value);
}
}
diff --git a/src/SingleStoreConnector/Core/ResultSet.cs b/src/SingleStoreConnector/Core/ResultSet.cs
index 5260ee802..92e31ce9f 100644
--- a/src/SingleStoreConnector/Core/ResultSet.cs
+++ b/src/SingleStoreConnector/Core/ResultSet.cs
@@ -231,7 +231,6 @@ public async Task ReadAsync(IOBehavior ioBehavior, CancellationToken cance
if (BufferState is ResultSetState.HasMoreData or ResultSetState.NoMoreData or ResultSetState.None)
return new ValueTask(default(Row?));
- using var registration = Command.CancellableCommand.RegisterCancel(cancellationToken);
var payloadValueTask = Session.ReceiveReplyAsync(ioBehavior, CancellationToken.None);
return payloadValueTask.IsCompletedSuccessfully
? new ValueTask(ScanRowAsyncRemainder(this, payloadValueTask.Result, row))
diff --git a/src/SingleStoreConnector/Core/ServerSession.cs b/src/SingleStoreConnector/Core/ServerSession.cs
index 7eae823f2..1d118e55a 100644
--- a/src/SingleStoreConnector/Core/ServerSession.cs
+++ b/src/SingleStoreConnector/Core/ServerSession.cs
@@ -1104,7 +1104,11 @@ private async Task OpenTcpSocketAsync(ConnectionSettings cs, ILoadBalancer
{
if (ioBehavior == IOBehavior.Asynchronous)
{
+#if NET5_0_OR_GREATER
+ await tcpClient.ConnectAsync(ipAddress, cs.Port, cancellationToken).ConfigureAwait(false);
+#else
await tcpClient.ConnectAsync(ipAddress, cs.Port).ConfigureAwait(false);
+#endif
}
else
{
diff --git a/src/SingleStoreConnector/SingleStoreBatch.cs b/src/SingleStoreConnector/SingleStoreBatch.cs
index ecaea2a6c..d2c26ef3a 100644
--- a/src/SingleStoreConnector/SingleStoreBatch.cs
+++ b/src/SingleStoreConnector/SingleStoreBatch.cs
@@ -133,7 +133,7 @@ protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
private DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
#endif
{
- ((ICancellableCommand) this).ResetCommandTimeout();
+ this.ResetCommandTimeout();
return ExecuteReaderAsync(behavior, IOBehavior.Synchronous, CancellationToken.None).GetAwaiter().GetResult();
}
@@ -143,7 +143,7 @@ protected override async Task ExecuteDbDataReaderAsync(CommandBeha
private async Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
#endif
{
- ((ICancellableCommand) this).ResetCommandTimeout();
+ this.ResetCommandTimeout();
using var registration = ((ICancellableCommand) this).RegisterCancel(cancellationToken);
return await ExecuteReaderAsync(behavior, AsyncIOBehavior, cancellationToken).ConfigureAwait(false);
}
@@ -191,11 +191,19 @@ public Task ExecuteNonQueryAsync(CancellationToken cancellationToken = defa
#endif
ExecuteScalarAsync(AsyncIOBehavior, cancellationToken);
+ public
#if NET6_0_OR_GREATER
- public override int Timeout { get; set; }
-#else
- public int Timeout { get; set; }
+ override
#endif
+ int Timeout
+ {
+ get => m_timeout;
+ set
+ {
+ m_timeout = value;
+ ((ICancellableCommand) this).EffectiveCommandTimeout = null;
+ }
+ }
#if NET6_0_OR_GREATER
public override void Prepare()
@@ -247,12 +255,13 @@ public void Dispose()
int ICancellableCommand.CommandId => m_commandId;
int ICancellableCommand.CommandTimeout => Timeout;
+ int? ICancellableCommand.EffectiveCommandTimeout { get; set; }
int ICancellableCommand.CancelAttemptCount { get; set; }
- IDisposable? ICancellableCommand.RegisterCancel(CancellationToken cancellationToken)
+ CancellationTokenRegistration ICancellableCommand.RegisterCancel(CancellationToken cancellationToken)
{
if (!cancellationToken.CanBeCanceled)
- return null;
+ return default;
m_cancelAction ??= Cancel;
return cancellationToken.Register(m_cancelAction);
@@ -282,7 +291,7 @@ private void CancelCommandForTimeout()
private async Task ExecuteNonQueryAsync(IOBehavior ioBehavior, CancellationToken cancellationToken)
{
- ((ICancellableCommand) this).ResetCommandTimeout();
+ this.ResetCommandTimeout();
using var registration = ((ICancellableCommand) this).RegisterCancel(cancellationToken);
using var reader = await ExecuteReaderAsync(CommandBehavior.Default, ioBehavior, cancellationToken).ConfigureAwait(false);
do
@@ -296,7 +305,7 @@ private async Task ExecuteNonQueryAsync(IOBehavior ioBehavior, Cancellation
private async Task