From 23f03e7965649ca3bbdac8d5e9bd64f4e8c37b2c Mon Sep 17 00:00:00 2001 From: Shaya Potter Date: Mon, 7 Mar 2022 17:37:57 +0200 Subject: [PATCH] Modules: Add REDISMODULE_EVENT_CONFIG (#10311) Add a new REDISMODULE_EVENT_CONFIG event type for notifying modules when Redis configuration changes. --- src/config.c | 6 ++++++ src/module.c | 19 +++++++++++++++++++ src/redismodule.h | 23 +++++++++++++++++++++-- tests/modules/hooks.c | 15 +++++++++++++++ tests/unit/moduleapi/hooks.tcl | 6 +++++- 5 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/config.c b/src/config.c index fd720e50a..4684d8661 100644 --- a/src/config.c +++ b/src/config.c @@ -709,6 +709,7 @@ void configSetCommand(client *c) { const char *invalid_arg_name = NULL; const char *err_arg_name = NULL; standardConfig **set_configs; /* TODO: make this a dict for better performance */ + const char **config_names; sds *new_values; sds *old_values = NULL; apply_fn *apply_fns; /* TODO: make this a set for better performance */ @@ -724,6 +725,7 @@ void configSetCommand(client *c) { config_count = (c->argc - 2) / 2; set_configs = zcalloc(sizeof(standardConfig*)*config_count); + config_names = zcalloc(sizeof(char*)*config_count); new_values = zmalloc(sizeof(sds*)*config_count); old_values = zcalloc(sizeof(sds*)*config_count); apply_fns = zcalloc(sizeof(apply_fn)*config_count); @@ -779,6 +781,7 @@ void configSetCommand(client *c) { } } set_configs[i] = config; + config_names[i] = config->name; new_values[i] = c->argv[2+i*2+1]->ptr; } @@ -824,6 +827,8 @@ void configSetCommand(client *c) { goto err; } } + RedisModuleConfigChangeV1 cc = {.num_changes = config_count, .config_names = config_names}; + moduleFireServerEvent(REDISMODULE_EVENT_CONFIG, REDISMODULE_SUBEVENT_CONFIG_CHANGE, &cc); addReply(c,shared.ok); goto end; @@ -840,6 +845,7 @@ err: } end: zfree(set_configs); + zfree(config_names); zfree(new_values); for (i = 0; i < config_count; i++) sdsfree(old_values[i]); diff --git a/src/module.c b/src/module.c index 2946f6229..d0b1d7ce5 100644 --- a/src/module.c +++ b/src/module.c @@ -9976,6 +9976,7 @@ static uint64_t moduleEventVersions[] = { -1, /* REDISMODULE_EVENT_FORK_CHILD */ -1, /* REDISMODULE_EVENT_REPL_ASYNC_LOAD */ -1, /* REDISMODULE_EVENT_EVENTLOOP */ + -1, /* REDISMODULE_EVENT_CONFIG */ }; /* Register to be notified, via a callback, when the specified server event @@ -10236,6 +10237,20 @@ static uint64_t moduleEventVersions[] = { * * `REDISMODULE_SUBEVENT_EVENTLOOP_BEFORE_SLEEP` * * `REDISMODULE_SUBEVENT_EVENTLOOP_AFTER_SLEEP` * + * * RedisModule_Event_Config + * + * Called when a configuration event happens + * The following sub events are available: + * + * * `REDISMODULE_SUBEVENT_CONFIG_CHANGE` + * + * The data pointer can be casted to a RedisModuleConfigChange + * structure with the following fields: + * + * const char **config_names; // An array of C string pointers containing the + * // name of each modified configuration item + * uint32_t num_changes; // The number of elements in the config_names array + * * The function returns REDISMODULE_OK if the module was successfully subscribed * for the specified event. If the API is called from a wrong context or unsupported event * is given then REDISMODULE_ERR is returned. */ @@ -10313,6 +10328,8 @@ int RM_IsSubEventSupported(RedisModuleEvent event, int64_t subevent) { return subevent < _REDISMODULE_SUBEVENT_FORK_CHILD_NEXT; case REDISMODULE_EVENT_EVENTLOOP: return subevent < _REDISMODULE_SUBEVENT_EVENTLOOP_NEXT; + case REDISMODULE_EVENT_CONFIG: + return subevent < _REDISMODULE_SUBEVENT_CONFIG_NEXT; default: break; } @@ -10389,6 +10406,8 @@ void moduleFireServerEvent(uint64_t eid, int subid, void *data) { moduledata = data; } else if (eid == REDISMODULE_EVENT_SWAPDB) { moduledata = data; + } else if (eid == REDISMODULE_EVENT_CONFIG) { + moduledata = data; } el->module->in_hook++; diff --git a/src/redismodule.h b/src/redismodule.h index 79ce2c697..1f16708c0 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -429,7 +429,8 @@ typedef void (*RedisModuleEventLoopOneShotFunc)(void *user_data); #define REDISMODULE_EVENT_FORK_CHILD 13 #define REDISMODULE_EVENT_REPL_ASYNC_LOAD 14 #define REDISMODULE_EVENT_EVENTLOOP 15 -#define _REDISMODULE_EVENT_NEXT 16 /* Next event flag, should be updated if a new event added. */ +#define REDISMODULE_EVENT_CONFIG 16 +#define _REDISMODULE_EVENT_NEXT 17 /* Next event flag, should be updated if a new event added. */ typedef struct RedisModuleEvent { uint64_t id; /* REDISMODULE_EVENT_... defines. */ @@ -532,7 +533,11 @@ static const RedisModuleEvent RedisModuleEvent_EventLoop = { REDISMODULE_EVENT_EVENTLOOP, 1 -}; + }, + RedisModuleEvent_Config = { + REDISMODULE_EVENT_CONFIG, + 1 + }; /* Those are values that are used for the 'subevent' callback argument. */ #define REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START 0 @@ -574,6 +579,9 @@ static const RedisModuleEvent #define REDISMODULE_SUBEVENT_MODULE_UNLOADED 1 #define _REDISMODULE_SUBEVENT_MODULE_NEXT 2 +#define REDISMODULE_SUBEVENT_CONFIG_CHANGE 0 +#define _REDISMODULE_SUBEVENT_CONFIG_NEXT 1 + #define REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB 0 #define REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF 1 #define _REDISMODULE_SUBEVENT_LOADING_PROGRESS_NEXT 2 @@ -674,6 +682,17 @@ typedef struct RedisModuleModuleChange { #define RedisModuleModuleChange RedisModuleModuleChangeV1 +#define REDISMODULE_CONFIGCHANGE_VERSION 1 +typedef struct RedisModuleConfigChange { + uint64_t version; /* Not used since this structure is never passed + from the module to the core right now. Here + for future compatibility. */ + uint32_t num_changes; /* how many redis config options were changed */ + const char **config_names; /* the config names that were changed */ +} RedisModuleConfigChangeV1; + +#define RedisModuleConfigChange RedisModuleConfigChangeV1 + #define REDISMODULE_CRON_LOOP_VERSION 1 typedef struct RedisModuleCronLoopInfo { uint64_t version; /* Not used since this structure is never passed diff --git a/tests/modules/hooks.c b/tests/modules/hooks.c index af4681cf9..94d902d22 100644 --- a/tests/modules/hooks.c +++ b/tests/modules/hooks.c @@ -267,6 +267,18 @@ void swapDbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void LogNumericEvent(ctx, "swapdb-second", ei->dbnum_second); } +void configChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data) +{ + REDISMODULE_NOT_USED(e); + if (sub != REDISMODULE_SUBEVENT_CONFIG_CHANGE) { + return; + } + + RedisModuleConfigChangeV1 *ei = data; + LogNumericEvent(ctx, "config-change-count", ei->num_changes); + LogStringEvent(ctx, "config-change-first", ei->config_names[0]); +} + /* This function must be present on each Redis module. It is used in order to * register the commands into the Redis server. */ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { @@ -317,6 +329,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_SwapDB, swapDbCallback); + RedisModule_SubscribeToServerEvent(ctx, + RedisModuleEvent_Config, configChangeCallback); + event_log = RedisModule_CreateDict(ctx); if (RedisModule_CreateCommand(ctx,"hooks.event_count", cmdEventCount,"",0,0,0) == REDISMODULE_ERR) diff --git a/tests/unit/moduleapi/hooks.tcl b/tests/unit/moduleapi/hooks.tcl index cb36c9f71..814f31bc0 100644 --- a/tests/unit/moduleapi/hooks.tcl +++ b/tests/unit/moduleapi/hooks.tcl @@ -150,13 +150,17 @@ tags "modules" { r swapdb 0 10 assert_equal [r hooks.event_last swapdb-first] 0 assert_equal [r hooks.event_last swapdb-second] 10 + } + test {Test configchange hooks} { + r config set rdbcompression no + assert_equal [r hooks.event_last config-change-count] 1 + assert_equal [r hooks.event_last config-change-first] rdbcompression } # look into the log file of the server that just exited test {Test shutdown hook} { assert_equal [string match {*module-event-shutdown*} [exec tail -5 < $replica_stdout]] 1 } - } }