Skip to content

Commit 35be6c3

Browse files
committed
basic resp3
1 parent 961ed28 commit 35be6c3

4 files changed

Lines changed: 128 additions & 86 deletions

File tree

src/StackExchange.Redis/PhysicalConnection.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,21 @@ internal static void WriteMultiBulkHeader(PipeWriter output, long count)
925925
output.Advance(offset);
926926
}
927927

928+
internal static void WriteMultiBulkHeader(PipeWriter output, long count, ResultType type)
929+
{
930+
// *{count}\r\n = 3 + MaxInt32TextLen
931+
var span = output.GetSpan(3 + Format.MaxInt32TextLen);
932+
span[0] = type switch
933+
{
934+
ResultType.Map => (byte)'%',
935+
ResultType.Set => (byte)'~',
936+
_ => (byte)'*',
937+
};
938+
if (type is ResultType.Map & count > 1) count >>= 1;
939+
int offset = WriteRaw(span, count, offset: 1);
940+
output.Advance(offset);
941+
}
942+
928943
[MethodImpl(MethodImplOptions.AggressiveInlining)]
929944
internal static int WriteCrlf(Span<byte> span, int offset)
930945
{

toys/StackExchange.Redis.Server/RedisServer.cs

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,11 @@ protected virtual TypedRedisValue Hello(RedisClient client, in RedisRequest requ
198198
switch (protover)
199199
{
200200
case 2:
201-
case 3:
202201
protocol = RedisProtocol.Resp2;
203202
break;
204-
/* case 3: // this client does not currently support RESP3
203+
case 3: // this client does not currently support RESP3
205204
protocol = RedisProtocol.Resp3;
206-
break; */
205+
break;
207206
default:
208207
return TypedRedisValue.Error("NOPROTO unsupported protocol version");
209208
}
@@ -234,7 +233,7 @@ protected virtual TypedRedisValue Hello(RedisClient client, in RedisRequest requ
234233
client.IsAuthenticated = isAuthed;
235234
client.Name = name;
236235

237-
var reply = TypedRedisValue.Rent(14, out var span);
236+
var reply = TypedRedisValue.Rent(14, out var span, ResultType.Map);
238237
span[0] = TypedRedisValue.BulkString("server");
239238
span[1] = TypedRedisValue.BulkString("redis");
240239
span[2] = TypedRedisValue.BulkString("version");
@@ -248,7 +247,7 @@ protected virtual TypedRedisValue Hello(RedisClient client, in RedisRequest requ
248247
span[10] = TypedRedisValue.BulkString("role");
249248
span[11] = TypedRedisValue.BulkString("master");
250249
span[12] = TypedRedisValue.BulkString("modules");
251-
span[13] = TypedRedisValue.EmptyArray;
250+
span[13] = TypedRedisValue.EmptyArray(ResultType.Array);
252251
return reply;
253252
}
254253

@@ -385,21 +384,21 @@ protected virtual TypedRedisValue ClusterSlots(RedisClient client, in RedisReque
385384
{
386385
count += pair.Value.Slots.Length;
387386
}
388-
var slots = TypedRedisValue.Rent(count, out var slotsSpan);
387+
var slots = TypedRedisValue.Rent(count, out var slotsSpan, ResultType.Array);
389388
foreach (var pair in _nodes.OrderBy(x => x.Key, EndPointComparer.Instance))
390389
{
391390
string host = GetHost(pair.Key, out int port);
392391
foreach (var range in pair.Value.Slots)
393392
{
394393
if (index >= count) break; // someone changed things while we were working
395-
slotsSpan[index++] = TypedRedisValue.Rent(3, out var slotSpan);
394+
slotsSpan[index++] = TypedRedisValue.Rent(3, out var slotSpan, ResultType.Array);
396395
slotSpan[0] = TypedRedisValue.Integer(range.From);
397396
slotSpan[1] = TypedRedisValue.Integer(range.To);
398-
slotSpan[2] = TypedRedisValue.Rent(4, out var nodeSpan);
397+
slotSpan[2] = TypedRedisValue.Rent(4, out var nodeSpan, ResultType.Array);
399398
nodeSpan[0] = TypedRedisValue.BulkString(host);
400399
nodeSpan[1] = TypedRedisValue.Integer(port);
401400
nodeSpan[2] = TypedRedisValue.BulkString(pair.Value.Id);
402-
nodeSpan[3] = TypedRedisValue.EmptyArray;
401+
nodeSpan[3] = TypedRedisValue.EmptyArray(ResultType.Array);
403402
}
404403
}
405404
return slots;
@@ -717,20 +716,20 @@ protected virtual TypedRedisValue LRange(RedisClient client, in RedisRequest req
717716
long start = request.GetInt64(2), stop = request.GetInt64(3);
718717

719718
var len = Llen(client.Database, key);
720-
if (len == 0) return TypedRedisValue.EmptyArray;
719+
if (len == 0) return TypedRedisValue.EmptyArray(ResultType.Array);
721720

722721
if (start < 0) start = len + start;
723722
if (stop < 0) stop = len + stop;
724723

725-
if (stop < 0 || start >= len || stop < start) return TypedRedisValue.EmptyArray;
724+
if (stop < 0 || start >= len || stop < start) return TypedRedisValue.EmptyArray(ResultType.Array);
726725

727726
if (start < 0) start = 0;
728727
else if (start >= len) start = len - 1;
729728

730729
if (stop < 0) stop = 0;
731730
else if (stop >= len) stop = len - 1;
732731

733-
var arr = TypedRedisValue.Rent(checked((int)((stop - start) + 1)), out var span);
732+
var arr = TypedRedisValue.Rent(checked((int)((stop - start) + 1)), out var span, ResultType.Array);
734733
LRange(client.Database, key, start, span);
735734
return arr;
736735
}
@@ -775,9 +774,9 @@ protected virtual TypedRedisValue Config(RedisClient client, in RedisRequest req
775774
OnUpdateServerConfiguration();
776775
var config = ServerConfiguration;
777776
var matches = config.CountMatch(pattern);
778-
if (matches == 0) return TypedRedisValue.EmptyArray;
777+
if (matches == 0) return TypedRedisValue.EmptyArray(ResultType.Map);
779778

780-
var arr = TypedRedisValue.Rent(2 * matches, out var span);
779+
var arr = TypedRedisValue.Rent(2 * matches, out var span, ResultType.Map);
781780
int index = 0;
782781
foreach (var pair in config.Wrapped)
783782
{
@@ -967,8 +966,8 @@ protected virtual TypedRedisValue Keys(RedisClient client, in RedisRequest reque
967966
if (found == null) found = new List<TypedRedisValue>();
968967
found.Add(TypedRedisValue.BulkString(key.AsRedisValue()));
969968
}
970-
if (found == null) return TypedRedisValue.EmptyArray;
971-
return TypedRedisValue.MultiBulk(found);
969+
if (found == null) return TypedRedisValue.EmptyArray(ResultType.Array);
970+
return TypedRedisValue.MultiBulk(found, ResultType.Array);
972971
}
973972
protected virtual IEnumerable<RedisKey> Keys(int database, in RedisKey pattern) => throw new NotSupportedException();
974973

@@ -1063,7 +1062,7 @@ protected virtual TypedRedisValue MemoryPurge(RedisClient client, in RedisReques
10631062
protected virtual TypedRedisValue Mget(RedisClient client, in RedisRequest request)
10641063
{
10651064
int argCount = request.Count;
1066-
var arr = TypedRedisValue.Rent(argCount - 1, out var span);
1065+
var arr = TypedRedisValue.Rent(argCount - 1, out var span, ResultType.Map);
10671066
var db = client.Database;
10681067
for (int i = 1; i < argCount; i++)
10691068
{
@@ -1096,10 +1095,10 @@ protected virtual TypedRedisValue Quit(RedisClient client, in RedisRequest reque
10961095
[RedisCommand(1, LockFree = true)]
10971096
protected virtual TypedRedisValue Role(RedisClient client, in RedisRequest request)
10981097
{
1099-
var arr = TypedRedisValue.Rent(3, out var span);
1098+
var arr = TypedRedisValue.Rent(3, out var span, ResultType.Array);
11001099
span[0] = TypedRedisValue.BulkString("master");
11011100
span[1] = TypedRedisValue.Integer(0);
1102-
span[2] = TypedRedisValue.EmptyArray;
1101+
span[2] = TypedRedisValue.EmptyArray(ResultType.Array);
11031102
return arr;
11041103
}
11051104

@@ -1123,7 +1122,7 @@ protected virtual TypedRedisValue Unsubscribe(RedisClient client, in RedisReques
11231122

11241123
private TypedRedisValue SubscribeImpl(RedisClient client, in RedisRequest request)
11251124
{
1126-
var reply = TypedRedisValue.Rent(3 * (request.Count - 1), out var span);
1125+
var reply = TypedRedisValue.Rent(3 * (request.Count - 1), out var span, ResultType.Array);
11271126
int index = 0;
11281127
request.TryGetCommandBytes(0, out var cmd);
11291128
var cmdString = TypedRedisValue.BulkString(cmd.ToArray());
@@ -1163,7 +1162,7 @@ protected virtual TypedRedisValue Time(RedisClient client, in RedisRequest reque
11631162
var ticks = delta.Ticks;
11641163
var seconds = ticks / TimeSpan.TicksPerSecond;
11651164
var micros = (ticks % TimeSpan.TicksPerSecond) / (TimeSpan.TicksPerMillisecond / 1000);
1166-
var reply = TypedRedisValue.Rent(2, out var span);
1165+
var reply = TypedRedisValue.Rent(2, out var span, ResultType.Array);
11671166
span[0] = TypedRedisValue.BulkString(seconds);
11681167
span[1] = TypedRedisValue.BulkString(micros);
11691168
return reply;

toys/StackExchange.Redis.Server/RespServer.cs

Lines changed: 73 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ public virtual void Log(string message)
311311
}
312312
}
313313

314-
public static async ValueTask WriteResponseAsync(RedisClient client, PipeWriter output, TypedRedisValue value)
314+
public static async ValueTask WriteResponseAsync(RedisClient client, PipeWriter output, TypedRedisValue value, RedisProtocol protocol)
315315
{
316316
static void WritePrefix(PipeWriter ooutput, char pprefix)
317317
{
@@ -323,52 +323,77 @@ static void WritePrefix(PipeWriter ooutput, char pprefix)
323323
if (value.IsNil) return; // not actually a request (i.e. empty/whitespace request)
324324
if (client != null && client.ShouldSkipResponse()) return; // intentionally skipping the result
325325
char prefix;
326-
switch (value.Type.ToResp2())
326+
var type = value.Type;
327+
if (protocol is RedisProtocol.Resp2 & type is not ResultType.Null) type = type.ToResp2();
328+
RetryResp2:
329+
if (protocol is RedisProtocol.Resp3 && value.IsNullValueOrArray)
327330
{
328-
case ResultType.Integer:
329-
PhysicalConnection.WriteInteger(output, (long)value.AsRedisValue());
330-
break;
331-
case ResultType.Error:
332-
prefix = '-';
333-
goto BasicMessage;
334-
case ResultType.SimpleString:
335-
prefix = '+';
336-
BasicMessage:
337-
WritePrefix(output, prefix);
338-
var val = (string)value.AsRedisValue();
339-
var expectedLength = Encoding.UTF8.GetByteCount(val);
340-
PhysicalConnection.WriteRaw(output, val, expectedLength);
341-
PhysicalConnection.WriteCrlf(output);
342-
break;
343-
case ResultType.BulkString:
344-
PhysicalConnection.WriteBulkString(value.AsRedisValue(), output);
345-
break;
346-
case ResultType.Array:
347-
if (value.IsNullArray)
348-
{
349-
PhysicalConnection.WriteMultiBulkHeader(output, -1);
350-
}
351-
else
352-
{
353-
var segment = value.Segment;
354-
PhysicalConnection.WriteMultiBulkHeader(output, segment.Count);
355-
var arr = segment.Array;
356-
int offset = segment.Offset;
357-
for (int i = 0; i < segment.Count; i++)
331+
output.Write("_\r\n"u8);
332+
}
333+
else
334+
{
335+
switch (type)
336+
{
337+
case ResultType.Null:
338+
output.Write("_\r\n"u8);
339+
break;
340+
case ResultType.Integer:
341+
PhysicalConnection.WriteInteger(output, (long)value.AsRedisValue());
342+
break;
343+
case ResultType.Error:
344+
prefix = '-';
345+
goto BasicMessage;
346+
case ResultType.SimpleString:
347+
prefix = '+';
348+
BasicMessage:
349+
WritePrefix(output, prefix);
350+
var val = (string)value.AsRedisValue();
351+
var expectedLength = Encoding.UTF8.GetByteCount(val);
352+
PhysicalConnection.WriteRaw(output, val, expectedLength);
353+
PhysicalConnection.WriteCrlf(output);
354+
break;
355+
case ResultType.BulkString:
356+
PhysicalConnection.WriteBulkString(value.AsRedisValue(), output);
357+
break;
358+
case ResultType.Push:
359+
case ResultType.Map:
360+
case ResultType.Array:
361+
if (value.IsNullArray)
362+
{
363+
PhysicalConnection.WriteMultiBulkHeader(output, -1, type);
364+
}
365+
else
358366
{
359-
var item = arr[offset++];
360-
if (item.IsNil)
361-
throw new InvalidOperationException("Array element cannot be nil, index " + i);
367+
var segment = value.Segment;
368+
PhysicalConnection.WriteMultiBulkHeader(output, segment.Count, type);
369+
var arr = segment.Array;
370+
int offset = segment.Offset;
371+
for (int i = 0; i < segment.Count; i++)
372+
{
373+
var item = arr[offset++];
374+
if (item.IsNil)
375+
throw new InvalidOperationException("Array element cannot be nil, index " + i);
376+
377+
// note: don't pass client down; this would impact SkipReplies
378+
await WriteResponseAsync(null, output, item, protocol);
379+
}
380+
}
362381

363-
// note: don't pass client down; this would impact SkipReplies
364-
await WriteResponseAsync(null, output, item);
382+
break;
383+
default:
384+
// retry with RESP2
385+
var r2 = type.ToResp2();
386+
if (r2 != type)
387+
{
388+
Debug.WriteLine($"{type} not handled in RESP3; using {r2} instead");
389+
goto RetryResp2;
365390
}
366-
}
367-
break;
368-
default:
369-
throw new InvalidOperationException(
370-
"Unexpected result type: " + value.Type);
391+
392+
throw new InvalidOperationException(
393+
"Unexpected result type: " + value.Type);
394+
}
371395
}
396+
372397
await output.FlushAsync().ConfigureAwait(false);
373398
}
374399

@@ -408,7 +433,7 @@ static async ValueTask<bool> Awaited(ValueTask write, TypedRedisValue response)
408433
client.ResetAfterRequest();
409434
}
410435

411-
var write = WriteResponseAsync(client, output, response);
436+
var write = WriteResponseAsync(client, output, response, client.Protocol);
412437
if (!write.IsCompletedSuccessfully) return Awaited(write, response);
413438
response.Recycle();
414439
return new ValueTask<bool>(true);
@@ -543,7 +568,7 @@ internal static string ToLower(in RawResult value)
543568
[RedisCommand(1, LockFree = true)]
544569
protected virtual TypedRedisValue Command(RedisClient client, in RedisRequest request)
545570
{
546-
var results = TypedRedisValue.Rent(_commands.Count, out var span);
571+
var results = TypedRedisValue.Rent(_commands.Count, out var span, ResultType.Array);
547572
int index = 0;
548573
foreach (var pair in _commands)
549574
span[index++] = CommandInfo(pair.Value);
@@ -553,22 +578,22 @@ protected virtual TypedRedisValue Command(RedisClient client, in RedisRequest re
553578
[RedisCommand(-2, "command", "info", LockFree = true)]
554579
protected virtual TypedRedisValue CommandInfo(RedisClient client, in RedisRequest request)
555580
{
556-
var results = TypedRedisValue.Rent(request.Count - 2, out var span);
581+
var results = TypedRedisValue.Rent(request.Count - 2, out var span, ResultType.Array);
557582
for (int i = 2; i < request.Count; i++)
558583
{
559584
span[i - 2] = request.TryGetCommandBytes(i, out var cmdBytes)
560585
&& _commands.TryGetValue(cmdBytes, out var cmdInfo)
561-
? CommandInfo(cmdInfo) : TypedRedisValue.NullArray;
586+
? CommandInfo(cmdInfo) : TypedRedisValue.NullArray(ResultType.Array);
562587
}
563588
return results;
564589
}
565590

566591
private TypedRedisValue CommandInfo(RespCommand command)
567592
{
568-
var arr = TypedRedisValue.Rent(6, out var span);
593+
var arr = TypedRedisValue.Rent(6, out var span, ResultType.Array);
569594
span[0] = TypedRedisValue.BulkString(command.Command);
570595
span[1] = TypedRedisValue.Integer(command.NetArity());
571-
span[2] = TypedRedisValue.EmptyArray;
596+
span[2] = TypedRedisValue.EmptyArray(ResultType.Array);
572597
span[3] = TypedRedisValue.Zero;
573598
span[4] = TypedRedisValue.Zero;
574599
span[5] = TypedRedisValue.Zero;

0 commit comments

Comments
 (0)