Skip to content

Commit 89e1a3d

Browse files
committed
fix cluster routing of subkey notifications; assert newline logic; use deterministic tests (estimate was causing flakiness)
1 parent d6d0135 commit 89e1a3d

2 files changed

Lines changed: 385 additions & 8 deletions

File tree

src/StackExchange.Redis/RedisChannel.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,43 @@ internal static ReadOnlySpan<byte> StripKeySpacePrefix(ReadOnlySpan<byte> span)
4141
int end = subspan.IndexOf("__:"u8);
4242
if (end >= 0) return subspan.Slice(end + 3);
4343
}
44+
else if (span.Length >= 19 && span.StartsWith("__subkeyspace@"u8))
45+
{
46+
// Format: __subkeyspace@{db}__:{key}
47+
// We want to extract {key} for routing
48+
var subspan = span.Slice(14); // Skip "__subkeyspace@"
49+
int end = subspan.IndexOf("__:"u8);
50+
if (end >= 0) return subspan.Slice(end + 3);
51+
}
52+
else if (span.Length >= 20 && span.StartsWith("__subkeyspaceitem@"u8))
53+
{
54+
// Format: __subkeyspaceitem@{db}__:{key}\n{field}
55+
// We want to extract {key} for routing
56+
var subspan = span.Slice(18); // Skip "__subkeyspaceitem@"
57+
int end = subspan.IndexOf("__:"u8);
58+
if (end >= 0)
59+
{
60+
subspan = subspan.Slice(end + 3); // Skip "{db}__:"
61+
// Find the newline that separates key from field
62+
int newline = subspan.IndexOf((byte)'\n');
63+
if (newline >= 0) return subspan.Slice(0, newline); // Return just the key
64+
return subspan; // No newline found, return rest
65+
}
66+
}
67+
else if (span.Length >= 23 && span.StartsWith("__subkeyspaceevent@"u8))
68+
{
69+
// Format: __subkeyspaceevent@{db}__:{event}|{key}
70+
// We want to extract {key} for routing
71+
var subspan = span.Slice(19); // Skip "__subkeyspaceevent@"
72+
int end = subspan.IndexOf("__:"u8);
73+
if (end >= 0)
74+
{
75+
subspan = subspan.Slice(end + 3); // Skip "{db}__:"
76+
// Find the pipe that separates event from key
77+
int pipe = subspan.IndexOf((byte)'|');
78+
if (pipe >= 0) return subspan.Slice(pipe + 1); // Return just the key
79+
}
80+
}
4481
return span;
4582
}
4683

@@ -365,6 +402,14 @@ public static RedisChannel SubKeySpaceItem(in RedisKey key, in RedisKey subkey,
365402
if (key.IsEmpty) throw new ArgumentNullException(nameof(key));
366403
if (subkey.IsEmpty) throw new ArgumentNullException(nameof(subkey));
367404

405+
// Redis does not issue notifications for keys containing \n to avoid ambiguity in SubKeySpaceItem format
406+
// Check by converting to string and looking for \n
407+
var keyString = key.ToString();
408+
if (keyString?.IndexOf('\n') >= 0)
409+
{
410+
throw new ArgumentException("Keys containing newline characters are not supported for SubKeySpaceItem notifications", nameof(key));
411+
}
412+
368413
var db = AppendDatabase(stackalloc byte[DatabaseScratchBufferSize], database, RedisChannelOptions.None);
369414

370415
// __subkeyspaceitem@{db}__:{key}\n{subkey}
@@ -405,6 +450,12 @@ public static RedisChannel SubKeySpaceEvent(ReadOnlySpan<byte> type, in RedisKey
405450
if (type.IsEmpty) throw new ArgumentNullException(nameof(type));
406451
if (key.IsEmpty) throw new ArgumentNullException(nameof(key));
407452

453+
// Redis rejects events containing | to avoid ambiguity in SubKeySpaceEvent format
454+
if (type.IndexOf((byte)'|') >= 0)
455+
{
456+
throw new ArgumentException("Event types containing pipe characters are not supported for SubKeySpaceEvent notifications", nameof(type));
457+
}
458+
408459
RedisChannelOptions options = RedisChannelOptions.MultiNode;
409460
if (database is null) options |= RedisChannelOptions.Pattern;
410461
var db = AppendDatabase(stackalloc byte[DatabaseScratchBufferSize], database, options);

0 commit comments

Comments
 (0)