From 61a08ff7db6a10597988b1fd344b69de883e74b8 Mon Sep 17 00:00:00 2001 From: Joan Fontanals Martinez Date: Fri, 14 Nov 2025 11:00:21 +0100 Subject: [PATCH] option to add try lock for API --- src/module.c | 335 ++++++++++++++++++++++++++++------------------ src/redismodule.h | 9 +- 2 files changed, 212 insertions(+), 132 deletions(-) diff --git a/src/module.c b/src/module.c index 9371b4150..775154b34 100644 --- a/src/module.c +++ b/src/module.c @@ -147,7 +147,7 @@ struct RedisModuleCtx { gets called for clients blocked on keys. */ - /* Used if there is the REDISMODULE_CTX_KEYS_POS_REQUEST or + /* Used if there is the REDISMODULE_CTX_KEYS_POS_REQUEST or * REDISMODULE_CTX_CHANNEL_POS_REQUEST flag set. */ getKeysResult *keys_result; @@ -428,11 +428,11 @@ typedef struct RedisModuleUser { /* This is a structure used to export some meta-information such as dbid to the module. */ typedef struct RedisModuleKeyOptCtx { - struct redisObject *from_key, *to_key; /* Optional name of key processed, NULL when unknown. - In most cases, only 'from_key' is valid, but in callbacks + struct redisObject *from_key, *to_key; /* Optional name of key processed, NULL when unknown. + In most cases, only 'from_key' is valid, but in callbacks such as `copy2`, both 'from_key' and 'to_key' are valid. */ int from_dbid, to_dbid; /* The dbid of the key being processed, -1 when unknown. - In most cases, only 'from_dbid' is valid, but in callbacks such + In most cases, only 'from_dbid' is valid, but in callbacks such as `copy2`, 'from_dbid' and 'to_dbid' are both valid. */ } RedisModuleKeyOptCtx; @@ -455,8 +455,8 @@ struct ModuleConfig { sds name; /* Fullname of the config (as it appears in the config file) */ sds alias; /* Optional alias for the configuration. NULL if none exists */ - int unprefixedFlag; /* Indicates if the REDISMODULE_CONFIG_UNPREFIXED flag was set. - * If the configuration name was prefixed,during get_fn/set_fn + int unprefixedFlag; /* Indicates if the REDISMODULE_CONFIG_UNPREFIXED flag was set. + * If the configuration name was prefixed,during get_fn/set_fn * callbacks, it should be reported without the prefix */ void *privdata; /* Optional data passed into the module config callbacks */ @@ -1075,14 +1075,14 @@ int RM_IsChannelsPositionRequest(RedisModuleCtx *ctx) { * registration, the command implementation checks for this special call * using the RedisModule_IsChannelsPositionRequest() API and uses this * function in order to report the channels. - * + * * The supported flags are: * * REDISMODULE_CMD_CHANNEL_SUBSCRIBE: This command will subscribe to the channel. * * REDISMODULE_CMD_CHANNEL_UNSUBSCRIBE: This command will unsubscribe from this channel. * * REDISMODULE_CMD_CHANNEL_PUBLISH: This command will publish to this channel. - * * REDISMODULE_CMD_CHANNEL_PATTERN: Instead of acting on a specific channel, will act on any + * * REDISMODULE_CMD_CHANNEL_PATTERN: Instead of acting on a specific channel, will act on any * channel specified by the pattern. This is the same access - * used by the PSUBSCRIBE and PUNSUBSCRIBE commands available + * used by the PSUBSCRIBE and PUNSUBSCRIBE commands available * in Redis. Not intended to be used with PUBLISH permissions. * * The following is an example of how it could be used: @@ -1490,13 +1490,13 @@ int populateArgsStructure(struct redisCommandArg *args) { /* RedisModule_AddACLCategory can be used to add new ACL command categories. Category names * can only contain alphanumeric characters, underscores, or dashes. Categories can only be added - * during the RedisModule_OnLoad function. Once a category has been added, it can not be removed. + * during the RedisModule_OnLoad function. Once a category has been added, it can not be removed. * Any module can register a command to any added categories using RedisModule_SetCommandACLCategories. - * + * * Returns: - * - REDISMODULE_OK on successfully adding the new ACL category. + * - REDISMODULE_OK on successfully adding the new ACL category. * - REDISMODULE_ERR on failure. - * + * * On error the errno is set to: * - EINVAL if the name contains invalid characters. * - EBUSY if the category name already exists. @@ -1542,9 +1542,9 @@ int matchAclCategoryFlag(char *flag, int64_t *acl_categories_flags) { } /* Helper for RM_SetCommandACLCategories(). Turns a string representing acl category - * flags into the acl category flags used by Redis ACL which allows users to access + * flags into the acl category flags used by Redis ACL which allows users to access * the module commands by acl categories. - * + * * It returns the set of acl flags, or -1 if unknown flags are found. */ int64_t categoryFlagsFromString(char *aclflags) { int count, j; @@ -1565,12 +1565,12 @@ int64_t categoryFlagsFromString(char *aclflags) { /* RedisModule_SetCommandACLCategories can be used to set ACL categories to module * commands and subcommands. The set of ACL categories should be passed as * a space separated C string 'aclflags'. - * - * Example, the acl flags 'write slow' marks the command as part of the write and + * + * Example, the acl flags 'write slow' marks the command as part of the write and * slow ACL categories. - * + * * On success REDISMODULE_OK is returned. On error REDISMODULE_ERR is returned. - * + * * This function can only be called during the RedisModule_OnLoad function. If called * outside of this function, an error is returned. */ @@ -1802,7 +1802,7 @@ int RM_SetCommandACLCategories(RedisModuleCommand *command, const char *aclflags * * Other flags: * - * * `REDISMODULE_CMD_KEY_NOT_KEY`: The key is not actually a key, but + * * `REDISMODULE_CMD_KEY_NOT_KEY`: The key is not actually a key, but * should be routed in cluster mode as if it was a key. * * * `REDISMODULE_CMD_KEY_INCOMPLETE`: The keyspec might not point out all @@ -2383,7 +2383,7 @@ ustime_t RM_CachedMicroseconds(void) { * RM_BlockedClientMeasureTimeStart() and RM_BlockedClientMeasureTimeEnd() * to accumulate independent time intervals to the background duration. * This method always return REDISMODULE_OK. - * + * * This function is not thread safe, If used in module thread and blocked callback (possibly main thread) * simultaneously, it's recommended to protect them with lock owned by caller instead of GIL. */ int RM_BlockedClientMeasureTimeStart(RedisModuleBlockedClient *bc) { @@ -2396,7 +2396,7 @@ int RM_BlockedClientMeasureTimeStart(RedisModuleBlockedClient *bc) { * On success REDISMODULE_OK is returned. * This method only returns REDISMODULE_ERR if no start time was * previously defined ( meaning RM_BlockedClientMeasureTimeStart was not called ). - * + * * This function is not thread safe, If used in module thread and blocked callback (possibly main thread) * simultaneously, it's recommended to protect them with lock owned by caller instead of GIL. */ int RM_BlockedClientMeasureTimeEnd(RedisModuleBlockedClient *bc) { @@ -2510,7 +2510,7 @@ void RM_Yield(RedisModuleCtx *ctx, int flags, const char *busy_reply) { * * REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED: * See RM_SignalModifiedKey(). - * + * * REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD: * Setting this flag indicates module awareness of diskless async replication (repl-diskless-load=swapdb) * and that redis could be serving reads during replication instead of blocking with LOADING status. @@ -3246,9 +3246,9 @@ int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) { * * If the connected client is using RESP2, the reply will be converted to a flat * array. - * + * * Use RM_ReplySetMapLength() to set deferred length. - * + * * The function always returns REDISMODULE_OK. */ int RM_ReplyWithMap(RedisModuleCtx *ctx, long len) { return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_MAP); @@ -3265,7 +3265,7 @@ int RM_ReplyWithMap(RedisModuleCtx *ctx, long len) { * array type. * * Use RM_ReplySetSetLength() to set deferred length. - * + * * The function always returns REDISMODULE_OK. */ int RM_ReplyWithSet(RedisModuleCtx *ctx, long len) { return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_SET); @@ -3280,12 +3280,12 @@ int RM_ReplyWithSet(RedisModuleCtx *ctx, long len) { * See Reply APIs section for more details. * * Use RM_ReplySetAttributeLength() to set deferred length. - * + * * Not supported by RESP2 and will return REDISMODULE_ERR, otherwise * the function always returns REDISMODULE_OK. */ int RM_ReplyWithAttribute(RedisModuleCtx *ctx, long len) { if (ctx->client->resp == 2) return REDISMODULE_ERR; - + return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_ATTRIBUTE); } @@ -3529,7 +3529,7 @@ int RM_ReplyWithCallReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply) { * a string into a C buffer, and then calling the function * RedisModule_ReplyWithStringBuffer() with the buffer and length. * - * In RESP3 the string is tagged as a double, while in RESP2 it's just a plain string + * In RESP3 the string is tagged as a double, while in RESP2 it's just a plain string * that the user will have to parse. * * The function always returns REDISMODULE_OK. */ @@ -3543,7 +3543,7 @@ int RM_ReplyWithDouble(RedisModuleCtx *ctx, double d) { /* Reply with a RESP3 BigNumber type. * Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. * - * In RESP3, this is a string of length `len` that is tagged as a BigNumber, + * In RESP3, this is a string of length `len` that is tagged as a BigNumber, * however, it's up to the caller to ensure that it's a valid BigNumber. * In RESP2, this is just a plain bulk string response. * @@ -3699,7 +3699,7 @@ RedisModuleString *RM_GetClientUserNameById(RedisModuleCtx *ctx, uint64_t id) { errno = ENOENT; return NULL; } - + if (client->user == NULL) { errno = ENOTSUP; return NULL; @@ -4286,7 +4286,7 @@ int RM_SetExpire(RedisModuleKey *key, mstime_t expire) { return REDISMODULE_ERR; if (expire != REDISMODULE_NO_EXPIRE) { expire += commandTimeSnapshot(); - /* setExpire() might realloc kvobj */ + /* setExpire() might realloc kvobj */ key->kv = setExpire(key->ctx->client,key->db,key->key,expire); } else { removeExpire(key->db,key->key); @@ -4307,7 +4307,7 @@ mstime_t RM_GetAbsExpire(RedisModuleKey *key) { /* Set a new expire for the key. If the special expire * REDISMODULE_NO_EXPIRE is set, the expire is cancelled if there was * one (the same as the PERSIST command). - * + * * Note that the expire must be provided as a positive integer representing * the absolute Unix timestamp the key should have. * @@ -4858,7 +4858,7 @@ int moduleZsetAddFlagsFromCoreFlags(int flags) { * * REDISMODULE_ZADD_XX: Element must already exist. Do nothing otherwise. * REDISMODULE_ZADD_NX: Element must not exist. Do nothing otherwise. - * REDISMODULE_ZADD_GT: If element exists, new score must be greater than the current score. + * REDISMODULE_ZADD_GT: If element exists, new score must be greater than the current score. * Do nothing otherwise. Can optionally be combined with XX. * REDISMODULE_ZADD_LT: If element exists, new score must be less than the current score. * Do nothing otherwise. Can optionally be combined with XX. @@ -5503,12 +5503,12 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) { * expecting a RedisModuleString pointer to pointer, the function just * reports if the field exists or not and expects an integer pointer * as the second element of each pair. - * + * * REDISMODULE_HASH_EXPIRE_TIME: retrieves the expiration time of a field in the hash. * The function expects a `mstime_t` pointer as the second element of each pair. - * If the field does not exist or has no expiration, the value is set to + * If the field does not exist or has no expiration, the value is set to * `REDISMODULE_NO_EXPIRE`. This flag must not be used with `REDISMODULE_HASH_EXISTS`. - * + * * Example of REDISMODULE_HASH_CFIELDS: * * RedisModuleString *username, *hashedpass; @@ -5521,9 +5521,9 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) { * * Example of REDISMODULE_HASH_EXPIRE_TIME: * - * mstime_t hpExpireTime; + * mstime_t hpExpireTime; * RedisModule_HashGet(mykey,REDISMODULE_HASH_EXPIRE_TIME,"hp",&hpExpireTime,NULL); - * + * * The function returns REDISMODULE_OK on success and REDISMODULE_ERR if * the key is not a hash value. * @@ -5541,8 +5541,8 @@ int RM_HashGet(RedisModuleKey *key, int flags, ...) { hfeFlags = HFE_LAZY_ACCESS_EXPIRED; /* allow read also expired fields */ /* Verify flag HASH_EXISTS is not set together with HASH_EXPIRE_TIME */ - if ((flags & REDISMODULE_HASH_EXISTS) && (flags & REDISMODULE_HASH_EXPIRE_TIME)) - return REDISMODULE_ERR; + if ((flags & REDISMODULE_HASH_EXISTS) && (flags & REDISMODULE_HASH_EXPIRE_TIME)) + return REDISMODULE_ERR; va_start(ap, flags); while(1) { @@ -5567,7 +5567,7 @@ int RM_HashGet(RedisModuleKey *key, int flags, ...) { *existsptr = 0; } } else if (flags & REDISMODULE_HASH_EXPIRE_TIME) { - mstime_t *expireptr = va_arg(ap,mstime_t*); + mstime_t *expireptr = va_arg(ap,mstime_t*); *expireptr = REDISMODULE_NO_EXPIRE; if (key->kv) { uint64_t expireTime = 0; @@ -5604,7 +5604,7 @@ int RM_HashGet(RedisModuleKey *key, int flags, ...) { /** * Retrieves the minimum expiration time of fields in a hash. - * + * * Return: * - The minimum expiration time (in milliseconds) of the hash fields if at * least one field has an expiration set. @@ -5614,7 +5614,7 @@ int RM_HashGet(RedisModuleKey *key, int flags, ...) { mstime_t RM_HashFieldMinExpire(RedisModuleKey *key) { if ((!key->kv) || (key->kv->type != OBJ_HASH)) return REDISMODULE_NO_EXPIRE; - + mstime_t min = hashTypeGetMinExpire(key->kv, 1); return (min == EB_EXPIRE_TIME_INVALID) ? REDISMODULE_NO_EXPIRE : min; } @@ -7032,7 +7032,7 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj } else { newval = mt->copy(fromkey, tokey, mv->value); } - + if (!newval) { addReplyError(c, "module key failed to copy"); return NULL; @@ -7082,7 +7082,7 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj * .unlink = myType_UnlinkCallBack, * .copy = myType_CopyCallback, * .defrag = myType_DefragCallback - * + * * // Enhanced optional fields * .mem_usage2 = myType_MemUsageCallBack2, * .free_effort2 = myType_FreeEffortCallBack2, @@ -7101,11 +7101,11 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj * Similar to aux_save, returns REDISMODULE_OK on success, and ERR otherwise. * * **free_effort**: A callback function pointer that used to determine whether the module's * memory needs to be lazy reclaimed. The module should return the complexity involved by - * freeing the value. for example: how many pointers are gonna be freed. Note that if it + * freeing the value. for example: how many pointers are gonna be freed. Note that if it * returns 0, we'll always do an async free. - * * **unlink**: A callback function pointer that used to notifies the module that the key has - * been removed from the DB by redis, and may soon be freed by a background thread. Note that - * it won't be called on FLUSHALL/FLUSHDB (both sync and async), and the module can use the + * * **unlink**: A callback function pointer that used to notifies the module that the key has + * been removed from the DB by redis, and may soon be freed by a background thread. Note that + * it won't be called on FLUSHALL/FLUSHDB (both sync and async), and the module can use the * RedisModuleEvent_FlushDB to hook into that. * * **copy**: A callback function pointer that is used to make a copy of the specified key. * The module is expected to perform a deep copy of the specified value and return it. @@ -7113,7 +7113,7 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj * A NULL return value is considered an error and the copy operation fails. * Note: if the target key exists and is being overwritten, the copy callback will be * called first, followed by a free callback to the value that is being replaced. - * + * * * **defrag**: A callback function pointer that is used to request the module to defrag * a key. The module should then iterate pointers and call the relevant RM_Defrag*() * functions to defragment pointers or complex types. The module should continue @@ -7141,7 +7141,7 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj * * **aux_save2**: Similar to `aux_save`, but with small semantic change, if the module * saves nothing on this callback then no data about this aux field will be written to the * RDB and it will be possible to load the RDB even if the module is not loaded. - * + * * Note: the module name "AAAAAAAAA" is reserved and produces an error, it * happens to be pretty lame as well. * @@ -7712,7 +7712,7 @@ void *RM_LoadDataTypeFromStringEncver(const RedisModuleString *str, const module } /* Similar to RM_LoadDataTypeFromStringEncver, original version of the API, kept - * for backward compatibility. + * for backward compatibility. */ void *RM_LoadDataTypeFromString(const RedisModuleString *str, const moduleType *mt) { return RM_LoadDataTypeFromStringEncver(str, mt, 0); @@ -8660,7 +8660,7 @@ int moduleBlockedClientMayTimeout(client *c) { /* Called when our client timed out. After this function unblockClient() * is called, and it will invalidate the blocked client. So this function * does not need to do any cleanup. Eventually the module will call the - * API to unblock the client and the memory will be released. + * API to unblock the client and the memory will be released. * * This function should only be called from the main thread, we must handle the unblocking * of the client synchronously. This ensures that we can reply to the client before @@ -8843,6 +8843,24 @@ int RM_ThreadSafeContextTryLock(RedisModuleCtx *ctx) { return REDISMODULE_OK; } +/* Similar to RM_ThreadSafeContextTryLock but this function + * would block only for the specified amount of time if the server lock is already acquired. + * + * If successful (lock acquired) REDISMODULE_OK is returned, + * otherwise REDISMODULE_ERR is returned and errno is set + * accordingly. */ +int RM_ThreadSafeContextTryLockFor(RedisModuleCtx *ctx, long long timeout_ns) { + UNUSED(ctx); + + int res = moduleTryAcquireGILFor(timeout_ns); + if(res != 0) { + errno = res; + return REDISMODULE_ERR; + } + moduleGILAfterLock(); + return REDISMODULE_OK; +} + void moduleGILBeforeUnlock(void) { /* We should never get here if we already inside a module * code block which already opened a context, except @@ -8870,6 +8888,67 @@ int moduleTryAcquireGIL(void) { return pthread_mutex_trylock(&moduleGIL); } +#define NANOSEC_PER_SECOND 1000000000 + +static inline void set_timeout(struct timespec *ts, long long timeout_ns) { + clock_gettime(CLOCK_REALTIME, ts); + // Assumes TIMEOUT_NANOSECONDS will not exceed NANOSEC_PER_SECOND, + // so we are only off by one second maximum. + ts->tv_nsec += timeout_ns; + if (ts->tv_nsec >= NANOSEC_PER_SECOND) { + ts->tv_nsec -= NANOSEC_PER_SECOND; + ts->tv_sec += 1; + } +} + +int moduleTryAcquireGILFor(long long timeout_ns) { +#ifdef __APPLE__ + /* macOS doesn't support pthread_mutex_timedlock, so we implement it + * using pthread_mutex_trylock with a loop and nanosleep. */ + + /* Calculate the absolute timeout */ + struct timespec timeout; + set_timeout(&timeout, timeout_ns); + /* Try to acquire the lock immediately */ + int res = pthread_mutex_trylock(&moduleGIL); + if (res == 0) return 0; /* Success */ + if (res != EBUSY) return res; /* Error other than busy */ + + struct timespec sleep_time, now; + /* Sleep time: 1ms initially, will be adjusted if timeout is near */ + sleep_time.tv_sec = 0; + sleep_time.tv_nsec = 1000000; /* 1ms */ + + /* Keep trying until timeout */ + while (1) { + nanosleep(&sleep_time, NULL); + + res = pthread_mutex_trylock(&moduleGIL); + if (res == 0) return 0; /* Success */ + if (res != EBUSY) return res; /* Error other than busy */ + + /* Check if we've exceeded the timeout */ + clock_gettime(CLOCK_REALTIME, &now); + if (now.tv_sec > timeout.tv_sec || + (now.tv_sec == timeout.tv_sec && now.tv_nsec >= timeout.tv_nsec)) { + return ETIMEDOUT; + } + + /* Adjust sleep time if we're close to timeout */ + long long remaining_ns = (timeout.tv_sec - now.tv_sec) * NANOSEC_PER_SECOND + + (timeout.tv_nsec - now.tv_nsec); + if (remaining_ns < sleep_time.tv_nsec) { + sleep_time.tv_nsec = remaining_ns; + } + } +#else + struct timespec timeout; + set_timeout(&timeout, timeout_ns); + + return pthread_mutex_timedlock(&moduleGIL, &timeout); +#endif +} + void moduleReleaseGIL(void) { pthread_mutex_unlock(&moduleGIL); } @@ -10126,13 +10205,13 @@ int RM_ACLCheckCommandPermissions(RedisModuleUser *user, RedisModuleString **arg * keyspec for logical operations. These flags are documented in RedisModule_SetCommandInfo as * the REDISMODULE_CMD_KEY_ACCESS, REDISMODULE_CMD_KEY_UPDATE, REDISMODULE_CMD_KEY_INSERT, * and REDISMODULE_CMD_KEY_DELETE flags. - * + * * If no flags are supplied, the user is still required to have some access to the key for * this command to return successfully. * * If the user is able to access the key then REDISMODULE_OK is returned, otherwise * REDISMODULE_ERR is returned and errno is set to one of the following values: - * + * * * EINVAL: The provided flags are invalid. * * EACCESS: The user does not have permission to access the key. */ @@ -10156,18 +10235,18 @@ int RM_ACLCheckKeyPermissions(RedisModuleUser *user, RedisModuleString *key, int return REDISMODULE_OK; } -/* Check if the user can access keys matching the given key prefix according to the ACLs - * attached to the user and the flags representing key access. The flags are the same that - * are used in the keyspec for logical operations. These flags are documented in - * RedisModule_SetCommandInfo as the REDISMODULE_CMD_KEY_ACCESS, +/* Check if the user can access keys matching the given key prefix according to the ACLs + * attached to the user and the flags representing key access. The flags are the same that + * are used in the keyspec for logical operations. These flags are documented in + * RedisModule_SetCommandInfo as the REDISMODULE_CMD_KEY_ACCESS, * REDISMODULE_CMD_KEY_UPDATE, REDISMODULE_CMD_KEY_INSERT, and REDISMODULE_CMD_KEY_DELETE flags. - * - * If no flags are supplied, the user is still required to have some access to keys matching + * + * If no flags are supplied, the user is still required to have some access to keys matching * the prefix for this command to return successfully. * * If the user is able to access keys matching the prefix, then REDISMODULE_OK is returned. * Otherwise, REDISMODULE_ERR is returned and errno is set to one of the following values: - * + * * * EINVAL: The provided flags are invalid. * * EACCES: The user does not have permission to access keys matching the prefix. */ @@ -10201,9 +10280,9 @@ int RM_ACLCheckKeyPrefixPermissions(RedisModuleUser *user, RedisModuleString *pr * * If the user is able to access the pubsub channel then REDISMODULE_OK is returned, otherwise * REDISMODULE_ERR is returned and errno is set to one of the following values: - * + * * * EINVAL: The provided flags are invalid. - * * EACCESS: The user does not have permission to access the pubsub channel. + * * EACCESS: The user does not have permission to access the pubsub channel. */ int RM_ACLCheckChannelPermissions(RedisModuleUser *user, RedisModuleString *ch, int flags) { const int allow_mask = (REDISMODULE_CMD_CHANNEL_PUBLISH @@ -10361,15 +10440,15 @@ int RM_DeauthenticateAndCloseClient(RedisModuleCtx *ctx, uint64_t client_id) { return REDISMODULE_OK; } -/* Redact the client command argument specified at the given position. Redacted arguments +/* Redact the client command argument specified at the given position. Redacted arguments * are obfuscated in user facing commands such as SLOWLOG or MONITOR, as well as * never being written to server logs. This command may be called multiple times on the * same position. - * - * Note that the command name, position 0, can not be redacted. - * - * Returns REDISMODULE_OK if the argument was redacted and REDISMODULE_ERR if there - * was an invalid parameter passed in or the position is outside the client + * + * Note that the command name, position 0, can not be redacted. + * + * Returns REDISMODULE_OK if the argument was redacted and REDISMODULE_ERR if there + * was an invalid parameter passed in or the position is outside the client * argument range. */ int RM_RedactClientCommandArgument(RedisModuleCtx *ctx, int pos) { if (!ctx || !ctx->client || pos <= 0 || ctx->client->argc <= pos) { @@ -12044,7 +12123,7 @@ static uint64_t moduleEventVersions[] = { * int32_t dbnum_second; // Swap Db second dbnum * * * RedisModuleEvent_ReplBackup - * + * * WARNING: Replication Backup events are deprecated since Redis 7.0 and are never fired. * See RedisModuleEvent_ReplAsyncLoad for understanding how Async Replication Loading events * are now triggered when repl-diskless-load is set to swapdb. @@ -12059,7 +12138,7 @@ static uint64_t moduleEventVersions[] = { * * `REDISMODULE_SUBEVENT_REPL_BACKUP_CREATE` * * `REDISMODULE_SUBEVENT_REPL_BACKUP_RESTORE` * * `REDISMODULE_SUBEVENT_REPL_BACKUP_DISCARD` - * + * * * RedisModuleEvent_ReplAsyncLoad * * Called when repl-diskless-load config is set to swapdb and a replication with a master of same @@ -12101,7 +12180,7 @@ static uint64_t moduleEventVersions[] = { * structure with the following fields: * * const char **config_names; // An array of C string pointers containing the - * // name of each modified configuration item + * // name of each modified configuration item * uint32_t num_changes; // The number of elements in the config_names array * * * RedisModule_Event_Key @@ -12255,7 +12334,7 @@ int RM_IsSubEventSupported(RedisModuleEvent event, int64_t subevent) { case REDISMODULE_EVENT_EVENTLOOP: return subevent < _REDISMODULE_SUBEVENT_EVENTLOOP_NEXT; case REDISMODULE_EVENT_CONFIG: - return subevent < _REDISMODULE_SUBEVENT_CONFIG_NEXT; + return subevent < _REDISMODULE_SUBEVENT_CONFIG_NEXT; case REDISMODULE_EVENT_KEY: return subevent < _REDISMODULE_SUBEVENT_KEY_NEXT; case REDISMODULE_EVENT_CLUSTER_SLOT_MIGRATION: @@ -12437,7 +12516,7 @@ void moduleNotifyKeyUnlink(robj *key, kvobj *kv, int dbid, int flags) { server.allow_access_trimmed--; } -/* Return the free_effort of the module, it will automatically choose to call +/* Return the free_effort of the module, it will automatically choose to call * `free_effort` or `free_effort2`, and the default return value is 1. * value of 0 means very high effort (always asynchronous freeing). */ size_t moduleGetFreeEffort(robj *key, robj *val, int dbid) { @@ -12450,12 +12529,12 @@ size_t moduleGetFreeEffort(robj *key, robj *val, int dbid) { effort = mt->free_effort2(&ctx,mv->value); } else if (mt->free_effort != NULL) { effort = mt->free_effort(key,mv->value); - } + } return effort; } -/* Return the memory usage of the module, it will automatically choose to call +/* Return the memory usage of the module, it will automatically choose to call * `mem_usage` or `mem_usage2`, and the default return value is 0. */ size_t moduleGetMemUsage(robj *key, robj *val, size_t sample_size, int dbid) { moduleValue *mv = val->ptr; @@ -12467,7 +12546,7 @@ size_t moduleGetMemUsage(robj *key, robj *val, size_t sample_size, int dbid) { size = mt->mem_usage2(&ctx, mv->value, sample_size); } else if (mt->mem_usage != NULL) { size = mt->mem_usage(mv->value); - } + } return size; } @@ -12938,7 +13017,7 @@ int moduleOnLoad(int (*onload)(void *, void **, int), const char *path, void *ha /* Unload the module registered with the specified name. On success * C_OK is returned, otherwise C_ERR is returned and errmsg is set * with an appropriate message. - * Only forcefully unload this module, passing forced_unload != 0, + * Only forcefully unload this module, passing forced_unload != 0, * if it is certain that it has not yet been in use (e.g., immediate * unload on failed load). */ int moduleUnload(sds name, const char **errmsg, int forced_unload) { @@ -13121,7 +13200,7 @@ sds genModulesInfoString(sds info) { /* -------------------------------------------------------------------------- * Module Configurations API internals * -------------------------------------------------------------------------- */ - + /* Check if the configuration name is already registered */ int isModuleConfigNameRegistered(RedisModule *module, const char *name) { listNode *match = listSearchKey(module->module_configs, (void *) name); @@ -13175,11 +13254,11 @@ int moduleVerifyResourceName(const char *name) { return REDISMODULE_OK; } -/* Verify unprefixed name config might be a single "" or in the form - * "|". Unlike moduleVerifyResourceName(), unprefixed name config - * allows a single dot in the name or alias. - * - * delim - Updates to point to "|" if it exists, NULL otherwise. +/* Verify unprefixed name config might be a single "" or in the form + * "|". Unlike moduleVerifyResourceName(), unprefixed name config + * allows a single dot in the name or alias. + * + * delim - Updates to point to "|" if it exists, NULL otherwise. */ int moduleVerifyUnprefixedName(const char *nameAlias, const char **delim) { if (nameAlias[0] == '\0') @@ -13190,7 +13269,7 @@ int moduleVerifyUnprefixedName(const char *nameAlias, const char **delim) { for (size_t i = 0; nameAlias[i] != '\0'; i++) { char ch = nameAlias[i]; - + if (((*delim) == NULL) && (ch == '|')) { /* Handle single separator between name and alias */ if (!lname) { @@ -13205,7 +13284,7 @@ int moduleVerifyUnprefixedName(const char *nameAlias, const char **delim) { ++lname; } else if (ch == '.') { /* Allow only one dot per section (name or alias) */ - if (++dot_count > 1) { + if (++dot_count > 1) { serverLog(LL_WARNING, "Invalid character sequence in Module configuration name or alias: %s", nameAlias); return REDISMODULE_ERR; } @@ -13214,7 +13293,7 @@ int moduleVerifyUnprefixedName(const char *nameAlias, const char **delim) { return REDISMODULE_ERR; } } - + if (!lname) { serverLog(LL_WARNING, "Module configuration name or alias is empty : %s", nameAlias); return REDISMODULE_ERR; @@ -13223,7 +13302,7 @@ int moduleVerifyUnprefixedName(const char *nameAlias, const char **delim) { return REDISMODULE_OK; } -/* This is a series of set functions for each type that act as dispatchers for +/* This is a series of set functions for each type that act as dispatchers for * config.c to call module set callbacks. */ #define CONFIG_ERR_SIZE 256 static char configerr[CONFIG_ERR_SIZE]; @@ -13235,8 +13314,8 @@ static void propagateErrorString(RedisModuleString *err_in, const char **err) { } } -/* If configuration was originally registered with indication to prefix the name, - * return the name without the prefix by skipping prefix ".". +/* If configuration was originally registered with indication to prefix the name, + * return the name without the prefix by skipping prefix ".". * Otherwise, return the stored name as is. */ static char *getRegisteredConfigName(ModuleConfig *config) { if (config->unprefixedFlag) @@ -13244,7 +13323,7 @@ static char *getRegisteredConfigName(ModuleConfig *config) { /* For prefixed configuration, find the '.' indicating the end of the prefix */ char *endOfPrefix = strchr(config->name, '.'); - serverAssert(endOfPrefix != NULL); + serverAssert(endOfPrefix != NULL); return endOfPrefix + 1; } @@ -13260,7 +13339,7 @@ int setModuleBoolConfig(ModuleConfig *config, int val, const char **err) { int setModuleStringConfig(ModuleConfig *config, sds strval, const char **err) { RedisModuleString *error = NULL; RedisModuleString *new = createStringObject(strval, sdslen(strval)); - + char *rname = getRegisteredConfigName(config); int return_code = config->set_fn.set_string(rname, new, config->privdata, &error); propagateErrorString(error, err); @@ -13283,7 +13362,7 @@ int setModuleNumericConfig(ModuleConfig *config, long long val, const char **err return return_code == REDISMODULE_OK ? 1 : 0; } -/* This is a series of get functions for each type that act as dispatchers for +/* This is a series of get functions for each type that act as dispatchers for * config.c to call module set callbacks. */ int getModuleBoolConfig(ModuleConfig *module_config) { char *rname = getRegisteredConfigName(module_config); @@ -13422,19 +13501,19 @@ int moduleConfigApplyConfig(list *module_configs, const char **err, const char * * -------------------------------------------------------------------------- */ /* Resolve config name and create a module config object */ -ModuleConfig *createModuleConfig(const char *name, RedisModuleConfigApplyFunc apply_fn, - void *privdata, RedisModule *module, unsigned int flags) +ModuleConfig *createModuleConfig(const char *name, RedisModuleConfigApplyFunc apply_fn, + void *privdata, RedisModule *module, unsigned int flags) { sds cname, alias = NULL; /* Determine the configuration name: * - If the unprefixed flag is set, the "." prefix is omitted. * - An optional alias can be specified using "|". - * + * * Examples: * - Unprefixed: "bf.initial_size" or "bf-initial-size|bf.initial_size". * - Prefixed: "initial_size" becomes ".initial_size". - */ + */ if (flags & REDISMODULE_CONFIG_UNPREFIXED) { const char *delim = strchr(name, '|'); cname = sdsnew(name); @@ -13446,7 +13525,7 @@ ModuleConfig *createModuleConfig(const char *name, RedisModuleConfigApplyFunc ap /* Add the module name prefix */ cname = sdscatfmt(sdsempty(), "%s.%s", module->name, name); } - + ModuleConfig *new_config = zmalloc(sizeof(ModuleConfig)); new_config->unprefixedFlag = flags & REDISMODULE_CONFIG_UNPREFIXED; new_config->name = cname; @@ -13458,7 +13537,7 @@ ModuleConfig *createModuleConfig(const char *name, RedisModuleConfigApplyFunc ap } /* Verify the configuration name and check for duplicates. - * + * * - If the configuration is flagged as unprefixed, it checks for duplicate * names and optional aliases in the format |. * - If the configuration is prefixed, it ensures the name is unique with @@ -13473,22 +13552,22 @@ int moduleConfigValidityCheck(RedisModule *module, const char *name, unsigned in errno = EINVAL; return REDISMODULE_ERR; } - - int isdup = 0; + + int isdup = 0; if (flags & REDISMODULE_CONFIG_UNPREFIXED) { const char *delim = NULL; /* Pointer to the '|' delimiter in | */ if (moduleVerifyUnprefixedName(name, &delim)){ errno = EINVAL; return REDISMODULE_ERR; } - - if (delim) { + + if (delim) { /* Temporary split the "|" for the check */ int count; sds *ar = sdssplitlen(name, strlen(name), "|", 1, &count); serverAssert(count == 2); /* Already validated */ - isdup = configExists(ar[0]) || - configExists(ar[1]) || + isdup = configExists(ar[0]) || + configExists(ar[1]) || (sdscmp(ar[0], ar[1]) == 0); sdsfreesplitres(ar, count); } else { @@ -13506,7 +13585,7 @@ int moduleConfigValidityCheck(RedisModule *module, const char *name, unsigned in isdup = configExists(fullname); sdsfree(fullname); } - + if (isdup) { serverLog(LL_WARNING, "Configuration by the name: %s already registered", name); errno = EALREADY; @@ -13617,19 +13696,19 @@ int RM_RegisterStringConfig(RedisModuleCtx *ctx, const char *name, const char *d if (moduleConfigValidityCheck(module, name, flags, NUMERIC_CONFIG)) { return REDISMODULE_ERR; } - + ModuleConfig *mc = createModuleConfig(name, applyfn, privdata, module, flags); mc->get_fn.get_string = getfn; mc->set_fn.set_string = setfn; listAddNodeTail(module->module_configs, mc); unsigned int cflags = maskModuleConfigFlags(flags); - addModuleStringConfig(sdsdup(mc->name), (mc->alias) ? sdsdup(mc->alias) : NULL, + addModuleStringConfig(sdsdup(mc->name), (mc->alias) ? sdsdup(mc->alias) : NULL, cflags, mc, default_val ? sdsnew(default_val) : NULL); return REDISMODULE_OK; } -/* Create a bool config that server clients can interact with via the - * `CONFIG SET`, `CONFIG GET`, and `CONFIG REWRITE` commands. See +/* Create a bool config that server clients can interact with via the + * `CONFIG SET`, `CONFIG GET`, and `CONFIG REWRITE` commands. See * RedisModule_RegisterStringConfig for detailed information about configs. */ int RM_RegisterBoolConfig(RedisModuleCtx *ctx, const char *name, int default_val, unsigned int flags, RedisModuleConfigGetBoolFunc getfn, RedisModuleConfigSetBoolFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) { RedisModule *module = ctx->module; @@ -13641,15 +13720,15 @@ int RM_RegisterBoolConfig(RedisModuleCtx *ctx, const char *name, int default_val mc->set_fn.set_bool = setfn; listAddNodeTail(module->module_configs, mc); unsigned int cflags = maskModuleConfigFlags(flags); - addModuleBoolConfig(sdsdup(mc->name), (mc->alias) ? sdsdup(mc->alias) : NULL, + addModuleBoolConfig(sdsdup(mc->name), (mc->alias) ? sdsdup(mc->alias) : NULL, cflags, mc, default_val); return REDISMODULE_OK; } -/* - * Create an enum config that server clients can interact with via the - * `CONFIG SET`, `CONFIG GET`, and `CONFIG REWRITE` commands. - * Enum configs are a set of string tokens to corresponding integer values, where +/* + * Create an enum config that server clients can interact with via the + * `CONFIG SET`, `CONFIG GET`, and `CONFIG REWRITE` commands. + * Enum configs are a set of string tokens to corresponding integer values, where * the string value is exposed to Redis clients but the value passed Redis and the * module is the integer value. These values are defined in enum_values, an array * of null-terminated c strings, and int_vals, an array of enum values who has an @@ -13662,7 +13741,7 @@ int RM_RegisterBoolConfig(RedisModuleCtx *ctx, const char *name, int default_val * int getEnumConfigCommand(const char *name, void *privdata) { * return enum_val; * } - * + * * int setEnumConfigCommand(const char *name, int val, void *privdata, const char **err) { * enum_val = val; * return REDISMODULE_OK; @@ -13693,14 +13772,14 @@ int RM_RegisterEnumConfig(RedisModuleCtx *ctx, const char *name, int default_val listAddNodeTail(module->module_configs, mc); unsigned int cflags = maskModuleConfigFlags(flags) | maskModuleEnumConfigFlags(flags); - addModuleEnumConfig(sdsdup(mc->name), (mc->alias) ? sdsdup(mc->alias) : NULL, + addModuleEnumConfig(sdsdup(mc->name), (mc->alias) ? sdsdup(mc->alias) : NULL, cflags, mc, default_val, enum_vals, num_enum_vals); return REDISMODULE_OK; } /* - * Create an integer config that server clients can interact with via the - * `CONFIG SET`, `CONFIG GET`, and `CONFIG REWRITE` commands. See + * Create an integer config that server clients can interact with via the + * `CONFIG SET`, `CONFIG GET`, and `CONFIG REWRITE` commands. See * RedisModule_RegisterStringConfig for detailed information about configs. */ int RM_RegisterNumericConfig(RedisModuleCtx *ctx, const char *name, long long default_val, unsigned int flags, long long min, long long max, RedisModuleConfigGetNumericFunc getfn, RedisModuleConfigSetNumericFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) { RedisModule *module = ctx->module; @@ -13714,7 +13793,7 @@ int RM_RegisterNumericConfig(RedisModuleCtx *ctx, const char *name, long long de unsigned int numeric_flags = maskModuleNumericConfigFlags(flags); unsigned int cflags = maskModuleConfigFlags(flags); - addModuleNumericConfig(sdsdup(mc->name), (mc->alias) ? sdsdup(mc->alias) : NULL, + addModuleNumericConfig(sdsdup(mc->name), (mc->alias) ? sdsdup(mc->alias) : NULL, cflags, mc, default_val, numeric_flags, min, max); return REDISMODULE_OK; } @@ -14222,7 +14301,7 @@ NULL argc = c->argc - 3; argv = &c->argv[3]; } - /* If this is a loadex command we want to populate server.module_configs_queue with + /* If this is a loadex command we want to populate server.module_configs_queue with * sds NAME VALUE pairs. We also want to increment argv to just after ARGS, if supplied. */ if (parseLoadexArguments((RedisModuleString ***) &argv, &argc) == REDISMODULE_OK && moduleLoad(c->argv[2]->ptr, (void **)argv, argc, 1) == C_OK) @@ -14627,8 +14706,8 @@ void *RM_DefragAlloc(RedisModuleDefragCtx *ctx, void *ptr) { * owner. For such usecase RM_DefragAlloc is enough. But on some usecases the user * might want to replace a pointer with multiple owners in different keys. * In such case, an in place replacement can not work because the other key still - * keep a pointer to the old value. - * + * keep a pointer to the old value. + * * RM_DefragAllocRaw and RM_DefragFreeRaw allows to control when the memory * for defrag purposes will be allocated and when it will be freed, * allow to support more complex defrag usecases. */ @@ -14638,7 +14717,7 @@ void *RM_DefragAllocRaw(RedisModuleDefragCtx *ctx, size_t size) { } /* Free memory for defrag purposes - * + * * See RM_DefragAllocRaw for more information. */ void RM_DefragFreeRaw(RedisModuleDefragCtx *ctx, void *ptr) { UNUSED(ctx); @@ -14812,7 +14891,7 @@ int moduleDefragValue(robj *key, robj *value, int dbid) { /* Call registered module API defrag start functions */ void moduleDefragStart(void) { - dictForEach(modules, struct RedisModule, module, + dictForEach(modules, struct RedisModule, module, if (module->defrag_start_cb) { RedisModuleDefragCtx defrag_ctx = INIT_MODULE_DEFRAG_CTX(0, NULL, NULL, -1); module->defrag_start_cb(&defrag_ctx); @@ -14822,7 +14901,7 @@ void moduleDefragStart(void) { /* Call registered module API defrag end functions */ void moduleDefragEnd(void) { - dictForEach(modules, struct RedisModule, module, + dictForEach(modules, struct RedisModule, module, if (module->defrag_end_cb) { RedisModuleDefragCtx defrag_ctx = INIT_MODULE_DEFRAG_CTX(0, NULL, NULL, -1); module->defrag_end_cb(&defrag_ctx); diff --git a/src/redismodule.h b/src/redismodule.h index ed8cfca7d..200915983 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -41,7 +41,7 @@ typedef long long ustime_t; /* API versions. */ #define REDISMODULE_APIVER_1 1 -/* Version of the RedisModuleTypeMethods structure. Once the RedisModuleTypeMethods +/* Version of the RedisModuleTypeMethods structure. Once the RedisModuleTypeMethods * structure is changed, this version number needs to be changed synchronistically. */ #define REDISMODULE_TYPE_METHOD_VERSION 5 @@ -121,7 +121,7 @@ typedef long long ustime_t; #define REDISMODULE_HASH_CFIELDS (1<<2) #define REDISMODULE_HASH_EXISTS (1<<3) #define REDISMODULE_HASH_COUNT_ALL (1<<4) -#define REDISMODULE_HASH_EXPIRE_TIME (1<<5) +#define REDISMODULE_HASH_EXPIRE_TIME (1<<5) #define REDISMODULE_CONFIG_DEFAULT 0 /* This is the default for a module config. */ #define REDISMODULE_CONFIG_IMMUTABLE (1ULL<<0) /* Can this value only be set at startup? */ @@ -599,7 +599,7 @@ static const RedisModuleEvent /* Deprecated since Redis 7.0, not used anymore. */ __attribute__ ((deprecated)) RedisModuleEvent_ReplBackup = { - REDISMODULE_EVENT_REPL_BACKUP, + REDISMODULE_EVENT_REPL_BACKUP, 1 }, RedisModuleEvent_ReplAsyncLoad = { @@ -1313,6 +1313,7 @@ REDISMODULE_API RedisModuleCtx * (*RedisModule_GetDetachedThreadSafeContext)(Red REDISMODULE_API void (*RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_ThreadSafeContextTryLock)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ThreadSafeContextTryLockFor)(RedisModuleCtx *ctx, long long timeout_ns) REDISMODULE_ATTR; REDISMODULE_API void (*RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb) REDISMODULE_ATTR; REDISMODULE_API int (*RedisModule_UnsubscribeFromKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb) REDISMODULE_ATTR; @@ -1763,7 +1764,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(GetModuleUserFromUserName); REDISMODULE_GET_API(ACLCheckCommandPermissions); REDISMODULE_GET_API(ACLCheckKeyPermissions); - REDISMODULE_GET_API(ACLCheckKeyPrefixPermissions); + REDISMODULE_GET_API(ACLCheckKeyPrefixPermissions); REDISMODULE_GET_API(ACLCheckChannelPermissions); REDISMODULE_GET_API(ACLAddLogEntry); REDISMODULE_GET_API(ACLAddLogEntryByUserName);