Modules: Replicate lazy-expire even if replication is not allowed (#8816)

Before this commit using RM_Call without "!" could cause the master
to lazy-expire a key (delete it) but without replicating to replicas.
This could cause the replica's memory usage to gradually grow and
could also cause consistency issues if the master and replica have
a clock diff.
This bug was introduced in #8617

Added a test which demonstrates that scenario.
This commit is contained in:
guybe7 2021-04-19 16:16:02 +02:00 committed by GitHub
parent 7a3d1487e4
commit f40ca9cb58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 49 additions and 1 deletions

View File

@ -1453,7 +1453,12 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
incrRefCount(argv[0]);
incrRefCount(argv[1]);
/* If the master decided to expire a key we must propagate it to replicas no matter what..
* Even if module executed a command without asking for propagation. */
int prev_replication_allowed = server.replication_allowed;
server.replication_allowed = 1;
propagate(server.delCommand,db->id,argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
server.replication_allowed = prev_replication_allowed;
decrRefCount(argv[0]);
decrRefCount(argv[1]);

View File

@ -192,6 +192,19 @@ int propagateTestNestedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, in
return REDISMODULE_OK;
}
int propagateTestIncr(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argc);
RedisModuleCallReply *reply;
/* This test propagates the module command, not the INCR it executes. */
reply = RedisModule_Call(ctx, "INCR", "s", argv[1]);
RedisModule_ReplyWithCallReply(ctx,reply);
RedisModule_FreeCallReply(reply);
RedisModule_ReplicateVerbatim(ctx);
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
@ -234,5 +247,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx,"propagate-test.incr",
propagateTestIncr,
"",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}

View File

@ -2,7 +2,7 @@ set testmodule [file normalize tests/modules/propagate.so]
tags "modules" {
test {Modules can propagate in async and threaded contexts} {
start_server {} {
start_server [list overrides [list loadmodule "$testmodule"]] {
set replica [srv 0 client]
set replica_host [srv 0 host]
set replica_port [srv 0 port]
@ -213,6 +213,31 @@ tags "modules" {
}
close_replication_stream $repl
}
test {module RM_Call of expired key propagation} {
$master debug set-active-expire 0
$master set k1 900 px 100
wait_for_ofs_sync $master $replica
after 110
set repl [attach_to_replication_stream]
$master propagate-test.incr k1
wait_for_ofs_sync $master $replica
assert_replication_stream $repl {
{select *}
{del k1}
{propagate-test.incr k1}
}
close_replication_stream $repl
assert_equal [$master get k1] 1
assert_equal [$master ttl k1] -1
assert_equal [$replica get k1] 1
assert_equal [$replica ttl k1] -1
}
assert_equal [s -1 unexpected_error_replies] 0
}
}