From bf7a45b8159153390465adcbc89344ba3e544ea5 Mon Sep 17 00:00:00 2001 From: Kevin Montrose Date: Fri, 5 Jun 2026 16:42:31 -0400 Subject: [PATCH 1/4] fix CLUSTER|NODES and CLUSTER|SHARDS to handle larger string responses; another example of the issue we should cleanup up re. #1781 --- .../Session/RespClusterBasicCommands.cs | 47 +++++++++++++++++-- libs/common/RespWriteUtils.cs | 10 +++- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/libs/cluster/Session/RespClusterBasicCommands.cs b/libs/cluster/Session/RespClusterBasicCommands.cs index 58a49320325..1b8986650d6 100644 --- a/libs/cluster/Session/RespClusterBasicCommands.cs +++ b/libs/cluster/Session/RespClusterBasicCommands.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System; +using System.Buffers; using System.Diagnostics; using System.Text; using Garnet.common; @@ -274,8 +275,8 @@ private bool NetworkClusterNodes(out bool invalidParameters) } var nodes = clusterProvider.clusterManager.CurrentConfig.GetClusterInfo(clusterProvider); - while (!RespWriteUtils.TryWriteAsciiBulkString(nodes, ref dcurr, dend)) - SendAndReset(); + + WriteAsciiLargeRespString(nodes); return true; } @@ -343,8 +344,8 @@ private bool NetworkClusterShards(out bool invalidParameters) var preferredType = clusterProvider.serverOptions.ClusterPreferredEndpointType; var shardsInfo = clusterProvider.clusterManager.CurrentConfig.GetShardsInfo(clusterProvider.clusterManager.clusterConnectionStore, preferredType); - while (!RespWriteUtils.TryWriteAsciiDirect(shardsInfo, ref dcurr, dend)) - SendAndReset(); + + WriteAsciiLargeRespString(shardsInfo); return true; } @@ -523,5 +524,43 @@ private bool NetworkClusterPublish(out bool invalidParameters) clusterProvider.storeWrapper.subscribeBroker.Publish(parseState.GetArgSliceByRef(0), parseState.GetArgSliceByRef(1)); return true; } + + /// + /// Handle a potentially quite large string by breaking it into pieces if necessary. + /// + private void WriteAsciiLargeRespString(ReadOnlySpan message) + { + while (!RespWriteUtils.TryWriteBulkStringLength(message.Length, ref dcurr, dend)) + SendAndReset(); + + // Attempt to write w/o any buffering + if (RespWriteUtils.TryWriteAsciiDirect(message, ref dcurr, dend)) + { + return; + } + + var buffer = ArrayPool.Shared.Rent(message.Length); + try + { + var len = Encoding.ASCII.GetBytes(message, buffer); + var remaining = buffer.AsSpan()[..len]; + + while (!remaining.IsEmpty) + { + var space = (int)(dend - dcurr); + var res = RespWriteUtils.TryWriteDirect(remaining[..space], ref dcurr, dend); + Debug.Assert(res, "Should never fail to fit in output buffer"); + + SendAndReset(); + } + + while (!RespWriteUtils.TryWriteNewLine(ref dcurr, dend)) + SendAndReset(); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } } } \ No newline at end of file diff --git a/libs/common/RespWriteUtils.cs b/libs/common/RespWriteUtils.cs index 3319cf897a7..04568db1fcd 100644 --- a/libs/common/RespWriteUtils.cs +++ b/libs/common/RespWriteUtils.cs @@ -321,14 +321,20 @@ public static bool TryWriteDirect(ref T item, ref byte* curr, byte* end) wher /// Write length header of bulk string /// public static bool TryWriteBulkStringLength(ReadOnlySpan item, ref byte* curr, byte* end) + => TryWriteBulkStringLength(item.Length, ref curr, end); + + /// + /// Write length header of bulk string + /// + public static bool TryWriteBulkStringLength(int len, ref byte* curr, byte* end) { - var itemDigits = NumUtils.CountDigits(item.Length); + var itemDigits = NumUtils.CountDigits(len); var totalLen = 1 + itemDigits + 2; if (totalLen > (int)(end - curr)) return false; *curr++ = (byte)'$'; - NumUtils.WriteInt32(item.Length, itemDigits, ref curr); + NumUtils.WriteInt32(len, itemDigits, ref curr); WriteNewline(ref curr); return true; } From d65a2acce0e88b37d6feb8040896121e2dc9941a Mon Sep 17 00:00:00 2001 From: Kevin Montrose Date: Fri, 5 Jun 2026 17:01:15 -0400 Subject: [PATCH 2/4] fixes --- libs/cluster/Session/RespClusterBasicCommands.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/cluster/Session/RespClusterBasicCommands.cs b/libs/cluster/Session/RespClusterBasicCommands.cs index 1b8986650d6..7fdb3fa172a 100644 --- a/libs/cluster/Session/RespClusterBasicCommands.cs +++ b/libs/cluster/Session/RespClusterBasicCommands.cs @@ -536,6 +536,9 @@ private void WriteAsciiLargeRespString(ReadOnlySpan message) // Attempt to write w/o any buffering if (RespWriteUtils.TryWriteAsciiDirect(message, ref dcurr, dend)) { + while (!RespWriteUtils.TryWriteNewLine(ref dcurr, dend)) + SendAndReset(); + return; } @@ -552,6 +555,8 @@ private void WriteAsciiLargeRespString(ReadOnlySpan message) Debug.Assert(res, "Should never fail to fit in output buffer"); SendAndReset(); + + remaining = remaining[space..]; } while (!RespWriteUtils.TryWriteNewLine(ref dcurr, dend)) From 5d7161eca09aef191cca222c44a40094254bb029 Mon Sep 17 00:00:00 2001 From: Kevin Montrose Date: Fri, 5 Jun 2026 17:16:19 -0400 Subject: [PATCH 3/4] more fixes --- .../Session/RespClusterBasicCommands.cs | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/libs/cluster/Session/RespClusterBasicCommands.cs b/libs/cluster/Session/RespClusterBasicCommands.cs index 7fdb3fa172a..df671d4c7db 100644 --- a/libs/cluster/Session/RespClusterBasicCommands.cs +++ b/libs/cluster/Session/RespClusterBasicCommands.cs @@ -345,7 +345,7 @@ private bool NetworkClusterShards(out bool invalidParameters) var preferredType = clusterProvider.serverOptions.ClusterPreferredEndpointType; var shardsInfo = clusterProvider.clusterManager.CurrentConfig.GetShardsInfo(clusterProvider.clusterManager.clusterConnectionStore, preferredType); - WriteAsciiLargeRespString(shardsInfo); + WriteLargeAsciiDirectString(shardsInfo); return true; } @@ -525,8 +525,43 @@ private bool NetworkClusterPublish(out bool invalidParameters) return true; } + + /// + /// Handle a potentially large already encoded RESP response, breaking it into pieces if necessary. + /// + private void WriteLargeAsciiDirectString(ReadOnlySpan message) + { + // Attempt to write w/o any buffering + if (RespWriteUtils.TryWriteAsciiDirect(message, ref dcurr, dend)) + { + return; + } + + var buffer = ArrayPool.Shared.Rent(message.Length); + try + { + var len = Encoding.ASCII.GetBytes(message, buffer); + var remaining = buffer.AsSpan()[..len]; + + while (!remaining.IsEmpty) + { + var space = (int)(dend - dcurr); + var res = RespWriteUtils.TryWriteDirect(remaining[..space], ref dcurr, dend); + Debug.Assert(res, "Should never fail to fit in output buffer"); + + SendAndReset(); + + remaining = remaining[space..]; + } + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } + /// - /// Handle a potentially quite large string by breaking it into pieces if necessary. + /// Handle a potentially large bulk string by breaking it into pieces if necessary. /// private void WriteAsciiLargeRespString(ReadOnlySpan message) { From ba6e2a247e2182ae774d0e88857719c4d371e04f Mon Sep 17 00:00:00 2001 From: Kevin Montrose Date: Fri, 5 Jun 2026 17:54:25 -0400 Subject: [PATCH 4/4] even more fixes; tricky to actually test this --- libs/cluster/Session/RespClusterBasicCommands.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/libs/cluster/Session/RespClusterBasicCommands.cs b/libs/cluster/Session/RespClusterBasicCommands.cs index df671d4c7db..81b1b8b5b86 100644 --- a/libs/cluster/Session/RespClusterBasicCommands.cs +++ b/libs/cluster/Session/RespClusterBasicCommands.cs @@ -545,9 +545,8 @@ private void WriteLargeAsciiDirectString(ReadOnlySpan message) while (!remaining.IsEmpty) { - var space = (int)(dend - dcurr); - var res = RespWriteUtils.TryWriteDirect(remaining[..space], ref dcurr, dend); - Debug.Assert(res, "Should never fail to fit in output buffer"); + var space = Math.Min((int)(dend - dcurr), remaining.Length); + _ = RespWriteUtils.TryWriteDirect(remaining[..space], ref dcurr, dend); SendAndReset(); @@ -585,9 +584,8 @@ private void WriteAsciiLargeRespString(ReadOnlySpan message) while (!remaining.IsEmpty) { - var space = (int)(dend - dcurr); - var res = RespWriteUtils.TryWriteDirect(remaining[..space], ref dcurr, dend); - Debug.Assert(res, "Should never fail to fit in output buffer"); + var space = Math.Min((int)(dend - dcurr), remaining.Length); + _ = RespWriteUtils.TryWriteDirect(remaining[..space], ref dcurr, dend); SendAndReset();