Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for CLIENT KILL MAXAGE #2727

Merged
merged 8 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading