summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOran Agra <oran@redislabs.com>2023-01-12 08:29:20 +0200
committerOran Agra <oran@redislabs.com>2023-01-16 18:40:35 +0200
commit4537830ea1459a0890744380a6ebb6082de24ea1 (patch)
treeb2bc207ab78f2b7879ae893fe79f9db29cffa6a2
parent5fa7d9a272574c43dd348442a16808462c3b5718 (diff)
downloadredis-4537830ea1459a0890744380a6ebb6082de24ea1.tar.gz
Obuf limit, exit during loop in *RAND* commands and KEYS
Related to the hang reported in #11671 Currently, redis can disconnect a client due to reaching output buffer limit, it'll also avoid feeding that output buffer with more data, but it will keep running the loop in the command (despite the client already being marked for disconnection) This PR is an attempt to mitigate the problem, specifically for commands that are easy to abuse, specifically: KEYS, HRANDFIELD, SRANDMEMBER, ZRANDMEMBER. The RAND family of commands can take a negative COUNT argument (which is not bound to the number of elements in the key), so it's enough to create a key with one field, and then these commands can be used to hang redis. For KEYS the caller can use the existing keyspace in redis (if big enough).
-rw-r--r--src/db.c2
-rw-r--r--src/t_hash.c4
-rw-r--r--src/t_set.c2
-rw-r--r--src/t_zset.c4
-rw-r--r--tests/unit/obuf-limits.tcl16
5 files changed, 28 insertions, 0 deletions
diff --git a/src/db.c b/src/db.c
index 188052eb1..4d2288196 100644
--- a/src/db.c
+++ b/src/db.c
@@ -757,6 +757,8 @@ void keysCommand(client *c) {
}
decrRefCount(keyobj);
}
+ if (c->flags & CLIENT_CLOSE_ASAP)
+ break;
}
dictReleaseIterator(di);
setDeferredArrayLen(c,replylen,numkeys);
diff --git a/src/t_hash.c b/src/t_hash.c
index 7aec270aa..405a55695 100644
--- a/src/t_hash.c
+++ b/src/t_hash.c
@@ -956,6 +956,8 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
addReplyBulkCBuffer(c, key, sdslen(key));
if (withvalues)
addReplyBulkCBuffer(c, value, sdslen(value));
+ if (c->flags & CLIENT_CLOSE_ASAP)
+ break;
}
} else if (hash->encoding == OBJ_ENCODING_LISTPACK) {
listpackEntry *keys, *vals = NULL;
@@ -970,6 +972,8 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
count -= sample_count;
lpRandomPairs(hash->ptr, sample_count, keys, vals);
hrandfieldReplyWithListpack(c, sample_count, keys, vals);
+ if (c->flags & CLIENT_CLOSE_ASAP)
+ break;
}
zfree(keys);
zfree(vals);
diff --git a/src/t_set.c b/src/t_set.c
index 3fb9f5259..b01729f0a 100644
--- a/src/t_set.c
+++ b/src/t_set.c
@@ -699,6 +699,8 @@ void srandmemberWithCountCommand(client *c) {
} else {
addReplyBulkCBuffer(c,ele,sdslen(ele));
}
+ if (c->flags & CLIENT_CLOSE_ASAP)
+ break;
}
return;
}
diff --git a/src/t_zset.c b/src/t_zset.c
index 34f8fb74b..dc97c7075 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -4126,6 +4126,8 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
addReplyBulkCBuffer(c, key, sdslen(key));
if (withscores)
addReplyDouble(c, *(double*)dictGetVal(de));
+ if (c->flags & CLIENT_CLOSE_ASAP)
+ break;
}
} else if (zsetobj->encoding == OBJ_ENCODING_LISTPACK) {
listpackEntry *keys, *vals = NULL;
@@ -4139,6 +4141,8 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
count -= sample_count;
lpRandomPairs(zsetobj->ptr, sample_count, keys, vals);
zrandmemberReplyWithListpack(c, sample_count, keys, vals);
+ if (c->flags & CLIENT_CLOSE_ASAP)
+ break;
}
zfree(keys);
zfree(vals);
diff --git a/tests/unit/obuf-limits.tcl b/tests/unit/obuf-limits.tcl
index 38048935a..7eb6def58 100644
--- a/tests/unit/obuf-limits.tcl
+++ b/tests/unit/obuf-limits.tcl
@@ -211,4 +211,20 @@ start_server {tags {"obuf-limits external:skip"}} {
assert_equal "v2" [r get k2]
assert_equal "v3" [r get k3]
}
+
+ test "Obuf limit, HRANDFIELD with huge count stopped mid-run" {
+ r config set client-output-buffer-limit {normal 1000000 0 0}
+ r hset myhash a b
+ catch {r hrandfield myhash -999999999} e
+ assert_match "*I/O error*" $e
+ reconnect
+ }
+
+ test "Obuf limit, KEYS stopped mid-run" {
+ r config set client-output-buffer-limit {normal 100000 0 0}
+ populate 1000 "long-key-name-prefix-of-100-chars-------------------------------------------------------------------"
+ catch {r keys *} e
+ assert_match "*I/O error*" $e
+ reconnect
+ }
}