Commit 3d70d03a authored by Marc Gravell's avatar Marc Gravell

Redis 2.8.12 tweaks; EVAL/EVALSHA no longer break the database; new CLIENT KILL overloads

parent 9f0442b6
......@@ -12,4 +12,5 @@ Mono/
*.rdb
*.orig
redis-cli.exe
Redis Configs/*.dat
\ No newline at end of file
Redis Configs/*.dat
RedisQFork*.dat
\ No newline at end of file
......@@ -63,6 +63,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="StackExchange\Redis\Aggregate.cs" />
<Compile Include="StackExchange\Redis\ClientType.cs" />
<Compile Include="StackExchange\Redis\ExtensionMethods.cs" />
<Compile Include="StackExchange\Redis\HashEntry.cs" />
<Compile Include="StackExchange\Redis\InternalErrorEventArgs.cs" />
......
......@@ -107,6 +107,11 @@ public int Port
/// </summary>
public int TransactionCommandLength { get; private set; }
/// <summary>
/// an unique 64-bit client ID (introduced in Redis 2.8.12).
/// </summary>
public long Id { get;private set; }
/// <summary>
/// Format the object as a string
/// </summary>
......@@ -116,6 +121,19 @@ public override string ToString()
return string.IsNullOrWhiteSpace(Name) ? addr : (addr + " - " + Name);
}
/// <summary>
/// The class of the connection
/// </summary>
public ClientType ClientType
{
get
{
if (SubscriptionCount != 0 || PatternSubscriptionCount != 0) return ClientType.PubSub;
if ((Flags & ClientFlags.Slave) != 0) return ClientType.Slave;
return ClientType.Normal;
}
}
internal static ClientInfo[] Parse(string input)
{
if (input == null) return null;
......@@ -161,6 +179,7 @@ internal static ClientInfo[] Parse(string input)
AddFlag(ref flags, value, ClientFlags.CloseASAP, 'A');
client.Flags = flags;
break;
case "id": client.Id = Format.ParseInt64(value); break;
}
}
clients.Add(client);
......
namespace StackExchange.Redis
{
/// <summary>
/// The class of the connection
/// </summary>
public enum ClientType
{
/// <summary>
/// Regular connections, including MONITOR connections
/// </summary>
Normal,
/// <summary>
/// Replication connections
/// </summary>
Slave,
/// <summary>
/// Subscription connections
/// </summary>
PubSub
}
}
......@@ -11,6 +11,11 @@ public static int ParseInt32(string s)
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
}
public static long ParseInt64(string s)
{
return long.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
}
public static string ToString(int value)
{
return value.ToString(NumberFormatInfo.InvariantInfo);
......
......@@ -63,6 +63,19 @@ public partial interface IServer : IRedis
/// <remarks>http://redis.io/commands/client-kill</remarks>
Task ClientKillAsync(EndPoint endpoint, CommandFlags flags = CommandFlags.None);
/// <summary>
/// The CLIENT KILL command closes multiple connections that match the specified filters
/// </summary>
/// <returns>the number of clients killed.</returns>
/// <remarks>http://redis.io/commands/client-kill</remarks>
long ClientKill(long? id = null, ClientType? clientType = null, EndPoint endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None);
/// <summary>
/// The CLIENT KILL command closes multiple connections that match the specified filters
/// </summary>
/// <returns>the number of clients killed.</returns>
/// <remarks>http://redis.io/commands/client-kill</remarks>
Task<long> ClientKillAsync(long? id = null, ClientType? clientType = null, EndPoint endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None);
/// <summary>
/// The CLIENT LIST command returns information and statistics about the client connections server in a mostly human readable format.
/// </summary>
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
......@@ -417,15 +419,29 @@ internal static Message Create(int db, CommandFlags flags, RedisCommand command,
}
}
internal static Message Create(int db, CommandFlags flags, RedisCommand command, RedisKey[] keys)
internal static Message Create(int db, CommandFlags flags, RedisCommand command, IList<RedisKey> keys)
{
switch (keys.Length)
switch (keys.Count)
{
case 0: return new CommandMessage(db, flags, command);
case 1: return new CommandKeyMessage(db, flags, command, keys[0]);
case 2: return new CommandKeyKeyMessage(db, flags, command, keys[0], keys[1]);
case 3: return new CommandKeyKeyKeyMessage(db, flags, command, keys[0], keys[1], keys[2]);
default: return new CommandKeysMessage(db, flags, command, keys);
default: return new CommandKeysMessage(db, flags, command, (keys as RedisKey[]) ?? keys.ToArray());
}
}
internal static Message Create(int db, CommandFlags flags, RedisCommand command, IList<RedisValue> values)
{
switch (values.Count)
{
case 0: return new CommandMessage(db, flags, command);
case 1: return new CommandValueMessage(db, flags, command, values[0]);
case 2: return new CommandValueValueMessage(db, flags, command, values[0], values[1]);
case 3: return new CommandValueValueValueMessage(db, flags, command, values[0], values[1], values[2]);
// no 4; not worth adding
case 5: return new CommandValueValueValueValueValueMessage(db, flags, command, values[0], values[1], values[2], values[3], values[4]);
default: return new CommandValuesMessage(db, flags, command, (values as RedisValue[]) ?? values.ToArray());
}
}
......@@ -735,6 +751,26 @@ internal override void WriteImpl(PhysicalConnection physical)
physical.Write(Key);
}
}
sealed class CommandValuesMessage : Message
{
private readonly RedisValue[] values;
public CommandValuesMessage(int db, CommandFlags flags, RedisCommand command, RedisValue[] values) : base(db, flags, command)
{
for (int i = 0; i < values.Length; i++)
{
values[i].AssertNotNull();
}
this.values = values;
}
internal override void WriteImpl(PhysicalConnection physical)
{
physical.WriteHeader(command, values.Length);
for (int i = 0; i < values.Length; i++)
{
physical.Write(values[i]);
}
}
}
sealed class CommandKeysMessage : Message
{
private readonly RedisKey[] keys;
......
......@@ -802,6 +802,11 @@ private bool WriteMessageToServer(PhysicalConnection connection, Message message
{
case RedisCommand.EVAL:
case RedisCommand.EVALSHA:
if(!serverEndPoint.GetFeatures().ScriptingDatabaseSafe)
{
connection.SetUnknownDatabase();
}
break;
case RedisCommand.DISCARD:
case RedisCommand.EXEC:
connection.SetUnknownDatabase();
......
......@@ -24,6 +24,7 @@ public struct RedisFeatures
v2_6_9 = new Version(2, 6, 9),
v2_6_12 = new Version(2, 6, 12),
v2_8_0 = new Version(2, 8, 0),
v2_8_12 = new Version(2, 8, 12),
v2_9_5 = new Version(2, 9, 5);
private readonly Version version;
......@@ -131,6 +132,11 @@ public RedisFeatures(Version version)
/// </summary>
public bool Time { get { return Version >= v2_6_0; } }
/// <summary>
/// Are Lua changes to the calling database transparent to the calling client?
/// </summary>
public bool ScriptingDatabaseSafe { get { return Version >= v2_8_12; } }
/// <summary>
/// The Redis version of the server
/// </summary>
......
......@@ -8,65 +8,74 @@ internal static class RedisLiterals
// unlike primary commands, these do not get altered by the command-map; we may as
// well compute the bytes once and share them
public static readonly RedisValue
BEFORE = "BEFORE",
ADDR = "ADDR",
AFTER = "AFTER",
AGGREGATE = "AGGREGATE",
ALPHA = "ALPHA",
AND = "AND",
BEFORE = "BEFORE",
BY = "BY",
CHANNELS = "CHANNELS",
COPY = "COPY",
COUNT = "COUNT",
DESC = "DESC",
EX = "EX",
EXISTS = "EXISTS",
FLUSH = "FLUSH",
GET = "GET",
SET = "SET",
SEGFAULT = "SEGFAULT",
PAUSE = "PAUSE",
SETNAME = "SETNAME",
GETNAME = "GETNAME",
NUMPAT = "NUMPAT",
NUMSUB = "NUMSUB",
ID = "ID",
KILL = "KILL",
LIMIT = "LIMIT",
LIST = "LIST",
NODES = "NODES",
COUNT = "COUNT",
LOAD = "LOAD",
MATCH = "MATCH",
OBJECT = "OBJECT",
REWRITE = "REWRITE",
KILL = "KILL",
SAVE = "SAVE",
MAX = "MAX",
MIN = "MIN",
NODES = "NODES",
NOSAVE = "NOSAVE",
RESET = "RESET",
NOT = "NOT",
NUMPAT = "NUMPAT",
NUMSUB = "NUMSUB",
NX = "NX",
PX = "PX",
EX = "EX",
XX = "XX",
WITHSCORES = "WITHSCORES",
LIMIT = "LIMIT",
AND = "AND",
OBJECT = "OBJECT",
OR = "OR",
NOT = "NOT",
XOR = "XOR",
PAUSE = "PAUSE",
PING = "PING",
PX = "PX",
REPLACE = "REPLACE",
RESET = "RESET",
RESETSTAT = "RESETSTAT",
BY = "BY",
DESC = "DESC",
ALPHA = "ALPHA",
REWRITE = "REWRITE",
SAVE = "SAVE",
SEGFAULT = "SEGFAULT",
SET = "SET",
SETNAME = "SETNAME",
SKIPME = "SKIPME",
STORE = "STORE",
TYPE = "TYPE",
WEIGHTS = "WEIGHTS",
MIN = "MIN",
MAX = "MAX",
AGGREGATE = "AGGREGATE",
LOAD = "LOAD",
EXISTS = "EXISTS",
FLUSH = "FLUSH",
PING = "PING",
COPY = "COPY",
REPLACE = "REPLACE",
WITHSCORES = "WITHSCORES",
XOR = "XOR",
XX = "XX",
// DO NOT CHANGE CASE: these are configuration settings and MUST be as-is
databases = "databases",
timeout = "timeout",
slave_read_only = "slave-read-only",
yes = "yes",
no = "no",
normal = "normal",
pubsub = "pubsub",
replication = "replication",
server = "server",
Wildcard = "*",
slave = "slave",
slave_read_only = "slave-read-only",
timeout = "timeout",
yes = "yes",
MinusSymbol = "-",
PlusSumbol = "+",
MinusSymbol = "-";
Wildcard = "*";
public static readonly byte[] BytesOK = Encoding.UTF8.GetBytes("OK");
public static readonly byte[] BytesPONG = Encoding.UTF8.GetBytes("PONG");
......
......@@ -52,6 +52,58 @@ public Task ClientKillAsync(EndPoint endpoint, CommandFlags flags = CommandFlags
return ExecuteAsync(msg, ResultProcessor.DemandOK);
}
public long ClientKill(long? id = null, ClientType? clientType = null, EndPoint endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None)
{
var msg = GetClientKillMessage(endpoint, id, clientType, skipMe, flags);
return ExecuteSync(msg, ResultProcessor.Int64);
}
public Task<long> ClientKillAsync(long? id = null, ClientType? clientType = null, EndPoint endpoint = null, bool skipMe = true, CommandFlags flags = CommandFlags.None)
{
var msg = GetClientKillMessage(endpoint, id, clientType, skipMe, flags);
return ExecuteAsync(msg, ResultProcessor.Int64);
}
Message GetClientKillMessage(EndPoint endpoint, long? id, ClientType? clientType, bool skipMe, CommandFlags flags)
{
List<RedisValue> parts = new List<RedisValue>(9);
parts.Add(RedisLiterals.KILL);
if(id != null)
{
parts.Add(RedisLiterals.ID);
parts.Add(id.Value);
}
if (clientType != null)
{
parts.Add(RedisLiterals.TYPE);
switch(clientType.Value)
{
case ClientType.Normal:
parts.Add(RedisLiterals.normal);
break;
case ClientType.Slave:
parts.Add(RedisLiterals.slave);
break;
case ClientType.PubSub:
parts.Add(RedisLiterals.pubsub);
break;
default:
throw new ArgumentOutOfRangeException("clientType");
}
parts.Add(id.Value);
}
if (endpoint != null)
{
parts.Add(RedisLiterals.ADDR);
parts.Add((RedisValue)Format.ToString(endpoint));
}
if(!skipMe)
{
parts.Add(RedisLiterals.SKIPME);
parts.Add(RedisLiterals.no);
}
return Message.Create(-1, flags, RedisCommand.CLIENT, parts);
}
public ClientInfo[] ClientList(CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(-1, flags, RedisCommand.CLIENT, RedisLiterals.LIST);
......
......@@ -64,6 +64,9 @@
<Compile Include="..\StackExchange.Redis\StackExchange\Redis\Bitwise.cs" />
<Compile Include="..\StackExchange.Redis\StackExchange\Redis\ClientFlags.cs" />
<Compile Include="..\StackExchange.Redis\StackExchange\Redis\ClientInfo.cs" />
<Compile Include="..\StackExchange.Redis\StackExchange\Redis\ClientType.cs">
<Link>ClientType.cs</Link>
</Compile>
<Compile Include="..\StackExchange.Redis\StackExchange\Redis\ClusterConfiguration.cs" />
<Compile Include="..\StackExchange.Redis\StackExchange\Redis\CommandFlags.cs" />
<Compile Include="..\StackExchange.Redis\StackExchange\Redis\CommandMap.cs" />
......
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