summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorantirez <antirez@gmail.com>2018-03-30 20:40:35 +0200
committerantirez <antirez@gmail.com>2018-03-30 20:40:35 +0200
commit561039c1258b71a888c64eaa85e671a630312346 (patch)
tree16d87b1b3ef63518a578fa30a736fba0a321a2e9
parent192361b562bdcad15618600a1d741c7415a72b13 (diff)
downloadredis-561039c1258b71a888c64eaa85e671a630312346.tar.gz
Modules Timer API: initial implementation.
-rw-r--r--src/module.c93
-rw-r--r--src/redismodule.h6
2 files changed, 99 insertions, 0 deletions
diff --git a/src/module.c b/src/module.c
index a38540bbe..2ad9e3c7f 100644
--- a/src/module.c
+++ b/src/module.c
@@ -4018,6 +4018,96 @@ int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *m
}
/* --------------------------------------------------------------------------
+ * Modules Timers API
+ *
+ * Module timers are an high precision "green timers" abstraction where
+ * every module can register even millions of timers without problems, even if
+ * the actual event loop will just have a single timer that is used to awake the
+ * module timers subsystem in order to process the next event.
+ *
+ * All the timers are stored into a radix tree, ordered by expire time, when
+ * the main Redis event loop timer callback is called, we try to process all
+ * the timers already expired one after the other. Then we re-enter the event
+ * loop registering a timer that will expire when the next to process module
+ * timer will expire.
+ *
+ * Every time the list of active timers drops to zero, we unregister the
+ * main event loop timer, so that there is no overhead when such feature is
+ * not used.
+ * -------------------------------------------------------------------------- */
+
+static rax *Timers; /* The radix tree of all the timers sorted by expire. */
+long long aeTimer = -1; /* Main event loop (ae.c) timer identifier. */
+
+typedef int64_t (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
+
+/* The timer descriptor, stored as value in the radix tree. */
+typedef struct RedisModuleTimer {
+ RedisModule *module; /* Module reference. */
+ RedisModuleTimerProc callback; /* The callback to invoke on expire. */
+ void *data; /* Private data for the callback. */
+} RedisModuleTimer;
+
+/* Create a new timer that will fire after `period` milliseconds, and will call
+ * the specified function using `data` as argument. The returned timer ID can be
+ * used to get information from the timer or to stop it before it fires. */
+RedisModuleTimerID RM_CreateTimer(RedisModuleCtx *ctx, mstime_t period, RedisModuleTimerProc callback, void *data) {
+ RedisModuleTimer *timer = zmalloc(sizeof(*timer));
+ timer->module = ctx->module;
+ timer->callback = callback;
+ timer->data = data;
+ uint64_t expiretime = ustime()+period*1000;
+ uint64_t key;
+
+ while(1) {
+ key = htonu64(expiretime);
+ int retval = raxInsert(Timers,(unsigned char*)&key,sizeof(key),timer,NULL);
+ if (retval)
+ expiretime = key;
+ else
+ expiretime++;
+ }
+
+ /* We need to install the main event loop timer if it's not already
+ * installed, or we may need to refresh its period if we just installed
+ * a timer that will expire sooner than any other else. */
+ return key;
+}
+
+/* Stop a timer, returns REDISMODULE_OK if the timer was found, belonged to the
+ * calling module, and was stoped, otherwise REDISMODULE_ERR is returned.
+ * If not NULL, the data pointer is set to the value of the data argument when
+ * the timer was created. */
+int RM_StopTimer(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data) {
+ RedisModuleTimer *timer = raxFind(Timers,(unsigned char*)&id,sizeof(id));
+ if (timer == raxNotFound || timer->module != ctx->module)
+ return REDISMODULE_ERR;
+ if (data) *data = timer->data;
+ raxRemove(Timers,(unsigned char*)&id,sizeof(id),NULL);
+ zfree(timer);
+ return REDISMODULE_OK;
+}
+
+/* Obtain information about a timer: its remaining time before firing
+ * (in milliseconds), and the private data pointer associated with the timer.
+ * If the timer specified does not exist or belongs to a different module
+ * no information is returned and the function returns REDISMODULE_ERR, otherwise
+ * REDISMODULE_OK is returned. The argumnets remaining or data can be NULL if
+ * the caller does not need certain information. */
+int RM_GetTimerInfo(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data) {
+ RedisModuleTimer *timer = raxFind(Timers,(unsigned char*)&id,sizeof(id));
+ if (timer == raxNotFound || timer->module != ctx->module)
+ return REDISMODULE_ERR;
+ if (remaining) {
+ int64_t rem = ntohu64(id)-ustime();
+ if (rem < 0) rem = 0;
+ *remaining = rem;
+ }
+ if (data) *data = timer->data;
+ return REDISMODULE_OK;
+}
+
+/* --------------------------------------------------------------------------
* Modules API internals
* -------------------------------------------------------------------------- */
@@ -4074,6 +4164,9 @@ void moduleInitModulesSystem(void) {
anetNonBlock(NULL,server.module_blocked_pipe[0]);
anetNonBlock(NULL,server.module_blocked_pipe[1]);
+ /* Create the timers radix tree. */
+ Timers = raxNew();
+
/* Our thread-safe contexts GIL must start with already locked:
* it is just unlocked when it's safe. */
pthread_mutex_lock(&moduleGIL);
diff --git a/src/redismodule.h b/src/redismodule.h
index 345cd5e12..85e25c7e9 100644
--- a/src/redismodule.h
+++ b/src/redismodule.h
@@ -115,6 +115,12 @@
#define REDISMODULE_NOT_USED(V) ((void) V)
+/* This type represents a timer handle, and is returned when a timer is
+ * registered and used in order to invalidate a timer. It's just a 64 bit
+ * number, because this is how each timer is represented inside the radix tree
+ * of timers that are going to expire, sorted by expire time. */
+typedef uint64_t RedisModuleTimerID;
+
/* ------------------------- End of common defines ------------------------ */
#ifndef REDISMODULE_CORE