Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/StackExchange.Redis/ArrayGrepRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ internal override void WriteTo(in MessageWriter writer)
}

private sealed class ArrayGrepMessage(int db, RedisKey key, ArrayGrepRequest request, CommandFlags flags)
: Message(db, flags, RedisCommand.ARGREP)
: Message.CommandKeyBase(db, flags, RedisCommand.ARGREP, key)
{
public override int ArgCount
{
Expand Down Expand Up @@ -374,7 +374,7 @@ private static void AddIndex(in MessageWriter writer, RedisArrayIndex? index, Re
protected override void WriteImpl(in MessageWriter writer)
{
writer.WriteHeader(Command, ArgCount);
writer.Write(key);
writer.Write(Key);
if (request.IsReversed)
{
AddIndex(writer, request.End, "$1\r\n+\r\n"u8);
Expand Down
3 changes: 1 addition & 2 deletions src/StackExchange.Redis/Increx.IncrexMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ internal abstract class IncrexMessageBase(
CommandFlags flags,
RedisKey key,
Expiration expiry,
IncrementOptions options) : Message(database, flags, RedisCommand.INCREX)
IncrementOptions options) : Message.CommandKeyBase(database, flags, RedisCommand.INCREX, key)
{
protected RedisKey Key => key;
protected Expiration Expiry => expiry;
protected IncrementOptions Options => options;

Expand Down
96 changes: 96 additions & 0 deletions tests/StackExchange.Redis.Tests/KeyedMessageSlotTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.Threading.Tasks;
using Xunit;

namespace StackExchange.Redis.Tests;

/// <summary>
/// A message that carries a key must report that key's hash slot, or a cluster client sends it to an arbitrary node
/// and leans on <c>-MOVED</c>: an extra round trip in the usual case, and a failure under
/// <see cref="CommandFlags.NoRedirect"/>. <c>INCREX</c> and <c>ARGREP</c> both shipped without one.
/// </summary>
public class KeyedMessageSlotTests(ITestOutputHelper output) : TestBase(output)
{
private static readonly ServerSelectionStrategy Cluster = new(null!) { ServerType = ServerType.Cluster };

private static void AssertRoutedByKey(Message message, RedisKey key)
{
var expected = Cluster.HashSlot(key);
Assert.NotEqual(ServerSelectionStrategy.NoSlot, expected);
Assert.Equal(expected, message.GetHashSlot(Cluster));
}

[Fact]
public void Increx_Int64_IsRoutedByKey()
{
RedisKey key = "some-counter";
var msg = new RedisDatabase.IncrexInt64Message(0, CommandFlags.None, key, 1, null, null, default, IncrementOptions.None);
AssertRoutedByKey(msg, key);
Assert.Equal("INCREX some-counter", msg.CommandAndKey);
}

[Fact]
public void Increx_Double_IsRoutedByKey()
{
RedisKey key = "some-counter";
var msg = new RedisDatabase.IncrexDoubleMessage(0, CommandFlags.None, key, 1.5, null, null, default, IncrementOptions.None);
AssertRoutedByKey(msg, key);
}

[Fact]
public void ArrayGrep_IsRoutedByKey()
{
RedisKey key = "some-array";
AssertRoutedByKey(CreateGrep().CreateMessage(0, key, CommandFlags.None), key);
}

private static ArrayGrepRequest CreateGrep()
{
var request = new ArrayGrepRequest();
request.AddPredicate(ArrayGrepRequest.Predicate.Match("alpha"));
return request;
}

// enough keys that, sent to an arbitrary node, some would certainly miss their owner
private const int KeyCount = 32;

[Fact]
public async Task Increx_OnCluster_NeedsNoRedirect()
{
await using var conn = Create(configuration: GetClusterConfiguration(), require: RedisFeatures.v8_8_0);
var db = conn.GetDatabase();
var expiry = Expiration.CreateOrKeepTtl(TimeSpan.FromMinutes(1), keepTtl: false);

for (int i = 0; i < KeyCount; i++)
{
RedisKey key = Me() + ":" + i;
await db.KeyDeleteAsync(key);

// NoRedirect: lands on the slot owner first time, or throws
var result = await db.StringIncrementAsync(key, 5, expiry, flags: CommandFlags.NoRedirect);
Assert.Equal(5, result.Value);

await db.KeyDeleteAsync(key, CommandFlags.FireAndForget);
}
}

[Fact]
public async Task ArrayGrep_OnCluster_NeedsNoRedirect()
{
await using var conn = Create(configuration: GetClusterConfiguration(), require: RedisFeatures.v8_8_0);
var db = conn.GetDatabase();
var grep = CreateGrep();

for (int i = 0; i < KeyCount; i++)
{
RedisKey key = Me() + ":" + i;
await db.KeyDeleteAsync(key);
await db.ArraySetAsync(key, 0, "alpha");

var found = await db.ArrayGrepAsync(key, grep, CommandFlags.NoRedirect);
Assert.Equal(0, Assert.Single(found).Index);

await db.KeyDeleteAsync(key, CommandFlags.FireAndForget);
}
}
}
Loading