From c01b72f30dce0c0a85da4d51d3d66073f7d7bdb5 Mon Sep 17 00:00:00 2001 From: guybe7 Date: Mon, 2 Nov 2020 17:18:42 +0100 Subject: [PATCH] Modules: Improve timer accuracy (#7987) The bug occurs when 'callback' re-registers itself to a point in the future and the execution time in non-negligible: 'now' refers to time BEFORE callback was executed and is used to calculate 'next_period'. We must get the actual current time when calculating 'next_period' (cherry picked from commit 9cbdc8dcdbaf96869251dd9728c0876adf1b2492) --- src/module.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/module.c b/src/module.c index 3c96c601b..ae7fa22b2 100644 --- a/src/module.c +++ b/src/module.c @@ -5441,7 +5441,10 @@ int moduleTimerHandler(struct aeEventLoop *eventLoop, long long id, void *client raxRemove(Timers,(unsigned char*)ri.key,ri.key_len,NULL); zfree(timer); } else { - next_period = (expiretime-now)/1000; /* Scale to milliseconds. */ + /* We call ustime() again instead of using the cached 'now' so that + * 'next_period' isn't affected by the time it took to execute + * previous calls to 'callback. */ + next_period = (expiretime-ustime())/1000; /* Scale to milliseconds. */ break; } } @@ -5454,7 +5457,16 @@ int moduleTimerHandler(struct aeEventLoop *eventLoop, long long id, void *client /* 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. */ + * used to get information from the timer or to stop it before it fires. + * Note that for the common use case of a repeating timer (Re-registration + * of the timer inside the RedisModuleTimerProc callback) it matters when + * this API is called: + * If it is called at the beginning of 'callback' it means + * the event will triggered every 'period'. + * If it is called at the end of 'callback' it means + * there will 'period' milliseconds gaps between events. + * (If the time it takes to execute 'callback' is negligible the two + * statements above mean the same) */ RedisModuleTimerID RM_CreateTimer(RedisModuleCtx *ctx, mstime_t period, RedisModuleTimerProc callback, void *data) { RedisModuleTimer *timer = zmalloc(sizeof(*timer)); timer->module = ctx->module;