Fix memory corruption in moduleHandleBlockedClients

By using a "circular BRPOPLPUSH"-like scenario it was
possible the get the same client on db->blocking_keys
twice (See comment in moduleTryServeClientBlockedOnKey)

The fix was actually already implememnted in
moduleTryServeClientBlockedOnKey but it had a bug:
the funxction should return 0 or 1 (not OK or ERR)

Other changes:
1. Added two commands to blockonkeys.c test module (To
   reproduce the case described above)
2. Simplify blockonkeys.c in order to make testing easier
3. cast raxSize() to avoid warning with format spec
This commit is contained in:
Guy Benoish
2020-01-21 15:09:42 +05:30
committed by antirez
parent 957e917a84
commit 193fc241ca
3 changed files with 149 additions and 46 deletions

View File

@ -4393,14 +4393,26 @@ RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdF
* can really be unblocked, since the module was able to serve the client.
* If the callback returns REDISMODULE_OK, then the client can be unblocked,
* otherwise the client remains blocked and we'll retry again when one of
* the keys it blocked for becomes "ready" again. */
* the keys it blocked for becomes "ready" again.
* This function returns 1 if client was served (and should be unblocked) */
int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
int served = 0;
RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
/* Protect against re-processing: don't serve clients that are already
* in the unblocking list for any reason (including RM_UnblockClient()
* explicit call). */
if (bc->unblocked) return REDISMODULE_ERR;
* explicit call).
* For example, the following pathological case:
* Assume a module called LIST implements the same command as
* the Redis list data type.
* LIST.BRPOPLPUSH src dst 0 ('src' goes into db->blocking_keys)
* LIST.BRPOPLPUSH dst src 0 ('dst' goes into db->blocking_keys)
* LIST.LPUSH src foo
* 'src' is in db->blocking_keys after the first BRPOPLPUSH is served
* (and stays there until the next beforeSleep).
* The second BRPOPLPUSH will signal 'src' as ready, leading to the
* unblocking of the already unblocked (and worst, freed) reply_client
* of the first BRPOPLPUSH. */
if (bc->unblocked) return 0;
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
ctx.flags |= REDISMODULE_CTX_BLOCKED_REPLY;
ctx.blocked_ready_key = key;