Skip to content

Commit e703aba

Browse files
committed
assertions for multi-node and key-routing logic
1 parent 723cca1 commit e703aba

File tree

3 files changed

+62
-17
lines changed

3 files changed

+62
-17
lines changed

src/StackExchange.Redis/KeyNotification.cs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,18 @@ public static bool TryParse(in RedisChannel channel, in RedisValue value, out Ke
1717
// validate that it looks reasonable
1818
var span = channel.Span;
1919

20-
const int PREFIX_LEN = KeySpaceStart.Length, MIN_LEN = PREFIX_LEN + MinSuffixBytes;
21-
Debug.Assert(KeyEventStart.Length == PREFIX_LEN); // prove these are the same, DEBUG only
22-
23-
if (span.Length >= MIN_LEN)
20+
// KeySpaceStart and KeyEventStart are the same size, see KeyEventPrefix_KeySpacePrefix_Length_Matches
21+
if (span.Length >= KeySpacePrefix.Length + MinSuffixBytes)
2422
{
25-
var prefix = span.Slice(0, PREFIX_LEN);
23+
// check that the prefix is valid, i.e. "__keyspace@" or "__keyevent@"
24+
var prefix = span.Slice(0, KeySpacePrefix.Length);
2625
var hash = prefix.Hash64();
2726
switch (hash)
2827
{
29-
case KeySpaceStart.Hash when KeySpaceStart.Is(hash, prefix):
30-
case KeyEventStart.Hash when KeyEventStart.Is(hash, prefix):
28+
case KeySpacePrefix.Hash when KeySpacePrefix.Is(hash, prefix):
29+
case KeyEventPrefix.Hash when KeyEventPrefix.Is(hash, prefix):
3130
// check that there is *something* non-empty after the prefix, with __: as the suffix (we don't verify *what*)
32-
if (span.Slice(PREFIX_LEN).IndexOf("__:"u8) > 0)
31+
if (span.Slice(KeySpacePrefix.Length).IndexOf("__:"u8) > 0)
3332
{
3433
notification = new KeyNotification(in channel, in value);
3534
return true;
@@ -74,8 +73,8 @@ public int Database
7473
{
7574
// prevalidated format, so we can just skip past the prefix (except for the default value)
7675
if (_channel.IsNull) return -1;
77-
var span = _channel.Span.Slice(11);
78-
var end = span.IndexOf((byte)'_'); // expecting __:
76+
var span = _channel.Span.Slice(KeySpacePrefix.Length); // also works for KeyEventPrefix
77+
var end = span.IndexOf((byte)'_'); // expecting "__:foo" - we'll just stop at the underscore
7978
if (end <= 0) return -1;
8079

8180
span = span.Slice(0, end);
@@ -207,39 +206,39 @@ public KeyNotificationType Type
207206
}
208207

209208
/// <summary>
210-
/// Indicates whether this notification originated from a keyspace notification, for example <c>__keyspace@0__:mykey</c> with payload <c>set</c>.
209+
/// Indicates whether this notification originated from a keyspace notification, for example <c>__keyspace@4__:mykey</c> with payload <c>set</c>.
211210
/// </summary>
212211
public bool IsKeySpace
213212
{
214213
get
215214
{
216215
var span = _channel.Span;
217-
return span.Length >= KeySpaceStart.Length + MinSuffixBytes && KeySpaceStart.Is(span.Hash64(), span.Slice(0, KeySpaceStart.Length));
216+
return span.Length >= KeySpacePrefix.Length + MinSuffixBytes && KeySpacePrefix.Is(span.Hash64(), span.Slice(0, KeySpacePrefix.Length));
218217
}
219218
}
220219

221220
/// <summary>
222-
/// Indicates whether this notification originated from a keyevent notification, for example <c>__keyevent@0__:set</c> with payload <c>mykey</c>.
221+
/// Indicates whether this notification originated from a keyevent notification, for example <c>__keyevent@4__:set</c> with payload <c>mykey</c>.
223222
/// </summary>
224223
public bool IsKeyEvent
225224
{
226225
get
227226
{
228227
var span = _channel.Span;
229-
return span.Length >= KeyEventStart.Length + MinSuffixBytes && KeyEventStart.Is(span.Hash64(), span.Slice(0, KeyEventStart.Length));
228+
return span.Length >= KeyEventPrefix.Length + MinSuffixBytes && KeyEventPrefix.Is(span.Hash64(), span.Slice(0, KeyEventPrefix.Length));
230229
}
231230
}
232231
}
233232

234233
internal static partial class KeyNotificationChannels
235234
{
236235
[FastHash("__keyspace@")]
237-
internal static partial class KeySpaceStart
236+
internal static partial class KeySpacePrefix
238237
{
239238
}
240239

241240
[FastHash("__keyevent@")]
242-
internal static partial class KeyEventStart
241+
internal static partial class KeyEventPrefix
243242
{
244243
}
245244
}

src/StackExchange.Redis/RedisChannel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public static RedisChannel Sharded(string value) =>
167167
/// Create a key-notification channel for a single key in a single database.
168168
/// </summary>
169169
public static RedisChannel KeySpace(in RedisKey key, int database)
170-
=> BuildKeySpace(key, database, RedisChannelOptions.None);
170+
=> BuildKeySpace(key, database, RedisChannelOptions.KeyRouted);
171171

172172
/// <summary>
173173
/// Create a key-notification channel for a pattern, optionally in a specified database.

tests/StackExchange.Redis.Tests/KeyNotificationTests.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ public void CreateKeySpaceNotification_Valid()
409409
var channel = RedisChannel.KeySpace("abc", 42);
410410
Assert.Equal("__keyspace@42__:abc", channel.ToString());
411411
Assert.False(channel.IsMultiNode);
412+
Assert.True(channel.IsKeyRouted);
412413
Assert.False(channel.IsSharded);
413414
Assert.False(channel.IsPattern);
414415
}
@@ -423,6 +424,7 @@ public void CreateKeySpaceNotificationPattern(string? pattern, int? database, st
423424
var channel = RedisChannel.KeySpacePattern(pattern, database);
424425
Assert.Equal(expected, channel.ToString());
425426
Assert.True(channel.IsMultiNode);
427+
Assert.False(channel.IsKeyRouted);
426428
Assert.False(channel.IsSharded);
427429
Assert.True(channel.IsPattern);
428430
}
@@ -437,6 +439,7 @@ public void CreateKeyEventNotification(KeyNotificationType type, int? database,
437439
var channel = RedisChannel.KeyEvent(type, database);
438440
Assert.Equal(expected, channel.ToString());
439441
Assert.True(channel.IsMultiNode);
442+
Assert.False(channel.IsKeyRouted);
440443
Assert.False(channel.IsSharded);
441444
if (isPattern)
442445
{
@@ -447,4 +450,47 @@ public void CreateKeyEventNotification(KeyNotificationType type, int? database,
447450
Assert.False(channel.IsPattern);
448451
}
449452
}
453+
454+
[Fact]
455+
public void Cannot_KeyRoute_KeySpace_SingleKeyIsKeyRouted()
456+
{
457+
var channel = RedisChannel.KeySpace("abc", 42);
458+
Assert.False(channel.IsMultiNode);
459+
Assert.True(channel.IsKeyRouted);
460+
Assert.True(channel.WithKeyRouting().IsKeyRouted); // no change, still key-routed
461+
}
462+
463+
[Fact]
464+
public void Cannot_KeyRoute_KeySpacePattern()
465+
{
466+
var channel = RedisChannel.KeySpacePattern("abc", 42);
467+
Assert.True(channel.IsMultiNode);
468+
Assert.False(channel.IsKeyRouted);
469+
Assert.StartsWith("Key routing is not supported for multi-node channels", Assert.Throws<InvalidOperationException>(() => channel.WithKeyRouting()).Message);
470+
}
471+
472+
[Fact]
473+
public void Cannot_KeyRoute_KeyEvent()
474+
{
475+
var channel = RedisChannel.KeyEvent(KeyNotificationType.Set, 42);
476+
Assert.True(channel.IsMultiNode);
477+
Assert.False(channel.IsKeyRouted);
478+
Assert.StartsWith("Key routing is not supported for multi-node channels", Assert.Throws<InvalidOperationException>(() => channel.WithKeyRouting()).Message);
479+
}
480+
481+
[Fact]
482+
public void Cannot_KeyRoute_KeyEvent_Custom()
483+
{
484+
var channel = RedisChannel.KeyEvent("foo"u8, 42);
485+
Assert.True(channel.IsMultiNode);
486+
Assert.False(channel.IsKeyRouted);
487+
Assert.StartsWith("Key routing is not supported for multi-node channels", Assert.Throws<InvalidOperationException>(() => channel.WithKeyRouting()).Message);
488+
}
489+
490+
[Fact]
491+
public void KeyEventPrefix_KeySpacePrefix_Length_Matches()
492+
{
493+
// this is a sanity check for the parsing step in KeyNotification.TryParse
494+
Assert.Equal(KeyNotificationChannels.KeySpacePrefix.Length, KeyNotificationChannels.KeyEventPrefix.Length);
495+
}
450496
}

0 commit comments

Comments
 (0)