Skip to content

Commit 2bf194e

Browse files
[DEV] Verbatim String Fixes (#1777)
* fix verbatim string uses to handle 'too large for send buffer' cases; more general fix is needed throughout, but this should fix INFO with large numbers of clients or nodes * address feedback * address feedback; rollback unrelated change, simplify WriteLargeVerbatimString * nit --------- Co-authored-by: Badrish Chandramouli <badrishc@microsoft.com>
1 parent c927b02 commit 2bf194e

5 files changed

Lines changed: 59 additions & 19 deletions

File tree

libs/common/RespWriteUtils.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,34 @@ public static bool TryWriteVerbatimString(ReadOnlySpan<byte> str, ReadOnlySpan<b
761761
return true;
762762
}
763763

764+
/// <summary>
765+
/// Write verbtatim string header.
766+
///
767+
/// That is ={len}\r\n{ext}:
768+
/// </summary>
769+
public static bool TryWriteVerbatimStringHeader(ReadOnlySpan<byte> str, ReadOnlySpan<byte> ext, ref byte* curr, byte* end)
770+
{
771+
Debug.Assert(ext.Length == 3);
772+
773+
// Verbatim string length includes the type metadata.
774+
// So ext (3 bytes) + ':' (1 byte separator) + str
775+
var actualLength = 3 + 1 + str.Length;
776+
var itemDigits = NumUtils.CountDigits(actualLength);
777+
778+
var headerLen = 1 + itemDigits + 2 + 3 + 1;
779+
if (headerLen > (int)(end - curr))
780+
return false;
781+
782+
*curr++ = (byte)'=';
783+
NumUtils.WriteInt32(actualLength, itemDigits, ref curr);
784+
WriteNewline(ref curr);
785+
ext.CopyTo(new Span<byte>(curr, 3));
786+
curr += 3;
787+
*curr++ = (byte)':';
788+
789+
return true;
790+
}
791+
764792
/// <summary>
765793
/// Write RESP3 true
766794
/// </summary>

libs/server/Metrics/Info/InfoCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ private bool NetworkINFO()
7171
var info = garnetInfo.GetRespInfo(sectionsArr, activeDbId, storeWrapper);
7272
if (!string.IsNullOrEmpty(info))
7373
{
74-
WriteVerbatimString(Encoding.ASCII.GetBytes(info));
74+
WriteLargeVerbatimString(Encoding.ASCII.GetBytes(info));
7575
}
7676
else
7777
{

libs/server/Resp/ClientCommands.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ private bool NetworkCLIENTLIST()
160160

161161
resultSb.Append("\n");
162162
var result = resultSb.ToString();
163-
WriteVerbatimString(Encoding.ASCII.GetBytes(result));
163+
WriteLargeVerbatimString(Encoding.ASCII.GetBytes(result));
164164

165165
return true;
166166
}
@@ -194,7 +194,7 @@ private bool NetworkCLIENTINFO()
194194

195195
resultSb.Append("\n");
196196
var result = resultSb.ToString();
197-
WriteVerbatimString(Encoding.ASCII.GetBytes(result));
197+
WriteLargeVerbatimString(Encoding.ASCII.GetBytes(result));
198198

199199
return true;
200200
}

libs/server/Resp/RespServerSessionOutput.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,17 +272,25 @@ private void WriteUtf8BulkString(ReadOnlySpan<char> chars)
272272
}
273273

274274
[MethodImpl(MethodImplOptions.AggressiveInlining)]
275-
private void WriteVerbatimString(scoped ReadOnlySpan<byte> item, scoped ReadOnlySpan<byte> ext = default)
275+
private void WriteLargeVerbatimString(scoped ReadOnlySpan<byte> item, scoped ReadOnlySpan<byte> ext = default)
276276
{
277277
if (respProtocolVersion >= 3)
278278
{
279-
while (!RespWriteUtils.TryWriteVerbatimString(item, ext.IsEmpty ? RespStrings.VerbatimTxt : ext, ref dcurr, dend))
279+
ext = ext.IsEmpty ? RespStrings.VerbatimTxt : ext;
280+
281+
while (!RespWriteUtils.TryWriteVerbatimStringHeader(item, ext, ref dcurr, dend))
282+
SendAndReset();
283+
284+
// Write out item
285+
WriteDirectLarge(item);
286+
287+
while (!RespWriteUtils.TryWriteNewLine(ref dcurr, dend))
280288
SendAndReset();
281289
}
282290
else
283291
{
284-
while (!RespWriteUtils.TryWriteBulkString(item, ref dcurr, dend))
285-
SendAndReset();
292+
// RESP2 just gets a bulk string
293+
WriteDirectLargeRespString(item);
286294
}
287295
}
288296
}

test/Garnet.test/RespInfoTests.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ public void TearDown()
3434
TestUtils.OnTearDown();
3535
}
3636

37-
[Test]
38-
public void ResetStatsTest()
37+
[TestCase(RedisProtocol.Resp2)]
38+
[TestCase(RedisProtocol.Resp3)]
39+
public void ResetStatsTest(RedisProtocol protocol)
3940
{
4041
TimeSpan metricsUpdateDelay = TimeSpan.FromSeconds(1.1);
41-
using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
42+
using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(protocol: protocol));
4243
var db = redis.GetDatabase(0);
4344

4445
var infoResult = db.Execute("INFO").ToString();
@@ -93,13 +94,15 @@ static long ParseUptime(string info) =>
9394
ClassicAssert.Greater(second, first, "uptime_in_seconds should increase between INFO calls");
9495
}
9596

96-
[Test]
97-
[TestCase("ALL")]
98-
[TestCase("DEFAULT")]
99-
[TestCase("EVERYTHING")]
100-
public void InfoSectionOptionsTest(string option)
97+
[TestCase("ALL", RedisProtocol.Resp2)]
98+
[TestCase("ALL", RedisProtocol.Resp3)]
99+
[TestCase("DEFAULT", RedisProtocol.Resp2)]
100+
[TestCase("DEFAULT", RedisProtocol.Resp3)]
101+
[TestCase("EVERYTHING", RedisProtocol.Resp2)]
102+
[TestCase("EVERYTHING", RedisProtocol.Resp3)]
103+
public void InfoSectionOptionsTest(string option, RedisProtocol protocol)
101104
{
102-
using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
105+
using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(protocol: protocol));
103106
var db = redis.GetDatabase(0);
104107

105108
var infoResult = db.Execute("INFO", option).ToString();
@@ -132,10 +135,11 @@ public void InfoSectionOptionsTest(string option)
132135
ClassicAssert.IsFalse(infoResult.Contains("# Commandstats"), $"INFO {option} should not contain Commandstats section");
133136
}
134137

135-
[Test]
136-
public void InfoDefaultMatchesNoArgsTest()
138+
[TestCase(RedisProtocol.Resp2)]
139+
[TestCase(RedisProtocol.Resp3)]
140+
public void InfoDefaultMatchesNoArgsTest(RedisProtocol protocol)
137141
{
138-
using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig());
142+
using var redis = ConnectionMultiplexer.Connect(TestUtils.GetConfig(protocol: protocol));
139143
var db = redis.GetDatabase(0);
140144

141145
var infoNoArgs = db.Execute("INFO").ToString();

0 commit comments

Comments
 (0)