From 22137adeade8073dede1bcff2a6160df9a9c2c6f Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 14 Jul 2025 14:21:36 +0100 Subject: [PATCH 1/9] Intial spike of ambient cancellation --- StackExchange.Redis.sln | 7 + docs/AmbientCancellation.md | 195 +++++++++++++++ .../ConnectionMultiplexer.cs | 8 +- src/StackExchange.Redis/Message.cs | 12 + .../PublicAPI/PublicAPI.Unshipped.txt | 6 +- src/StackExchange.Redis/RedisBase.cs | 6 + .../RedisCancellationExtensions.cs | 158 ++++++++++++ src/StackExchange.Redis/ResultBox.cs | 29 ++- tests/Examples/CancellationExample.cs | 231 ++++++++++++++++++ tests/Examples/SimpleCancellationDemo.cs | 172 +++++++++++++ tests/SimpleCancellationDemo/Program.cs | 118 +++++++++ .../SimpleCancellationDemo.csproj | 13 + .../CancellationTests.cs | 220 +++++++++++++++++ 13 files changed, 1171 insertions(+), 4 deletions(-) create mode 100644 docs/AmbientCancellation.md create mode 100644 src/StackExchange.Redis/RedisCancellationExtensions.cs create mode 100644 tests/Examples/CancellationExample.cs create mode 100644 tests/Examples/SimpleCancellationDemo.cs create mode 100644 tests/SimpleCancellationDemo/Program.cs create mode 100644 tests/SimpleCancellationDemo/SimpleCancellationDemo.csproj create mode 100644 tests/StackExchange.Redis.Tests/CancellationTests.cs diff --git a/StackExchange.Redis.sln b/StackExchange.Redis.sln index 18a30a9af..8d996b545 100644 --- a/StackExchange.Redis.sln +++ b/StackExchange.Redis.sln @@ -142,6 +142,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}") = "SimpleCancellationDemo", "tests\SimpleCancellationDemo\SimpleCancellationDemo.csproj", "{368A06AA-9DEB-4C55-B9CF-A1070B85E502}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -192,6 +194,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 + {368A06AA-9DEB-4C55-B9CF-A1070B85E502}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {368A06AA-9DEB-4C55-B9CF-A1070B85E502}.Debug|Any CPU.Build.0 = Debug|Any CPU + {368A06AA-9DEB-4C55-B9CF-A1070B85E502}.Release|Any CPU.ActiveCfg = Release|Any CPU + {368A06AA-9DEB-4C55-B9CF-A1070B85E502}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -214,6 +220,7 @@ Global {A9F81DA3-DA82-423E-A5DD-B11C37548E06} = {96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05} {A0F89B8B-32A3-4C28-8F1B-ADE343F16137} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} {69A0ACF2-DF1F-4F49-B554-F732DCA938A3} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} + {368A06AA-9DEB-4C55-B9CF-A1070B85E502} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {193AA352-6748-47C1-A5FC-C9AA6B5F000B} diff --git a/docs/AmbientCancellation.md b/docs/AmbientCancellation.md new file mode 100644 index 000000000..e8d8f0452 --- /dev/null +++ b/docs/AmbientCancellation.md @@ -0,0 +1,195 @@ +# Ambient Cancellation Support + +StackExchange.Redis now supports ambient cancellation using `AsyncLocal` to provide cancellation tokens to Redis operations without expanding the API surface. + +## Overview + +The ambient cancellation feature allows you to set a cancellation context that applies to all Redis operations within an async scope. This provides a clean way to handle timeouts and cancellation without adding `CancellationToken` parameters to every method. + +## Key Features + +- **Zero API Surface Impact**: No new parameters added to existing methods +- **Scoped Cancellation**: Uses `using` statements for proper scope management +- **Timeout Support**: Can specify timeouts that are converted to cancellation tokens +- **Composable**: Can combine cancellation tokens with timeouts +- **Nested Scopes**: Inner scopes override outer scopes +- **Backward Compatible**: All existing code continues to work unchanged + +## Usage + +### Basic Cancellation + +```csharp +using var cts = new CancellationTokenSource(); + +using (database.WithCancellation(cts.Token)) +{ + await database.StringSetAsync("key", "value"); + var value = await database.StringGetAsync("key"); + // Both operations use the cancellation token +} +``` + +### Timeout Support + +```csharp +using (database.WithTimeout(TimeSpan.FromSeconds(5))) +{ + await database.StringSetAsync("key", "value"); + // Operation will be cancelled if it takes longer than 5 seconds +} +``` + +### Combined Cancellation and Timeout + +```csharp +using var cts = new CancellationTokenSource(); + +using (database.WithCancellationAndTimeout(cts.Token, TimeSpan.FromSeconds(10))) +{ + await database.StringSetAsync("key", "value"); + // Operation will be cancelled if either the token is cancelled OR 10 seconds elapse +} +``` + +### Nested Scopes + +```csharp +using var outerToken = new CancellationTokenSource(); +using var innerToken = new CancellationTokenSource(); + +using (database.WithCancellation(outerToken.Token)) +{ + await database.StringSetAsync("key1", "value1"); // Uses outerToken + + using (database.WithCancellation(innerToken.Token)) + { + await database.StringSetAsync("key2", "value2"); // Uses innerToken + } + + await database.StringSetAsync("key3", "value3"); // Uses outerToken again +} +``` + +### Pub/Sub Operations + +```csharp +using var cts = new CancellationTokenSource(); + +using (subscriber.WithCancellation(cts.Token)) +{ + await subscriber.SubscribeAsync("channel", handler); + await subscriber.PublishAsync("channel", "message"); + // Both operations use the cancellation token +} +``` + +## Extension Methods + +The functionality is provided through extension methods on `IRedisAsync`: + +- `WithCancellation(CancellationToken)` - Sets ambient cancellation token +- `WithTimeout(TimeSpan)` - Sets ambient timeout (converted to cancellation token) +- `WithCancellationAndTimeout(CancellationToken, TimeSpan)` - Sets both cancellation and timeout + +## Implementation Details + +### AsyncLocal Context + +The implementation uses `AsyncLocal` to flow the cancellation context through async operations: + +```csharp +private static readonly AsyncLocal _context = new(); +``` + +### Scope Management + +Each `WithCancellation` call returns an `IDisposable` that manages the scope: + +```csharp +public static IDisposable WithCancellation(this IRedisAsync redis, CancellationToken cancellationToken) +{ + return new CancellationScope(cancellationToken, null); +} +``` + +### Integration Points + +The cancellation token is applied at the core execution level: + +1. `RedisBase.ExecuteAsync` retrieves the ambient cancellation token +2. `ConnectionMultiplexer.ExecuteAsyncImpl` accepts the cancellation token +3. `TaskResultBox` registers for cancellation and properly handles cancellation + +### Timeout Handling + +Timeouts are converted to cancellation tokens using `CancellationTokenSource`: + +```csharp +public CancellationToken GetEffectiveToken() +{ + if (!Timeout.HasValue) return Token; + + var timeoutSource = new CancellationTokenSource(Timeout.Value); + return Token.CanBeCanceled + ? CancellationTokenSource.CreateLinkedTokenSource(Token, timeoutSource.Token).Token + : timeoutSource.Token; +} +``` + +## Error Handling + +When an operation is cancelled, it throws an `OperationCanceledException`: + +```csharp +try +{ + using (database.WithCancellation(cancelledToken)) + { + await database.StringSetAsync("key", "value"); + } +} +catch (OperationCanceledException) +{ + // Handle cancellation +} +``` + +## Performance Considerations + +- **Minimal Overhead**: When no ambient cancellation is set, there's virtually no performance impact +- **Efficient Scoping**: Uses struct-based scoping to minimize allocations +- **Proper Cleanup**: Cancellation registrations are properly disposed when operations complete + +## Limitations + +- **Client-Side Only**: Redis doesn't support server-side cancellation, so cancellation only prevents the client from waiting for a response +- **In-Flight Operations**: Operations that have already been sent to the server will continue executing on the server even if cancelled on the client +- **Connection Health**: Cancelled operations don't affect connection health or availability + +## Migration + +Existing code requires no changes. The ambient cancellation is purely additive: + +```csharp +// This continues to work exactly as before +await database.StringSetAsync("key", "value"); + +// This adds cancellation support +using (database.WithCancellation(cancellationToken)) +{ + await database.StringSetAsync("key", "value"); +} +``` + +## Best Practices + +1. **Use `using` statements** to ensure proper scope cleanup +2. **Prefer cancellation tokens over timeouts** when possible for better control +3. **Handle `OperationCanceledException`** appropriately in your application +4. **Don't rely on cancellation for server-side operation termination** +5. **Test cancellation scenarios** to ensure your application handles them gracefully + +## Examples + +See `examples/CancellationExample.cs` for comprehensive usage examples and `tests/StackExchange.Redis.Tests/CancellationTests.cs` for test cases demonstrating the functionality. diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.cs b/src/StackExchange.Redis/ConnectionMultiplexer.cs index e17a9503b..b0817c895 100644 --- a/src/StackExchange.Redis/ConnectionMultiplexer.cs +++ b/src/StackExchange.Redis/ConnectionMultiplexer.cs @@ -2132,7 +2132,9 @@ static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, Value IResultBox? source = null; if (!message.IsFireAndForget) { - 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(out tcs, state, message.CancellationToken); } var write = TryPushMessageToBridgeAsync(message, processor, source, ref server); if (!write.IsCompletedSuccessfully) @@ -2183,7 +2185,9 @@ static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, Value IResultBox? source = null; if (!message.IsFireAndForget) { - 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(out tcs, state, message.CancellationToken); } var write = TryPushMessageToBridgeAsync(message, processor, source!, ref server); if (!write.IsCompletedSuccessfully) diff --git a/src/StackExchange.Redis/Message.cs b/src/StackExchange.Redis/Message.cs index fd75585a5..0d75b157c 100644 --- a/src/StackExchange.Redis/Message.cs +++ b/src/StackExchange.Redis/Message.cs @@ -83,6 +83,9 @@ 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; @@ -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 = RedisCancellationExtensions.GetEffectiveCancellationToken(); + CreatedDateTime = DateTime.UtcNow; CreatedTimestamp = Stopwatch.GetTimestamp(); Status = CommandStatus.WaitingToBeSent; @@ -217,6 +223,12 @@ 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) diff --git a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index 91b0e1a43..2a5dc601e 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ -#nullable enable \ No newline at end of file +#nullable enable +StackExchange.Redis.RedisCancellationExtensions +static StackExchange.Redis.RedisCancellationExtensions.WithCancellation(this StackExchange.Redis.IRedisAsync! redis, System.Threading.CancellationToken cancellationToken) -> System.IDisposable! +static StackExchange.Redis.RedisCancellationExtensions.WithCancellationAndTimeout(this StackExchange.Redis.IRedisAsync! redis, System.Threading.CancellationToken cancellationToken, System.TimeSpan timeout) -> System.IDisposable! +static StackExchange.Redis.RedisCancellationExtensions.WithTimeout(this StackExchange.Redis.IRedisAsync! 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..2a0f6b5b8 100644 --- a/src/StackExchange.Redis/RedisBase.cs +++ b/src/StackExchange.Redis/RedisBase.cs @@ -44,6 +44,9 @@ internal virtual Task ExecuteAsync(Message? message, ResultProcessor? p { 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); } @@ -51,6 +54,9 @@ internal virtual Task ExecuteAsync(Message? message, ResultProcessor? p { 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); } diff --git a/src/StackExchange.Redis/RedisCancellationExtensions.cs b/src/StackExchange.Redis/RedisCancellationExtensions.cs new file mode 100644 index 000000000..c8fdef093 --- /dev/null +++ b/src/StackExchange.Redis/RedisCancellationExtensions.cs @@ -0,0 +1,158 @@ +using System; +using System.Threading; + +namespace StackExchange.Redis +{ + /// + /// Extension methods for adding ambient cancellation support to Redis operations. + /// + public static class RedisCancellationExtensions + { + private static readonly AsyncLocal _context = new(); + + /// + /// 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 IRedisAsync redis, CancellationToken cancellationToken) + { + return new CancellationScope(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 IRedisAsync redis, TimeSpan timeout) + { + return new CancellationScope(default, 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 IRedisAsync redis, + CancellationToken cancellationToken, + TimeSpan timeout) + { + return new CancellationScope(cancellationToken, timeout); + } + + /// + /// 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() + { + var context = _context.Value; + return context?.GetEffectiveToken() ?? default; + } + + /// + /// Gets the current cancellation context for diagnostic purposes. + /// + /// The current context, or null if no ambient context is set. + internal static CancellationContext? GetCurrentContext() => _context.Value; + + /// + /// Represents the cancellation context for Redis operations. + /// + internal record CancellationContext(CancellationToken Token, TimeSpan? Timeout) + { + /// + /// Gets the effective cancellation token, combining the explicit token with any timeout. + /// + /// A cancellation token that will be cancelled when either the explicit token is cancelled or the timeout expires. + public CancellationToken GetEffectiveToken() + { + if (!Timeout.HasValue) return Token; + + var timeoutSource = new CancellationTokenSource(Timeout.Value); + return Token.CanBeCanceled + ? CancellationTokenSource.CreateLinkedTokenSource(Token, timeoutSource.Token).Token + : timeoutSource.Token; + } + + /// + /// Gets a string representation of this context for debugging. + /// + public override string ToString() + { + var parts = new System.Collections.Generic.List(); + if (Token.CanBeCanceled) parts.Add($"Token: {(Token.IsCancellationRequested ? "Cancelled" : "Active")}"); + if (Timeout.HasValue) parts.Add($"Timeout: {Timeout.Value.TotalMilliseconds}ms"); + return parts.Count > 0 ? string.Join(", ", parts) : "None"; + } + } + + /// + /// A disposable scope that manages the ambient cancellation context. + /// + private sealed class CancellationScope : IDisposable + { + private readonly CancellationContext? _previous; + private bool _disposed; + + /// + /// Creates a new cancellation scope with the specified token and timeout. + /// + /// The cancellation token for this scope. + /// The timeout for this scope. + public CancellationScope(CancellationToken cancellationToken, TimeSpan? timeout) + { + _previous = _context.Value; + _context.Value = new CancellationContext(cancellationToken, timeout); + } + + /// + /// Restores the previous cancellation context. + /// + public void Dispose() + { + if (!_disposed) + { + _context.Value = _previous; + _disposed = true; + } + } + } + } +} diff --git a/src/StackExchange.Redis/ResultBox.cs b/src/StackExchange.Redis/ResultBox.cs index 20b76ba15..fc6d23795 100644 --- a/src/StackExchange.Redis/ResultBox.cs +++ b/src/StackExchange.Redis/ResultBox.cs @@ -88,6 +88,7 @@ internal sealed class TaskResultBox : TaskCompletionSource, IResultBox // the fun TryComplete indirection - so we need somewhere to buffer them private volatile Exception? _exception; private T _value = default!; + private CancellationTokenRegistration _cancellationRegistration; private TaskResultBox(object? asyncState, TaskCreationOptions creationOptions) : base(asyncState, creationOptions) { } @@ -124,13 +125,23 @@ private void ActivateContinuationsImpl() var val = _value; var ex = _exception; + // Clean up cancellation registration + try + { + _cancellationRegistration.Dispose(); + } + catch + { + // Ignore disposal errors + } + if (ex == null) { TrySetResult(val); } else { - if (ex is TaskCanceledException) TrySetCanceled(); + if (ex is TaskCanceledException or OperationCanceledException) TrySetCanceled(); else TrySetException(ex); var task = Task; GC.KeepAlive(task.Exception); // mark any exception as observed @@ -139,6 +150,11 @@ private void ActivateContinuationsImpl() } public static IResultBox Create(out TaskCompletionSource source, object? asyncState) + { + return Create(out source, asyncState, default); + } + + public static IResultBox Create(out TaskCompletionSource source, object? asyncState, CancellationToken cancellationToken) { // 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 @@ -149,6 +165,17 @@ public static IResultBox Create(out TaskCompletionSource source, object? a asyncState, // if we don't trust the TPL/sync-context, avoid a double QUWI dispatch ConnectionMultiplexer.PreventThreadTheft ? TaskCreationOptions.None : TaskCreationOptions.RunContinuationsAsynchronously); + + // Register for cancellation if the token can be cancelled + if (cancellationToken.CanBeCanceled) + { + obj._cancellationRegistration = cancellationToken.Register(() => + { + obj._exception = new OperationCanceledException("The Redis operation was cancelled.", cancellationToken); + obj.TrySetCanceled(cancellationToken); + }); + } + source = obj; return obj; } diff --git a/tests/Examples/CancellationExample.cs b/tests/Examples/CancellationExample.cs new file mode 100644 index 000000000..cc14aa8a1 --- /dev/null +++ b/tests/Examples/CancellationExample.cs @@ -0,0 +1,231 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using StackExchange.Redis; + +namespace Examples +{ + /// + /// Demonstrates the new ambient cancellation functionality in StackExchange.Redis. + /// + public class CancellationExample + { + public static async Task Main(string[] args) + { + // Connect to Redis + using var redis = ConnectionMultiplexer.Connect("localhost"); + var database = redis.GetDatabase(); + var subscriber = redis.GetSubscriber(); + + Console.WriteLine("=== StackExchange.Redis Ambient Cancellation Examples ===\n"); + + // Example 1: Basic cancellation + await BasicCancellationExample(database); + + // Example 2: Timeout example + await TimeoutExample(database); + + // Example 3: Combined cancellation and timeout + await CombinedExample(database); + + // Example 4: Nested scopes + await NestedScopesExample(database); + + // Example 5: Pub/Sub with cancellation + await PubSubExample(subscriber); + + // Example 6: Cancellation during operation + await CancellationDuringOperationExample(database); + + Console.WriteLine("\n=== All examples completed ==="); + } + + static async Task BasicCancellationExample(IDatabase database) + { + Console.WriteLine("1. Basic Cancellation Example"); + Console.WriteLine("------------------------------"); + + using var cts = new CancellationTokenSource(); + + try + { + using (database.WithCancellation(cts.Token)) + { + Console.WriteLine("Setting key with cancellation token..."); + await database.StringSetAsync("example:basic", "Hello, World!"); + + Console.WriteLine("Getting key with cancellation token..."); + var value = await database.StringGetAsync("example:basic"); + Console.WriteLine($"Retrieved value: {value}"); + } + } + catch (OperationCanceledException) + { + Console.WriteLine("Operation was cancelled!"); + } + + Console.WriteLine(); + } + + static async Task TimeoutExample(IDatabase database) + { + Console.WriteLine("2. Timeout Example"); + Console.WriteLine("------------------"); + + try + { + using (database.WithTimeout(TimeSpan.FromSeconds(5))) + { + Console.WriteLine("Setting key with 5-second timeout..."); + await database.StringSetAsync("example:timeout", "Timeout test"); + + Console.WriteLine("Getting key with 5-second timeout..."); + var value = await database.StringGetAsync("example:timeout"); + Console.WriteLine($"Retrieved value: {value}"); + } + } + catch (OperationCanceledException) + { + Console.WriteLine("Operation timed out!"); + } + + Console.WriteLine(); + } + + static async Task CombinedExample(IDatabase database) + { + Console.WriteLine("3. Combined Cancellation and Timeout Example"); + Console.WriteLine("---------------------------------------------"); + + using var cts = new CancellationTokenSource(); + + try + { + using (database.WithCancellationAndTimeout(cts.Token, TimeSpan.FromSeconds(10))) + { + Console.WriteLine("Setting key with both cancellation token and timeout..."); + await database.StringSetAsync("example:combined", "Combined test"); + + Console.WriteLine("Getting key with both cancellation token and timeout..."); + var value = await database.StringGetAsync("example:combined"); + Console.WriteLine($"Retrieved value: {value}"); + } + } + catch (OperationCanceledException) + { + Console.WriteLine("Operation was cancelled or timed out!"); + } + + Console.WriteLine(); + } + + static async Task NestedScopesExample(IDatabase database) + { + Console.WriteLine("4. Nested Scopes Example"); + Console.WriteLine("-------------------------"); + + using var outerCts = new CancellationTokenSource(); + using var innerCts = new CancellationTokenSource(); + + try + { + using (database.WithCancellation(outerCts.Token)) + { + Console.WriteLine("In outer scope - setting key1..."); + await database.StringSetAsync("example:outer", "Outer scope"); + + using (database.WithCancellation(innerCts.Token)) + { + Console.WriteLine("In inner scope - setting key2..."); + await database.StringSetAsync("example:inner", "Inner scope"); + } + + Console.WriteLine("Back in outer scope - setting key3..."); + await database.StringSetAsync("example:outer2", "Outer scope again"); + } + + // Verify all operations + Console.WriteLine($"Outer value: {await database.StringGetAsync("example:outer")}"); + Console.WriteLine($"Inner value: {await database.StringGetAsync("example:inner")}"); + Console.WriteLine($"Outer2 value: {await database.StringGetAsync("example:outer2")}"); + } + catch (OperationCanceledException) + { + Console.WriteLine("One of the operations was cancelled!"); + } + + Console.WriteLine(); + } + + static async Task PubSubExample(ISubscriber subscriber) + { + Console.WriteLine("5. Pub/Sub with Cancellation Example"); + Console.WriteLine("-------------------------------------"); + + using var cts = new CancellationTokenSource(); + var messageReceived = new TaskCompletionSource(); + + try + { + using (subscriber.WithCancellation(cts.Token)) + { + var channel = "example:channel"; + + Console.WriteLine("Subscribing to channel with cancellation..."); + await subscriber.SubscribeAsync(channel, (ch, message) => + { + Console.WriteLine($"Received message: {message}"); + messageReceived.TrySetResult(message); + }); + + Console.WriteLine("Publishing message with cancellation..."); + await subscriber.PublishAsync(channel, "Hello from pub/sub!"); + + // Wait for the message + var receivedMessage = await messageReceived.Task.WaitAsync(TimeSpan.FromSeconds(5)); + Console.WriteLine($"Successfully received: {receivedMessage}"); + } + } + catch (OperationCanceledException) + { + Console.WriteLine("Pub/Sub operation was cancelled!"); + } + + Console.WriteLine(); + } + + static async Task CancellationDuringOperationExample(IDatabase database) + { + Console.WriteLine("6. Cancellation During Operation Example"); + Console.WriteLine("-----------------------------------------"); + + using var cts = new CancellationTokenSource(); + + try + { + using (database.WithCancellation(cts.Token)) + { + Console.WriteLine("Starting operation..."); + var task = database.StringSetAsync("example:cancel-during", "This might be cancelled"); + + // Cancel after a short delay + _ = Task.Run(async () => + { + await Task.Delay(50); + Console.WriteLine("Cancelling operation..."); + cts.Cancel(); + }); + + await task; + Console.WriteLine("Operation completed before cancellation"); + } + } + catch (OperationCanceledException) + { + Console.WriteLine("Operation was successfully cancelled!"); + } + + Console.WriteLine(); + } + } +} diff --git a/tests/Examples/SimpleCancellationDemo.cs b/tests/Examples/SimpleCancellationDemo.cs new file mode 100644 index 000000000..313c9f43c --- /dev/null +++ b/tests/Examples/SimpleCancellationDemo.cs @@ -0,0 +1,172 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using StackExchange.Redis; + +namespace Examples +{ + /// + /// Simple demonstration of the new ambient cancellation functionality. + /// + public class SimpleCancellationDemo + { + public static async Task Main(string[] args) + { + Console.WriteLine("=== Simple Ambient Cancellation Demo ===\n"); + + // For this demo, we'll use a mock connection since we don't have Redis running + // In a real scenario, you would connect to an actual Redis instance + Console.WriteLine("Note: This demo shows the API usage. In a real scenario, connect to Redis:"); + Console.WriteLine("using var redis = ConnectionMultiplexer.Connect(\"localhost\");"); + Console.WriteLine("var database = redis.GetDatabase();\n"); + + await DemoBasicUsage(); + await DemoNestedScopes(); + await DemoTimeoutUsage(); + await DemoContextInspection(); + + Console.WriteLine("Demo completed successfully!"); + } + + static async Task DemoBasicUsage() + { + Console.WriteLine("1. Basic Cancellation Usage"); + Console.WriteLine("----------------------------"); + + // Create a cancellation token + using var cts = new CancellationTokenSource(); + + // Simulate getting a database instance + IDatabase database = null; // In real usage: redis.GetDatabase() + + try + { + // Set ambient cancellation - all operations in this scope will use this token + using (database?.WithCancellation(cts.Token)) + { + Console.WriteLine("✓ Ambient cancellation token set"); + Console.WriteLine(" All Redis operations in this scope will use the cancellation token"); + + // In real usage, these would be actual Redis operations: + // await database.StringSetAsync("key", "value"); + // var value = await database.StringGetAsync("key"); + + Console.WriteLine(" (Redis operations would execute here with cancellation support)"); + } + Console.WriteLine("✓ Cancellation scope disposed - back to normal operation"); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + + Console.WriteLine(); + } + + static async Task DemoNestedScopes() + { + Console.WriteLine("2. Nested Scopes Example"); + Console.WriteLine("-------------------------"); + + using var outerToken = new CancellationTokenSource(); + using var innerToken = new CancellationTokenSource(); + + IDatabase database = null; // In real usage: redis.GetDatabase() + + try + { + using (database?.WithCancellation(outerToken.Token)) + { + Console.WriteLine("✓ Outer scope: Using outer cancellation token"); + + using (database?.WithCancellation(innerToken.Token)) + { + Console.WriteLine("✓ Inner scope: Using inner cancellation token (overrides outer)"); + + // Check current context + var context = RedisCancellationExtensions.GetCurrentContext(); + if (context != null) + { + Console.WriteLine($" Current context: {context}"); + } + } + + Console.WriteLine("✓ Back to outer scope: Using outer cancellation token again"); + } + Console.WriteLine("✓ No cancellation scope: Normal operation"); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + + Console.WriteLine(); + } + + static async Task DemoTimeoutUsage() + { + Console.WriteLine("3. Timeout Usage Example"); + Console.WriteLine("-------------------------"); + + IDatabase database = null; // In real usage: redis.GetDatabase() + + try + { + using (database?.WithTimeout(TimeSpan.FromSeconds(5))) + { + Console.WriteLine("✓ Timeout scope: Operations will timeout after 5 seconds"); + + // Check current context + var context = RedisCancellationExtensions.GetCurrentContext(); + if (context != null) + { + Console.WriteLine($" Current context: {context}"); + } + } + Console.WriteLine("✓ Timeout scope disposed"); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + + Console.WriteLine(); + } + + static async Task DemoContextInspection() + { + Console.WriteLine("4. Context Inspection Example"); + Console.WriteLine("------------------------------"); + + using var cts = new CancellationTokenSource(); + IDatabase database = null; // In real usage: redis.GetDatabase() + + // No context initially + var context = RedisCancellationExtensions.GetCurrentContext(); + Console.WriteLine($"Initial context: {context?.ToString() ?? "None"}"); + + using (database?.WithCancellation(cts.Token)) + { + context = RedisCancellationExtensions.GetCurrentContext(); + Console.WriteLine($"With cancellation: {context?.ToString() ?? "None"}"); + + using (database?.WithTimeout(TimeSpan.FromSeconds(10))) + { + context = RedisCancellationExtensions.GetCurrentContext(); + Console.WriteLine($"With timeout: {context?.ToString() ?? "None"}"); + + using (database?.WithCancellationAndTimeout(cts.Token, TimeSpan.FromSeconds(3))) + { + context = RedisCancellationExtensions.GetCurrentContext(); + Console.WriteLine($"With both: {context?.ToString() ?? "None"}"); + } + } + } + + context = RedisCancellationExtensions.GetCurrentContext(); + Console.WriteLine($"Final context: {context?.ToString() ?? "None"}"); + + Console.WriteLine(); + } + } +} diff --git a/tests/SimpleCancellationDemo/Program.cs b/tests/SimpleCancellationDemo/Program.cs new file mode 100644 index 000000000..3faf3c7df --- /dev/null +++ b/tests/SimpleCancellationDemo/Program.cs @@ -0,0 +1,118 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using StackExchange.Redis; + +namespace Examples +{ + /// + /// Simple demonstration of the new ambient cancellation functionality. + /// + public class Program + { + /// + /// Main entry point. + /// + /// Command line arguments. + /// A task representing the asynchronous operation. + public static async Task Main(string[] args) + { + Console.WriteLine("=== Simple Ambient Cancellation Demo ===\n"); + + Console.WriteLine("Note: This demo shows the API usage. In a real scenario, connect to Redis:"); + Console.WriteLine("using var redis = ConnectionMultiplexer.Connect(\"localhost\");"); + Console.WriteLine("var database = redis.GetDatabase();\n"); + + DemoBasicUsage(); + DemoNestedScopes(); + DemoTimeoutUsage(); + + Console.WriteLine("Demo completed successfully!"); + await Task.CompletedTask; + } + + private static void DemoBasicUsage() + { + Console.WriteLine("1. Basic Cancellation Usage"); + Console.WriteLine("----------------------------"); + + using var cts = new CancellationTokenSource(); + IDatabase? database = null; // In real usage: redis.GetDatabase() + + try + { + using (database?.WithCancellation(cts.Token)) + { + Console.WriteLine("✓ Ambient cancellation token set"); + Console.WriteLine(" All Redis operations in this scope will use the cancellation token"); + Console.WriteLine(" (Redis operations would execute here with cancellation support)"); + } + + Console.WriteLine("✓ Cancellation scope disposed - back to normal operation"); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + + Console.WriteLine(); + } + + private static void DemoNestedScopes() + { + Console.WriteLine("2. Nested Scopes Example"); + Console.WriteLine("-------------------------"); + + using var outerToken = new CancellationTokenSource(); + using var innerToken = new CancellationTokenSource(); + IDatabase? database = null; // In real usage: redis.GetDatabase() + + try + { + using (database?.WithCancellation(outerToken.Token)) + { + Console.WriteLine("✓ Outer scope: Using outer cancellation token"); + + using (database?.WithCancellation(innerToken.Token)) + { + Console.WriteLine("✓ Inner scope: Using inner cancellation token (overrides outer)"); + } + + Console.WriteLine("✓ Back to outer scope: Using outer cancellation token again"); + } + + Console.WriteLine("✓ No cancellation scope: Normal operation"); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + + Console.WriteLine(); + } + + private static void DemoTimeoutUsage() + { + Console.WriteLine("3. Timeout Usage Example"); + Console.WriteLine("-------------------------"); + + IDatabase? database = null; // In real usage: redis.GetDatabase() + + try + { + using (database?.WithTimeout(TimeSpan.FromSeconds(5))) + { + Console.WriteLine("✓ Timeout scope: Operations will timeout after 5 seconds"); + } + + Console.WriteLine("✓ Timeout scope disposed"); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + + Console.WriteLine(); + } + } +} diff --git a/tests/SimpleCancellationDemo/SimpleCancellationDemo.csproj b/tests/SimpleCancellationDemo/SimpleCancellationDemo.csproj new file mode 100644 index 000000000..3fba134db --- /dev/null +++ b/tests/SimpleCancellationDemo/SimpleCancellationDemo.csproj @@ -0,0 +1,13 @@ + + + + Exe + net8.0 + enable + + + + + + + diff --git a/tests/StackExchange.Redis.Tests/CancellationTests.cs b/tests/StackExchange.Redis.Tests/CancellationTests.cs new file mode 100644 index 000000000..8643dc49e --- /dev/null +++ b/tests/StackExchange.Redis.Tests/CancellationTests.cs @@ -0,0 +1,220 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace StackExchange.Redis.Tests +{ + [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.WithCancellation(cts.Token)) + { + await Assert.ThrowsAsync(async () => + { + await db.StringSetAsync("test:cancelled", "value"); + }); + } + } + + [Fact] + public async Task WithCancellation_ValidToken_OperationSucceeds() + { + using var conn = Create(); + var db = conn.GetDatabase(); + + using var cts = new CancellationTokenSource(); + + using (db.WithCancellation(cts.Token)) + { + // This should succeed + await db.StringSetAsync("test:success", "value"); + var result = await db.StringGetAsync("test:success"); + Assert.Equal("value", result); + } + } + + [Fact] + public async Task WithTimeout_ShortTimeout_ThrowsOperationCanceledException() + { + using var conn = Create(); + var db = conn.GetDatabase(); + + using (db.WithTimeout(TimeSpan.FromMilliseconds(1))) + { + // This might throw due to timeout, but let's test the mechanism + try + { + await db.StringSetAsync("test:timeout", "value"); + // If it succeeds, that's fine too - Redis is fast + } + catch (OperationCanceledException) + { + // Expected for very short timeouts + } + } + } + + [Fact] + public async Task WithCancellationAndTimeout_CombinesCorrectly() + { + using var conn = Create(); + var db = conn.GetDatabase(); + + using var cts = new CancellationTokenSource(); + + using (db.WithCancellationAndTimeout(cts.Token, TimeSpan.FromSeconds(10))) + { + // This should succeed with both cancellation and timeout + await db.StringSetAsync("test:combined", "value"); + var result = await db.StringGetAsync("test:combined"); + Assert.Equal("value", result); + } + } + + [Fact] + public async Task NestedScopes_InnerScopeTakesPrecedence() + { + using var conn = Create(); + var db = conn.GetDatabase(); + + using var outerCts = new CancellationTokenSource(); + using var innerCts = new CancellationTokenSource(); + + using (db.WithCancellation(outerCts.Token)) + { + // Outer scope active + await db.StringSetAsync("test:outer", "value1"); + + using (db.WithCancellation(innerCts.Token)) + { + // Inner scope should take precedence + await db.StringSetAsync("test:inner", "value2"); + } + + // Back to outer scope + await db.StringSetAsync("test:outer2", "value3"); + } + + // Verify all operations succeeded + Assert.Equal("value1", await db.StringGetAsync("test:outer")); + Assert.Equal("value2", await db.StringGetAsync("test:inner")); + Assert.Equal("value3", await db.StringGetAsync("test:outer2")); + } + + [Fact] + public async Task WithoutAmbientCancellation_OperationsWorkNormally() + { + using var conn = Create(); + var db = conn.GetDatabase(); + + // No ambient cancellation - should work normally + await db.StringSetAsync("test:normal", "value"); + var result = await db.StringGetAsync("test:normal"); + Assert.Equal("value", result); + } + + [Fact] + public async Task CancellationDuringOperation_CancelsGracefully() + { + using var conn = Create(); + var db = conn.GetDatabase(); + + using var cts = new CancellationTokenSource(); + + using (db.WithCancellation(cts.Token)) + { + // Start an operation and cancel it mid-flight + var task = db.StringSetAsync("test:cancel-during", "value"); + + // Cancel after a short delay + _ = Task.Run(async () => + { + await Task.Delay(10); + cts.Cancel(); + }); + + try + { + await task; + // If it completes before cancellation, that's fine + } + catch (OperationCanceledException) + { + // Expected if cancellation happens during operation + } + } + } + + [Fact] + public void GetCurrentContext_ReturnsCorrectContext() + { + using var conn = Create(); + var db = conn.GetDatabase(); + + // No context initially + var context = RedisCancellationExtensions.GetCurrentContext(); + Assert.Null(context); + + using var cts = new CancellationTokenSource(); + using (db.WithCancellation(cts.Token)) + { + context = RedisCancellationExtensions.GetCurrentContext(); + Assert.NotNull(context); + Assert.Equal(cts.Token, context.Token); + Assert.Null(context.Timeout); + } + + using (db.WithTimeout(TimeSpan.FromSeconds(5))) + { + context = RedisCancellationExtensions.GetCurrentContext(); + Assert.NotNull(context); + Assert.False(context.Token.CanBeCanceled); + Assert.Equal(TimeSpan.FromSeconds(5), context.Timeout); + } + + // Context should be null again + context = RedisCancellationExtensions.GetCurrentContext(); + Assert.Null(context); + } + + [Fact] + public async Task PubSub_WithCancellation_WorksCorrectly() + { + using var conn = Create(); + var subscriber = conn.GetSubscriber(); + + using var cts = new CancellationTokenSource(); + + using (subscriber.WithCancellation(cts.Token)) + { + // Test pub/sub operations with cancellation + var channel = RedisChannel.Literal(Me()); + var messageReceived = new TaskCompletionSource(); + + await subscriber.SubscribeAsync(channel, (ch, msg) => + { + messageReceived.TrySetResult(true); + }); + + await subscriber.PublishAsync(channel, "test message"); + + // Wait for message with timeout + var received = await messageReceived.Task.WaitAsync(TimeSpan.FromSeconds(5)); + Assert.True(received); + } + } + } +} From 74202ba5dbc65bc79198e629a238205e21365167 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 14 Jul 2025 16:58:55 +0100 Subject: [PATCH 2/9] - reduce scope+context to single type - use Me() for key in tests --- .../RedisCancellationExtensions.cs | 101 ++++++------ .../CancellationTests.cs | 154 +++++++++--------- .../Helpers/TaskExtensions.cs | 21 +++ 3 files changed, 152 insertions(+), 124 deletions(-) create mode 100644 tests/StackExchange.Redis.Tests/Helpers/TaskExtensions.cs diff --git a/src/StackExchange.Redis/RedisCancellationExtensions.cs b/src/StackExchange.Redis/RedisCancellationExtensions.cs index c8fdef093..7d41e6a44 100644 --- a/src/StackExchange.Redis/RedisCancellationExtensions.cs +++ b/src/StackExchange.Redis/RedisCancellationExtensions.cs @@ -8,8 +8,6 @@ namespace StackExchange.Redis /// public static class RedisCancellationExtensions { - private static readonly AsyncLocal _context = new(); - /// /// Sets an ambient cancellation token that will be used for all Redis operations /// in the current async context until the returned scope is disposed. @@ -27,9 +25,7 @@ public static class RedisCancellationExtensions /// /// public static IDisposable WithCancellation(this IRedisAsync redis, CancellationToken cancellationToken) - { - return new CancellationScope(cancellationToken, null); - } + => new CancellationScope(cancellationToken, null); /// /// Sets an ambient timeout that will be used for all Redis operations @@ -47,9 +43,7 @@ public static IDisposable WithCancellation(this IRedisAsync redis, CancellationT /// /// public static IDisposable WithTimeout(this IRedisAsync redis, TimeSpan timeout) - { - return new CancellationScope(default, timeout); - } + => new CancellationScope(CancellationToken.None, timeout); /// /// Sets both an ambient cancellation token and timeout that will be used for all Redis operations @@ -71,9 +65,7 @@ public static IDisposable WithCancellationAndTimeout( this IRedisAsync redis, CancellationToken cancellationToken, TimeSpan timeout) - { - return new CancellationScope(cancellationToken, timeout); - } + => new CancellationScope(cancellationToken, timeout); /// /// Gets the effective cancellation token for the current async context, @@ -81,56 +73,27 @@ public static IDisposable WithCancellationAndTimeout( /// /// The effective cancellation token, or CancellationToken.None if no ambient context is set. internal static CancellationToken GetEffectiveCancellationToken() - { - var context = _context.Value; - return context?.GetEffectiveToken() ?? default; - } + => _context.Value?.Token ?? CancellationToken.None; /// /// Gets the current cancellation context for diagnostic purposes. /// /// The current context, or null if no ambient context is set. - internal static CancellationContext? GetCurrentContext() => _context.Value; + internal static object? GetCurrentScope() => _context.Value; - /// - /// Represents the cancellation context for Redis operations. - /// - internal record CancellationContext(CancellationToken Token, TimeSpan? Timeout) - { - /// - /// Gets the effective cancellation token, combining the explicit token with any timeout. - /// - /// A cancellation token that will be cancelled when either the explicit token is cancelled or the timeout expires. - public CancellationToken GetEffectiveToken() - { - if (!Timeout.HasValue) return Token; - - var timeoutSource = new CancellationTokenSource(Timeout.Value); - return Token.CanBeCanceled - ? CancellationTokenSource.CreateLinkedTokenSource(Token, timeoutSource.Token).Token - : timeoutSource.Token; - } - - /// - /// Gets a string representation of this context for debugging. - /// - public override string ToString() - { - var parts = new System.Collections.Generic.List(); - if (Token.CanBeCanceled) parts.Add($"Token: {(Token.IsCancellationRequested ? "Cancelled" : "Active")}"); - if (Timeout.HasValue) parts.Add($"Timeout: {Timeout.Value.TotalMilliseconds}ms"); - return parts.Count > 0 ? string.Join(", ", parts) : "None"; - } - } + private static readonly AsyncLocal _context = new(); /// /// A disposable scope that manages the ambient cancellation context. /// private sealed class CancellationScope : IDisposable { - private readonly CancellationContext? _previous; + private readonly CancellationTokenSource? _ownedSource; + private readonly CancellationScope? _previous; private bool _disposed; + public CancellationToken Token { get; } + /// /// Creates a new cancellation scope with the specified token and timeout. /// @@ -139,7 +102,42 @@ private sealed class CancellationScope : IDisposable public CancellationScope(CancellationToken cancellationToken, TimeSpan? timeout) { _previous = _context.Value; - _context.Value = new CancellationContext(cancellationToken, timeout); + 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; } /// @@ -149,8 +147,13 @@ public void Dispose() { if (!_disposed) { - _context.Value = _previous; _disposed = true; + if (ReferenceEquals(_context.Value, this)) + { + // reinstate the previous context + _context.Value = _previous; + } + _ownedSource?.Dispose(); } } } diff --git a/tests/StackExchange.Redis.Tests/CancellationTests.cs b/tests/StackExchange.Redis.Tests/CancellationTests.cs index 8643dc49e..df3b882e0 100644 --- a/tests/StackExchange.Redis.Tests/CancellationTests.cs +++ b/tests/StackExchange.Redis.Tests/CancellationTests.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using StackExchange.Redis.Tests.Helpers; using Xunit; using Xunit.Abstractions; @@ -11,6 +12,62 @@ public class CancellationTests : TestBase { public CancellationTests(ITestOutputHelper output, SharedConnectionFixture fixture) : base(output, fixture) { } + [Fact] + public async Task GetEffectiveCancellationToken_Nesting() + { + // this is a pure test - no database access + IDatabase? db = null!; + + // No context initially + Assert.Null(RedisCancellationExtensions.GetCurrentScope()); + Assert.Equal(CancellationToken.None, RedisCancellationExtensions.GetEffectiveCancellationToken()); + + using var cts = new CancellationTokenSource(); + using (var outer = db.WithCancellation(cts.Token)) + { + Assert.NotNull(outer); + Assert.Same(outer, RedisCancellationExtensions.GetCurrentScope()); + Assert.Equal(cts.Token, RedisCancellationExtensions.GetEffectiveCancellationToken()); + + // nest with timeout + using (var inner = db.WithTimeout(TimeSpan.FromSeconds(0.5))) + { + Assert.NotNull(inner); + Assert.Same(inner, RedisCancellationExtensions.GetCurrentScope()); + var active = RedisCancellationExtensions.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, RedisCancellationExtensions.GetEffectiveCancellationToken()); + } + + // back to outer + Assert.Same(outer, RedisCancellationExtensions.GetCurrentScope()); + Assert.Equal(cts.Token, RedisCancellationExtensions.GetEffectiveCancellationToken()); + + // nest with suppression + using (var inner = db.WithCancellation(CancellationToken.None)) + { + Assert.NotNull(inner); + Assert.Same(inner, RedisCancellationExtensions.GetCurrentScope()); + Assert.Equal(CancellationToken.None, RedisCancellationExtensions.GetEffectiveCancellationToken()); + } + + // back to outer + Assert.Same(outer, RedisCancellationExtensions.GetCurrentScope()); + Assert.Equal(cts.Token, RedisCancellationExtensions.GetEffectiveCancellationToken()); + } + Assert.Null(RedisCancellationExtensions.GetCurrentScope()); + Assert.Equal(CancellationToken.None, RedisCancellationExtensions.GetEffectiveCancellationToken()); + } + [Fact] public async Task WithCancellation_CancelledToken_ThrowsOperationCanceledException() { @@ -22,9 +79,9 @@ public async Task WithCancellation_CancelledToken_ThrowsOperationCanceledExcepti using (db.WithCancellation(cts.Token)) { - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAnyAsync(async () => { - await db.StringSetAsync("test:cancelled", "value"); + await db.StringSetAsync(Me(), "value"); }); } } @@ -39,9 +96,10 @@ public async Task WithCancellation_ValidToken_OperationSucceeds() using (db.WithCancellation(cts.Token)) { + RedisKey key = Me(); // This should succeed - await db.StringSetAsync("test:success", "value"); - var result = await db.StringGetAsync("test:success"); + await db.StringSetAsync(key, "value"); + var result = await db.StringGetAsync(key); Assert.Equal("value", result); } } @@ -57,7 +115,7 @@ public async Task WithTimeout_ShortTimeout_ThrowsOperationCanceledException() // This might throw due to timeout, but let's test the mechanism try { - await db.StringSetAsync("test:timeout", "value"); + await db.StringSetAsync(Me(), "value"); // If it succeeds, that's fine too - Redis is fast } catch (OperationCanceledException) @@ -78,8 +136,9 @@ public async Task WithCancellationAndTimeout_CombinesCorrectly() using (db.WithCancellationAndTimeout(cts.Token, TimeSpan.FromSeconds(10))) { // This should succeed with both cancellation and timeout - await db.StringSetAsync("test:combined", "value"); - var result = await db.StringGetAsync("test:combined"); + RedisKey key = Me(); + await db.StringSetAsync(key, "value"); + var result = await db.StringGetAsync(key); Assert.Equal("value", result); } } @@ -93,25 +152,28 @@ public async Task NestedScopes_InnerScopeTakesPrecedence() using var outerCts = new CancellationTokenSource(); using var innerCts = new CancellationTokenSource(); + RedisKey key1 = Me() + ":outer", + key2 = Me() + ":inner", + key3 = Me() + ":outer2"; using (db.WithCancellation(outerCts.Token)) { // Outer scope active - await db.StringSetAsync("test:outer", "value1"); + await db.StringSetAsync(key1, "value1"); using (db.WithCancellation(innerCts.Token)) { // Inner scope should take precedence - await db.StringSetAsync("test:inner", "value2"); + await db.StringSetAsync(key2, "value2"); } // Back to outer scope - await db.StringSetAsync("test:outer2", "value3"); + await db.StringSetAsync(key3, "value3"); } // Verify all operations succeeded - Assert.Equal("value1", await db.StringGetAsync("test:outer")); - Assert.Equal("value2", await db.StringGetAsync("test:inner")); - Assert.Equal("value3", await db.StringGetAsync("test:outer2")); + Assert.Equal("value1", await db.StringGetAsync(key1)); + Assert.Equal("value2", await db.StringGetAsync(key2)); + Assert.Equal("value3", await db.StringGetAsync(key3)); } [Fact] @@ -121,8 +183,9 @@ public async Task WithoutAmbientCancellation_OperationsWorkNormally() var db = conn.GetDatabase(); // No ambient cancellation - should work normally - await db.StringSetAsync("test:normal", "value"); - var result = await db.StringGetAsync("test:normal"); + RedisKey key = Me(); + await db.StringSetAsync(key, "value"); + var result = await db.StringGetAsync(key); Assert.Equal("value", result); } @@ -137,7 +200,7 @@ public async Task CancellationDuringOperation_CancelsGracefully() using (db.WithCancellation(cts.Token)) { // Start an operation and cancel it mid-flight - var task = db.StringSetAsync("test:cancel-during", "value"); + var task = db.StringSetAsync(Me(), "value"); // Cancel after a short delay _ = Task.Run(async () => @@ -157,64 +220,5 @@ public async Task CancellationDuringOperation_CancelsGracefully() } } } - - [Fact] - public void GetCurrentContext_ReturnsCorrectContext() - { - using var conn = Create(); - var db = conn.GetDatabase(); - - // No context initially - var context = RedisCancellationExtensions.GetCurrentContext(); - Assert.Null(context); - - using var cts = new CancellationTokenSource(); - using (db.WithCancellation(cts.Token)) - { - context = RedisCancellationExtensions.GetCurrentContext(); - Assert.NotNull(context); - Assert.Equal(cts.Token, context.Token); - Assert.Null(context.Timeout); - } - - using (db.WithTimeout(TimeSpan.FromSeconds(5))) - { - context = RedisCancellationExtensions.GetCurrentContext(); - Assert.NotNull(context); - Assert.False(context.Token.CanBeCanceled); - Assert.Equal(TimeSpan.FromSeconds(5), context.Timeout); - } - - // Context should be null again - context = RedisCancellationExtensions.GetCurrentContext(); - Assert.Null(context); - } - - [Fact] - public async Task PubSub_WithCancellation_WorksCorrectly() - { - using var conn = Create(); - var subscriber = conn.GetSubscriber(); - - using var cts = new CancellationTokenSource(); - - using (subscriber.WithCancellation(cts.Token)) - { - // Test pub/sub operations with cancellation - var channel = RedisChannel.Literal(Me()); - var messageReceived = new TaskCompletionSource(); - - await subscriber.SubscribeAsync(channel, (ch, msg) => - { - messageReceived.TrySetResult(true); - }); - - await subscriber.PublishAsync(channel, "test message"); - - // Wait for message with timeout - var received = await messageReceived.Task.WaitAsync(TimeSpan.FromSeconds(5)); - Assert.True(received); - } - } } } 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 From 2c6bce1440404f5a483efbf78ab918dcf0129fb5 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 14 Jul 2025 17:03:19 +0100 Subject: [PATCH 3/9] clarify inconclusive if too fast to detect timeout --- tests/StackExchange.Redis.Tests/CancellationTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/StackExchange.Redis.Tests/CancellationTests.cs b/tests/StackExchange.Redis.Tests/CancellationTests.cs index df3b882e0..d7caa9682 100644 --- a/tests/StackExchange.Redis.Tests/CancellationTests.cs +++ b/tests/StackExchange.Redis.Tests/CancellationTests.cs @@ -117,6 +117,7 @@ public async Task WithTimeout_ShortTimeout_ThrowsOperationCanceledException() { await db.StringSetAsync(Me(), "value"); // If it succeeds, that's fine too - Redis is fast + Skip.Inconclusive("Redis is too fast for this test."); } catch (OperationCanceledException) { From 121a41aaae9660d3dcc29aa800d3ac770cbfeb8a Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 16 Jul 2025 09:48:34 +0100 Subject: [PATCH 4/9] intermediate broken --- StackExchange.Redis.sln | 32 +- docs/AmbientCancellation.md | 195 ------- docs/CancellationTimeout.md | 77 +++ docs/docs.csproj | 6 + src/StackExchange.Redis/Condition.cs | 21 +- .../ConnectionMultiplexer.cs | 33 +- src/StackExchange.Redis/CursorEnumerable.cs | 14 +- src/StackExchange.Redis/Message.cs | 292 ++++++----- src/StackExchange.Redis/PhysicalBridge.cs | 6 +- src/StackExchange.Redis/PhysicalConnection.cs | 8 +- .../PublicAPI/PublicAPI.Unshipped.txt | 9 +- src/StackExchange.Redis/RedisBase.cs | 12 +- src/StackExchange.Redis/RedisBatch.cs | 4 +- .../RedisCancellationExtensions.cs | 92 +++- src/StackExchange.Redis/RedisDatabase.cs | 488 +++++++++--------- src/StackExchange.Redis/RedisServer.cs | 276 +++++----- src/StackExchange.Redis/RedisSubscriber.cs | 14 +- src/StackExchange.Redis/RedisTransaction.cs | 22 +- src/StackExchange.Redis/ResultBox.cs | 93 ++-- src/StackExchange.Redis/ResultProcessor.cs | 9 +- src/StackExchange.Redis/ServerEndPoint.cs | 55 +- .../CancellationTests.cs | 20 +- 22 files changed, 890 insertions(+), 888 deletions(-) delete mode 100644 docs/AmbientCancellation.md create mode 100644 docs/CancellationTimeout.md create mode 100644 docs/docs.csproj diff --git a/StackExchange.Redis.sln b/StackExchange.Redis.sln index 8d996b545..9aa9994ec 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}" @@ -144,6 +119,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleTestBaseline", "test EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleCancellationDemo", "tests\SimpleCancellationDemo\SimpleCancellationDemo.csproj", "{368A06AA-9DEB-4C55-B9CF-A1070B85E502}" 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 @@ -198,6 +175,10 @@ Global {368A06AA-9DEB-4C55-B9CF-A1070B85E502}.Debug|Any CPU.Build.0 = Debug|Any CPU {368A06AA-9DEB-4C55-B9CF-A1070B85E502}.Release|Any CPU.ActiveCfg = Release|Any CPU {368A06AA-9DEB-4C55-B9CF-A1070B85E502}.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 @@ -215,7 +196,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/AmbientCancellation.md b/docs/AmbientCancellation.md deleted file mode 100644 index e8d8f0452..000000000 --- a/docs/AmbientCancellation.md +++ /dev/null @@ -1,195 +0,0 @@ -# Ambient Cancellation Support - -StackExchange.Redis now supports ambient cancellation using `AsyncLocal` to provide cancellation tokens to Redis operations without expanding the API surface. - -## Overview - -The ambient cancellation feature allows you to set a cancellation context that applies to all Redis operations within an async scope. This provides a clean way to handle timeouts and cancellation without adding `CancellationToken` parameters to every method. - -## Key Features - -- **Zero API Surface Impact**: No new parameters added to existing methods -- **Scoped Cancellation**: Uses `using` statements for proper scope management -- **Timeout Support**: Can specify timeouts that are converted to cancellation tokens -- **Composable**: Can combine cancellation tokens with timeouts -- **Nested Scopes**: Inner scopes override outer scopes -- **Backward Compatible**: All existing code continues to work unchanged - -## Usage - -### Basic Cancellation - -```csharp -using var cts = new CancellationTokenSource(); - -using (database.WithCancellation(cts.Token)) -{ - await database.StringSetAsync("key", "value"); - var value = await database.StringGetAsync("key"); - // Both operations use the cancellation token -} -``` - -### Timeout Support - -```csharp -using (database.WithTimeout(TimeSpan.FromSeconds(5))) -{ - await database.StringSetAsync("key", "value"); - // Operation will be cancelled if it takes longer than 5 seconds -} -``` - -### Combined Cancellation and Timeout - -```csharp -using var cts = new CancellationTokenSource(); - -using (database.WithCancellationAndTimeout(cts.Token, TimeSpan.FromSeconds(10))) -{ - await database.StringSetAsync("key", "value"); - // Operation will be cancelled if either the token is cancelled OR 10 seconds elapse -} -``` - -### Nested Scopes - -```csharp -using var outerToken = new CancellationTokenSource(); -using var innerToken = new CancellationTokenSource(); - -using (database.WithCancellation(outerToken.Token)) -{ - await database.StringSetAsync("key1", "value1"); // Uses outerToken - - using (database.WithCancellation(innerToken.Token)) - { - await database.StringSetAsync("key2", "value2"); // Uses innerToken - } - - await database.StringSetAsync("key3", "value3"); // Uses outerToken again -} -``` - -### Pub/Sub Operations - -```csharp -using var cts = new CancellationTokenSource(); - -using (subscriber.WithCancellation(cts.Token)) -{ - await subscriber.SubscribeAsync("channel", handler); - await subscriber.PublishAsync("channel", "message"); - // Both operations use the cancellation token -} -``` - -## Extension Methods - -The functionality is provided through extension methods on `IRedisAsync`: - -- `WithCancellation(CancellationToken)` - Sets ambient cancellation token -- `WithTimeout(TimeSpan)` - Sets ambient timeout (converted to cancellation token) -- `WithCancellationAndTimeout(CancellationToken, TimeSpan)` - Sets both cancellation and timeout - -## Implementation Details - -### AsyncLocal Context - -The implementation uses `AsyncLocal` to flow the cancellation context through async operations: - -```csharp -private static readonly AsyncLocal _context = new(); -``` - -### Scope Management - -Each `WithCancellation` call returns an `IDisposable` that manages the scope: - -```csharp -public static IDisposable WithCancellation(this IRedisAsync redis, CancellationToken cancellationToken) -{ - return new CancellationScope(cancellationToken, null); -} -``` - -### Integration Points - -The cancellation token is applied at the core execution level: - -1. `RedisBase.ExecuteAsync` retrieves the ambient cancellation token -2. `ConnectionMultiplexer.ExecuteAsyncImpl` accepts the cancellation token -3. `TaskResultBox` registers for cancellation and properly handles cancellation - -### Timeout Handling - -Timeouts are converted to cancellation tokens using `CancellationTokenSource`: - -```csharp -public CancellationToken GetEffectiveToken() -{ - if (!Timeout.HasValue) return Token; - - var timeoutSource = new CancellationTokenSource(Timeout.Value); - return Token.CanBeCanceled - ? CancellationTokenSource.CreateLinkedTokenSource(Token, timeoutSource.Token).Token - : timeoutSource.Token; -} -``` - -## Error Handling - -When an operation is cancelled, it throws an `OperationCanceledException`: - -```csharp -try -{ - using (database.WithCancellation(cancelledToken)) - { - await database.StringSetAsync("key", "value"); - } -} -catch (OperationCanceledException) -{ - // Handle cancellation -} -``` - -## Performance Considerations - -- **Minimal Overhead**: When no ambient cancellation is set, there's virtually no performance impact -- **Efficient Scoping**: Uses struct-based scoping to minimize allocations -- **Proper Cleanup**: Cancellation registrations are properly disposed when operations complete - -## Limitations - -- **Client-Side Only**: Redis doesn't support server-side cancellation, so cancellation only prevents the client from waiting for a response -- **In-Flight Operations**: Operations that have already been sent to the server will continue executing on the server even if cancelled on the client -- **Connection Health**: Cancelled operations don't affect connection health or availability - -## Migration - -Existing code requires no changes. The ambient cancellation is purely additive: - -```csharp -// This continues to work exactly as before -await database.StringSetAsync("key", "value"); - -// This adds cancellation support -using (database.WithCancellation(cancellationToken)) -{ - await database.StringSetAsync("key", "value"); -} -``` - -## Best Practices - -1. **Use `using` statements** to ensure proper scope cleanup -2. **Prefer cancellation tokens over timeouts** when possible for better control -3. **Handle `OperationCanceledException`** appropriately in your application -4. **Don't rely on cancellation for server-side operation termination** -5. **Test cancellation scenarios** to ensure your application handles them gracefully - -## Examples - -See `examples/CancellationExample.cs` for comprehensive usage examples and `tests/StackExchange.Redis.Tests/CancellationTests.cs` for test cases demonstrating the functionality. diff --git a/docs/CancellationTimeout.md b/docs/CancellationTimeout.md new file mode 100644 index 000000000..071a6a887 --- /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.WithTimeout(5000)) // 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 + + +using (database.Multiplexer.WithCancellationAndTimeout(yourToken, TimeSpan.FromSeconds(10))) +{ + 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/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/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 b0817c895..f02740db6 100644 --- a/src/StackExchange.Redis/ConnectionMultiplexer.cs +++ b/src/StackExchange.Redis/ConnectionMultiplexer.cs @@ -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.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) { @@ -2063,7 +2077,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 +2093,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 +2114,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; @@ -2134,7 +2151,7 @@ static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, Value { // 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(out tcs, state, message.CancellationToken); + source = TaskResultBox.Create(message.CancellationToken, out tcs, state); } var write = TryPushMessageToBridgeAsync(message, processor, source, ref server); if (!write.IsCompletedSuccessfully) @@ -2187,7 +2204,7 @@ static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, Value { // 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(out tcs, state, message.CancellationToken); + source = TaskResultBox.Create(message.CancellationToken, out tcs, state); } var write = TryPushMessageToBridgeAsync(message, processor, source!, ref server); if (!write.IsCompletedSuccessfully) diff --git a/src/StackExchange.Redis/CursorEnumerable.cs b/src/StackExchange.Redis/CursorEnumerable.cs index 55d93d6a6..79e72cfe0 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; @@ -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/Message.cs b/src/StackExchange.Redis/Message.cs index 0d75b157c..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; @@ -91,7 +91,7 @@ internal abstract class Message : ICompletable 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) @@ -120,7 +120,7 @@ protected Message(int db, CommandFlags flags, RedisCommand command) if (primaryOnly) SetPrimaryOnly(); // Get ambient cancellation token when the message is created - _cancellationToken = RedisCancellationExtensions.GetEffectiveCancellationToken(); + _cancellationToken = cancellationToken; CreatedDateTime = DateTime.UtcNow; CreatedTimestamp = Stopwatch.GetTimestamp(); @@ -231,47 +231,47 @@ internal void WithHighIntegrity(uint value) 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); @@ -285,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; @@ -295,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, @@ -329,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, @@ -340,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, @@ -352,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, @@ -365,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, @@ -379,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, @@ -394,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. /// @@ -460,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); @@ -497,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); @@ -515,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) @@ -602,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) @@ -815,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; @@ -866,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; @@ -881,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; @@ -894,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) { @@ -907,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; @@ -925,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(); @@ -953,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; @@ -977,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++) { @@ -1011,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; @@ -1030,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) { @@ -1043,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++) { @@ -1066,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++) { @@ -1099,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; @@ -1118,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++) { @@ -1148,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++) { @@ -1170,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++) { @@ -1195,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(); @@ -1216,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(); @@ -1240,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(); @@ -1267,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(); @@ -1298,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(); @@ -1332,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(); @@ -1377,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(); @@ -1412,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(); @@ -1451,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(); @@ -1494,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(); @@ -1541,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(); @@ -1592,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(); @@ -1631,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); @@ -1644,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++) @@ -1671,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; @@ -1690,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; @@ -1714,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; @@ -1731,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(); @@ -1751,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(); @@ -1774,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(); @@ -1802,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 { } @@ -1819,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 2a5dc601e..800be70ae 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -1,5 +1,8 @@ #nullable enable StackExchange.Redis.RedisCancellationExtensions -static StackExchange.Redis.RedisCancellationExtensions.WithCancellation(this StackExchange.Redis.IRedisAsync! redis, System.Threading.CancellationToken cancellationToken) -> System.IDisposable! -static StackExchange.Redis.RedisCancellationExtensions.WithCancellationAndTimeout(this StackExchange.Redis.IRedisAsync! redis, System.Threading.CancellationToken cancellationToken, System.TimeSpan timeout) -> System.IDisposable! -static StackExchange.Redis.RedisCancellationExtensions.WithTimeout(this StackExchange.Redis.IRedisAsync! redis, System.TimeSpan timeout) -> System.IDisposable! \ No newline at end of file +static StackExchange.Redis.RedisCancellationExtensions.GetEffectiveCancellationToken(this StackExchange.Redis.IConnectionMultiplexer! redis) -> System.Threading.CancellationToken +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 2a0f6b5b8..384f43a02 100644 --- a/src/StackExchange.Redis/RedisBase.cs +++ b/src/StackExchange.Redis/RedisBase.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; namespace StackExchange.Redis @@ -18,6 +19,8 @@ internal RedisBase(ConnectionMultiplexer multiplexer, object? asyncState) IConnectionMultiplexer IRedisAsync.Multiplexer => multiplexer; + internal CancellationToken GetEffectiveCancellationToken() => multiplexer.GetEffectiveCancellationToken(); + public virtual TimeSpan Ping(CommandFlags flags = CommandFlags.None) { var msg = GetTimerMessage(flags); @@ -116,15 +119,16 @@ private ResultProcessor.TimingProcessor.TimerMessage GetTimerMessage(CommandFlag { // do the best we can with available commands 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..2e4c023dd 100644 --- a/src/StackExchange.Redis/RedisBatch.cs +++ b/src/StackExchange.Redis/RedisBatch.cs @@ -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, asyncState); task = tcs.Task; message.SetSource(source, processor); } @@ -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, asyncState); task = tcs.Task; message.SetSource(source!, processor); } diff --git a/src/StackExchange.Redis/RedisCancellationExtensions.cs b/src/StackExchange.Redis/RedisCancellationExtensions.cs index 7d41e6a44..a0d75b891 100644 --- a/src/StackExchange.Redis/RedisCancellationExtensions.cs +++ b/src/StackExchange.Redis/RedisCancellationExtensions.cs @@ -24,8 +24,8 @@ public static class RedisCancellationExtensions /// } /// /// - public static IDisposable WithCancellation(this IRedisAsync redis, CancellationToken cancellationToken) - => new CancellationScope(cancellationToken, null); + 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 @@ -42,8 +42,26 @@ public static IDisposable WithCancellation(this IRedisAsync redis, CancellationT /// } /// /// - public static IDisposable WithTimeout(this IRedisAsync redis, TimeSpan timeout) - => new CancellationScope(CancellationToken.None, timeout); + 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 @@ -62,18 +80,61 @@ public static IDisposable WithTimeout(this IRedisAsync redis, TimeSpan timeout) /// /// public static IDisposable WithCancellationAndTimeout( - this IRedisAsync redis, + this IConnectionMultiplexer redis, CancellationToken cancellationToken, TimeSpan timeout) - => new CancellationScope(cancellationToken, 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() - => _context.Value?.Token ?? CancellationToken.None; + public static CancellationToken GetEffectiveCancellationToken(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 fromScope = scope.Target; // need to null-check because weak-ref / GC + if (fromScope is not null && fromScope.Equals(redis)) + { + var token = scope.Token; + token.ThrowIfCancellationRequested(); + return token; + } + scope = scope.Previous; + } + } + + return CancellationToken.None; + } /// /// Gets the current cancellation context for diagnostic purposes. @@ -86,22 +147,23 @@ internal static CancellationToken GetEffectiveCancellationToken() /// /// A disposable scope that manages the ambient cancellation context. /// - private sealed class CancellationScope : IDisposable + private sealed class CancellationScope : WeakReference, IDisposable { private readonly CancellationTokenSource? _ownedSource; - private readonly CancellationScope? _previous; - private bool _disposed; - 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(CancellationToken cancellationToken, TimeSpan? timeout) + public CancellationScope(object redis, CancellationToken cancellationToken, TimeSpan? timeout) + : base(redis ?? throw new ArgumentNullException(nameof(redis))) { - _previous = _context.Value; + Previous = _context.Value; if (timeout.HasValue) { // has a timeout @@ -151,7 +213,7 @@ public void Dispose() if (ReferenceEquals(_context.Value, this)) { // reinstate the previous context - _context.Value = _previous; + _context.Value = Previous; } _ownedSource?.Dispose(); } diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 716176662..24a375216 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; @@ -43,13 +44,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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -65,25 +66,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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -99,13 +100,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, StackExchange.Redis.GeoPosition.GetRedisUnit(unit), this.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, StackExchange.Redis.GeoPosition.GetRedisUnit(unit), this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.NullableDouble); } @@ -114,7 +115,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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.NullableStringArray, defaultValue: Array.Empty()); } @@ -123,19 +124,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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.String); } @@ -144,7 +145,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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisGeoPositionArray, defaultValue: Array.Empty()); } @@ -153,19 +154,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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisGeoPosition); } @@ -212,9 +213,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 +249,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 +352,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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -371,19 +373,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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -424,7 +426,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 +438,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 +468,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, this.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,31 +482,31 @@ 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, this.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, this.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, this.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, this.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); } @@ -512,95 +514,95 @@ public Task HashGetAsync(RedisKey key, RedisValue[] hashFields, Co { 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); + var msg = Message.Create(Database, flags, RedisCommand.HMGET, key, hashFields, this.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, this.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, this.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, this.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, this.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, this.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, this.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, this.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, this.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, this.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, this.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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.HashEntryArray, defaultValue: Array.Empty()); } @@ -645,8 +647,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, this.GetEffectiveCancellationToken()) + : Message.Create(Database, flags, when == When.Always ? RedisCommand.HSET : RedisCommand.HSETNX, key, hashField, value, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } @@ -659,7 +661,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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -667,14 +669,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, this.GetEffectiveCancellationToken()) + : Message.Create(Database, flags, when == When.Always ? RedisCommand.HSET : RedisCommand.HSETNX, key, hashField, value, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -686,50 +688,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, this.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, this.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, this.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, this.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, this.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, this.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, this.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, this.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 +741,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, this.GetEffectiveCancellationToken()); if (keys.Length != 0) { var features = GetFeatures(keys[0], flags, RedisCommand.PFCOUNT, out server); @@ -752,7 +754,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 +764,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,37 +776,39 @@ 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); } @@ -829,7 +833,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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.DemandZeroOrOne, server); } @@ -839,7 +843,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 +852,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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandZeroOrOne, server); } @@ -858,7 +862,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); @@ -876,49 +880,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, this.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, this.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, this.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, this.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, this.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, this.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,37 +964,37 @@ 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, this.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, this.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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.TimeSpanFromSeconds); } @@ -1055,63 +1059,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, this.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, this.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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -1133,10 +1137,10 @@ public Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, Message msg; 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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.TimeSpanFromMilliseconds, server); } - msg = Message.Create(Database, flags, RedisCommand.TTL, key); + msg = Message.Create(Database, flags, RedisCommand.TTL, key, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.TimeSpanFromSeconds); } @@ -1146,70 +1150,70 @@ public Task KeyRestoreAsync(RedisKey key, byte[] value, TimeSpan? expiry = null, Message msg; 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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.TimeSpanFromMilliseconds, server); } - msg = Message.Create(Database, flags, RedisCommand.TTL, key); + msg = Message.Create(Database, flags, RedisCommand.TTL, key, this.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, this.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, this.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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -1233,13 +1237,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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -1264,7 +1268,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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -1273,21 +1277,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, this.GetEffectiveCancellationToken()) : Message.Create(Database, flags, command, key, values, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -1296,74 +1301,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, this.GetEffectiveCancellationToken()) : Message.Create(Database, flags, command, key, values, this.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, this.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, this.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, this.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, this.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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -1375,13 +1381,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 +1399,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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -1415,21 +1421,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, this.GetEffectiveCancellationToken()) : Message.Create(Database, flags, command, key, values, this.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, this.GetEffectiveCancellationToken()) : Message.Create(Database, flags, RedisCommand.RPUSH, key, values, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -1438,38 +1444,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, this.GetEffectiveCancellationToken()) : Message.Create(Database, flags, command, key, values, this.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, this.GetEffectiveCancellationToken()) : Message.Create(Database, flags, RedisCommand.RPUSH, key, values, this.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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } @@ -1568,21 +1574,21 @@ public LCSMatchResult StringLongestCommonSubsequenceWithMatches(RedisKey key1, R 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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -1703,27 +1709,27 @@ public Task ScriptEvaluateReadOnlyAsync(byte[] hash, RedisKey[]? ke 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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -1777,25 +1783,25 @@ public Task SetCombineAsync(SetOperation operation, RedisKey[] key 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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.BooleanArray, defaultValue: Array.Empty()); } @@ -1813,25 +1819,25 @@ 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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -1849,13 +1855,13 @@ public Task SetMoveAsync(RedisKey source, RedisKey destination, RedisValue 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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -1863,8 +1869,8 @@ 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, this.GetEffectiveCancellationToken()) + : Message.Create(Database, flags, RedisCommand.SPOP, key, count, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -1903,7 +1909,7 @@ public Task SetRandomMembersAsync(RedisKey key, long count, Comman 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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } @@ -1911,13 +1917,13 @@ 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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -2140,19 +2146,19 @@ 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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.SortedSetWithScores, defaultValue: Array.Empty()); } @@ -2256,49 +2262,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, this.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, this.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, this.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, this.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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -2335,37 +2341,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, this.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, this.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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.SortedSetEntry); } @@ -3009,13 +3015,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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -3117,7 +3123,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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } @@ -3155,19 +3161,19 @@ public RedisValue[] StringGet(RedisKey[] keys, CommandFlags flags = CommandFlags 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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Lease); } @@ -3181,25 +3187,25 @@ public Task StringGetAsync(RedisKey[] keys, CommandFlags flags = C 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, this.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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -3248,7 +3254,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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Double); } @@ -3261,19 +3267,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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -3333,19 +3339,19 @@ 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, this.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, this.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, this.GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } @@ -3385,7 +3391,7 @@ 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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -3412,7 +3418,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, this.GetEffectiveCancellationToken()), _ => throw new ArgumentException("PERSIST cannot be used with when."), }; } @@ -3428,7 +3434,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, this.GetEffectiveCancellationToken()), _ => throw new ArgumentException("PERSIST cannot be used with when."), }; } @@ -3454,8 +3460,8 @@ private Message GetExpiryMessage( { 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, this.GetEffectiveCancellationToken()), + _ => Message.Create(Database, flags, millisecondsCommand, key, milliseconds, when.ToLiteral(), this.GetEffectiveCancellationToken()), }; } server = null; @@ -3464,8 +3470,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, this.GetEffectiveCancellationToken()), + _ => Message.Create(Database, flags, secondsCommand, key, seconds, when.ToLiteral(), this.GetEffectiveCancellationToken()), }; } @@ -3494,7 +3500,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) @@ -3538,7 +3544,8 @@ private Message GetSortedSetMultiPopMessage(RedisKey[] keys, Order order, long c RedisCommand.HMSET, key, hashFields[0].name, - hashFields[0].value); + hashFields[0].value, + this.GetEffectiveCancellationToken()); case 2: return Message.Create( Database, @@ -3548,7 +3555,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, + this.GetEffectiveCancellationToken()); default: var arr = new RedisValue[hashFields.Length * 2]; int offset = 0; @@ -3557,7 +3565,7 @@ 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, this.GetEffectiveCancellationToken()); } } @@ -3617,8 +3625,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 +3697,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 +3706,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."); @@ -3820,7 +3828,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, this.GetEffectiveCancellationToken()); } private Message? GetSortedSetAddMessage(RedisKey key, SortedSetEntry[] values, SortedSetWhen when, bool change, CommandFlags flags) @@ -3860,7 +3868,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, this.GetEffectiveCancellationToken()); } } @@ -4037,11 +4045,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, this.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, this.GetEffectiveCancellationToken()); } private Message GetSortedSetIntersectionLengthMessage(RedisKey[] keys, long limit, CommandFlags flags) @@ -4825,35 +4833,35 @@ public ScanEnumerable( private protected override ResultProcessor.ScanResult> 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 +4870,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 +4925,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) { @@ -4976,21 +4984,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; diff --git a/src/StackExchange.Redis/RedisServer.cs b/src/StackExchange.Redis/RedisServer.cs index 8810e1e2b..75ff589fa 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; @@ -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); } @@ -365,19 +367,19 @@ 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); } @@ -394,13 +396,13 @@ public async Task MakePrimaryAsync(ReplicationChangeOptions options, TextWriter? 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); + 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); + 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,7 +623,7 @@ 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() @@ -629,7 +632,7 @@ internal static Message CreateReplicaOfMessage(ServerEndPoint sendMessageTo, End 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; } @@ -642,7 +645,7 @@ internal static Message CreateReplicaOfMessage(ServerEndPoint sendMessageTo, End 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; } @@ -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..a59368876 100644 --- a/src/StackExchange.Redis/RedisSubscriber.cs +++ b/src/StackExchange.Redis/RedisSubscriber.cs @@ -204,7 +204,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, this.GetEffectiveCancellationToken()); msg.SetForSubscriptionBridge(); if (internalCall) { @@ -313,14 +313,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, this.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, this.GetEffectiveCancellationToken()); msg.SetInternalCall(); return ExecuteAsync(msg, ResultProcessor.ConnectionIdentity); } @@ -359,13 +359,13 @@ private Message CreatePingMessage(CommandFlags flags) 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); + 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 +383,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, this.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, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } diff --git a/src/StackExchange.Redis/RedisTransaction.cs b/src/StackExchange.Redis/RedisTransaction.cs index 04d7293ac..426287e7b 100644 --- a/src/StackExchange.Redis/RedisTransaction.cs +++ b/src/StackExchange.Redis/RedisTransaction.cs @@ -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; } @@ -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, asyncState); message.SetSource(source, processor); task = tcs.Task; } @@ -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, 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 @@ -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 fc6d23795..6416d744c 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,16 @@ 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 = GetCancelledException(CancellationToken, cancellationToken); void IResultBox.ActivateContinuations() { @@ -40,7 +45,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 +69,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 +89,7 @@ private SimpleResultBox() { } Exception = null; _value = default!; _perThreadInstance = this; + CancellationToken = CancellationToken.None; } return value; } @@ -87,19 +101,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 CancellationTokenRegistration _cancellationRegistration; - 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; @@ -125,36 +145,39 @@ private void ActivateContinuationsImpl() var val = _value; var ex = _exception; - // Clean up cancellation registration - try - { - _cancellationRegistration.Dispose(); - } - catch - { - // Ignore disposal errors - } - - if (ex == null) + if (ex is null) { TrySetResult(val); } else { - if (ex is TaskCanceledException or OperationCanceledException) 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) - { - return Create(out source, asyncState, default); - } - - public static IResultBox Create(out TaskCompletionSource source, object? asyncState, CancellationToken cancellationToken) + 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 @@ -162,20 +185,10 @@ 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); - - // Register for cancellation if the token can be cancelled - if (cancellationToken.CanBeCanceled) - { - obj._cancellationRegistration = cancellationToken.Register(() => - { - obj._exception = new OperationCanceledException("The Redis operation was cancelled.", cancellationToken); - obj.TrySetCanceled(cancellationToken); - }); - } - source = obj; return obj; } 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/CancellationTests.cs b/tests/StackExchange.Redis.Tests/CancellationTests.cs index d7caa9682..7fc30712b 100644 --- a/tests/StackExchange.Redis.Tests/CancellationTests.cs +++ b/tests/StackExchange.Redis.Tests/CancellationTests.cs @@ -23,14 +23,14 @@ public async Task GetEffectiveCancellationToken_Nesting() Assert.Equal(CancellationToken.None, RedisCancellationExtensions.GetEffectiveCancellationToken()); using var cts = new CancellationTokenSource(); - using (var outer = db.WithCancellation(cts.Token)) + using (var outer = db.Multiplexer.WithCancellation(cts.Token)) { Assert.NotNull(outer); Assert.Same(outer, RedisCancellationExtensions.GetCurrentScope()); Assert.Equal(cts.Token, RedisCancellationExtensions.GetEffectiveCancellationToken()); // nest with timeout - using (var inner = db.WithTimeout(TimeSpan.FromSeconds(0.5))) + using (var inner = db.Multiplexer.WithTimeout(TimeSpan.FromSeconds(0.5))) { Assert.NotNull(inner); Assert.Same(inner, RedisCancellationExtensions.GetCurrentScope()); @@ -53,7 +53,7 @@ public async Task GetEffectiveCancellationToken_Nesting() Assert.Equal(cts.Token, RedisCancellationExtensions.GetEffectiveCancellationToken()); // nest with suppression - using (var inner = db.WithCancellation(CancellationToken.None)) + using (var inner = db.Multiplexer.WithCancellation(CancellationToken.None)) { Assert.NotNull(inner); Assert.Same(inner, RedisCancellationExtensions.GetCurrentScope()); @@ -77,7 +77,7 @@ public async Task WithCancellation_CancelledToken_ThrowsOperationCanceledExcepti using var cts = new CancellationTokenSource(); cts.Cancel(); // Cancel immediately - using (db.WithCancellation(cts.Token)) + using (db.Multiplexer.WithCancellation(cts.Token)) { await Assert.ThrowsAnyAsync(async () => { @@ -94,7 +94,7 @@ public async Task WithCancellation_ValidToken_OperationSucceeds() using var cts = new CancellationTokenSource(); - using (db.WithCancellation(cts.Token)) + using (db.Multiplexer.WithCancellation(cts.Token)) { RedisKey key = Me(); // This should succeed @@ -110,7 +110,7 @@ public async Task WithTimeout_ShortTimeout_ThrowsOperationCanceledException() using var conn = Create(); var db = conn.GetDatabase(); - using (db.WithTimeout(TimeSpan.FromMilliseconds(1))) + using (db.Multiplexer.WithTimeout(TimeSpan.FromMilliseconds(1))) { // This might throw due to timeout, but let's test the mechanism try @@ -134,7 +134,7 @@ public async Task WithCancellationAndTimeout_CombinesCorrectly() using var cts = new CancellationTokenSource(); - using (db.WithCancellationAndTimeout(cts.Token, TimeSpan.FromSeconds(10))) + using (db.Multiplexer.WithCancellationAndTimeout(cts.Token, TimeSpan.FromSeconds(10))) { // This should succeed with both cancellation and timeout RedisKey key = Me(); @@ -156,12 +156,12 @@ public async Task NestedScopes_InnerScopeTakesPrecedence() RedisKey key1 = Me() + ":outer", key2 = Me() + ":inner", key3 = Me() + ":outer2"; - using (db.WithCancellation(outerCts.Token)) + using (db.Multiplexer.WithCancellation(outerCts.Token)) { // Outer scope active await db.StringSetAsync(key1, "value1"); - using (db.WithCancellation(innerCts.Token)) + using (db.Multiplexer.WithCancellation(innerCts.Token)) { // Inner scope should take precedence await db.StringSetAsync(key2, "value2"); @@ -198,7 +198,7 @@ public async Task CancellationDuringOperation_CancelsGracefully() using var cts = new CancellationTokenSource(); - using (db.WithCancellation(cts.Token)) + using (db.Multiplexer.WithCancellation(cts.Token)) { // Start an operation and cancel it mid-flight var task = db.StringSetAsync(Me(), "value"); From a5074a1e17cb6b1b65cdec641e7bba4b01d8c7c9 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 16 Jul 2025 14:20:05 +0100 Subject: [PATCH 5/9] intermediate, investigating weird connection oddity --- StackExchange.Redis.sln | 7 - .../ChannelMessageQueue.cs | 4 +- src/StackExchange.Redis/CursorEnumerable.cs | 2 +- src/StackExchange.Redis/Exceptions.cs | 2 + .../PublicAPI/PublicAPI.Unshipped.txt | 1 - src/StackExchange.Redis/RedisBase.cs | 48 +- src/StackExchange.Redis/RedisBatch.cs | 26 +- .../RedisCancellationExtensions.cs | 34 +- src/StackExchange.Redis/RedisDatabase.cs | 831 +++++++++--------- src/StackExchange.Redis/RedisServer.cs | 54 +- src/StackExchange.Redis/RedisSubscriber.cs | 75 +- src/StackExchange.Redis/RedisTransaction.cs | 26 +- tests/SimpleCancellationDemo/Program.cs | 118 --- .../SimpleCancellationDemo.csproj | 13 - .../StackExchange.Redis.Tests/BacklogTests.cs | 6 +- .../CancellationTests.cs | 343 ++++++-- .../ExceptionFactoryTests.cs | 10 +- .../ResultBoxTests.cs | 8 +- 18 files changed, 854 insertions(+), 754 deletions(-) delete mode 100644 tests/SimpleCancellationDemo/Program.cs delete mode 100644 tests/SimpleCancellationDemo/SimpleCancellationDemo.csproj diff --git a/StackExchange.Redis.sln b/StackExchange.Redis.sln index 9aa9994ec..104511e31 100644 --- a/StackExchange.Redis.sln +++ b/StackExchange.Redis.sln @@ -117,8 +117,6 @@ 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}") = "SimpleCancellationDemo", "tests\SimpleCancellationDemo\SimpleCancellationDemo.csproj", "{368A06AA-9DEB-4C55-B9CF-A1070B85E502}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "docs", "docs\docs.csproj", "{64CF03B6-6B29-4C4C-88B8-7B9E317D631A}" EndProject Global @@ -171,10 +169,6 @@ 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 - {368A06AA-9DEB-4C55-B9CF-A1070B85E502}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {368A06AA-9DEB-4C55-B9CF-A1070B85E502}.Debug|Any CPU.Build.0 = Debug|Any CPU - {368A06AA-9DEB-4C55-B9CF-A1070B85E502}.Release|Any CPU.ActiveCfg = Release|Any CPU - {368A06AA-9DEB-4C55-B9CF-A1070B85E502}.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 @@ -200,7 +194,6 @@ Global {A9F81DA3-DA82-423E-A5DD-B11C37548E06} = {96E891CD-2ED7-4293-A7AB-4C6F5D8D2B05} {A0F89B8B-32A3-4C28-8F1B-ADE343F16137} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} {69A0ACF2-DF1F-4F49-B554-F732DCA938A3} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} - {368A06AA-9DEB-4C55-B9CF-A1070B85E502} = {73A5C363-CA1F-44C4-9A9B-EF791A76BA6A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {193AA352-6748-47C1-A5FC-C9AA6B5F000B} 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/CursorEnumerable.cs b/src/StackExchange.Redis/CursorEnumerable.cs index 79e72cfe0..7c265b14d 100644 --- a/src/StackExchange.Redis/CursorEnumerable.cs +++ b/src/StackExchange.Redis/CursorEnumerable.cs @@ -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) { 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/PublicAPI/PublicAPI.Unshipped.txt b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt index 800be70ae..b590d5b24 100644 --- a/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/StackExchange.Redis/PublicAPI/PublicAPI.Unshipped.txt @@ -1,6 +1,5 @@ #nullable enable StackExchange.Redis.RedisCancellationExtensions -static StackExchange.Redis.RedisCancellationExtensions.GetEffectiveCancellationToken(this StackExchange.Redis.IConnectionMultiplexer! redis) -> System.Threading.CancellationToken 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! diff --git a/src/StackExchange.Redis/RedisBase.cs b/src/StackExchange.Redis/RedisBase.cs index 384f43a02..e8deb55df 100644 --- a/src/StackExchange.Redis/RedisBase.cs +++ b/src/StackExchange.Redis/RedisBase.cs @@ -5,21 +5,21 @@ 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(); + internal CancellationToken GetEffectiveCancellationToken() => Multiplexer.GetEffectiveCancellationToken(); public virtual TimeSpan Ping(CommandFlags flags = CommandFlags.None) { @@ -33,48 +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); + 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); + 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); + 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 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); } @@ -118,7 +118,7 @@ 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, default, cancellationToken); @@ -128,7 +128,7 @@ private ResultProcessor.TimingProcessor.TimerMessage GetTimerMessage(CommandFlag 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, cancellationToken); + 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 2e4c023dd..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(GetEffectiveCancellationToken(), 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(message.CancellationToken, 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 index a0d75b891..bbee65960 100644 --- a/src/StackExchange.Redis/RedisCancellationExtensions.cs +++ b/src/StackExchange.Redis/RedisCancellationExtensions.cs @@ -112,7 +112,7 @@ public static IDisposable WithCancellationAndTimeout( /// combining any ambient cancellation token and timeout. /// /// The effective cancellation token, or CancellationToken.None if no ambient context is set. - public static CancellationToken GetEffectiveCancellationToken(this IConnectionMultiplexer redis) + internal static CancellationToken GetEffectiveCancellationToken(this IConnectionMultiplexer redis, bool checkForCancellation = true) { var scope = _context.Value; @@ -126,7 +126,11 @@ public static CancellationToken GetEffectiveCancellationToken(this IConnectionMu if (fromScope is not null && fromScope.Equals(redis)) { var token = scope.Token; - token.ThrowIfCancellationRequested(); + if (checkForCancellation) + { + token.ThrowIfCancellationRequested(); + } + return token; } scope = scope.Previous; @@ -137,10 +141,30 @@ public static CancellationToken GetEffectiveCancellationToken(this IConnectionMu } /// - /// Gets the current cancellation context for diagnostic purposes. + /// Gets the current cancellation scope for diagnostic purposes. /// - /// The current context, or null if no ambient context is set. - internal static object? GetCurrentScope() => _context.Value; + /// 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(); diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index 24a375216..e53441525 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -16,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) @@ -34,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; @@ -44,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -66,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, values, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -100,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), this.GetEffectiveCancellationToken()); + 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), this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.GEODIST, key, value0, value1, Redis.GeoPosition.GetRedisUnit(unit), GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.NullableDouble); } @@ -115,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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.NullableStringArray, defaultValue: Array.Empty()); } @@ -124,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, member, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.String); } @@ -145,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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, redisValues, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisGeoPositionArray, defaultValue: Array.Empty()); } @@ -154,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, member, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisGeoPosition); } @@ -352,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.HDEL, key, hashField, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -373,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.HEXISTS, key, hashField, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -468,7 +466,7 @@ 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } @@ -482,25 +480,25 @@ 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.HGET, key, hashField, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -513,96 +511,96 @@ public Task HashGetAsync(RedisKey key, RedisValue hashField, Command 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + ? 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, this.GetEffectiveCancellationToken()); + ? 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, this.GetEffectiveCancellationToken()); + ? 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, this.GetEffectiveCancellationToken()); + ? 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.HRANDFIELD, key, count, RedisLiterals.WITHVALUES, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.HashEntryArray, defaultValue: Array.Empty()); } @@ -647,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, this.GetEffectiveCancellationToken()) - : Message.Create(Database, flags, when == When.Always ? RedisCommand.HSET : RedisCommand.HSETNX, key, hashField, value, this.GetEffectiveCancellationToken()); + ? 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); } @@ -661,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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.HSTRLEN, key, hashField, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -669,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, this.GetEffectiveCancellationToken()) - : Message.Create(Database, flags, when == When.Always ? RedisCommand.HSET : RedisCommand.HSETNX, key, hashField, value, this.GetEffectiveCancellationToken()); + ? 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.HSTRLEN, key, hashField, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -688,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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); @@ -741,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, this.GetEffectiveCancellationToken()); + var cmd = Message.Create(Database, flags, RedisCommand.PFCOUNT, keys, GetEffectiveCancellationToken()); if (keys.Length != 0) { var features = GetFeatures(keys[0], flags, RedisCommand.PFCOUNT, out server); @@ -814,7 +812,7 @@ public Task HyperLogLogMergeAsync(RedisKey destination, RedisKey[] sourceKeys, C 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; } @@ -833,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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, cmd, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.DemandZeroOrOne, server); } @@ -852,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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, cmd, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandZeroOrOne, server); } @@ -871,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; } @@ -880,31 +878,31 @@ 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.EXISTS, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } @@ -916,7 +914,7 @@ public long KeyExists(RedisKey[] keys, CommandFlags flags = CommandFlags.None) public Task KeyExistsAsync(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.EXISTS, key, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.EXISTS, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -964,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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); } @@ -1019,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)); @@ -1083,39 +1081,39 @@ public Task KeyPersistAsync(RedisKey key, CommandFlags flags = CommandFlag public RedisKey KeyRandom(CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.RANDOMKEY, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RENAME : RedisCommand.RENAMENX, key, newKey, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -1135,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, this.GetEffectiveCancellationToken()); + msg = Message.Create(Database, flags, RedisCommand.PTTL, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.TimeSpanFromMilliseconds, server); } - msg = Message.Create(Database, flags, RedisCommand.TTL, key, this.GetEffectiveCancellationToken()); + msg = Message.Create(Database, flags, RedisCommand.TTL, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.TimeSpanFromSeconds); } @@ -1148,36 +1146,36 @@ 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, this.GetEffectiveCancellationToken()); + msg = Message.Create(Database, flags, RedisCommand.PTTL, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.TimeSpanFromMilliseconds, server); } - msg = Message.Create(Database, flags, RedisCommand.TTL, key, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.LINDEX, key, index, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -1207,13 +1205,13 @@ public Task ListInsertBeforeAsync(RedisKey key, RedisValue pivot, RedisVal public RedisValue ListLeftPop(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LPOP, key, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.LPOP, key, count, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -1237,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.LPOP, key, count, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -1268,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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX, key, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -1277,7 +1275,7 @@ 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, this.GetEffectiveCancellationToken()) : Message.Create(Database, flags, command, key, values, this.GetEffectiveCancellationToken()); + 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); } @@ -1292,7 +1290,7 @@ public long ListLeftPush(RedisKey key, RedisValue[] values, CommandFlags flags = 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.LPUSH : RedisCommand.LPUSHX, key, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -1301,7 +1299,7 @@ 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, this.GetEffectiveCancellationToken()) : Message.Create(Database, flags, command, key, values, this.GetEffectiveCancellationToken()); + 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); } @@ -1315,13 +1313,13 @@ public Task ListLeftPushAsync(RedisKey key, RedisValue[] values, CommandFl public long ListLength(RedisKey key, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(Database, flags, RedisCommand.LLEN, key, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.LLEN, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -1339,37 +1337,37 @@ public Task ListMoveAsync(RedisKey sourceKey, RedisKey destinationKe 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.RPOP, key, count, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValueArray, defaultValue: Array.Empty()); } @@ -1412,7 +1410,7 @@ public Task ListRightPopLeftPushAsync(RedisKey source, RedisKey dest 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX, key, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -1421,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, this.GetEffectiveCancellationToken()) : Message.Create(Database, flags, command, key, values, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()) : Message.Create(Database, flags, RedisCommand.RPUSH, key, values, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, when == When.Always ? RedisCommand.RPUSH : RedisCommand.RPUSHX, key, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -1444,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, this.GetEffectiveCancellationToken()) : Message.Create(Database, flags, command, key, values, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()) : Message.Create(Database, flags, RedisCommand.RPUSH, key, values, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.LTRIM, key, start, stop, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } @@ -1544,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(-1, flags, channel.PublishCommand, channel, message, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -1597,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)!; } @@ -1606,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); @@ -1627,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); } @@ -1644,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 { @@ -1659,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); } @@ -1676,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); @@ -1690,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.SMISMEMBER, key, values, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.BooleanArray, defaultValue: Array.Empty()); } @@ -1819,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.SPOP, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.RedisValue); } @@ -1869,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, this.GetEffectiveCancellationToken()) - : Message.Create(Database, flags, RedisCommand.SPOP, key, count, this.GetEffectiveCancellationToken()); + ? 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.SREM, key, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } @@ -1917,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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); } @@ -2110,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); } @@ -2146,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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()); } @@ -2204,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()); } @@ -2226,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()); } @@ -2262,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.ZREMRANGEBYRANK, key, start, stop, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -2341,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, order == Order.Descending ? RedisCommand.ZPOPMAX : RedisCommand.ZPOPMIN, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.SortedSetEntry); } @@ -2379,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()); } @@ -2392,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()); } @@ -2573,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); } @@ -2590,7 +2589,8 @@ public Task StreamConsumerGroupSetPositionAsync(RedisKey key, RedisValue g key.AsRedisValue(), groupName, StreamPosition.Resolve(position, RedisCommand.XGROUP), - }); + }, + GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Boolean); } @@ -2650,7 +2650,8 @@ public StreamConsumerInfo[] StreamConsumerInfo(RedisKey key, RedisValue groupNam StreamConstants.Consumers, key.AsRedisValue(), groupName, - }); + }, + GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.StreamConsumerInfo, defaultValue: Array.Empty()); } @@ -2666,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); } @@ -2714,7 +2716,8 @@ public long StreamDelete(RedisKey key, RedisValue[] messageIds, CommandFlags fla flags, RedisCommand.XDEL, key, - messageIds); + messageIds, + GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -2726,7 +2729,8 @@ public Task StreamDeleteAsync(RedisKey key, RedisValue[] messageIds, Comma flags, RedisCommand.XDEL, key, - messageIds); + messageIds, + GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -2743,7 +2747,8 @@ public long StreamDeleteConsumer(RedisKey key, RedisValue groupName, RedisValue key.AsRedisValue(), groupName, consumerName, - }); + }, + GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Int64); } @@ -2760,7 +2765,8 @@ public Task StreamDeleteConsumerAsync(RedisKey key, RedisValue groupName, key.AsRedisValue(), groupName, consumerName, - }); + }, + GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -2776,7 +2782,8 @@ public bool StreamDeleteConsumerGroup(RedisKey key, RedisValue groupName, Comman StreamConstants.Destroy, key.AsRedisValue(), groupName, - }); + }, + GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Boolean); } @@ -2792,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); } @@ -2887,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()); } @@ -3015,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.APPEND, key, value, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -3030,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); } @@ -3043,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); } @@ -3080,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); } @@ -3093,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); } @@ -3123,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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.GET, key, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.RedisValue); } @@ -3155,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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); } @@ -3254,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, this.GetEffectiveCancellationToken()); + ? null : Message.Create(Database, flags, RedisCommand.INCRBYFLOAT, key, value, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.Double); } @@ -3267,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, this.GetEffectiveCancellationToken()); + ? 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Database, flags, RedisCommand.STRLEN, key, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -3339,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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); } @@ -3366,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; @@ -3374,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); } @@ -3383,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); @@ -3391,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, this.GetEffectiveCancellationToken()); + 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) { @@ -3418,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, this.GetEffectiveCancellationToken()), + ExpireWhen.Always => Message.Create(Database, flags, RedisCommand.PERSIST, key, GetEffectiveCancellationToken()), _ => throw new ArgumentException("PERSIST cannot be used with when."), }; } @@ -3434,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, this.GetEffectiveCancellationToken()), + ExpireWhen.Always => Message.Create(Database, flags, RedisCommand.PERSIST, key, GetEffectiveCancellationToken()), _ => throw new ArgumentException("PERSIST cannot be used with when."), }; } @@ -3456,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, this.GetEffectiveCancellationToken()), - _ => Message.Create(Database, flags, millisecondsCommand, key, milliseconds, when.ToLiteral(), this.GetEffectiveCancellationToken()), + ExpireWhen.Always => Message.Create(Database, flags, millisecondsCommand, key, milliseconds, GetEffectiveCancellationToken()), + _ => Message.Create(Database, flags, millisecondsCommand, key, milliseconds, when.ToLiteral(), GetEffectiveCancellationToken()), }; } server = null; @@ -3470,8 +3485,8 @@ private Message GetExpiryMessage( long seconds = milliseconds / 1000; return when switch { - ExpireWhen.Always => Message.Create(Database, flags, secondsCommand, key, seconds, this.GetEffectiveCancellationToken()), - _ => Message.Create(Database, flags, secondsCommand, key, seconds, when.ToLiteral(), this.GetEffectiveCancellationToken()), + ExpireWhen.Always => Message.Create(Database, flags, secondsCommand, key, seconds, GetEffectiveCancellationToken()), + _ => Message.Create(Database, flags, secondsCommand, key, seconds, when.ToLiteral(), GetEffectiveCancellationToken()), }; } @@ -3482,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; @@ -3510,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; @@ -3528,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) @@ -3545,7 +3560,7 @@ private Message GetSortedSetMultiPopMessage(RedisKey[] keys, Order order, long c key, hashFields[0].name, hashFields[0].value, - this.GetEffectiveCancellationToken()); + GetEffectiveCancellationToken()); case 2: return Message.Create( Database, @@ -3556,7 +3571,7 @@ private Message GetSortedSetMultiPopMessage(RedisKey[] keys, Order order, long c hashFields[0].value, hashFields[1].name, hashFields[1].value, - this.GetEffectiveCancellationToken()); + GetEffectiveCancellationToken()); default: var arr = new RedisValue[hashFields.Length * 2]; int offset = 0; @@ -3565,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, this.GetEffectiveCancellationToken()); + 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)); @@ -3582,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)); @@ -3614,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 { @@ -3779,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) @@ -3799,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) @@ -3828,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, this.GetEffectiveCancellationToken()); + return Message.Create(Database, flags, RedisCommand.ZADD, key, arr, GetEffectiveCancellationToken()); } private Message? GetSortedSetAddMessage(RedisKey key, SortedSetEntry[] values, SortedSetWhen when, bool change, CommandFlags flags) @@ -3868,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, this.GetEffectiveCancellationToken()); + return Message.Create(Database, flags, RedisCommand.ZADD, key, arr, GetEffectiveCancellationToken()); } } @@ -3890,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)), }; @@ -3941,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) @@ -3977,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) @@ -4007,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) @@ -4045,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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + return Message.Create(Database, flags, RedisCommand.ZCOUNT, key, from, to, GetEffectiveCancellationToken()); } private Message GetSortedSetIntersectionLengthMessage(RedisKey[] keys, long limit, CommandFlags flags) @@ -4068,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) @@ -4094,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()); } } @@ -4112,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) @@ -4123,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) @@ -4142,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) @@ -4177,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()); } /// @@ -4225,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) @@ -4251,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) @@ -4278,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) @@ -4301,7 +4319,8 @@ private Message GetStreamCreateConsumerGroupMessage(RedisKey key, RedisValue gro Database, flags, RedisCommand.XGROUP, - values); + values, + GetEffectiveCancellationToken()); } /// @@ -4341,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) @@ -4370,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 > { @@ -4385,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) { @@ -4432,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 { @@ -4440,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) { @@ -4499,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); @@ -4518,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) { @@ -4556,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) @@ -4576,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()); } } @@ -4596,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)), }; } @@ -4620,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)), }; } @@ -4645,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)), }; } @@ -4669,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)), }; } @@ -4689,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 @@ -4709,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; @@ -4723,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) @@ -4831,7 +4854,7 @@ 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, CancellationToken cancellationToken) { @@ -5033,7 +5056,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; @@ -5087,12 +5110,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, @@ -5105,6 +5131,7 @@ private static Message CreateSortedSetRangeStoreMessage( long skip, long? take) { + var cancellationToken = GetEffectiveCancellationToken(); if (sortedSetOrder == SortedSetOrder.ByRank) { if (take > 0) @@ -5118,8 +5145,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)), }; } @@ -5141,13 +5168,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)), }; } @@ -5156,8 +5183,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(); @@ -5201,8 +5228,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; } @@ -5211,8 +5238,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 75ff589fa..260baed45 100644 --- a/src/StackExchange.Redis/RedisServer.cs +++ b/src/StackExchange.Redis/RedisServer.cs @@ -35,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 { @@ -257,13 +257,13 @@ 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.DBSIZE, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.Int64); } @@ -293,13 +293,13 @@ public Task FlushAllDatabasesAsync(CommandFlags flags = CommandFlags.None) public void FlushDatabase(int database = -1, CommandFlags flags = CommandFlags.None) { - var msg = Message.Create(multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.FLUSHDB, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + var msg = Message.Create(Multiplexer.ApplyDefaultDatabase(database), flags, RedisCommand.FLUSHDB, this.GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.DemandOK); } @@ -355,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(); @@ -386,12 +386,12 @@ public Task LastSaveAsync(CommandFlags flags = CommandFlags.None) 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) @@ -444,14 +444,14 @@ public Task ScriptExistsAsync(byte[] sha1, CommandFlags flags = CommandFla public void ScriptFlush(CommandFlags flags = CommandFlags.None) { - if (!multiplexer.RawConfig.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(multiplexer.RawConfig.IncludeDetailInExceptions, RedisCommand.SCRIPT, null, server); + 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); + 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); } @@ -628,9 +628,9 @@ internal static Message CreateReplicaOfMessage(ServerEndPoint sendMessageTo, End 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, GetEffectiveCancellationToken()); msg.SetInternalCall(); @@ -642,8 +642,8 @@ 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, GetEffectiveCancellationToken()); msg.SetInternalCall(); @@ -659,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; } } @@ -681,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; } } @@ -707,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); @@ -1040,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, GetEffectiveCancellationToken()); + var msg = new RedisDatabase.ExecuteMessage(Multiplexer?.CommandMap, -1, flags, command, args, GetEffectiveCancellationToken()); return ExecuteSync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } @@ -1048,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, GetEffectiveCancellationToken()); + var msg = new RedisDatabase.ExecuteMessage(Multiplexer?.CommandMap, -1, flags, command, args, GetEffectiveCancellationToken()); return ExecuteAsync(msg, ResultProcessor.ScriptResult, defaultValue: RedisResult.NullSingle); } diff --git a/src/StackExchange.Redis/RedisSubscriber.cs b/src/StackExchange.Redis/RedisSubscriber.cs index a59368876..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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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,9 +353,10 @@ 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 { } } @@ -364,7 +368,7 @@ private Message CreatePingMessage(CommandFlags flags) else { // can't use regular PING, but we can unsubscribe from something random that we weren't even subscribed to... - RedisValue channel = multiplexer.UniqueId; + 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; @@ -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, this.GetEffectiveCancellationToken()); + 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, this.GetEffectiveCancellationToken()); + 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 426287e7b..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) @@ -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(message.CancellationToken, 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(message.CancellationToken, out var tcs, asyncState); + var source = TaskResultBox.Create(message.CancellationToken, out var tcs, ((RedisBase)this).AsyncState); message.SetSource(source!, processor); task = tcs.Task; } @@ -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 @@ -166,7 +166,7 @@ private void QueueMessage(Message message) return Message.Create(-1, flags, RedisCommand.PING, GetEffectiveCancellationToken()); } processor = TransactionProcessor.Default; - return new TransactionMessage(Database, flags, cond, work, multiplexer.GetEffectiveCancellationToken()); + return new TransactionMessage(Database, flags, cond, work, Multiplexer.GetEffectiveCancellationToken()); } private class QueuedMessage : Message diff --git a/tests/SimpleCancellationDemo/Program.cs b/tests/SimpleCancellationDemo/Program.cs deleted file mode 100644 index 3faf3c7df..000000000 --- a/tests/SimpleCancellationDemo/Program.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using StackExchange.Redis; - -namespace Examples -{ - /// - /// Simple demonstration of the new ambient cancellation functionality. - /// - public class Program - { - /// - /// Main entry point. - /// - /// Command line arguments. - /// A task representing the asynchronous operation. - public static async Task Main(string[] args) - { - Console.WriteLine("=== Simple Ambient Cancellation Demo ===\n"); - - Console.WriteLine("Note: This demo shows the API usage. In a real scenario, connect to Redis:"); - Console.WriteLine("using var redis = ConnectionMultiplexer.Connect(\"localhost\");"); - Console.WriteLine("var database = redis.GetDatabase();\n"); - - DemoBasicUsage(); - DemoNestedScopes(); - DemoTimeoutUsage(); - - Console.WriteLine("Demo completed successfully!"); - await Task.CompletedTask; - } - - private static void DemoBasicUsage() - { - Console.WriteLine("1. Basic Cancellation Usage"); - Console.WriteLine("----------------------------"); - - using var cts = new CancellationTokenSource(); - IDatabase? database = null; // In real usage: redis.GetDatabase() - - try - { - using (database?.WithCancellation(cts.Token)) - { - Console.WriteLine("✓ Ambient cancellation token set"); - Console.WriteLine(" All Redis operations in this scope will use the cancellation token"); - Console.WriteLine(" (Redis operations would execute here with cancellation support)"); - } - - Console.WriteLine("✓ Cancellation scope disposed - back to normal operation"); - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - } - - Console.WriteLine(); - } - - private static void DemoNestedScopes() - { - Console.WriteLine("2. Nested Scopes Example"); - Console.WriteLine("-------------------------"); - - using var outerToken = new CancellationTokenSource(); - using var innerToken = new CancellationTokenSource(); - IDatabase? database = null; // In real usage: redis.GetDatabase() - - try - { - using (database?.WithCancellation(outerToken.Token)) - { - Console.WriteLine("✓ Outer scope: Using outer cancellation token"); - - using (database?.WithCancellation(innerToken.Token)) - { - Console.WriteLine("✓ Inner scope: Using inner cancellation token (overrides outer)"); - } - - Console.WriteLine("✓ Back to outer scope: Using outer cancellation token again"); - } - - Console.WriteLine("✓ No cancellation scope: Normal operation"); - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - } - - Console.WriteLine(); - } - - private static void DemoTimeoutUsage() - { - Console.WriteLine("3. Timeout Usage Example"); - Console.WriteLine("-------------------------"); - - IDatabase? database = null; // In real usage: redis.GetDatabase() - - try - { - using (database?.WithTimeout(TimeSpan.FromSeconds(5))) - { - Console.WriteLine("✓ Timeout scope: Operations will timeout after 5 seconds"); - } - - Console.WriteLine("✓ Timeout scope disposed"); - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - } - - Console.WriteLine(); - } - } -} diff --git a/tests/SimpleCancellationDemo/SimpleCancellationDemo.csproj b/tests/SimpleCancellationDemo/SimpleCancellationDemo.csproj deleted file mode 100644 index 3fba134db..000000000 --- a/tests/SimpleCancellationDemo/SimpleCancellationDemo.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - Exe - net8.0 - enable - - - - - - - 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 index 7fc30712b..808d1d19f 100644 --- a/tests/StackExchange.Redis.Tests/CancellationTests.cs +++ b/tests/StackExchange.Redis.Tests/CancellationTests.cs @@ -1,40 +1,48 @@ using System; +using System.Diagnostics; +using System.IO; +using System.Net; using System.Threading; using System.Threading.Tasks; -using StackExchange.Redis.Tests.Helpers; +using StackExchange.Redis.Maintenance; +using StackExchange.Redis.Profiling; using Xunit; using Xunit.Abstractions; namespace StackExchange.Redis.Tests { - [Collection(SharedConnectionFixture.Key)] - public class CancellationTests : TestBase + public class PureCancellationTests { - public CancellationTests(ITestOutputHelper output, SharedConnectionFixture fixture) : base(output, fixture) { } - [Fact] public async Task GetEffectiveCancellationToken_Nesting() { // this is a pure test - no database access - IDatabase? db = null!; + IConnectionMultiplexer muxerA = new DummyMultiplexer(), muxerB = new DummyMultiplexer(); // No context initially - Assert.Null(RedisCancellationExtensions.GetCurrentScope()); - Assert.Equal(CancellationToken.None, RedisCancellationExtensions.GetEffectiveCancellationToken()); + 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 = db.Multiplexer.WithCancellation(cts.Token)) + using (var outer = muxerA.WithCancellation(cts.Token)) { Assert.NotNull(outer); - Assert.Same(outer, RedisCancellationExtensions.GetCurrentScope()); - Assert.Equal(cts.Token, RedisCancellationExtensions.GetEffectiveCancellationToken()); + 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 = db.Multiplexer.WithTimeout(TimeSpan.FromSeconds(0.5))) + using (var inner = muxerA.WithTimeout(TimeSpan.FromSeconds(0.5))) { Assert.NotNull(inner); - Assert.Same(inner, RedisCancellationExtensions.GetCurrentScope()); - var active = RedisCancellationExtensions.GetEffectiveCancellationToken(); + 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); @@ -45,28 +53,181 @@ public async Task GetEffectiveCancellationToken_Nesting() await Task.Delay(TimeSpan.FromSeconds(0.1)); } Assert.True(active.IsCancellationRequested); - Assert.Equal(active, RedisCancellationExtensions.GetEffectiveCancellationToken()); + Assert.Equal(active, muxerA.GetEffectiveCancellationToken(checkForCancellation: false)); } // back to outer - Assert.Same(outer, RedisCancellationExtensions.GetCurrentScope()); - Assert.Equal(cts.Token, RedisCancellationExtensions.GetEffectiveCancellationToken()); + 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 = db.Multiplexer.WithCancellation(CancellationToken.None)) + using (var inner = muxerA.WithCancellation(CancellationToken.None)) { Assert.NotNull(inner); - Assert.Same(inner, RedisCancellationExtensions.GetCurrentScope()); - Assert.Equal(CancellationToken.None, RedisCancellationExtensions.GetEffectiveCancellationToken()); + Assert.Same(inner, muxerA.GetCurrentScope()); + Assert.Equal(CancellationToken.None, muxerA.GetEffectiveCancellationToken()); } // back to outer - Assert.Same(outer, RedisCancellationExtensions.GetCurrentScope()); - Assert.Equal(cts.Token, RedisCancellationExtensions.GetEffectiveCancellationToken()); + 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 { } } - Assert.Null(RedisCancellationExtensions.GetCurrentScope()); - Assert.Equal(CancellationToken.None, RedisCancellationExtensions.GetEffectiveCancellationToken()); + + 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() @@ -104,20 +265,31 @@ public async Task WithCancellation_ValidToken_OperationSucceeds() } } + private void Pause(IDatabase db) + { + db.Execute("client", "pause", ConnectionPauseMilliseconds, CommandFlags.FireAndForget); + } + + private ConnectionMultiplexer Create() => ConnectionMultiplexer.Connect("127.0.0.1:4000"); + [Fact] - public async Task WithTimeout_ShortTimeout_ThrowsOperationCanceledException() + public async Task WithTimeout_ShortTimeout_Async_ThrowsOperationCanceledException() { using var conn = Create(); var db = conn.GetDatabase(); - using (db.Multiplexer.WithTimeout(TimeSpan.FromMilliseconds(1))) + 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 db.StringSetAsync(Me(), "value"); + await pending; // If it succeeds, that's fine too - Redis is fast - Skip.Inconclusive("Redis is too fast for this test."); + Skip.Inconclusive(TooFast + ": " + watch.ElapsedMilliseconds + "ms"); } catch (OperationCanceledException) { @@ -127,56 +299,32 @@ public async Task WithTimeout_ShortTimeout_ThrowsOperationCanceledException() } [Fact] - public async Task WithCancellationAndTimeout_CombinesCorrectly() + public void WithTimeout_ShortTimeout_Sync_ThrowsOperationCanceledException() { using var conn = Create(); var db = conn.GetDatabase(); - using var cts = new CancellationTokenSource(); + var watch = Stopwatch.StartNew(); + Pause(db); - using (db.Multiplexer.WithCancellationAndTimeout(cts.Token, TimeSpan.FromSeconds(10))) + using (db.Multiplexer.WithTimeout(TimeSpan.FromMilliseconds(ShortDelayMilliseconds))) { - // This should succeed with both cancellation and timeout - RedisKey key = Me(); - await db.StringSetAsync(key, "value"); - var result = await db.StringGetAsync(key); - Assert.Equal("value", result); - } - } - - [Fact] - public async Task NestedScopes_InnerScopeTakesPrecedence() - { - using var conn = Create(); - var db = conn.GetDatabase(); - - using var outerCts = new CancellationTokenSource(); - using var innerCts = new CancellationTokenSource(); - - RedisKey key1 = Me() + ":outer", - key2 = Me() + ":inner", - key3 = Me() + ":outer2"; - using (db.Multiplexer.WithCancellation(outerCts.Token)) - { - // Outer scope active - await db.StringSetAsync(key1, "value1"); - - using (db.Multiplexer.WithCancellation(innerCts.Token)) + // This might throw due to timeout, but let's test the mechanism + try { - // Inner scope should take precedence - await db.StringSetAsync(key2, "value2"); + db.StringSet(Me(), "value"); // check we get past this + // If it succeeds, that's fine too - Redis is fast + Skip.Inconclusive(TooFast + ": " + watch.ElapsedMilliseconds + "ms"); + } + catch (OperationCanceledException) + { + // Expected for very short timeouts } - - // Back to outer scope - await db.StringSetAsync(key3, "value3"); } - - // Verify all operations succeeded - Assert.Equal("value1", await db.StringGetAsync(key1)); - Assert.Equal("value2", await db.StringGetAsync(key2)); - Assert.Equal("value3", await db.StringGetAsync(key3)); } + private const string TooFast = "This operation completed too quickly to verify this behaviour."; + [Fact] public async Task WithoutAmbientCancellation_OperationsWorkNormally() { @@ -190,32 +338,65 @@ public async Task WithoutAmbientCancellation_OperationsWorkNormally() Assert.Equal("value", result); } - [Fact] - public async Task CancellationDuringOperation_CancelsGracefully() + public enum CancelStrategy + { + Constructor, + Method, + Manual, + } + + private const int ConnectionPauseMilliseconds = 50, ShortDelayMilliseconds = 5; + + [Theory] + [InlineData(CancelStrategy.Constructor)] + [InlineData(CancelStrategy.Method)] + [InlineData(CancelStrategy.Manual)] + public async Task CancellationDuringOperation_CancelsGracefully(CancelStrategy strategy) { using var conn = Create(); var db = conn.GetDatabase(); - using var cts = new CancellationTokenSource(); + 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)); + } + } + + 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 task = db.StringSetAsync(Me(), "value"); - - // Cancel after a short delay - _ = Task.Run(async () => - { - await Task.Delay(10); - cts.Cancel(); - }); + var pending = db.StringSetAsync($"{Me()}:{strategy}", "value"); try { - await task; - // If it completes before cancellation, that's fine + await pending; + Skip.Inconclusive(TooFast + ": " + watch.ElapsedMilliseconds + "ms"); } - catch (OperationCanceledException) + catch (OperationCanceledException oce) when (oce.CancellationToken == cts.Token) { // Expected if cancellation happens during operation } 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/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); From 20698f02b1d1fdfe4651e84ed212d788b5a10389 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 16 Jul 2025 14:22:53 +0100 Subject: [PATCH 6/9] cleanup --- tests/Examples/CancellationExample.cs | 231 ----------------------- tests/Examples/SimpleCancellationDemo.cs | 172 ----------------- 2 files changed, 403 deletions(-) delete mode 100644 tests/Examples/CancellationExample.cs delete mode 100644 tests/Examples/SimpleCancellationDemo.cs diff --git a/tests/Examples/CancellationExample.cs b/tests/Examples/CancellationExample.cs deleted file mode 100644 index cc14aa8a1..000000000 --- a/tests/Examples/CancellationExample.cs +++ /dev/null @@ -1,231 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using StackExchange.Redis; - -namespace Examples -{ - /// - /// Demonstrates the new ambient cancellation functionality in StackExchange.Redis. - /// - public class CancellationExample - { - public static async Task Main(string[] args) - { - // Connect to Redis - using var redis = ConnectionMultiplexer.Connect("localhost"); - var database = redis.GetDatabase(); - var subscriber = redis.GetSubscriber(); - - Console.WriteLine("=== StackExchange.Redis Ambient Cancellation Examples ===\n"); - - // Example 1: Basic cancellation - await BasicCancellationExample(database); - - // Example 2: Timeout example - await TimeoutExample(database); - - // Example 3: Combined cancellation and timeout - await CombinedExample(database); - - // Example 4: Nested scopes - await NestedScopesExample(database); - - // Example 5: Pub/Sub with cancellation - await PubSubExample(subscriber); - - // Example 6: Cancellation during operation - await CancellationDuringOperationExample(database); - - Console.WriteLine("\n=== All examples completed ==="); - } - - static async Task BasicCancellationExample(IDatabase database) - { - Console.WriteLine("1. Basic Cancellation Example"); - Console.WriteLine("------------------------------"); - - using var cts = new CancellationTokenSource(); - - try - { - using (database.WithCancellation(cts.Token)) - { - Console.WriteLine("Setting key with cancellation token..."); - await database.StringSetAsync("example:basic", "Hello, World!"); - - Console.WriteLine("Getting key with cancellation token..."); - var value = await database.StringGetAsync("example:basic"); - Console.WriteLine($"Retrieved value: {value}"); - } - } - catch (OperationCanceledException) - { - Console.WriteLine("Operation was cancelled!"); - } - - Console.WriteLine(); - } - - static async Task TimeoutExample(IDatabase database) - { - Console.WriteLine("2. Timeout Example"); - Console.WriteLine("------------------"); - - try - { - using (database.WithTimeout(TimeSpan.FromSeconds(5))) - { - Console.WriteLine("Setting key with 5-second timeout..."); - await database.StringSetAsync("example:timeout", "Timeout test"); - - Console.WriteLine("Getting key with 5-second timeout..."); - var value = await database.StringGetAsync("example:timeout"); - Console.WriteLine($"Retrieved value: {value}"); - } - } - catch (OperationCanceledException) - { - Console.WriteLine("Operation timed out!"); - } - - Console.WriteLine(); - } - - static async Task CombinedExample(IDatabase database) - { - Console.WriteLine("3. Combined Cancellation and Timeout Example"); - Console.WriteLine("---------------------------------------------"); - - using var cts = new CancellationTokenSource(); - - try - { - using (database.WithCancellationAndTimeout(cts.Token, TimeSpan.FromSeconds(10))) - { - Console.WriteLine("Setting key with both cancellation token and timeout..."); - await database.StringSetAsync("example:combined", "Combined test"); - - Console.WriteLine("Getting key with both cancellation token and timeout..."); - var value = await database.StringGetAsync("example:combined"); - Console.WriteLine($"Retrieved value: {value}"); - } - } - catch (OperationCanceledException) - { - Console.WriteLine("Operation was cancelled or timed out!"); - } - - Console.WriteLine(); - } - - static async Task NestedScopesExample(IDatabase database) - { - Console.WriteLine("4. Nested Scopes Example"); - Console.WriteLine("-------------------------"); - - using var outerCts = new CancellationTokenSource(); - using var innerCts = new CancellationTokenSource(); - - try - { - using (database.WithCancellation(outerCts.Token)) - { - Console.WriteLine("In outer scope - setting key1..."); - await database.StringSetAsync("example:outer", "Outer scope"); - - using (database.WithCancellation(innerCts.Token)) - { - Console.WriteLine("In inner scope - setting key2..."); - await database.StringSetAsync("example:inner", "Inner scope"); - } - - Console.WriteLine("Back in outer scope - setting key3..."); - await database.StringSetAsync("example:outer2", "Outer scope again"); - } - - // Verify all operations - Console.WriteLine($"Outer value: {await database.StringGetAsync("example:outer")}"); - Console.WriteLine($"Inner value: {await database.StringGetAsync("example:inner")}"); - Console.WriteLine($"Outer2 value: {await database.StringGetAsync("example:outer2")}"); - } - catch (OperationCanceledException) - { - Console.WriteLine("One of the operations was cancelled!"); - } - - Console.WriteLine(); - } - - static async Task PubSubExample(ISubscriber subscriber) - { - Console.WriteLine("5. Pub/Sub with Cancellation Example"); - Console.WriteLine("-------------------------------------"); - - using var cts = new CancellationTokenSource(); - var messageReceived = new TaskCompletionSource(); - - try - { - using (subscriber.WithCancellation(cts.Token)) - { - var channel = "example:channel"; - - Console.WriteLine("Subscribing to channel with cancellation..."); - await subscriber.SubscribeAsync(channel, (ch, message) => - { - Console.WriteLine($"Received message: {message}"); - messageReceived.TrySetResult(message); - }); - - Console.WriteLine("Publishing message with cancellation..."); - await subscriber.PublishAsync(channel, "Hello from pub/sub!"); - - // Wait for the message - var receivedMessage = await messageReceived.Task.WaitAsync(TimeSpan.FromSeconds(5)); - Console.WriteLine($"Successfully received: {receivedMessage}"); - } - } - catch (OperationCanceledException) - { - Console.WriteLine("Pub/Sub operation was cancelled!"); - } - - Console.WriteLine(); - } - - static async Task CancellationDuringOperationExample(IDatabase database) - { - Console.WriteLine("6. Cancellation During Operation Example"); - Console.WriteLine("-----------------------------------------"); - - using var cts = new CancellationTokenSource(); - - try - { - using (database.WithCancellation(cts.Token)) - { - Console.WriteLine("Starting operation..."); - var task = database.StringSetAsync("example:cancel-during", "This might be cancelled"); - - // Cancel after a short delay - _ = Task.Run(async () => - { - await Task.Delay(50); - Console.WriteLine("Cancelling operation..."); - cts.Cancel(); - }); - - await task; - Console.WriteLine("Operation completed before cancellation"); - } - } - catch (OperationCanceledException) - { - Console.WriteLine("Operation was successfully cancelled!"); - } - - Console.WriteLine(); - } - } -} diff --git a/tests/Examples/SimpleCancellationDemo.cs b/tests/Examples/SimpleCancellationDemo.cs deleted file mode 100644 index 313c9f43c..000000000 --- a/tests/Examples/SimpleCancellationDemo.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using StackExchange.Redis; - -namespace Examples -{ - /// - /// Simple demonstration of the new ambient cancellation functionality. - /// - public class SimpleCancellationDemo - { - public static async Task Main(string[] args) - { - Console.WriteLine("=== Simple Ambient Cancellation Demo ===\n"); - - // For this demo, we'll use a mock connection since we don't have Redis running - // In a real scenario, you would connect to an actual Redis instance - Console.WriteLine("Note: This demo shows the API usage. In a real scenario, connect to Redis:"); - Console.WriteLine("using var redis = ConnectionMultiplexer.Connect(\"localhost\");"); - Console.WriteLine("var database = redis.GetDatabase();\n"); - - await DemoBasicUsage(); - await DemoNestedScopes(); - await DemoTimeoutUsage(); - await DemoContextInspection(); - - Console.WriteLine("Demo completed successfully!"); - } - - static async Task DemoBasicUsage() - { - Console.WriteLine("1. Basic Cancellation Usage"); - Console.WriteLine("----------------------------"); - - // Create a cancellation token - using var cts = new CancellationTokenSource(); - - // Simulate getting a database instance - IDatabase database = null; // In real usage: redis.GetDatabase() - - try - { - // Set ambient cancellation - all operations in this scope will use this token - using (database?.WithCancellation(cts.Token)) - { - Console.WriteLine("✓ Ambient cancellation token set"); - Console.WriteLine(" All Redis operations in this scope will use the cancellation token"); - - // In real usage, these would be actual Redis operations: - // await database.StringSetAsync("key", "value"); - // var value = await database.StringGetAsync("key"); - - Console.WriteLine(" (Redis operations would execute here with cancellation support)"); - } - Console.WriteLine("✓ Cancellation scope disposed - back to normal operation"); - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - } - - Console.WriteLine(); - } - - static async Task DemoNestedScopes() - { - Console.WriteLine("2. Nested Scopes Example"); - Console.WriteLine("-------------------------"); - - using var outerToken = new CancellationTokenSource(); - using var innerToken = new CancellationTokenSource(); - - IDatabase database = null; // In real usage: redis.GetDatabase() - - try - { - using (database?.WithCancellation(outerToken.Token)) - { - Console.WriteLine("✓ Outer scope: Using outer cancellation token"); - - using (database?.WithCancellation(innerToken.Token)) - { - Console.WriteLine("✓ Inner scope: Using inner cancellation token (overrides outer)"); - - // Check current context - var context = RedisCancellationExtensions.GetCurrentContext(); - if (context != null) - { - Console.WriteLine($" Current context: {context}"); - } - } - - Console.WriteLine("✓ Back to outer scope: Using outer cancellation token again"); - } - Console.WriteLine("✓ No cancellation scope: Normal operation"); - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - } - - Console.WriteLine(); - } - - static async Task DemoTimeoutUsage() - { - Console.WriteLine("3. Timeout Usage Example"); - Console.WriteLine("-------------------------"); - - IDatabase database = null; // In real usage: redis.GetDatabase() - - try - { - using (database?.WithTimeout(TimeSpan.FromSeconds(5))) - { - Console.WriteLine("✓ Timeout scope: Operations will timeout after 5 seconds"); - - // Check current context - var context = RedisCancellationExtensions.GetCurrentContext(); - if (context != null) - { - Console.WriteLine($" Current context: {context}"); - } - } - Console.WriteLine("✓ Timeout scope disposed"); - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - } - - Console.WriteLine(); - } - - static async Task DemoContextInspection() - { - Console.WriteLine("4. Context Inspection Example"); - Console.WriteLine("------------------------------"); - - using var cts = new CancellationTokenSource(); - IDatabase database = null; // In real usage: redis.GetDatabase() - - // No context initially - var context = RedisCancellationExtensions.GetCurrentContext(); - Console.WriteLine($"Initial context: {context?.ToString() ?? "None"}"); - - using (database?.WithCancellation(cts.Token)) - { - context = RedisCancellationExtensions.GetCurrentContext(); - Console.WriteLine($"With cancellation: {context?.ToString() ?? "None"}"); - - using (database?.WithTimeout(TimeSpan.FromSeconds(10))) - { - context = RedisCancellationExtensions.GetCurrentContext(); - Console.WriteLine($"With timeout: {context?.ToString() ?? "None"}"); - - using (database?.WithCancellationAndTimeout(cts.Token, TimeSpan.FromSeconds(3))) - { - context = RedisCancellationExtensions.GetCurrentContext(); - Console.WriteLine($"With both: {context?.ToString() ?? "None"}"); - } - } - } - - context = RedisCancellationExtensions.GetCurrentContext(); - Console.WriteLine($"Final context: {context?.ToString() ?? "None"}"); - - Console.WriteLine(); - } - } -} From 86e89c57a6a0d97ba6fecb8423be73c0527c4c63 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 16 Jul 2025 15:30:49 +0100 Subject: [PATCH 7/9] Works for sync --- src/StackExchange.Redis/RedisDatabase.cs | 3 +- src/StackExchange.Redis/ResultBox.cs | 4 +- .../CancellationTests.cs | 130 +++++++++++++----- 3 files changed, 98 insertions(+), 39 deletions(-) diff --git a/src/StackExchange.Redis/RedisDatabase.cs b/src/StackExchange.Redis/RedisDatabase.cs index e53441525..838674bb3 100644 --- a/src/StackExchange.Redis/RedisDatabase.cs +++ b/src/StackExchange.Redis/RedisDatabase.cs @@ -4973,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); diff --git a/src/StackExchange.Redis/ResultBox.cs b/src/StackExchange.Redis/ResultBox.cs index 6416d744c..bb30e080c 100644 --- a/src/StackExchange.Redis/ResultBox.cs +++ b/src/StackExchange.Redis/ResultBox.cs @@ -30,7 +30,9 @@ internal abstract class SimpleResultBox : IResultBox void IResultBox.SetException(Exception exception) => _exception = exception ?? CancelledException; void IResultBox.Cancel(CancellationToken cancellationToken) => - _exception = GetCancelledException(CancellationToken, cancellationToken); + _exception = cancellationToken.IsCancellationRequested + ? new OperationCanceledException(cancellationToken) // for sync, need to capture this eagerly + : GetCancelledException(CancellationToken, cancellationToken); void IResultBox.ActivateContinuations() { diff --git a/tests/StackExchange.Redis.Tests/CancellationTests.cs b/tests/StackExchange.Redis.Tests/CancellationTests.cs index 808d1d19f..bc30ce6b9 100644 --- a/tests/StackExchange.Redis.Tests/CancellationTests.cs +++ b/tests/StackExchange.Redis.Tests/CancellationTests.cs @@ -52,6 +52,7 @@ public async Task GetEffectiveCancellationToken_Nesting() if (active.IsCancellationRequested) break; await Task.Delay(TimeSpan.FromSeconds(0.1)); } + Assert.True(active.IsCancellationRequested); Assert.Equal(active, muxerA.GetEffectiveCancellationToken(checkForCancellation: false)); } @@ -76,6 +77,7 @@ public async Task GetEffectiveCancellationToken_Nesting() 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 @@ -120,7 +122,8 @@ int IConnectionMultiplexer.StormLogThreshold set { } } - void IConnectionMultiplexer.RegisterProfiler(Func profilingSessionProvider) => throw new NotImplementedException(); + void IConnectionMultiplexer.RegisterProfiler(Func profilingSessionProvider) => + throw new NotImplementedException(); ServerCounters IConnectionMultiplexer.GetCounters() => throw new NotImplementedException(); @@ -184,15 +187,19 @@ event EventHandler? IConnectionMultiplexer.HashSlotMoved ISubscriber IConnectionMultiplexer.GetSubscriber(object? asyncState) => throw new NotImplementedException(); - IDatabase IConnectionMultiplexer.GetDatabase(int db, 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 host, int port, object? asyncState) => + throw new NotImplementedException(); - IServer IConnectionMultiplexer.GetServer(string hostAndPort, 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.GetServer(EndPoint endpoint, object? asyncState) => + throw new NotImplementedException(); IServer[] IConnectionMultiplexer.GetServers() => throw new NotImplementedException(); @@ -214,11 +221,13 @@ event EventHandler? IConnectionMultiplexer.HashSlotMoved long IConnectionMultiplexer.PublishReconfigure(CommandFlags flags) => throw new NotImplementedException(); - Task IConnectionMultiplexer.PublishReconfigureAsync(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.ExportConfiguration(Stream destination, ExportOptions options) => + throw new NotImplementedException(); void IConnectionMultiplexer.AddLibraryNameSuffix(string suffix) => throw new NotImplementedException(); } @@ -247,6 +256,8 @@ await Assert.ThrowsAnyAsync(async () => } } + private IInternalConnectionMultiplexer Create() => Create(syncTimeout: 10_000); + [Fact] public async Task WithCancellation_ValidToken_OperationSucceeds() { @@ -267,10 +278,19 @@ public async Task WithCancellation_ValidToken_OperationSucceeds() private void Pause(IDatabase db) { - db.Execute("client", "pause", ConnectionPauseMilliseconds, CommandFlags.FireAndForget); + db.Execute("client", new object[] { "pause", ConnectionPauseMilliseconds }, CommandFlags.FireAndForget); } - private ConnectionMultiplexer Create() => ConnectionMultiplexer.Connect("127.0.0.1:4000"); + /* + 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() @@ -289,11 +309,12 @@ public async Task WithTimeout_ShortTimeout_Async_ThrowsOperationCanceledExceptio { await pending; // If it succeeds, that's fine too - Redis is fast - Skip.Inconclusive(TooFast + ": " + watch.ElapsedMilliseconds + "ms"); + Assert.Fail(ExpectedCancel + ": " + watch.ElapsedMilliseconds + "ms"); } catch (OperationCanceledException) { // Expected for very short timeouts + Log($"Cancelled after {watch.ElapsedMilliseconds}ms"); } } } @@ -314,16 +335,17 @@ public void WithTimeout_ShortTimeout_Sync_ThrowsOperationCanceledException() { db.StringSet(Me(), "value"); // check we get past this // If it succeeds, that's fine too - Redis is fast - Skip.Inconclusive(TooFast + ": " + watch.ElapsedMilliseconds + "ms"); + Assert.Fail(ExpectedCancel + ": " + watch.ElapsedMilliseconds + "ms"); } catch (OperationCanceledException) { // Expected for very short timeouts + Log($"Cancelled after {watch.ElapsedMilliseconds}ms"); } } } - private const string TooFast = "This operation completed too quickly to verify this behaviour."; + private const string ExpectedCancel = "This operation should have been cancelled"; [Fact] public async Task WithoutAmbientCancellation_OperationsWorkNormally() @@ -347,38 +369,72 @@ public enum CancelStrategy 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_CancelsGracefully(CancelStrategy strategy) + public async Task CancellationDuringOperation_Async_CancelsGracefully(CancelStrategy strategy) { using var conn = Create(); var db = conn.GetDatabase(); - static CancellationTokenSource CreateCts(CancelStrategy strategy) + var watch = Stopwatch.StartNew(); + Pause(db); + + using var cts = CreateCts(strategy); + + // Cancel after a short delay + using (db.Multiplexer.WithCancellation(cts.Token)) { - switch (strategy) + // Start an operation and cancel it mid-flight + var pending = db.StringSetAsync($"{Me()}:{strategy}", "value"); + + try { - 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)); + 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); @@ -389,16 +445,16 @@ static CancellationTokenSource CreateCts(CancelStrategy strategy) using (db.Multiplexer.WithCancellation(cts.Token)) { // Start an operation and cancel it mid-flight - var pending = db.StringSetAsync($"{Me()}:{strategy}", "value"); - try { - await pending; - Skip.Inconclusive(TooFast + ": " + watch.ElapsedMilliseconds + "ms"); + db.StringSet($"{Me()}:{strategy}", "value"); + Assert.Fail(ExpectedCancel + ": " + watch.ElapsedMilliseconds + "ms"); } - catch (OperationCanceledException oce) when (oce.CancellationToken == cts.Token) + catch (OperationCanceledException oce) { // Expected if cancellation happens during operation + Log($"Cancelled after {watch.ElapsedMilliseconds}ms"); + Assert.Equal(cts.Token, oce.CancellationToken); } } } From 582a6bb9732fbc1e035112ef0e741681913a86a6 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 17 Jul 2025 10:31:10 +0100 Subject: [PATCH 8/9] release notes --- docs/ReleaseNotes.md | 1 + 1 file changed, 1 insertion(+) 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 From f4f834977633ab1d68b66df51a780bc0a36b1720 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 17 Jul 2025 11:39:15 +0100 Subject: [PATCH 9/9] async aspect --- docs/CancellationTimeout.md | 8 +- .../ConnectionMultiplexer.cs | 129 ++++++++++++++---- 2 files changed, 107 insertions(+), 30 deletions(-) diff --git a/docs/CancellationTimeout.md b/docs/CancellationTimeout.md index 071a6a887..a7eaef252 100644 --- a/docs/CancellationTimeout.md +++ b/docs/CancellationTimeout.md @@ -14,7 +14,7 @@ Timeouts are probably the most common cancellation scenario, so is exposed direc ```csharp using (database.Multiplexer.WithTimeout(TimeSpan.FromSeconds(5))) -// using (database.WithTimeout(5000)) // identical +// using (database.Multiplexer.WithTimeout(5_000)) // identical { await database.StringSetAsync("key", "value"); var value = await database.StringGetAsync("key"); @@ -40,9 +40,9 @@ using (database.Multiplexer.WithCancellation(token)) These two concepts can be combined: ```csharp - - -using (database.Multiplexer.WithCancellationAndTimeout(yourToken, TimeSpan.FromSeconds(10))) +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"); diff --git a/src/StackExchange.Redis/ConnectionMultiplexer.cs b/src/StackExchange.Redis/ConnectionMultiplexer.cs index f02740db6..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; @@ -2043,9 +2043,9 @@ internal static void ThrowFailed(TaskCompletionSource? source, Exception u } } - private static CancellationTokenRegistration ObserveCancellation(IResultBox box) + private static CancellationTokenRegistration ObserveCancellation(IResultBox? box) { - return box.CancellationToken.Register( + return box is null ? default : box.CancellationToken.Register( static state => { var typed = Unsafe.As(state!); @@ -2067,6 +2067,14 @@ private static CancellationTokenRegistration ObserveCancellation(IResultBox box) 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 @@ -2125,15 +2133,26 @@ private static CancellationTokenRegistration ObserveCancellation(IResultBox box) 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()); @@ -2145,9 +2164,21 @@ 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) + { + 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 @@ -2156,7 +2187,7 @@ static async Task ExecuteAsyncImpl_Awaited(ConnectionMultiplexer @this, Value 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) @@ -2171,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()); @@ -2200,16 +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 { // 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); + 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) @@ -2224,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); ///