Unverified Commit eb6accad authored by Meir Shpilraien (Spielrein)'s avatar Meir Shpilraien (Spielrein) Committed by GitHub
Browse files

Fix crash on RM_Call inside module load (#11346)

PR #9320 introduces initialization order changes. Now cluster is initialized after modules.
This changes causes a crash if the module uses RM_Call inside the load function
on cluster mode (the code will try to access `server.cluster` which at this point is NULL).

To solve it, separate cluster initialization into 2 phases:
1. Structure initialization that happened before the modules initialization
2. Listener initialization that happened after.

Test was added to verify the fix.
parent a370bbe2
......@@ -694,11 +694,31 @@ void clusterInit(void) {
exit(1);
}
/* Initialize data for the Slot to key API. */
slotToKeyInit(server.db);
/* The slots -> channels map is a radix tree. Initialize it here. */
server.cluster->slots_to_channels = raxNew();
/* Set myself->port/cport/pport to my listening ports, we'll just need to
* discover the IP address via MEET messages. */
deriveAnnouncedPorts(&myself->port, &myself->pport, &myself->cport);
server.cluster->mf_end = 0;
server.cluster->mf_slave = NULL;
resetManualFailover();
clusterUpdateMyselfFlags();
clusterUpdateMyselfIp();
clusterUpdateMyselfHostname();
}
void clusterInitListeners(void) {
if (connectionIndexByType(connTypeOfCluster()->get_type(NULL)) < 0) {
serverLog(LL_WARNING, "Missing connection type %s, but it is required for the Cluster bus.", connTypeOfCluster()->get_type(NULL));
exit(1);
}
int port = server.tls_cluster ? server.tls_port : server.port;
connListener *listener = &server.clistener;
listener->count = 0;
listener->bindaddr = server.bindaddr;
......@@ -714,23 +734,6 @@ void clusterInit(void) {
if (createSocketAcceptHandler(&server.clistener, clusterAcceptHandler) != C_OK) {
serverPanic("Unrecoverable error creating Redis Cluster socket accept handler.");
}
/* Initialize data for the Slot to key API. */
slotToKeyInit(server.db);
/* The slots -> channels map is a radix tree. Initialize it here. */
server.cluster->slots_to_channels = raxNew();
/* Set myself->port/cport/pport to my listening ports, we'll just need to
* discover the IP address via MEET messages. */
deriveAnnouncedPorts(&myself->port, &myself->pport, &myself->cport);
server.cluster->mf_end = 0;
server.cluster->mf_slave = NULL;
resetManualFailover();
clusterUpdateMyselfFlags();
clusterUpdateMyselfIp();
clusterUpdateMyselfHostname();
}
/* Reset a node performing a soft or hard reset:
......
......@@ -383,6 +383,7 @@ static_assert(offsetof(clusterMsg, data) == 2256, "unexpected field offset");
/* ---------------------- API exported outside cluster.c -------------------- */
void clusterInit(void);
void clusterInitListeners(void);
void clusterCron(void);
void clusterBeforeSleep(void);
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *ask);
......
......@@ -7053,6 +7053,9 @@ int main(int argc, char **argv) {
if (server.set_proc_title) redisSetProcTitle(NULL);
redisAsciiArt();
checkTcpBacklogSettings();
if (server.cluster_enabled) {
clusterInit();
}
if (!server.sentinel_mode) {
moduleInitModulesSystemLast();
moduleLoadFromQueue();
......@@ -7060,7 +7063,7 @@ int main(int argc, char **argv) {
ACLLoadUsersAtStartup();
initListeners();
if (server.cluster_enabled) {
clusterInit();
clusterInitListeners();
}
InitServerLast();
......
......@@ -893,6 +893,28 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_Init(ctx,"test",1,REDISMODULE_APIVER_1)
== REDISMODULE_ERR) return REDISMODULE_ERR;
/* Perform RM_Call inside the RedisModule_OnLoad
* to verify that it works as expected without crashing.
* The tests will verify it on different configurations
* options (cluster/no cluster). A simple ping command
* is enough for this test. */
RedisModuleCallReply *reply = RedisModule_Call(ctx, "ping", "");
if (RedisModule_CallReplyType(reply) != REDISMODULE_REPLY_STRING) {
RedisModule_FreeCallReply(reply);
return REDISMODULE_ERR;
}
size_t len;
const char *reply_str = RedisModule_CallReplyStringPtr(reply, &len);
if (len != 4) {
RedisModule_FreeCallReply(reply);
return REDISMODULE_ERR;
}
if (memcmp(reply_str, "PONG", 4) != 0) {
RedisModule_FreeCallReply(reply);
return REDISMODULE_ERR;
}
RedisModule_FreeCallReply(reply);
if (RedisModule_CreateCommand(ctx,"test.call",
TestCall,"write deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
......
......@@ -38,4 +38,4 @@ start_server {tags {"modules external:skip"} overrides {enable-module-command no
test {module command disabled} {
assert_error "ERR *MODULE command not allowed*" {r module load $testmodule}
}
}
\ No newline at end of file
}
......@@ -163,3 +163,17 @@ start_cluster 3 0 [list config_lines $modules] {
$node2_rd close
}
}
set testmodule [file normalize tests/modules/basics.so]
set modules [list loadmodule $testmodule]
start_cluster 3 0 [list config_lines $modules] {
set node1 [srv 0 client]
set node2 [srv -1 client]
set node3 [srv -2 client]
test "Verify RM_Call inside module load function on cluster mode" {
assert_equal {PONG} [$node1 PING]
assert_equal {PONG} [$node2 PING]
assert_equal {PONG} [$node3 PING]
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment