Add RESET command. (#7982)

Perform full reset of all client connection states, is if the client was
disconnected and re-connected. This affects:

* MULTI state
* Watched keys
* MONITOR mode
* Pub/Sub subscription
* ACL/Authenticated state
* Client tracking state
* Cluster read-only/asking state
* RESP version (reset to 2)
* Selected database
* CLIENT REPLY state

The response is +RESET to make it easily distinguishable from other
responses.

Co-authored-by: Oran Agra <oran@redislabs.com>
Co-authored-by: Itamar Haber <itamar@redislabs.com>
This commit is contained in:
Yossi Gottlieb 2020-11-05 10:51:26 +02:00 committed by GitHub
parent f6546eff45
commit 1fd456f91a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 119 additions and 10 deletions

View File

@ -97,6 +97,16 @@ void linkClient(client *c) {
raxInsert(server.clients_index,(unsigned char*)&id,sizeof(id),c,NULL);
}
/* Initialize client authentication state.
*/
static void clientSetDefaultAuth(client *c) {
/* If the default user does not require authentication, the user is
* directly authenticated. */
c->user = DefaultUser;
c->authenticated = (c->user->flags & USER_FLAG_NOPASS) &&
!(c->user->flags & USER_FLAG_DISABLED);
}
client *createClient(connection *conn) {
client *c = zmalloc(sizeof(client));
@ -130,16 +140,12 @@ client *createClient(connection *conn) {
c->argv = NULL;
c->argv_len_sum = 0;
c->cmd = c->lastcmd = NULL;
c->user = DefaultUser;
c->multibulklen = 0;
c->bulklen = -1;
c->sentlen = 0;
c->flags = 0;
c->ctime = c->lastinteraction = server.unixtime;
/* If the default user does not require authentication, the user is
* directly authenticated. */
c->authenticated = (c->user->flags & USER_FLAG_NOPASS) &&
!(c->user->flags & USER_FLAG_DISABLED);
clientSetDefaultAuth(c);
c->replstate = REPL_STATE_NONE;
c->repl_put_online_on_ack = 0;
c->reploff = 0;
@ -2257,6 +2263,50 @@ int clientSetNameOrReply(client *c, robj *name) {
return C_OK;
}
/* Reset the client state to resemble a newly connected client.
*/
void resetCommand(client *c) {
listNode *ln;
/* MONITOR clients are also marked with CLIENT_SLAVE, we need to
* distinguish between the two.
*/
if (c->flags & CLIENT_MONITOR) {
ln = listSearchKey(server.monitors,c);
serverAssert(ln != NULL);
listDelNode(server.monitors,ln);
c->flags &= ~(CLIENT_MONITOR|CLIENT_SLAVE);
}
if (c->flags & (CLIENT_SLAVE|CLIENT_MASTER|CLIENT_MODULE)) {
addReplyError(c,"can only reset normal client connections");
return;
}
if (c->flags & CLIENT_TRACKING) disableTracking(c);
selectDb(c,0);
c->resp = 2;
clientSetDefaultAuth(c);
moduleNotifyUserChanged(c);
discardTransaction(c);
pubsubUnsubscribeAllChannels(c,0);
pubsubUnsubscribeAllPatterns(c,0);
if (c->name) {
decrRefCount(c->name);
c->name = NULL;
}
/* Selectively clear state flags not covered above */
c->flags &= ~(CLIENT_ASKING|CLIENT_READONLY|CLIENT_PUBSUB|
CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP_NEXT);
addReplyStatus(c,"RESET");
}
void clientCommand(client *c) {
listNode *ln;
listIter li;

View File

@ -1036,7 +1036,11 @@ struct redisCommand redisCommandTable[] = {
{"stralgo",stralgoCommand,-2,
"read-only @string",
0,lcsGetKeys,0,0,0,0,0,0}
0,lcsGetKeys,0,0,0,0,0,0},
{"reset",resetCommand,-1,
"no-script ok-stale ok-loading fast @connection",
0,NULL,0,0,0,0,0,0}
};
/*============================ Utility functions ============================ */
@ -3740,7 +3744,8 @@ int processCommand(client *c) {
* set. */
if (c->flags & CLIENT_MULTI &&
c->cmd->proc != execCommand &&
c->cmd->proc != discardCommand) {
c->cmd->proc != discardCommand &&
c->cmd->proc != resetCommand) {
reject_cmd_on_oom = 1;
}
@ -3806,10 +3811,11 @@ int processCommand(client *c) {
c->cmd->proc != subscribeCommand &&
c->cmd->proc != unsubscribeCommand &&
c->cmd->proc != psubscribeCommand &&
c->cmd->proc != punsubscribeCommand) {
c->cmd->proc != punsubscribeCommand &&
c->cmd->proc != resetCommand) {
rejectCommandFormat(c,
"Can't execute '%s': only (P)SUBSCRIBE / "
"(P)UNSUBSCRIBE / PING / QUIT are allowed in this context",
"(P)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context",
c->cmd->name);
return C_OK;
}
@ -3860,7 +3866,8 @@ int processCommand(client *c) {
/* Exec the command */
if (c->flags & CLIENT_MULTI &&
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
c->cmd->proc != multiCommand && c->cmd->proc != watchCommand &&
c->cmd->proc != resetCommand)
{
queueMultiCommand(c);
addReply(c,shared.queued);

View File

@ -2493,6 +2493,7 @@ void xtrimCommand(client *c);
void lolwutCommand(client *c);
void aclCommand(client *c);
void stralgoCommand(client *c);
void resetCommand(client *c);
#if defined(__GNUC__)
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));

View File

@ -249,6 +249,57 @@ start_server {tags {"other"}} {
waitForBgsave r
r save
} {OK}
test {RESET clears client state} {
r client setname test-client
r client tracking on
assert_equal [r reset] "RESET"
set client [r client list]
assert_match {*name= *} $client
assert_match {*flags=N *} $client
}
test {RESET clears MONITOR state} {
set rd [redis_deferring_client]
$rd monitor
assert_equal [$rd read] "OK"
$rd reset
# skip reset ouptut
$rd read
assert_equal [$rd read] "RESET"
assert_no_match {*flags=O*} [r client list]
}
test {RESET clears and discards MULTI state} {
r multi
r set key-a a
r reset
catch {r exec} err
assert_match {*EXEC without MULTI*} $err
}
test {RESET clears Pub/Sub state} {
r subscribe channel-1
r reset
# confirm we're not subscribed by executing another command
r set key val
}
test {RESET clears authenticated state} {
r acl setuser user1 on >secret +@all
r auth user1 secret
assert_equal [r acl whoami] user1
r reset
assert_equal [r acl whoami] default
}
}
start_server {tags {"other"}} {