summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorguybe7 <guy.benoish@redislabs.com>2021-04-19 16:16:02 +0200
committerGitHub <noreply@github.com>2021-04-19 17:16:02 +0300
commitf40ca9cb58049683b563245006892001a5ebfacd (patch)
tree7811abbd3add6aad3b30baceed6b47551fde1a16
parent7a3d1487e443c3cb5fde3605aebea20698a940b4 (diff)
downloadredis-f40ca9cb58049683b563245006892001a5ebfacd.tar.gz
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.
-rw-r--r--src/db.c5
-rw-r--r--tests/modules/propagate.c18
-rw-r--r--tests/unit/moduleapi/propagate.tcl27
3 files changed, 49 insertions, 1 deletions
diff --git a/src/db.c b/src/db.c
index 27af0a53e..ec68c228c 100644
--- a/src/db.c
+++ b/src/db.c
@@ -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]);
diff --git a/tests/modules/propagate.c b/tests/modules/propagate.c
index 7a63ed807..ac04d4f9d 100644
--- a/tests/modules/propagate.c
+++ b/tests/modules/propagate.c
@@ -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;
}
diff --git a/tests/unit/moduleapi/propagate.tcl b/tests/unit/moduleapi/propagate.tcl
index 108637dd9..a8c710074 100644
--- a/tests/unit/moduleapi/propagate.tcl
+++ b/tests/unit/moduleapi/propagate.tcl
@@ -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
}
}