From 2136035e47c5c50aec07270e0c87a9e5cd5e243d Mon Sep 17 00:00:00 2001 From: Dvir Volk Date: Mon, 27 Nov 2017 16:29:55 +0200 Subject: [PATCH 01/10] finished implementation of notifications. Tests unfinished --- src/module.c | 137 +++++++++++++++++++++++++++++++++++++- src/modules/Makefile | 7 ++ src/modules/hellonotify.c | 79 ++++++++++++++++++++++ src/modules/testmodule.c | 89 ++++++++++++++++++++++++- src/notify.c | 9 +++ src/redismodule.h | 16 +++++ src/server.h | 3 + 7 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 src/modules/hellonotify.c diff --git a/src/module.c b/src/module.c index 545f1a74..9288dafc 100644 --- a/src/module.c +++ b/src/module.c @@ -216,6 +216,25 @@ static list *moduleUnblockedClients; * allow thread safe contexts to execute commands at a safe moment. */ static pthread_mutex_t moduleGIL = PTHREAD_MUTEX_INITIALIZER; + +/* Function pointer type for keyspace event notification subscriptions from modules. */ +typedef int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key); + +/* Keyspace notification subscriber information. See RM_SubscribeToKeyspaceEvents */ +typedef struct RedisModuleKeyspaceSubscriber { + /* The module subscribed to the event */ + RedisModule *module; + /* Notification callback in the module*/ + RedisModuleNotificationFunc notify_callback; + /* A bit mask of the events the module is interested in */ + int event_mask; + /* Active flag set on entry, to avoid reentrant subscribers calling themselves */ + int active; +} RedisModuleKeyspaceSubscriber; + +/* The module keyspace notification subscribers list */ +static list *moduleKeyspaceSubscribers; + /* -------------------------------------------------------------------------- * Prototypes * -------------------------------------------------------------------------- */ @@ -3669,6 +3688,114 @@ void moduleReleaseGIL(void) { pthread_mutex_unlock(&moduleGIL); } + +/* -------------------------------------------------------------------------- + * Module Keyspace Notifications API + * -------------------------------------------------------------------------- */ + +/* Subscribe to keyspace notifications. This is a low-level version of the + * keyspace-notifications API. A module cand register callbacks to be notified + * when keyspce events occur. + * Notification events are filtered by their type (string events, set events, + * etc), and the subsriber callback receives only events that match a specific + * mask of event types. + * We do not distinguish between key events and keyspace events, and it is up + * to the module to filter the actions taken based on the key. + * + * The subscriber signature is: + * + * int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type, + * const char *event, + * RedisModuleString *key); + * + * `type` is the event type bit, that must match the mask given at registration + * time. The event string is the actual command being executed, and key is the + * relevant Redis key. + * + * A notification callback gets executed with a redis context that can not be + * used to send anything to the client, and has the db number where the event + * occured as its selected db number. + * + * Notice that it is not necessary to enable norifications in redis.conf for + * module notifications to work. + * + * Warning: the notification callbacks are performed in a synchronous manner, + * so notification callbacks must to be fast, or they would slow Redis down. + * If you need to take long actions, use threads to offload them. + * + * See https://redis.io/topics/notifications for more information. + */ +int RM_SubscribeToKeyspaceEvents(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc callback) { + RedisModuleKeyspaceSubscriber *sub = zmalloc(sizeof(*sub)); + sub->module = ctx->module; + sub->event_mask = types; + sub->notify_callback = callback; + sub->active = 0; + + /* Let the notification system know that modules are interested in notifications */ + server.notify_keyspace_events |= NOTIFY_MODULE; + listAddNodeTail(moduleKeyspaceSubscribers, sub); + 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 */ +void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) { + listIter li; + listNode *ln; + + listRewind(moduleKeyspaceSubscribers,&li); + + /* Remove irrelevant flags from the type mask */ + type &= ~(NOTIFY_KEYEVENT | NOTIFY_KEYSPACE); + + /* Setup a fake client, so we can have proper db selection when performing + * actions. We use one client for all handlers, writing to it will crash */ + client *c = createClient(-1); + c->flags |= CLIENT_MODULE; + + while((ln = listNext(&li))) { + RedisModuleKeyspaceSubscriber *sub = ln->value; + /* Only notify subscribers on events matching they registration, + * and avoid subscribers triggering themselves */ + if ((sub->event_mask & type) && sub->active == 0) { + RedisModuleCtx ctx = REDISMODULE_CTX_INIT; + ctx.module = sub->module; + selectDb(c, dbid); + ctx.client = c; + /* mark the handler as activer to avoid reentrant loops. + * If the subscriber performs an action triggering itself, + * it will not be notified about it. */ + sub->active = 1; + sub->notify_callback(&ctx, type, event, key); + sub->active = 0; + moduleFreeContext(&ctx); + } + } + freeClient(c); +} + +/* Unsubscribe any notification subscirbers this module has upon unloading */ +void moduleUnsubscribeNotifications(RedisModule *module) { + listIter li; + listNode *ln; + listRewind(moduleKeyspaceSubscribers,&li); + while((ln = listNext(&li))) { + RedisModuleKeyspaceSubscriber *sub = ln->value; + if (sub->module == module) { + listDelNode(moduleKeyspaceSubscribers, ln); + zfree(sub); + } + } + /* If no subscribers are left - do not call the module norification function */ + if (listLength(moduleKeyspaceSubscribers) == 0) { + server.notify_keyspace_events &= ~NOTIFY_MODULE; + } +} + + /* -------------------------------------------------------------------------- * Modules API internals * -------------------------------------------------------------------------- */ @@ -3706,9 +3833,11 @@ void moduleRegisterCoreAPI(void); void moduleInitModulesSystem(void) { moduleUnblockedClients = listCreate(); - + server.loadmodule_queue = listCreate(); modules = dictCreate(&modulesDictType,NULL); + moduleKeyspaceSubscribers = listCreate(); + moduleRegisterCoreAPI(); if (pipe(server.module_blocked_pipe) == -1) { serverLog(LL_WARNING, @@ -3759,6 +3888,7 @@ void moduleFreeModuleStructure(struct RedisModule *module) { zfree(module); } + void moduleUnregisterCommands(struct RedisModule *module) { /* Unregister all the commands registered by this module. */ dictIterator *di = dictGetSafeIterator(server.commands); @@ -3819,6 +3949,7 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) { return C_OK; } + /* Unload the module registered with the specified name. On success * C_OK is returned, otherwise C_ERR is returned and errno is set * to the following values depending on the type of error: @@ -3840,6 +3971,9 @@ int moduleUnload(sds name) { moduleUnregisterCommands(module); + /* Remvoe any noification subscribers this module might have */ + moduleUnsubscribeNotifications(module); + /* Unregister all the hooks. TODO: Yet no hooks support here. */ /* Unload the dynamic library. */ @@ -4037,4 +4171,5 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(DigestAddStringBuffer); REGISTER_API(DigestAddLongLong); REGISTER_API(DigestEndSequence); + REGISTER_API(SubscribeToKeyspaceEvents); } diff --git a/src/modules/Makefile b/src/modules/Makefile index 066e65e9..7f7b9a74 100644 --- a/src/modules/Makefile +++ b/src/modules/Makefile @@ -33,6 +33,13 @@ helloblock.xo: ../redismodule.h helloblock.so: helloblock.xo $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lpthread -lc + +hellonotify.xo: ../redismodule.h + +hellonotify.so: hellonotify.xo + $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lpthread -lc + + testmodule.xo: ../redismodule.h testmodule.so: testmodule.xo diff --git a/src/modules/hellonotify.c b/src/modules/hellonotify.c new file mode 100644 index 00000000..f5885923 --- /dev/null +++ b/src/modules/hellonotify.c @@ -0,0 +1,79 @@ +/* Helloworld module -- A few examples of the Redis Modules API in the form + * of commands showing how to accomplish common tasks. + * + * This module does not do anything useful, if not for a few commands. The + * examples are designed in order to show the API. + * + * ----------------------------------------------------------------------------- + * + * Copyright (c) 2016, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define REDISMODULE_EXPERIMENTAL_API +#include +#include +#include +#include +#include "../redismodule.h" + +/* HELLO.SIMPLE is among the simplest commands you can implement. + * It just returns the currently selected DB id, a functionality which is + * missing in Redis. The command uses two important API calls: one to + * fetch the currently selected DB, the other in order to send the client + * an integer reply as response. */ +int HelloSimple_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, + int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + RedisModule_ReplyWithLongLong(ctx, RedisModule_GetSelectedDb(ctx)); + return REDISMODULE_OK; +} + +int HelloNotify_Callback(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) { + REDISMODULE_NOT_USED(ctx); + REDISMODULE_NOT_USED(ctx); + RedisModule_Log(ctx, + "notice", + "Received notification! Event type: %d, event: %s, key: %s", + type, event, RedisModule_StringPtrLen(key, NULL)); + RedisModule_Call(ctx, "SET", "cc", "foo", "bar"); + return REDISMODULE_OK; +} +/* 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) { + if (RedisModule_Init(ctx, "notify", 1, REDISMODULE_APIVER_1) == + REDISMODULE_ERR) + return REDISMODULE_ERR; + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_ALL, HelloNotify_Callback); + + return REDISMODULE_OK; +} \ No newline at end of file diff --git a/src/modules/testmodule.c b/src/modules/testmodule.c index 956b2417..e360866f 100644 --- a/src/modules/testmodule.c +++ b/src/modules/testmodule.c @@ -30,6 +30,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#define REDISMODULE_EXPERIMENTAL_API #include "../redismodule.h" #include @@ -153,6 +154,80 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { } +int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) { + // Increment a counter on the notifications + RedisModule_Log(ctx, "notice", "Got event type %d, event %s, key %s\n", type, event, RedisModule_StringPtrLen(key, NULL)); + + RedisModule_Call(ctx, "HINCRBY", "csc", "notifications", key, "1"); + return REDISMODULE_OK; +} + +/* TEST.NOTIFICATIONS -- Test Keyspace Notifications. */ +int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); + +#define FAIL(msg, ...) \ + { \ + RedisModule_Log(ctx, "warning", "Failed NOTIFY Test. Reason: " #msg, \ + ##__VA_ARGS__); \ + goto err; \ + } + RedisModule_Call(ctx, "FLUSHDB", ""); + + RedisModule_Call(ctx, "SET", "cc", "foo", "bar"); + RedisModule_Call(ctx, "SET", "cc", "foo", "baz"); + RedisModule_Call(ctx, "SADD", "cc", "bar", "x"); + RedisModule_Call(ctx, "SADD", "cc", "bar", "y"); + + RedisModule_Call(ctx, "HSET", "ccc", "baz", "x", "y"); + // LPUSH should be ignored and not increment any counters + RedisModule_Call(ctx, "LPUSH", "cc", "l", "y"); + RedisModule_Call(ctx, "LPUSH", "cc", "l", "y"); + + size_t sz; + const char *rep; + RedisModuleCallReply *r = + RedisModule_Call(ctx, "HGET", "cc", "notifications", "foo"); + if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { + FAIL("Wrong or no reply for foo"); + } else { + rep = RedisModule_CallReplyStringPtr(r, &sz); + if (sz != 1 || *rep != '2') { + FAIL("Got reply '%s'. expected '2'", + RedisModule_CallReplyStringPtr(r, NULL)); + } + } + + r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "bar"); + if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { + FAIL("Wrong or no reply for bar"); + } else { + rep = RedisModule_CallReplyStringPtr(r, &sz); + if (sz != 1 || *rep != '2') { + FAIL("Got reply '%s'. expected '2'", rep); + } + } + + r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "baz"); + if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { + FAIL("Wrong or no reply for baz"); + } else { + rep = RedisModule_CallReplyStringPtr(r, &sz); + if (sz != 1 || *rep != '1') { + FAIL("Got reply '%.*s'. expected '1'", sz, rep); + } + } + r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "l"); + if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_NULL) { + FAIL("Wrong reply for l"); + } + + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +err: + return RedisModule_ReplyWithSimpleString(ctx, "ERR"); +} + /* TEST.CTXFLAGS -- Test GetContextFlags. */ int TestCtxFlags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { REDISMODULE_NOT_USED(argc); @@ -162,7 +237,7 @@ int TestCtxFlags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int ok = 1; const char *errString = NULL; - + #undef FAIL #define FAIL(msg) \ { \ ok = 0; \ @@ -310,6 +385,9 @@ int TestIt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { T("test.string.printf", "cc", "foo", "bar"); if (!TestAssertStringReply(ctx,reply,"Got 3 args. argv[1]: foo, argv[2]: bar",38)) goto fail; + T("test.notify", ""); + if (!TestAssertStringReply(ctx,reply,"OK",2)) goto fail; + RedisModule_ReplyWithSimpleString(ctx,"ALL TESTS PASSED"); return REDISMODULE_OK; @@ -354,5 +432,14 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) TestIt,"readonly",1,1,1) == REDISMODULE_ERR) return REDISMODULE_ERR; + RedisModule_SubscribeToKeyspaceEvents(ctx, + REDISMODULE_NOTIFY_HASH | + REDISMODULE_NOTIFY_SET | + REDISMODULE_NOTIFY_STRING, + NotifyCallback); + if (RedisModule_CreateCommand(ctx,"test.notify", + TestNotifications,"write deny-oom",1,1,1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + return REDISMODULE_OK; } diff --git a/src/notify.c b/src/notify.c index 9bbeb142..e77f6791 100644 --- a/src/notify.c +++ b/src/notify.c @@ -87,6 +87,8 @@ sds keyspaceEventsFlagsToString(int flags) { return res; } + + /* The API provided to the rest of the Redis core is a simple function: * * notifyKeyspaceEvent(char *event, robj *key, int dbid); @@ -100,6 +102,13 @@ void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) { int len = -1; char buf[24]; + /* If any modules are interested in events, notify the module system now. + * This bypasses the notifications configuration, but the module engine + * will only call event subscribers if the event type matches the types + * they are interested in. */ + if (server.notify_keyspace_events & NOTIFY_MODULE) + moduleNotifyKeyspaceEvent(type, event, key, dbid); + /* If notifications for this class of events are off, return ASAP. */ if (!(server.notify_keyspace_events & type)) return; diff --git a/src/redismodule.h b/src/redismodule.h index 09d681c2..4c512cfd 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -82,6 +82,17 @@ #define REDISMODULE_CTX_FLAGS_EVICT 0x0200 +#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */ +#define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */ +#define REDISMODULE_NOTIFY_LIST (1<<4) /* l */ +#define REDISMODULE_NOTIFY_SET (1<<5) /* s */ +#define REDISMODULE_NOTIFY_HASH (1<<6) /* h */ +#define REDISMODULE_NOTIFY_ZSET (1<<7) /* z */ +#define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */ +#define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */ +#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED) /* A */ + + /* A special pointer that we can use between the core and the module to signal * field deletion, and that is impossible to be a valid pointer. */ #define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1) @@ -112,6 +123,7 @@ typedef struct RedisModuleBlockedClient RedisModuleBlockedClient; typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc); +typedef int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key); typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver); typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value); typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value); @@ -251,6 +263,8 @@ RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetThreadSafeContext)(RedisModu 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); + #endif /* This is included inline inside each Redis module. */ @@ -372,6 +386,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(IsBlockedTimeoutRequest); REDISMODULE_GET_API(GetBlockedClientPrivateData); REDISMODULE_GET_API(AbortBlock); + REDISMODULE_GET_API(SubscribeToKeyspaceEvents); + #endif if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR; diff --git a/src/server.h b/src/server.h index 271f217f..f4cb7cde 100644 --- a/src/server.h +++ b/src/server.h @@ -429,6 +429,7 @@ typedef long long mstime_t; /* millisecond time type. */ #define NOTIFY_EXPIRED (1<<8) /* x */ #define NOTIFY_EVICTED (1<<9) /* e */ #define NOTIFY_STREAM (1<<10) /* t */ +#define NOTIFY_MODULE (1<<11) /* modules are interested in notifications */ #define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM) /* A flag */ /* Get the first bind addr or NULL */ @@ -1335,6 +1336,8 @@ void moduleBlockedClientPipeReadable(aeEventLoop *el, int fd, void *privdata, in size_t moduleCount(void); void moduleAcquireGIL(void); void moduleReleaseGIL(void); +void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid); + /* Utils */ long long ustime(void); From 896db12b418877967d7689e2731c266d8dd1d4f9 Mon Sep 17 00:00:00 2001 From: Dvir Volk Date: Mon, 27 Nov 2017 23:13:45 +0200 Subject: [PATCH 02/10] fixed test --- src/modules/testmodule.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/testmodule.c b/src/modules/testmodule.c index e360866f..8ab8c4ea 100644 --- a/src/modules/testmodule.c +++ b/src/modules/testmodule.c @@ -155,7 +155,8 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { } int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) { - // Increment a counter on the notifications + // Increment a counter on the notifications: + // for each key notified we increment a counter RedisModule_Log(ctx, "notice", "Got event type %d, event %s, key %s\n", type, event, RedisModule_StringPtrLen(key, NULL)); RedisModule_Call(ctx, "HINCRBY", "csc", "notifications", key, "1"); @@ -218,13 +219,18 @@ int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { FAIL("Got reply '%.*s'. expected '1'", sz, rep); } } + // for l we expect nothing since we didn't subscribe to list events r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "l"); if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_NULL) { FAIL("Wrong reply for l"); } + RedisModule_Call(ctx, "FLUSHDB", ""); + return RedisModule_ReplyWithSimpleString(ctx, "OK"); err: + RedisModule_Call(ctx, "FLUSHDB", ""); + return RedisModule_ReplyWithSimpleString(ctx, "ERR"); } From 5b7b12e38fccb92d2778eed51962be0edf8c5ec6 Mon Sep 17 00:00:00 2001 From: Dvir Volk Date: Mon, 27 Nov 2017 23:18:45 +0200 Subject: [PATCH 03/10] removed hellonotify.c --- src/modules/Makefile | 7 ---- src/modules/hellonotify.c | 79 --------------------------------------- src/modules/testmodule.c | 2 +- 3 files changed, 1 insertion(+), 87 deletions(-) delete mode 100644 src/modules/hellonotify.c diff --git a/src/modules/Makefile b/src/modules/Makefile index 7f7b9a74..066e65e9 100644 --- a/src/modules/Makefile +++ b/src/modules/Makefile @@ -33,13 +33,6 @@ helloblock.xo: ../redismodule.h helloblock.so: helloblock.xo $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lpthread -lc - -hellonotify.xo: ../redismodule.h - -hellonotify.so: hellonotify.xo - $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lpthread -lc - - testmodule.xo: ../redismodule.h testmodule.so: testmodule.xo diff --git a/src/modules/hellonotify.c b/src/modules/hellonotify.c deleted file mode 100644 index f5885923..00000000 --- a/src/modules/hellonotify.c +++ /dev/null @@ -1,79 +0,0 @@ -/* Helloworld module -- A few examples of the Redis Modules API in the form - * of commands showing how to accomplish common tasks. - * - * This module does not do anything useful, if not for a few commands. The - * examples are designed in order to show the API. - * - * ----------------------------------------------------------------------------- - * - * Copyright (c) 2016, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#define REDISMODULE_EXPERIMENTAL_API -#include -#include -#include -#include -#include "../redismodule.h" - -/* HELLO.SIMPLE is among the simplest commands you can implement. - * It just returns the currently selected DB id, a functionality which is - * missing in Redis. The command uses two important API calls: one to - * fetch the currently selected DB, the other in order to send the client - * an integer reply as response. */ -int HelloSimple_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, - int argc) { - REDISMODULE_NOT_USED(argv); - REDISMODULE_NOT_USED(argc); - RedisModule_ReplyWithLongLong(ctx, RedisModule_GetSelectedDb(ctx)); - return REDISMODULE_OK; -} - -int HelloNotify_Callback(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) { - REDISMODULE_NOT_USED(ctx); - REDISMODULE_NOT_USED(ctx); - RedisModule_Log(ctx, - "notice", - "Received notification! Event type: %d, event: %s, key: %s", - type, event, RedisModule_StringPtrLen(key, NULL)); - RedisModule_Call(ctx, "SET", "cc", "foo", "bar"); - return REDISMODULE_OK; -} -/* 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) { - if (RedisModule_Init(ctx, "notify", 1, REDISMODULE_APIVER_1) == - REDISMODULE_ERR) - return REDISMODULE_ERR; - REDISMODULE_NOT_USED(argv); - REDISMODULE_NOT_USED(argc); - RedisModule_SubscribeToKeyspaceEvents(ctx, REDISMODULE_NOTIFY_ALL, HelloNotify_Callback); - - return REDISMODULE_OK; -} \ No newline at end of file diff --git a/src/modules/testmodule.c b/src/modules/testmodule.c index 8ab8c4ea..79be2fb8 100644 --- a/src/modules/testmodule.c +++ b/src/modules/testmodule.c @@ -157,7 +157,7 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) { // Increment a counter on the notifications: // for each key notified we increment a counter - RedisModule_Log(ctx, "notice", "Got event type %d, event %s, key %s\n", type, event, RedisModule_StringPtrLen(key, NULL)); + RedisModule_Log(ctx, "notice", "Got event type %d, event %s, key %s", type, event, RedisModule_StringPtrLen(key, NULL)); RedisModule_Call(ctx, "HINCRBY", "csc", "notifications", key, "1"); return REDISMODULE_OK; From d4d753dae44fa96974eefea83be7aa2bb8838b41 Mon Sep 17 00:00:00 2001 From: Dvir Volk Date: Mon, 27 Nov 2017 23:19:51 +0200 Subject: [PATCH 04/10] removed some trailing whitespaces --- src/notify.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/notify.c b/src/notify.c index e77f6791..23789804 100644 --- a/src/notify.c +++ b/src/notify.c @@ -87,8 +87,6 @@ sds keyspaceEventsFlagsToString(int flags) { return res; } - - /* The API provided to the rest of the Redis core is a simple function: * * notifyKeyspaceEvent(char *event, robj *key, int dbid); From a8e2e99a88968a22cdbfdac404a812e80b5dd0d7 Mon Sep 17 00:00:00 2001 From: Dvir Volk Date: Mon, 27 Nov 2017 23:27:20 +0200 Subject: [PATCH 05/10] Document flags for notifications --- src/module.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/module.c b/src/module.c index 9288dafc..deb2b505 100644 --- a/src/module.c +++ b/src/module.c @@ -3696,9 +3696,25 @@ void moduleReleaseGIL(void) { /* Subscribe to keyspace notifications. This is a low-level version of the * keyspace-notifications API. A module cand register callbacks to be notified * when keyspce events occur. + * * Notification events are filtered by their type (string events, set events, * etc), and the subsriber callback receives only events that match a specific * mask of event types. + * + * When subscribing to notifications with RedisModule_SubscribeToKeyspaceEvents + * the module must provide an event type-mask, denoting the events the subscriber + * is interested in. This can be an ORed mask of any of the following flags: + * + * - REDISMODULE_NOTIFY_GENERIC: Generic commands like DEL, EXPIRE, RENAME + * - REDISMODULE_NOTIFY_STRING: String events + * - REDISMODULE_NOTIFY_LIST: List events + * - REDISMODULE_NOTIFY_SET: Set events + * - REDISMODULE_NOTIFY_HASH: Hash events + * - REDISMODULE_NOTIFY_ZSET: Sorted Set events + * - REDISMODULE_NOTIFY_EXPIRED: Expiration events + * - REDISMODULE_NOTIFY_EVICTED: Eviction events + * - REDISMODULE_NOTIFY_ALL: All events + * * We do not distinguish between key events and keyspace events, and it is up * to the module to filter the actions taken based on the key. * @@ -3712,7 +3728,7 @@ void moduleReleaseGIL(void) { * time. The event string is the actual command being executed, and key is the * relevant Redis key. * - * A notification callback gets executed with a redis context that can not be + * Notification callback gets executed with a redis context that can not be * used to send anything to the client, and has the db number where the event * occured as its selected db number. * From 3aab12414f27685bf8e9dc9fd4693c8c9ecf9ccb Mon Sep 17 00:00:00 2001 From: Dvir Volk Date: Thu, 7 Dec 2017 16:55:46 +0200 Subject: [PATCH 06/10] Remove the NOTIFY_MODULE flag and simplify the module notification flow if there aren't subscribers --- src/module.c | 10 ++++------ src/notify.c | 3 +-- src/server.h | 1 - 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/module.c b/src/module.c index deb2b505..e2c878ea 100644 --- a/src/module.c +++ b/src/module.c @@ -3748,8 +3748,6 @@ int RM_SubscribeToKeyspaceEvents(RedisModuleCtx *ctx, int types, RedisModuleNoti sub->notify_callback = callback; sub->active = 0; - /* Let the notification system know that modules are interested in notifications */ - server.notify_keyspace_events |= NOTIFY_MODULE; listAddNodeTail(moduleKeyspaceSubscribers, sub); return REDISMODULE_OK; @@ -3759,6 +3757,10 @@ int RM_SubscribeToKeyspaceEvents(RedisModuleCtx *ctx, int types, RedisModuleNoti * This gets called only if at least one module requested to be notified on * keyspace notifications */ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) { + + /* Don't do anything if there aren't any subscribers */ + if (listLength(moduleKeyspaceSubscribers) == 0) return; + listIter li; listNode *ln; @@ -3805,10 +3807,6 @@ void moduleUnsubscribeNotifications(RedisModule *module) { zfree(sub); } } - /* If no subscribers are left - do not call the module norification function */ - if (listLength(moduleKeyspaceSubscribers) == 0) { - server.notify_keyspace_events &= ~NOTIFY_MODULE; - } } diff --git a/src/notify.c b/src/notify.c index 23789804..3473a125 100644 --- a/src/notify.c +++ b/src/notify.c @@ -104,8 +104,7 @@ void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) { * This bypasses the notifications configuration, but the module engine * will only call event subscribers if the event type matches the types * they are interested in. */ - if (server.notify_keyspace_events & NOTIFY_MODULE) - moduleNotifyKeyspaceEvent(type, event, key, dbid); + moduleNotifyKeyspaceEvent(type, event, key, dbid); /* If notifications for this class of events are off, return ASAP. */ if (!(server.notify_keyspace_events & type)) return; diff --git a/src/server.h b/src/server.h index f4cb7cde..5b6074a8 100644 --- a/src/server.h +++ b/src/server.h @@ -429,7 +429,6 @@ typedef long long mstime_t; /* millisecond time type. */ #define NOTIFY_EXPIRED (1<<8) /* x */ #define NOTIFY_EVICTED (1<<9) /* e */ #define NOTIFY_STREAM (1<<10) /* t */ -#define NOTIFY_MODULE (1<<11) /* modules are interested in notifications */ #define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM) /* A flag */ /* Get the first bind addr or NULL */ From f27a64232e59e5bc5f8ee648b3610d19128ab149 Mon Sep 17 00:00:00 2001 From: Dvir Volk Date: Thu, 7 Dec 2017 17:15:16 +0200 Subject: [PATCH 07/10] Use one static client for all keyspace notification callbacks --- src/module.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/module.c b/src/module.c index e2c878ea..c909cfa9 100644 --- a/src/module.c +++ b/src/module.c @@ -235,6 +235,9 @@ typedef struct RedisModuleKeyspaceSubscriber { /* The module keyspace notification subscribers list */ static list *moduleKeyspaceSubscribers; +/* Static client recycled for all notification clients, to avoid allocating per round. */ +static client *moduleKeyspaceSubscribersClient; + /* -------------------------------------------------------------------------- * Prototypes * -------------------------------------------------------------------------- */ @@ -3769,10 +3772,6 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) /* Remove irrelevant flags from the type mask */ type &= ~(NOTIFY_KEYEVENT | NOTIFY_KEYSPACE); - /* Setup a fake client, so we can have proper db selection when performing - * actions. We use one client for all handlers, writing to it will crash */ - client *c = createClient(-1); - c->flags |= CLIENT_MODULE; while((ln = listNext(&li))) { RedisModuleKeyspaceSubscriber *sub = ln->value; @@ -3781,8 +3780,9 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) if ((sub->event_mask & type) && sub->active == 0) { RedisModuleCtx ctx = REDISMODULE_CTX_INIT; ctx.module = sub->module; - selectDb(c, dbid); - ctx.client = c; + ctx.client = moduleKeyspaceSubscribersClient; + selectDb(ctx.client, dbid); + /* mark the handler as activer to avoid reentrant loops. * If the subscriber performs an action triggering itself, * it will not be notified about it. */ @@ -3792,7 +3792,7 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) moduleFreeContext(&ctx); } } - freeClient(c); + } /* Unsubscribe any notification subscirbers this module has upon unloading */ @@ -3850,7 +3850,11 @@ void moduleInitModulesSystem(void) { server.loadmodule_queue = listCreate(); modules = dictCreate(&modulesDictType,NULL); + + /* Set up the keyspace notification susbscriber list and static client */ moduleKeyspaceSubscribers = listCreate(); + moduleKeyspaceSubscribersClient = createClient(-1); + moduleKeyspaceSubscribersClient->flags |= CLIENT_MODULE; moduleRegisterCoreAPI(); if (pipe(server.module_blocked_pipe) == -1) { From 613831f82023601df874b9cfa9a1935ff942ceb6 Mon Sep 17 00:00:00 2001 From: Dvir Volk Date: Thu, 7 Dec 2017 17:19:04 +0200 Subject: [PATCH 08/10] Fix indentation and comment style in testmodule --- src/modules/testmodule.c | 190 +++++++++++++++++++-------------------- 1 file changed, 92 insertions(+), 98 deletions(-) diff --git a/src/modules/testmodule.c b/src/modules/testmodule.c index 79be2fb8..67a86170 100644 --- a/src/modules/testmodule.c +++ b/src/modules/testmodule.c @@ -125,6 +125,7 @@ int failTest(RedisModuleCtx *ctx, const char *msg) { RedisModule_ReplyWithError(ctx, msg); return REDISMODULE_ERR; } + int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { RedisModule_AutoMemory(ctx); REDISMODULE_NOT_USED(argv); @@ -154,10 +155,12 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { } -int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) { - // Increment a counter on the notifications: - // for each key notified we increment a counter - RedisModule_Log(ctx, "notice", "Got event type %d, event %s, key %s", type, event, RedisModule_StringPtrLen(key, NULL)); +int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event, + RedisModuleString *key) { + /* Increment a counter on the notifications: for each key notified we + * increment a counter */ + RedisModule_Log(ctx, "notice", "Got event type %d, event %s, key %s", type, + event, RedisModule_StringPtrLen(key, NULL)); RedisModule_Call(ctx, "HINCRBY", "csc", "notifications", key, "1"); return REDISMODULE_OK; @@ -165,149 +168,140 @@ int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event, RedisModule /* TEST.NOTIFICATIONS -- Test Keyspace Notifications. */ int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { - REDISMODULE_NOT_USED(argv); - REDISMODULE_NOT_USED(argc); + REDISMODULE_NOT_USED(argv); + REDISMODULE_NOT_USED(argc); -#define FAIL(msg, ...) \ - { \ - RedisModule_Log(ctx, "warning", "Failed NOTIFY Test. Reason: " #msg, \ - ##__VA_ARGS__); \ - goto err; \ - } - RedisModule_Call(ctx, "FLUSHDB", ""); - - RedisModule_Call(ctx, "SET", "cc", "foo", "bar"); - RedisModule_Call(ctx, "SET", "cc", "foo", "baz"); - RedisModule_Call(ctx, "SADD", "cc", "bar", "x"); - RedisModule_Call(ctx, "SADD", "cc", "bar", "y"); - - RedisModule_Call(ctx, "HSET", "ccc", "baz", "x", "y"); - // LPUSH should be ignored and not increment any counters - RedisModule_Call(ctx, "LPUSH", "cc", "l", "y"); - RedisModule_Call(ctx, "LPUSH", "cc", "l", "y"); - - size_t sz; - const char *rep; - RedisModuleCallReply *r = - RedisModule_Call(ctx, "HGET", "cc", "notifications", "foo"); - if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { - FAIL("Wrong or no reply for foo"); - } else { - rep = RedisModule_CallReplyStringPtr(r, &sz); - if (sz != 1 || *rep != '2') { - FAIL("Got reply '%s'. expected '2'", - RedisModule_CallReplyStringPtr(r, NULL)); +#define FAIL(msg, ...) \ + { \ + RedisModule_Log(ctx, "warning", "Failed NOTIFY Test. Reason: " #msg, ##__VA_ARGS__); \ + goto err; \ } - } + RedisModule_Call(ctx, "FLUSHDB", ""); - r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "bar"); - if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { - FAIL("Wrong or no reply for bar"); - } else { - rep = RedisModule_CallReplyStringPtr(r, &sz); - if (sz != 1 || *rep != '2') { - FAIL("Got reply '%s'. expected '2'", rep); - } - } + RedisModule_Call(ctx, "SET", "cc", "foo", "bar"); + RedisModule_Call(ctx, "SET", "cc", "foo", "baz"); + RedisModule_Call(ctx, "SADD", "cc", "bar", "x"); + RedisModule_Call(ctx, "SADD", "cc", "bar", "y"); - r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "baz"); - if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { - FAIL("Wrong or no reply for baz"); - } else { - rep = RedisModule_CallReplyStringPtr(r, &sz); - if (sz != 1 || *rep != '1') { - FAIL("Got reply '%.*s'. expected '1'", sz, rep); + RedisModule_Call(ctx, "HSET", "ccc", "baz", "x", "y"); + /* LPUSH should be ignored and not increment any counters */ + RedisModule_Call(ctx, "LPUSH", "cc", "l", "y"); + RedisModule_Call(ctx, "LPUSH", "cc", "l", "y"); + + size_t sz; + const char *rep; + RedisModuleCallReply *r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "foo"); + if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { + FAIL("Wrong or no reply for foo"); + } else { + rep = RedisModule_CallReplyStringPtr(r, &sz); + if (sz != 1 || *rep != '2') { + FAIL("Got reply '%s'. expected '2'", RedisModule_CallReplyStringPtr(r, NULL)); + } + } + + r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "bar"); + if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { + FAIL("Wrong or no reply for bar"); + } else { + rep = RedisModule_CallReplyStringPtr(r, &sz); + if (sz != 1 || *rep != '2') { + FAIL("Got reply '%s'. expected '2'", rep); + } + } + + r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "baz"); + if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) { + FAIL("Wrong or no reply for baz"); + } else { + rep = RedisModule_CallReplyStringPtr(r, &sz); + if (sz != 1 || *rep != '1') { + FAIL("Got reply '%.*s'. expected '1'", sz, rep); + } + } + /* For l we expect nothing since we didn't subscribe to list events */ + r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "l"); + if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_NULL) { + FAIL("Wrong reply for l"); } - } - // for l we expect nothing since we didn't subscribe to list events - r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "l"); - if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_NULL) { - FAIL("Wrong reply for l"); - } RedisModule_Call(ctx, "FLUSHDB", ""); - return RedisModule_ReplyWithSimpleString(ctx, "OK"); + return RedisModule_ReplyWithSimpleString(ctx, "OK"); err: - RedisModule_Call(ctx, "FLUSHDB", ""); + RedisModule_Call(ctx, "FLUSHDB", ""); - return RedisModule_ReplyWithSimpleString(ctx, "ERR"); + return RedisModule_ReplyWithSimpleString(ctx, "ERR"); } /* TEST.CTXFLAGS -- Test GetContextFlags. */ int TestCtxFlags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { REDISMODULE_NOT_USED(argc); REDISMODULE_NOT_USED(argv); - + RedisModule_AutoMemory(ctx); - + int ok = 1; const char *errString = NULL; - #undef FAIL - #define FAIL(msg) \ - { \ - ok = 0; \ - errString = msg; \ - goto end; \ +#undef FAIL +#define FAIL(msg) \ + { \ + ok = 0; \ + errString = msg; \ + goto end; \ } - + int flags = RedisModule_GetContextFlags(ctx); if (flags == 0) { - FAIL("Got no flags"); + FAIL("Got no flags"); } - + if (flags & REDISMODULE_CTX_FLAGS_LUA) FAIL("Lua flag was set"); if (flags & REDISMODULE_CTX_FLAGS_MULTI) FAIL("Multi flag was set"); - + if (flags & REDISMODULE_CTX_FLAGS_AOF) FAIL("AOF Flag was set") /* Enable AOF to test AOF flags */ RedisModule_Call(ctx, "config", "ccc", "set", "appendonly", "yes"); flags = RedisModule_GetContextFlags(ctx); - if (!(flags & REDISMODULE_CTX_FLAGS_AOF)) - FAIL("AOF Flag not set after config set"); - + if (!(flags & REDISMODULE_CTX_FLAGS_AOF)) FAIL("AOF Flag not set after config set"); + if (flags & REDISMODULE_CTX_FLAGS_RDB) FAIL("RDB Flag was set"); /* Enable RDB to test RDB flags */ RedisModule_Call(ctx, "config", "ccc", "set", "save", "900 1"); flags = RedisModule_GetContextFlags(ctx); - if (!(flags & REDISMODULE_CTX_FLAGS_RDB)) - FAIL("RDB Flag was not set after config set"); - + if (!(flags & REDISMODULE_CTX_FLAGS_RDB)) FAIL("RDB Flag was not set after config set"); + if (!(flags & REDISMODULE_CTX_FLAGS_MASTER)) FAIL("Master flag was not set"); if (flags & REDISMODULE_CTX_FLAGS_SLAVE) FAIL("Slave flag was set"); if (flags & REDISMODULE_CTX_FLAGS_READONLY) FAIL("Read-only flag was set"); if (flags & REDISMODULE_CTX_FLAGS_CLUSTER) FAIL("Cluster flag was set"); - + if (flags & REDISMODULE_CTX_FLAGS_MAXMEMORY) FAIL("Maxmemory flag was set"); - ; + RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "100000000"); flags = RedisModule_GetContextFlags(ctx); if (!(flags & REDISMODULE_CTX_FLAGS_MAXMEMORY)) - FAIL("Maxmemory flag was not set after config set"); - + FAIL("Maxmemory flag was not set after config set"); + if (flags & REDISMODULE_CTX_FLAGS_EVICT) FAIL("Eviction flag was set"); - RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory-policy", - "allkeys-lru"); + RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory-policy", "allkeys-lru"); flags = RedisModule_GetContextFlags(ctx); - if (!(flags & REDISMODULE_CTX_FLAGS_EVICT)) - FAIL("Eviction flag was not set after config set"); - - end: + if (!(flags & REDISMODULE_CTX_FLAGS_EVICT)) FAIL("Eviction flag was not set after config set"); + +end: /* Revert config changes */ RedisModule_Call(ctx, "config", "ccc", "set", "appendonly", "no"); RedisModule_Call(ctx, "config", "ccc", "set", "save", ""); RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory", "0"); RedisModule_Call(ctx, "config", "ccc", "set", "maxmemory-policy", "noeviction"); - - if (!ok) { - RedisModule_Log(ctx, "warning", "Failed CTXFLAGS Test. Reason: %s", - errString); - return RedisModule_ReplyWithSimpleString(ctx, "ERR"); - } - - return RedisModule_ReplyWithSimpleString(ctx, "OK"); - } + if (!ok) { + RedisModule_Log(ctx, "warning", "Failed CTXFLAGS Test. Reason: %s", errString); + return RedisModule_ReplyWithSimpleString(ctx, "ERR"); + } + + return RedisModule_ReplyWithSimpleString(ctx, "OK"); +} /* ----------------------------- Test framework ----------------------------- */ From 10efdf307b09d57b9b9b499cecca3cc4f7aed197 Mon Sep 17 00:00:00 2001 From: Dvir Volk Date: Wed, 14 Feb 2018 21:50:42 +0200 Subject: [PATCH 09/10] Add REDISMODULE_NOTIFY_STREAM flag to support stream notifications --- src/redismodule.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/redismodule.h b/src/redismodule.h index 4c512cfd..8c14fd0d 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -90,7 +90,8 @@ #define REDISMODULE_NOTIFY_ZSET (1<<7) /* z */ #define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */ #define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */ -#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED) /* A */ +#define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */ +#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */ /* A special pointer that we can use between the core and the module to signal From 0a36196ce47e1ee6db6c2e5461777e72ea504143 Mon Sep 17 00:00:00 2001 From: Dvir Volk Date: Wed, 14 Feb 2018 21:54:00 +0200 Subject: [PATCH 10/10] Add doc comment about notification flags --- src/module.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/module.c b/src/module.c index c909cfa9..96e65f0b 100644 --- a/src/module.c +++ b/src/module.c @@ -3716,6 +3716,7 @@ void moduleReleaseGIL(void) { * - REDISMODULE_NOTIFY_ZSET: Sorted Set events * - REDISMODULE_NOTIFY_EXPIRED: Expiration events * - REDISMODULE_NOTIFY_EVICTED: Eviction events + * - REDISMODULE_NOTIFY_STREAM: Stream events * - REDISMODULE_NOTIFY_ALL: All events * * We do not distinguish between key events and keyspace events, and it is up