Skip to content

Commit

Permalink
support for CLIENT KILL MAXAGE (#2727)
Browse files Browse the repository at this point in the history
  • Loading branch information
atakavci and NickCraver authored Jul 9, 2024
1 parent 0d38f6f commit a64ca14
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 41 deletions.
179 changes: 179 additions & 0 deletions src/StackExchange.Redis/APITypes/ClientKillFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Net;

namespace StackExchange.Redis;

/// <summary>
/// Filter determining which Redis clients to kill.
/// </summary>
/// <seealso href="https://redis.io/docs/latest/commands/client-kill/"/>
public class ClientKillFilter
{
/// <summary>
/// Filter arguments builder for `CLIENT KILL`.
/// </summary>
public ClientKillFilter() { }

/// <summary>
/// The ID of the client to kill.
/// </summary>
public long? Id { get; private set; }

/// <summary>
/// The type of client.
/// </summary>
public ClientType? ClientType { get; private set; }

/// <summary>
/// The authenticated ACL username.
/// </summary>
public string? Username { get; private set; }

/// <summary>
/// The endpoint to kill.
/// </summary>
public EndPoint? Endpoint { get; private set; }

/// <summary>
/// The server endpoint to kill.
/// </summary>
public EndPoint? ServerEndpoint { get; private set; }

/// <summary>
/// Whether to skip the current connection.
/// </summary>
public bool? SkipMe { get; private set; }

/// <summary>
/// Age of connection in seconds.
/// </summary>
public long? MaxAgeInSeconds { get; private set; }

/// <summary>
/// Sets client id filter.
/// </summary>
/// <param name="id">Id of the client to kill.</param>
public ClientKillFilter WithId(long? id)
{
Id = id;
return this;
}

/// <summary>
/// Sets client type filter.
/// </summary>
/// <param name="clientType">The type of the client.</param>
public ClientKillFilter WithClientType(ClientType? clientType)
{
ClientType = clientType;
return this;
}

/// <summary>
/// Sets the username filter.
/// </summary>
/// <param name="username">Authenticated ACL username.</param>
public ClientKillFilter WithUsername(string? username)
{
Username = username;
return this;
}

/// <summary>
/// Set the endpoint filter.
/// </summary>
/// <param name="endpoint">The endpoint to kill.</param>
public ClientKillFilter WithEndpoint(EndPoint? endpoint)
{
Endpoint = endpoint;
return this;
}

/// <summary>
/// Set the server endpoint filter.
/// </summary>
/// <param name="serverEndpoint">The server endpoint to kill.</param>
public ClientKillFilter WithServerEndpoint(EndPoint? serverEndpoint)
{
ServerEndpoint = serverEndpoint;
return this;
}

/// <summary>
/// Set the skipMe filter (whether to skip the current connection).
/// </summary>
/// <param name="skipMe">Whether to skip the current connection.</param>
public ClientKillFilter WithSkipMe(bool? skipMe)
{
SkipMe = skipMe;
return this;
}

/// <summary>
/// Set the MaxAgeInSeconds filter.
/// </summary>
/// <param name="maxAgeInSeconds">Age of connection in seconds</param>
public ClientKillFilter WithMaxAgeInSeconds(long? maxAgeInSeconds)
{
MaxAgeInSeconds = maxAgeInSeconds;
return this;
}

internal List<RedisValue> ToList(bool withReplicaCommands)
{
var parts = new List<RedisValue>(15)
{
RedisLiterals.KILL
};
if (Id != null)
{
parts.Add(RedisLiterals.ID);
parts.Add(Id.Value);
}
if (ClientType != null)
{
parts.Add(RedisLiterals.TYPE);
switch (ClientType.Value)
{
case Redis.ClientType.Normal:
parts.Add(RedisLiterals.normal);
break;
case Redis.ClientType.Replica:
parts.Add(withReplicaCommands ? RedisLiterals.replica : RedisLiterals.slave);
break;
case Redis.ClientType.PubSub:
parts.Add(RedisLiterals.pubsub);
break;
default:
throw new ArgumentOutOfRangeException(nameof(ClientType));
}
}
if (Username != null)
{
parts.Add(RedisLiterals.USERNAME);
parts.Add(Username);
}
if (Endpoint != null)
{
parts.Add(RedisLiterals.ADDR);
parts.Add((RedisValue)Format.ToString(Endpoint));
}
if (ServerEndpoint != null)
{
parts.Add(RedisLiterals.LADDR);
parts.Add((RedisValue)Format.ToString(ServerEndpoint));
}
if (SkipMe != null)
{
parts.Add(RedisLiterals.SKIPME);
parts.Add(SkipMe.Value ? RedisLiterals.yes : RedisLiterals.no);
}
if (MaxAgeInSeconds != null)
{
parts.Add(RedisLiterals.MAXAGE);
parts.Add(MaxAgeInSeconds);
}
return parts;
}
}
12 changes: 12 additions & 0 deletions src/StackExchange.Redis/Interfaces/IServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ public partial interface IServer : IRedis
/// <inheritdoc cref="ClientKill(long?, ClientType?, EndPoint?, bool, CommandFlags)"/>
Task<long> ClientKillAsync(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>
/// <param name="filter"></param>
/// <param name="flags"></param>
/// <returns></returns>
long ClientKill(ClientKillFilter filter, CommandFlags flags = CommandFlags.None);

/// <inheritdoc cref="ClientKill(ClientKillFilter, CommandFlags)"/>
Task<long> ClientKillAsync(ClientKillFilter filter, CommandFlags flags = CommandFlags.None);


/// <summary>
/// The <c>CLIENT LIST</c> command returns information and statistics about the client connections server in a mostly human readable format.
/// </summary>
Expand Down
21 changes: 20 additions & 1 deletion src/StackExchange.Redis/PublicAPI/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,22 @@ StackExchange.Redis.ClientInfo.ProtocolVersion.get -> string?
StackExchange.Redis.ClientInfo.Raw.get -> string?
StackExchange.Redis.ClientInfo.SubscriptionCount.get -> int
StackExchange.Redis.ClientInfo.TransactionCommandLength.get -> int
StackExchange.Redis.ClientKillFilter
StackExchange.Redis.ClientKillFilter.ClientKillFilter() -> void
StackExchange.Redis.ClientKillFilter.ClientType.get -> StackExchange.Redis.ClientType?
StackExchange.Redis.ClientKillFilter.Endpoint.get -> System.Net.EndPoint?
StackExchange.Redis.ClientKillFilter.Id.get -> long?
StackExchange.Redis.ClientKillFilter.MaxAgeInSeconds.get -> long?
StackExchange.Redis.ClientKillFilter.ServerEndpoint.get -> System.Net.EndPoint?
StackExchange.Redis.ClientKillFilter.SkipMe.get -> bool?
StackExchange.Redis.ClientKillFilter.Username.get -> string?
StackExchange.Redis.ClientKillFilter.WithClientType(StackExchange.Redis.ClientType? clientType) -> StackExchange.Redis.ClientKillFilter!
StackExchange.Redis.ClientKillFilter.WithEndpoint(System.Net.EndPoint? endpoint) -> StackExchange.Redis.ClientKillFilter!
StackExchange.Redis.ClientKillFilter.WithId(long? id) -> StackExchange.Redis.ClientKillFilter!
StackExchange.Redis.ClientKillFilter.WithMaxAgeInSeconds(long? maxAgeInSeconds) -> StackExchange.Redis.ClientKillFilter!
StackExchange.Redis.ClientKillFilter.WithServerEndpoint(System.Net.EndPoint? serverEndpoint) -> StackExchange.Redis.ClientKillFilter!
StackExchange.Redis.ClientKillFilter.WithSkipMe(bool? skipMe) -> StackExchange.Redis.ClientKillFilter!
StackExchange.Redis.ClientKillFilter.WithUsername(string? username) -> StackExchange.Redis.ClientKillFilter!
StackExchange.Redis.ClientType
StackExchange.Redis.ClientType.Normal = 0 -> StackExchange.Redis.ClientType
StackExchange.Redis.ClientType.PubSub = 2 -> StackExchange.Redis.ClientType
Expand Down Expand Up @@ -1006,8 +1022,10 @@ StackExchange.Redis.IServer.AllowSlaveWrites.get -> bool
StackExchange.Redis.IServer.AllowSlaveWrites.set -> void
StackExchange.Redis.IServer.ClientKill(long? id = null, StackExchange.Redis.ClientType? clientType = null, System.Net.EndPoint? endpoint = null, bool skipMe = true, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IServer.ClientKill(System.Net.EndPoint! endpoint, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> void
StackExchange.Redis.IServer.ClientKill(StackExchange.Redis.ClientKillFilter! filter, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> long
StackExchange.Redis.IServer.ClientKillAsync(long? id = null, StackExchange.Redis.ClientType? clientType = null, System.Net.EndPoint? endpoint = null, bool skipMe = true, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
StackExchange.Redis.IServer.ClientKillAsync(System.Net.EndPoint! endpoint, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task!
StackExchange.Redis.IServer.ClientKillAsync(StackExchange.Redis.ClientKillFilter! filter, StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<long>!
StackExchange.Redis.IServer.ClientList(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> StackExchange.Redis.ClientInfo![]!
StackExchange.Redis.IServer.ClientListAsync(StackExchange.Redis.CommandFlags flags = StackExchange.Redis.CommandFlags.None) -> System.Threading.Tasks.Task<StackExchange.Redis.ClientInfo![]!>!
StackExchange.Redis.IServer.ClusterConfiguration.get -> StackExchange.Redis.ClusterConfiguration?
Expand Down Expand Up @@ -1851,4 +1869,5 @@ static StackExchange.Redis.RedisResult.Create(StackExchange.Redis.RedisValue[]!
virtual StackExchange.Redis.RedisResult.Length.get -> int
virtual StackExchange.Redis.RedisResult.this[int index].get -> StackExchange.Redis.RedisResult!
StackExchange.Redis.ConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) -> void
StackExchange.Redis.IConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) -> void
StackExchange.Redis.IConnectionMultiplexer.AddLibraryNameSuffix(string! suffix) -> void

3 changes: 3 additions & 0 deletions src/StackExchange.Redis/RedisLiterals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public static readonly RedisValue
IDLETIME = "IDLETIME",
KEEPTTL = "KEEPTTL",
KILL = "KILL",
LADDR = "LADDR",
LATEST = "LATEST",
LEFT = "LEFT",
LEN = "LEN",
Expand All @@ -101,6 +102,7 @@ public static readonly RedisValue
MATCH = "MATCH",
MALLOC_STATS = "MALLOC-STATS",
MAX = "MAX",
MAXAGE = "MAXAGE",
MAXLEN = "MAXLEN",
MIN = "MIN",
MINMATCHLEN = "MINMATCHLEN",
Expand Down Expand Up @@ -137,6 +139,7 @@ public static readonly RedisValue
STATS = "STATS",
STORE = "STORE",
TYPE = "TYPE",
USERNAME = "USERNAME",
WEIGHTS = "WEIGHTS",
WITHMATCHLEN = "WITHMATCHLEN",
WITHSCORES = "WITHSCORES",
Expand Down
56 changes: 16 additions & 40 deletions src/StackExchange.Redis/RedisServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,46 +75,22 @@ public Task<long> ClientKillAsync(long? id = null, ClientType? clientType = null
return ExecuteAsync(msg, ResultProcessor.Int64);
}

private Message GetClientKillMessage(EndPoint? endpoint, long? id, ClientType? clientType, bool skipMe, CommandFlags flags)
public long ClientKill(ClientKillFilter filter, CommandFlags flags = CommandFlags.None)
{
var parts = new List<RedisValue>(9)
{
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.Replica:
parts.Add(Features.ReplicaCommands ? RedisLiterals.replica : RedisLiterals.slave);
break;
case ClientType.PubSub:
parts.Add(RedisLiterals.pubsub);
break;
default:
throw new ArgumentOutOfRangeException(nameof(clientType));
}
}
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);
var msg = Message.Create(-1, flags, RedisCommand.CLIENT, filter.ToList(Features.ReplicaCommands));
return ExecuteSync(msg, ResultProcessor.Int64);
}

public Task<long> ClientKillAsync(ClientKillFilter filter, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(-1, flags, RedisCommand.CLIENT, filter.ToList(Features.ReplicaCommands));
return ExecuteAsync(msg, ResultProcessor.Int64);
}

private Message GetClientKillMessage(EndPoint? endpoint, long? id, ClientType? clientType, bool? skipMe, CommandFlags flags)
{
var args = new ClientKillFilter().WithId(id).WithClientType(clientType).WithEndpoint(endpoint).WithSkipMe(skipMe).ToList(Features.ReplicaCommands);
return Message.Create(-1, flags, RedisCommand.CLIENT, args);
}

public ClientInfo[] ClientList(CommandFlags flags = CommandFlags.None)
Expand Down Expand Up @@ -408,7 +384,7 @@ public Task<DateTime> LastSaveAsync(CommandFlags flags = CommandFlags.None)
}

public void MakeMaster(ReplicationChangeOptions options, TextWriter? log = null)
{
{
// Do you believe in magic?
multiplexer.MakePrimaryAsync(server, options, log).Wait(60000);
}
Expand Down
Loading

0 comments on commit a64ca14

Please sign in to comment.