Implement `CLIENT KILL MAXAGE <maxage>` (#12299)

Adds an ability to kill clients older than a specified age.

Also, fixed the age calculation in `catClientInfoString` to use
`commandTimeSnapshot`
instead of the old `server.unixtime`, and added missing documentation
for
`CLIENT KILL ID` to output of `CLIENT help`.

---------

Co-authored-by: Oran Agra <oran@redislabs.com>
This commit is contained in:
Slava Koyfman 2024-01-30 20:24:36 +02:00 committed by GitHub
parent 7c9f41b52b
commit 24f6d08b3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 61 additions and 3 deletions

View File

@ -1177,6 +1177,7 @@ commandHistory CLIENT_KILL_History[] = {
{"3.2.0","Added `master` type in for `TYPE` option."},
{"5.0.0","Replaced `slave` `TYPE` with `replica`. `slave` still supported for backward compatibility."},
{"6.2.0","`LADDR` option."},
{"8.0.0","`MAXAGE` option."},
};
#endif
@ -1213,12 +1214,13 @@ struct COMMAND_ARG CLIENT_KILL_filter_new_format_Subargs[] = {
{MAKE_ARG("addr",ARG_TYPE_STRING,-1,"ADDR",NULL,NULL,CMD_ARG_OPTIONAL,0,NULL),.display_text="ip:port"},
{MAKE_ARG("laddr",ARG_TYPE_STRING,-1,"LADDR",NULL,"6.2.0",CMD_ARG_OPTIONAL,0,NULL),.display_text="ip:port"},
{MAKE_ARG("skipme",ARG_TYPE_ONEOF,-1,"SKIPME",NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=CLIENT_KILL_filter_new_format_skipme_Subargs},
{MAKE_ARG("maxage",ARG_TYPE_INTEGER,-1,"MAXAGE",NULL,"8.0.0",CMD_ARG_OPTIONAL,0,NULL)},
};
/* CLIENT KILL filter argument table */
struct COMMAND_ARG CLIENT_KILL_filter_Subargs[] = {
{MAKE_ARG("old-format",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,0,"2.8.12"),.display_text="ip:port"},
{MAKE_ARG("new-format",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,6,NULL),.subargs=CLIENT_KILL_filter_new_format_Subargs},
{MAKE_ARG("new-format",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,7,NULL),.subargs=CLIENT_KILL_filter_new_format_Subargs},
};
/* CLIENT KILL argument table */
@ -1543,7 +1545,7 @@ struct COMMAND_STRUCT CLIENT_Subcommands[] = {
{MAKE_CMD("help","Returns helpful text about the different subcommands.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_HELP_History,0,CLIENT_HELP_Tips,0,clientCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_HELP_Keyspecs,0,NULL,0)},
{MAKE_CMD("id","Returns the unique client ID of the connection.","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_ID_History,0,CLIENT_ID_Tips,0,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_ID_Keyspecs,0,NULL,0)},
{MAKE_CMD("info","Returns information about the connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_INFO_History,0,CLIENT_INFO_Tips,1,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_INFO_Keyspecs,0,NULL,0)},
{MAKE_CMD("kill","Terminates open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,5,CLIENT_KILL_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_KILL_Keyspecs,0,NULL,1),.args=CLIENT_KILL_Args},
{MAKE_CMD("kill","Terminates open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,6,CLIENT_KILL_Tips,0,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_KILL_Keyspecs,0,NULL,1),.args=CLIENT_KILL_Args},
{MAKE_CMD("list","Lists open connections.","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,6,CLIENT_LIST_Tips,1,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_LIST_Keyspecs,0,NULL,2),.args=CLIENT_LIST_Args},
{MAKE_CMD("no-evict","Sets the client eviction mode of the connection.","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_EVICT_History,0,CLIENT_NO_EVICT_Tips,0,clientCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,CLIENT_NO_EVICT_Keyspecs,0,NULL,1),.args=CLIENT_NO_EVICT_Args},
{MAKE_CMD("no-touch","Controls whether commands sent by the client affect the LRU/LFU of accessed keys.","O(1)","7.2.0",CMD_DOC_NONE,NULL,NULL,"connection",COMMAND_GROUP_CONNECTION,CLIENT_NO_TOUCH_History,0,CLIENT_NO_TOUCH_Tips,0,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,CLIENT_NO_TOUCH_Keyspecs,0,NULL,1),.args=CLIENT_NO_TOUCH_Args},

View File

@ -27,6 +27,10 @@
[
"6.2.0",
"`LADDR` option."
],
[
"8.0.0",
"`MAXAGE` option."
]
],
"command_flags": [
@ -136,6 +140,13 @@
"token": "NO"
}
]
},
{
"token": "MAXAGE",
"name": "maxage",
"type": "integer",
"optional": true,
"since": "8.0.0"
}
]
}

View File

@ -2850,7 +2850,7 @@ sds catClientInfoString(sds s, client *client) {
" laddr=%s", getClientSockname(client),
" %s", connGetInfo(client->conn, conninfo, sizeof(conninfo)),
" name=%s", client->name ? (char*)client->name->ptr : "",
" age=%I", (long long)(server.unixtime - client->ctime),
" age=%I", (long long)(commandTimeSnapshot() / 1000 - client->ctime),
" idle=%I", (long long)(server.unixtime - client->lastinteraction),
" flags=%s", flags,
" db=%i", client->db->id,
@ -3042,6 +3042,10 @@ void clientCommand(client *c) {
" Kill connections authenticated by <username>.",
" * SKIPME (YES|NO)",
" Skip killing current connection (default: yes).",
" * ID <client-id>",
" Kill connections by client id.",
" * MAXAGE <maxage>",
" Kill connections older than the specified age.",
"LIST [options ...]",
" Return information about client connections. Options:",
" * TYPE (NORMAL|MASTER|REPLICA|PUBSUB)",
@ -3153,6 +3157,7 @@ NULL
user *user = NULL;
int type = -1;
uint64_t id = 0;
long long max_age = 0;
int skipme = 1;
int killed = 0, close_this_client = 0;
@ -3174,6 +3179,18 @@ NULL
"client-id should be greater than 0") != C_OK)
return;
id = tmp;
} else if (!strcasecmp(c->argv[i]->ptr,"maxage") && moreargs) {
long long tmp;
if (getLongLongFromObjectOrReply(c, c->argv[i+1], &tmp,
"maxage is not an integer or out of range") != C_OK)
return;
if (tmp <= 0) {
addReplyError(c, "maxage should be greater than 0");
return;
}
max_age = tmp;
} else if (!strcasecmp(c->argv[i]->ptr,"type") && moreargs) {
type = getClientTypeByName(c->argv[i+1]->ptr);
if (type == -1) {
@ -3223,6 +3240,7 @@ NULL
if (id != 0 && client->id != id) continue;
if (user && client->user != user) continue;
if (c == client && skipme) continue;
if (max_age != 0 && (long long)(commandTimeSnapshot() / 1000 - client->ctime) < max_age) continue;
/* Kill it. */
if (c == client) {

View File

@ -32,8 +32,35 @@ start_server {tags {"introspection"}} {
assert_error "ERR No such user*" {r client kill user wrong_user}
assert_error "ERR syntax error*" {r client kill skipme yes_or_no}
assert_error "ERR *not an integer or out of range*" {r client kill maxage str}
assert_error "ERR *not an integer or out of range*" {r client kill maxage 9999999999999999999}
assert_error "ERR *greater than 0*" {r client kill maxage -1}
}
test {CLIENT KILL maxAGE will kill old clients} {
set rd1 [redis_deferring_client]
r debug sleep 2
set rd2 [redis_deferring_client]
r acl setuser dummy on nopass +ping
$rd1 auth dummy ""
$rd1 read
$rd2 auth dummy ""
$rd2 read
# Should kill rd1 but not rd2
set res [r client kill user dummy maxage 1]
assert {$res == 1}
# rd2 should still be connected
$rd2 ping
assert_equal "PONG" [$rd2 read]
$rd1 close
$rd2 close
} {0} {"needs:debug"}
test {CLIENT KILL SKIPME YES/NO will kill all clients} {
# Kill all clients except `me`
set rd1 [redis_deferring_client]