Commit 1a51bc11 authored by Marc Gravell's avatar Marc Gravell

add TypedRedisValue - works a lot like RedisResult, but: value-typed; helps perf a lot

parent 40c5d21d
......@@ -110,7 +110,8 @@ protected override long Llen(int database, RedisKey key)
[MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException();
protected override void LRange(int database, RedisKey key, long start, RedisValue[] arr)
protected override void LRange(int database, RedisKey key, long start, Span<TypedRedisValue> arr)
{
var stack = GetStack(key, false);
......@@ -123,7 +124,7 @@ protected override void LRange(int database, RedisKey key, long start, RedisValu
for (int i = 0; i < arr.Length; i++)
{
if (!iter.MoveNext()) ThrowArgumentOutOfRangeException();
arr[i] = iter.Current;
arr[i] = TypedRedisValue.BulkString(iter.Current);
}
}
}
......
......@@ -14,16 +14,13 @@ namespace StackExchange.Redis.Server
public override string ToString() => Command;
public override bool Equals(object obj) => throw new NotSupportedException();
public RedisResult WrongArgCount() => RedisResult.Create($"ERR wrong number of arguments for '{Command}' command", ResultType.Error);
public TypedRedisValue WrongArgCount() => TypedRedisValue.Error($"ERR wrong number of arguments for '{Command}' command");
public RedisResult UnknownSubcommandOrArgumentCount() => RedisResult.Create($"ERR Unknown subcommand or wrong number of arguments for '{Command}'.", ResultType.Error);
public TypedRedisValue UnknownSubcommandOrArgumentCount() => TypedRedisValue.Error($"ERR Unknown subcommand or wrong number of arguments for '{Command}'.");
public string GetString(int index)
=> _inner[index].GetString();
internal RedisResult GetResult(int index)
=> RedisResult.Create(_inner[index].AsRedisValue());
public bool IsString(int index, string value) // TODO: optimize
=> string.Equals(value, _inner[index].GetString(), StringComparison.OrdinalIgnoreCase);
......
......@@ -42,7 +42,7 @@ protected override void AppendStats(StringBuilder sb)
public int Databases { get; }
[RedisCommand(-3)]
protected virtual RedisResult Sadd(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Sadd(RedisClient client, RedisRequest request)
{
int added = 0;
var key = request.GetKey(1);
......@@ -51,12 +51,12 @@ protected virtual RedisResult Sadd(RedisClient client, RedisRequest request)
if (Sadd(client.Database, key, request.GetValue(i)))
added++;
}
return RedisResult.Create(added, ResultType.Integer);
return TypedRedisValue.Integer(added);
}
protected virtual bool Sadd(int database, RedisKey key, RedisValue value) => throw new NotSupportedException();
[RedisCommand(-3)]
protected virtual RedisResult Srem(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Srem(RedisClient client, RedisRequest request)
{
int removed = 0;
var key = request.GetKey(1);
......@@ -65,55 +65,55 @@ protected virtual RedisResult Srem(RedisClient client, RedisRequest request)
if (Srem(client.Database, key, request.GetValue(i)))
removed++;
}
return RedisResult.Create(removed, ResultType.Integer);
return TypedRedisValue.Integer(removed);
}
protected virtual bool Srem(int database, RedisKey key, RedisValue value) => throw new NotSupportedException();
[RedisCommand(2)]
protected virtual RedisResult Spop(RedisClient client, RedisRequest request)
=> RedisResult.Create(Spop(client.Database, request.GetKey(1)), ResultType.BulkString);
protected virtual TypedRedisValue Spop(RedisClient client, RedisRequest request)
=> TypedRedisValue.BulkString(Spop(client.Database, request.GetKey(1)));
protected virtual RedisValue Spop(int database, RedisKey key) => throw new NotSupportedException();
[RedisCommand(2)]
protected virtual RedisResult Scard(RedisClient client, RedisRequest request)
=> RedisResult.Create(Scard(client.Database, request.GetKey(1)), ResultType.Integer);
protected virtual TypedRedisValue Scard(RedisClient client, RedisRequest request)
=> TypedRedisValue.Integer(Scard(client.Database, request.GetKey(1)));
protected virtual long Scard(int database, RedisKey key) => throw new NotSupportedException();
[RedisCommand(3)]
protected virtual RedisResult Sismember(RedisClient client, RedisRequest request)
=> Sismember(client.Database, request.GetKey(1), request.GetValue(2)) ? RedisResult.One : RedisResult.Zero;
protected virtual TypedRedisValue Sismember(RedisClient client, RedisRequest request)
=> Sismember(client.Database, request.GetKey(1), request.GetValue(2)) ? TypedRedisValue.One : TypedRedisValue.Zero;
protected virtual bool Sismember(int database, RedisKey key, RedisValue value) => throw new NotSupportedException();
[RedisCommand(3, "client", "setname", LockFree = true)]
protected virtual RedisResult ClientSetname(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue ClientSetname(RedisClient client, RedisRequest request)
{
client.Name = request.GetString(2);
return RedisResult.OK;
return TypedRedisValue.OK;
}
[RedisCommand(2, "client", "getname", LockFree = true)]
protected virtual RedisResult ClientGetname(RedisClient client, RedisRequest request)
=> RedisResult.Create(client.Name, ResultType.BulkString);
protected virtual TypedRedisValue ClientGetname(RedisClient client, RedisRequest request)
=> TypedRedisValue.BulkString(client.Name);
[RedisCommand(3, "client", "reply", LockFree = true)]
protected virtual RedisResult ClientReply(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue ClientReply(RedisClient client, RedisRequest request)
{
if (request.IsString(2, "on")) client.SkipReplies = -1; // reply to nothing
else if (request.IsString(2, "off")) client.SkipReplies = 0; // reply to everything
else if (request.IsString(2, "skip")) client.SkipReplies = 2; // this one, and the next one
else return RedisResult.Create("ERR syntax error", ResultType.Error);
return RedisResult.OK;
else return TypedRedisValue.Error("ERR syntax error");
return TypedRedisValue.OK;
}
[RedisCommand(-1)]
protected virtual RedisResult Cluster(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Cluster(RedisClient client, RedisRequest request)
=> CommandNotFound(request.Command);
[RedisCommand(-3)]
protected virtual RedisResult Lpush(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Lpush(RedisClient client, RedisRequest request)
{
var key = request.GetKey(1);
long length = -1;
......@@ -121,11 +121,11 @@ protected virtual RedisResult Lpush(RedisClient client, RedisRequest request)
{
length = Lpush(client.Database, key, request.GetValue(i));
}
return RedisResult.Create(length, ResultType.Integer);
return TypedRedisValue.Integer(length);
}
[RedisCommand(-3)]
protected virtual RedisResult Rpush(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Rpush(RedisClient client, RedisRequest request)
{
var key = request.GetKey(1);
long length = -1;
......@@ -133,20 +133,20 @@ protected virtual RedisResult Rpush(RedisClient client, RedisRequest request)
{
length = Rpush(client.Database, key, request.GetValue(i));
}
return RedisResult.Create(length, ResultType.Integer);
return TypedRedisValue.Integer(length);
}
[RedisCommand(2)]
protected virtual RedisResult Lpop(RedisClient client, RedisRequest request)
=> RedisResult.Create(Lpop(client.Database, request.GetKey(1)), ResultType.BulkString);
protected virtual TypedRedisValue Lpop(RedisClient client, RedisRequest request)
=> TypedRedisValue.BulkString(Lpop(client.Database, request.GetKey(1)));
[RedisCommand(2)]
protected virtual RedisResult Rpop(RedisClient client, RedisRequest request)
=> RedisResult.Create(Rpop(client.Database, request.GetKey(1)), ResultType.BulkString);
protected virtual TypedRedisValue Rpop(RedisClient client, RedisRequest request)
=> TypedRedisValue.BulkString(Rpop(client.Database, request.GetKey(1)));
[RedisCommand(2)]
protected virtual RedisResult Llen(RedisClient client, RedisRequest request)
=> RedisResult.Create(Llen(client.Database, request.GetKey(1)), ResultType.Integer);
protected virtual TypedRedisValue Llen(RedisClient client, RedisRequest request)
=> TypedRedisValue.Integer(Llen(client.Database, request.GetKey(1)));
protected virtual long Lpush(int database, RedisKey key, RedisValue value) => throw new NotSupportedException();
protected virtual long Rpush(int database, RedisKey key, RedisValue value) => throw new NotSupportedException();
......@@ -155,18 +155,18 @@ protected virtual RedisResult Llen(RedisClient client, RedisRequest request)
protected virtual RedisValue Lpop(int database, RedisKey key) => throw new NotSupportedException();
[RedisCommand(4)]
protected virtual RedisResult LRange(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue LRange(RedisClient client, RedisRequest request)
{
var key = request.GetKey(1);
long start = request.GetInt64(2), stop = request.GetInt64(3);
var len = Llen(client.Database, key);
if (len == 0) return RedisResult.EmptyArray;
if (len == 0) return TypedRedisValue.EmptyArray;
if (start < 0) start = len + start;
if (stop < 0) stop = len + stop;
if (stop < 0 || start >= len || stop < start) return RedisResult.EmptyArray;
if (stop < 0 || start >= len || stop < start) return TypedRedisValue.EmptyArray;
if (start < 0) start = 0;
else if (start >= len) start = len - 1;
......@@ -174,11 +174,11 @@ protected virtual RedisResult LRange(RedisClient client, RedisRequest request)
if (stop < 0) stop = 0;
else if (stop >= len) stop = len - 1;
var arr = new RedisValue[(stop - start) + 1];
LRange(client.Database, key, start, arr);
return RedisResult.Create(arr);
var arr = TypedRedisValue.Rent(checked((int)((stop - start) + 1)));
LRange(client.Database, key, start, arr.MutableSpan);
return arr;
}
protected virtual void LRange(int database, RedisKey key, long start, RedisValue[] arr) => throw new NotSupportedException();
protected virtual void LRange(int database, RedisKey key, long start, Span<TypedRedisValue> arr) => throw new NotSupportedException();
protected virtual void OnUpdateServerConfiguration() { }
protected RedisConfig ServerConfiguration { get; } = RedisConfig.Create();
......@@ -212,35 +212,35 @@ internal int CountMatch(string pattern)
}
}
[RedisCommand(3, "config", "get", LockFree = true)]
protected virtual RedisResult Config(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Config(RedisClient client, RedisRequest request)
{
var pattern = request.GetString(2);
OnUpdateServerConfiguration();
var config = ServerConfiguration;
var matches = config.CountMatch(pattern);
if (matches == 0) return RedisResult.Create(Array.Empty<RedisResult>());
if (matches == 0) return TypedRedisValue.EmptyArray;
var arr = new RedisResult[2 * matches];
var arr = TypedRedisValue.Rent(2 * matches);
int index = 0;
foreach (var pair in config.Wrapped)
{
if (IsMatch(pattern, pair.Key))
{
arr[index++] = RedisResult.Create(pair.Key, ResultType.BulkString);
arr[index++] = RedisResult.Create(pair.Value, ResultType.BulkString);
arr[index++] = TypedRedisValue.BulkString(pair.Key);
arr[index++] = TypedRedisValue.BulkString(pair.Value);
}
}
if (index != arr.Length) throw new InvalidOperationException("Configuration CountMatch fail");
return RedisResult.Create(arr);
return arr;
}
[RedisCommand(2, LockFree = true)]
protected virtual RedisResult Echo(RedisClient client, RedisRequest request)
=> request.GetResult(1);
protected virtual TypedRedisValue Echo(RedisClient client, RedisRequest request)
=> TypedRedisValue.BulkString(request.GetValue(1));
[RedisCommand(2)]
protected virtual RedisResult Exists(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Exists(RedisClient client, RedisRequest request)
{
int count = 0;
var db = client.Database;
......@@ -249,7 +249,7 @@ protected virtual RedisResult Exists(RedisClient client, RedisRequest request)
if (Exists(db, request.GetKey(i)))
count++;
}
return RedisResult.Create(count, ResultType.Integer);
return TypedRedisValue.Integer(count);
}
protected virtual bool Exists(int database, RedisKey key)
......@@ -262,32 +262,32 @@ protected virtual bool Exists(int database, RedisKey key)
}
[RedisCommand(2)]
protected virtual RedisResult Get(RedisClient client, RedisRequest request)
=> RedisResult.Create(Get(client.Database, request.GetKey(1)), ResultType.BulkString);
protected virtual TypedRedisValue Get(RedisClient client, RedisRequest request)
=> TypedRedisValue.BulkString(Get(client.Database, request.GetKey(1)));
protected virtual RedisValue Get(int database, RedisKey key) => throw new NotSupportedException();
[RedisCommand(3)]
protected virtual RedisResult Set(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Set(RedisClient client, RedisRequest request)
{
Set(client.Database, request.GetKey(1), request.GetValue(2));
return RedisResult.OK;
return TypedRedisValue.OK;
}
protected virtual void Set(int database, RedisKey key, RedisValue value) => throw new NotSupportedException();
[RedisCommand(1)]
protected new virtual RedisResult Shutdown(RedisClient client, RedisRequest request)
protected new virtual TypedRedisValue Shutdown(RedisClient client, RedisRequest request)
{
DoShutdown();
return RedisResult.OK;
return TypedRedisValue.OK;
}
[RedisCommand(2)]
protected virtual RedisResult Strlen(RedisClient client, RedisRequest request)
=> RedisResult.Create(Strlen(client.Database, request.GetKey(1)), ResultType.Integer);
protected virtual TypedRedisValue Strlen(RedisClient client, RedisRequest request)
=> TypedRedisValue.Integer(Strlen(client.Database, request.GetKey(1)));
protected virtual long Strlen(int database, RedisKey key) => Get(database, key).Length();
[RedisCommand(-2)]
protected virtual RedisResult Del(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Del(RedisClient client, RedisRequest request)
{
int count = 0;
for (int i = 1; i < request.Count; i++)
......@@ -295,40 +295,40 @@ protected virtual RedisResult Del(RedisClient client, RedisRequest request)
if (Del(client.Database, request.GetKey(i)))
count++;
}
return RedisResult.Create(count, ResultType.Integer);
return TypedRedisValue.Integer(count);
}
protected virtual bool Del(int database, RedisKey key) => throw new NotSupportedException();
[RedisCommand(1)]
protected virtual RedisResult Dbsize(RedisClient client, RedisRequest request)
=> RedisResult.Create(Dbsize(client.Database), ResultType.Integer);
protected virtual TypedRedisValue Dbsize(RedisClient client, RedisRequest request)
=> TypedRedisValue.Integer(Dbsize(client.Database));
protected virtual long Dbsize(int database) => throw new NotSupportedException();
[RedisCommand(1)]
protected virtual RedisResult Flushall(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Flushall(RedisClient client, RedisRequest request)
{
var count = Databases;
for (int i = 0; i < count; i++)
{
Flushdb(i);
}
return RedisResult.OK;
return TypedRedisValue.OK;
}
[RedisCommand(1)]
protected virtual RedisResult Flushdb(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Flushdb(RedisClient client, RedisRequest request)
{
Flushdb(client.Database);
return RedisResult.OK;
return TypedRedisValue.OK;
}
protected virtual void Flushdb(int database) => throw new NotSupportedException();
[RedisCommand(-1, LockFree = true, MaxArgs = 2)]
protected virtual RedisResult Info(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Info(RedisClient client, RedisRequest request)
{
var info = Info(request.Count == 1 ? null : request.GetString(1));
return RedisResult.Create(info, ResultType.BulkString);
return TypedRedisValue.BulkString(info);
}
protected virtual string Info(string selected)
{
......@@ -346,16 +346,16 @@ bool IsMatch(string section) => string.IsNullOrWhiteSpace(selected)
}
[RedisCommand(2)]
protected virtual RedisResult Keys(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Keys(RedisClient client, RedisRequest request)
{
List<RedisResult> found = null;
List<TypedRedisValue> found = null;
foreach (var key in Keys(client.Database, request.GetKey(1)))
{
if (found == null) found = new List<RedisResult>();
found.Add(RedisResult.Create(key));
if (found == null) found = new List<TypedRedisValue>();
found.Add(TypedRedisValue.BulkString(key.AsRedisValue()));
}
return RedisResult.Create(
found == null ? Array.Empty<RedisResult>() : found.ToArray());
if (found == null) return TypedRedisValue.EmptyArray;
return TypedRedisValue.MultiBulk(found.ToArray());
}
protected virtual IEnumerable<RedisKey> Keys(int database, RedisKey pattern) => throw new NotSupportedException();
......@@ -401,25 +401,25 @@ StringBuilder AddHeader()
}
}
[RedisCommand(2, "memory", "purge")]
protected virtual RedisResult MemoryPurge(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue MemoryPurge(RedisClient client, RedisRequest request)
{
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
return RedisResult.OK;
return TypedRedisValue.OK;
}
[RedisCommand(-2)]
protected virtual RedisResult Mget(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Mget(RedisClient client, RedisRequest request)
{
int argCount = request.Count;
var arr = new RedisResult[argCount - 1];
var arr = TypedRedisValue.Rent(argCount - 1);
var db = client.Database;
for (int i = 1; i < argCount; i++)
{
arr[i - 1] = RedisResult.Create(Get(db, request.GetKey(i)), ResultType.BulkString);
arr[i - 1] = TypedRedisValue.BulkString(Get(db, request.GetKey(i)));
}
return RedisResult.Create(arr);
return arr;
}
[RedisCommand(-3)]
protected virtual RedisResult Mset(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Mset(RedisClient client, RedisRequest request)
{
int argCount = request.Count;
var db = client.Database;
......@@ -427,51 +427,50 @@ protected virtual RedisResult Mset(RedisClient client, RedisRequest request)
{
Set(db, request.GetKey(i++), request.GetValue(i++));
}
return RedisResult.OK;
return TypedRedisValue.OK;
}
[RedisCommand(-1, LockFree = true, MaxArgs = 2)]
protected virtual RedisResult Ping(RedisClient client, RedisRequest request)
=> RedisResult.Create(request.Count == 1 ? "PONG" : request.GetString(1), ResultType.SimpleString);
protected virtual TypedRedisValue Ping(RedisClient client, RedisRequest request)
=> TypedRedisValue.SimpleString(request.Count == 1 ? "PONG" : request.GetString(1));
[RedisCommand(1, LockFree = true)]
protected virtual RedisResult Quit(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Quit(RedisClient client, RedisRequest request)
{
RemoveClient(client);
return RedisResult.OK;
return TypedRedisValue.OK;
}
[RedisCommand(1, LockFree = true)]
protected virtual RedisResult Role(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Role(RedisClient client, RedisRequest request)
{
return RedisResult.Create(new[]
{
RedisResult.Create("master", ResultType.BulkString),
RedisResult.Create(0, ResultType.Integer),
RedisResult.Create(Array.Empty<RedisResult>())
});
var arr = TypedRedisValue.Rent(3);
arr[0] = TypedRedisValue.BulkString("master");
arr[1] = TypedRedisValue.Integer(0);
arr[2] = TypedRedisValue.EmptyArray;
return arr;
}
[RedisCommand(2, LockFree = true)]
protected virtual RedisResult Select(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Select(RedisClient client, RedisRequest request)
{
var raw = request.GetValue(1);
if (!raw.IsInteger) return RedisResult.Create("ERR invalid DB index", ResultType.Error);
if (!raw.IsInteger) return TypedRedisValue.Error("ERR invalid DB index");
int db = (int)raw;
if (db < 0 || db >= Databases) return RedisResult.Create("ERR DB index is out of range", ResultType.Error);
if (db < 0 || db >= Databases) return TypedRedisValue.Error("ERR DB index is out of range");
client.Database = db;
return RedisResult.OK;
return TypedRedisValue.OK;
}
[RedisCommand(-2)]
protected virtual RedisResult Subscribe(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Subscribe(RedisClient client, RedisRequest request)
=> SubscribeImpl(client, request);
[RedisCommand(-2)]
protected virtual RedisResult Unsubscribe(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Unsubscribe(RedisClient client, RedisRequest request)
=> SubscribeImpl(client, request);
private RedisResult SubscribeImpl(RedisClient client, RedisRequest request)
private TypedRedisValue SubscribeImpl(RedisClient client, RedisRequest request)
{
var reply = new RedisResult[3 * (request.Count - 1)];
var reply = TypedRedisValue.Rent(3 * (request.Count - 1));
int index = 0;
var mode = request.Command[0] == 'p' ? RedisChannel.PatternMode.Pattern : RedisChannel.PatternMode.Literal;
for (int i = 1; i < request.Count; i++)
......@@ -482,44 +481,46 @@ private RedisResult SubscribeImpl(RedisClient client, RedisRequest request)
{
case "subscribe": count = client.Subscribe(channel); break;
case "unsubscribe": count = client.Unsubscribe(channel); break;
default: return null;
default:
reply.Recycle(index);
return TypedRedisValue.Nil;
}
reply[index++] = RedisResult.Create(request.Command, ResultType.BulkString);
reply[index++] = RedisResult.Create(channel);
reply[index++] = RedisResult.Create(count, ResultType.Integer);
reply[index++] = TypedRedisValue.BulkString(request.Command);
reply[index++] = TypedRedisValue.BulkString((byte[])channel);
reply[index++] = TypedRedisValue.Integer(count);
}
return RedisResult.Create(reply);
return reply;
}
static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
[RedisCommand(1, LockFree = true)]
protected virtual RedisResult Time(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Time(RedisClient client, RedisRequest request)
{
var delta = Time() - UnixEpoch;
var ticks = delta.Ticks;
var seconds = ticks / TimeSpan.TicksPerSecond;
var micros = (ticks % TimeSpan.TicksPerSecond) / (TimeSpan.TicksPerMillisecond / 1000);
return RedisResult.Create(new[] {
RedisResult.Create(seconds, ResultType.BulkString),
RedisResult.Create(micros, ResultType.BulkString),
});
var reply = TypedRedisValue.Rent(2);
reply[0] = TypedRedisValue.BulkString(seconds);
reply[1] = TypedRedisValue.BulkString(micros);
return reply;
}
protected virtual DateTime Time() => DateTime.UtcNow;
[RedisCommand(-2)]
protected virtual RedisResult Unlink(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Unlink(RedisClient client, RedisRequest request)
=> Del(client, request);
[RedisCommand(2)]
protected virtual RedisResult Incr(RedisClient client, RedisRequest request)
=> RedisResult.Create(IncrBy(client.Database, request.GetKey(1), 1), ResultType.Integer);
protected virtual TypedRedisValue Incr(RedisClient client, RedisRequest request)
=> TypedRedisValue.Integer(IncrBy(client.Database, request.GetKey(1), 1));
[RedisCommand(2)]
protected virtual RedisResult Decr(RedisClient client, RedisRequest request)
=> RedisResult.Create(IncrBy(client.Database, request.GetKey(1), -1), ResultType.Integer);
protected virtual TypedRedisValue Decr(RedisClient client, RedisRequest request)
=> TypedRedisValue.Integer(IncrBy(client.Database, request.GetKey(1), -1));
[RedisCommand(3)]
protected virtual RedisResult IncrBy(RedisClient client, RedisRequest request)
=> RedisResult.Create(IncrBy(client.Database, request.GetKey(1), request.GetInt64(2)), ResultType.Integer);
protected virtual TypedRedisValue IncrBy(RedisClient client, RedisRequest request)
=> TypedRedisValue.Integer(IncrBy(client.Database, request.GetKey(1), request.GetInt64(2)));
protected virtual long IncrBy(int database, RedisKey key, long delta)
{
......
......@@ -30,7 +30,7 @@ public RespServer(TextWriter output = null)
{
RedisCommandAttribute CheckSignatureAndGetAttribute(MethodInfo method)
{
if (method.ReturnType != typeof(RedisResult)) return null;
if (method.ReturnType != typeof(TypedRedisValue)) return null;
var p = method.GetParameters();
if (p.Length != 2 || p[0].ParameterType != typeof(RedisClient) || p[1].ParameterType != typeof(RedisRequest))
return null;
......@@ -149,7 +149,7 @@ public RespCommand Resolve(in RedisRequest request)
}
return this;
}
public RedisResult Execute(RedisClient client, RedisRequest request)
public TypedRedisValue Execute(RedisClient client, RedisRequest request)
{
var args = request.Count;
if (!CheckArity(request.Count)) return IsSubCommand
......@@ -175,7 +175,7 @@ internal int NetArity()
return varadic ? -minMagnitude : minMagnitude;
}
}
delegate RedisResult RespOperation(RedisClient client, RedisRequest request);
delegate TypedRedisValue RespOperation(RedisClient client, RedisRequest request);
protected int TcpPort()
{
......@@ -348,7 +348,7 @@ private void Log(string message)
}
static Encoder s_sharedEncoder; // swapped in/out to avoid alloc on the public WriteResponse API
public static ValueTask WriteResponseAsync(RedisClient client, PipeWriter output, RedisResult response)
public static ValueTask WriteResponseAsync(RedisClient client, PipeWriter output, TypedRedisValue value)
{
async ValueTask Awaited(ValueTask wwrite, Encoder eenc)
{
......@@ -356,13 +356,13 @@ async ValueTask Awaited(ValueTask wwrite, Encoder eenc)
Interlocked.Exchange(ref s_sharedEncoder, eenc);
}
var enc = Interlocked.Exchange(ref s_sharedEncoder, null) ?? Encoding.UTF8.GetEncoder();
var write = WriteResponseAsync(client, output, response, enc);
var write = WriteResponseAsync(client, output, value, enc);
if (!write.IsCompletedSuccessfully) return Awaited(write, enc);
Interlocked.Exchange(ref s_sharedEncoder, enc);
return default;
}
internal static async ValueTask WriteResponseAsync(RedisClient client, PipeWriter output, RedisResult response, Encoder encoder)
internal static async ValueTask WriteResponseAsync(RedisClient client, PipeWriter output, TypedRedisValue value, Encoder encoder)
{
void WritePrefix(PipeWriter ooutput, char pprefix)
{
......@@ -371,13 +371,13 @@ void WritePrefix(PipeWriter ooutput, char pprefix)
ooutput.Advance(1);
}
if (response == null) return; // not actually a request (i.e. empty/whitespace request)
if (value.IsNil) return; // not actually a request (i.e. empty/whitespace request)
if (client != null && client.ShouldSkipResponse()) return; // intentionally skipping the result
char prefix;
switch (response.Type)
switch (value.Type)
{
case ResultType.Integer:
PhysicalConnection.WriteInteger(output, (long)response);
PhysicalConnection.WriteInteger(output, (long)value.AsRedisValue());
break;
case ResultType.Error:
prefix = '-';
......@@ -386,30 +386,28 @@ void WritePrefix(PipeWriter ooutput, char pprefix)
prefix = '+';
BasicMessage:
WritePrefix(output, prefix);
var val = response.AsString();
var val = (string)value.AsRedisValue();
var expectedLength = Encoding.UTF8.GetByteCount(val);
PhysicalConnection.WriteRaw(output, val, expectedLength, encoder);
PhysicalConnection.WriteCrlf(output);
break;
case ResultType.BulkString:
PhysicalConnection.WriteBulkString(response.AsRedisValue(), output, encoder);
PhysicalConnection.WriteBulkString(value.AsRedisValue(), output, encoder);
break;
case ResultType.MultiBulk:
if (response.IsNull)
if (value.IsNullArray)
{
PhysicalConnection.WriteMultiBulkHeader(output, -1);
}
else
{
var arr = (RedisResult[])response;
PhysicalConnection.WriteMultiBulkHeader(output, arr.Length);
for (int i = 0; i < arr.Length; i++)
var count = value.Length;
PhysicalConnection.WriteMultiBulkHeader(output, count);
for (int i = 0; i < count ; i++)
{
var item = arr[i];
if (item == null)
throw new InvalidOperationException("Array element cannot be null, index " + i);
var item = value[i];
if (item.IsNil)
throw new InvalidOperationException("Array element cannot be nil, index " + i);
// note: don't pass client down; this would impact SkipReplies
await WriteResponseAsync(null, output, item, encoder);
......@@ -418,7 +416,7 @@ void WritePrefix(PipeWriter ooutput, char pprefix)
break;
default:
throw new InvalidOperationException(
"Unexpected result type: " + response.Type);
"Unexpected result type: " + value.Type);
}
await output.FlushAsync();
}
......@@ -438,18 +436,21 @@ public static bool TryParseRequest(ref ReadOnlySequence<byte> buffer, out RedisR
}
public ValueTask<bool> TryProcessRequestAsync(ref ReadOnlySequence<byte> buffer, RedisClient client, PipeWriter output)
{
async ValueTask<bool> Awaited(ValueTask wwrite)
async ValueTask<bool> Awaited(ValueTask wwrite, TypedRedisValue rresponse)
{
await wwrite;
rresponse.Recycle();
return true;
}
if (!buffer.IsEmpty && TryParseRequest(ref buffer, out var request))
{
RedisResult response;
TypedRedisValue response;
try { response = Execute(client, request); }
finally { request.Recycle(); }
var write = WriteResponseAsync(client, output, response);
if (!write.IsCompletedSuccessfully) return Awaited(write);
if (!write.IsCompletedSuccessfully) return Awaited(write, response);
response.Recycle();
return new ValueTask<bool>(true);
}
return new ValueTask<bool>(false);
......@@ -461,13 +462,13 @@ async ValueTask<bool> Awaited(ValueTask wwrite)
public long TotalCommandsProcesed => _totalCommandsProcesed;
public long TotalErrorCount => _totalErrorCount;
public RedisResult Execute(RedisClient client, RedisRequest request)
public TypedRedisValue Execute(RedisClient client, RedisRequest request)
{
if (string.IsNullOrWhiteSpace(request.Command)) return null; // not a request
if (string.IsNullOrWhiteSpace(request.Command)) return default; // not a request
Interlocked.Increment(ref _totalCommandsProcesed);
try
{
RedisResult result;
TypedRedisValue result;
if (_commands.TryGetValue(request.Command, out var cmd))
{
request = request.AsCommand(cmd.Command); // fixup casing
......@@ -490,12 +491,16 @@ public RedisResult Execute(RedisClient client, RedisRequest request)
}
else
{
result = null;
result = TypedRedisValue.Nil;
}
if (result == null) Log($"missing command: '{request.Command}'");
else if (result.Type == ResultType.Error) Interlocked.Increment(ref _totalErrorCount);
return result ?? CommandNotFound(request.Command);
if (result.IsNil)
{
Log($"missing command: '{request.Command}'");
return CommandNotFound(request.Command);
}
if (result.Type == ResultType.Error) Interlocked.Increment(ref _totalErrorCount);
return result;
}
catch (NotSupportedException)
{
......@@ -509,12 +514,12 @@ public RedisResult Execute(RedisClient client, RedisRequest request)
}
catch (InvalidCastException)
{
return RedisResult.Create("WRONGTYPE Operation against a key holding the wrong kind of value", ResultType.Error);
return TypedRedisValue.Error("WRONGTYPE Operation against a key holding the wrong kind of value");
}
catch (Exception ex)
{
if (!_isShutdown) Log(ex.Message);
return RedisResult.Create("ERR " + ex.Message, ResultType.Error);
return TypedRedisValue.Error("ERR " + ex.Message);
}
}
......@@ -525,39 +530,40 @@ internal static string ToLower(RawResult value)
return val.ToLowerInvariant();
}
protected static RedisResult CommandNotFound(string command)
=> RedisResult.Create($"ERR unknown command '{command}'", ResultType.Error);
protected static TypedRedisValue CommandNotFound(string command)
=> TypedRedisValue.Error($"ERR unknown command '{command}'");
[RedisCommand(1, LockFree = true)]
protected virtual RedisResult Command(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue Command(RedisClient client, RedisRequest request)
{
var results = new RedisResult[_commands.Count];
var results = TypedRedisValue.Rent(_commands.Count);
int index = 0;
foreach (var pair in _commands)
results[index++] = CommandInfo(pair.Value);
return RedisResult.Create(results);
return results;
}
[RedisCommand(-2, "command", "info", LockFree = true)]
protected virtual RedisResult CommandInfo(RedisClient client, RedisRequest request)
protected virtual TypedRedisValue CommandInfo(RedisClient client, RedisRequest request)
{
var results = new RedisResult[request.Count - 2];
var results = TypedRedisValue.Rent(request.Count - 2);
for (int i = 2; i < request.Count; i++)
{
results[i - 2] = _commands.TryGetValue(request.GetString(i), out var cmd)
? CommandInfo(cmd) : null;
? CommandInfo(cmd) : TypedRedisValue.NullArray;
}
return RedisResult.Create(results);
return results;
}
private RedisResult CommandInfo(RespCommand command)
=> RedisResult.Create(new[]
private TypedRedisValue CommandInfo(RespCommand command)
{
RedisResult.Create(command.Command, ResultType.BulkString),
RedisResult.Create(command.NetArity(), ResultType.Integer),
RedisResult.EmptyArray,
RedisResult.Zero,
RedisResult.Zero,
RedisResult.Zero,
});
var arr = TypedRedisValue.Rent(6);
arr[0] = TypedRedisValue.BulkString(command.Command);
arr[1] = TypedRedisValue.Integer(command.NetArity());
arr[2] = TypedRedisValue.EmptyArray;
arr[3] = TypedRedisValue.Zero;
arr[4] = TypedRedisValue.Zero;
arr[5] = TypedRedisValue.Zero;
return arr;
}
}
}
using System;
using System.Buffers;
namespace StackExchange.Redis
{
/// <summary>
/// A RedisValue with an eplicit encoding type, which could represent an array of items
/// </summary>
public readonly struct TypedRedisValue
{
internal static TypedRedisValue Rent(int count)
=> new TypedRedisValue(ArrayPool<TypedRedisValue>.Shared.Rent(count), count);
/// <summary>
/// An invalid empty value that has no type
/// </summary>
public static TypedRedisValue Nil => default;
/// <summary>
/// Returns whether this value is an invalid empty value
/// </summary>
public bool IsNil => Type == ResultType.None;
/// <summary>
/// Returns whether this value represents a null array
/// </summary>
public bool IsNullArray => Type == ResultType.MultiBulk && _oversizedItems == null;
/// <summary>
/// The type of value being represented
/// </summary>
public ResultType Type { get; }
private readonly RedisValue _value;
private readonly TypedRedisValue[] _oversizedItems;
/// <summary>
/// Gets items from an array by index
/// </summary>
public TypedRedisValue this[int index]
{
get => _oversizedItems[index];
internal set => _oversizedItems[index] = value;
}
/// <summary>
/// Gets the length of the value as an array
/// </summary>
public int Length { get; }
/// <summary>
/// Initialize a TypedRedisValue from a value and optionally a type
/// </summary>
private TypedRedisValue(RedisValue value, ResultType? type = null)
{
Type = type ?? (value.IsInteger ? ResultType.Integer : ResultType.BulkString);
_value = value;
Length = default;
_oversizedItems = default;
}
/// <summary>
/// Initialize a TypedRedisValue that represents an error
/// </summary>
public static TypedRedisValue Error(string value)
=> new TypedRedisValue(value, ResultType.Error);
/// <summary>
/// Initialize a TypedRedisValue that represents a simple string
/// </summary>
public static TypedRedisValue SimpleString(string value)
=> new TypedRedisValue(value, ResultType.SimpleString);
/// <summary>
/// The simple string OK
/// </summary>
public static TypedRedisValue OK { get; } = SimpleString("OK");
internal static TypedRedisValue Zero { get; } = Integer(0);
internal static TypedRedisValue One { get; } = Integer(1);
internal static TypedRedisValue NullArray { get; } = MultiBulk(null);
internal static TypedRedisValue EmptyArray { get; } = MultiBulk(Array.Empty<TypedRedisValue>());
/// <summary>
/// Gets the array elements as a span
/// </summary>
public ReadOnlySpan<TypedRedisValue> Span => new ReadOnlySpan<TypedRedisValue>(_oversizedItems, 0, Length);
internal Span<TypedRedisValue> MutableSpan => new Span<TypedRedisValue>(_oversizedItems, 0, Length);
/// <summary>
/// Initialize a TypedRedisValue that represents an integer
/// </summary>
public static TypedRedisValue Integer(long value)
=> new TypedRedisValue(value, ResultType.Integer);
/// <summary>
/// Initialize a TypedRedisValue from an array
/// </summary>
public static TypedRedisValue MultiBulk(TypedRedisValue[] items)
=> new TypedRedisValue(items, items == null ? 0 : items.Length);
/// <summary>
/// Initialize a TypedRedisValue from an oversized array
/// </summary>
public static TypedRedisValue MultiBulk(TypedRedisValue[] oversizedItems, int count)
=> new TypedRedisValue(oversizedItems, count);
/// <summary>
/// Initialize a TypedRedisValue that represents a bulk string
/// </summary>
public static TypedRedisValue BulkString(RedisValue value)
=> new TypedRedisValue(value, ResultType.BulkString);
private TypedRedisValue(TypedRedisValue[] oversizedItems, int count)
{
if (oversizedItems == null)
{
if (count != 0) throw new ArgumentOutOfRangeException(nameof(count));
}
else
{
if (count < 0 || count > oversizedItems.Length) throw new ArgumentOutOfRangeException(nameof(count));
if (count == 0) oversizedItems = Array.Empty<TypedRedisValue>();
}
_value = default;
Type = ResultType.MultiBulk;
Length = count;
_oversizedItems = oversizedItems;
}
internal void Recycle(int limit = -1)
{
var arr = _oversizedItems;
if (arr != null)
{
if (limit < 0) limit = Length;
for (int i = 0; i < limit; i++)
{
arr[i].Recycle();
}
ArrayPool<TypedRedisValue>.Shared.Return(arr, clearArray: false);
}
}
/// <summary>
/// Get the underlying value assuming that it is a valid type with a meaningful value
/// </summary>
internal RedisValue AsRedisValue() => _value;
/// <summary>
/// Obtain the value as a string
/// </summary>
public override string ToString()
{
switch (Type)
{
case ResultType.BulkString:
case ResultType.SimpleString:
case ResultType.Integer:
case ResultType.Error:
return $"{Type}:{_value}";
case ResultType.MultiBulk:
return $"{Type}:[{Length}]";
default:
return Type.ToString();
}
}
/// <summary>
/// Not supported
/// </summary>
public override int GetHashCode() => throw new NotSupportedException();
/// <summary>
/// Not supported
/// </summary>
public override bool Equals(object obj) => throw new NotSupportedException();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment