From 81d3e1d354e1f6cc37f880c862969f407aebebb6 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Fri, 15 Mar 2019 21:09:59 +0200 Subject: [PATCH 01/10] add CI --- .circleci/config.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..52ff69721 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,21 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/buildpack-deps + steps: + - checkout + - run: + name: dep + command: sudo apt-get install -y tcl + - run: + name: Build + command: make + - run: + name: Test + command: make test +workflows: + version: 2 + workflow: + jobs: + - build From 366fe793358940b9e8b92dd5e778c73891989906 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Fri, 15 Mar 2019 21:14:15 +0200 Subject: [PATCH 02/10] add pull app --- .github/pull.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/pull.yml diff --git a/.github/pull.yml b/.github/pull.yml new file mode 100644 index 000000000..4cc55b0f5 --- /dev/null +++ b/.github/pull.yml @@ -0,0 +1,5 @@ +version: "1" +rules: + - base: unstable + upstream: antirez:unstable + mergeMethod: merge From c9cdf67d91022cec714de34b0d0e2f60506c41dd Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Fri, 15 Mar 2019 21:30:09 +0200 Subject: [PATCH 03/10] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 52ff69721..445031a35 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ jobs: steps: - checkout - run: - name: dep + name: Install dependency command: sudo apt-get install -y tcl - run: name: Build From ebdcb1618fe37a06e551cb117f93f410c854678d Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Fri, 15 Mar 2019 21:35:24 +0200 Subject: [PATCH 04/10] Update pull.yml --- .github/pull.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/pull.yml b/.github/pull.yml index 4cc55b0f5..6ccf19ca9 100644 --- a/.github/pull.yml +++ b/.github/pull.yml @@ -3,3 +3,12 @@ rules: - base: unstable upstream: antirez:unstable mergeMethod: merge + - base: 5.0 + upstream: antirez:5.0 + mergeMethod: merge + - base: 4.0 + upstream: antirez:4.0 + mergeMethod: merge + - base: 3.2 + upstream: antirez:3.2 + mergeMethod: merge From 661b5097e90edc5fe2c4d08810c8190541139769 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Fri, 15 Mar 2019 22:22:06 +0200 Subject: [PATCH 05/10] Update config.yml --- .circleci/config.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 445031a35..fe2a6ea07 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,8 +14,3 @@ jobs: - run: name: Test command: make test -workflows: - version: 2 - workflow: - jobs: - - build From 82e1417eb7c3dd0256d6693a6f29d67fbc7f53a2 Mon Sep 17 00:00:00 2001 From: Guy Korland Date: Sun, 20 Oct 2019 10:04:25 +0300 Subject: [PATCH 06/10] add CI action --- .circleci/config.yml | 16 ---------------- .github/pull.yml | 14 -------------- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 30 deletions(-) delete mode 100644 .circleci/config.yml delete mode 100644 .github/pull.yml create mode 100644 .github/workflows/ci.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index fe2a6ea07..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,16 +0,0 @@ -version: 2 -jobs: - build: - docker: - - image: circleci/buildpack-deps - steps: - - checkout - - run: - name: Install dependency - command: sudo apt-get install -y tcl - - run: - name: Build - command: make - - run: - name: Test - command: make test diff --git a/.github/pull.yml b/.github/pull.yml deleted file mode 100644 index 6ccf19ca9..000000000 --- a/.github/pull.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: "1" -rules: - - base: unstable - upstream: antirez:unstable - mergeMethod: merge - - base: 5.0 - upstream: antirez:5.0 - mergeMethod: merge - - base: 4.0 - upstream: antirez:4.0 - mergeMethod: merge - - base: 3.2 - upstream: antirez:3.2 - mergeMethod: merge diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..847abcf02 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: [push, pull_request] + +jobs: + build-ubuntu: + strategy: + matrix: + platform: [ubuntu-latest, ubuntu-16.04] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v1 + - name: make + run: make + - name: test + run: | + sudo apt-get install tcl8.5 + make test + + build-macos-latest: + strategy: + matrix: + platform: [macos-latest, macOS-10.14] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v1 + - name: make + run: make From 6700a2dc668ebefe058d26c4f272a89c1e0c4db6 Mon Sep 17 00:00:00 2001 From: Oran Agra Date: Wed, 23 Oct 2019 12:08:47 +0300 Subject: [PATCH 07/10] Module API for explicit SignalModifiedKey instead of implicit one. This commit also fixes an uninitialized module struct member (that luckily never got released) --- src/module.c | 19 ++++++++++++++++++- src/redismodule.h | 6 ++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/module.c b/src/module.c index 971bf5c08..21bab5cdf 100644 --- a/src/module.c +++ b/src/module.c @@ -322,6 +322,13 @@ static struct RedisModuleForkInfo { #define REDISMODULE_ARGV_NO_AOF (1<<1) #define REDISMODULE_ARGV_NO_REPLICAS (1<<2) +/* Determine whether Redis should signalModifiedKey implicitly. + * In case 'ctx' has no 'module' member (and therefore no module->options), + * we assume default behavior, that is, Redis signals. + * (see RM_GetThreadSafeContext) */ +#define SHOULD_SIGNAL_MODIFIED_KEYS(ctx) \ + ctx->module? !(ctx->module->options & REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED) : 1 + /* Server events hooks data structures and defines: this modules API * allow modules to subscribe to certain events in Redis, such as * the start and end of an RDB or AOF save, the change of role in replication, @@ -822,6 +829,7 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api module->filters = listCreate(); module->in_call = 0; module->in_hook = 0; + module->options = 0; ctx->module = module; } @@ -852,6 +860,12 @@ void RM_SetModuleOptions(RedisModuleCtx *ctx, int options) { ctx->module->options = options; } +/* Signals that the key is modified from user's perspective (i.e. invalidate WATCH). */ +int RM_SignalModifiedKey(RedisModuleCtx *ctx, RedisModuleString *keyname) { + signalModifiedKey(ctx->client->db,keyname); + return REDISMODULE_OK; +} + /* -------------------------------------------------------------------------- * Automatic memory management for modules * -------------------------------------------------------------------------- */ @@ -1843,7 +1857,9 @@ void *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) { /* Close a key handle. */ void RM_CloseKey(RedisModuleKey *key) { if (key == NULL) return; - if (key->mode & REDISMODULE_WRITE) signalModifiedKey(key->db,key->key); + int signal = SHOULD_SIGNAL_MODIFIED_KEYS(key->ctx); + if ((key->mode & REDISMODULE_WRITE) && signal) + signalModifiedKey(key->db,key->key); /* TODO: if (key->iter) RM_KeyIteratorStop(kp); */ RM_ZsetRangeStop(key); decrRefCount(key->key); @@ -6449,6 +6465,7 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(ModuleTypeGetValue); REGISTER_API(IsIOError); REGISTER_API(SetModuleOptions); + REGISTER_API(SignalModifiedKey); REGISTER_API(SaveUnsigned); REGISTER_API(LoadUnsigned); REGISTER_API(SaveSigned); diff --git a/src/redismodule.h b/src/redismodule.h index 4b63a227c..9fe0f5eba 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -161,6 +161,10 @@ typedef uint64_t RedisModuleTimerID; /* Declare that the module can handle errors with RedisModule_SetModuleOptions. */ #define REDISMODULE_OPTIONS_HANDLE_IO_ERRORS (1<<0) +/* When set, Redis will not call RedisModule_SignalModifiedKey(), implicitly in + * RedisModule_CloseKey, and the module needs to do that when manually when keys + * are modified from the user's sperspective, to invalidate WATCH. */ +#define REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED (1<<1) /* Server events definitions. */ #define REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED 0 @@ -434,6 +438,7 @@ RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModule void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key); int REDISMODULE_API_FUNC(RedisModule_IsIOError)(RedisModuleIO *io); void REDISMODULE_API_FUNC(RedisModule_SetModuleOptions)(RedisModuleCtx *ctx, int options); +int REDISMODULE_API_FUNC(RedisModule_SignalModifiedKey)(RedisModuleCtx *ctx, RedisModuleString *keyname); void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value); uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io); void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value); @@ -629,6 +634,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(ModuleTypeGetValue); REDISMODULE_GET_API(IsIOError); REDISMODULE_GET_API(SetModuleOptions); + REDISMODULE_GET_API(SignalModifiedKey); REDISMODULE_GET_API(SaveUnsigned); REDISMODULE_GET_API(LoadUnsigned); REDISMODULE_GET_API(SaveSigned); From 5d01f01beb3857ad312bf9b706dd2b89f54a9d3c Mon Sep 17 00:00:00 2001 From: Guy Benoish Date: Tue, 15 Oct 2019 12:51:30 +0200 Subject: [PATCH 08/10] Modules: Allow notifying custom keyspace events Also, add an API for getting server.notify_keyspace_events Other (unrelated) changes: Add RM_GetKeynameFromModuleKey --- src/module.c | 22 ++++++++++++++++++++++ src/redismodule.h | 8 ++++++++ 2 files changed, 30 insertions(+) diff --git a/src/module.c b/src/module.c index 971bf5c08..55568aca8 100644 --- a/src/module.c +++ b/src/module.c @@ -3859,6 +3859,11 @@ const RedisModuleString *RM_GetKeyNameFromIO(RedisModuleIO *io) { return io->key; } +/* Returns a RedisModuleString with the name of the key from RedisModuleKey */ +const RedisModuleString *RM_GetKeyNameFromModuleKey(RedisModuleKey *key) { + return key ? key->key : NULL; +} + /* -------------------------------------------------------------------------- * Logging * -------------------------------------------------------------------------- */ @@ -4368,6 +4373,20 @@ int RM_SubscribeToKeyspaceEvents(RedisModuleCtx *ctx, int types, RedisModuleNoti return REDISMODULE_OK; } +/* Get the configured bitmap of notify-keyspace-events (Could be used + * for additional filtering in RedisModuleNotificationFunc) */ +int RM_GetNotifyKeyspaceEvents() { + return server.notify_keyspace_events; +} + +/* Expose notifyKeyspaceEvent to modules */ +int RM_NotifyKeyspaceEvent(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) { + if (!ctx || !ctx->client) + return REDISMODULE_ERR; + notifyKeyspaceEvent(type, (char *)event, key, ctx->client->db->id); + return REDISMODULE_OK; +} + /* Dispatcher for keyspace notifications to module subscriber functions. * This gets called only if at least one module requested to be notified on * keyspace notifications */ @@ -6471,6 +6490,7 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(StringCompare); REGISTER_API(GetContextFromIO); REGISTER_API(GetKeyNameFromIO); + REGISTER_API(GetKeyNameFromModuleKey); REGISTER_API(BlockClient); REGISTER_API(UnblockClient); REGISTER_API(IsBlockedReplyRequest); @@ -6485,6 +6505,8 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(DigestAddStringBuffer); REGISTER_API(DigestAddLongLong); REGISTER_API(DigestEndSequence); + REGISTER_API(NotifyKeyspaceEvent); + REGISTER_API(GetNotifyKeyspaceEvents); REGISTER_API(SubscribeToKeyspaceEvents); REGISTER_API(RegisterClusterMessageReceiver); REGISTER_API(SendClusterMessage); diff --git a/src/redismodule.h b/src/redismodule.h index 4b63a227c..434849c77 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -106,6 +106,11 @@ /* There is currently some background process active. */ #define REDISMODULE_CTX_FLAGS_ACTIVE_CHILD (1<<18) +/* Keyspace changes notification classes. Every class is associated with a + * character for configuration purposes. + * NOTE: These have to be in sync with NOTIFY_* in server.h */ +#define REDISMODULE_NOTIFY_KEYSPACE (1<<0) /* K */ +#define REDISMODULE_NOTIFY_KEYEVENT (1<<1) /* E */ #define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */ #define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */ #define REDISMODULE_NOTIFY_LIST (1<<4) /* l */ @@ -651,6 +656,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(StringCompare); REDISMODULE_GET_API(GetContextFromIO); REDISMODULE_GET_API(GetKeyNameFromIO); + REDISMODULE_GET_API(GetKeyNameFromModuleKey); REDISMODULE_GET_API(Milliseconds); REDISMODULE_GET_API(DigestAddStringBuffer); REDISMODULE_GET_API(DigestAddLongLong); @@ -703,6 +709,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(AbortBlock); REDISMODULE_GET_API(SetDisconnectCallback); REDISMODULE_GET_API(SubscribeToKeyspaceEvents); + REDISMODULE_GET_API(NotifyKeyspaceEvent); + REDISMODULE_GET_API(GetNotifyKeyspaceEvents); REDISMODULE_GET_API(BlockedClientDisconnected); REDISMODULE_GET_API(RegisterClusterMessageReceiver); REDISMODULE_GET_API(SendClusterMessage); From d7257888735480277cbbb6d470935b745a32800c Mon Sep 17 00:00:00 2001 From: Oran Agra Date: Thu, 24 Oct 2019 09:38:52 +0300 Subject: [PATCH 09/10] Module api tests for RM_Call Adding a test for coverage for RM_Call in a new "misc" unit to be used for various short simple tests also solves compilation warnings in redismodule.h and fork.c --- runtest-moduleapi | 10 ++++++- src/module.c | 5 +++- src/redismodule.h | 2 +- tests/modules/Makefile | 1 + tests/modules/fork.c | 6 +++- tests/modules/misc.c | 55 +++++++++++++++++++++++++++++++++++ tests/unit/moduleapi/misc.tcl | 19 ++++++++++++ 7 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 tests/modules/misc.c create mode 100644 tests/unit/moduleapi/misc.tcl diff --git a/runtest-moduleapi b/runtest-moduleapi index 444204919..9301002c9 100755 --- a/runtest-moduleapi +++ b/runtest-moduleapi @@ -13,4 +13,12 @@ then fi make -C tests/modules && \ -$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/fork --single unit/moduleapi/testrdb --single unit/moduleapi/infotest --single unit/moduleapi/propagate --single unit/moduleapi/hooks "${@}" +$TCLSH tests/test_helper.tcl \ +--single unit/moduleapi/commandfilter \ +--single unit/moduleapi/fork \ +--single unit/moduleapi/testrdb \ +--single unit/moduleapi/infotest \ +--single unit/moduleapi/propagate \ +--single unit/moduleapi/hooks \ +--single unit/moduleapi/misc \ +"${@}" diff --git a/src/module.c b/src/module.c index 971bf5c08..8236728b6 100644 --- a/src/module.c +++ b/src/module.c @@ -3067,7 +3067,10 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch /* We handle the above format error only when the client is setup so that * we can free it normally. */ - if (argv == NULL) goto cleanup; + if (argv == NULL) { + errno = EINVAL; + goto cleanup; + } /* Call command filters */ moduleCallCommandFilters(c); diff --git a/src/redismodule.h b/src/redismodule.h index 4b63a227c..1171f81b4 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -181,7 +181,7 @@ typedef struct RedisModuleEvent { struct RedisModuleCtx; typedef void (*RedisModuleEventCallback)(struct RedisModuleCtx *ctx, RedisModuleEvent eid, uint64_t subevent, void *data); -static RedisModuleEvent +static const RedisModuleEvent RedisModuleEvent_ReplicationRoleChanged = { REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED, 1 diff --git a/tests/modules/Makefile b/tests/modules/Makefile index f357faad2..71c0b5ef8 100644 --- a/tests/modules/Makefile +++ b/tests/modules/Makefile @@ -17,6 +17,7 @@ TEST_MODULES = \ fork.so \ infotest.so \ propagate.so \ + misc.so \ hooks.so .PHONY: all diff --git a/tests/modules/fork.c b/tests/modules/fork.c index 0804e4355..1a139ef1b 100644 --- a/tests/modules/fork.c +++ b/tests/modules/fork.c @@ -1,6 +1,10 @@ #define REDISMODULE_EXPERIMENTAL_API -#include "redismodule.h" +/* define macros for having usleep */ +#define _BSD_SOURCE +#define _DEFAULT_SOURCE + +#include "redismodule.h" #include #include #include diff --git a/tests/modules/misc.c b/tests/modules/misc.c new file mode 100644 index 000000000..fd892f52c --- /dev/null +++ b/tests/modules/misc.c @@ -0,0 +1,55 @@ +#define REDISMODULE_EXPERIMENTAL_API +#include "redismodule.h" + +#include +#include +#include +#include + +int test_call_generic(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) +{ + if (argc<2) { + RedisModule_WrongArity(ctx); + return REDISMODULE_OK; + } + + const char* cmdname = RedisModule_StringPtrLen(argv[1], NULL); + RedisModuleCallReply *reply = RedisModule_Call(ctx, cmdname, "v", argv+2, argc-2); + if (reply) { + RedisModule_ReplyWithCallReply(ctx, reply); + RedisModule_FreeCallReply(reply); + } else { + RedisModule_ReplyWithError(ctx, strerror(errno)); + } + return REDISMODULE_OK; +} + +int test_call_info(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) +{ + RedisModuleCallReply *reply; + if (argc>1) + reply = RedisModule_Call(ctx, "info", "s", argv[1]); + else + reply = RedisModule_Call(ctx, "info", ""); + if (reply) { + RedisModule_ReplyWithCallReply(ctx, reply); + RedisModule_FreeCallReply(reply); + } else { + RedisModule_ReplyWithError(ctx, strerror(errno)); + } + return REDISMODULE_OK; +} + +int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + if (RedisModule_Init(ctx,"misc",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_CreateCommand(ctx,"test.call_generic", test_call_generic,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + if (RedisModule_CreateCommand(ctx,"test.call_info", test_call_info,"",0,0,0) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + return REDISMODULE_OK; +} diff --git a/tests/unit/moduleapi/misc.tcl b/tests/unit/moduleapi/misc.tcl new file mode 100644 index 000000000..d392aeab0 --- /dev/null +++ b/tests/unit/moduleapi/misc.tcl @@ -0,0 +1,19 @@ +set testmodule [file normalize tests/modules/misc.so] + + +start_server {tags {"modules"}} { + r module load $testmodule + + test {test RM_Call} { + set info [r test.call_info commandstats] + # cmdstat is not in a default section, so we also test an argument was passed + assert { [string match "*cmdstat_module*" $info] } + } + + test {test RM_Call args array} { + set info [r test.call_generic info commandstats] + # cmdstat is not in a default section, so we also test an argument was passed + assert { [string match "*cmdstat_module*" $info] } + } + +} From 7be7591b7b80429394795bcb65109818081721c2 Mon Sep 17 00:00:00 2001 From: Guy Benoish Date: Mon, 28 Oct 2019 17:58:07 +0530 Subject: [PATCH 10/10] Fix compilation error introduced by 5d01f01be Need to add calls to REDISMODULE_API_FUNC... --- src/redismodule.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/redismodule.h b/src/redismodule.h index 434849c77..0a0573ce9 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -461,6 +461,7 @@ void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisMo int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b); RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io); const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromIO)(RedisModuleIO *io); +const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromModuleKey)(RedisModuleKey *key); long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void); void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len); void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele); @@ -513,6 +514,8 @@ void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx); void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx); int REDISMODULE_API_FUNC(RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb); +int REDISMODULE_API_FUNC(RedisModule_NotifyKeyspaceEvent)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key); +int REDISMODULE_API_FUNC(RedisModule_GetNotifyKeyspaceEvents)(); int REDISMODULE_API_FUNC(RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx); void REDISMODULE_API_FUNC(RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback); int REDISMODULE_API_FUNC(RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len);