diff --git a/StackExchange.Redis.sln b/StackExchange.Redis.sln index 18a30a9af..104511e31 100644 --- a/StackExchange.Redis.sln +++ b/StackExchange.Redis.sln @@ -103,31 +103,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{00CA0876-DA9 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "toys", "toys", "{E25031D3-5C64-430D-B86F-697B66816FD8}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{153A10E4-E668-41AD-9E0F-6785CE7EED66}" - ProjectSection(SolutionItems) = preProject - docs\Basics.md = docs\Basics.md - docs\Configuration.md = docs\Configuration.md - docs\Events.md = docs\Events.md - docs\ExecSync.md = docs\ExecSync.md - docs\index.md = docs\index.md - docs\KeysScan.md = docs\KeysScan.md - docs\KeysValues.md = docs\KeysValues.md - docs\PipelinesMultiplexers.md = docs\PipelinesMultiplexers.md - docs\Profiling.md = docs\Profiling.md - docs\Profiling_v1.md = docs\Profiling_v1.md - docs\Profiling_v2.md = docs\Profiling_v2.md - docs\PubSubOrder.md = docs\PubSubOrder.md - docs\ReleaseNotes.md = docs\ReleaseNotes.md - docs\Resp3.md = docs\Resp3.md - docs\RespLogging.md = docs\RespLogging.md - docs\Scripting.md = docs\Scripting.md - docs\Server.md = docs\Server.md - docs\Testing.md = docs\Testing.md - docs\ThreadTheft.md = docs\ThreadTheft.md - docs\Timeouts.md = docs\Timeouts.md - docs\Transactions.md = docs\Transactions.md - EndProjectSection -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestConsoleBaseline", "toys\TestConsoleBaseline\TestConsoleBaseline.csproj", "{D58114AE-4998-4647-AFCA-9353D20495AE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = ".github", ".github\.github.csproj", "{8FB98E7D-DAE2-4465-BD9A-104000E0A2D4}" @@ -142,6 +117,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleTest", "tests\Consol EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleTestBaseline", "tests\ConsoleTestBaseline\ConsoleTestBaseline.csproj", "{69A0ACF2-DF1F-4F49-B554-F732DCA938A3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "docs", "docs\docs.csproj", "{64CF03B6-6B29-4C4C-88B8-7B9E317D631A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -192,6 +169,10 @@ Global {69A0ACF2-DF1F-4F49-B554-F732DCA938A3}.Debug|Any CPU.Build.0 = Debug|Any CPU {69A0ACF2-DF1F-4F49-B554-F732DCA938A3}.Release|Any CPU.ActiveCfg = Release|Any CPU {69A0ACF2-DF1F-4F49-B554-F732DCA938A3}.Release|Any CPU.Build.0 = Release|Any CPU + {64CF03B6-6B29-4C4C-88B8-7B9E317D631A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64CF03B6-6B29-4C4C-88B8-7B9E317D631A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64CF03B6-6B29-4C4C-88B8-7B9E317D631A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64CF03B6-6B29-4C4C-88B8-7B9E317D631A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -209,7 +190,6 @@ Global {D082703F-1652-4C35-840D-7D377F6B9979} = {96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05} {8375813E-FBAF-4DA3-A2C7-E4645B39B931} = {E25031D3-5C64-430D-B86F-697B66816FD8} {3DA1EEED-E9FE-43D9-B293-E000CFCCD91A} = {E25031D3-5C64-430D-B86F-697B66816FD8} - {153A10E4-E668-41AD-9E0F-6785CE7EED66} = {3AD17044-6BFF-4750-9AC2-2CA466375F2A} {D58114AE-4998-4647-AFCA-9353D20495AE} = {E25031D3-5C64-430D-B86F-697B66816FD8} {A9F81DA3-DA82-423E-A5DD-B11C37548E06} = {96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05} {A0F89B8B-32A3-4C28-8F1B-ADE343F16137} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} diff --git a/docs/CancellationTimeout.md b/docs/CancellationTimeout.md new file mode 100644 index 000000000..a7eaef252 --- /dev/null +++ b/docs/CancellationTimeout.md @@ -0,0 +1,77 @@ +# Ambient Cancellation Support + +StackExchange.Redis supports cancellation / timeout of operations. Because this feature impacts all operations, rather than add new parameters +to every method, it uses a declarative scope - an "ambient" context. This uses the `AsyncLocal` feature, allowing for meaning: + +- unrelated code-paths (threads or async-contexts) can have different values without conflicting with each-other +- the value is correctly propagated between `async`/`await` code + +## Usage + +### Timeout + +Timeouts are probably the most common cancellation scenario, so is exposed directly: + +```csharp +using (database.Multiplexer.WithTimeout(TimeSpan.FromSeconds(5))) +// using (database.Multiplexer.WithTimeout(5_000)) // identical +{ + await database.StringSetAsync("key", "value"); + var value = await database.StringGetAsync("key"); + // operations will be cancelled when the *combined* time (i.e. from the `WithTimeout` call) exceeds 5 seconds +} +``` + +### Cancellation + +You can also use `CancellationToken` to drive cancellation: + +```csharp +CancellationToken token = ...; // for example, from HttpContext.RequestAborted +using (database.Multiplexer.WithCancellation(token)) +{ + await database.StringSetAsync("key", "value"); + var value = await database.StringGetAsync("key"); + // both operations use the cancellation token +} +``` +### Combined Cancellation and Timeout + +These two concepts can be combined: + +```csharp +CancellationToken token = ...; // for example, from HttpContext.RequestAborted +using (database.Multiplexer.WithCancellationAndTimeout(token, TimeSpan.FromSeconds(10))) +// using (database.Multiplexer.WithCancellationAndTimeout(token, 10_000)) // identical +{ + await database.StringSetAsync("key", "value"); + var value = await database.StringGetAsync("key"); + // operations use the cancellation token *and* observe the specified timeout +} +``` + +### Nested Scopes + +Timeout/cancellation scopes can be nested, with the inner scope *replacing* the outer scope for that database: + +```csharp +using (database.Multiplexer.WithCancellation(yourToken)) +{ + await database.StringSetAsync("key1", "value1"); // Uses yourToken + + using (database.Multiplexer.WithTimeout(5000)) + { + await database.StringSetAsync("key2", "value2"); // Uses 5s timeout, but does *not* observe yourToken + } + + await database.StringSetAsync("key3", "value3"); // Uses yourToken +} +``` + +Consequently, timeout/cancellation can be suppressed by using `.WithCancellation(CancellationToken.None)`. + +## Multiplexer scope + +The scope of a `WithTimeout` (etc) call is tied to the *multiplexer*, hence the typical usage of `database.Multiplexer.WithTimeout(...)`. +Usually, there is only a single multiplexer in use, but this choice ensures that there are no surprises by library code outside of +your control / knowledge being impacted by your local cancellation / timeout choices. \ No newline at end of file diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index b3a788286..59346dbbe 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -11,6 +11,7 @@ Current package versions: - Add support for new `BITOP` operations in CE 8.2 ([#2900 by atakavci](https://github.com/StackExchange/StackExchange.Redis/pull/2900)) - Package updates ([#2906 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2906)) - Fix handshake error with `CLIENT ID` ([#2909 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2909)) +- Support scoped timeout and cancellation ([#2908 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2908)) ## 2.8.41 diff --git a/docs/docs.csproj b/docs/docs.csproj new file mode 100644 index 000000000..977e065bc --- /dev/null +++ b/docs/docs.csproj @@ -0,0 +1,6 @@ + + + + netstandard2.0 + + diff --git a/src/StackExchange.Redis/ChannelMessageQueue.cs b/src/StackExchange.Redis/ChannelMessageQueue.cs index e58fb393b..08e018918 100644 --- a/src/StackExchange.Redis/ChannelMessageQueue.cs +++ b/src/StackExchange.Redis/ChannelMessageQueue.cs @@ -184,7 +184,7 @@ private async Task OnMessageSyncImpl() catch (ChannelClosedException) { break; } // expected catch (Exception ex) { - _parent?.multiplexer?.OnInternalError(ex); + _parent?.Multiplexer?.OnInternalError(ex); break; } @@ -305,7 +305,7 @@ private async Task OnMessageAsyncImpl() catch (ChannelClosedException) { break; } // expected catch (Exception ex) { - _parent?.multiplexer?.OnInternalError(ex); + _parent?.Multiplexer?.OnInternalError(ex); break; } diff --git a/src/StackExchange.Redis/Condition.cs b/src/StackExchange.Redis/Condition.cs index 308c87c11..c1b5f945a 100644 --- a/src/StackExchange.Redis/Condition.cs +++ b/src/StackExchange.Redis/Condition.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; namespace StackExchange.Redis { @@ -391,7 +392,7 @@ private class ConditionMessage : Message.CommandKeyBase private readonly RedisValue value1; public ConditionMessage(Condition condition, int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value) - : base(db, flags, command, key) + : base(db, flags, command, key, CancellationToken.None) // once we're inside a transaction: just issue the commands { Condition = condition; this.value = value; // note no assert here @@ -468,7 +469,7 @@ public override string ToString() => internal override IEnumerable CreateMessages(int db, IResultBox? resultBox) { - yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key, CancellationToken.None); var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key, expectedValue); message.SetSource(ConditionProcessor.Default, resultBox); @@ -537,7 +538,7 @@ public override string ToString() => internal sealed override IEnumerable CreateMessages(int db, IResultBox? resultBox) { - yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key, CancellationToken.None); var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key, memberName); message.SetSource(ConditionProcessor.Default, resultBox); @@ -608,7 +609,7 @@ public override string ToString() => internal sealed override IEnumerable CreateMessages(int db, IResultBox? resultBox) { - yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key, CancellationToken.None); var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, RedisCommand.LINDEX, key, index); message.SetSource(ConditionProcessor.Default, resultBox); @@ -681,7 +682,7 @@ public LengthCondition(in RedisKey key, RedisType type, int compareToResult, lon internal sealed override IEnumerable CreateMessages(int db, IResultBox? resultBox) { - yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key, CancellationToken.None); var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, cmd, key); message.SetSource(ConditionProcessor.Default, resultBox); @@ -738,7 +739,7 @@ public override string ToString() => internal sealed override IEnumerable CreateMessages(int db, IResultBox? resultBox) { - yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key, CancellationToken.None); // inside a tran: just issue the command var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, RedisCommand.ZCOUNT, key, min, max); message.SetSource(ConditionProcessor.Default, resultBox); @@ -794,7 +795,7 @@ public override string ToString() => internal sealed override IEnumerable CreateMessages(int db, IResultBox? resultBox) { - yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key); + yield return Message.Create(db, CommandFlags.None, RedisCommand.WATCH, key, resultBox?.CancellationToken ?? CancellationToken.None); var message = ConditionProcessor.CreateMessage(this, db, CommandFlags.None, RedisCommand.ZCOUNT, key, sortedSetScore, sortedSetScore); message.SetSource(ConditionProcessor.Default, resultBox); @@ -832,10 +833,12 @@ public sealed class ConditionResult private volatile bool wasSatisfied; - internal ConditionResult(Condition condition) + internal CancellationToken CancellationToken => resultBox?.CancellationToken ?? CancellationToken.None; + + internal ConditionResult(Condition condition, CancellationToken cancellationToken) { Condition = condition; - resultBox = SimpleResultBox.Create(); + resultBox = SimpleResultBox.Create(cancellationToken); } /// diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.cs b/src/StackExchange.Redis/ConnectionMultiplexer.cs index e17a9503b..c5dfc1d6d 100644 --- a/src/StackExchange.Redis/ConnectionMultiplexer.cs +++ b/src/StackExchange.Redis/ConnectionMultiplexer.cs @@ -34,7 +34,7 @@ public sealed partial class ConnectionMultiplexer : IInternalConnectionMultiplex /// internal int _connectAttemptCount = 0, _connectCompletedCount = 0, _connectionCloseCount = 0; internal long syncOps, asyncOps; - private long syncTimeouts, fireAndForgets, asyncTimeouts; + private long syncTimeouts, fireAndForgets, asyncTimeouts, cancelledPreSend; private string? failureMessage, activeConfigCause; private TimerToken? pulse; @@ -206,6 +206,8 @@ internal async Task MakePrimaryAsync(ServerEndPoint server, ReplicationChangeOpt var cmd = server.GetFeatures().ReplicaCommands ? RedisCommand.REPLICAOF : RedisCommand.SLAVEOF; CommandMap.AssertAvailable(cmd); + var cancellationToken = this.GetEffectiveCancellationToken(); + if (!RawConfig.AllowAdmin) { throw ExceptionFactory.AdminModeNotEnabled(RawConfig.IncludeDetailInExceptions, cmd, null, server); @@ -242,7 +244,7 @@ internal async Task MakePrimaryAsync(ServerEndPoint server, ReplicationChangeOpt { if (!node.IsConnected || node.IsReplica) continue; log?.LogInformation($"Attempting to set tie-breaker on {Format.ToString(node.EndPoint)}..."); - msg = Message.Create(0, flags | CommandFlags.FireAndForget, RedisCommand.SET, tieBreakerKey, newPrimary); + msg = Message.Create(0, flags | CommandFlags.FireAndForget, RedisCommand.SET, tieBreakerKey, newPrimary, cancellationToken); try { await node.WriteDirectAsync(msg, ResultProcessor.DemandOK).ForAwait(); @@ -267,7 +269,7 @@ internal async Task MakePrimaryAsync(ServerEndPoint server, ReplicationChangeOpt if (!tieBreakerKey.IsNull && !server.IsReplica) { log?.LogInformation($"Resending tie-breaker to {Format.ToString(server.EndPoint)}..."); - msg = Message.Create(0, flags | CommandFlags.FireAndForget, RedisCommand.SET, tieBreakerKey, newPrimary); + msg = Message.Create(0, flags | CommandFlags.FireAndForget, RedisCommand.SET, tieBreakerKey, newPrimary, cancellationToken); try { await server.WriteDirectAsync(msg, ResultProcessor.DemandOK).ForAwait(); @@ -298,7 +300,7 @@ async Task BroadcastAsync(ServerSnapshot serverNodes) { if (!node.IsConnected) continue; log?.LogInformation($"Broadcasting via {Format.ToString(node.EndPoint)}..."); - msg = Message.Create(-1, flags | CommandFlags.FireAndForget, RedisCommand.PUBLISH, channel, newPrimary); + msg = Message.Create(-1, flags | CommandFlags.FireAndForget, RedisCommand.PUBLISH, channel, newPrimary, cancellationToken); await node.WriteDirectAsync(msg, ResultProcessor.Int64).ForAwait(); } } @@ -314,7 +316,7 @@ async Task BroadcastAsync(ServerSnapshot serverNodes) if (node == server || node.ServerType != ServerType.Standalone) continue; log?.LogInformation($"Replicating to {Format.ToString(node.EndPoint)}..."); - msg = RedisServer.CreateReplicaOfMessage(node, server.EndPoint, flags); + msg = RedisServer.CreateReplicaOfMessage(node, server.EndPoint, flags, cancellationToken); await node.WriteDirectAsync(msg, ResultProcessor.DemandOK).ForAwait(); } } @@ -1738,7 +1740,7 @@ public EndPoint[] GetEndPoints(bool configuredOnly = false) => private async Task GetEndpointsFromClusterNodes(ServerEndPoint server, ILogger? log) { - var message = Message.Create(-1, CommandFlags.None, RedisCommand.CLUSTER, RedisLiterals.NODES); + var message = Message.Create(-1, CommandFlags.None, RedisCommand.CLUSTER, RedisLiterals.NODES, this.GetEffectiveCancellationToken()); try { var clusterConfig = await ExecuteAsyncImpl(message, ResultProcessor.ClusterNodes, null, server).ForAwait(); @@ -2041,6 +2043,18 @@ internal static void ThrowFailed(TaskCompletionSource? source, Exception u } } + private static CancellationTokenRegistration ObserveCancellation(IResultBox? box) + { + return box is null ? default : box.CancellationToken.Register( + static state => + { + var typed = Unsafe.As(state!); + typed.Cancel(typed.CancellationToken); + typed.ActivateContinuations(); + }, + box); + } + [return: NotNullIfNotNull(nameof(defaultValue))] internal T? ExecuteSyncImpl(Message message, ResultProcessor? processor, ServerEndPoint? server, T? defaultValue = default) { @@ -2053,6 +2067,14 @@ internal static void ThrowFailed(TaskCompletionSource? source, Exception u Interlocked.Increment(ref syncOps); + var ct = message.CancellationToken; + if (ct.IsCancellationRequested) + { + Interlocked.Increment(ref cancelledPreSend); + if (message.IsFireAndForget) return defaultValue; + ct.ThrowIfCancellationRequested(); + } + if (message.IsFireAndForget) { #pragma warning disable CS0618 // Type or member is obsolete @@ -2063,7 +2085,7 @@ internal static void ThrowFailed(TaskCompletionSource? source, Exception u } else { - var source = SimpleResultBox.Get(); + var source = SimpleResultBox.Get(message.CancellationToken); bool timeout = false; WriteResult result; @@ -2079,6 +2101,8 @@ internal static void ThrowFailed(TaskCompletionSource? source, Exception u throw GetException(result, message, server); } + using var reg = ObserveCancellation(source); + if (Monitor.Wait(source, TimeoutMilliseconds)) { Trace("Timely response to " + message); @@ -2098,6 +2122,7 @@ internal static void ThrowFailed(TaskCompletionSource? source, Exception u // Also note we return "success" when queueing a messages to the backlog, so we need to manually fake it back here when timing out in the backlog throw ExceptionFactory.Timeout(this, null, message, server, message.IsBacklogged ? WriteResult.TimeoutBeforeWrite : result, server?.GetBridge(message.Command, create: false)); } + // Snapshot these so that we can recycle the box var val = source.GetResult(out var ex, canRecycle: true); // now that we aren't locking it... if (ex != null) throw ex; @@ -2108,15 +2133,26 @@ internal static void ThrowFailed(TaskCompletionSource? source, Exception u internal Task ExecuteAsyncImpl(Message? message, ResultProcessor? processor, object? state, ServerEndPoint? server, T defaultValue) { - static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, ValueTask write, TaskCompletionSource? tcs, Message message, ServerEndPoint? server, T defaultValue) - { - var result = await write.ForAwait(); - if (result != WriteResult.Success) - { - var ex = @this.GetException(result, message, server); - ThrowFailed(tcs, ex); + static async Task ExecuteAsyncImpl_Awaited( + ConnectionMultiplexer @this, + ValueTask write, + TaskCompletionSource? tcs, + Message message, + ServerEndPoint? server, + T defaultValue, + IResultBox? source) + { + using (ObserveCancellation(source)) + { + var result = await write.ForAwait(); + if (result != WriteResult.Success) + { + var ex = @this.GetException(result, message, server); + ThrowFailed(tcs, ex); + } + + return tcs == null ? defaultValue : await tcs.Task.ForAwait(); } - return tcs == null ? defaultValue : await tcs.Task.ForAwait(); } if (_isDisposed) throw new ObjectDisposedException(ToString()); @@ -2128,16 +2164,30 @@ static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, Value Interlocked.Increment(ref asyncOps); + var ct = message.CancellationToken; + if (ct.IsCancellationRequested) + { + // pre-doomed? + Interlocked.Increment(ref cancelledPreSend); + return message.IsFireAndForget ? CompletedTask.Default(null)! : PreCancelled(ct, state); + } + TaskCompletionSource? tcs = null; IResultBox? source = null; - if (!message.IsFireAndForget) + if (message.IsFireAndForget) { - source = TaskResultBox.Create(out tcs, state); + Interlocked.Increment(ref fireAndForgets); + } + else + { + // Use the message's cancellation token, which preserves the ambient context from when the message was created + // This ensures that resent messages (due to MOVED, failover, etc.) still respect the original cancellation + source = TaskResultBox.Create(message.CancellationToken, out tcs, state); } var write = TryPushMessageToBridgeAsync(message, processor, source, ref server); if (!write.IsCompletedSuccessfully) { - return ExecuteAsyncImpl_Awaited(this, write, tcs, message, server, defaultValue); + return ExecuteAsyncImpl_Awaited(this, write, tcs, message, server, defaultValue, source); } if (tcs == null) @@ -2152,22 +2202,33 @@ static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, Value var ex = GetException(result, message, server); ThrowFailed(tcs, ex); } - return tcs.Task; + + return WithCancellation(tcs.Task, source); } } internal Task ExecuteAsyncImpl(Message? message, ResultProcessor? processor, object? state, ServerEndPoint? server) { [return: NotNullIfNotNull(nameof(tcs))] - static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, ValueTask write, TaskCompletionSource? tcs, Message message, ServerEndPoint? server) - { - var result = await write.ForAwait(); - if (result != WriteResult.Success) - { - var ex = @this.GetException(result, message, server); - ThrowFailed(tcs, ex); + static async Task ExecuteAsyncImpl_Awaited( + ConnectionMultiplexer @this, + ValueTask write, + TaskCompletionSource? tcs, + Message message, + ServerEndPoint? server, + IResultBox? source) + { + using (ObserveCancellation(source)) + { + var result = await write.ForAwait(); + if (result != WriteResult.Success) + { + var ex = @this.GetException(result, message, server); + ThrowFailed(tcs, ex); + } + + return tcs == null ? default : await tcs.Task.ForAwait(); } - return tcs == null ? default : await tcs.Task.ForAwait(); } if (_isDisposed) throw new ObjectDisposedException(ToString()); @@ -2181,14 +2242,30 @@ static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, Value TaskCompletionSource? tcs = null; IResultBox? source = null; - if (!message.IsFireAndForget) + + var ct = message.CancellationToken; + if (ct.IsCancellationRequested) + { + // pre-doomed? + Interlocked.Increment(ref cancelledPreSend); + return message.IsFireAndForget ? CompletedTask.Default(null) : PreCancelled(ct, state); + } + + if (message.IsFireAndForget) + { + Interlocked.Increment(ref fireAndForgets); + } + else { - source = TaskResultBox.Create(out tcs, state); + // Use the message's cancellation token, which preserves the ambient context from when the message was created + // This ensures that resent messages (due to MOVED, failover, etc.) still respect the original cancellation + source = TaskResultBox.Create(ct, out tcs, state); } + var write = TryPushMessageToBridgeAsync(message, processor, source!, ref server); if (!write.IsCompletedSuccessfully) { - return ExecuteAsyncImpl_Awaited(this, write, tcs, message, server); + return ExecuteAsyncImpl_Awaited(this, write, tcs, message, server, source); } if (tcs == null) @@ -2203,10 +2280,31 @@ static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, Value var ex = GetException(result, message, server); ThrowFailed(tcs, ex); } - return tcs.Task; + + return WithCancellation(tcs.Task, source); } } + private static Task WithCancellation(Task raw, IResultBox? source) + { + return source is { CancellationToken.CanBeCanceled: true } ? Wrap(raw, source) : raw; + + static async Task Wrap(Task raw, IResultBox source) + { + using (ObserveCancellation(source)) + { + return await raw.ForAwait(); + } + } + } + + private static Task PreCancelled(CancellationToken ct, object? asyncState) + { + var tcs = TaskSource.Create(asyncState); + tcs.TrySetCanceled(ct); + return tcs.Task; + } + internal void OnAsyncTimeout() => Interlocked.Increment(ref asyncTimeouts); /// diff --git a/src/StackExchange.Redis/CursorEnumerable.cs b/src/StackExchange.Redis/CursorEnumerable.cs index 55d93d6a6..7c265b14d 100644 --- a/src/StackExchange.Redis/CursorEnumerable.cs +++ b/src/StackExchange.Redis/CursorEnumerable.cs @@ -64,14 +64,14 @@ public ScanResult(RedisValue cursor, T[]? valuesOversized, int count, bool isPoo } } - private protected abstract Message? CreateMessage(in RedisValue cursor); + private protected abstract Message? CreateMessage(in RedisValue cursor, CancellationToken cancellationToken); private protected abstract ResultProcessor? Processor { get; } - private protected virtual Task GetNextPageAsync(IScanningCursor obj, RedisValue cursor, out Message? message) + private protected virtual Task GetNextPageAsync(IScanningCursor obj, RedisValue cursor, out Message? message, CancellationToken cancellationToken) { activeCursor = obj; - message = CreateMessage(cursor); + message = CreateMessage(cursor, cancellationToken); return redis.ExecuteAsync(message, Processor, server); } @@ -181,7 +181,7 @@ private void ProcessReply(in ScanResult result, bool isInitial) else { // start the next page right away - _pending = parent.GetNextPageAsync(this, _nextCursor, out _pendingMessage); + _pending = parent.GetNextPageAsync(this, _nextCursor, out _pendingMessage, cancellationToken); } } @@ -219,7 +219,7 @@ private ValueTask SlowNextAsync() switch (_state) { case State.Initial: - _pending = parent.GetNextPageAsync(this, _nextCursor, out _pendingMessage); + _pending = parent.GetNextPageAsync(this, _nextCursor, out _pendingMessage, cancellationToken); isInitial = true; _state = State.Running; goto case State.Running; @@ -246,7 +246,7 @@ private void ThrowTimeout(Message message) { try { - throw ExceptionFactory.Timeout(parent.redis.multiplexer, null, message, parent.server); + throw ExceptionFactory.Timeout(parent.redis.Multiplexer, null, message, parent.server); } catch (Exception ex) { @@ -352,7 +352,7 @@ public SingleBlockEnumerable(RedisBase redis, ServerEndPoint? server, Task _pending = pending; } - private protected override Task GetNextPageAsync(IScanningCursor obj, RedisValue cursor, out Message? message) + private protected override Task GetNextPageAsync(IScanningCursor obj, RedisValue cursor, out Message? message, CancellationToken cancellationToken) { message = null; return AwaitedGetNextPageAsync(); @@ -363,7 +363,7 @@ private async Task AwaitedGetNextPageAsync() return new ScanResult(RedisBase.CursorUtils.Origin, arr, arr.Length, false); } private protected override ResultProcessor? Processor => null; - private protected override Message? CreateMessage(in RedisValue cursor) => null; + private protected override Message? CreateMessage(in RedisValue cursor, CancellationToken cancellationToken) => null; } } } diff --git a/src/StackExchange.Redis/Exceptions.cs b/src/StackExchange.Redis/Exceptions.cs index 1f1c973ce..43a160894 100644 --- a/src/StackExchange.Redis/Exceptions.cs +++ b/src/StackExchange.Redis/Exceptions.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.Runtime.Serialization; namespace StackExchange.Redis @@ -106,6 +107,7 @@ public RedisConnectionException(ConnectionFailureType failureType, string messag /// The status of the command. public RedisConnectionException(ConnectionFailureType failureType, string message, Exception? innerException, CommandStatus commandStatus) : base(message, innerException) { + Debug.WriteLine($"{failureType}: {message}"); FailureType = failureType; CommandStatus = commandStatus; } diff --git a/src/StackExchange.Redis/Message.cs b/src/StackExchange.Redis/Message.cs index fd75585a5..075899f5d 100644 --- a/src/StackExchange.Redis/Message.cs +++ b/src/StackExchange.Redis/Message.cs @@ -21,7 +21,7 @@ public static Message Create(ILogger? log, Message tail) return log == null ? tail : new LoggingMessage(log, tail); } - private LoggingMessage(ILogger log, Message tail) : base(tail.Db, tail.Flags, tail.Command) + private LoggingMessage(ILogger log, Message tail) : base(tail.Db, tail.Flags, tail.Command, tail.CancellationToken) { this.log = log; this.tail = tail; @@ -83,12 +83,15 @@ internal abstract class Message : ICompletable private ResultProcessor? resultProcessor; + // Cancellation token for this specific message + private CancellationToken _cancellationToken; + // All for profiling purposes private ProfiledCommand? performance; internal DateTime CreatedDateTime; internal long CreatedTimestamp; - protected Message(int db, CommandFlags flags, RedisCommand command) + protected Message(int db, CommandFlags flags, RedisCommand command, CancellationToken cancellationToken) { bool dbNeeded = RequiresDatabase(command); if (command == RedisCommand.UNKNOWN) @@ -116,6 +119,9 @@ protected Message(int db, CommandFlags flags, RedisCommand command) Flags = flags & UserSelectableFlags; if (primaryOnly) SetPrimaryOnly(); + // Get ambient cancellation token when the message is created + _cancellationToken = cancellationToken; + CreatedDateTime = DateTime.UtcNow; CreatedTimestamp = Stopwatch.GetTimestamp(); Status = CommandStatus.WaitingToBeSent; @@ -217,49 +223,55 @@ internal void WithHighIntegrity(uint value) public IResultBox? ResultBox => resultBox; + /// + /// Gets the cancellation token associated with this message. + /// This token is captured when the message is created and preserved across resends. + /// + internal CancellationToken CancellationToken => _cancellationToken; + public abstract int ArgCount { get; } // note: over-estimate if necessary - public static Message Create(int db, CommandFlags flags, RedisCommand command) + public static Message Create(int db, CommandFlags flags, RedisCommand command, CancellationToken cancellationToken) { if (command == RedisCommand.SELECT) return new SelectMessage(db, flags); - return new CommandMessage(db, flags, command); + return new CommandMessage(db, flags, command, cancellationToken); } - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key) => - new CommandKeyMessage(db, flags, command, key); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, CancellationToken cancellationToken) => + new CommandKeyMessage(db, flags, command, key, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1) => - new CommandKeyKeyMessage(db, flags, command, key0, key1); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, CancellationToken cancellationToken) => + new CommandKeyKeyMessage(db, flags, command, key0, key1, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, in RedisValue value) => - new CommandKeyKeyValueMessage(db, flags, command, key0, key1, value); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, in RedisValue value, CancellationToken cancellationToken) => + new CommandKeyKeyValueMessage(db, flags, command, key0, key1, value, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, in RedisKey key2) => - new CommandKeyKeyKeyMessage(db, flags, command, key0, key1, key2); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, in RedisKey key2, CancellationToken cancellationToken) => + new CommandKeyKeyKeyMessage(db, flags, command, key0, key1, key2, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value) => - new CommandValueMessage(db, flags, command, value); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value, CancellationToken cancellationToken) => + new CommandValueMessage(db, flags, command, value, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value) => - new CommandKeyValueMessage(db, flags, command, key, value); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value, CancellationToken cancellationToken) => + new CommandKeyValueMessage(db, flags, command, key, value, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel) => - new CommandChannelMessage(db, flags, command, channel); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel, CancellationToken cancellationToken) => + new CommandChannelMessage(db, flags, command, channel, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel, in RedisValue value) => - new CommandChannelValueMessage(db, flags, command, channel, value); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel, in RedisValue value, CancellationToken cancellationToken) => + new CommandChannelValueMessage(db, flags, command, channel, value, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value, in RedisChannel channel) => - new CommandValueChannelMessage(db, flags, command, value, channel); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value, in RedisChannel channel, CancellationToken cancellationToken) => + new CommandValueChannelMessage(db, flags, command, value, channel, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1) => - new CommandKeyValueValueMessage(db, flags, command, key, value0, value1); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, CancellationToken cancellationToken) => + new CommandKeyValueValueMessage(db, flags, command, key, value0, value1, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2) => - new CommandKeyValueValueValueMessage(db, flags, command, key, value0, value1, value2); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, CancellationToken cancellationToken) => + new CommandKeyValueValueValueMessage(db, flags, command, key, value0, value1, value2, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, GeoEntry[] values) + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, GeoEntry[] values, CancellationToken cancellationToken) { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(values); @@ -273,7 +285,7 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, i if (values.Length == 1) { var value = values[0]; - return Create(db, flags, command, key, value.Longitude, value.Latitude, value.Member); + return Create(db, flags, command, key, value.Longitude, value.Latitude, value.Member, cancellationToken); } var arr = new RedisValue[3 * values.Length]; int index = 0; @@ -283,32 +295,32 @@ public static Message Create(int db, CommandFlags flags, RedisCommand command, i arr[index++] = value.Latitude; arr[index++] = value.Member; } - return new CommandKeyValuesMessage(db, flags, command, key, arr); + return new CommandKeyValuesMessage(db, flags, command, key, arr, cancellationToken); } - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3) => - new CommandKeyValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, CancellationToken cancellationToken) => + new CommandKeyValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) => - new CommandKeyValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, CancellationToken cancellationToken) => + new CommandKeyValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5) => - new CommandKeyValueValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4, value5); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5, CancellationToken cancellationToken) => + new CommandKeyValueValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4, value5, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5, in RedisValue value6) => - new CommandKeyValueValueValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4, value5, value6); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5, in RedisValue value6, CancellationToken cancellationToken) => + new CommandKeyValueValueValueValueValueValueValueMessage(db, flags, command, key, value0, value1, value2, value3, value4, value5, value6, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1) => - new CommandValueValueMessage(db, flags, command, value0, value1); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, CancellationToken cancellationToken) => + new CommandValueValueMessage(db, flags, command, value0, value1, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value, in RedisKey key) => - new CommandValueKeyMessage(db, flags, command, value, key); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value, in RedisKey key, CancellationToken cancellationToken) => + new CommandValueKeyMessage(db, flags, command, value, key, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2) => - new CommandValueValueValueMessage(db, flags, command, value0, value1, value2); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2, CancellationToken cancellationToken) => + new CommandValueValueValueMessage(db, flags, command, value0, value1, value2, cancellationToken); - public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) => - new CommandValueValueValueValueValueMessage(db, flags, command, value0, value1, value2, value3, value4); + public static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, CancellationToken cancellationToken) => + new CommandValueValueValueValueValueMessage(db, flags, command, value0, value1, value2, value3, value4, cancellationToken); public static Message Create( int db, @@ -317,8 +329,9 @@ public static Message Create( in RedisKey key0, in RedisKey key1, in RedisValue value0, - in RedisValue value1) => - new CommandKeyKeyValueValueMessage(db, flags, command, key0, key1, value0, value1); + in RedisValue value1, + CancellationToken cancellationToken) => + new CommandKeyKeyValueValueMessage(db, flags, command, key0, key1, value0, value1, cancellationToken); public static Message Create( int db, @@ -328,8 +341,9 @@ public static Message Create( in RedisKey key1, in RedisValue value0, in RedisValue value1, - in RedisValue value2) => - new CommandKeyKeyValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2); + in RedisValue value2, + CancellationToken cancellationToken) => + new CommandKeyKeyValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, cancellationToken); public static Message Create( int db, @@ -340,8 +354,9 @@ public static Message Create( in RedisValue value0, in RedisValue value1, in RedisValue value2, - in RedisValue value3) => - new CommandKeyKeyValueValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, value3); + in RedisValue value3, + CancellationToken cancellationToken) => + new CommandKeyKeyValueValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, value3, cancellationToken); public static Message Create( int db, @@ -353,8 +368,9 @@ public static Message Create( in RedisValue value1, in RedisValue value2, in RedisValue value3, - in RedisValue value4) => - new CommandKeyKeyValueValueValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, value3, value4); + in RedisValue value4, + CancellationToken cancellationToken) => + new CommandKeyKeyValueValueValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, value3, value4, cancellationToken); public static Message Create( int db, @@ -367,8 +383,9 @@ public static Message Create( in RedisValue value2, in RedisValue value3, in RedisValue value4, - in RedisValue value5) => - new CommandKeyKeyValueValueValueValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, value3, value4, value5); + in RedisValue value5, + CancellationToken cancellationToken) => + new CommandKeyKeyValueValueValueValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, value3, value4, value5, cancellationToken); public static Message Create( int db, @@ -382,11 +399,12 @@ public static Message Create( in RedisValue value3, in RedisValue value4, in RedisValue value5, - in RedisValue value6) => - new CommandKeyKeyValueValueValueValueValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, value3, value4, value5, value6); + in RedisValue value6, + CancellationToken cancellationToken) => + new CommandKeyKeyValueValueValueValueValueValueValueMessage(db, flags, command, key0, key1, value0, value1, value2, value3, value4, value5, value6, cancellationToken); - public static Message CreateInSlot(int db, int slot, CommandFlags flags, RedisCommand command, RedisValue[] values) => - new CommandSlotValuesMessage(db, slot, flags, command, values); + public static Message CreateInSlot(int db, int slot, CommandFlags flags, RedisCommand command, RedisValue[] values, CancellationToken cancellationToken) => + new CommandSlotValuesMessage(db, slot, flags, command, values, cancellationToken); /// Gets whether this is primary-only. /// @@ -448,35 +466,35 @@ public void Complete() internal bool ResultBoxIsAsync => Volatile.Read(ref resultBox)?.IsAsync == true; - internal static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisKey[] keys) => keys.Length switch + internal static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisKey[] keys, CancellationToken cancellationToken) => keys.Length switch { - 0 => new CommandKeyMessage(db, flags, command, key), - 1 => new CommandKeyKeyMessage(db, flags, command, key, keys[0]), - 2 => new CommandKeyKeyKeyMessage(db, flags, command, key, keys[0], keys[1]), - _ => new CommandKeyKeysMessage(db, flags, command, key, keys), + 0 => new CommandKeyMessage(db, flags, command, key, cancellationToken), + 1 => new CommandKeyKeyMessage(db, flags, command, key, keys[0], cancellationToken), + 2 => new CommandKeyKeyKeyMessage(db, flags, command, key, keys[0], keys[1], cancellationToken), + _ => new CommandKeyKeysMessage(db, flags, command, key, keys, cancellationToken), }; - internal static Message Create(int db, CommandFlags flags, RedisCommand command, IList keys) => keys.Count switch + internal static Message Create(int db, CommandFlags flags, RedisCommand command, IList keys, CancellationToken cancellationToken) => keys.Count switch { - 0 => new CommandMessage(db, flags, command), - 1 => new CommandKeyMessage(db, flags, command, keys[0]), - 2 => new CommandKeyKeyMessage(db, flags, command, keys[0], keys[1]), - 3 => new CommandKeyKeyKeyMessage(db, flags, command, keys[0], keys[1], keys[2]), - _ => new CommandKeysMessage(db, flags, command, (keys as RedisKey[]) ?? keys.ToArray()), + 0 => new CommandMessage(db, flags, command, cancellationToken), + 1 => new CommandKeyMessage(db, flags, command, keys[0], cancellationToken), + 2 => new CommandKeyKeyMessage(db, flags, command, keys[0], keys[1], cancellationToken), + 3 => new CommandKeyKeyKeyMessage(db, flags, command, keys[0], keys[1], keys[2], cancellationToken), + _ => new CommandKeysMessage(db, flags, command, (keys as RedisKey[]) ?? keys.ToArray(), cancellationToken), }; - internal static Message Create(int db, CommandFlags flags, RedisCommand command, IList values) => values.Count switch + internal static Message Create(int db, CommandFlags flags, RedisCommand command, IList values, CancellationToken cancellationToken) => values.Count switch { - 0 => new CommandMessage(db, flags, command), - 1 => new CommandValueMessage(db, flags, command, values[0]), - 2 => new CommandValueValueMessage(db, flags, command, values[0], values[1]), - 3 => new CommandValueValueValueMessage(db, flags, command, values[0], values[1], values[2]), + 0 => new CommandMessage(db, flags, command, cancellationToken), + 1 => new CommandValueMessage(db, flags, command, values[0], cancellationToken), + 2 => new CommandValueValueMessage(db, flags, command, values[0], values[1], cancellationToken), + 3 => new CommandValueValueValueMessage(db, flags, command, values[0], values[1], values[2], cancellationToken), // no 4; not worth adding - 5 => new CommandValueValueValueValueValueMessage(db, flags, command, values[0], values[1], values[2], values[3], values[4]), - _ => new CommandValuesMessage(db, flags, command, (values as RedisValue[]) ?? values.ToArray()), + 5 => new CommandValueValueValueValueValueMessage(db, flags, command, values[0], values[1], values[2], values[3], values[4], cancellationToken), + _ => new CommandValuesMessage(db, flags, command, (values as RedisValue[]) ?? values.ToArray(), cancellationToken), }; - internal static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisValue[] values) + internal static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisValue[] values, CancellationToken cancellationToken) { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(values); @@ -485,16 +503,16 @@ internal static Message Create(int db, CommandFlags flags, RedisCommand command, #endif return values.Length switch { - 0 => new CommandKeyMessage(db, flags, command, key), - 1 => new CommandKeyValueMessage(db, flags, command, key, values[0]), - 2 => new CommandKeyValueValueMessage(db, flags, command, key, values[0], values[1]), - 3 => new CommandKeyValueValueValueMessage(db, flags, command, key, values[0], values[1], values[2]), - 4 => new CommandKeyValueValueValueValueMessage(db, flags, command, key, values[0], values[1], values[2], values[3]), - _ => new CommandKeyValuesMessage(db, flags, command, key, values), + 0 => new CommandKeyMessage(db, flags, command, key, cancellationToken), + 1 => new CommandKeyValueMessage(db, flags, command, key, values[0], cancellationToken), + 2 => new CommandKeyValueValueMessage(db, flags, command, key, values[0], values[1], cancellationToken), + 3 => new CommandKeyValueValueValueMessage(db, flags, command, key, values[0], values[1], values[2], cancellationToken), + 4 => new CommandKeyValueValueValueValueMessage(db, flags, command, key, values[0], values[1], values[2], values[3], cancellationToken), + _ => new CommandKeyValuesMessage(db, flags, command, key, values, cancellationToken), }; } - internal static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, RedisValue[] values) + internal static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, RedisValue[] values, CancellationToken cancellationToken) { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(values); @@ -503,26 +521,26 @@ internal static Message Create(int db, CommandFlags flags, RedisCommand command, #endif return values.Length switch { - 0 => new CommandKeyKeyMessage(db, flags, command, key0, key1), - 1 => new CommandKeyKeyValueMessage(db, flags, command, key0, key1, values[0]), - 2 => new CommandKeyKeyValueValueMessage(db, flags, command, key0, key1, values[0], values[1]), - 3 => new CommandKeyKeyValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2]), - 4 => new CommandKeyKeyValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3]), - 5 => new CommandKeyKeyValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4]), - 6 => new CommandKeyKeyValueValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4], values[5]), - 7 => new CommandKeyKeyValueValueValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4], values[5], values[6]), - _ => new CommandKeyKeyValuesMessage(db, flags, command, key0, key1, values), + 0 => new CommandKeyKeyMessage(db, flags, command, key0, key1, cancellationToken), + 1 => new CommandKeyKeyValueMessage(db, flags, command, key0, key1, values[0], cancellationToken), + 2 => new CommandKeyKeyValueValueMessage(db, flags, command, key0, key1, values[0], values[1], cancellationToken), + 3 => new CommandKeyKeyValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], cancellationToken), + 4 => new CommandKeyKeyValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], cancellationToken), + 5 => new CommandKeyKeyValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4], cancellationToken), + 6 => new CommandKeyKeyValueValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4], values[5], cancellationToken), + 7 => new CommandKeyKeyValueValueValueValueValueValueValueMessage(db, flags, command, key0, key1, values[0], values[1], values[2], values[3], values[4], values[5], values[6], cancellationToken), + _ => new CommandKeyKeyValuesMessage(db, flags, command, key0, key1, values, cancellationToken), }; } - internal static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, RedisValue[] values, in RedisKey key1) + internal static Message Create(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, RedisValue[] values, in RedisKey key1, CancellationToken cancellationToken) { #if NET6_0_OR_GREATER ArgumentNullException.ThrowIfNull(values); #else if (values == null) throw new ArgumentNullException(nameof(values)); #endif - return new CommandKeyValuesKeyMessage(db, flags, command, key0, values, key1); + return new CommandKeyValuesKeyMessage(db, flags, command, key0, values, key1, cancellationToken); } internal static CommandFlags GetPrimaryReplicaFlags(CommandFlags flags) @@ -590,7 +608,7 @@ internal static CommandFlags SetPrimaryReplicaFlags(CommandFlags everything, Com | primaryReplica; } - internal void Cancel() => resultBox?.Cancel(); + internal void Cancel() => resultBox?.Cancel(CancellationToken.None); // true if ready to be completed (i.e. false if re-issued to another server) internal bool ComputeResult(PhysicalConnection connection, in RawResult result) @@ -803,16 +821,16 @@ internal void WriteHighIntegrityChecksumRequest(PhysicalConnection physical) } } - internal static Message CreateHello(int protocolVersion, string? username, string? password, string? clientName, CommandFlags flags) - => new HelloMessage(protocolVersion, username, password, clientName, flags); + internal static Message CreateHello(int protocolVersion, string? username, string? password, string? clientName, CommandFlags flags, CancellationToken cancellationToken) + => new HelloMessage(protocolVersion, username, password, clientName, flags, cancellationToken); internal sealed class HelloMessage : Message { private readonly string? _username, _password, _clientName; private readonly int _protocolVersion; - internal HelloMessage(int protocolVersion, string? username, string? password, string? clientName, CommandFlags flags) - : base(-1, flags, RedisCommand.HELLO) + internal HelloMessage(int protocolVersion, string? username, string? password, string? clientName, CommandFlags flags, CancellationToken cancellationToken) + : base(-1, flags, RedisCommand.HELLO, cancellationToken) { _protocolVersion = protocolVersion; _username = username; @@ -854,7 +872,7 @@ internal abstract class CommandChannelBase : Message { protected readonly RedisChannel Channel; - protected CommandChannelBase(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel) : base(db, flags, command) + protected CommandChannelBase(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel, CancellationToken cancellationToken) : base(db, flags, command, cancellationToken) { channel.AssertNotNull(); Channel = channel; @@ -869,7 +887,7 @@ internal abstract class CommandKeyBase : Message { protected readonly RedisKey Key; - protected CommandKeyBase(int db, CommandFlags flags, RedisCommand command, in RedisKey key) : base(db, flags, command) + protected CommandKeyBase(int db, CommandFlags flags, RedisCommand command, in RedisKey key, CancellationToken cancellationToken) : base(db, flags, command, cancellationToken) { key.AssertNotNull(); Key = key; @@ -882,7 +900,7 @@ protected CommandKeyBase(int db, CommandFlags flags, RedisCommand command, in Re private sealed class CommandChannelMessage : CommandChannelBase { - public CommandChannelMessage(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel) : base(db, flags, command, channel) + public CommandChannelMessage(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel, CancellationToken cancellationToken) : base(db, flags, command, channel, cancellationToken) { } protected override void WriteImpl(PhysicalConnection physical) { @@ -895,7 +913,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandChannelValueMessage : CommandChannelBase { private readonly RedisValue value; - public CommandChannelValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel, in RedisValue value) : base(db, flags, command, channel) + public CommandChannelValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisChannel channel, in RedisValue value, CancellationToken cancellationToken) : base(db, flags, command, channel, cancellationToken) { value.AssertNotNull(); this.value = value; @@ -913,7 +931,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandKeyKeyKeyMessage : CommandKeyBase { private readonly RedisKey key1, key2; - public CommandKeyKeyKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, in RedisKey key2) : base(db, flags, command, key0) + public CommandKeyKeyKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, in RedisKey key2, CancellationToken cancellationToken) : base(db, flags, command, key0, cancellationToken) { key1.AssertNotNull(); key2.AssertNotNull(); @@ -941,7 +959,7 @@ protected override void WriteImpl(PhysicalConnection physical) private class CommandKeyKeyMessage : CommandKeyBase { protected readonly RedisKey key1; - public CommandKeyKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1) : base(db, flags, command, key0) + public CommandKeyKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, CancellationToken cancellationToken) : base(db, flags, command, key0, cancellationToken) { key1.AssertNotNull(); this.key1 = key1; @@ -965,7 +983,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandKeyKeysMessage : CommandKeyBase { private readonly RedisKey[] keys; - public CommandKeyKeysMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisKey[] keys) : base(db, flags, command, key) + public CommandKeyKeysMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisKey[] keys, CancellationToken cancellationToken) : base(db, flags, command, key, cancellationToken) { for (int i = 0; i < keys.Length; i++) { @@ -999,7 +1017,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandKeyKeyValueMessage : CommandKeyKeyMessage { private readonly RedisValue value; - public CommandKeyKeyValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, in RedisValue value) : base(db, flags, command, key0, key1) + public CommandKeyKeyValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, in RedisKey key1, in RedisValue value, CancellationToken cancellationToken) : base(db, flags, command, key0, key1, cancellationToken) { value.AssertNotNull(); this.value = value; @@ -1018,7 +1036,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandKeyMessage : CommandKeyBase { - public CommandKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key) : base(db, flags, command, key) + public CommandKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, CancellationToken cancellationToken) : base(db, flags, command, key, cancellationToken) { } protected override void WriteImpl(PhysicalConnection physical) { @@ -1031,7 +1049,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandValuesMessage : Message { private readonly RedisValue[] values; - public CommandValuesMessage(int db, CommandFlags flags, RedisCommand command, RedisValue[] values) : base(db, flags, command) + public CommandValuesMessage(int db, CommandFlags flags, RedisCommand command, RedisValue[] values, CancellationToken cancellationToken) : base(db, flags, command, cancellationToken) { for (int i = 0; i < values.Length; i++) { @@ -1054,7 +1072,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandKeysMessage : Message { private readonly RedisKey[] keys; - public CommandKeysMessage(int db, CommandFlags flags, RedisCommand command, RedisKey[] keys) : base(db, flags, command) + public CommandKeysMessage(int db, CommandFlags flags, RedisCommand command, RedisKey[] keys, CancellationToken cancellationToken) : base(db, flags, command, cancellationToken) { for (int i = 0; i < keys.Length; i++) { @@ -1087,7 +1105,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandKeyValueMessage : CommandKeyBase { private readonly RedisValue value; - public CommandKeyValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value) : base(db, flags, command, key) + public CommandKeyValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value, CancellationToken cancellationToken) : base(db, flags, command, key, cancellationToken) { value.AssertNotNull(); this.value = value; @@ -1106,7 +1124,7 @@ private sealed class CommandKeyValuesKeyMessage : CommandKeyBase { private readonly RedisKey key1; private readonly RedisValue[] values; - public CommandKeyValuesKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, RedisValue[] values, in RedisKey key1) : base(db, flags, command, key0) + public CommandKeyValuesKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key0, RedisValue[] values, in RedisKey key1, CancellationToken cancellationToken) : base(db, flags, command, key0, cancellationToken) { for (int i = 0; i < values.Length; i++) { @@ -1136,7 +1154,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandKeyValuesMessage : CommandKeyBase { private readonly RedisValue[] values; - public CommandKeyValuesMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisValue[] values) : base(db, flags, command, key) + public CommandKeyValuesMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, RedisValue[] values, CancellationToken cancellationToken) : base(db, flags, command, key, cancellationToken) { for (int i = 0; i < values.Length; i++) { @@ -1158,7 +1176,7 @@ private sealed class CommandKeyKeyValuesMessage : CommandKeyBase { private readonly RedisKey key1; private readonly RedisValue[] values; - public CommandKeyKeyValuesMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisKey key1, RedisValue[] values) : base(db, flags, command, key) + public CommandKeyKeyValuesMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisKey key1, RedisValue[] values, CancellationToken cancellationToken) : base(db, flags, command, key, cancellationToken) { for (int i = 0; i < values.Length; i++) { @@ -1183,7 +1201,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandKeyValueValueMessage : CommandKeyBase { private readonly RedisValue value0, value1; - public CommandKeyValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1) : base(db, flags, command, key) + public CommandKeyValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, CancellationToken cancellationToken) : base(db, flags, command, key, cancellationToken) { value0.AssertNotNull(); value1.AssertNotNull(); @@ -1204,7 +1222,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandKeyValueValueValueMessage : CommandKeyBase { private readonly RedisValue value0, value1, value2; - public CommandKeyValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2) : base(db, flags, command, key) + public CommandKeyValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, CancellationToken cancellationToken) : base(db, flags, command, key, cancellationToken) { value0.AssertNotNull(); value1.AssertNotNull(); @@ -1228,7 +1246,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandKeyValueValueValueValueMessage : CommandKeyBase { private readonly RedisValue value0, value1, value2, value3; - public CommandKeyValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3) : base(db, flags, command, key) + public CommandKeyValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, CancellationToken cancellationToken) : base(db, flags, command, key, cancellationToken) { value0.AssertNotNull(); value1.AssertNotNull(); @@ -1255,7 +1273,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandKeyValueValueValueValueValueMessage : CommandKeyBase { private readonly RedisValue value0, value1, value2, value3, value4; - public CommandKeyValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) : base(db, flags, command, key) + public CommandKeyValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, CancellationToken cancellationToken) : base(db, flags, command, key, cancellationToken) { value0.AssertNotNull(); value1.AssertNotNull(); @@ -1286,7 +1304,7 @@ private sealed class CommandKeyValueValueValueValueValueValueMessage : CommandKe { private readonly RedisValue value0, value1, value2, value3, value4, value5; - public CommandKeyValueValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5) : base(db, flags, command, key) + public CommandKeyValueValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5, CancellationToken cancellationToken) : base(db, flags, command, key, cancellationToken) { value0.AssertNotNull(); value1.AssertNotNull(); @@ -1320,7 +1338,7 @@ private sealed class CommandKeyValueValueValueValueValueValueValueMessage : Comm { private readonly RedisValue value0, value1, value2, value3, value4, value5, value6; - public CommandKeyValueValueValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5, in RedisValue value6) : base(db, flags, command, key) + public CommandKeyValueValueValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisKey key, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, in RedisValue value5, in RedisValue value6, CancellationToken cancellationToken) : base(db, flags, command, key, cancellationToken) { value0.AssertNotNull(); value1.AssertNotNull(); @@ -1365,7 +1383,8 @@ public CommandKeyKeyValueValueMessage( in RedisKey key0, in RedisKey key1, in RedisValue value0, - in RedisValue value1) : base(db, flags, command, key0) + in RedisValue value1, + CancellationToken cancellationToken) : base(db, flags, command, key0, cancellationToken) { key1.AssertNotNull(); value0.AssertNotNull(); @@ -1400,7 +1419,8 @@ public CommandKeyKeyValueValueValueMessage( in RedisKey key1, in RedisValue value0, in RedisValue value1, - in RedisValue value2) : base(db, flags, command, key0) + in RedisValue value2, + CancellationToken cancellationToken) : base(db, flags, command, key0, cancellationToken) { key1.AssertNotNull(); value0.AssertNotNull(); @@ -1439,7 +1459,8 @@ public CommandKeyKeyValueValueValueValueMessage( in RedisValue value0, in RedisValue value1, in RedisValue value2, - in RedisValue value3) : base(db, flags, command, key0) + in RedisValue value3, + CancellationToken cancellationToken) : base(db, flags, command, key0, cancellationToken) { key1.AssertNotNull(); value0.AssertNotNull(); @@ -1482,7 +1503,8 @@ public CommandKeyKeyValueValueValueValueValueMessage( in RedisValue value1, in RedisValue value2, in RedisValue value3, - in RedisValue value4) : base(db, flags, command, key0) + in RedisValue value4, + CancellationToken cancellationToken) : base(db, flags, command, key0, cancellationToken) { key1.AssertNotNull(); value0.AssertNotNull(); @@ -1529,7 +1551,8 @@ public CommandKeyKeyValueValueValueValueValueValueMessage( in RedisValue value2, in RedisValue value3, in RedisValue value4, - in RedisValue value5) : base(db, flags, command, key0) + in RedisValue value5, + CancellationToken cancellationToken) : base(db, flags, command, key0, cancellationToken) { key1.AssertNotNull(); value0.AssertNotNull(); @@ -1580,7 +1603,8 @@ public CommandKeyKeyValueValueValueValueValueValueValueMessage( in RedisValue value3, in RedisValue value4, in RedisValue value5, - in RedisValue value6) : base(db, flags, command, key0) + in RedisValue value6, + CancellationToken cancellationToken) : base(db, flags, command, key0, cancellationToken) { key1.AssertNotNull(); value0.AssertNotNull(); @@ -1619,7 +1643,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandMessage : Message { - public CommandMessage(int db, CommandFlags flags, RedisCommand command) : base(db, flags, command) { } + public CommandMessage(int db, CommandFlags flags, RedisCommand command, CancellationToken cancellationToken) : base(db, flags, command, cancellationToken) { } protected override void WriteImpl(PhysicalConnection physical) { physical.WriteHeader(Command, 0); @@ -1632,8 +1656,8 @@ private class CommandSlotValuesMessage : Message private readonly int slot; private readonly RedisValue[] values; - public CommandSlotValuesMessage(int db, int slot, CommandFlags flags, RedisCommand command, RedisValue[] values) - : base(db, flags, command) + public CommandSlotValuesMessage(int db, int slot, CommandFlags flags, RedisCommand command, RedisValue[] values, CancellationToken cancellationToken) + : base(db, flags, command, cancellationToken) { this.slot = slot; for (int i = 0; i < values.Length; i++) @@ -1659,7 +1683,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandValueChannelMessage : CommandChannelBase { private readonly RedisValue value; - public CommandValueChannelMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value, in RedisChannel channel) : base(db, flags, command, channel) + public CommandValueChannelMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value, in RedisChannel channel, CancellationToken cancellationToken) : base(db, flags, command, channel, cancellationToken) { value.AssertNotNull(); this.value = value; @@ -1678,7 +1702,7 @@ private sealed class CommandValueKeyMessage : CommandKeyBase { private readonly RedisValue value; - public CommandValueKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value, in RedisKey key) : base(db, flags, command, key) + public CommandValueKeyMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value, in RedisKey key, CancellationToken cancellationToken) : base(db, flags, command, key, cancellationToken) { value.AssertNotNull(); this.value = value; @@ -1702,7 +1726,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandValueMessage : Message { private readonly RedisValue value; - public CommandValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value) : base(db, flags, command) + public CommandValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value, CancellationToken cancellationToken) : base(db, flags, command, cancellationToken) { value.AssertNotNull(); this.value = value; @@ -1719,7 +1743,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandValueValueMessage : Message { private readonly RedisValue value0, value1; - public CommandValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1) : base(db, flags, command) + public CommandValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, CancellationToken cancellationToken) : base(db, flags, command, cancellationToken) { value0.AssertNotNull(); value1.AssertNotNull(); @@ -1739,7 +1763,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandValueValueValueMessage : Message { private readonly RedisValue value0, value1, value2; - public CommandValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2) : base(db, flags, command) + public CommandValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2, CancellationToken cancellationToken) : base(db, flags, command, cancellationToken) { value0.AssertNotNull(); value1.AssertNotNull(); @@ -1762,7 +1786,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class CommandValueValueValueValueValueMessage : Message { private readonly RedisValue value0, value1, value2, value3, value4; - public CommandValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4) : base(db, flags, command) + public CommandValueValueValueValueValueMessage(int db, CommandFlags flags, RedisCommand command, in RedisValue value0, in RedisValue value1, in RedisValue value2, in RedisValue value3, in RedisValue value4, CancellationToken cancellationToken) : base(db, flags, command, cancellationToken) { value0.AssertNotNull(); value1.AssertNotNull(); @@ -1790,7 +1814,7 @@ protected override void WriteImpl(PhysicalConnection physical) private sealed class SelectMessage : Message { - public SelectMessage(int db, CommandFlags flags) : base(db, flags, RedisCommand.SELECT) + public SelectMessage(int db, CommandFlags flags) : base(db, flags, RedisCommand.SELECT, CancellationToken.None) // select should not be cancellable { } @@ -1807,7 +1831,7 @@ protected override void WriteImpl(PhysicalConnection physical) internal sealed class UnknownMessage : Message { public static UnknownMessage Instance { get; } = new(); - private UnknownMessage() : base(0, CommandFlags.None, RedisCommand.UNKNOWN) { } + private UnknownMessage() : base(0, CommandFlags.None, RedisCommand.UNKNOWN, CancellationToken.None) { } public override int ArgCount => 0; protected override void WriteImpl(PhysicalConnection physical) => throw new InvalidOperationException("This message cannot be written"); } diff --git a/src/StackExchange.Redis/PhysicalBridge.cs b/src/StackExchange.Redis/PhysicalBridge.cs index f5d75c188..e03fc8e03 100644 --- a/src/StackExchange.Redis/PhysicalBridge.cs +++ b/src/StackExchange.Redis/PhysicalBridge.cs @@ -23,7 +23,7 @@ internal sealed class PhysicalBridge : IDisposable private const double ProfileLogSeconds = (1000 /* ms */ * ProfileLogSamples) / 1000.0; - private static readonly Message ReusableAskingCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.ASKING); + private static readonly Message ReusableAskingCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.ASKING, CancellationToken.None); private readonly long[] profileLog = new long[ProfileLogSamples]; @@ -394,13 +394,13 @@ internal void KeepAlive(bool forceRun = false) case ConnectionType.Subscription: if (commandMap.IsAvailable(RedisCommand.PING) && features.PingOnSubscriber) { - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.PING); + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.PING, CancellationToken.None); msg.SetForSubscriptionBridge(); msg.SetSource(ResultProcessor.Tracer, null); } else if (commandMap.IsAvailable(RedisCommand.UNSUBSCRIBE)) { - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.UNSUBSCRIBE, RedisChannel.Literal(Multiplexer.UniqueId)); + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.UNSUBSCRIBE, RedisChannel.Literal(Multiplexer.UniqueId), CancellationToken.None); msg.SetSource(ResultProcessor.TrackSubscriptions, null); } break; diff --git a/src/StackExchange.Redis/PhysicalConnection.cs b/src/StackExchange.Redis/PhysicalConnection.cs index 8a0bad393..9b54b6ecc 100644 --- a/src/StackExchange.Redis/PhysicalConnection.cs +++ b/src/StackExchange.Redis/PhysicalConnection.cs @@ -32,11 +32,11 @@ internal sealed partial class PhysicalConnection : IDisposable private static readonly CommandBytes message = "message", pmessage = "pmessage", smessage = "smessage"; private static readonly Message[] ReusableChangeDatabaseCommands = Enumerable.Range(0, DefaultRedisDatabaseCount).Select( - i => Message.Create(i, CommandFlags.FireAndForget, RedisCommand.SELECT)).ToArray(); + i => Message.Create(i, CommandFlags.FireAndForget, RedisCommand.SELECT, CancellationToken.None)).ToArray(); private static readonly Message - ReusableReadOnlyCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.READONLY), - ReusableReadWriteCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.READWRITE); + ReusableReadOnlyCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.READONLY, CancellationToken.None), + ReusableReadWriteCommand = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.READWRITE, CancellationToken.None); private static int totalCount; @@ -716,7 +716,7 @@ internal static Message GetSelectDatabaseCommand(int targetDatabase) { return targetDatabase < DefaultRedisDatabaseCount ? ReusableChangeDatabaseCommands[targetDatabase] // 0-15 by default - : Message.Create(targetDatabase, CommandFlags.FireAndForget, RedisCommand.SELECT); + : Message.Create(targetDatabase, CommandFlags.FireAndForget, RedisCommand.SELECT, CancellationToken.None); } internal int GetSentAwaitingResponseCount() diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index 91b0e1a43..b590d5b24 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,7 @@ -#nullable enable \ No newline at end of file +#nullable enable +StackExchange.Redis.RedisCancellationExtensions +static StackExchange.Redis.RedisCancellationExtensions.WithCancellation(this StackExchange.Redis.IConnectionMultiplexer! redis, System.Threading.CancellationToken cancellationToken) -> System.IDisposable! +static StackExchange.Redis.RedisCancellationExtensions.WithCancellationAndTimeout(this StackExchange.Redis.IConnectionMultiplexer! redis, System.Threading.CancellationToken cancellationToken, int milliseconds) -> System.IDisposable! +static StackExchange.Redis.RedisCancellationExtensions.WithCancellationAndTimeout(this StackExchange.Redis.IConnectionMultiplexer! redis, System.Threading.CancellationToken cancellationToken, System.TimeSpan timeout) -> System.IDisposable! +static StackExchange.Redis.RedisCancellationExtensions.WithTimeout(this StackExchange.Redis.IConnectionMultiplexer! redis, int milliseconds) -> System.IDisposable! +static StackExchange.Redis.RedisCancellationExtensions.WithTimeout(this StackExchange.Redis.IConnectionMultiplexer! redis, System.TimeSpan timeout) -> System.IDisposable! \ No newline at end of file diff --git a/src/StackExchange.Redis/RedisBase.cs b/src/StackExchange.Redis/RedisBase.cs index 095835efd..e8deb55df 100644 --- a/src/StackExchange.Redis/RedisBase.cs +++ b/src/StackExchange.Redis/RedisBase.cs @@ -1,22 +1,25 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; namespace StackExchange.Redis { - internal abstract partial class RedisBase : IRedis + internal abstract class RedisBase : IRedis { internal static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - internal readonly ConnectionMultiplexer multiplexer; - protected readonly object? asyncState; + internal readonly ConnectionMultiplexer Multiplexer; + internal readonly object? AsyncState; internal RedisBase(ConnectionMultiplexer multiplexer, object? asyncState) { - this.multiplexer = multiplexer; - this.asyncState = asyncState; + Multiplexer = multiplexer; + AsyncState = asyncState; } - IConnectionMultiplexer IRedisAsync.Multiplexer => multiplexer; + IConnectionMultiplexer IRedisAsync.Multiplexer => Multiplexer; + + internal CancellationToken GetEffectiveCancellationToken() => Multiplexer.GetEffectiveCancellationToken(); public virtual TimeSpan Ping(CommandFlags flags = CommandFlags.None) { @@ -30,42 +33,48 @@ public virtual Task PingAsync(CommandFlags flags = CommandFlags.None) return ExecuteAsync(msg, ResultProcessor.ResponseTimer); } - public override string ToString() => multiplexer.ToString(); + public override string ToString() => Multiplexer.ToString(); - public bool TryWait(Task task) => task.Wait(multiplexer.TimeoutMilliseconds); + public bool TryWait(Task task) => task.Wait(Multiplexer.TimeoutMilliseconds); - public void Wait(Task task) => multiplexer.Wait(task); + public void Wait(Task task) => Multiplexer.Wait(task); - public T Wait(Task task) => multiplexer.Wait(task); + public T Wait(Task task) => Multiplexer.Wait(task); - public void WaitAll(params Task[] tasks) => multiplexer.WaitAll(tasks); + public void WaitAll(params Task[] tasks) => Multiplexer.WaitAll(tasks); internal virtual Task ExecuteAsync(Message? message, ResultProcessor? processor, T defaultValue, ServerEndPoint? server = null) { - if (message is null) return CompletedTask.FromDefault(defaultValue, asyncState); - multiplexer.CheckMessage(message); - return multiplexer.ExecuteAsyncImpl(message, processor, asyncState, server, defaultValue); + if (message is null) return CompletedTask.FromDefault(defaultValue, AsyncState); + Multiplexer.CheckMessage(message); + + // The message already captures the ambient cancellation token when it was created, + // so we don't need to pass it again. This ensures resent messages preserve their original cancellation context. + return Multiplexer.ExecuteAsyncImpl(message, processor, AsyncState, server, defaultValue); } internal virtual Task ExecuteAsync(Message? message, ResultProcessor? processor, ServerEndPoint? server = null) { - if (message is null) return CompletedTask.Default(asyncState); - multiplexer.CheckMessage(message); - return multiplexer.ExecuteAsyncImpl(message, processor, asyncState, server); + if (message is null) return CompletedTask.Default(AsyncState); + Multiplexer.CheckMessage(message); + + // The message already captures the ambient cancellation token when it was created, + // so we don't need to pass it again. This ensures resent messages preserve their original cancellation context. + return Multiplexer.ExecuteAsyncImpl(message, processor, AsyncState, server); } [return: NotNullIfNotNull("defaultValue")] internal virtual T? ExecuteSync(Message? message, ResultProcessor? processor, ServerEndPoint? server = null, T? defaultValue = default) { if (message is null) return defaultValue; // no-op - multiplexer.CheckMessage(message); - return multiplexer.ExecuteSyncImpl(message, processor, server, defaultValue); + Multiplexer.CheckMessage(message); + return Multiplexer.ExecuteSyncImpl(message, processor, server, defaultValue); } internal virtual RedisFeatures GetFeatures(in RedisKey key, CommandFlags flags, RedisCommand command, out ServerEndPoint? server) { - server = multiplexer.SelectServer(command, flags, key); - var version = server == null ? multiplexer.RawConfig.DefaultVersion : server.Version; + server = Multiplexer.SelectServer(command, flags, key); + var version = server == null ? Multiplexer.RawConfig.DefaultVersion : server.Version; return new RedisFeatures(version); } @@ -109,16 +118,17 @@ protected static void WhenAlwaysOrNotExists(When when) private ResultProcessor.TimingProcessor.TimerMessage GetTimerMessage(CommandFlags flags) { // do the best we can with available commands - var map = multiplexer.CommandMap; + var map = Multiplexer.CommandMap; + var cancellationToken = GetEffectiveCancellationToken(); if (map.IsAvailable(RedisCommand.PING)) - return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.PING); + return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.PING, default, cancellationToken); if (map.IsAvailable(RedisCommand.TIME)) - return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.TIME); + return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.TIME, default, cancellationToken); if (map.IsAvailable(RedisCommand.ECHO)) - return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.ECHO, RedisLiterals.PING); + return ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.ECHO, RedisLiterals.PING, cancellationToken); // as our fallback, we'll do something odd... we'll treat a key like a value, out of sheer desperation // note: this usually means: twemproxy/envoyproxy - in which case we're fine anyway, since the proxy does the routing - return ResultProcessor.TimingProcessor.CreateMessage(0, flags, RedisCommand.EXISTS, (RedisValue)multiplexer.UniqueId); + return ResultProcessor.TimingProcessor.CreateMessage(0, flags, RedisCommand.EXISTS, (RedisValue)Multiplexer.UniqueId, cancellationToken); } internal static class CursorUtils diff --git a/src/StackExchange.Redis/RedisBatch.cs b/src/StackExchange.Redis/RedisBatch.cs index 0a4c888f2..e87fb8e84 100644 --- a/src/StackExchange.Redis/RedisBatch.cs +++ b/src/StackExchange.Redis/RedisBatch.cs @@ -8,7 +8,7 @@ internal class RedisBatch : RedisDatabase, IBatch { private List? pending; - public RedisBatch(RedisDatabase wrapped, object? asyncState) : base(wrapped.multiplexer, wrapped.Database, asyncState ?? wrapped.AsyncState) { } + public RedisBatch(RedisDatabase wrapped, object? asyncState) : base(wrapped.Multiplexer, wrapped.Database, asyncState ?? wrapped.AsyncState) { } public void Execute() { @@ -24,17 +24,17 @@ public void Execute() List? lastList = null; foreach (var message in snapshot) { - var server = multiplexer.SelectServer(message); + var server = Multiplexer.SelectServer(message); if (server == null) { - FailNoServer(multiplexer, snapshot); - throw ExceptionFactory.NoConnectionAvailable(multiplexer, message, server); + FailNoServer(Multiplexer, snapshot); + throw ExceptionFactory.NoConnectionAvailable(Multiplexer, message, server); } var bridge = server.GetBridge(message); if (bridge == null) { - FailNoServer(multiplexer, snapshot); - throw ExceptionFactory.NoConnectionAvailable(multiplexer, message, server); + FailNoServer(Multiplexer, snapshot); + throw ExceptionFactory.NoConnectionAvailable(Multiplexer, message, server); } // identity a list @@ -58,15 +58,15 @@ public void Execute() { if (!pair.Key.TryEnqueue(pair.Value, pair.Key.ServerEndPoint.IsReplica)) { - FailNoServer(multiplexer, pair.Value); + FailNoServer(Multiplexer, pair.Value); } } } internal override Task ExecuteAsync(Message? message, ResultProcessor? processor, T defaultValue, ServerEndPoint? server = null) { - if (message == null) return CompletedTask.FromDefault(defaultValue, asyncState); - multiplexer.CheckMessage(message); + if (message == null) return CompletedTask.FromDefault(defaultValue, ((RedisBase)this).AsyncState); + Multiplexer.CheckMessage(message); // prepare the inner command as a task Task task; @@ -76,7 +76,7 @@ internal override Task ExecuteAsync(Message? message, ResultProcessor? } else { - var source = TaskResultBox.Create(out var tcs, asyncState); + var source = TaskResultBox.Create(GetEffectiveCancellationToken(), out var tcs, ((RedisBase)this).AsyncState); task = tcs.Task; message.SetSource(source, processor); } @@ -88,8 +88,8 @@ internal override Task ExecuteAsync(Message? message, ResultProcessor? internal override Task ExecuteAsync(Message? message, ResultProcessor? processor, ServerEndPoint? server = null) where T : default { - if (message == null) return CompletedTask.Default(asyncState); - multiplexer.CheckMessage(message); + if (message == null) return CompletedTask.Default(((RedisBase)this).AsyncState); + Multiplexer.CheckMessage(message); // prepare the inner command as a task Task task; @@ -99,7 +99,7 @@ internal override Task ExecuteAsync(Message? message, ResultProcessor? } else { - var source = TaskResultBox.Create(out var tcs, asyncState); + var source = TaskResultBox.Create(message.CancellationToken, out var tcs, ((RedisBase)this).AsyncState); task = tcs.Task; message.SetSource(source!, processor); } diff --git a/src/StackExchange.Redis/RedisCancellationExtensions.cs b/src/StackExchange.Redis/RedisCancellationExtensions.cs new file mode 100644 index 000000000..bbee65960 --- /dev/null +++ b/src/StackExchange.Redis/RedisCancellationExtensions.cs @@ -0,0 +1,247 @@ +using System; +using System.Threading; + +namespace StackExchange.Redis +{ + /// + /// Extension methods for adding ambient cancellation support to Redis operations. + /// + public static class RedisCancellationExtensions + { + /// + /// Sets an ambient cancellation token that will be used for all Redis operations + /// in the current async context until the returned scope is disposed. + /// + /// The Redis instance (used for extension method syntax). + /// The cancellation token to use for operations in this scope. + /// A disposable scope that restores the previous cancellation context when disposed. + /// + /// + /// using (database.WithCancellation(cancellationToken)) + /// { + /// await database.StringSetAsync("key", "value"); + /// var value = await database.StringGetAsync("key"); + /// } + /// + /// + public static IDisposable WithCancellation(this IConnectionMultiplexer redis, CancellationToken cancellationToken) + => new CancellationScope(redis, cancellationToken, null); + + /// + /// Sets an ambient timeout that will be used for all Redis operations + /// in the current async context until the returned scope is disposed. + /// + /// The Redis instance (used for extension method syntax). + /// The timeout to use for operations in this scope. + /// A disposable scope that restores the previous cancellation context when disposed. + /// + /// + /// using (database.WithTimeout(TimeSpan.FromSeconds(5))) + /// { + /// await database.StringSetAsync("key", "value"); + /// } + /// + /// + public static IDisposable WithTimeout(this IConnectionMultiplexer redis, TimeSpan timeout) + => new CancellationScope(redis, CancellationToken.None, timeout); + + /// + /// Sets an ambient timeout that will be used for all Redis operations + /// in the current async context until the returned scope is disposed. + /// + /// The Redis instance (used for extension method syntax). + /// The timeout, in milliseconds, to use for operations in this scope. + /// A disposable scope that restores the previous cancellation context when disposed. + /// + /// + /// using (database.WithTimeout(5000)) + /// { + /// await database.StringSetAsync("key", "value"); + /// } + /// + /// + public static IDisposable WithTimeout(this IConnectionMultiplexer redis, int milliseconds) + => new CancellationScope(redis, CancellationToken.None, TimeSpan.FromMilliseconds(milliseconds)); + + /// + /// Sets both an ambient cancellation token and timeout that will be used for all Redis operations + /// in the current async context until the returned scope is disposed. + /// + /// The Redis instance (used for extension method syntax). + /// The cancellation token to use for operations in this scope. + /// The timeout to use for operations in this scope. + /// A disposable scope that restores the previous cancellation context when disposed. + /// + /// + /// using (database.WithCancellationAndTimeout(cancellationToken, TimeSpan.FromSeconds(10))) + /// { + /// await database.StringSetAsync("key", "value"); + /// } + /// + /// + public static IDisposable WithCancellationAndTimeout( + this IConnectionMultiplexer redis, + CancellationToken cancellationToken, + TimeSpan timeout) + => new CancellationScope(redis, cancellationToken, timeout); + + /// + /// Sets both an ambient cancellation token and timeout that will be used for all Redis operations + /// in the current async context until the returned scope is disposed. + /// + /// The Redis instance (used for extension method syntax). + /// The cancellation token to use for operations in this scope. + /// The timeout to use for operations in this scope. + /// A disposable scope that restores the previous cancellation context when disposed. + /// + /// + /// using (database.WithCancellationAndTimeout(cancellationToken, TimeSpan.FromSeconds(10))) + /// { + /// await database.StringSetAsync("key", "value"); + /// } + /// + /// + public static IDisposable WithCancellationAndTimeout( + this IConnectionMultiplexer redis, + CancellationToken cancellationToken, + int milliseconds) + => new CancellationScope(redis, cancellationToken, TimeSpan.FromMilliseconds(milliseconds)); + + /// + /// Gets the effective cancellation token for the current async context, + /// combining any ambient cancellation token and timeout. + /// + /// The effective cancellation token, or CancellationToken.None if no ambient context is set. + internal static CancellationToken GetEffectiveCancellationToken(this IConnectionMultiplexer redis, bool checkForCancellation = true) + { + var scope = _context.Value; + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - trust no-one + if (redis is not null) + { + // check for the top scope *that relates to this redis instance* + while (scope is not null) + { + var fromScope = scope.Target; // need to null-check because weak-ref / GC + if (fromScope is not null && fromScope.Equals(redis)) + { + var token = scope.Token; + if (checkForCancellation) + { + token.ThrowIfCancellationRequested(); + } + + return token; + } + scope = scope.Previous; + } + } + + return CancellationToken.None; + } + + /// + /// Gets the current cancellation scope for diagnostic purposes. + /// + /// The current scope, or null if no ambient context is set. + internal static object? GetCurrentScope(this IConnectionMultiplexer redis) + { + var scope = _context.Value; + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - trust no-one + if (redis is not null) + { + // check for the top scope *that relates to this redis instance* + while (scope is not null) + { + var scopeMuxer = scope.Target; // need to null-check because weak-ref / GC + if (scopeMuxer is not null && scopeMuxer.Equals(redis)) + { + return scope; + } + scope = scope.Previous; + } + } + + return null; + } + + private static readonly AsyncLocal _context = new(); + + /// + /// A disposable scope that manages the ambient cancellation context. + /// + private sealed class CancellationScope : WeakReference, IDisposable + { + private readonly CancellationTokenSource? _ownedSource; + public CancellationToken Token { get; } + public CancellationScope? Previous { get; } + private bool _disposed; + + /// + /// Creates a new cancellation scope with the specified token and timeout. + /// + /// The parent instance. + /// The cancellation token for this scope. + /// The timeout for this scope. + public CancellationScope(object redis, CancellationToken cancellationToken, TimeSpan? timeout) + : base(redis ?? throw new ArgumentNullException(nameof(redis))) + { + Previous = _context.Value; + if (timeout.HasValue) + { + // has a timeout + if (cancellationToken.CanBeCanceled) + { + // need both timeout and cancellation; but we can avoid some layers if + // we're already doomed + if (cancellationToken.IsCancellationRequested) + { + Token = cancellationToken; + } + else + { + _ownedSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + _ownedSource.CancelAfter(timeout.GetValueOrDefault()); + Token = _ownedSource.Token; + } + } + else + { + // just a timeout + _ownedSource = new CancellationTokenSource(timeout.GetValueOrDefault()); + Token = _ownedSource.Token; + } + } + else if (cancellationToken.CanBeCanceled) + { + // nice and simple, just a CT + Token = cancellationToken; + } + else + { + Token = CancellationToken.None; + } + + _context.Value = this; + } + + /// + /// Restores the previous cancellation context. + /// + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + if (ReferenceEquals(_context.Value, this)) + { + // reinstate the previous context + _context.Value = Previous; + } + _ownedSource?.Dispose(); + } + } + } + } +} diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 716176662..838674bb3 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -2,6 +2,7 @@ using System.Buffers; using System.Collections.Generic; using System.Net; +using System.Threading; using System.Threading.Tasks; using Pipelines.Sockets.Unofficial.Arenas; @@ -15,8 +16,6 @@ internal RedisDatabase(ConnectionMultiplexer multiplexer, int db, object? asyncS Database = db; } - public object? AsyncState => asyncState; - public int Database { get; } public IBatch CreateBatch(object? asyncState) @@ -33,7 +32,7 @@ public ITransaction CreateTransaction(object? asyncState) private ITransaction? CreateTransactionIfAvailable(object? asyncState) { - var map = multiplexer.CommandMap; + var map = Multiplexer.CommandMap; if (!map.IsAvailable(RedisCommand.MULTI) || !map.IsAvailable(RedisCommand.EXEC)) { return null; @@ -43,13 +42,13 @@ public ITransaction CreateTransaction(object? asyncState) public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key); + var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Task DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key); + var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -65,25 +64,25 @@ public Task GeoAddAsync(RedisKey key, double longitude, double latitude, R public bool GeoAdd(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, value.Longitude, value.Latitude, value.Member); + var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, value.Longitude, value.Latitude, value.Member, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public Task GeoAddAsync(RedisKey key, GeoEntry value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, value.Longitude, value.Latitude, value.Member); + var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, value.Longitude, value.Latitude, value.Member, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public long GeoAdd(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, values); + var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, values, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task GeoAddAsync(RedisKey key, GeoEntry[] values, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, values); + var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, values, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -99,13 +98,13 @@ public Task GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags f public double? GeoDistance(RedisKey key, RedisValue member1, RedisValue member2, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GEODIST, key, member1, member2, StackExchange.Redis.GeoPosition.GetRedisUnit(unit)); + var msg = Message.Create(Database, flags, RedisCommand.GEODIST, key, member1, member2, Redis.GeoPosition.GetRedisUnit(unit), GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.NullableDouble); } public Task GeoDistanceAsync(RedisKey key, RedisValue value0, RedisValue value1, GeoUnit unit = GeoUnit.Meters, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GEODIST, key, value0, value1, StackExchange.Redis.GeoPosition.GetRedisUnit(unit)); + var msg = Message.Create(Database, flags, RedisCommand.GEODIST, key, value0, value1, Redis.GeoPosition.GetRedisUnit(unit), GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.NullableDouble); } @@ -114,7 +113,7 @@ public Task GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags f if (members == null) throw new ArgumentNullException(nameof(members)); var redisValues = new RedisValue[members.Length]; for (var i = 0; i < members.Length; i++) redisValues[i] = members[i]; - var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues); + var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.NullableStringArray, defaultValue: Array.Empty()); } @@ -123,19 +122,19 @@ public Task GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags f if (members == null) throw new ArgumentNullException(nameof(members)); var redisValues = new RedisValue[members.Length]; for (var i = 0; i < members.Length; i++) redisValues[i] = members[i]; - var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues); + var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.NullableStringArray, defaultValue: Array.Empty()); } public string? GeoHash(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, member); + var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, member, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.String); } public Task GeoHashAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, member); + var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, member, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.String); } @@ -144,7 +143,7 @@ public Task GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags f if (members == null) throw new ArgumentNullException(nameof(members)); var redisValues = new RedisValue[members.Length]; for (var i = 0; i < members.Length; i++) redisValues[i] = members[i]; - var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, redisValues); + var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, redisValues, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisGeoPositionArray, defaultValue: Array.Empty()); } @@ -153,19 +152,19 @@ public Task GeoRemoveAsync(RedisKey key, RedisValue member, CommandFlags f if (members == null) throw new ArgumentNullException(nameof(members)); var redisValues = new RedisValue[members.Length]; for (var i = 0; i < members.Length; i++) redisValues[i] = members[i]; - var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, redisValues); + var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, redisValues, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisGeoPositionArray, defaultValue: Array.Empty()); } public GeoPosition? GeoPosition(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, member); + var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, member, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisGeoPosition); } public Task GeoPositionAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, member); + var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, member, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisGeoPosition); } @@ -212,9 +211,10 @@ private Message GetGeoSearchMessage(in RedisKey sourceKey, in RedisKey destinati redisValues.Add(RedisLiterals.STOREDIST); } + var cancellationToken = GetEffectiveCancellationToken(); return destinationKey.IsNull - ? Message.Create(Database, flags, RedisCommand.GEOSEARCH, sourceKey, redisValues.ToArray()) - : Message.Create(Database, flags, RedisCommand.GEOSEARCHSTORE, destinationKey, sourceKey, redisValues.ToArray()); + ? Message.Create(Database, flags, RedisCommand.GEOSEARCH, sourceKey, redisValues.ToArray(), cancellationToken) + : Message.Create(Database, flags, RedisCommand.GEOSEARCHSTORE, destinationKey, sourceKey, redisValues.ToArray(), cancellationToken); } private Message GetGeoRadiusMessage(in RedisKey key, RedisValue? member, double longitude, double latitude, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags) @@ -247,7 +247,7 @@ private Message GetGeoRadiusMessage(in RedisKey key, RedisValue? member, double redisValues.Add(order.Value.ToLiteral()); } - return Message.Create(Database, flags, command, key, redisValues.ToArray()); + return Message.Create(Database, flags, command, key, redisValues.ToArray(), GetEffectiveCancellationToken()); } public GeoRadiusResult[] GeoRadius(RedisKey key, RedisValue member, double radius, GeoUnit unit, int count, Order? order, GeoRadiusOptions options, CommandFlags flags) @@ -350,20 +350,20 @@ public Task HashDecrementAsync(RedisKey key, RedisValue hashField, doubl public bool HashDelete(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HDEL, key, hashField); + var msg = Message.Create(Database, flags, RedisCommand.HDEL, key, hashField, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public long HashDelete(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - var msg = hashFields.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.HDEL, key, hashFields); + var msg = hashFields.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.HDEL, key, hashFields, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task HashDeleteAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HDEL, key, hashField); + var msg = Message.Create(Database, flags, RedisCommand.HDEL, key, hashField, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -371,19 +371,19 @@ public Task HashDeleteAsync(RedisKey key, RedisValue[] hashFields, Command { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - var msg = hashFields.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.HDEL, key, hashFields); + var msg = hashFields.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.HDEL, key, hashFields, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public bool HashExists(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HEXISTS, key, hashField); + var msg = Message.Create(Database, flags, RedisCommand.HEXISTS, key, hashField, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public Task HashExistsAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HEXISTS, key, hashField); + var msg = Message.Create(Database, flags, RedisCommand.HEXISTS, key, hashField, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -424,7 +424,7 @@ private T HashFieldExpireExecute(RedisKey key, long milliseconds, _ => new List { expiry, when.ToLiteral(), RedisLiterals.FIELDS, hashFields.Length }, }; values.AddRange(hashFields); - var msg = Message.Create(Database, flags, cmd, key, values.ToArray()); + var msg = Message.Create(Database, flags, cmd, key, values.ToArray(), GetEffectiveCancellationToken()); return executor(msg, processor); } @@ -436,7 +436,7 @@ private T HashFieldExecute(RedisCommand cmd, RedisKey key, Custom { var values = new List { RedisLiterals.FIELDS, hashFields.Length }; values.AddRange(hashFields); - var msg = Message.Create(Database, flags, cmd, key, values.ToArray()); + var msg = Message.Create(Database, flags, cmd, key, values.ToArray(), GetEffectiveCancellationToken()); return executor(msg, processor); } @@ -466,13 +466,13 @@ public Task HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashF public RedisValue HashGet(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField); + var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Lease? HashGetLease(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField); + var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Lease); } @@ -480,127 +480,127 @@ public RedisValue[] HashGet(RedisKey key, RedisValue[] hashFields, CommandFlags { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); if (hashFields.Length == 0) return Array.Empty(); - var msg = Message.Create(Database, flags, RedisCommand.HMGET, key, hashFields); + var msg = Message.Create(Database, flags, RedisCommand.HMGET, key, hashFields, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public HashEntry[] HashGetAll(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HGETALL, key); + var msg = Message.Create(Database, flags, RedisCommand.HGETALL, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.HashEntryArray, defaultValue: Array.Empty()); } public Task HashGetAllAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HGETALL, key); + var msg = Message.Create(Database, flags, RedisCommand.HGETALL, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.HashEntryArray, defaultValue: Array.Empty()); } public Task HashGetAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField); + var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public Task?> HashGetLeaseAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField); + var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Lease); } public Task HashGetAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) { if (hashFields == null) throw new ArgumentNullException(nameof(hashFields)); - if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); - var msg = Message.Create(Database, flags, RedisCommand.HMGET, key, hashFields); + if (hashFields.Length == 0) return CompletedTask.FromDefault(Array.Empty(), AsyncState); + var msg = Message.Create(Database, flags, RedisCommand.HMGET, key, hashFields, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public long HashIncrement(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) { var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 - ? null : Message.Create(Database, flags, RedisCommand.HINCRBY, key, hashField, value); + ? null : Message.Create(Database, flags, RedisCommand.HINCRBY, key, hashField, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public double HashIncrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) { var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 - ? null : Message.Create(Database, flags, RedisCommand.HINCRBYFLOAT, key, hashField, value); + ? null : Message.Create(Database, flags, RedisCommand.HINCRBYFLOAT, key, hashField, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Double); } public Task HashIncrementAsync(RedisKey key, RedisValue hashField, long value = 1, CommandFlags flags = CommandFlags.None) { var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 - ? null : Message.Create(Database, flags, RedisCommand.HINCRBY, key, hashField, value); + ? null : Message.Create(Database, flags, RedisCommand.HINCRBY, key, hashField, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public Task HashIncrementAsync(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None) { var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 - ? null : Message.Create(Database, flags, RedisCommand.HINCRBYFLOAT, key, hashField, value); + ? null : Message.Create(Database, flags, RedisCommand.HINCRBYFLOAT, key, hashField, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Double); } public RedisValue[] HashKeys(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HKEYS, key); + var msg = Message.Create(Database, flags, RedisCommand.HKEYS, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public Task HashKeysAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HKEYS, key); + var msg = Message.Create(Database, flags, RedisCommand.HKEYS, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public long HashLength(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HLEN, key); + var msg = Message.Create(Database, flags, RedisCommand.HLEN, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public RedisValue HashRandomField(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key); + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public RedisValue[] HashRandomFields(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count); + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public HashEntry[] HashRandomFieldsWithValues(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count, RedisLiterals.WITHVALUES); + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count, RedisLiterals.WITHVALUES, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.HashEntryArray, defaultValue: Array.Empty()); } public Task HashLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HLEN, key); + var msg = Message.Create(Database, flags, RedisCommand.HLEN, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public Task HashRandomFieldAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key); + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public Task HashRandomFieldsAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count); + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public Task HashRandomFieldsWithValuesAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count, RedisLiterals.WITHVALUES); + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count, RedisLiterals.WITHVALUES, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.HashEntryArray, defaultValue: Array.Empty()); } @@ -645,8 +645,8 @@ public bool HashSet(RedisKey key, RedisValue hashField, RedisValue value, When w { WhenAlwaysOrNotExists(when); var msg = value.IsNull - ? Message.Create(Database, flags, RedisCommand.HDEL, key, hashField) - : Message.Create(Database, flags, when == When.Always ? RedisCommand.HSET : RedisCommand.HSETNX, key, hashField, value); + ? Message.Create(Database, flags, RedisCommand.HDEL, key, hashField, GetEffectiveCancellationToken()) + : Message.Create(Database, flags, when == When.Always ? RedisCommand.HSET : RedisCommand.HSETNX, key, hashField, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } @@ -659,7 +659,7 @@ public void HashSet(RedisKey key, HashEntry[] hashFields, CommandFlags flags = C public long HashStringLength(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HSTRLEN, key, hashField); + var msg = Message.Create(Database, flags, RedisCommand.HSTRLEN, key, hashField, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -667,14 +667,14 @@ public Task HashSetAsync(RedisKey key, RedisValue hashField, RedisValue va { WhenAlwaysOrNotExists(when); var msg = value.IsNull - ? Message.Create(Database, flags, RedisCommand.HDEL, key, hashField) - : Message.Create(Database, flags, when == When.Always ? RedisCommand.HSET : RedisCommand.HSETNX, key, hashField, value); + ? Message.Create(Database, flags, RedisCommand.HDEL, key, hashField, GetEffectiveCancellationToken()) + : Message.Create(Database, flags, when == When.Always ? RedisCommand.HSET : RedisCommand.HSETNX, key, hashField, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public Task HashStringLengthAsync(RedisKey key, RedisValue hashField, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HSTRLEN, key, hashField); + var msg = Message.Create(Database, flags, RedisCommand.HSTRLEN, key, hashField, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -686,50 +686,50 @@ public Task HashSetAsync(RedisKey key, HashEntry[] hashFields, CommandFlags flag public Task HashSetIfNotExistsAsync(RedisKey key, RedisValue hashField, RedisValue value, CommandFlags flags) { - var msg = Message.Create(Database, flags, RedisCommand.HSETNX, key, hashField, value); + var msg = Message.Create(Database, flags, RedisCommand.HSETNX, key, hashField, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public RedisValue[] HashValues(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HVALS, key); + var msg = Message.Create(Database, flags, RedisCommand.HVALS, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public Task HashValuesAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.HVALS, key); + var msg = Message.Create(Database, flags, RedisCommand.HVALS, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public bool HyperLogLogAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { - var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, value); + var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, value, GetEffectiveCancellationToken()); return ExecuteSync(cmd, ResultProcessor.Boolean); } public bool HyperLogLogAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) { - var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, values); + var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, values, GetEffectiveCancellationToken()); return ExecuteSync(cmd, ResultProcessor.Boolean); } public Task HyperLogLogAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { - var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, value); + var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, value, GetEffectiveCancellationToken()); return ExecuteAsync(cmd, ResultProcessor.Boolean); } public Task HyperLogLogAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) { - var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, values); + var cmd = Message.Create(Database, flags, RedisCommand.PFADD, key, values, GetEffectiveCancellationToken()); return ExecuteAsync(cmd, ResultProcessor.Boolean); } public long HyperLogLogLength(RedisKey key, CommandFlags flags = CommandFlags.None) { var features = GetFeatures(key, flags, RedisCommand.PFCOUNT, out ServerEndPoint? server); - var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, key); + var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, key, GetEffectiveCancellationToken()); // technically a write / primary-only command until 2.8.18 if (server != null && !features.HyperLogLogCountReplicaSafe) cmd.SetPrimaryOnly(); return ExecuteSync(cmd, ResultProcessor.Int64, server); @@ -739,7 +739,7 @@ public long HyperLogLogLength(RedisKey[] keys, CommandFlags flags = CommandFlags { if (keys == null) throw new ArgumentNullException(nameof(keys)); ServerEndPoint? server = null; - var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, keys); + var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, keys, GetEffectiveCancellationToken()); if (keys.Length != 0) { var features = GetFeatures(keys[0], flags, RedisCommand.PFCOUNT, out server); @@ -752,7 +752,7 @@ public long HyperLogLogLength(RedisKey[] keys, CommandFlags flags = CommandFlags public Task HyperLogLogLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { var features = GetFeatures(key, flags, RedisCommand.PFCOUNT, out ServerEndPoint? server); - var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, key); + var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, key, GetEffectiveCancellationToken()); // technically a write / primary-only command until 2.8.18 if (server != null && !features.HyperLogLogCountReplicaSafe) cmd.SetPrimaryOnly(); return ExecuteAsync(cmd, ResultProcessor.Int64, server); @@ -762,7 +762,7 @@ public Task HyperLogLogLengthAsync(RedisKey[] keys, CommandFlags flags = C { if (keys == null) throw new ArgumentNullException(nameof(keys)); ServerEndPoint? server = null; - var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, keys); + var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, keys, GetEffectiveCancellationToken()); if (keys.Length != 0) { var features = GetFeatures(keys[0], flags, RedisCommand.PFCOUNT, out server); @@ -774,43 +774,45 @@ public Task HyperLogLogLengthAsync(RedisKey[] keys, CommandFlags flags = C public void HyperLogLogMerge(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) { - var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, first, second); + var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, first, second, GetEffectiveCancellationToken()); ExecuteSync(cmd, ResultProcessor.DemandOK); } public void HyperLogLogMerge(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) { - var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, sourceKeys); + var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, sourceKeys, GetEffectiveCancellationToken()); ExecuteSync(cmd, ResultProcessor.DemandOK); } public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) { - var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, first, second); + var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, first, second, GetEffectiveCancellationToken()); return ExecuteAsync(cmd, ResultProcessor.DemandOK); } public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey[] sourceKeys, CommandFlags flags = CommandFlags.None) { - var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, sourceKeys); + var cmd = Message.Create(Database, flags, RedisCommand.PFMERGE, destination, sourceKeys, GetEffectiveCancellationToken()); return ExecuteAsync(cmd, ResultProcessor.DemandOK); } public EndPoint? IdentifyEndpoint(RedisKey key = default, CommandFlags flags = CommandFlags.None) { - var msg = key.IsNull ? Message.Create(-1, flags, RedisCommand.PING) : Message.Create(Database, flags, RedisCommand.EXISTS, key); + var cancellationToken = GetEffectiveCancellationToken(); + var msg = key.IsNull ? Message.Create(-1, flags, RedisCommand.PING, cancellationToken) : Message.Create(Database, flags, RedisCommand.EXISTS, key, cancellationToken); return ExecuteSync(msg, ResultProcessor.ConnectionIdentity); } public Task IdentifyEndpointAsync(RedisKey key = default, CommandFlags flags = CommandFlags.None) { - var msg = key.IsNull ? Message.Create(-1, flags, RedisCommand.PING) : Message.Create(Database, flags, RedisCommand.EXISTS, key); + var cancellationToken = GetEffectiveCancellationToken(); + var msg = key.IsNull ? Message.Create(-1, flags, RedisCommand.PING, cancellationToken) : Message.Create(Database, flags, RedisCommand.EXISTS, key, cancellationToken); return ExecuteAsync(msg, ResultProcessor.ConnectionIdentity); } public bool IsConnected(RedisKey key, CommandFlags flags = CommandFlags.None) { - var server = multiplexer.SelectServer(RedisCommand.PING, flags, key); + var server = Multiplexer.SelectServer(RedisCommand.PING, flags, key); return server?.IsConnected == true; } @@ -829,7 +831,7 @@ public Task KeyCopyAsync(RedisKey sourceKey, RedisKey destinationKey, int public bool KeyDelete(RedisKey key, CommandFlags flags = CommandFlags.None) { var cmd = GetDeleteCommand(key, flags, out var server); - var msg = Message.Create(Database, flags, cmd, key); + var msg = Message.Create(Database, flags, cmd, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.DemandZeroOrOne, server); } @@ -839,7 +841,7 @@ public long KeyDelete(RedisKey[] keys, CommandFlags flags = CommandFlags.None) if (keys.Length > 0) { var cmd = GetDeleteCommand(keys[0], flags, out var server); - var msg = keys.Length == 0 ? null : Message.Create(Database, flags, cmd, keys); + var msg = keys.Length == 0 ? null : Message.Create(Database, flags, cmd, keys, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64, server); } return 0; @@ -848,7 +850,7 @@ public long KeyDelete(RedisKey[] keys, CommandFlags flags = CommandFlags.None) public Task KeyDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { var cmd = GetDeleteCommand(key, flags, out var server); - var msg = Message.Create(Database, flags, cmd, key); + var msg = Message.Create(Database, flags, cmd, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandZeroOrOne, server); } @@ -858,7 +860,7 @@ public Task KeyDeleteAsync(RedisKey[] keys, CommandFlags flags = CommandFl if (keys.Length > 0) { var cmd = GetDeleteCommand(keys[0], flags, out var server); - var msg = keys.Length == 0 ? null : Message.Create(Database, flags, cmd, keys); + var msg = keys.Length == 0 ? null : Message.Create(Database, flags, cmd, keys, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64, server); } return CompletedTask.Default(0); @@ -867,7 +869,7 @@ public Task KeyDeleteAsync(RedisKey[] keys, CommandFlags flags = CommandFl private RedisCommand GetDeleteCommand(RedisKey key, CommandFlags flags, out ServerEndPoint? server) { var features = GetFeatures(key, flags, RedisCommand.UNLINK, out server); - if (server != null && features.Unlink && multiplexer.CommandMap.IsAvailable(RedisCommand.UNLINK)) + if (server != null && features.Unlink && Multiplexer.CommandMap.IsAvailable(RedisCommand.UNLINK)) { return RedisCommand.UNLINK; } @@ -876,49 +878,49 @@ private RedisCommand GetDeleteCommand(RedisKey key, CommandFlags flags, out Serv public byte[]? KeyDump(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.DUMP, key); + var msg = Message.Create(Database, flags, RedisCommand.DUMP, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.ByteArray); } public Task KeyDumpAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.DUMP, key); + var msg = Message.Create(Database, flags, RedisCommand.DUMP, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.ByteArray); } public string? KeyEncoding(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.ENCODING, key); + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.ENCODING, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.String); } public Task KeyEncodingAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.ENCODING, key); + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.ENCODING, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.String); } public bool KeyExists(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.EXISTS, key); + var msg = Message.Create(Database, flags, RedisCommand.EXISTS, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public long KeyExists(RedisKey[] keys, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.EXISTS, keys); + var msg = Message.Create(Database, flags, RedisCommand.EXISTS, keys, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task KeyExistsAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.EXISTS, key); + var msg = Message.Create(Database, flags, RedisCommand.EXISTS, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public Task KeyExistsAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.EXISTS, keys); + var msg = Message.Create(Database, flags, RedisCommand.EXISTS, keys, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -960,51 +962,51 @@ public Task KeyExpireAsync(RedisKey key, DateTime? expire, ExpireWhen when public DateTime? KeyExpireTime(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.PEXPIRETIME, key); + var msg = Message.Create(Database, flags, RedisCommand.PEXPIRETIME, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.NullableDateTimeFromMilliseconds); } public Task KeyExpireTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.PEXPIRETIME, key); + var msg = Message.Create(Database, flags, RedisCommand.PEXPIRETIME, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.NullableDateTimeFromMilliseconds); } public long? KeyFrequency(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.FREQ, key); + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.FREQ, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.NullableInt64); } public Task KeyFrequencyAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.FREQ, key); + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.FREQ, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.NullableInt64); } public TimeSpan? KeyIdleTime(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.IDLETIME, key); + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.IDLETIME, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.TimeSpanFromSeconds); } public Task KeyIdleTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.IDLETIME, key); + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.IDLETIME, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.TimeSpanFromSeconds); } public void KeyMigrate(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) { - if (timeoutMilliseconds <= 0) timeoutMilliseconds = multiplexer.TimeoutMilliseconds; - var msg = new KeyMigrateCommandMessage(Database, key, toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); + if (timeoutMilliseconds <= 0) timeoutMilliseconds = Multiplexer.TimeoutMilliseconds; + var msg = new KeyMigrateCommandMessage(Database, key, toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags, GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task KeyMigrateAsync(RedisKey key, EndPoint toServer, int toDatabase = 0, int timeoutMilliseconds = 0, MigrateOptions migrateOptions = MigrateOptions.None, CommandFlags flags = CommandFlags.None) { - if (timeoutMilliseconds <= 0) timeoutMilliseconds = multiplexer.TimeoutMilliseconds; - var msg = new KeyMigrateCommandMessage(Database, key, toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags); + if (timeoutMilliseconds <= 0) timeoutMilliseconds = Multiplexer.TimeoutMilliseconds; + var msg = new KeyMigrateCommandMessage(Database, key, toServer, toDatabase, timeoutMilliseconds, migrateOptions, flags, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } @@ -1015,8 +1017,8 @@ private sealed class KeyMigrateCommandMessage : Message.CommandKeyBase // MIGRAT private readonly int toDatabase; private readonly RedisValue toHost, toPort; - public KeyMigrateCommandMessage(int db, RedisKey key, EndPoint toServer, int toDatabase, int timeoutMilliseconds, MigrateOptions migrateOptions, CommandFlags flags) - : base(db, flags, RedisCommand.MIGRATE, key) + public KeyMigrateCommandMessage(int db, RedisKey key, EndPoint toServer, int toDatabase, int timeoutMilliseconds, MigrateOptions migrateOptions, CommandFlags flags, CancellationToken cancellationToken) + : base(db, flags, RedisCommand.MIGRATE, key, cancellationToken) { if (toServer == null) throw new ArgumentNullException(nameof(toServer)); if (!Format.TryGetHostPort(toServer, out string? toHost, out int? toPort)) throw new ArgumentException($"Couldn't get host and port from {toServer}", nameof(toServer)); @@ -1055,63 +1057,63 @@ public override int ArgCount public bool KeyMove(RedisKey key, int database, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.MOVE, key, database); + var msg = Message.Create(Database, flags, RedisCommand.MOVE, key, database, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public Task KeyMoveAsync(RedisKey key, int database, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.MOVE, key, database); + var msg = Message.Create(Database, flags, RedisCommand.MOVE, key, database, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public bool KeyPersist(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.PERSIST, key); + var msg = Message.Create(Database, flags, RedisCommand.PERSIST, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public Task KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.PERSIST, key); + var msg = Message.Create(Database, flags, RedisCommand.PERSIST, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public RedisKey KeyRandom(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.RANDOMKEY); + var msg = Message.Create(Database, flags, RedisCommand.RANDOMKEY, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisKey); } public Task KeyRandomAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.RANDOMKEY); + var msg = Message.Create(Database, flags, RedisCommand.RANDOMKEY, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisKey); } public long? KeyRefCount(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.REFCOUNT, key); + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.REFCOUNT, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.NullableInt64); } public Task KeyRefCountAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.REFCOUNT, key); + var msg = Message.Create(Database, flags, RedisCommand.OBJECT, RedisLiterals.REFCOUNT, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.NullableInt64); } public bool KeyRename(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) { WhenAlwaysOrNotExists(when); - var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RENAME : RedisCommand.RENAMENX, key, newKey); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RENAME : RedisCommand.RENAMENX, key, newKey, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public Task KeyRenameAsync(RedisKey key, RedisKey newKey, When when = When.Always, CommandFlags flags = CommandFlags.None) { WhenAlwaysOrNotExists(when); - var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RENAME : RedisCommand.RENAMENX, key, newKey); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RENAME : RedisCommand.RENAMENX, key, newKey, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -1131,12 +1133,12 @@ public Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, { var features = GetFeatures(key, flags, RedisCommand.TTL, out ServerEndPoint? server); Message msg; - if (server != null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(RedisCommand.PTTL)) + if (server != null && features.MillisecondExpiry && Multiplexer.CommandMap.IsAvailable(RedisCommand.PTTL)) { - msg = Message.Create(Database, flags, RedisCommand.PTTL, key); + msg = Message.Create(Database, flags, RedisCommand.PTTL, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.TimeSpanFromMilliseconds, server); } - msg = Message.Create(Database, flags, RedisCommand.TTL, key); + msg = Message.Create(Database, flags, RedisCommand.TTL, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.TimeSpanFromSeconds); } @@ -1144,72 +1146,72 @@ public Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, { var features = GetFeatures(key, flags, RedisCommand.TTL, out ServerEndPoint? server); Message msg; - if (server != null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(RedisCommand.PTTL)) + if (server != null && features.MillisecondExpiry && Multiplexer.CommandMap.IsAvailable(RedisCommand.PTTL)) { - msg = Message.Create(Database, flags, RedisCommand.PTTL, key); + msg = Message.Create(Database, flags, RedisCommand.PTTL, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.TimeSpanFromMilliseconds, server); } - msg = Message.Create(Database, flags, RedisCommand.TTL, key); + msg = Message.Create(Database, flags, RedisCommand.TTL, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.TimeSpanFromSeconds); } public RedisType KeyType(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.TYPE, key); + var msg = Message.Create(Database, flags, RedisCommand.TYPE, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisType); } public Task KeyTypeAsync(RedisKey key, CommandFlags flags) { - var msg = Message.Create(Database, flags, RedisCommand.TYPE, key); + var msg = Message.Create(Database, flags, RedisCommand.TYPE, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisType); } public RedisValue ListGetByIndex(RedisKey key, long index, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LINDEX, key, index); + var msg = Message.Create(Database, flags, RedisCommand.LINDEX, key, index, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Task ListGetByIndexAsync(RedisKey key, long index, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LINDEX, key, index); + var msg = Message.Create(Database, flags, RedisCommand.LINDEX, key, index, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public long ListInsertAfter(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.AFTER, pivot, value); + var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.AFTER, pivot, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task ListInsertAfterAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.AFTER, pivot, value); + var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.AFTER, pivot, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public long ListInsertBefore(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.BEFORE, pivot, value); + var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.BEFORE, pivot, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task ListInsertBeforeAsync(RedisKey key, RedisValue pivot, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.BEFORE, pivot, value); + var msg = Message.Create(Database, flags, RedisCommand.LINSERT, key, RedisLiterals.BEFORE, pivot, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public RedisValue ListLeftPop(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LPOP, key); + var msg = Message.Create(Database, flags, RedisCommand.LPOP, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public RedisValue[] ListLeftPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LPOP, key, count); + var msg = Message.Create(Database, flags, RedisCommand.LPOP, key, count, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -1233,13 +1235,13 @@ public long[] ListPositions(RedisKey key, RedisValue element, long count, long r public Task ListLeftPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LPOP, key); + var msg = Message.Create(Database, flags, RedisCommand.LPOP, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public Task ListLeftPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LPOP, key, count); + var msg = Message.Create(Database, flags, RedisCommand.LPOP, key, count, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -1264,7 +1266,7 @@ public Task ListPositionsAsync(RedisKey key, RedisValue element, long co public long ListLeftPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) { WhenAlwaysOrExists(when); - var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX, key, value); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX, key, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -1273,21 +1275,22 @@ public long ListLeftPush(RedisKey key, RedisValue[] values, When when = When.Alw WhenAlwaysOrExists(when); if (values == null) throw new ArgumentNullException(nameof(values)); var command = when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX; - var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, command, key, values); + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key, GetEffectiveCancellationToken()) : Message.Create(Database, flags, command, key, values, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public long ListLeftPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) { if (values == null) throw new ArgumentNullException(nameof(values)); - var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, RedisCommand.LPUSH, key, values); + var cancellationToken = GetEffectiveCancellationToken(); + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key, cancellationToken) : Message.Create(Database, flags, RedisCommand.LPUSH, key, values, cancellationToken); return ExecuteSync(msg, ResultProcessor.Int64); } public Task ListLeftPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) { WhenAlwaysOrExists(when); - var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX, key, value); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX, key, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -1296,74 +1299,75 @@ public Task ListLeftPushAsync(RedisKey key, RedisValue[] values, When when WhenAlwaysOrExists(when); if (values == null) throw new ArgumentNullException(nameof(values)); var command = when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX; - var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, command, key, values); + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key, GetEffectiveCancellationToken()) : Message.Create(Database, flags, command, key, values, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public Task ListLeftPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) { if (values == null) throw new ArgumentNullException(nameof(values)); - var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, RedisCommand.LPUSH, key, values); + var cancellationToken = GetEffectiveCancellationToken(); + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key, cancellationToken) : Message.Create(Database, flags, RedisCommand.LPUSH, key, values, cancellationToken); return ExecuteAsync(msg, ResultProcessor.Int64); } public long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LLEN, key); + var msg = Message.Create(Database, flags, RedisCommand.LLEN, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task ListLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LLEN, key); + var msg = Message.Create(Database, flags, RedisCommand.LLEN, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public RedisValue ListMove(RedisKey sourceKey, RedisKey destinationKey, ListSide sourceSide, ListSide destinationSide, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LMOVE, sourceKey, destinationKey, sourceSide.ToLiteral(), destinationSide.ToLiteral()); + var msg = Message.Create(Database, flags, RedisCommand.LMOVE, sourceKey, destinationKey, sourceSide.ToLiteral(), destinationSide.ToLiteral(), GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Task ListMoveAsync(RedisKey sourceKey, RedisKey destinationKey, ListSide sourceSide, ListSide destinationSide, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LMOVE, sourceKey, destinationKey, sourceSide.ToLiteral(), destinationSide.ToLiteral()); + var msg = Message.Create(Database, flags, RedisCommand.LMOVE, sourceKey, destinationKey, sourceSide.ToLiteral(), destinationSide.ToLiteral(), GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public RedisValue[] ListRange(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LRANGE, key, start, stop); + var msg = Message.Create(Database, flags, RedisCommand.LRANGE, key, start, stop, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public Task ListRangeAsync(RedisKey key, long start = 0, long stop = -1, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LRANGE, key, start, stop); + var msg = Message.Create(Database, flags, RedisCommand.LRANGE, key, start, stop, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public long ListRemove(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LREM, key, count, value); + var msg = Message.Create(Database, flags, RedisCommand.LREM, key, count, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task ListRemoveAsync(RedisKey key, RedisValue value, long count = 0, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LREM, key, count, value); + var msg = Message.Create(Database, flags, RedisCommand.LREM, key, count, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public RedisValue ListRightPop(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.RPOP, key); + var msg = Message.Create(Database, flags, RedisCommand.RPOP, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public RedisValue[] ListRightPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.RPOP, key, count); + var msg = Message.Create(Database, flags, RedisCommand.RPOP, key, count, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -1375,13 +1379,13 @@ public ListPopResult ListRightPop(RedisKey[] keys, long count, CommandFlags flag public Task ListRightPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.RPOP, key); + var msg = Message.Create(Database, flags, RedisCommand.RPOP, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public Task ListRightPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.RPOP, key, count); + var msg = Message.Create(Database, flags, RedisCommand.RPOP, key, count, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -1393,20 +1397,20 @@ public Task ListRightPopAsync(RedisKey[] keys, long count, Comman public RedisValue ListRightPopLeftPush(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.RPOPLPUSH, source, destination); + var msg = Message.Create(Database, flags, RedisCommand.RPOPLPUSH, source, destination, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Task ListRightPopLeftPushAsync(RedisKey source, RedisKey destination, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.RPOPLPUSH, source, destination); + var msg = Message.Create(Database, flags, RedisCommand.RPOPLPUSH, source, destination, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public long ListRightPush(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) { WhenAlwaysOrExists(when); - var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX, key, value); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX, key, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -1415,21 +1419,21 @@ public long ListRightPush(RedisKey key, RedisValue[] values, When when = When.Al WhenAlwaysOrExists(when); if (values == null) throw new ArgumentNullException(nameof(values)); var command = when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX; - var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, command, key, values); + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key, GetEffectiveCancellationToken()) : Message.Create(Database, flags, command, key, values, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public long ListRightPush(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) { if (values == null) throw new ArgumentNullException(nameof(values)); - var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, RedisCommand.RPUSH, key, values); + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key, GetEffectiveCancellationToken()) : Message.Create(Database, flags, RedisCommand.RPUSH, key, values, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task ListRightPushAsync(RedisKey key, RedisValue value, When when = When.Always, CommandFlags flags = CommandFlags.None) { WhenAlwaysOrExists(when); - var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX, key, value); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX, key, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -1438,38 +1442,38 @@ public Task ListRightPushAsync(RedisKey key, RedisValue[] values, When whe WhenAlwaysOrExists(when); if (values == null) throw new ArgumentNullException(nameof(values)); var command = when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX; - var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, command, key, values); + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key, GetEffectiveCancellationToken()) : Message.Create(Database, flags, command, key, values, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public Task ListRightPushAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) { if (values == null) throw new ArgumentNullException(nameof(values)); - var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key) : Message.Create(Database, flags, RedisCommand.RPUSH, key, values); + var msg = values.Length == 0 ? Message.Create(Database, flags, RedisCommand.LLEN, key, GetEffectiveCancellationToken()) : Message.Create(Database, flags, RedisCommand.RPUSH, key, values, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public void ListSetByIndex(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LSET, key, index, value); + var msg = Message.Create(Database, flags, RedisCommand.LSET, key, index, value, GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task ListSetByIndexAsync(RedisKey key, long index, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LSET, key, index, value); + var msg = Message.Create(Database, flags, RedisCommand.LSET, key, index, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } public void ListTrim(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LTRIM, key, start, stop); + var msg = Message.Create(Database, flags, RedisCommand.LTRIM, key, start, stop, GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task ListTrimAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LTRIM, key, start, stop); + var msg = Message.Create(Database, flags, RedisCommand.LTRIM, key, start, stop, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } @@ -1538,51 +1542,51 @@ public Task LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry, public string? StringLongestCommonSubsequence(RedisKey key1, RedisKey key2, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2); + var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.String); } public Task StringLongestCommonSubsequenceAsync(RedisKey key1, RedisKey key2, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2); + var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.String); } public long StringLongestCommonSubsequenceLength(RedisKey key1, RedisKey key2, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.LEN); + var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.LEN, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task StringLongestCommonSubsequenceLengthAsync(RedisKey key1, RedisKey key2, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.LEN); + var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.LEN, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public LCSMatchResult StringLongestCommonSubsequenceWithMatches(RedisKey key1, RedisKey key2, long minSubMatchLength = 0, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.IDX, RedisLiterals.MINMATCHLEN, minSubMatchLength, RedisLiterals.WITHMATCHLEN); + var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.IDX, RedisLiterals.MINMATCHLEN, minSubMatchLength, RedisLiterals.WITHMATCHLEN, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.LCSMatchResult); } public Task StringLongestCommonSubsequenceWithMatchesAsync(RedisKey key1, RedisKey key2, long minSubMatchLength = 0, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.IDX, RedisLiterals.MINMATCHLEN, minSubMatchLength, RedisLiterals.WITHMATCHLEN); + var msg = Message.Create(Database, flags, RedisCommand.LCS, key1, key2, RedisLiterals.IDX, RedisLiterals.MINMATCHLEN, minSubMatchLength, RedisLiterals.WITHMATCHLEN, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.LCSMatchResult); } public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) { if (channel.IsNullOrEmpty) throw new ArgumentNullException(nameof(channel)); - var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message); + var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) { if (channel.IsNullOrEmpty) throw new ArgumentNullException(nameof(channel)); - var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message); + var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -1591,7 +1595,7 @@ public RedisResult Execute(string command, params object[] args) public RedisResult Execute(string command, ICollection args, CommandFlags flags = CommandFlags.None) { - var msg = new ExecuteMessage(multiplexer?.CommandMap, Database, flags, command, args); + var msg = new ExecuteMessage(Multiplexer?.CommandMap, Database, flags, command, args, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.ScriptResult)!; } @@ -1600,14 +1604,14 @@ public Task ExecuteAsync(string command, params object[] args) public Task ExecuteAsync(string command, ICollection? args, CommandFlags flags = CommandFlags.None) { - var msg = new ExecuteMessage(multiplexer?.CommandMap, Database, flags, command, args); + var msg = new ExecuteMessage(Multiplexer?.CommandMap, Database, flags, command, args, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } public RedisResult ScriptEvaluate(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL; - var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); + var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values, GetEffectiveCancellationToken()); try { return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); @@ -1621,7 +1625,7 @@ public RedisResult ScriptEvaluate(string script, RedisKey[]? keys = null, RedisV public RedisResult ScriptEvaluate(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { - var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA, hash, keys, values); + var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA, hash, keys, values, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } @@ -1638,7 +1642,7 @@ public RedisResult ScriptEvaluate(LoadedLuaScript script, object? parameters = n public async Task ScriptEvaluateAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA : RedisCommand.EVAL; - var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); + var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values, GetEffectiveCancellationToken()); try { @@ -1653,7 +1657,7 @@ public async Task ScriptEvaluateAsync(string script, RedisKey[]? ke public Task ScriptEvaluateAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { - var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA, hash, keys, values); + var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA, hash, keys, values, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } @@ -1670,7 +1674,7 @@ public Task ScriptEvaluateAsync(LoadedLuaScript script, object? par public RedisResult ScriptEvaluateReadOnly(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA_RO : RedisCommand.EVAL_RO; - var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); + var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values, GetEffectiveCancellationToken()); try { return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); @@ -1684,118 +1688,118 @@ public RedisResult ScriptEvaluateReadOnly(string script, RedisKey[]? keys = null public RedisResult ScriptEvaluateReadOnly(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { - var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA_RO, hash, keys, values); + var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA_RO, hash, keys, values, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } public Task ScriptEvaluateReadOnlyAsync(string script, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { var command = ResultProcessor.ScriptLoadProcessor.IsSHA1(script) ? RedisCommand.EVALSHA_RO : RedisCommand.EVAL_RO; - var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values); + var msg = new ScriptEvalMessage(Database, flags, command, script, keys, values, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } public Task ScriptEvaluateReadOnlyAsync(byte[] hash, RedisKey[]? keys = null, RedisValue[]? values = null, CommandFlags flags = CommandFlags.None) { - var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA_RO, hash, keys, values); + var msg = new ScriptEvalMessage(Database, flags, RedisCommand.EVALSHA_RO, hash, keys, values, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SADD, key, value); + var msg = Message.Create(Database, flags, RedisCommand.SADD, key, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public long SetAdd(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) { if (values.Length == 0) return 0; - var msg = Message.Create(Database, flags, RedisCommand.SADD, key, values); + var msg = Message.Create(Database, flags, RedisCommand.SADD, key, values, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task SetAddAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SADD, key, value); + var msg = Message.Create(Database, flags, RedisCommand.SADD, key, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public Task SetAddAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) { if (values.Length == 0) return Task.FromResult(0); - var msg = Message.Create(Database, flags, RedisCommand.SADD, key, values); + var msg = Message.Create(Database, flags, RedisCommand.SADD, key, values, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public RedisValue[] SetCombine(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), first, second); + var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), first, second, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public RedisValue[] SetCombine(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), keys); + var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), keys, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, first, second); + var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, first, second, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public long SetCombineAndStore(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, keys); + var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, keys, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, first, second); + var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, first, second, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public Task SetCombineAndStoreAsync(SetOperation operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, keys); + var msg = Message.Create(Database, flags, SetOperationCommand(operation, true), destination, keys, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public Task SetCombineAsync(SetOperation operation, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), first, second); + var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), first, second, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public Task SetCombineAsync(SetOperation operation, RedisKey[] keys, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), keys); + var msg = Message.Create(Database, flags, SetOperationCommand(operation, false), keys, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public bool SetContains(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SISMEMBER, key, value); + var msg = Message.Create(Database, flags, RedisCommand.SISMEMBER, key, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public Task SetContainsAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SISMEMBER, key, value); + var msg = Message.Create(Database, flags, RedisCommand.SISMEMBER, key, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SMISMEMBER, key, values); + var msg = Message.Create(Database, flags, RedisCommand.SMISMEMBER, key, values, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.BooleanArray, defaultValue: Array.Empty()); } public Task SetContainsAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SMISMEMBER, key, values); + var msg = Message.Create(Database, flags, RedisCommand.SMISMEMBER, key, values, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.BooleanArray, defaultValue: Array.Empty()); } @@ -1813,49 +1817,49 @@ public Task SetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, Co public long SetLength(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SCARD, key); + var msg = Message.Create(Database, flags, RedisCommand.SCARD, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task SetLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SCARD, key); + var msg = Message.Create(Database, flags, RedisCommand.SCARD, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public RedisValue[] SetMembers(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SMEMBERS, key); + var msg = Message.Create(Database, flags, RedisCommand.SMEMBERS, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public Task SetMembersAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SMEMBERS, key); + var msg = Message.Create(Database, flags, RedisCommand.SMEMBERS, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public bool SetMove(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SMOVE, source, destination, value); + var msg = Message.Create(Database, flags, RedisCommand.SMOVE, source, destination, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public Task SetMoveAsync(RedisKey source, RedisKey destination, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SMOVE, source, destination, value); + var msg = Message.Create(Database, flags, RedisCommand.SMOVE, source, destination, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public RedisValue SetPop(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SPOP, key); + var msg = Message.Create(Database, flags, RedisCommand.SPOP, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Task SetPopAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SPOP, key); + var msg = Message.Create(Database, flags, RedisCommand.SPOP, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -1863,47 +1867,47 @@ public RedisValue[] SetPop(RedisKey key, long count, CommandFlags flags = Comman { if (count == 0) return Array.Empty(); var msg = count == 1 - ? Message.Create(Database, flags, RedisCommand.SPOP, key) - : Message.Create(Database, flags, RedisCommand.SPOP, key, count); + ? Message.Create(Database, flags, RedisCommand.SPOP, key, GetEffectiveCancellationToken()) + : Message.Create(Database, flags, RedisCommand.SPOP, key, count, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public Task SetPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - if (count == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + if (count == 0) return CompletedTask.FromDefault(Array.Empty(), AsyncState); var msg = count == 1 - ? Message.Create(Database, flags, RedisCommand.SPOP, key) - : Message.Create(Database, flags, RedisCommand.SPOP, key, count); + ? Message.Create(Database, flags, RedisCommand.SPOP, key, GetEffectiveCancellationToken()) + : Message.Create(Database, flags, RedisCommand.SPOP, key, count, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public RedisValue SetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key); + var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Task SetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key); + var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public RedisValue[] SetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key, count); + var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key, count, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public Task SetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key, count); + var msg = Message.Create(Database, flags, RedisCommand.SRANDMEMBER, key, count, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public bool SetRemove(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SREM, key, value); + var msg = Message.Create(Database, flags, RedisCommand.SREM, key, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } @@ -1911,21 +1915,21 @@ public long SetRemove(RedisKey key, RedisValue[] values, CommandFlags flags = Co { if (values == null) throw new ArgumentNullException(nameof(values)); if (values.Length == 0) return 0; - var msg = Message.Create(Database, flags, RedisCommand.SREM, key, values); + var msg = Message.Create(Database, flags, RedisCommand.SREM, key, values, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task SetRemoveAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SREM, key, value); + var msg = Message.Create(Database, flags, RedisCommand.SREM, key, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public Task SetRemoveAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) { if (values == null) throw new ArgumentNullException(nameof(values)); - if (values.Length == 0) return CompletedTask.FromResult(0, asyncState); - var msg = Message.Create(Database, flags, RedisCommand.SREM, key, values); + if (values.Length == 0) return CompletedTask.FromResult(0, AsyncState); + var msg = Message.Create(Database, flags, RedisCommand.SREM, key, values, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -2104,13 +2108,13 @@ public Task SortedSetDecrementAsync(RedisKey key, RedisValue member, dou public double SortedSetIncrement(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZINCRBY, key, value, member); + var msg = Message.Create(Database, flags, RedisCommand.ZINCRBY, key, value, member, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Double); } public Task SortedSetIncrementAsync(RedisKey key, RedisValue member, double value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZINCRBY, key, value, member); + var msg = Message.Create(Database, flags, RedisCommand.ZINCRBY, key, value, member, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Double); } @@ -2140,43 +2144,43 @@ public Task SortedSetLengthAsync(RedisKey key, double min = double.Negativ public RedisValue SortedSetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key); + var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public RedisValue[] SortedSetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, count); + var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, count, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public SortedSetEntry[] SortedSetRandomMembersWithScores(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, count, RedisLiterals.WITHSCORES); + var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, count, RedisLiterals.WITHSCORES, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); } public Task SortedSetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key); + var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public Task SortedSetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, count); + var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, count, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public Task SortedSetRandomMembersWithScoresAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, count, RedisLiterals.WITHSCORES); + var msg = Message.Create(Database, flags, RedisCommand.ZRANDMEMBER, key, count, RedisLiterals.WITHSCORES, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); } public RedisValue[] SortedSetRangeByRank(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop); + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -2198,7 +2202,7 @@ public long SortedSetRangeAndStore( public Task SortedSetRangeByRankAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop); + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -2220,13 +2224,13 @@ public Task SortedSetRangeAndStoreAsync( public SortedSetEntry[] SortedSetRangeByRankWithScores(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop, RedisLiterals.WITHSCORES); + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop, RedisLiterals.WITHSCORES, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); } public Task SortedSetRangeByRankWithScoresAsync(RedisKey key, long start = 0, long stop = -1, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop, RedisLiterals.WITHSCORES); + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANGE : RedisCommand.ZRANGE, key, start, stop, RedisLiterals.WITHSCORES, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); } @@ -2256,49 +2260,49 @@ public Task SortedSetRangeByScoreWithScoresAsync(RedisKey key, public long? SortedSetRank(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANK : RedisCommand.ZRANK, key, member); + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANK : RedisCommand.ZRANK, key, member, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.NullableInt64); } public Task SortedSetRankAsync(RedisKey key, RedisValue member, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANK : RedisCommand.ZRANK, key, member); + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZREVRANK : RedisCommand.ZRANK, key, member, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.NullableInt64); } public bool SortedSetRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, member); + var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, member, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public long SortedSetRemove(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, members); + var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, members, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task SortedSetRemoveAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, member); + var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, member, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public Task SortedSetRemoveAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, members); + var msg = Message.Create(Database, flags, RedisCommand.ZREM, key, members, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public long SortedSetRemoveRangeByRank(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZREMRANGEBYRANK, key, start, stop); + var msg = Message.Create(Database, flags, RedisCommand.ZREMRANGEBYRANK, key, start, stop, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task SortedSetRemoveRangeByRankAsync(RedisKey key, long start, long stop, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZREMRANGEBYRANK, key, start, stop); + var msg = Message.Create(Database, flags, RedisCommand.ZREMRANGEBYRANK, key, start, stop, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -2335,37 +2339,37 @@ private CursorEnumerable SortedSetScanAsync(RedisKey key, RedisV public double? SortedSetScore(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZSCORE, key, member); + var msg = Message.Create(Database, flags, RedisCommand.ZSCORE, key, member, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.NullableDouble); } public double?[] SortedSetScores(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZMSCORE, key, members); + var msg = Message.Create(Database, flags, RedisCommand.ZMSCORE, key, members, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.NullableDoubleArray, defaultValue: Array.Empty()); } public Task SortedSetScoreAsync(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZSCORE, key, member); + var msg = Message.Create(Database, flags, RedisCommand.ZSCORE, key, member, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.NullableDouble); } public Task SortedSetScoresAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.ZMSCORE, key, members); + var msg = Message.Create(Database, flags, RedisCommand.ZMSCORE, key, members, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.NullableDoubleArray, defaultValue: Array.Empty()); } public SortedSetEntry? SortedSetPop(RedisKey key, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key); + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.SortedSetEntry); } public Task SortedSetPopAsync(RedisKey key, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key); + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.SortedSetEntry); } @@ -2373,8 +2377,8 @@ public SortedSetEntry[] SortedSetPop(RedisKey key, long count, Order order = Ord { if (count == 0) return Array.Empty(); var msg = count == 1 - ? Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key) - : Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key, count); + ? Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key, GetEffectiveCancellationToken()) + : Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key, count, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); } @@ -2386,10 +2390,10 @@ public SortedSetPopResult SortedSetPop(RedisKey[] keys, long count, Order order public Task SortedSetPopAsync(RedisKey key, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) { - if (count == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); + if (count == 0) return CompletedTask.FromDefault(Array.Empty(), AsyncState); var msg = count == 1 - ? Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key) - : Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key, count); + ? Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key, GetEffectiveCancellationToken()) + : Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key, count, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); } @@ -2567,7 +2571,8 @@ public bool StreamConsumerGroupSetPosition(RedisKey key, RedisValue groupName, R key.AsRedisValue(), groupName, StreamPosition.Resolve(position, RedisCommand.XGROUP), - }); + }, + GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } @@ -2584,7 +2589,8 @@ public Task StreamConsumerGroupSetPositionAsync(RedisKey key, RedisValue g key.AsRedisValue(), groupName, StreamPosition.Resolve(position, RedisCommand.XGROUP), - }); + }, + GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -2644,7 +2650,8 @@ public StreamConsumerInfo[] StreamConsumerInfo(RedisKey key, RedisValue groupNam StreamConstants.Consumers, key.AsRedisValue(), groupName, - }); + }, + GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.StreamConsumerInfo, defaultValue: Array.Empty()); } @@ -2660,44 +2667,45 @@ public Task StreamConsumerInfoAsync(RedisKey key, RedisVal StreamConstants.Consumers, key.AsRedisValue(), groupName, - }); + }, + GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.StreamConsumerInfo, defaultValue: Array.Empty()); } public StreamGroupInfo[] StreamGroupInfo(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Groups, key); + var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Groups, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.StreamGroupInfo, defaultValue: Array.Empty()); } public Task StreamGroupInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Groups, key); + var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Groups, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.StreamGroupInfo, defaultValue: Array.Empty()); } public StreamInfo StreamInfo(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Stream, key); + var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Stream, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.StreamInfo); } public Task StreamInfoAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Stream, key); + var msg = Message.Create(Database, flags, RedisCommand.XINFO, StreamConstants.Stream, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.StreamInfo); } public long StreamLength(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.XLEN, key); + var msg = Message.Create(Database, flags, RedisCommand.XLEN, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task StreamLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.XLEN, key); + var msg = Message.Create(Database, flags, RedisCommand.XLEN, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -2708,7 +2716,8 @@ public long StreamDelete(RedisKey key, RedisValue[] messageIds, CommandFlags fla flags, RedisCommand.XDEL, key, - messageIds); + messageIds, + GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -2720,7 +2729,8 @@ public Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, Comma flags, RedisCommand.XDEL, key, - messageIds); + messageIds, + GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -2737,7 +2747,8 @@ public long StreamDeleteConsumer(RedisKey key, RedisValue groupName, RedisValue key.AsRedisValue(), groupName, consumerName, - }); + }, + GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -2754,7 +2765,8 @@ public Task StreamDeleteConsumerAsync(RedisKey key, RedisValue groupName, key.AsRedisValue(), groupName, consumerName, - }); + }, + GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -2770,7 +2782,8 @@ public bool StreamDeleteConsumerGroup(RedisKey key, RedisValue groupName, Comman StreamConstants.Destroy, key.AsRedisValue(), groupName, - }); + }, + GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } @@ -2786,20 +2799,21 @@ public Task StreamDeleteConsumerGroupAsync(RedisKey key, RedisValue groupN StreamConstants.Destroy, key.AsRedisValue(), groupName, - }); + }, + GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public StreamPendingInfo StreamPending(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.XPENDING, key, groupName); + var msg = Message.Create(Database, flags, RedisCommand.XPENDING, key, groupName, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.StreamPendingInfo); } public Task StreamPendingAsync(RedisKey key, RedisValue groupName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.XPENDING, key, groupName); + var msg = Message.Create(Database, flags, RedisCommand.XPENDING, key, groupName, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.StreamPendingInfo); } @@ -2881,13 +2895,13 @@ public Task StreamReadAsync(RedisKey key, RedisValue position, in public RedisStream[] StreamRead(StreamPosition[] streamPositions, int? countPerStream = null, CommandFlags flags = CommandFlags.None) { - var msg = GetMultiStreamReadMessage(streamPositions, countPerStream, flags); + var msg = GetMultiStreamReadMessage(streamPositions, countPerStream, flags, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.MultiStream, defaultValue: Array.Empty()); } public Task StreamReadAsync(StreamPosition[] streamPositions, int? countPerStream = null, CommandFlags flags = CommandFlags.None) { - var msg = GetMultiStreamReadMessage(streamPositions, countPerStream, flags); + var msg = GetMultiStreamReadMessage(streamPositions, countPerStream, flags, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.MultiStream, defaultValue: Array.Empty()); } @@ -3009,13 +3023,13 @@ public Task StreamTrimAsync(RedisKey key, int maxLength, bool useApproxima public long StringAppend(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.APPEND, key, value); + var msg = Message.Create(Database, flags, RedisCommand.APPEND, key, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task StringAppendAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.APPEND, key, value); + var msg = Message.Create(Database, flags, RedisCommand.APPEND, key, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -3024,10 +3038,11 @@ public long StringBitCount(RedisKey key, long start, long end, CommandFlags flag public long StringBitCount(RedisKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) { + var cancellationToken = GetEffectiveCancellationToken(); var msg = indexType switch { - StringIndexType.Byte => Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end), - _ => Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end, indexType.ToLiteral()), + StringIndexType.Byte => Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end, cancellationToken), + _ => Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end, indexType.ToLiteral(), cancellationToken), }; return ExecuteSync(msg, ResultProcessor.Int64); } @@ -3037,35 +3052,36 @@ public Task StringBitCountAsync(RedisKey key, long start, long end, Comman public Task StringBitCountAsync(RedisKey key, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) { + var cancellationToken = GetEffectiveCancellationToken(); var msg = indexType switch { - StringIndexType.Byte => Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end), - _ => Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end, indexType.ToLiteral()), + StringIndexType.Byte => Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end, cancellationToken), + _ => Message.Create(Database, flags, RedisCommand.BITCOUNT, key, start, end, indexType.ToLiteral(), cancellationToken), }; return ExecuteAsync(msg, ResultProcessor.Int64); } public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) { - var msg = GetStringBitOperationMessage(operation, destination, first, second, flags); + var msg = GetStringBitOperationMessage(operation, destination, first, second, flags, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public long StringBitOperation(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) { - var msg = GetStringBitOperationMessage(operation, destination, keys, flags); + var msg = GetStringBitOperationMessage(operation, destination, keys, flags, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) { - var msg = GetStringBitOperationMessage(operation, destination, first, second, flags); + var msg = GetStringBitOperationMessage(operation, destination, first, second, flags, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public Task StringBitOperationAsync(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags = CommandFlags.None) { - var msg = GetStringBitOperationMessage(operation, destination, keys, flags); + var msg = GetStringBitOperationMessage(operation, destination, keys, flags, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -3074,10 +3090,11 @@ public long StringBitPosition(RedisKey key, bool bit, long start, long end, Comm public long StringBitPosition(RedisKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) { + var cancellationToken = GetEffectiveCancellationToken(); var msg = indexType switch { - StringIndexType.Byte => Message.Create(Database, flags, RedisCommand.BITPOS, key, bit, start, end), - _ => Message.Create(Database, flags, RedisCommand.BITPOS, key, bit, start, end, indexType.ToLiteral()), + StringIndexType.Byte => Message.Create(Database, flags, RedisCommand.BITPOS, key, bit, start, end, cancellationToken), + _ => Message.Create(Database, flags, RedisCommand.BITPOS, key, bit, start, end, indexType.ToLiteral(), cancellationToken), }; return ExecuteSync(msg, ResultProcessor.Int64); } @@ -3087,10 +3104,11 @@ public Task StringBitPositionAsync(RedisKey key, bool bit, long start, lon public Task StringBitPositionAsync(RedisKey key, bool bit, long start = 0, long end = -1, StringIndexType indexType = StringIndexType.Byte, CommandFlags flags = CommandFlags.None) { + var cancellationToken = GetEffectiveCancellationToken(); var msg = indexType switch { - StringIndexType.Byte => Message.Create(Database, flags, RedisCommand.BITPOS, key, bit, start, end), - _ => Message.Create(Database, flags, RedisCommand.BITPOS, key, bit, start, end, indexType.ToLiteral()), + StringIndexType.Byte => Message.Create(Database, flags, RedisCommand.BITPOS, key, bit, start, end, cancellationToken), + _ => Message.Create(Database, flags, RedisCommand.BITPOS, key, bit, start, end, indexType.ToLiteral(), cancellationToken), }; return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -3117,7 +3135,7 @@ public Task StringDecrementAsync(RedisKey key, double value, CommandFlag public RedisValue StringGet(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GET, key); + var msg = Message.Create(Database, flags, RedisCommand.GET, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } @@ -3149,81 +3167,81 @@ public RedisValue[] StringGet(RedisKey[] keys, CommandFlags flags = CommandFlags { if (keys == null) throw new ArgumentNullException(nameof(keys)); if (keys.Length == 0) return Array.Empty(); - var msg = Message.Create(Database, flags, RedisCommand.MGET, keys); + var msg = Message.Create(Database, flags, RedisCommand.MGET, keys, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public Lease? StringGetLease(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GET, key); + var msg = Message.Create(Database, flags, RedisCommand.GET, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Lease); } public Task StringGetAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GET, key); + var msg = Message.Create(Database, flags, RedisCommand.GET, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public Task?> StringGetLeaseAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GET, key); + var msg = Message.Create(Database, flags, RedisCommand.GET, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Lease); } public Task StringGetAsync(RedisKey[] keys, CommandFlags flags = CommandFlags.None) { if (keys == null) throw new ArgumentNullException(nameof(keys)); - if (keys.Length == 0) return CompletedTask.FromDefault(Array.Empty(), asyncState); - var msg = Message.Create(Database, flags, RedisCommand.MGET, keys); + if (keys.Length == 0) return CompletedTask.FromDefault(Array.Empty(), AsyncState); + var msg = Message.Create(Database, flags, RedisCommand.MGET, keys, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } public bool StringGetBit(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GETBIT, key, offset); + var msg = Message.Create(Database, flags, RedisCommand.GETBIT, key, offset, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public Task StringGetBitAsync(RedisKey key, long offset, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GETBIT, key, offset); + var msg = Message.Create(Database, flags, RedisCommand.GETBIT, key, offset, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public RedisValue StringGetRange(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GETRANGE, key, start, end); + var msg = Message.Create(Database, flags, RedisCommand.GETRANGE, key, start, end, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Task StringGetRangeAsync(RedisKey key, long start, long end, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GETRANGE, key, start, end); + var msg = Message.Create(Database, flags, RedisCommand.GETRANGE, key, start, end, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public RedisValue StringGetSet(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GETSET, key, value); + var msg = Message.Create(Database, flags, RedisCommand.GETSET, key, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Task StringGetSetAsync(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GETSET, key, value); + var msg = Message.Create(Database, flags, RedisCommand.GETSET, key, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public RedisValue StringGetDelete(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GETDEL, key); + var msg = Message.Create(Database, flags, RedisCommand.GETDEL, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Task StringGetDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.GETDEL, key); + var msg = Message.Create(Database, flags, RedisCommand.GETDEL, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -3248,7 +3266,7 @@ public long StringIncrement(RedisKey key, long value = 1, CommandFlags flags = C public double StringIncrement(RedisKey key, double value, CommandFlags flags = CommandFlags.None) { var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 - ? null : Message.Create(Database, flags, RedisCommand.INCRBYFLOAT, key, value); + ? null : Message.Create(Database, flags, RedisCommand.INCRBYFLOAT, key, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Double); } @@ -3261,19 +3279,19 @@ public Task StringIncrementAsync(RedisKey key, long value = 1, CommandFlag public Task StringIncrementAsync(RedisKey key, double value, CommandFlags flags = CommandFlags.None) { var msg = value == 0 && (flags & CommandFlags.FireAndForget) != 0 - ? null : Message.Create(Database, flags, RedisCommand.INCRBYFLOAT, key, value); + ? null : Message.Create(Database, flags, RedisCommand.INCRBYFLOAT, key, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Double); } public long StringLength(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.STRLEN, key); + var msg = Message.Create(Database, flags, RedisCommand.STRLEN, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task StringLengthAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.STRLEN, key); + var msg = Message.Create(Database, flags, RedisCommand.STRLEN, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -3333,25 +3351,25 @@ public Task StringSetAndGetAsync(RedisKey key, RedisValue value, Tim public bool StringSetBit(RedisKey key, long offset, bool bit, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SETBIT, key, offset, bit); + var msg = Message.Create(Database, flags, RedisCommand.SETBIT, key, offset, bit, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public Task StringSetBitAsync(RedisKey key, long offset, bool value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SETBIT, key, offset, value); + var msg = Message.Create(Database, flags, RedisCommand.SETBIT, key, offset, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public RedisValue StringSetRange(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SETRANGE, key, offset, value); + var msg = Message.Create(Database, flags, RedisCommand.SETRANGE, key, offset, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public bool KeyTouch(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.TOUCH, key); + var msg = Message.Create(Database, flags, RedisCommand.TOUCH, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.DemandZeroOrOne); } @@ -3360,7 +3378,7 @@ public long KeyTouch(RedisKey[] keys, CommandFlags flags = CommandFlags.None) if (keys == null) throw new ArgumentNullException(nameof(keys)); if (keys.Length > 0) { - var msg = keys.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.TOUCH, keys); + var msg = keys.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.TOUCH, keys, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } return 0; @@ -3368,7 +3386,7 @@ public long KeyTouch(RedisKey[] keys, CommandFlags flags = CommandFlags.None) public Task KeyTouchAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.TOUCH, key); + var msg = Message.Create(Database, flags, RedisCommand.TOUCH, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandZeroOrOne); } @@ -3377,7 +3395,7 @@ public Task KeyTouchAsync(RedisKey[] keys, CommandFlags flags = CommandFla if (keys == null) throw new ArgumentNullException(nameof(keys)); if (keys.Length > 0) { - var msg = keys.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.TOUCH, keys); + var msg = keys.Length == 0 ? null : Message.Create(Database, flags, RedisCommand.TOUCH, keys, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } return CompletedTask.Default(0); @@ -3385,25 +3403,28 @@ public Task KeyTouchAsync(RedisKey[] keys, CommandFlags flags = CommandFla public Task StringSetRangeAsync(RedisKey key, long offset, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.SETRANGE, key, offset, value); + var msg = Message.Create(Database, flags, RedisCommand.SETRANGE, key, offset, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } private long GetMillisecondsUntil(DateTime when) => when.Kind switch { - DateTimeKind.Local or DateTimeKind.Utc => (when.ToUniversalTime() - RedisBase.UnixEpoch).Ticks / TimeSpan.TicksPerMillisecond, + DateTimeKind.Local or DateTimeKind.Utc => (when.ToUniversalTime() - UnixEpoch).Ticks / TimeSpan.TicksPerMillisecond, _ => throw new ArgumentException("Expiry time must be either Utc or Local", nameof(when)), }; - private Message GetCopyMessage(in RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase, bool replace, CommandFlags flags) => - destinationDatabase switch + private Message GetCopyMessage(in RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase, bool replace, CommandFlags flags) + { + var cancellationToken = GetEffectiveCancellationToken(); + return destinationDatabase switch { < -1 => throw new ArgumentOutOfRangeException(nameof(destinationDatabase)), - -1 when replace => Message.Create(Database, flags, RedisCommand.COPY, sourceKey, destinationKey, RedisLiterals.REPLACE), - -1 => Message.Create(Database, flags, RedisCommand.COPY, sourceKey, destinationKey), - _ when replace => Message.Create(Database, flags, RedisCommand.COPY, sourceKey, destinationKey, RedisLiterals.DB, destinationDatabase, RedisLiterals.REPLACE), - _ => Message.Create(Database, flags, RedisCommand.COPY, sourceKey, destinationKey, RedisLiterals.DB, destinationDatabase), + -1 when replace => Message.Create(Database, flags, RedisCommand.COPY, sourceKey, destinationKey, RedisLiterals.REPLACE, cancellationToken), + -1 => Message.Create(Database, flags, RedisCommand.COPY, sourceKey, destinationKey, cancellationToken), + _ when replace => Message.Create(Database, flags, RedisCommand.COPY, sourceKey, destinationKey, RedisLiterals.DB, destinationDatabase, RedisLiterals.REPLACE, cancellationToken), + _ => Message.Create(Database, flags, RedisCommand.COPY, sourceKey, destinationKey, RedisLiterals.DB, destinationDatabase, cancellationToken), }; + } private Message GetExpiryMessage(in RedisKey key, CommandFlags flags, TimeSpan? expiry, ExpireWhen when, out ServerEndPoint? server) { @@ -3412,7 +3433,7 @@ private Message GetExpiryMessage(in RedisKey key, CommandFlags flags, TimeSpan? server = null; return when switch { - ExpireWhen.Always => Message.Create(Database, flags, RedisCommand.PERSIST, key), + ExpireWhen.Always => Message.Create(Database, flags, RedisCommand.PERSIST, key, GetEffectiveCancellationToken()), _ => throw new ArgumentException("PERSIST cannot be used with when."), }; } @@ -3428,7 +3449,7 @@ private Message GetExpiryMessage(in RedisKey key, CommandFlags flags, DateTime? server = null; return when switch { - ExpireWhen.Always => Message.Create(Database, flags, RedisCommand.PERSIST, key), + ExpireWhen.Always => Message.Create(Database, flags, RedisCommand.PERSIST, key, GetEffectiveCancellationToken()), _ => throw new ArgumentException("PERSIST cannot be used with when."), }; } @@ -3450,12 +3471,12 @@ private Message GetExpiryMessage( if ((milliseconds % 1000) != 0) { var features = GetFeatures(key, flags, RedisCommand.PEXPIRE, out server); - if (server is not null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(millisecondsCommand)) + if (server is not null && features.MillisecondExpiry && Multiplexer.CommandMap.IsAvailable(millisecondsCommand)) { return when switch { - ExpireWhen.Always => Message.Create(Database, flags, millisecondsCommand, key, milliseconds), - _ => Message.Create(Database, flags, millisecondsCommand, key, milliseconds, when.ToLiteral()), + ExpireWhen.Always => Message.Create(Database, flags, millisecondsCommand, key, milliseconds, GetEffectiveCancellationToken()), + _ => Message.Create(Database, flags, millisecondsCommand, key, milliseconds, when.ToLiteral(), GetEffectiveCancellationToken()), }; } server = null; @@ -3464,8 +3485,8 @@ private Message GetExpiryMessage( long seconds = milliseconds / 1000; return when switch { - ExpireWhen.Always => Message.Create(Database, flags, secondsCommand, key, seconds), - _ => Message.Create(Database, flags, secondsCommand, key, seconds, when.ToLiteral()), + ExpireWhen.Always => Message.Create(Database, flags, secondsCommand, key, seconds, GetEffectiveCancellationToken()), + _ => Message.Create(Database, flags, secondsCommand, key, seconds, when.ToLiteral(), GetEffectiveCancellationToken()), }; } @@ -3476,7 +3497,7 @@ private Message GetListMultiPopMessage(RedisKey[] keys, RedisValue side, long co throw new ArgumentOutOfRangeException(nameof(keys), "keys must have a size of at least 1"); } - var slot = multiplexer.ServerSelectionStrategy.HashSlot(keys[0]); + var slot = Multiplexer.ServerSelectionStrategy.HashSlot(keys[0]); var args = new RedisValue[2 + keys.Length + (count == 1 ? 0 : 2)]; var i = 0; @@ -3494,7 +3515,7 @@ private Message GetListMultiPopMessage(RedisKey[] keys, RedisValue side, long co args[i++] = count; } - return Message.CreateInSlot(Database, slot, flags, RedisCommand.LMPOP, args); + return Message.CreateInSlot(Database, slot, flags, RedisCommand.LMPOP, args, GetEffectiveCancellationToken()); } private Message GetSortedSetMultiPopMessage(RedisKey[] keys, Order order, long count, CommandFlags flags) @@ -3504,7 +3525,7 @@ private Message GetSortedSetMultiPopMessage(RedisKey[] keys, Order order, long c throw new ArgumentOutOfRangeException(nameof(keys), "keys must have a size of at least 1"); } - var slot = multiplexer.ServerSelectionStrategy.HashSlot(keys[0]); + var slot = Multiplexer.ServerSelectionStrategy.HashSlot(keys[0]); var args = new RedisValue[2 + keys.Length + (count == 1 ? 0 : 2)]; var i = 0; @@ -3522,7 +3543,7 @@ private Message GetSortedSetMultiPopMessage(RedisKey[] keys, Order order, long c args[i++] = count; } - return Message.CreateInSlot(Database, slot, flags, RedisCommand.ZMPOP, args); + return Message.CreateInSlot(Database, slot, flags, RedisCommand.ZMPOP, args, GetEffectiveCancellationToken()); } private Message? GetHashSetMessage(RedisKey key, HashEntry[] hashFields, CommandFlags flags) @@ -3538,7 +3559,8 @@ private Message GetSortedSetMultiPopMessage(RedisKey[] keys, Order order, long c RedisCommand.HMSET, key, hashFields[0].name, - hashFields[0].value); + hashFields[0].value, + GetEffectiveCancellationToken()); case 2: return Message.Create( Database, @@ -3548,7 +3570,8 @@ private Message GetSortedSetMultiPopMessage(RedisKey[] keys, Order order, long c hashFields[0].name, hashFields[0].value, hashFields[1].name, - hashFields[1].value); + hashFields[1].value, + GetEffectiveCancellationToken()); default: var arr = new RedisValue[hashFields.Length * 2]; int offset = 0; @@ -3557,13 +3580,13 @@ private Message GetSortedSetMultiPopMessage(RedisKey[] keys, Order order, long c arr[offset++] = hashFields[i].name; arr[offset++] = hashFields[i].value; } - return Message.Create(Database, flags, RedisCommand.HMSET, key, arr); + return Message.Create(Database, flags, RedisCommand.HMSET, key, arr, GetEffectiveCancellationToken()); } } private ITransaction? GetLockExtendTransaction(RedisKey key, RedisValue value, TimeSpan expiry) { - var tran = CreateTransactionIfAvailable(asyncState); + var tran = CreateTransactionIfAvailable(AsyncState); if (tran is not null) { tran.AddCondition(Condition.StringEqual(key, value)); @@ -3574,7 +3597,7 @@ private Message GetSortedSetMultiPopMessage(RedisKey[] keys, Order order, long c private ITransaction? GetLockReleaseTransaction(RedisKey key, RedisValue value) { - var tran = CreateTransactionIfAvailable(asyncState); + var tran = CreateTransactionIfAvailable(AsyncState); if (tran is not null) { tran.AddCondition(Condition.StringEqual(key, value)); @@ -3606,7 +3629,8 @@ private Message GetMultiStreamReadGroupMessage(StreamPosition[] streamPositions, groupName, consumerName, countPerStream, - noAck); + noAck, + GetEffectiveCancellationToken()); private sealed class MultiStreamReadGroupCommandMessage : Message // XREADGROUP with multiple stream. Example: XREADGROUP GROUP groupName consumerName COUNT countPerStream STREAMS stream1 stream2 id1 id2 { @@ -3617,8 +3641,8 @@ private sealed class MultiStreamReadGroupCommandMessage : Message // XREADGROUP private readonly bool noAck; private readonly int argCount; - public MultiStreamReadGroupCommandMessage(int db, CommandFlags flags, StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream, bool noAck) - : base(db, flags, RedisCommand.XREADGROUP) + public MultiStreamReadGroupCommandMessage(int db, CommandFlags flags, StreamPosition[] streamPositions, RedisValue groupName, RedisValue consumerName, int? countPerStream, bool noAck, CancellationToken cancellationToken) + : base(db, flags, RedisCommand.XREADGROUP, cancellationToken) { if (streamPositions == null) throw new ArgumentNullException(nameof(streamPositions)); if (streamPositions.Length == 0) throw new ArgumentOutOfRangeException(nameof(streamPositions), "streamOffsetPairs must contain at least one item."); @@ -3689,8 +3713,8 @@ protected override void WriteImpl(PhysicalConnection physical) public override int ArgCount => argCount; } - private Message GetMultiStreamReadMessage(StreamPosition[] streamPositions, int? countPerStream, CommandFlags flags) => - new MultiStreamReadCommandMessage(Database, flags, streamPositions, countPerStream); + private Message GetMultiStreamReadMessage(StreamPosition[] streamPositions, int? countPerStream, CommandFlags flags, CancellationToken cancellationToken) => + new MultiStreamReadCommandMessage(Database, flags, streamPositions, countPerStream, cancellationToken); private sealed class MultiStreamReadCommandMessage : Message // XREAD with multiple stream. Example: XREAD COUNT 2 STREAMS mystream writers 0-0 0-0 { @@ -3698,8 +3722,8 @@ private sealed class MultiStreamReadCommandMessage : Message // XREAD with multi private readonly int? countPerStream; private readonly int argCount; - public MultiStreamReadCommandMessage(int db, CommandFlags flags, StreamPosition[] streamPositions, int? countPerStream) - : base(db, flags, RedisCommand.XREAD) + public MultiStreamReadCommandMessage(int db, CommandFlags flags, StreamPosition[] streamPositions, int? countPerStream, CancellationToken cancellationToken) + : base(db, flags, RedisCommand.XREAD, cancellationToken) { if (streamPositions == null) throw new ArgumentNullException(nameof(streamPositions)); if (streamPositions.Length == 0) throw new ArgumentOutOfRangeException(nameof(streamPositions), "streamOffsetPairs must contain at least one item."); @@ -3771,7 +3795,7 @@ private static RedisValue GetRange(double value, Exclude exclude, bool isStart) private Message GetRestoreMessage(RedisKey key, byte[] value, TimeSpan? expiry, CommandFlags flags) { long pttl = (expiry == null || expiry.Value == TimeSpan.MaxValue) ? 0 : (expiry.Value.Ticks / TimeSpan.TicksPerMillisecond); - return Message.Create(Database, flags, RedisCommand.RESTORE, key, pttl, value); + return Message.Create(Database, flags, RedisCommand.RESTORE, key, pttl, value, GetEffectiveCancellationToken()); } private Message GetSetIntersectionLengthMessage(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) @@ -3791,7 +3815,7 @@ private Message GetSetIntersectionLengthMessage(RedisKey[] keys, long limit = 0, values[i] = limit; } - return Message.Create(Database, flags, RedisCommand.SINTERCARD, values); + return Message.Create(Database, flags, RedisCommand.SINTERCARD, values, GetEffectiveCancellationToken()); } private Message GetSortedSetAddMessage(RedisKey key, RedisValue member, double score, SortedSetWhen when, bool change, CommandFlags flags) @@ -3820,7 +3844,7 @@ private Message GetSortedSetAddMessage(RedisKey key, RedisValue member, double s } arr[index++] = score; arr[index++] = member; - return Message.Create(Database, flags, RedisCommand.ZADD, key, arr); + return Message.Create(Database, flags, RedisCommand.ZADD, key, arr, GetEffectiveCancellationToken()); } private Message? GetSortedSetAddMessage(RedisKey key, SortedSetEntry[] values, SortedSetWhen when, bool change, CommandFlags flags) @@ -3860,7 +3884,7 @@ private Message GetSortedSetAddMessage(RedisKey key, RedisValue member, double s arr[index++] = values[i].score; arr[index++] = values[i].element; } - return Message.Create(Database, flags, RedisCommand.ZADD, key, arr); + return Message.Create(Database, flags, RedisCommand.ZADD, key, arr, GetEffectiveCancellationToken()); } } @@ -3882,10 +3906,10 @@ private Message GetSortMessage(RedisKey destination, RedisKey key, long skip, lo { return order switch { - Order.Ascending when sortType == SortType.Numeric => Message.Create(Database, flags, command, key), - Order.Ascending when sortType == SortType.Alphabetic => Message.Create(Database, flags, command, key, RedisLiterals.ALPHA), - Order.Descending when sortType == SortType.Numeric => Message.Create(Database, flags, command, key, RedisLiterals.DESC), - Order.Descending when sortType == SortType.Alphabetic => Message.Create(Database, flags, command, key, RedisLiterals.DESC, RedisLiterals.ALPHA), + Order.Ascending when sortType == SortType.Numeric => Message.Create(Database, flags, command, key, GetEffectiveCancellationToken()), + Order.Ascending when sortType == SortType.Alphabetic => Message.Create(Database, flags, command, key, RedisLiterals.ALPHA, GetEffectiveCancellationToken()), + Order.Descending when sortType == SortType.Numeric => Message.Create(Database, flags, command, key, RedisLiterals.DESC, GetEffectiveCancellationToken()), + Order.Descending when sortType == SortType.Alphabetic => Message.Create(Database, flags, command, key, RedisLiterals.DESC, RedisLiterals.ALPHA, GetEffectiveCancellationToken()), Order.Ascending or Order.Descending => throw new ArgumentOutOfRangeException(nameof(sortType)), _ => throw new ArgumentOutOfRangeException(nameof(order)), }; @@ -3933,16 +3957,17 @@ private Message GetSortMessage(RedisKey destination, RedisKey key, long skip, lo values.Add(item); } } - if (destination.IsNull) return Message.Create(Database, flags, command, key, values.ToArray()); + var cancellationToken = GetEffectiveCancellationToken(); + if (destination.IsNull) return Message.Create(Database, flags, command, key, values.ToArray(), cancellationToken); // Because we are using STORE, we need to push this to a primary if (Message.GetPrimaryReplicaFlags(flags) == CommandFlags.DemandReplica) { - throw ExceptionFactory.PrimaryOnly(multiplexer.RawConfig.IncludeDetailInExceptions, RedisCommand.SORT, null, null); + throw ExceptionFactory.PrimaryOnly(Multiplexer.RawConfig.IncludeDetailInExceptions, RedisCommand.SORT, null, null); } flags = Message.SetPrimaryReplicaFlags(flags, CommandFlags.DemandMaster); values.Add(RedisLiterals.STORE); - return Message.Create(Database, flags, RedisCommand.SORT, key, values.ToArray(), destination); + return Message.Create(Database, flags, RedisCommand.SORT, key, values.ToArray(), destination, cancellationToken); } private Message GetSortedSetCombineAndStoreCommandMessage(SetOperation operation, RedisKey destination, RedisKey[] keys, double[]? weights, Aggregate aggregate, CommandFlags flags) @@ -3969,7 +3994,7 @@ private Message GetSortedSetCombineAndStoreCommandMessage(SetOperation operation values = new RedisValue[argsLength]; AddWeightsAggregationAndScore(values, weights, aggregate); } - return new SortedSetCombineAndStoreCommandMessage(Database, flags, command, destination, keys, values); + return new SortedSetCombineAndStoreCommandMessage(Database, flags, command, destination, keys, values, GetEffectiveCancellationToken()); } private Message GetSortedSetCombineCommandMessage(SetOperation operation, RedisKey[] keys, double[]? weights, Aggregate aggregate, bool withScores, CommandFlags flags) @@ -3999,7 +4024,7 @@ private Message GetSortedSetCombineCommandMessage(SetOperation operation, RedisK values[i++] = key.AsRedisValue(); } AddWeightsAggregationAndScore(values.AsSpan(i), weights, aggregate, withScores: withScores); - return Message.Create(Database, flags, command, values ?? RedisValue.EmptyArray); + return Message.Create(Database, flags, command, values ?? RedisValue.EmptyArray, GetEffectiveCancellationToken()); } private void AddWeightsAggregationAndScore(Span values, double[]? weights, Aggregate aggregate, bool withScores = false) @@ -4037,11 +4062,11 @@ private void AddWeightsAggregationAndScore(Span values, double[]? we private Message GetSortedSetLengthMessage(RedisKey key, double min, double max, Exclude exclude, CommandFlags flags) { if (double.IsNegativeInfinity(min) && double.IsPositiveInfinity(max)) - return Message.Create(Database, flags, RedisCommand.ZCARD, key); + return Message.Create(Database, flags, RedisCommand.ZCARD, key, GetEffectiveCancellationToken()); var from = GetRange(min, exclude, true); var to = GetRange(max, exclude, false); - return Message.Create(Database, flags, RedisCommand.ZCOUNT, key, from, to); + return Message.Create(Database, flags, RedisCommand.ZCOUNT, key, from, to, GetEffectiveCancellationToken()); } private Message GetSortedSetIntersectionLengthMessage(RedisKey[] keys, long limit, CommandFlags flags) @@ -4060,7 +4085,7 @@ private Message GetSortedSetIntersectionLengthMessage(RedisKey[] keys, long limi values[i++] = RedisLiterals.LIMIT; values[i++] = limit; } - return Message.Create(Database, flags, RedisCommand.ZINTERCARD, values); + return Message.Create(Database, flags, RedisCommand.ZINTERCARD, values, GetEffectiveCancellationToken()); } private Message GetSortedSetRangeByScoreMessage(RedisKey key, double start, double stop, Exclude exclude, Order order, long skip, long take, CommandFlags flags, bool withScores) @@ -4086,13 +4111,13 @@ private Message GetSortedSetRangeByScoreMessage(RedisKey key, double start, doub RedisValue from = GetRange(start, exclude, true), to = GetRange(stop, exclude, false); if (withScores) { - return unlimited ? Message.Create(Database, flags, command, key, from, to, RedisLiterals.WITHSCORES) - : Message.Create(Database, flags, command, key, new[] { from, to, RedisLiterals.WITHSCORES, RedisLiterals.LIMIT, skip, take }); + return unlimited ? Message.Create(Database, flags, command, key, from, to, RedisLiterals.WITHSCORES, GetEffectiveCancellationToken()) + : Message.Create(Database, flags, command, key, new[] { from, to, RedisLiterals.WITHSCORES, RedisLiterals.LIMIT, skip, take }, GetEffectiveCancellationToken()); } else { - return unlimited ? Message.Create(Database, flags, command, key, from, to) - : Message.Create(Database, flags, command, key, new[] { from, to, RedisLiterals.LIMIT, skip, take }); + return unlimited ? Message.Create(Database, flags, command, key, from, to, GetEffectiveCancellationToken()) + : Message.Create(Database, flags, command, key, new[] { from, to, RedisLiterals.LIMIT, skip, take }, GetEffectiveCancellationToken()); } } @@ -4104,7 +4129,8 @@ private Message GetSortedSetRemoveRangeByScoreMessage(RedisKey key, double start RedisCommand.ZREMRANGEBYSCORE, key, GetRange(start, exclude, true), - GetRange(stop, exclude, false)); + GetRange(stop, exclude, false), + GetEffectiveCancellationToken()); } private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, RedisValue messageId, CommandFlags flags) @@ -4115,7 +4141,7 @@ private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, messageId, }; - return Message.Create(Database, flags, RedisCommand.XACK, key, values); + return Message.Create(Database, flags, RedisCommand.XACK, key, values, GetEffectiveCancellationToken()); } private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, RedisValue[] messageIds, CommandFlags flags) @@ -4134,7 +4160,7 @@ private Message GetStreamAcknowledgeMessage(RedisKey key, RedisValue groupName, values[offset++] = messageIds[i]; } - return Message.Create(Database, flags, RedisCommand.XACK, key, values); + return Message.Create(Database, flags, RedisCommand.XACK, key, values, GetEffectiveCancellationToken()); } private Message GetStreamAddMessage(RedisKey key, RedisValue messageId, int? maxLength, bool useApproximateMaxLength, NameValueEntry streamPair, CommandFlags flags) @@ -4169,7 +4195,7 @@ private Message GetStreamAddMessage(RedisKey key, RedisValue messageId, int? max values[offset++] = streamPair.Name; values[offset] = streamPair.Value; - return Message.Create(Database, flags, RedisCommand.XADD, key, values); + return Message.Create(Database, flags, RedisCommand.XADD, key, values, GetEffectiveCancellationToken()); } /// @@ -4217,7 +4243,7 @@ private Message GetStreamAddMessage(RedisKey key, RedisValue entryId, int? maxLe values[offset++] = streamPairs[i].Value; } - return Message.Create(Database, flags, RedisCommand.XADD, key, values); + return Message.Create(Database, flags, RedisCommand.XADD, key, values, GetEffectiveCancellationToken()); } private Message GetStreamAutoClaimMessage(RedisKey key, RedisValue consumerGroup, RedisValue assignToConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count, bool idsOnly, CommandFlags flags) @@ -4243,7 +4269,7 @@ private Message GetStreamAutoClaimMessage(RedisKey key, RedisValue consumerGroup values[offset++] = StreamConstants.JustId; } - return Message.Create(Database, flags, RedisCommand.XAUTOCLAIM, key, values); + return Message.Create(Database, flags, RedisCommand.XAUTOCLAIM, key, values, GetEffectiveCancellationToken()); } private Message GetStreamClaimMessage(RedisKey key, RedisValue consumerGroup, RedisValue assignToConsumer, long minIdleTimeInMs, RedisValue[] messageIds, bool returnJustIds, CommandFlags flags) @@ -4270,7 +4296,7 @@ private Message GetStreamClaimMessage(RedisKey key, RedisValue consumerGroup, Re values[offset] = StreamConstants.JustId; } - return Message.Create(Database, flags, RedisCommand.XCLAIM, key, values); + return Message.Create(Database, flags, RedisCommand.XCLAIM, key, values, GetEffectiveCancellationToken()); } private Message GetStreamCreateConsumerGroupMessage(RedisKey key, RedisValue groupName, RedisValue? position = null, bool createStream = true, CommandFlags flags = CommandFlags.None) @@ -4293,7 +4319,8 @@ private Message GetStreamCreateConsumerGroupMessage(RedisKey key, RedisValue gro Database, flags, RedisCommand.XGROUP, - values); + values, + GetEffectiveCancellationToken()); } /// @@ -4333,7 +4360,8 @@ private Message GetStreamPendingMessagesMessage(RedisKey key, RedisValue groupNa flags, RedisCommand.XPENDING, key, - values); + values, + GetEffectiveCancellationToken()); } private Message GetStreamRangeMessage(RedisKey key, RedisValue? minId, RedisValue? maxId, int? count, Order messageOrder, CommandFlags flags) @@ -4362,11 +4390,12 @@ private Message GetStreamRangeMessage(RedisKey key, RedisValue? minId, RedisValu flags, messageOrder == Order.Ascending ? RedisCommand.XRANGE : RedisCommand.XREVRANGE, key, - values); + values, + GetEffectiveCancellationToken()); } private Message GetStreamReadGroupMessage(RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue afterId, int? count, bool noAck, CommandFlags flags) => - new SingleStreamReadGroupCommandMessage(Database, flags, key, groupName, consumerName, afterId, count, noAck); + new SingleStreamReadGroupCommandMessage(Database, flags, key, groupName, consumerName, afterId, count, noAck, GetEffectiveCancellationToken()); private sealed class SingleStreamReadGroupCommandMessage : Message.CommandKeyBase // XREADGROUP with single stream. eg XREADGROUP GROUP mygroup Alice COUNT 1 STREAMS mystream > { @@ -4377,8 +4406,8 @@ private sealed class SingleStreamReadGroupCommandMessage : Message.CommandKeyBas private readonly bool noAck; private readonly int argCount; - public SingleStreamReadGroupCommandMessage(int db, CommandFlags flags, RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue afterId, int? count, bool noAck) - : base(db, flags, RedisCommand.XREADGROUP, key) + public SingleStreamReadGroupCommandMessage(int db, CommandFlags flags, RedisKey key, RedisValue groupName, RedisValue consumerName, RedisValue afterId, int? count, bool noAck, CancellationToken cancellationToken) + : base(db, flags, RedisCommand.XREADGROUP, key, cancellationToken) { if (count.HasValue && count <= 0) { @@ -4424,7 +4453,7 @@ protected override void WriteImpl(PhysicalConnection physical) } private Message GetSingleStreamReadMessage(RedisKey key, RedisValue afterId, int? count, CommandFlags flags) => - new SingleStreamReadCommandMessage(Database, flags, key, afterId, count); + new SingleStreamReadCommandMessage(Database, flags, key, afterId, count, GetEffectiveCancellationToken()); private sealed class SingleStreamReadCommandMessage : Message.CommandKeyBase // XREAD with a single stream. Example: XREAD COUNT 2 STREAMS mystream 0-0 { @@ -4432,8 +4461,8 @@ private sealed class SingleStreamReadCommandMessage : Message.CommandKeyBase // private readonly int? count; private readonly int argCount; - public SingleStreamReadCommandMessage(int db, CommandFlags flags, RedisKey key, RedisValue afterId, int? count) - : base(db, flags, RedisCommand.XREAD, key) + public SingleStreamReadCommandMessage(int db, CommandFlags flags, RedisKey key, RedisValue afterId, int? count, CancellationToken cancellationToken) + : base(db, flags, RedisCommand.XREAD, key, cancellationToken) { if (count.HasValue && count <= 0) { @@ -4491,16 +4520,17 @@ private Message GetStreamTrimMessage(RedisKey key, int maxLength, bool useApprox flags, RedisCommand.XTRIM, key, - values); + values, + GetEffectiveCancellationToken()); } - private Message? GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags) + private Message? GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey[] keys, CommandFlags flags, CancellationToken cancellationToken) { if (keys == null) throw new ArgumentNullException(nameof(keys)); if (keys.Length == 0) return null; // these ones are too bespoke to warrant custom Message implementations - var serverSelectionStrategy = multiplexer.ServerSelectionStrategy; + var serverSelectionStrategy = Multiplexer.ServerSelectionStrategy; int slot = serverSelectionStrategy.HashSlot(destination); var values = new RedisValue[keys.Length + 2]; values[0] = RedisLiterals.Get(operation); @@ -4510,35 +4540,35 @@ private Message GetStreamTrimMessage(RedisKey key, int maxLength, bool useApprox values[i + 2] = keys[i].AsRedisValue(); slot = serverSelectionStrategy.CombineSlot(slot, keys[i]); } - return Message.CreateInSlot(Database, slot, flags, RedisCommand.BITOP, values); + return Message.CreateInSlot(Database, slot, flags, RedisCommand.BITOP, values, cancellationToken); } - private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags) + private Message GetStringBitOperationMessage(Bitwise operation, RedisKey destination, RedisKey first, RedisKey second, CommandFlags flags, CancellationToken cancellationToken) { // these ones are too bespoke to warrant custom Message implementations var op = RedisLiterals.Get(operation); - var serverSelectionStrategy = multiplexer.ServerSelectionStrategy; + var serverSelectionStrategy = Multiplexer.ServerSelectionStrategy; int slot = serverSelectionStrategy.HashSlot(destination); slot = serverSelectionStrategy.CombineSlot(slot, first); if (second.IsNull || operation == Bitwise.Not) { // unary - return Message.CreateInSlot(Database, slot, flags, RedisCommand.BITOP, new[] { op, destination.AsRedisValue(), first.AsRedisValue() }); + return Message.CreateInSlot(Database, slot, flags, RedisCommand.BITOP, new[] { op, destination.AsRedisValue(), first.AsRedisValue() }, cancellationToken); } // binary slot = serverSelectionStrategy.CombineSlot(slot, second); - return Message.CreateInSlot(Database, slot, flags, RedisCommand.BITOP, new[] { op, destination.AsRedisValue(), first.AsRedisValue(), second.AsRedisValue() }); + return Message.CreateInSlot(Database, slot, flags, RedisCommand.BITOP, new[] { op, destination.AsRedisValue(), first.AsRedisValue(), second.AsRedisValue() }, cancellationToken); } private Message GetStringGetExMessage(in RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) => expiry switch { - null => Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PERSIST), - _ => Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PX, (long)expiry.Value.TotalMilliseconds), + null => Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PERSIST, GetEffectiveCancellationToken()), + _ => Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PX, (long)expiry.Value.TotalMilliseconds, GetEffectiveCancellationToken()), }; private Message GetStringGetExMessage(in RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None) => expiry == DateTime.MaxValue - ? Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PERSIST) - : Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PXAT, GetMillisecondsUntil(expiry)); + ? Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PERSIST, GetEffectiveCancellationToken()) + : Message.Create(Database, flags, RedisCommand.GETEX, key, RedisLiterals.PXAT, GetMillisecondsUntil(expiry), GetEffectiveCancellationToken()); private Message GetStringGetWithExpiryMessage(RedisKey key, CommandFlags flags, out ResultProcessor processor, out ServerEndPoint? server) { @@ -4548,13 +4578,14 @@ private Message GetStringGetWithExpiryMessage(RedisKey key, CommandFlags flags, } var features = GetFeatures(key, flags, RedisCommand.PTTL, out server); processor = StringGetWithExpiryProcessor.Default; - if (server != null && features.MillisecondExpiry && multiplexer.CommandMap.IsAvailable(RedisCommand.PTTL)) + var cancellationToken = GetEffectiveCancellationToken(); + if (server != null && features.MillisecondExpiry && Multiplexer.CommandMap.IsAvailable(RedisCommand.PTTL)) { - return new StringGetWithExpiryMessage(Database, flags, RedisCommand.PTTL, key); + return new StringGetWithExpiryMessage(Database, flags, RedisCommand.PTTL, key, cancellationToken); } // if we use TTL, it doesn't matter which server server = null; - return new StringGetWithExpiryMessage(Database, flags, RedisCommand.TTL, key); + return new StringGetWithExpiryMessage(Database, flags, RedisCommand.TTL, key, cancellationToken); } private Message? GetStringSetMessage(KeyValuePair[] values, When when = When.Always, CommandFlags flags = CommandFlags.None) @@ -4568,14 +4599,14 @@ private Message GetStringGetWithExpiryMessage(RedisKey key, CommandFlags flags, WhenAlwaysOrNotExists(when); int slot = ServerSelectionStrategy.NoSlot, offset = 0; var args = new RedisValue[values.Length * 2]; - var serverSelectionStrategy = multiplexer.ServerSelectionStrategy; + var serverSelectionStrategy = Multiplexer.ServerSelectionStrategy; for (int i = 0; i < values.Length; i++) { args[offset++] = values[i].Key.AsRedisValue(); args[offset++] = values[i].Value; slot = serverSelectionStrategy.CombineSlot(slot, values[i].Key); } - return Message.CreateInSlot(Database, slot, flags, when == When.NotExists ? RedisCommand.MSETNX : RedisCommand.MSET, args); + return Message.CreateInSlot(Database, slot, flags, when == When.NotExists ? RedisCommand.MSETNX : RedisCommand.MSET, args, GetEffectiveCancellationToken()); } } @@ -4588,19 +4619,19 @@ private Message GetStringSetMessage( CommandFlags flags = CommandFlags.None) { WhenAlwaysOrExistsOrNotExists(when); - if (value.IsNull) return Message.Create(Database, flags, RedisCommand.DEL, key); + if (value.IsNull) return Message.Create(Database, flags, RedisCommand.DEL, key, GetEffectiveCancellationToken()); if (expiry == null || expiry.Value == TimeSpan.MaxValue) { // no expiry return when switch { - When.Always when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value), - When.Always when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.KEEPTTL), - When.NotExists when !keepTtl => Message.Create(Database, flags, RedisCommand.SETNX, key, value), - When.NotExists when keepTtl => Message.Create(Database, flags, RedisCommand.SETNX, key, value, RedisLiterals.KEEPTTL), - When.Exists when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX), - When.Exists when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX, RedisLiterals.KEEPTTL), + When.Always when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, GetEffectiveCancellationToken()), + When.Always when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.KEEPTTL, GetEffectiveCancellationToken()), + When.NotExists when !keepTtl => Message.Create(Database, flags, RedisCommand.SETNX, key, value, GetEffectiveCancellationToken()), + When.NotExists when keepTtl => Message.Create(Database, flags, RedisCommand.SETNX, key, value, RedisLiterals.KEEPTTL, GetEffectiveCancellationToken()), + When.Exists when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX, GetEffectiveCancellationToken()), + When.Exists when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX, RedisLiterals.KEEPTTL, GetEffectiveCancellationToken()), _ => throw new ArgumentOutOfRangeException(nameof(when)), }; } @@ -4612,18 +4643,18 @@ private Message GetStringSetMessage( long seconds = milliseconds / 1000; return when switch { - When.Always => Message.Create(Database, flags, RedisCommand.SETEX, key, seconds, value), - When.Exists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.XX), - When.NotExists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.NX), + When.Always => Message.Create(Database, flags, RedisCommand.SETEX, key, seconds, value, GetEffectiveCancellationToken()), + When.Exists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.XX, GetEffectiveCancellationToken()), + When.NotExists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.NX, GetEffectiveCancellationToken()), _ => throw new ArgumentOutOfRangeException(nameof(when)), }; } return when switch { - When.Always => Message.Create(Database, flags, RedisCommand.PSETEX, key, milliseconds, value), - When.Exists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.XX), - When.NotExists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.NX), + When.Always => Message.Create(Database, flags, RedisCommand.PSETEX, key, milliseconds, value, GetEffectiveCancellationToken()), + When.Exists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.XX, GetEffectiveCancellationToken()), + When.NotExists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.NX, GetEffectiveCancellationToken()), _ => throw new ArgumentOutOfRangeException(nameof(when)), }; } @@ -4637,19 +4668,19 @@ private Message GetStringSetAndGetMessage( CommandFlags flags = CommandFlags.None) { WhenAlwaysOrExistsOrNotExists(when); - if (value.IsNull) return Message.Create(Database, flags, RedisCommand.GETDEL, key); + if (value.IsNull) return Message.Create(Database, flags, RedisCommand.GETDEL, key, GetEffectiveCancellationToken()); if (expiry == null || expiry.Value == TimeSpan.MaxValue) { // no expiry return when switch { - When.Always when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.GET), - When.Always when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.GET, RedisLiterals.KEEPTTL), - When.Exists when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX, RedisLiterals.GET), - When.Exists when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX, RedisLiterals.GET, RedisLiterals.KEEPTTL), - When.NotExists when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.NX, RedisLiterals.GET), - When.NotExists when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.NX, RedisLiterals.GET, RedisLiterals.KEEPTTL), + When.Always when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.GET, GetEffectiveCancellationToken()), + When.Always when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.GET, RedisLiterals.KEEPTTL, GetEffectiveCancellationToken()), + When.Exists when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX, RedisLiterals.GET, GetEffectiveCancellationToken()), + When.Exists when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.XX, RedisLiterals.GET, RedisLiterals.KEEPTTL, GetEffectiveCancellationToken()), + When.NotExists when !keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.NX, RedisLiterals.GET, GetEffectiveCancellationToken()), + When.NotExists when keepTtl => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.NX, RedisLiterals.GET, RedisLiterals.KEEPTTL, GetEffectiveCancellationToken()), _ => throw new ArgumentOutOfRangeException(nameof(when)), }; } @@ -4661,18 +4692,18 @@ private Message GetStringSetAndGetMessage( long seconds = milliseconds / 1000; return when switch { - When.Always => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.GET), - When.Exists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.XX, RedisLiterals.GET), - When.NotExists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.NX, RedisLiterals.GET), + When.Always => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.GET, GetEffectiveCancellationToken()), + When.Exists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.XX, RedisLiterals.GET, GetEffectiveCancellationToken()), + When.NotExists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.EX, seconds, RedisLiterals.NX, RedisLiterals.GET, GetEffectiveCancellationToken()), _ => throw new ArgumentOutOfRangeException(nameof(when)), }; } return when switch { - When.Always => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.GET), - When.Exists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.XX, RedisLiterals.GET), - When.NotExists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.NX, RedisLiterals.GET), + When.Always => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.GET, GetEffectiveCancellationToken()), + When.Exists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.XX, RedisLiterals.GET, GetEffectiveCancellationToken()), + When.NotExists => Message.Create(Database, flags, RedisCommand.SET, key, value, RedisLiterals.PX, milliseconds, RedisLiterals.NX, RedisLiterals.GET, GetEffectiveCancellationToken()), _ => throw new ArgumentOutOfRangeException(nameof(when)), }; } @@ -4681,11 +4712,11 @@ private Message GetStringSetAndGetMessage( { 0 => ((flags & CommandFlags.FireAndForget) != 0) ? null - : Message.Create(Database, flags, RedisCommand.INCRBY, key, value), - 1 => Message.Create(Database, flags, RedisCommand.INCR, key), - -1 => Message.Create(Database, flags, RedisCommand.DECR, key), - > 0 => Message.Create(Database, flags, RedisCommand.INCRBY, key, value), - _ => Message.Create(Database, flags, RedisCommand.DECRBY, key, -value), + : Message.Create(Database, flags, RedisCommand.INCRBY, key, value, GetEffectiveCancellationToken()), + 1 => Message.Create(Database, flags, RedisCommand.INCR, key, GetEffectiveCancellationToken()), + -1 => Message.Create(Database, flags, RedisCommand.DECR, key, GetEffectiveCancellationToken()), + > 0 => Message.Create(Database, flags, RedisCommand.INCRBY, key, value, GetEffectiveCancellationToken()), + _ => Message.Create(Database, flags, RedisCommand.DECRBY, key, -value, GetEffectiveCancellationToken()), }; private static RedisCommand SetOperationCommand(SetOperation operation, bool store) => operation switch @@ -4701,7 +4732,7 @@ private Message GetStringSetAndGetMessage( server = null; if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); - if (!multiplexer.CommandMap.IsAvailable(command)) return null; + if (!Multiplexer.CommandMap.IsAvailable(command)) return null; var features = GetFeatures(key, flags, RedisCommand.SCAN, out server); if (!features.Scan) return null; @@ -4715,9 +4746,9 @@ private Message GetLexMessage(RedisCommand command, RedisKey key, RedisValue min RedisValue start = GetLexRange(min, exclude, true), stop = GetLexRange(max, exclude, false); if (skip == 0 && take == -1) - return Message.Create(Database, flags, command, key, start, stop); + return Message.Create(Database, flags, command, key, start, stop, GetEffectiveCancellationToken()); - return Message.Create(Database, flags, command, key, new[] { start, stop, RedisLiterals.LIMIT, skip, take }); + return Message.Create(Database, flags, command, key, new[] { start, stop, RedisLiterals.LIMIT, skip, take }, GetEffectiveCancellationToken()); } public long SortedSetLengthByValue(RedisKey key, RedisValue min, RedisValue max, Exclude exclude = Exclude.None, CommandFlags flags = CommandFlags.None) @@ -4823,37 +4854,37 @@ public ScanEnumerable( this.noValues = noValues; } - private protected override ResultProcessor.ScanResult> Processor { get; } + private protected override ResultProcessor Processor { get; } - private protected override Message CreateMessage(in RedisValue cursor) + private protected override Message CreateMessage(in RedisValue cursor, CancellationToken cancellationToken) { if (noValues) { - if (CursorUtils.IsNil(pattern) && pageSize == CursorUtils.DefaultRedisPageSize) return Message.Create(db, flags, command, key, cursor, RedisLiterals.NOVALUES); - if (CursorUtils.IsNil(pattern)) return Message.Create(db, flags, command, key, cursor, RedisLiterals.COUNT, pageSize, RedisLiterals.NOVALUES); - return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize, RedisLiterals.NOVALUES); + if (CursorUtils.IsNil(pattern) && pageSize == CursorUtils.DefaultRedisPageSize) return Message.Create(db, flags, command, key, cursor, RedisLiterals.NOVALUES, cancellationToken); + if (CursorUtils.IsNil(pattern)) return Message.Create(db, flags, command, key, cursor, RedisLiterals.COUNT, pageSize, RedisLiterals.NOVALUES, cancellationToken); + return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize, RedisLiterals.NOVALUES, cancellationToken); } if (CursorUtils.IsNil(pattern)) { if (pageSize == CursorUtils.DefaultRedisPageSize) { - return Message.Create(db, flags, command, key, cursor); + return Message.Create(db, flags, command, key, cursor, cancellationToken); } else { - return Message.Create(db, flags, command, key, cursor, RedisLiterals.COUNT, pageSize); + return Message.Create(db, flags, command, key, cursor, RedisLiterals.COUNT, pageSize, cancellationToken); } } else { if (pageSize == CursorUtils.DefaultRedisPageSize) { - return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern); + return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern, cancellationToken); } else { - return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize); + return Message.Create(db, flags, command, key, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize, cancellationToken); } } } @@ -4862,8 +4893,8 @@ private protected override Message CreateMessage(in RedisValue cursor) internal sealed class ScriptLoadMessage : Message { internal readonly string Script; - public ScriptLoadMessage(CommandFlags flags, string script) - : base(-1, flags, RedisCommand.SCRIPT) + public ScriptLoadMessage(CommandFlags flags, string script, CancellationToken cancellationToken) + : base(-1, flags, RedisCommand.SCRIPT, cancellationToken) { Script = script ?? throw new ArgumentNullException(nameof(script)); } @@ -4917,7 +4948,7 @@ internal sealed class ExecuteMessage : Message private readonly ICollection _args; public new CommandBytes Command { get; } - public ExecuteMessage(CommandMap? map, int db, CommandFlags flags, string command, ICollection? args) : base(db, flags, RedisCommand.UNKNOWN) + public ExecuteMessage(CommandMap? map, int db, CommandFlags flags, string command, ICollection? args, CancellationToken cancellationToken) : base(db, flags, RedisCommand.UNKNOWN, cancellationToken) { if (args != null && args.Count >= PhysicalConnection.REDIS_MAX_ARGS) // using >= here because we will be adding 1 for the command itself (which is an arg for the purposes of the multi-bulk protocol) { @@ -4942,7 +4973,8 @@ protected override void WriteImpl(PhysicalConnection physical) physical.Write(channel); } else - { // recognises well-known types + { + // recognises well-known types var val = RedisValue.TryParse(arg, out var valid); if (!valid) throw new InvalidCastException($"Unable to parse value: '{arg}'"); physical.WriteBulkString(val); @@ -4976,21 +5008,21 @@ private sealed class ScriptEvalMessage : Message, IMultiMessage private byte[]? asciiHash; private readonly byte[]? hexHash; - public ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string script, RedisKey[]? keys, RedisValue[]? values) - : this(db, flags, command, script, null, keys, values) + public ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string script, RedisKey[]? keys, RedisValue[]? values, CancellationToken cancellationToken) + : this(db, flags, command, script, null, keys, values, cancellationToken) { if (script == null) throw new ArgumentNullException(nameof(script)); } - public ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, byte[] hash, RedisKey[]? keys, RedisValue[]? values) - : this(db, flags, command, null, hash, keys, values) + public ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, byte[] hash, RedisKey[]? keys, RedisValue[]? values, CancellationToken cancellationToken) + : this(db, flags, command, null, hash, keys, values, cancellationToken) { if (hash == null) throw new ArgumentNullException(nameof(hash)); if (hash.Length != ResultProcessor.ScriptLoadProcessor.Sha1HashLength) throw new ArgumentOutOfRangeException(nameof(hash), "Invalid hash length"); } - private ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string? script, byte[]? hexHash, RedisKey[]? keys, RedisValue[]? values) - : base(db, flags, command) + private ScriptEvalMessage(int db, CommandFlags flags, RedisCommand command, string? script, byte[]? hexHash, RedisKey[]? keys, RedisValue[]? values, CancellationToken cancellationToken) + : base(db, flags, command, cancellationToken) { this.script = script; this.hexHash = hexHash; @@ -5025,7 +5057,7 @@ public IEnumerable GetMessages(PhysicalConnection connection) if (asciiHash == null) { - var msg = new ScriptLoadMessage(Flags, script); + var msg = new ScriptLoadMessage(Flags, script, CancellationToken.None); msg.SetInternalCall(); msg.SetSource(ResultProcessor.ScriptLoad, null); yield return msg; @@ -5079,12 +5111,15 @@ protected override RedisValue[] Parse(in RawResult result, out int count) } } - private static Message CreateListPositionMessage(int db, CommandFlags flags, RedisKey key, RedisValue element, long rank, long maxLen, long? count = null) => - count != null - ? Message.Create(db, flags, RedisCommand.LPOS, key, element, RedisLiterals.RANK, rank, RedisLiterals.MAXLEN, maxLen, RedisLiterals.COUNT, count) - : Message.Create(db, flags, RedisCommand.LPOS, key, element, RedisLiterals.RANK, rank, RedisLiterals.MAXLEN, maxLen); + private Message CreateListPositionMessage(int db, CommandFlags flags, RedisKey key, RedisValue element, long rank, long maxLen, long? count = null) + { + var cancellationToken = GetEffectiveCancellationToken(); + return count != null + ? Message.Create(db, flags, RedisCommand.LPOS, key, element, RedisLiterals.RANK, rank, RedisLiterals.MAXLEN, maxLen, RedisLiterals.COUNT, count, cancellationToken) + : Message.Create(db, flags, RedisCommand.LPOS, key, element, RedisLiterals.RANK, rank, RedisLiterals.MAXLEN, maxLen, cancellationToken); + } - private static Message CreateSortedSetRangeStoreMessage( + private Message CreateSortedSetRangeStoreMessage( int db, CommandFlags flags, RedisKey sourceKey, @@ -5097,6 +5132,7 @@ private static Message CreateSortedSetRangeStoreMessage( long skip, long? take) { + var cancellationToken = GetEffectiveCancellationToken(); if (sortedSetOrder == SortedSetOrder.ByRank) { if (take > 0) @@ -5110,8 +5146,8 @@ private static Message CreateSortedSetRangeStoreMessage( return order switch { - Order.Ascending => Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, start, stop), - Order.Descending => Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, start, stop, RedisLiterals.REV), + Order.Ascending => Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, start, stop, cancellationToken), + Order.Descending => Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, start, stop, RedisLiterals.REV, cancellationToken), _ => throw new ArgumentOutOfRangeException(nameof(order)), }; } @@ -5133,13 +5169,13 @@ private static Message CreateSortedSetRangeStoreMessage( return order switch { Order.Ascending when take != null && take > 0 => - Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, formattedStart, formattedStop, sortedSetOrder.GetLiteral(), RedisLiterals.LIMIT, skip, take), + Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, formattedStart, formattedStop, sortedSetOrder.GetLiteral(), RedisLiterals.LIMIT, skip, take, cancellationToken), Order.Ascending => - Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, formattedStart, formattedStop, sortedSetOrder.GetLiteral()), + Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, formattedStart, formattedStop, sortedSetOrder.GetLiteral(), cancellationToken), Order.Descending when take != null && take > 0 => - Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, formattedStart, formattedStop, sortedSetOrder.GetLiteral(), RedisLiterals.REV, RedisLiterals.LIMIT, skip, take), + Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, formattedStart, formattedStop, sortedSetOrder.GetLiteral(), RedisLiterals.REV, RedisLiterals.LIMIT, skip, take, cancellationToken), Order.Descending => - Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, formattedStart, formattedStop, sortedSetOrder.GetLiteral(), RedisLiterals.REV), + Message.Create(db, flags, RedisCommand.ZRANGESTORE, destinationKey, sourceKey, formattedStart, formattedStop, sortedSetOrder.GetLiteral(), RedisLiterals.REV, cancellationToken), _ => throw new ArgumentOutOfRangeException(nameof(order)), }; } @@ -5148,8 +5184,8 @@ private sealed class SortedSetCombineAndStoreCommandMessage : Message.CommandKey { private readonly RedisKey[] keys; private readonly RedisValue[] values; - public SortedSetCombineAndStoreCommandMessage(int db, CommandFlags flags, RedisCommand command, RedisKey destination, RedisKey[] keys, RedisValue[] values) - : base(db, flags, command, destination) + public SortedSetCombineAndStoreCommandMessage(int db, CommandFlags flags, RedisCommand command, RedisKey destination, RedisKey[] keys, RedisValue[] values, CancellationToken cancellationToken) + : base(db, flags, command, destination, cancellationToken) { for (int i = 0; i < keys.Length; i++) keys[i].AssertNotNull(); @@ -5193,8 +5229,8 @@ private class StringGetWithExpiryMessage : Message.CommandKeyBase, IMultiMessage private readonly RedisCommand ttlCommand; private IResultBox? box; - public StringGetWithExpiryMessage(int db, CommandFlags flags, RedisCommand ttlCommand, in RedisKey key) - : base(db, flags, RedisCommand.GET, key) + public StringGetWithExpiryMessage(int db, CommandFlags flags, RedisCommand ttlCommand, in RedisKey key, CancellationToken cancellationToken) + : base(db, flags, RedisCommand.GET, key, cancellationToken) { this.ttlCommand = ttlCommand; } @@ -5203,8 +5239,8 @@ public StringGetWithExpiryMessage(int db, CommandFlags flags, RedisCommand ttlCo public IEnumerable GetMessages(PhysicalConnection connection) { - box = SimpleResultBox.Create(); - var ttl = Message.Create(Db, Flags, ttlCommand, Key); + box = SimpleResultBox.Create(CancellationToken); + var ttl = Message.Create(Db, Flags, ttlCommand, Key, CancellationToken.None); var proc = ttlCommand == RedisCommand.PTTL ? ResultProcessor.TimeSpanFromMilliseconds : ResultProcessor.TimeSpanFromSeconds; ttl.SetSource(proc, box); yield return ttl; diff --git a/src/StackExchange.Redis/RedisServer.cs b/src/StackExchange.Redis/RedisServer.cs index 8810e1e2b..260baed45 100644 --- a/src/StackExchange.Redis/RedisServer.cs +++ b/src/StackExchange.Redis/RedisServer.cs @@ -7,6 +7,7 @@ using System.Net; using System.Security.Cryptography; using System.Text; +using System.Threading; using System.Threading.Tasks; using Pipelines.Sockets.Unofficial.Arenas; @@ -34,7 +35,7 @@ internal RedisServer(ConnectionMultiplexer multiplexer, ServerEndPoint server, o bool IServer.IsSlave => IsReplica; public bool IsReplica => server.IsReplica; - public RedisProtocol Protocol => server.Protocol ?? (multiplexer.RawConfig.TryResp3() ? RedisProtocol.Resp3 : RedisProtocol.Resp2); + public RedisProtocol Protocol => server.Protocol ?? (Multiplexer.RawConfig.TryResp3() ? RedisProtocol.Resp3 : RedisProtocol.Resp2); bool IServer.AllowSlaveWrites { @@ -53,13 +54,13 @@ public bool AllowReplicaWrites public void ClientKill(EndPoint endpoint, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.KILL, (RedisValue)Format.ToString(endpoint)); + var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.KILL, (RedisValue)Format.ToString(endpoint), this.GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task ClientKillAsync(EndPoint endpoint, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.KILL, (RedisValue)Format.ToString(endpoint)); + var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.KILL, (RedisValue)Format.ToString(endpoint), this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } @@ -77,132 +78,132 @@ public Task ClientKillAsync(long? id = null, ClientType? clientType = null public long ClientKill(ClientKillFilter filter, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, filter.ToList(Features.ReplicaCommands)); + var msg = Message.Create(-1, flags, RedisCommand.CLIENT, filter.ToList(Features.ReplicaCommands), this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task ClientKillAsync(ClientKillFilter filter, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, filter.ToList(Features.ReplicaCommands)); + var msg = Message.Create(-1, flags, RedisCommand.CLIENT, filter.ToList(Features.ReplicaCommands), this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } private Message GetClientKillMessage(EndPoint? endpoint, long? id, ClientType? clientType, bool? skipMe, CommandFlags flags) { var args = new ClientKillFilter().WithId(id).WithClientType(clientType).WithEndpoint(endpoint).WithSkipMe(skipMe).ToList(Features.ReplicaCommands); - return Message.Create(-1, flags, RedisCommand.CLIENT, args); + return Message.Create(-1, flags, RedisCommand.CLIENT, args, GetEffectiveCancellationToken()); } public ClientInfo[] ClientList(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.LIST); + var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.LIST, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ClientInfo.Processor, defaultValue: Array.Empty()); } public Task ClientListAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.LIST); + var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.LIST, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ClientInfo.Processor, defaultValue: Array.Empty()); } public ClusterConfiguration? ClusterNodes(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); + var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.ClusterNodes); } public Task ClusterNodesAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); + var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.ClusterNodes); } public string? ClusterNodesRaw(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); + var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.ClusterNodesRaw); } public Task ClusterNodesRawAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); + var msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.ClusterNodesRaw); } public KeyValuePair[] ConfigGet(RedisValue pattern = default, CommandFlags flags = CommandFlags.None) { if (pattern.IsNullOrEmpty) pattern = RedisLiterals.Wildcard; - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, pattern); + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, pattern, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.StringPairInterleaved, defaultValue: Array.Empty>()); } public Task[]> ConfigGetAsync(RedisValue pattern = default, CommandFlags flags = CommandFlags.None) { if (pattern.IsNullOrEmpty) pattern = RedisLiterals.Wildcard; - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, pattern); + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, pattern, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.StringPairInterleaved, defaultValue: Array.Empty>()); } public void ConfigResetStatistics(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.RESETSTAT); + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.RESETSTAT, this.GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task ConfigResetStatisticsAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.RESETSTAT); + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.RESETSTAT, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } public void ConfigRewrite(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.REWRITE); + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.REWRITE, this.GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task ConfigRewriteAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.REWRITE); + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.REWRITE, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } public void ConfigSet(RedisValue setting, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.SET, setting, value); + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.SET, setting, value, this.GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); - ExecuteSync(Message.Create(-1, flags | CommandFlags.FireAndForget, RedisCommand.CONFIG, RedisLiterals.GET, setting), ResultProcessor.AutoConfigure); + ExecuteSync(Message.Create(-1, flags | CommandFlags.FireAndForget, RedisCommand.CONFIG, RedisLiterals.GET, setting, this.GetEffectiveCancellationToken()), ResultProcessor.AutoConfigure); } public Task ConfigSetAsync(RedisValue setting, RedisValue value, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.SET, setting, value); + var msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.SET, setting, value, this.GetEffectiveCancellationToken()); var task = ExecuteAsync(msg, ResultProcessor.DemandOK); - ExecuteSync(Message.Create(-1, flags | CommandFlags.FireAndForget, RedisCommand.CONFIG, RedisLiterals.GET, setting), ResultProcessor.AutoConfigure); + ExecuteSync(Message.Create(-1, flags | CommandFlags.FireAndForget, RedisCommand.CONFIG, RedisLiterals.GET, setting, this.GetEffectiveCancellationToken()), ResultProcessor.AutoConfigure); return task; } public long CommandCount(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.COMMAND, RedisLiterals.COUNT); + var msg = Message.Create(-1, flags, RedisCommand.COMMAND, RedisLiterals.COUNT, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task CommandCountAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.COMMAND, RedisLiterals.COUNT); + var msg = Message.Create(-1, flags, RedisCommand.COMMAND, RedisLiterals.COUNT, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public RedisKey[] CommandGetKeys(RedisValue[] command, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.COMMAND, AddValueToArray(RedisLiterals.GETKEYS, command)); + var msg = Message.Create(-1, flags, RedisCommand.COMMAND, AddValueToArray(RedisLiterals.GETKEYS, command), this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisKeyArray, defaultValue: Array.Empty()); } public Task CommandGetKeysAsync(RedisValue[] command, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.COMMAND, AddValueToArray(RedisLiterals.GETKEYS, command)); + var msg = Message.Create(-1, flags, RedisCommand.COMMAND, AddValueToArray(RedisLiterals.GETKEYS, command), this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisKeyArray, defaultValue: Array.Empty()); } @@ -220,21 +221,22 @@ public Task CommandListAsync(RedisValue? moduleName = null, RedisValue private Message GetCommandListMessage(RedisValue? moduleName = null, RedisValue? category = null, RedisValue? pattern = null, CommandFlags flags = CommandFlags.None) { + var cancellationToken = GetEffectiveCancellationToken(); if (moduleName == null && category == null && pattern == null) { - return Message.Create(-1, flags, RedisCommand.COMMAND, RedisLiterals.LIST); + return Message.Create(-1, flags, RedisCommand.COMMAND, RedisLiterals.LIST, cancellationToken); } else if (moduleName != null && category == null && pattern == null) { - return Message.Create(-1, flags, RedisCommand.COMMAND, MakeArray(RedisLiterals.LIST, RedisLiterals.FILTERBY, RedisLiterals.MODULE, (RedisValue)moduleName)); + return Message.Create(-1, flags, RedisCommand.COMMAND, MakeArray(RedisLiterals.LIST, RedisLiterals.FILTERBY, RedisLiterals.MODULE, (RedisValue)moduleName), cancellationToken); } else if (moduleName == null && category != null && pattern == null) { - return Message.Create(-1, flags, RedisCommand.COMMAND, MakeArray(RedisLiterals.LIST, RedisLiterals.FILTERBY, RedisLiterals.ACLCAT, (RedisValue)category)); + return Message.Create(-1, flags, RedisCommand.COMMAND, MakeArray(RedisLiterals.LIST, RedisLiterals.FILTERBY, RedisLiterals.ACLCAT, (RedisValue)category), cancellationToken); } else if (moduleName == null && category == null && pattern != null) { - return Message.Create(-1, flags, RedisCommand.COMMAND, MakeArray(RedisLiterals.LIST, RedisLiterals.FILTERBY, RedisLiterals.PATTERN, (RedisValue)pattern)); + return Message.Create(-1, flags, RedisCommand.COMMAND, MakeArray(RedisLiterals.LIST, RedisLiterals.FILTERBY, RedisLiterals.PATTERN, (RedisValue)pattern), cancellationToken); } else { @@ -255,49 +257,49 @@ private RedisValue[] AddValueToArray(RedisValue val, RedisValue[] arr) public long DatabaseSize(int database = -1, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.DBSIZE); + var msg = Message.Create(Multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.DBSIZE, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task DatabaseSizeAsync(int database = -1, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.DBSIZE); + var msg = Message.Create(Multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.DBSIZE, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public RedisValue Echo(RedisValue message, CommandFlags flags) { - var msg = Message.Create(-1, flags, RedisCommand.ECHO, message); + var msg = Message.Create(-1, flags, RedisCommand.ECHO, message, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Task EchoAsync(RedisValue message, CommandFlags flags) { - var msg = Message.Create(-1, flags, RedisCommand.ECHO, message); + var msg = Message.Create(-1, flags, RedisCommand.ECHO, message, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public void FlushAllDatabases(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.FLUSHALL); + var msg = Message.Create(-1, flags, RedisCommand.FLUSHALL, this.GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task FlushAllDatabasesAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.FLUSHALL); + var msg = Message.Create(-1, flags, RedisCommand.FLUSHALL, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } public void FlushDatabase(int database = -1, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.FLUSHDB); + var msg = Message.Create(Multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.FLUSHDB, this.GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task FlushDatabaseAsync(int database = -1, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.FLUSHDB); + var msg = Message.Create(Multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.FLUSHDB, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } @@ -309,8 +311,8 @@ public Task FlushDatabaseAsync(int database = -1, CommandFlags flags = CommandFl public IGrouping>[] Info(RedisValue section = default, CommandFlags flags = CommandFlags.None) { var msg = section.IsNullOrEmpty - ? Message.Create(-1, flags, RedisCommand.INFO) - : Message.Create(-1, flags, RedisCommand.INFO, section); + ? Message.Create(-1, flags, RedisCommand.INFO, this.GetEffectiveCancellationToken()) + : Message.Create(-1, flags, RedisCommand.INFO, section, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Info, defaultValue: InfoDefault); } @@ -318,8 +320,8 @@ public IGrouping>[] Info(RedisValue section public Task>[]> InfoAsync(RedisValue section = default, CommandFlags flags = CommandFlags.None) { var msg = section.IsNullOrEmpty - ? Message.Create(-1, flags, RedisCommand.INFO) - : Message.Create(-1, flags, RedisCommand.INFO, section); + ? Message.Create(-1, flags, RedisCommand.INFO, this.GetEffectiveCancellationToken()) + : Message.Create(-1, flags, RedisCommand.INFO, section, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Info, defaultValue: InfoDefault); } @@ -327,8 +329,8 @@ public Task>[]> InfoAsync(RedisVa public string? InfoRaw(RedisValue section = default, CommandFlags flags = CommandFlags.None) { var msg = section.IsNullOrEmpty - ? Message.Create(-1, flags, RedisCommand.INFO) - : Message.Create(-1, flags, RedisCommand.INFO, section); + ? Message.Create(-1, flags, RedisCommand.INFO, this.GetEffectiveCancellationToken()) + : Message.Create(-1, flags, RedisCommand.INFO, section, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.String); } @@ -336,8 +338,8 @@ public Task>[]> InfoAsync(RedisVa public Task InfoRawAsync(RedisValue section = default, CommandFlags flags = CommandFlags.None) { var msg = section.IsNullOrEmpty - ? Message.Create(-1, flags, RedisCommand.INFO) - : Message.Create(-1, flags, RedisCommand.INFO, section); + ? Message.Create(-1, flags, RedisCommand.INFO, this.GetEffectiveCancellationToken()) + : Message.Create(-1, flags, RedisCommand.INFO, section, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.String); } @@ -353,11 +355,11 @@ IAsyncEnumerable IServer.KeysAsync(int database, RedisValue pattern, i private CursorEnumerable KeysAsync(int database, RedisValue pattern, int pageSize, long cursor, int pageOffset, CommandFlags flags) { - database = multiplexer.ApplyDefaultDatabase(database); + database = Multiplexer.ApplyDefaultDatabase(database); if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize)); if (CursorUtils.IsNil(pattern)) pattern = RedisLiterals.Wildcard; - if (multiplexer.CommandMap.IsAvailable(RedisCommand.SCAN)) + if (Multiplexer.CommandMap.IsAvailable(RedisCommand.SCAN)) { var features = server.GetFeatures(); @@ -365,42 +367,42 @@ private CursorEnumerable KeysAsync(int database, RedisValue pattern, i } if (cursor != 0) throw ExceptionFactory.NoCursor(RedisCommand.KEYS); - Message msg = Message.Create(database, flags, RedisCommand.KEYS, pattern); + Message msg = Message.Create(database, flags, RedisCommand.KEYS, pattern, GetEffectiveCancellationToken()); return CursorEnumerable.From(this, server, ExecuteAsync(msg, ResultProcessor.RedisKeyArray, defaultValue: Array.Empty()), pageOffset); } public DateTime LastSave(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.LASTSAVE); + var msg = Message.Create(-1, flags, RedisCommand.LASTSAVE, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.DateTime); } public Task LastSaveAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.LASTSAVE); + var msg = Message.Create(-1, flags, RedisCommand.LASTSAVE, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DateTime); } public void MakeMaster(ReplicationChangeOptions options, TextWriter? log = null) { // Do you believe in magic? - multiplexer.MakePrimaryAsync(server, options, log).Wait(60000); + Multiplexer.MakePrimaryAsync(server, options, log).Wait(60000); } public async Task MakePrimaryAsync(ReplicationChangeOptions options, TextWriter? log = null) { - await multiplexer.MakePrimaryAsync(server, options, log).ForAwait(); + await Multiplexer.MakePrimaryAsync(server, options, log).ForAwait(); } public Role Role(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.ROLE); + var msg = Message.Create(-1, flags, RedisCommand.ROLE, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Role, defaultValue: Redis.Role.Null); } public Task RoleAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.ROLE); + var msg = Message.Create(-1, flags, RedisCommand.ROLE, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Role, defaultValue: Redis.Role.Null); } @@ -418,51 +420,51 @@ public Task SaveAsync(SaveType type, CommandFlags flags = CommandFlags.None) public bool ScriptExists(string script, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Hash(script)); + var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Hash(script), this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public bool ScriptExists(byte[] sha1, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Encode(sha1)); + var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Encode(sha1), this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } public Task ScriptExistsAsync(string script, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Hash(script)); + var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Hash(script), this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public Task ScriptExistsAsync(byte[] sha1, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Encode(sha1)); + var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.EXISTS, ScriptHash.Encode(sha1), this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } public void ScriptFlush(CommandFlags flags = CommandFlags.None) { - if (!multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(multiplexer.RawConfig.IncludeDetailInExceptions, RedisCommand.SCRIPT, null, server); - var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.FLUSH); + if (!Multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(Multiplexer.RawConfig.IncludeDetailInExceptions, RedisCommand.SCRIPT, null, server); + var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.FLUSH, this.GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task ScriptFlushAsync(CommandFlags flags = CommandFlags.None) { - if (!multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(multiplexer.RawConfig.IncludeDetailInExceptions, RedisCommand.SCRIPT, null, server); - var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.FLUSH); + if (!Multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(Multiplexer.RawConfig.IncludeDetailInExceptions, RedisCommand.SCRIPT, null, server); + var msg = Message.Create(-1, flags, RedisCommand.SCRIPT, RedisLiterals.FLUSH, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } public byte[] ScriptLoad(string script, CommandFlags flags = CommandFlags.None) { - var msg = new RedisDatabase.ScriptLoadMessage(flags, script); + var msg = new RedisDatabase.ScriptLoadMessage(flags, script, CancellationToken.None); return ExecuteSync(msg, ResultProcessor.ScriptLoad, defaultValue: Array.Empty()); // Note: default isn't used on failure - we'll throw } public Task ScriptLoadAsync(string script, CommandFlags flags = CommandFlags.None) { - var msg = new RedisDatabase.ScriptLoadMessage(flags, script); + var msg = new RedisDatabase.ScriptLoadMessage(flags, script, CancellationToken.None); return ExecuteAsync(msg, ResultProcessor.ScriptLoad, defaultValue: Array.Empty()); // Note: default isn't used on failure - we'll throw } @@ -478,11 +480,12 @@ public Task ScriptLoadAsync(LuaScript script, CommandFlags flag public void Shutdown(ShutdownMode shutdownMode = ShutdownMode.Default, CommandFlags flags = CommandFlags.None) { + var cancellationToken = GetEffectiveCancellationToken(); Message msg = shutdownMode switch { - ShutdownMode.Default => Message.Create(-1, flags, RedisCommand.SHUTDOWN), - ShutdownMode.Always => Message.Create(-1, flags, RedisCommand.SHUTDOWN, RedisLiterals.SAVE), - ShutdownMode.Never => Message.Create(-1, flags, RedisCommand.SHUTDOWN, RedisLiterals.NOSAVE), + ShutdownMode.Default => Message.Create(-1, flags, RedisCommand.SHUTDOWN, cancellationToken), + ShutdownMode.Always => Message.Create(-1, flags, RedisCommand.SHUTDOWN, RedisLiterals.SAVE, cancellationToken), + ShutdownMode.Never => Message.Create(-1, flags, RedisCommand.SHUTDOWN, RedisLiterals.NOSAVE, cancellationToken), _ => throw new ArgumentOutOfRangeException(nameof(shutdownMode)), }; try @@ -499,8 +502,8 @@ public void Shutdown(ShutdownMode shutdownMode = ShutdownMode.Default, CommandFl public CommandTrace[] SlowlogGet(int count = 0, CommandFlags flags = CommandFlags.None) { var msg = count > 0 - ? Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET, count) - : Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET); + ? Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET, count, this.GetEffectiveCancellationToken()) + : Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, CommandTrace.Processor, defaultValue: Array.Empty()); } @@ -508,99 +511,99 @@ public CommandTrace[] SlowlogGet(int count = 0, CommandFlags flags = CommandFlag public Task SlowlogGetAsync(int count = 0, CommandFlags flags = CommandFlags.None) { var msg = count > 0 - ? Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET, count) - : Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET); + ? Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET, count, this.GetEffectiveCancellationToken()) + : Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.GET, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, CommandTrace.Processor, defaultValue: Array.Empty()); } public void SlowlogReset(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.RESET); + var msg = Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.RESET, this.GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task SlowlogResetAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.RESET); + var msg = Message.Create(-1, flags, RedisCommand.SLOWLOG, RedisLiterals.RESET, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } public RedisValue StringGet(int db, RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(db, flags, RedisCommand.GET, key); + var msg = Message.Create(db, flags, RedisCommand.GET, key, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } public Task StringGetAsync(int db, RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(db, flags, RedisCommand.GET, key); + var msg = Message.Create(db, flags, RedisCommand.GET, key, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } public RedisChannel[] SubscriptionChannels(RedisChannel pattern = default, CommandFlags flags = CommandFlags.None) { - var msg = pattern.IsNullOrEmpty ? Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS) - : Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS, pattern); + var msg = pattern.IsNullOrEmpty ? Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS, this.GetEffectiveCancellationToken()) + : Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS, pattern, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisChannelArrayLiteral, defaultValue: Array.Empty()); } public Task SubscriptionChannelsAsync(RedisChannel pattern = default, CommandFlags flags = CommandFlags.None) { - var msg = pattern.IsNullOrEmpty ? Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS) - : Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS, pattern); + var msg = pattern.IsNullOrEmpty ? Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS, this.GetEffectiveCancellationToken()) + : Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.CHANNELS, pattern, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisChannelArrayLiteral, defaultValue: Array.Empty()); } public long SubscriptionPatternCount(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMPAT); + var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMPAT, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task SubscriptionPatternCountAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMPAT); + var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMPAT, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } public long SubscriptionSubscriberCount(RedisChannel channel, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); + var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.PubSubNumSub); } public Task SubscriptionSubscriberCountAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); + var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.PubSubNumSub); } public void SwapDatabases(int first, int second, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SWAPDB, first, second); + var msg = Message.Create(-1, flags, RedisCommand.SWAPDB, first, second, this.GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task SwapDatabasesAsync(int first, int second, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SWAPDB, first, second); + var msg = Message.Create(-1, flags, RedisCommand.SWAPDB, first, second, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } public DateTime Time(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.TIME); + var msg = Message.Create(-1, flags, RedisCommand.TIME, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.DateTime); } public Task TimeAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.TIME); + var msg = Message.Create(-1, flags, RedisCommand.TIME, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DateTime); } - internal static Message CreateReplicaOfMessage(ServerEndPoint sendMessageTo, EndPoint? primaryEndpoint, CommandFlags flags = CommandFlags.None) + internal static Message CreateReplicaOfMessage(ServerEndPoint sendMessageTo, EndPoint? primaryEndpoint, CommandFlags flags, CancellationToken cancellationToken) { RedisValue host, port; if (primaryEndpoint == null) @@ -620,16 +623,16 @@ internal static Message CreateReplicaOfMessage(ServerEndPoint sendMessageTo, End throw new NotSupportedException("Unknown endpoint type: " + primaryEndpoint.GetType().Name); } } - return Message.Create(-1, flags, sendMessageTo.GetFeatures().ReplicaCommands ? RedisCommand.REPLICAOF : RedisCommand.SLAVEOF, host, port); + return Message.Create(-1, flags, sendMessageTo.GetFeatures().ReplicaCommands ? RedisCommand.REPLICAOF : RedisCommand.SLAVEOF, host, port, cancellationToken); } private Message? GetTiebreakerRemovalMessage() { - var configuration = multiplexer.RawConfig; + var configuration = Multiplexer.RawConfig; - if (configuration.TryGetTieBreaker(out var tieBreakerKey) && multiplexer.CommandMap.IsAvailable(RedisCommand.DEL)) + if (configuration.TryGetTieBreaker(out var tieBreakerKey) && Multiplexer.CommandMap.IsAvailable(RedisCommand.DEL)) { - var msg = Message.Create(0, CommandFlags.FireAndForget | CommandFlags.NoRedirect, RedisCommand.DEL, tieBreakerKey); + var msg = Message.Create(0, CommandFlags.FireAndForget | CommandFlags.NoRedirect, RedisCommand.DEL, tieBreakerKey, GetEffectiveCancellationToken()); msg.SetInternalCall(); return msg; } @@ -639,10 +642,10 @@ internal static Message CreateReplicaOfMessage(ServerEndPoint sendMessageTo, End private Message? GetConfigChangeMessage() { // attempt to broadcast a reconfigure message to anybody listening to this server - var channel = multiplexer.ConfigurationChangedChannel; - if (channel != null && multiplexer.CommandMap.IsAvailable(RedisCommand.PUBLISH)) + var channel = Multiplexer.ConfigurationChangedChannel; + if (channel != null && Multiplexer.CommandMap.IsAvailable(RedisCommand.PUBLISH)) { - var msg = Message.Create(-1, CommandFlags.FireAndForget | CommandFlags.NoRedirect, RedisCommand.PUBLISH, (RedisValue)channel, RedisLiterals.Wildcard); + var msg = Message.Create(-1, CommandFlags.FireAndForget | CommandFlags.NoRedirect, RedisCommand.PUBLISH, (RedisValue)channel, RedisLiterals.Wildcard, GetEffectiveCancellationToken()); msg.SetInternalCall(); return msg; } @@ -656,15 +659,15 @@ internal override Task ExecuteAsync(Message? message, ResultProcessor? FixFlags(message, server); if (!server.IsConnected) { - if (message == null) return CompletedTask.FromDefault(defaultValue, asyncState); + if (message == null) return CompletedTask.FromDefault(defaultValue, AsyncState); if (message.IsFireAndForget) return CompletedTask.FromDefault(defaultValue, null); // F+F explicitly does not get async-state // After the "don't care" cases above, if we can't queue then it's time to error - otherwise call through to queuing. - if (!multiplexer.RawConfig.BacklogPolicy.QueueWhileDisconnected) + if (!Multiplexer.RawConfig.BacklogPolicy.QueueWhileDisconnected) { // no need to deny exec-sync here; will be complete before they see if - var tcs = TaskSource.Create(asyncState); - ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(multiplexer, message, server)); + var tcs = TaskSource.Create(AsyncState); + ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(Multiplexer, message, server)); return tcs.Task; } } @@ -678,15 +681,15 @@ internal override Task ExecuteAsync(Message? message, ResultProcessor? FixFlags(message, server); if (!server.IsConnected) { - if (message == null) return CompletedTask.Default(asyncState); + if (message == null) return CompletedTask.Default(AsyncState); if (message.IsFireAndForget) return CompletedTask.Default(null); // F+F explicitly does not get async-state // After the "don't care" cases above, if we can't queue then it's time to error - otherwise call through to queuing. - if (!multiplexer.RawConfig.BacklogPolicy.QueueWhileDisconnected) + if (!Multiplexer.RawConfig.BacklogPolicy.QueueWhileDisconnected) { // no need to deny exec-sync here; will be complete before they see if - var tcs = TaskSource.Create(asyncState); - ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(multiplexer, message, server)); + var tcs = TaskSource.Create(AsyncState); + ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(Multiplexer, message, server)); return tcs.Task; } } @@ -704,9 +707,9 @@ internal override Task ExecuteAsync(Message? message, ResultProcessor? if (message == null || message.IsFireAndForget) return defaultValue; // After the "don't care" cases above, if we can't queue then it's time to error - otherwise call through to queuing. - if (!multiplexer.RawConfig.BacklogPolicy.QueueWhileDisconnected) + if (!Multiplexer.RawConfig.BacklogPolicy.QueueWhileDisconnected) { - throw ExceptionFactory.NoConnectionAvailable(multiplexer, message, server); + throw ExceptionFactory.NoConnectionAvailable(Multiplexer, message, server); } } return base.ExecuteSync(message, processor, server, defaultValue); @@ -736,7 +739,7 @@ public void ReplicaOf(EndPoint master, CommandFlags flags = CommandFlags.None) server.GetBridge(tieBreakerRemoval)?.TryWriteSync(tieBreakerRemoval, server.IsReplica); } - var replicaOfMsg = CreateReplicaOfMessage(server, master, flags); + var replicaOfMsg = CreateReplicaOfMessage(server, master, flags, GetEffectiveCancellationToken()); ExecuteSync(replicaOfMsg, ResultProcessor.DemandOK); // attempt to broadcast a reconfigure message to anybody listening to this server @@ -768,7 +771,7 @@ public async Task ReplicaOfAsync(EndPoint? master, CommandFlags flags = CommandF catch { } } - var msg = CreateReplicaOfMessage(server, master, flags); + var msg = CreateReplicaOfMessage(server, master, flags, GetEffectiveCancellationToken()); await ExecuteAsync(msg, ResultProcessor.DemandOK).ForAwait(); // attempt to broadcast a reconfigure message to anybody listening to this server @@ -799,15 +802,19 @@ private static void FixFlags(Message? message, ServerEndPoint server) } } - private static Message GetSaveMessage(SaveType type, CommandFlags flags = CommandFlags.None) => type switch + private Message GetSaveMessage(SaveType type, CommandFlags flags = CommandFlags.None) { - SaveType.BackgroundRewriteAppendOnlyFile => Message.Create(-1, flags, RedisCommand.BGREWRITEAOF), - SaveType.BackgroundSave => Message.Create(-1, flags, RedisCommand.BGSAVE), + var cmd = type switch + { + SaveType.BackgroundRewriteAppendOnlyFile => RedisCommand.BGREWRITEAOF, + SaveType.BackgroundSave => RedisCommand.BGSAVE, #pragma warning disable CS0618 // Type or member is obsolete - SaveType.ForegroundSave => Message.Create(-1, flags, RedisCommand.SAVE), + SaveType.ForegroundSave => RedisCommand.SAVE, #pragma warning restore CS0618 - _ => throw new ArgumentOutOfRangeException(nameof(type)), - }; + _ => throw new ArgumentOutOfRangeException(nameof(type)), + }; + return Message.Create(-1, flags, cmd, GetEffectiveCancellationToken()); + } private static ResultProcessor GetSaveResultProcessor(SaveType type) => type switch { @@ -860,28 +867,28 @@ public KeysScanEnumerable(RedisServer server, int db, in RedisValue pattern, int this.pattern = pattern; } - private protected override Message CreateMessage(in RedisValue cursor) + private protected override Message CreateMessage(in RedisValue cursor, CancellationToken cancellationToken) { if (CursorUtils.IsNil(pattern)) { if (pageSize == CursorUtils.DefaultRedisPageSize) { - return Message.Create(db, flags, RedisCommand.SCAN, cursor); + return Message.Create(db, flags, RedisCommand.SCAN, cursor, cancellationToken); } else { - return Message.Create(db, flags, RedisCommand.SCAN, cursor, RedisLiterals.COUNT, pageSize); + return Message.Create(db, flags, RedisCommand.SCAN, cursor, RedisLiterals.COUNT, pageSize, cancellationToken); } } else { if (pageSize == CursorUtils.DefaultRedisPageSize) { - return Message.Create(db, flags, RedisCommand.SCAN, cursor, RedisLiterals.MATCH, pattern); + return Message.Create(db, flags, RedisCommand.SCAN, cursor, RedisLiterals.MATCH, pattern, cancellationToken); } else { - return Message.Create(db, flags, RedisCommand.SCAN, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize); + return Message.Create(db, flags, RedisCommand.SCAN, cursor, RedisLiterals.MATCH, pattern, RedisLiterals.COUNT, pageSize, cancellationToken); } } } @@ -927,73 +934,73 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes public EndPoint? SentinelGetMasterAddressByName(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.GETMASTERADDRBYNAME, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.GETMASTERADDRBYNAME, (RedisValue)serviceName, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.SentinelPrimaryEndpoint); } public Task SentinelGetMasterAddressByNameAsync(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.GETMASTERADDRBYNAME, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.GETMASTERADDRBYNAME, (RedisValue)serviceName, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.SentinelPrimaryEndpoint); } public EndPoint[] SentinelGetSentinelAddresses(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SENTINELS, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SENTINELS, (RedisValue)serviceName, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.SentinelAddressesEndPoints, defaultValue: Array.Empty()); } public Task SentinelGetSentinelAddressesAsync(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SENTINELS, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SENTINELS, (RedisValue)serviceName, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.SentinelAddressesEndPoints, defaultValue: Array.Empty()); } public EndPoint[] SentinelGetReplicaAddresses(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, Features.ReplicaCommands ? RedisLiterals.REPLICAS : RedisLiterals.SLAVES, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, Features.ReplicaCommands ? RedisLiterals.REPLICAS : RedisLiterals.SLAVES, (RedisValue)serviceName, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.SentinelAddressesEndPoints, defaultValue: Array.Empty()); } public Task SentinelGetReplicaAddressesAsync(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, Features.ReplicaCommands ? RedisLiterals.REPLICAS : RedisLiterals.SLAVES, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, Features.ReplicaCommands ? RedisLiterals.REPLICAS : RedisLiterals.SLAVES, (RedisValue)serviceName, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.SentinelAddressesEndPoints, defaultValue: Array.Empty()); } public KeyValuePair[] SentinelMaster(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTER, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTER, (RedisValue)serviceName, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.StringPairInterleaved, defaultValue: Array.Empty>()); } public Task[]> SentinelMasterAsync(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTER, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTER, (RedisValue)serviceName, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.StringPairInterleaved, defaultValue: Array.Empty>()); } public void SentinelFailover(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.FAILOVER, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.FAILOVER, (RedisValue)serviceName, GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task SentinelFailoverAsync(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.FAILOVER, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.FAILOVER, (RedisValue)serviceName, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } public KeyValuePair[][] SentinelMasters(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTERS); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTERS, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.SentinelArrayOfArrays, defaultValue: Array.Empty[]>()); } public Task[][]> SentinelMastersAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTERS); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTERS, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.SentinelArrayOfArrays, defaultValue: Array.Empty[]>()); } @@ -1003,7 +1010,7 @@ KeyValuePair[][] IServer.SentinelSlaves(string serviceName, Comm public KeyValuePair[][] SentinelReplicas(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, Features.ReplicaCommands ? RedisLiterals.REPLICAS : RedisLiterals.SLAVES, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, Features.ReplicaCommands ? RedisLiterals.REPLICAS : RedisLiterals.SLAVES, (RedisValue)serviceName, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.SentinelArrayOfArrays, defaultValue: Array.Empty[]>()); } @@ -1013,19 +1020,19 @@ Task[][]> IServer.SentinelSlavesAsync(string servic public Task[][]> SentinelReplicasAsync(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, Features.ReplicaCommands ? RedisLiterals.REPLICAS : RedisLiterals.SLAVES, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, Features.ReplicaCommands ? RedisLiterals.REPLICAS : RedisLiterals.SLAVES, (RedisValue)serviceName, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.SentinelArrayOfArrays, defaultValue: Array.Empty[]>()); } public KeyValuePair[][] SentinelSentinels(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SENTINELS, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SENTINELS, (RedisValue)serviceName, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.SentinelArrayOfArrays, defaultValue: Array.Empty[]>()); } public Task[][]> SentinelSentinelsAsync(string serviceName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SENTINELS, (RedisValue)serviceName); + var msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.SENTINELS, (RedisValue)serviceName, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.SentinelArrayOfArrays, defaultValue: Array.Empty[]>()); } @@ -1033,7 +1040,7 @@ public Task[][]> SentinelSentinelsAsync(string serv public RedisResult Execute(string command, ICollection args, CommandFlags flags = CommandFlags.None) { - var msg = new RedisDatabase.ExecuteMessage(multiplexer?.CommandMap, -1, flags, command, args); + var msg = new RedisDatabase.ExecuteMessage(Multiplexer?.CommandMap, -1, flags, command, args, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } @@ -1041,7 +1048,7 @@ public RedisResult Execute(string command, ICollection args, CommandFlag public Task ExecuteAsync(string command, ICollection args, CommandFlags flags = CommandFlags.None) { - var msg = new RedisDatabase.ExecuteMessage(multiplexer?.CommandMap, -1, flags, command, args); + var msg = new RedisDatabase.ExecuteMessage(Multiplexer?.CommandMap, -1, flags, command, args, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } @@ -1052,31 +1059,32 @@ public Task ExecuteAsync(string command, ICollection args, public Task LatencyDoctorAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.DOCTOR); + var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.DOCTOR, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.String!, defaultValue: string.Empty); } public string LatencyDoctor(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.DOCTOR); + var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.DOCTOR, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.String, defaultValue: string.Empty); } - private static Message LatencyResetCommand(string[]? eventNames, CommandFlags flags) + private Message LatencyResetCommand(string[]? eventNames, CommandFlags flags) { if (eventNames == null) eventNames = Array.Empty(); + var cancellationToken = GetEffectiveCancellationToken(); switch (eventNames.Length) { case 0: - return Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.RESET); + return Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.RESET, cancellationToken); case 1: - return Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.RESET, (RedisValue)eventNames[0]); + return Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.RESET, (RedisValue)eventNames[0], cancellationToken); default: var arr = new RedisValue[eventNames.Length + 1]; arr[0] = RedisLiterals.RESET; for (int i = 0; i < eventNames.Length; i++) arr[i + 1] = eventNames[i]; - return Message.Create(-1, flags, RedisCommand.LATENCY, arr); + return Message.Create(-1, flags, RedisCommand.LATENCY, arr, cancellationToken); } } public Task LatencyResetAsync(string[]? eventNames = null, CommandFlags flags = CommandFlags.None) @@ -1093,73 +1101,73 @@ public long LatencyReset(string[]? eventNames = null, CommandFlags flags = Comma public Task LatencyHistoryAsync(string eventName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.HISTORY, (RedisValue)eventName); + var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.HISTORY, (RedisValue)eventName, GetEffectiveCancellationToken()); return ExecuteAsync(msg, LatencyHistoryEntry.ToArray, defaultValue: Array.Empty()); } public LatencyHistoryEntry[] LatencyHistory(string eventName, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.HISTORY, (RedisValue)eventName); + var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.HISTORY, (RedisValue)eventName, GetEffectiveCancellationToken()); return ExecuteSync(msg, LatencyHistoryEntry.ToArray, defaultValue: Array.Empty()); } public Task LatencyLatestAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.LATEST); + var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.LATEST, GetEffectiveCancellationToken()); return ExecuteAsync(msg, LatencyLatestEntry.ToArray, defaultValue: Array.Empty()); } public LatencyLatestEntry[] LatencyLatest(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.LATEST); + var msg = Message.Create(-1, flags, RedisCommand.LATENCY, RedisLiterals.LATEST, GetEffectiveCancellationToken()); return ExecuteSync(msg, LatencyLatestEntry.ToArray, defaultValue: Array.Empty()); } public Task MemoryDoctorAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.DOCTOR); + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.DOCTOR, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.String!, defaultValue: string.Empty); } public string MemoryDoctor(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.DOCTOR); + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.DOCTOR, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.String, defaultValue: string.Empty); } public Task MemoryPurgeAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.PURGE); + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.PURGE, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } public void MemoryPurge(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.PURGE); + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.PURGE, GetEffectiveCancellationToken()); ExecuteSync(msg, ResultProcessor.DemandOK); } public Task MemoryAllocatorStatsAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.MALLOC_STATS); + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.MALLOC_STATS, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.String); } public string? MemoryAllocatorStats(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.MALLOC_STATS); + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.MALLOC_STATS, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.String); } public Task MemoryStatsAsync(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.STATS); + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.STATS, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullArray); } public RedisResult MemoryStats(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.STATS); + var msg = Message.Create(-1, flags, RedisCommand.MEMORY, RedisLiterals.STATS, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullArray); } } diff --git a/src/StackExchange.Redis/RedisSubscriber.cs b/src/StackExchange.Redis/RedisSubscriber.cs index b641baf05..409f15dc2 100644 --- a/src/StackExchange.Redis/RedisSubscriber.cs +++ b/src/StackExchange.Redis/RedisSubscriber.cs @@ -180,10 +180,8 @@ public Subscription(CommandFlags flags) /// /// Gets the configured (P)SUBSCRIBE or (P)UNSUBSCRIBE for an action. /// - internal Message GetMessage(RedisChannel channel, SubscriptionAction action, CommandFlags flags, bool internalCall) + internal Message GetMessage(RedisChannel channel, SubscriptionAction action, CommandFlags flags, bool internalCall, CancellationToken cancellationToken) { - var isPattern = channel.IsPattern; - var isSharded = channel.IsSharded; var command = action switch { SubscriptionAction.Subscribe => channel.Options switch @@ -204,7 +202,7 @@ internal Message GetMessage(RedisChannel channel, SubscriptionAction action, Com }; // TODO: Consider flags here - we need to pass Fire and Forget, but don't want to intermingle Primary/Replica - var msg = Message.Create(-1, Flags | flags, command, channel); + var msg = Message.Create(-1, Flags | flags, command, channel, cancellationToken); msg.SetForSubscriptionBridge(); if (internalCall) { @@ -278,7 +276,12 @@ internal void GetSubscriberCounts(out int handlers, out int queues) else { handlers = 0; - foreach (var sub in tmp.AsEnumerable()) { handlers++; } + // ReSharper disable once GenericEnumeratorNotDisposed // no-op + var iter = tmp.AsEnumerable().GetEnumerator(); + while (iter.MoveNext()) + { + handlers++; + } } } @@ -313,14 +316,14 @@ internal RedisSubscriber(ConnectionMultiplexer multiplexer, object? asyncState) public EndPoint? IdentifyEndpoint(RedisChannel channel, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); + var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel, GetEffectiveCancellationToken()); msg.SetInternalCall(); return ExecuteSync(msg, ResultProcessor.ConnectionIdentity); } public Task IdentifyEndpointAsync(RedisChannel channel, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel); + var msg = Message.Create(-1, flags, RedisCommand.PUBSUB, RedisLiterals.NUMSUB, channel, GetEffectiveCancellationToken()); msg.SetInternalCall(); return ExecuteAsync(msg, ResultProcessor.ConnectionIdentity); } @@ -331,8 +334,8 @@ internal RedisSubscriber(ConnectionMultiplexer multiplexer, object? asyncState) /// public bool IsConnected(RedisChannel channel = default) { - var server = multiplexer.GetSubscribedServer(channel) ?? multiplexer.SelectServer(RedisCommand.SUBSCRIBE, CommandFlags.DemandMaster, channel); - return server?.IsConnected == true && server.IsSubscriberConnected; + var server = Multiplexer.GetSubscribedServer(channel) ?? Multiplexer.SelectServer(RedisCommand.SUBSCRIBE, CommandFlags.DemandMaster, channel); + return server is { IsConnected: true, IsSubscriberConnected: true }; } public override TimeSpan Ping(CommandFlags flags = CommandFlags.None) @@ -350,22 +353,23 @@ public override Task PingAsync(CommandFlags flags = CommandFlags.None) private Message CreatePingMessage(CommandFlags flags) { bool usePing = false; - if (multiplexer.CommandMap.IsAvailable(RedisCommand.PING)) + if (Multiplexer.CommandMap.IsAvailable(RedisCommand.PING)) { try { usePing = GetFeatures(default, flags, RedisCommand.PING, out _).PingOnSubscriber; } + // ReSharper disable once EmptyGeneralCatchClause - best efforts catch { } } Message msg; if (usePing) { - msg = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.PING); + msg = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.PING, default, GetEffectiveCancellationToken()); } else { // can't use regular PING, but we can unsubscribe from something random that we weren't even subscribed to... - RedisValue channel = multiplexer.UniqueId; - msg = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.UNSUBSCRIBE, channel); + RedisValue channel = Multiplexer.UniqueId; + msg = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.UNSUBSCRIBE, channel, GetEffectiveCancellationToken()); } // Ensure the ping is sent over the intended subscriber connection, which wouldn't happen in GetBridge() by default with PING; msg.SetForSubscriptionBridge(); @@ -383,14 +387,14 @@ private static void ThrowIfNull(in RedisChannel channel) public long Publish(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) { ThrowIfNull(channel); - var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message); + var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } public Task PublishAsync(RedisChannel channel, RedisValue message, CommandFlags flags = CommandFlags.None) { ThrowIfNull(channel); - var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message); + var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -409,7 +413,7 @@ public bool Subscribe(RedisChannel channel, Action? ha ThrowIfNull(channel); if (handler == null && queue == null) { return true; } - var sub = multiplexer.GetOrAddSubscription(channel, flags); + var sub = Multiplexer.GetOrAddSubscription(channel, flags); sub.Add(handler, queue); return EnsureSubscribedToServer(sub, channel, flags, false); } @@ -420,8 +424,8 @@ internal bool EnsureSubscribedToServer(Subscription sub, RedisChannel channel, C // TODO: Cleanup old hangers here? sub.SetCurrentServer(null); // we're not appropriately connected, so blank it out for eligible reconnection - var message = sub.GetMessage(channel, SubscriptionAction.Subscribe, flags, internalCall); - var selected = multiplexer.SelectServer(message); + var message = sub.GetMessage(channel, SubscriptionAction.Subscribe, flags, internalCall, GetEffectiveCancellationToken()); + var selected = Multiplexer.SelectServer(message); return ExecuteSync(message, sub.Processor, selected); } @@ -440,7 +444,7 @@ public Task SubscribeAsync(RedisChannel channel, Action.Default(null); } - var sub = multiplexer.GetOrAddSubscription(channel, flags); + var sub = Multiplexer.GetOrAddSubscription(channel, flags); sub.Add(handler, queue); return EnsureSubscribedToServerAsync(sub, channel, flags, false); } @@ -451,12 +455,12 @@ public Task EnsureSubscribedToServerAsync(Subscription sub, RedisChannel c // TODO: Cleanup old hangers here? sub.SetCurrentServer(null); // we're not appropriately connected, so blank it out for eligible reconnection - var message = sub.GetMessage(channel, SubscriptionAction.Subscribe, flags, internalCall); - var selected = multiplexer.SelectServer(message); + var message = sub.GetMessage(channel, SubscriptionAction.Subscribe, flags, internalCall, GetEffectiveCancellationToken()); + var selected = Multiplexer.SelectServer(message); return ExecuteAsync(message, sub.Processor, selected); } - public EndPoint? SubscribedEndpoint(RedisChannel channel) => multiplexer.GetSubscribedServer(channel)?.EndPoint; + public EndPoint? SubscribedEndpoint(RedisChannel channel) => Multiplexer.GetSubscribedServer(channel)?.EndPoint; void ISubscriber.Unsubscribe(RedisChannel channel, Action? handler, CommandFlags flags) => Unsubscribe(channel, handler, null, flags); @@ -465,6 +469,7 @@ public bool Unsubscribe(in RedisChannel channel, Action UnsubscribeAsync(in RedisChannel channel, Action.Default(asyncState); + ? UnsubscribeFromServerAsync(sub, channel, flags, AsyncState, false) + : CompletedTask.Default(AsyncState); } private Task UnsubscribeFromServerAsync(Subscription sub, RedisChannel channel, CommandFlags flags, object? asyncState, bool internalCall) { - if (sub.GetCurrentServer() is ServerEndPoint oldOwner) + if (sub.GetCurrentServer() is { } oldOwner) { - var message = sub.GetMessage(channel, SubscriptionAction.Unsubscribe, flags, internalCall); - return multiplexer.ExecuteAsyncImpl(message, sub.Processor, asyncState, oldOwner); + var message = sub.GetMessage(channel, SubscriptionAction.Unsubscribe, flags, internalCall, GetEffectiveCancellationToken()); + return Multiplexer.ExecuteAsyncImpl(message, sub.Processor, asyncState, oldOwner); } return CompletedTask.FromResult(true, asyncState); } @@ -509,19 +514,19 @@ private Task UnsubscribeFromServerAsync(Subscription sub, RedisChannel cha private bool UnregisterSubscription(in RedisChannel channel, Action? handler, ChannelMessageQueue? queue, [NotNullWhen(true)] out Subscription? sub) { ThrowIfNull(channel); - if (multiplexer.TryGetSubscription(channel, out sub)) + if (Multiplexer.TryGetSubscription(channel, out sub)) { if (handler == null & queue == null) { // This was a blanket wipe, so clear it completely sub.MarkCompleted(); - multiplexer.TryRemoveSubscription(channel, out _); + Multiplexer.TryRemoveSubscription(channel, out _); return true; } else if (sub.Remove(handler, queue)) { // Or this was the last handler and/or queue, which also means unsubscribe - multiplexer.TryRemoveSubscription(channel, out _); + Multiplexer.TryRemoveSubscription(channel, out _); return true; } } @@ -532,7 +537,7 @@ private bool UnregisterSubscription(in RedisChannel channel, Action.Default(asyncState); + return last ?? CompletedTask.Default(AsyncState); } } } diff --git a/src/StackExchange.Redis/RedisTransaction.cs b/src/StackExchange.Redis/RedisTransaction.cs index 04d7293ac..36b0a211f 100644 --- a/src/StackExchange.Redis/RedisTransaction.cs +++ b/src/StackExchange.Redis/RedisTransaction.cs @@ -12,10 +12,10 @@ internal class RedisTransaction : RedisDatabase, ITransaction private List? _pending; private object SyncLock => this; - public RedisTransaction(RedisDatabase wrapped, object? asyncState) : base(wrapped.multiplexer, wrapped.Database, asyncState ?? wrapped.AsyncState) + public RedisTransaction(RedisDatabase wrapped, object? asyncState) : base(wrapped.Multiplexer, wrapped.Database, asyncState ?? wrapped.AsyncState) { // need to check we can reliably do this... - var commandMap = multiplexer.CommandMap; + var commandMap = Multiplexer.CommandMap; commandMap.AssertAvailable(RedisCommand.MULTI); commandMap.AssertAvailable(RedisCommand.EXEC); commandMap.AssertAvailable(RedisCommand.DISCARD); @@ -25,7 +25,7 @@ public ConditionResult AddCondition(Condition condition) { if (condition == null) throw new ArgumentNullException(nameof(condition)); - var commandMap = multiplexer.CommandMap; + var commandMap = Multiplexer.CommandMap; lock (SyncLock) { if (_conditions == null) @@ -36,7 +36,7 @@ public ConditionResult AddCondition(Condition condition) _conditions = new List(); } condition.CheckCommands(commandMap); - var result = new ConditionResult(condition); + var result = new ConditionResult(condition, GetEffectiveCancellationToken()); _conditions.Add(result); return result; } @@ -58,10 +58,10 @@ public Task ExecuteAsync(CommandFlags flags) internal override Task ExecuteAsync(Message? message, ResultProcessor? processor, T defaultValue, ServerEndPoint? server = null) { - if (message == null) return CompletedTask.FromDefault(defaultValue, asyncState); - multiplexer.CheckMessage(message); + if (message == null) return CompletedTask.FromDefault(defaultValue, ((RedisBase)this).AsyncState); + Multiplexer.CheckMessage(message); - multiplexer.Trace("Wrapping " + message.Command, "Transaction"); + Multiplexer.Trace("Wrapping " + message.Command, "Transaction"); // prepare the inner command as a task Task task; if (message.IsFireAndForget) @@ -70,7 +70,7 @@ internal override Task ExecuteAsync(Message? message, ResultProcessor? } else { - var source = TaskResultBox.Create(out var tcs, asyncState); + var source = TaskResultBox.Create(message.CancellationToken, out var tcs, ((RedisBase)this).AsyncState); message.SetSource(source, processor); task = tcs.Task; } @@ -82,10 +82,10 @@ internal override Task ExecuteAsync(Message? message, ResultProcessor? internal override Task ExecuteAsync(Message? message, ResultProcessor? processor, ServerEndPoint? server = null) where T : default { - if (message == null) return CompletedTask.Default(asyncState); - multiplexer.CheckMessage(message); + if (message == null) return CompletedTask.Default(((RedisBase)this).AsyncState); + Multiplexer.CheckMessage(message); - multiplexer.Trace("Wrapping " + message.Command, "Transaction"); + Multiplexer.Trace("Wrapping " + message.Command, "Transaction"); // prepare the inner command as a task Task task; if (message.IsFireAndForget) @@ -94,7 +94,7 @@ internal override Task ExecuteAsync(Message? message, ResultProcessor? } else { - var source = TaskResultBox.Create(out var tcs, asyncState); + var source = TaskResultBox.Create(message.CancellationToken, out var tcs, ((RedisBase)this).AsyncState); message.SetSource(source!, processor); task = tcs.Task; } @@ -108,7 +108,7 @@ private void QueueMessage(Message message) { // prepare an outer-command that decorates that, but expects QUEUED var queued = new QueuedMessage(message); - var wasQueued = SimpleResultBox.Create(); + var wasQueued = SimpleResultBox.Create(message.CancellationToken); queued.SetSource(wasQueued, QueuedProcessor.Default); // store it, and return the task of the *outer* command @@ -121,7 +121,7 @@ private void QueueMessage(Message message) case RedisCommand.UNKNOWN: case RedisCommand.EVAL: case RedisCommand.EVALSHA: - var server = multiplexer.SelectServer(message); + var server = Multiplexer.SelectServer(message); if (server != null && server.SupportsDatabases) { // people can do very naughty things in an EVAL @@ -129,7 +129,7 @@ private void QueueMessage(Message message) // think it should be! var sel = PhysicalConnection.GetSelectDatabaseCommand(message.Db); queued = new QueuedMessage(sel); - wasQueued = SimpleResultBox.Create(); + wasQueued = SimpleResultBox.Create(message.CancellationToken); queued.SetSource(wasQueued, QueuedProcessor.Default); _pending.Add(queued); } @@ -163,10 +163,10 @@ private void QueueMessage(Message message) return null; // they won't notice if we don't do anything... } processor = ResultProcessor.DemandPONG; - return Message.Create(-1, flags, RedisCommand.PING); + return Message.Create(-1, flags, RedisCommand.PING, GetEffectiveCancellationToken()); } processor = TransactionProcessor.Default; - return new TransactionMessage(Database, flags, cond, work); + return new TransactionMessage(Database, flags, cond, work, Multiplexer.GetEffectiveCancellationToken()); } private class QueuedMessage : Message @@ -174,7 +174,7 @@ private class QueuedMessage : Message public Message Wrapped { get; } private volatile bool wasQueued; - public QueuedMessage(Message message) : base(message.Db, message.Flags | CommandFlags.NoRedirect, message.Command) + public QueuedMessage(Message message) : base(message.Db, message.Flags | CommandFlags.NoRedirect, message.Command, message.CancellationToken) { message.SetNoRedirect(); Wrapped = message; @@ -222,8 +222,8 @@ private class TransactionMessage : Message, IMultiMessage public QueuedMessage[] InnerOperations { get; } - public TransactionMessage(int db, CommandFlags flags, List? conditions, List? operations) - : base(db, flags, RedisCommand.EXEC) + public TransactionMessage(int db, CommandFlags flags, List? conditions, List? operations, CancellationToken cancellationToken) + : base(db, flags, RedisCommand.EXEC, cancellationToken) { InnerOperations = (operations?.Count > 0) ? operations.ToArray() : Array.Empty(); this.conditions = (conditions?.Count > 0) ? conditions.ToArray() : Array.Empty(); @@ -347,7 +347,7 @@ public IEnumerable GetMessages(PhysicalConnection connection) if (!IsAborted) { multiplexer.Trace("Beginning transaction"); - yield return Message.Create(-1, CommandFlags.None, RedisCommand.MULTI); + yield return Message.Create(-1, CommandFlags.None, RedisCommand.MULTI, multiplexer.GetEffectiveCancellationToken()); sb.AppendLine("issued MULTI"); } diff --git a/src/StackExchange.Redis/ResultBox.cs b/src/StackExchange.Redis/ResultBox.cs index 20b76ba15..bb30e080c 100644 --- a/src/StackExchange.Redis/ResultBox.cs +++ b/src/StackExchange.Redis/ResultBox.cs @@ -8,9 +8,10 @@ internal interface IResultBox { bool IsAsync { get; } bool IsFaulted { get; } + CancellationToken CancellationToken { get; } // as explicitly specified by the caller void SetException(Exception ex); void ActivateContinuations(); - void Cancel(); + void Cancel(CancellationToken cancellationToken); } internal interface IResultBox : IResultBox { @@ -20,12 +21,18 @@ internal interface IResultBox : IResultBox internal abstract class SimpleResultBox : IResultBox { + public CancellationToken CancellationToken { get; protected set; } + private volatile Exception? _exception; bool IResultBox.IsAsync => false; bool IResultBox.IsFaulted => _exception != null; void IResultBox.SetException(Exception exception) => _exception = exception ?? CancelledException; - void IResultBox.Cancel() => _exception = CancelledException; + + void IResultBox.Cancel(CancellationToken cancellationToken) => + _exception = cancellationToken.IsCancellationRequested + ? new OperationCanceledException(cancellationToken) // for sync, need to capture this eagerly + : GetCancelledException(CancellationToken, cancellationToken); void IResultBox.ActivateContinuations() { @@ -40,7 +47,14 @@ void IResultBox.ActivateContinuations() // that call Cancel are transactions etc - TCS-based, and we detect // that and use TrySetCanceled instead // about any confusion in stack-trace - internal static readonly Exception CancelledException = new TaskCanceledException(); + private static readonly Exception CancelledException = new TaskCanceledException(); + + internal static Exception GetCancelledException(CancellationToken message, CancellationToken specified) + { + if (message == specified) return CancelledException; // indicates to assume message-based + if (specified.IsCancellationRequested) return new OperationCanceledException(specified); + return CancelledException; // indicates to assume message-based + } protected Exception? Exception { @@ -57,11 +71,12 @@ private SimpleResultBox() { } [ThreadStatic] private static SimpleResultBox? _perThreadInstance; - public static IResultBox Create() => new SimpleResultBox(); - public static IResultBox Get() // includes recycled boxes; used from sync, so makes re-use easy + public static IResultBox Create(CancellationToken cancellationToken) => new SimpleResultBox { CancellationToken = cancellationToken }; + public static IResultBox Get(CancellationToken cancellationToken) // includes recycled boxes; used from sync, so makes re-use easy { var obj = _perThreadInstance ?? new SimpleResultBox(); _perThreadInstance = null; // in case of oddness; only set back when recycled + obj.CancellationToken = cancellationToken; return obj; } @@ -76,6 +91,7 @@ private SimpleResultBox() { } Exception = null; _value = default!; _perThreadInstance = this; + CancellationToken = CancellationToken.None; } return value; } @@ -87,18 +103,25 @@ internal sealed class TaskResultBox : TaskCompletionSource, IResultBox // I say: no; we can't set *immediately* due to thread-theft etc, hence // the fun TryComplete indirection - so we need somewhere to buffer them private volatile Exception? _exception; + private readonly CancellationToken _cancellationToken; private T _value = default!; - private TaskResultBox(object? asyncState, TaskCreationOptions creationOptions) : base(asyncState, creationOptions) - { } + private TaskResultBox(CancellationToken cancellationToken, object? asyncState, TaskCreationOptions creationOptions) : base( + asyncState, creationOptions) + { + _cancellationToken = cancellationToken; + } + + CancellationToken IResultBox.CancellationToken => _cancellationToken; bool IResultBox.IsAsync => true; bool IResultBox.IsFaulted => _exception != null; - void IResultBox.Cancel() => _exception = SimpleResultBox.CancelledException; + void IResultBox.Cancel(CancellationToken cancellationToken) => + _exception = SimpleResultBox.GetCancelledException(_cancellationToken, cancellationToken); - void IResultBox.SetException(Exception ex) => _exception = ex ?? SimpleResultBox.CancelledException; + void IResultBox.SetException(Exception ex) => _exception = ex ?? SimpleResultBox.GetCancelledException(_cancellationToken, CancellationToken.None); void IResultBox.SetResult(T value) => _value = value; @@ -124,21 +147,39 @@ private void ActivateContinuationsImpl() var val = _value; var ex = _exception; - if (ex == null) + if (ex is null) { TrySetResult(val); } else { - if (ex is TaskCanceledException) TrySetCanceled(); - else TrySetException(ex); + switch (ex) + { + case OperationCanceledException { CancellationToken.IsCancellationRequested: true } oc: + TrySetCanceled(oc.CancellationToken); + break; + case OperationCanceledException: // includes TaskCanceledException without token + if (_cancellationToken.IsCancellationRequested) + { + TrySetCanceled(_cancellationToken); + } + else + { + TrySetCanceled(); + } + + break; + default: + TrySetException(ex); + break; + } var task = Task; GC.KeepAlive(task.Exception); // mark any exception as observed GC.SuppressFinalize(task); // finalizer only exists for unobserved-exception purposes } } - public static IResultBox Create(out TaskCompletionSource source, object? asyncState) + public static IResultBox Create(CancellationToken cancellationToken, out TaskCompletionSource source, object? asyncState) { // it might look a little odd to return the same object as two different things, // but that's because it is serving two purposes, and I want to make it clear @@ -146,6 +187,7 @@ public static IResultBox Create(out TaskCompletionSource source, object? a // are the same underlying object is an implementation detail that the rest of // the code doesn't need to know about var obj = new TaskResultBox( + cancellationToken, asyncState, // if we don't trust the TPL/sync-context, avoid a double QUWI dispatch ConnectionMultiplexer.PreventThreadTheft ? TaskCreationOptions.None : TaskCreationOptions.RunContinuationsAsynchronously); diff --git a/src/StackExchange.Redis/ResultProcessor.cs b/src/StackExchange.Redis/ResultProcessor.cs index 06647212b..acef5d978 100644 --- a/src/StackExchange.Redis/ResultProcessor.cs +++ b/src/StackExchange.Redis/ResultProcessor.cs @@ -8,6 +8,7 @@ using System.Net; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using Microsoft.Extensions.Logging; using Pipelines.Sockets.Unofficial.Arenas; @@ -395,8 +396,8 @@ public sealed class TimingProcessor : ResultProcessor { private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; - public static TimerMessage CreateMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value = default) => - new TimerMessage(db, flags, command, value); + public static TimerMessage CreateMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value, CancellationToken cancellationToken) => + new TimerMessage(db, flags, command, value, cancellationToken); protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result) { @@ -428,8 +429,8 @@ internal sealed class TimerMessage : Message { public long StartedWritingTimestamp; private readonly RedisValue value; - public TimerMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value) - : base(db, flags, command) + public TimerMessage(int db, CommandFlags flags, RedisCommand command, RedisValue value, CancellationToken cancellationToken) + : base(db, flags, command, cancellationToken) { this.value = value; } diff --git a/src/StackExchange.Redis/ServerEndPoint.cs b/src/StackExchange.Redis/ServerEndPoint.cs index 1e32275d6..b5df45436 100644 --- a/src/StackExchange.Redis/ServerEndPoint.cs +++ b/src/StackExchange.Redis/ServerEndPoint.cs @@ -392,24 +392,25 @@ internal async Task AutoConfigureAsync(PhysicalConnection? connection, ILogger? var autoConfigProcessor = ResultProcessor.AutoConfigureProcessor.Create(log); + var cancellationToken = CancellationToken.None; // suppress cancellation for configure if (commandMap.IsAvailable(RedisCommand.CONFIG)) { if (Multiplexer.RawConfig.KeepAlive <= 0) { - msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, RedisLiterals.timeout); + msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, RedisLiterals.timeout, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); } - msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, features.ReplicaCommands ? RedisLiterals.replica_read_only : RedisLiterals.slave_read_only); + msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, features.ReplicaCommands ? RedisLiterals.replica_read_only : RedisLiterals.slave_read_only, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); - msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, RedisLiterals.databases); + msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, RedisLiterals.databases, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); } if (commandMap.IsAvailable(RedisCommand.SENTINEL)) { - msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTERS); + msg = Message.Create(-1, flags, RedisCommand.SENTINEL, RedisLiterals.MASTERS, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); } @@ -422,17 +423,17 @@ internal async Task AutoConfigureAsync(PhysicalConnection? connection, ILogger? // the server version at this point; we *could* use the optional // value on the config, but let's keep things simple: these // commands are suitably cheap - msg = Message.Create(-1, flags, RedisCommand.INFO, RedisLiterals.replication); + msg = Message.Create(-1, flags, RedisCommand.INFO, RedisLiterals.replication, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); - msg = Message.Create(-1, flags, RedisCommand.INFO, RedisLiterals.server); + msg = Message.Create(-1, flags, RedisCommand.INFO, RedisLiterals.server, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); } else { - msg = Message.Create(-1, flags, RedisCommand.INFO); + msg = Message.Create(-1, flags, RedisCommand.INFO, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); } @@ -444,13 +445,13 @@ internal async Task AutoConfigureAsync(PhysicalConnection? connection, ILogger? // The actual value here doesn't matter (we detect the error code if it fails). // The value here is to at least give some indication to anyone watching via "monitor", // but we could send two GUIDs (key/value) and it would work the same. - msg = Message.Create(0, flags, RedisCommand.SET, key, RedisLiterals.replica_read_only, RedisLiterals.PX, 1, RedisLiterals.NX); + msg = Message.Create(0, flags, RedisCommand.SET, key, RedisLiterals.replica_read_only, RedisLiterals.PX, 1, RedisLiterals.NX, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfigProcessor).ForAwait(); } if (commandMap.IsAvailable(RedisCommand.CLUSTER)) { - msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); + msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.ClusterNodes).ForAwait(); } @@ -459,7 +460,7 @@ internal async Task AutoConfigureAsync(PhysicalConnection? connection, ILogger? if (Multiplexer.RawConfig.TryGetTieBreaker(out var tieBreakerKey) && Multiplexer.CommandMap.IsAvailable(RedisCommand.GET)) { log?.LogInformation($"{Format.ToString(EndPoint)}: Requesting tie-break (Key=\"{tieBreakerKey}\")..."); - msg = Message.Create(0, flags, RedisCommand.GET, tieBreakerKey); + msg = Message.Create(0, flags, RedisCommand.GET, tieBreakerKey, cancellationToken); msg.SetInternalCall(); msg = LoggingMessage.Create(log, msg); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.TieBreaker).ForAwait(); @@ -485,7 +486,7 @@ internal Task Close(ConnectionType connectionType) } else { - return WriteDirectAsync(Message.Create(-1, CommandFlags.None, RedisCommand.QUIT), ResultProcessor.DemandOK, bridge: tmp); + return WriteDirectAsync(Message.Create(-1, CommandFlags.None, RedisCommand.QUIT, CancellationToken.None), ResultProcessor.DemandOK, bridge: tmp); } } catch (Exception ex) @@ -581,27 +582,28 @@ internal Message GetTracerMessage(bool checkResponse) var map = Multiplexer.CommandMap; Message msg; const CommandFlags flags = CommandFlags.NoRedirect | CommandFlags.FireAndForget; + var cancellationToken = CancellationToken.None; // do not cancel tracers if (checkResponse && map.IsAvailable(RedisCommand.ECHO)) { - msg = Message.Create(-1, flags, RedisCommand.ECHO, (RedisValue)Multiplexer.UniqueId); + msg = Message.Create(-1, flags, RedisCommand.ECHO, (RedisValue)Multiplexer.UniqueId, cancellationToken); } else if (map.IsAvailable(RedisCommand.PING)) { - msg = Message.Create(-1, flags, RedisCommand.PING); + msg = Message.Create(-1, flags, RedisCommand.PING, cancellationToken); } else if (map.IsAvailable(RedisCommand.TIME)) { - msg = Message.Create(-1, flags, RedisCommand.TIME); + msg = Message.Create(-1, flags, RedisCommand.TIME, cancellationToken); } else if (!checkResponse && map.IsAvailable(RedisCommand.ECHO)) { // We'll use echo as a PING substitute if it is all we have (in preference to EXISTS) - msg = Message.Create(-1, flags, RedisCommand.ECHO, (RedisValue)Multiplexer.UniqueId); + msg = Message.Create(-1, flags, RedisCommand.ECHO, (RedisValue)Multiplexer.UniqueId, cancellationToken); } else { map.AssertAvailable(RedisCommand.EXISTS); - msg = Message.Create(0, flags, RedisCommand.EXISTS, (RedisValue)Multiplexer.UniqueId); + msg = Message.Create(0, flags, RedisCommand.EXISTS, (RedisValue)Multiplexer.UniqueId, cancellationToken); } msg.SetInternalCall(); return msg; @@ -735,7 +737,7 @@ internal bool CheckInfoReplication() if (version.IsAtLeast(RedisFeatures.v2_8_0) && Multiplexer.CommandMap.IsAvailable(RedisCommand.INFO) && GetBridge(ConnectionType.Interactive, false) is PhysicalBridge bridge) { - var msg = Message.Create(-1, CommandFlags.FireAndForget | CommandFlags.NoRedirect, RedisCommand.INFO, RedisLiterals.replication); + var msg = Message.Create(-1, CommandFlags.FireAndForget | CommandFlags.NoRedirect, RedisCommand.INFO, RedisLiterals.replication, CancellationToken.None); msg.SetInternalCall(); msg.SetSource(ResultProcessor.AutoConfigure, null); #pragma warning disable CS0618 // Type or member is obsolete @@ -805,7 +807,7 @@ internal void OnHeartbeat() return await tcs.Task.ForAwait(); } - var source = TaskResultBox.Create(out var tcs, null); + var source = TaskResultBox.Create(message.CancellationToken, out var tcs, null); message.SetSource(processor, source); bridge ??= GetBridge(message); @@ -977,10 +979,11 @@ private async Task HandshakeAsync(PhysicalConnection connection, ILogger? log) // the various tasks and just `return connection.FlushAsync();` - however, since handshake is low // volume, we can afford to optimize for a good stack-trace rather than avoiding state machines. ResultProcessor? autoConfig = null; + var cancellationToken = CancellationToken.None; // do not cancel handshakes if (Multiplexer.RawConfig.TryResp3()) // note this includes an availability check on HELLO { log?.LogInformation($"{Format.ToString(this)}: Authenticating via HELLO"); - var hello = Message.CreateHello(3, user, password, clientName, CommandFlags.FireAndForget); + var hello = Message.CreateHello(3, user, password, clientName, CommandFlags.FireAndForget, cancellationToken); hello.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, hello, autoConfig ??= ResultProcessor.AutoConfigureProcessor.Create(log)).ForAwait(); @@ -998,14 +1001,14 @@ private async Task HandshakeAsync(PhysicalConnection connection, ILogger? log) if (!string.IsNullOrWhiteSpace(user) && Multiplexer.CommandMap.IsAvailable(RedisCommand.AUTH)) { log?.LogInformation($"{Format.ToString(this)}: Authenticating (user/password)"); - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.AUTH, (RedisValue)user, (RedisValue)password); + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.AUTH, (RedisValue)user, (RedisValue)password, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); } else if (!string.IsNullOrWhiteSpace(password) && Multiplexer.CommandMap.IsAvailable(RedisCommand.AUTH)) { log?.LogInformation($"{Format.ToString(this)}: Authenticating (password)"); - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.AUTH, (RedisValue)password); + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.AUTH, (RedisValue)password, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); } @@ -1015,7 +1018,7 @@ private async Task HandshakeAsync(PhysicalConnection connection, ILogger? log) if (!string.IsNullOrWhiteSpace(clientName)) { log?.LogInformation($"{Format.ToString(this)}: Setting client name: {clientName}"); - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETNAME, (RedisValue)clientName); + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETNAME, (RedisValue)clientName, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); } @@ -1029,7 +1032,7 @@ private async Task HandshakeAsync(PhysicalConnection connection, ILogger? log) var libName = Multiplexer.GetFullLibraryName(); if (!string.IsNullOrWhiteSpace(libName)) { - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETINFO, RedisLiterals.lib_name, libName); + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETINFO, RedisLiterals.lib_name, libName, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); } @@ -1037,13 +1040,13 @@ private async Task HandshakeAsync(PhysicalConnection connection, ILogger? log) var version = ClientInfoSanitize(Utils.GetLibVersion()); if (!string.IsNullOrWhiteSpace(version)) { - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETINFO, RedisLiterals.lib_ver, version); + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETINFO, RedisLiterals.lib_ver, version, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.DemandOK).ForAwait(); } } - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.ID); + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.ID, cancellationToken); msg.SetInternalCall(); await WriteDirectOrQueueFireAndForgetAsync(connection, msg, autoConfig ??= ResultProcessor.AutoConfigureProcessor.Create(log)).ForAwait(); } @@ -1072,7 +1075,7 @@ private async Task HandshakeAsync(PhysicalConnection connection, ILogger? log) var configChannel = Multiplexer.ConfigurationChangedChannel; if (configChannel != null) { - msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.SUBSCRIBE, RedisChannel.Literal(configChannel)); + msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.SUBSCRIBE, RedisChannel.Literal(configChannel), cancellationToken); // Note: this is NOT internal, we want it to queue in a backlog for sending when ready if necessary await WriteDirectOrQueueFireAndForgetAsync(connection, msg, ResultProcessor.TrackSubscriptions).ForAwait(); } diff --git a/tests/StackExchange.Redis.Tests/BacklogTests.cs b/tests/StackExchange.Redis.Tests/BacklogTests.cs index b81c86b8a..88f5966ef 100644 --- a/tests/StackExchange.Redis.Tests/BacklogTests.cs +++ b/tests/StackExchange.Redis.Tests/BacklogTests.cs @@ -91,7 +91,7 @@ void PrintSnapshot(ConnectionMultiplexer muxer) // For debug, print out the snapshot and server states PrintSnapshot(conn); - Assert.NotNull(conn.SelectServer(Message.Create(-1, CommandFlags.None, RedisCommand.PING))); + Assert.NotNull(conn.SelectServer(Message.Create(-1, CommandFlags.None, RedisCommand.PING, conn.GetEffectiveCancellationToken()))); // We should see none queued Assert.Equal(0, stats.BacklogMessagesPending); @@ -322,7 +322,7 @@ public async Task QueuesAndFlushesAfterReconnectingClusterAsync() await db.PingAsync(); RedisKey meKey = Me(); - var getMsg = Message.Create(0, CommandFlags.None, RedisCommand.GET, meKey); + var getMsg = Message.Create(0, CommandFlags.None, RedisCommand.GET, meKey, conn.GetEffectiveCancellationToken()); ServerEndPoint? server = null; // Get the server specifically for this message's hash slot await UntilConditionAsync(TimeSpan.FromSeconds(10), () => (server = conn.SelectServer(getMsg)) != null); @@ -333,7 +333,7 @@ public async Task QueuesAndFlushesAfterReconnectingClusterAsync() static Task PingAsync(ServerEndPoint server, CommandFlags flags = CommandFlags.None) { - var message = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.PING); + var message = ResultProcessor.TimingProcessor.CreateMessage(-1, flags, RedisCommand.PING, default, server.Multiplexer.GetEffectiveCancellationToken()); server.Multiplexer.CheckMessage(message); return server.Multiplexer.ExecuteAsyncImpl(message, ResultProcessor.ResponseTimer, null, server); diff --git a/tests/StackExchange.Redis.Tests/CancellationTests.cs b/tests/StackExchange.Redis.Tests/CancellationTests.cs new file mode 100644 index 000000000..bc30ce6b9 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/CancellationTests.cs @@ -0,0 +1,462 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using StackExchange.Redis.Maintenance; +using StackExchange.Redis.Profiling; +using Xunit; +using Xunit.Abstractions; + +namespace StackExchange.Redis.Tests +{ + public class PureCancellationTests + { + [Fact] + public async Task GetEffectiveCancellationToken_Nesting() + { + // this is a pure test - no database access + IConnectionMultiplexer muxerA = new DummyMultiplexer(), muxerB = new DummyMultiplexer(); + + // No context initially + Assert.Null(muxerA.GetCurrentScope()); + Assert.Equal(CancellationToken.None, muxerA.GetEffectiveCancellationToken()); + Assert.Null(muxerB.GetCurrentScope()); + Assert.Equal(CancellationToken.None, muxerB.GetEffectiveCancellationToken()); + + using var cts = new CancellationTokenSource(); + using (var outer = muxerA.WithCancellation(cts.Token)) + { + Assert.NotNull(outer); + Assert.Same(outer, muxerA.GetCurrentScope()); + Assert.Equal(cts.Token, muxerA.GetEffectiveCancellationToken()); + Assert.Null(muxerB.GetCurrentScope()); // B unaffected + Assert.Equal(CancellationToken.None, muxerB.GetEffectiveCancellationToken()); + + // nest with timeout + using (var inner = muxerA.WithTimeout(TimeSpan.FromSeconds(0.5))) + { + Assert.NotNull(inner); + Assert.Same(inner, muxerA.GetCurrentScope()); + var active = muxerA.GetEffectiveCancellationToken(); + + Assert.Null(muxerB.GetCurrentScope()); // B unaffected + Assert.Equal(CancellationToken.None, muxerB.GetEffectiveCancellationToken()); + + Assert.False(active.IsCancellationRequested); + + await Task.Delay(TimeSpan.FromSeconds(0.5)); + for (int i = 0; i < 20; i++) + { + if (active.IsCancellationRequested) break; + await Task.Delay(TimeSpan.FromSeconds(0.1)); + } + + Assert.True(active.IsCancellationRequested); + Assert.Equal(active, muxerA.GetEffectiveCancellationToken(checkForCancellation: false)); + } + + // back to outer + Assert.Same(outer, muxerA.GetCurrentScope()); + Assert.Equal(cts.Token, muxerA.GetEffectiveCancellationToken()); + Assert.Null(muxerB.GetCurrentScope()); // B unaffected + Assert.Equal(CancellationToken.None, muxerB.GetEffectiveCancellationToken()); + + // nest with suppression + using (var inner = muxerA.WithCancellation(CancellationToken.None)) + { + Assert.NotNull(inner); + Assert.Same(inner, muxerA.GetCurrentScope()); + Assert.Equal(CancellationToken.None, muxerA.GetEffectiveCancellationToken()); + } + + // back to outer + Assert.Same(outer, muxerA.GetCurrentScope()); + Assert.Equal(cts.Token, muxerA.GetEffectiveCancellationToken()); + Assert.Null(muxerB.GetCurrentScope()); // B unaffected + Assert.Equal(CancellationToken.None, muxerB.GetEffectiveCancellationToken()); + } + + Assert.Null(muxerA.GetCurrentScope()); + Assert.Equal(CancellationToken.None, muxerA.GetEffectiveCancellationToken()); + Assert.Null(muxerB.GetCurrentScope()); // B unaffected + Assert.Equal(CancellationToken.None, muxerB.GetEffectiveCancellationToken()); + } + + private sealed class DummyMultiplexer : IConnectionMultiplexer + { + public override string ToString() => ""; + + void IDisposable.Dispose() { } + + ValueTask IAsyncDisposable.DisposeAsync() => default; + + string IConnectionMultiplexer.ClientName => ""; + + string IConnectionMultiplexer.Configuration => ""; + + int IConnectionMultiplexer.TimeoutMilliseconds => 0; + + long IConnectionMultiplexer.OperationCount => 0; + + bool IConnectionMultiplexer.PreserveAsyncOrder + { + get => false; + set { } + } + + bool IConnectionMultiplexer.IsConnected => true; + + bool IConnectionMultiplexer.IsConnecting => false; + + bool IConnectionMultiplexer.IncludeDetailInExceptions + { + get => false; + set { } + } + + int IConnectionMultiplexer.StormLogThreshold + { + get => 0; + set { } + } + + void IConnectionMultiplexer.RegisterProfiler(Func profilingSessionProvider) => + throw new NotImplementedException(); + + ServerCounters IConnectionMultiplexer.GetCounters() => throw new NotImplementedException(); + + event EventHandler? IConnectionMultiplexer.ErrorMessage + { + add { } + remove { } + } + + event EventHandler? IConnectionMultiplexer.ConnectionFailed + { + add { } + remove { } + } + + event EventHandler? IConnectionMultiplexer.InternalError + { + add { } + remove { } + } + + event EventHandler? IConnectionMultiplexer.ConnectionRestored + { + add { } + remove { } + } + + event EventHandler? IConnectionMultiplexer.ConfigurationChanged + { + add { } + remove { } + } + + event EventHandler? IConnectionMultiplexer.ConfigurationChangedBroadcast + { + add { } + remove { } + } + + event EventHandler? IConnectionMultiplexer.ServerMaintenanceEvent + { + add { } + remove { } + } + + EndPoint[] IConnectionMultiplexer.GetEndPoints(bool configuredOnly) => throw new NotImplementedException(); + + void IConnectionMultiplexer.Wait(Task task) => throw new NotImplementedException(); + + T IConnectionMultiplexer.Wait(Task task) => throw new NotImplementedException(); + + void IConnectionMultiplexer.WaitAll(params Task[] tasks) => throw new NotImplementedException(); + + event EventHandler? IConnectionMultiplexer.HashSlotMoved + { + add { } + remove { } + } + + int IConnectionMultiplexer.HashSlot(RedisKey key) => throw new NotImplementedException(); + + ISubscriber IConnectionMultiplexer.GetSubscriber(object? asyncState) => throw new NotImplementedException(); + + IDatabase IConnectionMultiplexer.GetDatabase(int db, object? asyncState) => + throw new NotImplementedException(); + + IServer IConnectionMultiplexer.GetServer(string host, int port, object? asyncState) => + throw new NotImplementedException(); + + IServer IConnectionMultiplexer.GetServer(string hostAndPort, object? asyncState) => + throw new NotImplementedException(); + + IServer IConnectionMultiplexer.GetServer(IPAddress host, int port) => throw new NotImplementedException(); + + IServer IConnectionMultiplexer.GetServer(EndPoint endpoint, object? asyncState) => + throw new NotImplementedException(); + + IServer[] IConnectionMultiplexer.GetServers() => throw new NotImplementedException(); + + Task IConnectionMultiplexer.ConfigureAsync(TextWriter? log) => throw new NotImplementedException(); + + bool IConnectionMultiplexer.Configure(TextWriter? log) => throw new NotImplementedException(); + + string IConnectionMultiplexer.GetStatus() => throw new NotImplementedException(); + + void IConnectionMultiplexer.GetStatus(TextWriter log) => throw new NotImplementedException(); + + void IConnectionMultiplexer.Close(bool allowCommandsToComplete) => throw new NotImplementedException(); + + Task IConnectionMultiplexer.CloseAsync(bool allowCommandsToComplete) => throw new NotImplementedException(); + + string? IConnectionMultiplexer.GetStormLog() => throw new NotImplementedException(); + + void IConnectionMultiplexer.ResetStormLog() => throw new NotImplementedException(); + + long IConnectionMultiplexer.PublishReconfigure(CommandFlags flags) => throw new NotImplementedException(); + + Task IConnectionMultiplexer.PublishReconfigureAsync(CommandFlags flags) => + throw new NotImplementedException(); + + int IConnectionMultiplexer.GetHashSlot(RedisKey key) => throw new NotImplementedException(); + + void IConnectionMultiplexer.ExportConfiguration(Stream destination, ExportOptions options) => + throw new NotImplementedException(); + + void IConnectionMultiplexer.AddLibraryNameSuffix(string suffix) => throw new NotImplementedException(); + } + } + + [Collection(SharedConnectionFixture.Key)] + public class CancellationTests : TestBase + { + public CancellationTests(ITestOutputHelper output, SharedConnectionFixture fixture) : base(output, fixture) { } + + [Fact] + public async Task WithCancellation_CancelledToken_ThrowsOperationCanceledException() + { + using var conn = Create(); + var db = conn.GetDatabase(); + + using var cts = new CancellationTokenSource(); + cts.Cancel(); // Cancel immediately + + using (db.Multiplexer.WithCancellation(cts.Token)) + { + await Assert.ThrowsAnyAsync(async () => + { + await db.StringSetAsync(Me(), "value"); + }); + } + } + + private IInternalConnectionMultiplexer Create() => Create(syncTimeout: 10_000); + + [Fact] + public async Task WithCancellation_ValidToken_OperationSucceeds() + { + using var conn = Create(); + var db = conn.GetDatabase(); + + using var cts = new CancellationTokenSource(); + + using (db.Multiplexer.WithCancellation(cts.Token)) + { + RedisKey key = Me(); + // This should succeed + await db.StringSetAsync(key, "value"); + var result = await db.StringGetAsync(key); + Assert.Equal("value", result); + } + } + + private void Pause(IDatabase db) + { + db.Execute("client", new object[] { "pause", ConnectionPauseMilliseconds }, CommandFlags.FireAndForget); + } + + /* + private ConnectionMultiplexer Create([CallerMemberName] string caller = "") + { + var options = ConfigurationOptions.Parse("127.0.0.1:4000"); + #pragma warning disable CS0618 + LoggingTunnel.LogToDirectory(options, @"/home/marc/logs/"); + #pragma warning restore CS0618 + return ConnectionMultiplexer.Connect(options); + } + */ + + [Fact] + public async Task WithTimeout_ShortTimeout_Async_ThrowsOperationCanceledException() + { + using var conn = Create(); + var db = conn.GetDatabase(); + + var watch = Stopwatch.StartNew(); + Pause(db); + + using (db.Multiplexer.WithTimeout(TimeSpan.FromMilliseconds(ShortDelayMilliseconds))) + { + // This might throw due to timeout, but let's test the mechanism + var pending = db.StringSetAsync(Me(), "value"); // check we get past this + try + { + await pending; + // If it succeeds, that's fine too - Redis is fast + Assert.Fail(ExpectedCancel + ": " + watch.ElapsedMilliseconds + "ms"); + } + catch (OperationCanceledException) + { + // Expected for very short timeouts + Log($"Cancelled after {watch.ElapsedMilliseconds}ms"); + } + } + } + + [Fact] + public void WithTimeout_ShortTimeout_Sync_ThrowsOperationCanceledException() + { + using var conn = Create(); + var db = conn.GetDatabase(); + + var watch = Stopwatch.StartNew(); + Pause(db); + + using (db.Multiplexer.WithTimeout(TimeSpan.FromMilliseconds(ShortDelayMilliseconds))) + { + // This might throw due to timeout, but let's test the mechanism + try + { + db.StringSet(Me(), "value"); // check we get past this + // If it succeeds, that's fine too - Redis is fast + Assert.Fail(ExpectedCancel + ": " + watch.ElapsedMilliseconds + "ms"); + } + catch (OperationCanceledException) + { + // Expected for very short timeouts + Log($"Cancelled after {watch.ElapsedMilliseconds}ms"); + } + } + } + + private const string ExpectedCancel = "This operation should have been cancelled"; + + [Fact] + public async Task WithoutAmbientCancellation_OperationsWorkNormally() + { + using var conn = Create(); + var db = conn.GetDatabase(); + + // No ambient cancellation - should work normally + RedisKey key = Me(); + await db.StringSetAsync(key, "value"); + var result = await db.StringGetAsync(key); + Assert.Equal("value", result); + } + + public enum CancelStrategy + { + Constructor, + Method, + Manual, + } + + private const int ConnectionPauseMilliseconds = 50, ShortDelayMilliseconds = 5; + + private static CancellationTokenSource CreateCts(CancelStrategy strategy) + { + switch (strategy) + { + case CancelStrategy.Constructor: + return new CancellationTokenSource(TimeSpan.FromMilliseconds(ShortDelayMilliseconds)); + case CancelStrategy.Method: + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromMilliseconds(ShortDelayMilliseconds)); + return cts; + case CancelStrategy.Manual: + cts = new(); + _ = Task.Run(async () => + { + await Task.Delay(ShortDelayMilliseconds); + // ReSharper disable once MethodHasAsyncOverload - TFM-dependent + cts.Cancel(); + }); + return cts; + default: + throw new ArgumentOutOfRangeException(nameof(strategy)); + } + } + + [Theory] + [InlineData(CancelStrategy.Constructor)] + [InlineData(CancelStrategy.Method)] + [InlineData(CancelStrategy.Manual)] + public async Task CancellationDuringOperation_Async_CancelsGracefully(CancelStrategy strategy) + { + using var conn = Create(); + var db = conn.GetDatabase(); + + var watch = Stopwatch.StartNew(); + Pause(db); + + using var cts = CreateCts(strategy); + + // Cancel after a short delay + using (db.Multiplexer.WithCancellation(cts.Token)) + { + // Start an operation and cancel it mid-flight + var pending = db.StringSetAsync($"{Me()}:{strategy}", "value"); + + try + { + await pending; + Assert.Fail(ExpectedCancel + ": " + watch.ElapsedMilliseconds + "ms"); + } + catch (OperationCanceledException oce) + { + // Expected if cancellation happens during operation + Log($"Cancelled after {watch.ElapsedMilliseconds}ms"); + Assert.Equal(cts.Token, oce.CancellationToken); + } + } + } + + [Theory] + [InlineData(CancelStrategy.Constructor)] + [InlineData(CancelStrategy.Method)] + [InlineData(CancelStrategy.Manual)] + public void CancellationDuringOperation_Sync_CancelsGracefully(CancelStrategy strategy) + { + using var conn = Create(); + var db = conn.GetDatabase(); + + var watch = Stopwatch.StartNew(); + Pause(db); + + using var cts = CreateCts(strategy); + + // Cancel after a short delay + using (db.Multiplexer.WithCancellation(cts.Token)) + { + // Start an operation and cancel it mid-flight + try + { + db.StringSet($"{Me()}:{strategy}", "value"); + Assert.Fail(ExpectedCancel + ": " + watch.ElapsedMilliseconds + "ms"); + } + catch (OperationCanceledException oce) + { + // Expected if cancellation happens during operation + Log($"Cancelled after {watch.ElapsedMilliseconds}ms"); + Assert.Equal(cts.Token, oce.CancellationToken); + } + } + } + } +} diff --git a/tests/StackExchange.Redis.Tests/ExceptionFactoryTests.cs b/tests/StackExchange.Redis.Tests/ExceptionFactoryTests.cs index 2edee05c6..e7138c94c 100644 --- a/tests/StackExchange.Redis.Tests/ExceptionFactoryTests.cs +++ b/tests/StackExchange.Redis.Tests/ExceptionFactoryTests.cs @@ -107,7 +107,7 @@ public void TimeoutException() var server = GetServer(conn); conn.AllowConnect = false; - var msg = Message.Create(-1, CommandFlags.None, RedisCommand.PING); + var msg = Message.Create(-1, CommandFlags.None, RedisCommand.PING, conn.GetEffectiveCancellationToken()); var rawEx = ExceptionFactory.Timeout(conn.UnderlyingMultiplexer, "Test Timeout", msg, new ServerEndPoint(conn.UnderlyingMultiplexer, server.EndPoint)); var ex = Assert.IsType(rawEx); Log("Exception: " + ex.Message); @@ -187,7 +187,7 @@ public void NoConnectionException(bool abortOnConnect, int connCount, int comple options.IncludeDetailInExceptions = hasDetail; options.IncludePerformanceCountersInExceptions = hasDetail; - var msg = Message.Create(-1, CommandFlags.None, RedisCommand.PING); + var msg = Message.Create(-1, CommandFlags.None, RedisCommand.PING, conn.GetEffectiveCancellationToken()); var rawEx = ExceptionFactory.NoConnectionAvailable(conn, msg, new ServerEndPoint(conn, server.EndPoint)); var ex = Assert.IsType(rawEx); Log("Exception: " + ex.Message); @@ -222,7 +222,7 @@ public void NoConnectionPrimaryOnlyException() { using var conn = ConnectionMultiplexer.Connect(TestConfig.Current.ReplicaServerAndPort, Writer); - var msg = Message.Create(0, CommandFlags.None, RedisCommand.SET, (RedisKey)Me(), (RedisValue)"test"); + var msg = Message.Create(0, CommandFlags.None, RedisCommand.SET, (RedisKey)Me(), (RedisValue)"test", conn.GetEffectiveCancellationToken()); Assert.True(msg.IsPrimaryOnly()); var rawEx = ExceptionFactory.NoConnectionAvailable(conn, msg, null); var ex = Assert.IsType(rawEx); @@ -243,8 +243,8 @@ public void MessageFail(bool includeDetail, ConnectionFailureType failType, stri conn.RawConfig.IncludeDetailInExceptions = includeDetail; - var message = Message.Create(0, CommandFlags.None, RedisCommand.GET, (RedisKey)"myKey"); - var resultBox = SimpleResultBox.Create(); + var message = Message.Create(0, CommandFlags.None, RedisCommand.GET, (RedisKey)"myKey", conn.GetEffectiveCancellationToken()); + var resultBox = SimpleResultBox.Create(message.CancellationToken); message.SetSource(ResultProcessor.String, resultBox); message.Fail(failType, null, "my annotation", conn.UnderlyingMultiplexer); diff --git a/tests/StackExchange.Redis.Tests/Helpers/TaskExtensions.cs b/tests/StackExchange.Redis.Tests/Helpers/TaskExtensions.cs new file mode 100644 index 000000000..596f0d886 --- /dev/null +++ b/tests/StackExchange.Redis.Tests/Helpers/TaskExtensions.cs @@ -0,0 +1,21 @@ +#if !NET6_0_OR_GREATER +using System; +using System.Threading.Tasks; + +namespace StackExchange.Redis.Tests.Helpers; + +internal static class TaskExtensions +{ + public static async Task WaitAsync(this Task task, TimeSpan timeout) + { + if (task == await Task.WhenAny(task, Task.Delay(timeout)).ForAwait()) + { + return await task.ForAwait(); + } + else + { + throw new TimeoutException(); + } + } +} +#endif diff --git a/tests/StackExchange.Redis.Tests/ResultBoxTests.cs b/tests/StackExchange.Redis.Tests/ResultBoxTests.cs index adb1b309f..cc387e967 100644 --- a/tests/StackExchange.Redis.Tests/ResultBoxTests.cs +++ b/tests/StackExchange.Redis.Tests/ResultBoxTests.cs @@ -10,8 +10,8 @@ public class ResultBoxTests [Fact] public void SyncResultBox() { - var msg = Message.Create(-1, CommandFlags.None, RedisCommand.PING); - var box = SimpleResultBox.Get(); + var msg = Message.Create(-1, CommandFlags.None, RedisCommand.PING, CancellationToken.None); + var box = SimpleResultBox.Get(msg.CancellationToken); Assert.False(box.IsAsync); int activated = 0; @@ -62,8 +62,8 @@ public void TaskResultBox() // TaskResultBox currently uses a stating field for values before activations are // signalled; High Integrity Mode *demands* this behaviour, so: validate that it // works correctly - var msg = Message.Create(-1, CommandFlags.None, RedisCommand.PING); - var box = TaskResultBox.Create(out var tcs, null); + var msg = Message.Create(-1, CommandFlags.None, RedisCommand.PING, CancellationToken.None); + var box = TaskResultBox.Create(msg.CancellationToken, out var tcs, null); Assert.True(box.IsAsync); msg.SetSource(ResultProcessor.DemandOK, box);