diff --git a/src/module.c b/src/module.c index ced079f2f..61c924ee6 100644 --- a/src/module.c +++ b/src/module.c @@ -12364,7 +12364,7 @@ int moduleLoad(const char *path, void **module_argv, int module_argc, int is_loa } if (post_load_err) { - moduleUnload(ctx.module->name, NULL); + serverAssert(moduleUnload(ctx.module->name, NULL, 1) == C_OK); moduleFreeContext(&ctx); return C_ERR; } @@ -12380,14 +12380,17 @@ int moduleLoad(const char *path, void **module_argv, int module_argc, int is_loa /* Unload the module registered with the specified name. On success * C_OK is returned, otherwise C_ERR is returned and errmsg is set - * with an appropriate message. */ -int moduleUnload(sds name, const char **errmsg) { + * with an appropriate message. + * Only forcefully unload this module, passing forced_unload != 0, + * if it is certain that it has not yet been in use (e.g., immediate + * unload on failed load). */ +int moduleUnload(sds name, const char **errmsg, int forced_unload) { struct RedisModule *module = dictFetchValue(modules,name); if (module == NULL) { *errmsg = "no such module with that name"; return C_ERR; - } else if (listLength(module->types)) { + } else if (listLength(module->types) && !forced_unload) { *errmsg = "the module exports one or more module-side data " "types, can't unload"; return C_ERR; @@ -13170,7 +13173,7 @@ NULL } else if (!strcasecmp(subcmd,"unload") && c->argc == 3) { const char *errmsg = NULL; - if (moduleUnload(c->argv[2]->ptr, &errmsg) == C_OK) + if (moduleUnload(c->argv[2]->ptr, &errmsg, 0) == C_OK) addReply(c,shared.ok); else { if (errmsg == NULL) errmsg = "operation not possible."; diff --git a/src/server.h b/src/server.h index 459d5b974..ec68fa213 100644 --- a/src/server.h +++ b/src/server.h @@ -2510,7 +2510,7 @@ void moduleInitModulesSystem(void); void moduleInitModulesSystemLast(void); void modulesCron(void); int moduleLoad(const char *path, void **argv, int argc, int is_loadex); -int moduleUnload(sds name, const char **errmsg); +int moduleUnload(sds name, const char **errmsg, int forced_unload); void moduleLoadFromQueue(void); int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); int moduleGetCommandChannelsViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result); diff --git a/tests/modules/datatype.c b/tests/modules/datatype.c index 408d1a526..05cf2337c 100644 --- a/tests/modules/datatype.c +++ b/tests/modules/datatype.c @@ -312,3 +312,12 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) return REDISMODULE_OK; } + +int RedisModule_OnUnload(RedisModuleCtx *ctx) { + REDISMODULE_NOT_USED(ctx); + if (datatype) { + RedisModule_Free(datatype); + datatype = NULL; + } + return REDISMODULE_OK; +} diff --git a/tests/unit/moduleapi/datatype.tcl b/tests/unit/moduleapi/datatype.tcl index 951c060e7..5d1722caa 100644 --- a/tests/unit/moduleapi/datatype.tcl +++ b/tests/unit/moduleapi/datatype.tcl @@ -1,6 +1,11 @@ set testmodule [file normalize tests/modules/datatype.so] start_server {tags {"modules"}} { + test {DataType: test loadex with invalid config} { + catch { r module loadex $testmodule CONFIG invalid_config 1 } e + assert_match {*ERR Error loading the extension*} $e + } + r module load $testmodule test {DataType: Test module is sane, GET/SET work.} {