Repository: valkey-io/valkey (C)

Issue:
[NEW]Update the module args during runtime and save the new args value into the conf file
**The problem/use-case that the feature addresses**
Before Redis OSS 7, if we load a module with some args during runtime,
and run the command "config rewrite", the module information will not be saved into the 
conf file.

Since Redis OSS 7 and Valkey 7.2,  if we load a module with some args during runtime,
the module information (path, args number, and args value) can be saved into the conf file after config rewrite command is called.
Thus the module will be loaded automatically when the server startup next time.

Following is one example:

bind 172.25.0.58
port 7000
protected-mode no
enable-module-command yes

Generated by CONFIG REWRITE
latency-tracking-info-percentiles 50 99 99.9
dir "/home/ubuntu/valkey"
save 3600 1 300 100 60 10000
user default on nopass sanitize-payload ~* &* +@all
loadmodule tests/modules/datatype.so 10 20


However there is one problem. 
If developers write a module, and update the running args by someway, the updated args can not be saved into the conf file even "config rewrite" is called.
The reason comes from the following function  
rewriteConfigLoadmoduleOption     (src/config.c)

void rewriteConfigLoadmoduleOption(struct rewriteConfigState *state) {
    ..........    
    struct ValkeyModule *module = dictGetVal(de);
    line = sdsnew("loadmodule ");
    line = sdscatsds(line, module->loadmod->path);
    for (int i = 0; i < module->loadmod->argc; i++) {
        line = sdscatlen(line, " ", 1);
        line = sdscatsds(line, module->loadmod->argv[i]->ptr);
    }
    rewriteConfigRewriteLine(state, "loadmodule", line, 1);
    .......
}

The function only save the initial args information (module->loadmod) into the conf file.


**Description of the feature**

There are 2 ways to solve the problem: 

Solution 1:   It needs to take 2 steps.   (This option is not what we choose)

step 1: client calls command:  MODULE SET-ARGUMENT <module_name> [args...] to update the args (module->loadmod) during runtime
step 2: client gets the updated args (module->loadmod) in the module through the new API  ValkeyModule_GetRunTimeArgs(ctx);
Reference:   https://github.com/valkey-io/valkey/pull/1041


Solution 2:  it only takes 1 step

Call int VM_UpdateRunTimeArgs(ValkeyModuleCtx *ctx, int index, char *value) API in the module and update the specific args according to the index (module->loadmod).
Reference:   https://github.com/hwware/valkey/pull/8



Thus, I think the best way is to add a Module API to implement the above 2 features.

**Alternatives you've considered**

Administrators or developer could update the module args by the following way:

MODULE UNLOAD 
Update module args in the conf file
MODULE LOAD 

But I think there are following disadvantages:

1.  some modules can not be unloaded. Such as the example module datatype.so, which is tests/modules/datatype.so
2.  it is not atomic operation for MODULE UNLOAD + MODULE LOAD
3.  sometimes, if we just run the module unload, the client business could be interrupted



**Additional information**

Any additional information that is relevant to the feature request.




Fix (patch):
diff --git a/src/module.c b/src/module.c
index 4092ae6b06..5f9dff0402 100644
--- a/src/module.c
+++ b/src/module.c
@@ -2255,6 +2255,27 @@ int moduleIsModuleCommand(void *module_handle, struct serverCommand *cmd) {
     return (cp->module == module_handle);
 }
 
+/* ValkeyModule_UpdateRuntimeArgs can be used to update the module argument values.
+ * The function parameter 'argc' indicates the number of updated arguments, and 'argv'
+ * represents the values of the updated arguments.
+ * Once 'CONFIG REWRITE' command is called, the updated argument values can be saved into conf file.
+ *
+ * The function always returns VALKEYMODULE_OK. */
+int VM_UpdateRuntimeArgs(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) {
+    struct moduleLoadQueueEntry *loadmod = ctx->module->loadmod;
+    for (int i = 0; i < loadmod->argc; i++) {
+        decrRefCount(loadmod->argv[i]);
+    }
+    zfree(loadmod->argv);
+    loadmod->argv = argc - 1 ? zmalloc(sizeof(robj *) * (argc - 1)) : NULL;
+    loadmod->argc = argc - 1;
+    for (int i = 1; i < argc; i++) {
+        loadmod->argv[i - 1] = argv[i];
+        incrRefCount(loadmod->argv[i - 1]);
+    }
+    return VALKEYMODULE_OK;
+}
+
 /* --------------------------------------------------------------------------
  * ## Module information and time measurement
  * -------------------------------------------------------------------------- */
@@ -13560,6 +13581,7 @@ void moduleRegisterCoreAPI(void) {
     REGISTER_API(SetModuleAttribs);
     REGISTER_API(IsModuleNameBusy);
     REGISTER_API(WrongArity);
+    REGISTER_API(UpdateRuntimeArgs);
     REGISTER_API(ReplyWithLongLong);
     REGISTER_API(ReplyWithError);
     REGISTER_API(ReplyWithErrorFormat);
diff --git a/src/valkeymodule.h b/src/valkeymodule.h
index c2cdb2f0e7..7c3adfd477 100644
--- a/src/valkeymodule.h
+++ b/src/valkeymodule.h
@@ -967,6 +967,7 @@ VALKEYMODULE_API void (*ValkeyModule_SetModuleAttribs)(ValkeyModuleCtx *ctx, con
     VALKEYMODULE_ATTR;
 VALKEYMODULE_API int (*ValkeyModule_IsModuleNameBusy)(const char *name) VALKEYMODULE_ATTR;
 VALKEYMODULE_API int (*ValkeyModule_WrongArity)(ValkeyModuleCtx *ctx) VALKEYMODULE_ATTR;
+VALKEYMODULE_API int (*ValkeyModule_UpdateRuntimeArgs)(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) VALKEYMODULE_ATTR;
 VALKEYMODULE_API int (*ValkeyModule_ReplyWithLongLong)(ValkeyModuleCtx *ctx, long long ll) VALKEYMODULE_ATTR;
 VALKEYMODULE_API int (*ValkeyModule_GetSelectedDb)(ValkeyModuleCtx *ctx) VALKEYMODULE_ATTR;
 VALKEYMODULE_API int (*ValkeyModule_SelectDb)(ValkeyModuleCtx *ctx, int newid) VALKEYMODULE_ATTR;
@@ -1673,6 +1674,7 @@ static int ValkeyModule_Init(ValkeyModuleCtx *ctx, const char *name, int ver, in
     VALKEYMODULE_GET_API(SetModuleAttribs);
     VALKEYMODULE_GET_API(IsModuleNameBusy);
     VALKEYMODULE_GET_API(WrongArity);
+    VALKEYMODULE_GET_API(UpdateRuntimeArgs);
     VALKEYMODULE_GET_API(ReplyWithLongLong);
     VALKEYMODULE_GET_API(ReplyWithError);
     VALKEYMODULE_GET_API(ReplyWithErrorFormat);