Fix CLUSTER SLOTS crash when called from module timer callback (#2915)

The CLUSTER SLOTS reply depends on whether the client is connected over
IPv6, but for a fake client there is no connection and when this command
is called from a module timer callback or other scenario where no real
client is involved, there is no connection to check IPv6 support on.
This fix handles the missing case by returning the reply for IPv4
connected clients.

Fixes #2912.

---------

Signed-off-by: Su Ko <rhtn1128@gmail.com>
Signed-off-by: Binbin <binloveplay1314@qq.com>
Signed-off-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: Su Ko <rhtn1128@gmail.com>
Co-authored-by: KarthikSubbarao <karthikrs2021@gmail.com>
Co-authored-by: Binbin <binloveplay1314@qq.com>
Co-authored-by: Viktor Söderqvist <viktor.soderqvist@est.tech>
This commit is contained in:
bandalgomsu 2025-12-09 22:02:35 +09:00 committed by GitHub
parent 48e0cbbb41
commit 2a9a1731ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 54 additions and 3 deletions

View File

@ -4194,11 +4194,17 @@ int isClientConnIpV6(client *c) {
/* The cached client peer id is on the form "[IPv6]:port" for IPv6 /* The cached client peer id is on the form "[IPv6]:port" for IPv6
* addresses, so we just check for '[' here. */ * addresses, so we just check for '[' here. */
if (c->flag.fake && server.current_client) { if (c->flag.fake && server.current_client) {
/* Fake client? Use current client instead. /* Fake client? Use current client instead, if we have one. */
* Noted that in here we are assuming server.current_client is set
* and real (aof has already violated this in loadSingleAppendOnlyFil). */
c = server.current_client; c = server.current_client;
} }
if (c->flag.fake || !c->conn) {
/* If we still don't have a client with a real connection (e.g., called
* from module timer with no real current client), default to IPv4 to
* avoid crashing. */
return 0;
}
return getClientPeerId(c)[0] == '['; return getClientPeerId(c)[0] == '[';
} }

View File

@ -3,6 +3,35 @@
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
void cluster_timer_handler(ValkeyModuleCtx *ctx, void *data) {
VALKEYMODULE_NOT_USED(data);
ValkeyModuleCallReply *rep = ValkeyModule_Call(ctx, "CLUSTER", "c", "SLOTS");
if (rep) {
if (ValkeyModule_CallReplyType(rep) == VALKEYMODULE_REPLY_ARRAY) {
ValkeyModule_Log(ctx, "notice", "Timer: CLUSTER SLOTS success");
} else {
ValkeyModule_Log(ctx, "notice",
"Timer: CLUSTER SLOTS unexpected reply type %d",
ValkeyModule_CallReplyType(rep));
}
ValkeyModule_FreeCallReply(rep);
} else {
ValkeyModule_Log(ctx, "warning", "Timer: CLUSTER SLOTS failed");
}
}
int test_start_cluster_timer(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) {
VALKEYMODULE_NOT_USED(argv);
VALKEYMODULE_NOT_USED(argc);
ValkeyModule_CreateTimer(ctx, 1, cluster_timer_handler, NULL);
return ValkeyModule_ReplyWithSimpleString(ctx, "OK");
}
int test_cluster_slots(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) { int test_cluster_slots(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) {
UNUSED(argv); UNUSED(argv);
@ -77,8 +106,12 @@ int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int arg
if (ValkeyModule_CreateCommand(ctx, "test.cluster_shards", test_cluster_shards, "", 0, 0, 0) == VALKEYMODULE_ERR) if (ValkeyModule_CreateCommand(ctx, "test.cluster_shards", test_cluster_shards, "", 0, 0, 0) == VALKEYMODULE_ERR)
return VALKEYMODULE_ERR; return VALKEYMODULE_ERR;
if (ValkeyModule_CreateCommand(ctx, "test.start_cluster_timer", test_start_cluster_timer, "", 0, 0, 0) == VALKEYMODULE_ERR)
return VALKEYMODULE_ERR;
/* Register our handlers for different message types. */ /* Register our handlers for different message types. */
ValkeyModule_RegisterClusterMessageReceiver(ctx, MSGTYPE_DING, DingReceiver); ValkeyModule_RegisterClusterMessageReceiver(ctx, MSGTYPE_DING, DingReceiver);
ValkeyModule_RegisterClusterMessageReceiver(ctx, MSGTYPE_DONG, DongReceiver); ValkeyModule_RegisterClusterMessageReceiver(ctx, MSGTYPE_DONG, DongReceiver);
return VALKEYMODULE_OK; return VALKEYMODULE_OK;
} }

View File

@ -285,6 +285,18 @@ start_cluster 3 0 [list config_lines $modules] {
assert_equal [lsort [$node2 cluster shards]] [lsort [$node2 test.cluster_shards]] assert_equal [lsort [$node2 cluster shards]] [lsort [$node2 test.cluster_shards]]
assert_equal [lsort [$node3 cluster shards]] [lsort [$node3 test.cluster_shards]] assert_equal [lsort [$node3 cluster shards]] [lsort [$node3 test.cluster_shards]]
} }
test "VM_CALL CLUSTER SLOTS from Module Timer" {
assert_equal {OK} [$node1 test.start_cluster_timer]
assert_equal {OK} [$node2 test.start_cluster_timer]
assert_equal {OK} [$node3 test.start_cluster_timer]
wait_for_condition 50 100 {
[count_log_message 0 "* <cluster> Timer: CLUSTER SLOTS success*"] >= 1
} else {
fail "Timer did not execute CLUSTER SLOTS or server crashed"
}
}
} }
} ;# end tag } ;# end tag