From 2de3a42ad85c0e1e47dbed0a728947f6a5faad8d Mon Sep 17 00:00:00 2001 From: bentotten <59932872+bentotten@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:48:19 -0800 Subject: [PATCH 1/7] When one shard, sole primary node marks potentially failed replica as FAIL instead of PFAIL (#12824) Fixes issue where a single primary cannot mark a replica as failed in a single-shard cluster. Signed-off-by: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com> --- src/cluster_legacy.c | 6409 ++++++++++++++++++++++++ tests/support/cluster_util.tcl | 27 + tests/test_helper.tcl | 1 + tests/unit/cluster/failure-marking.tcl | 53 + 4 files changed, 6490 insertions(+) create mode 100644 src/cluster_legacy.c create mode 100644 tests/unit/cluster/failure-marking.tcl diff --git a/src/cluster_legacy.c b/src/cluster_legacy.c new file mode 100644 index 0000000000..db25461f5c --- /dev/null +++ b/src/cluster_legacy.c @@ -0,0 +1,6409 @@ +/* + * Copyright (c) 2009-2012, 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. + */ + +/* + * cluster_legacy.c contains the implementation of the cluster API that is + * specific to the standard, Redis cluster-bus based clustering mechanism. + */ + +#include "server.h" +#include "cluster.h" +#include "cluster_legacy.h" +#include "endianconv.h" +#include "connection.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* A global reference to myself is handy to make code more clear. + * Myself always points to server.cluster->myself, that is, the clusterNode + * that represents this node. */ +clusterNode *myself = NULL; + +clusterNode *createClusterNode(char *nodename, int flags); +void clusterAddNode(clusterNode *node); +void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); +void clusterReadHandler(connection *conn); +void clusterSendPing(clusterLink *link, int type); +void clusterSendFail(char *nodename); +void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request); +void clusterUpdateState(void); +int clusterNodeCoversSlot(clusterNode *n, int slot); +list *clusterGetNodesInMyShard(clusterNode *node); +int clusterNodeAddSlave(clusterNode *master, clusterNode *slave); +int clusterAddSlot(clusterNode *n, int slot); +int clusterDelSlot(int slot); +int clusterDelNodeSlots(clusterNode *node); +int clusterNodeSetSlotBit(clusterNode *n, int slot); +void clusterSetMaster(clusterNode *n); +void clusterHandleSlaveFailover(void); +void clusterHandleSlaveMigration(int max_slaves); +int bitmapTestBit(unsigned char *bitmap, int pos); +void bitmapSetBit(unsigned char *bitmap, int pos); +void bitmapClearBit(unsigned char *bitmap, int pos); +void clusterDoBeforeSleep(int flags); +void clusterSendUpdate(clusterLink *link, clusterNode *node); +void resetManualFailover(void); +void clusterCloseAllSlots(void); +void clusterSetNodeAsMaster(clusterNode *n); +void clusterDelNode(clusterNode *delnode); +sds representClusterNodeFlags(sds ci, uint16_t flags); +sds representSlotInfo(sds ci, uint16_t *slot_info_pairs, int slot_info_pairs_count); +void clusterFreeNodesSlotsInfo(clusterNode *n); +uint64_t clusterGetMaxEpoch(void); +int clusterBumpConfigEpochWithoutConsensus(void); +void moduleCallClusterReceivers(const char *sender_id, uint64_t module_id, uint8_t type, const unsigned char *payload, uint32_t len); +const char *clusterGetMessageTypeString(int type); +void removeChannelsInSlot(unsigned int slot); +unsigned int countKeysInSlot(unsigned int hashslot); +unsigned int countChannelsInSlot(unsigned int hashslot); +unsigned int delKeysInSlot(unsigned int hashslot); +void clusterAddNodeToShard(const char *shard_id, clusterNode *node); +list *clusterLookupNodeListByShardId(const char *shard_id); +void clusterRemoveNodeFromShard(clusterNode *node); +int auxShardIdSetter(clusterNode *n, void *value, int length); +sds auxShardIdGetter(clusterNode *n, sds s); +int auxShardIdPresent(clusterNode *n); +int auxHumanNodenameSetter(clusterNode *n, void *value, int length); +sds auxHumanNodenameGetter(clusterNode *n, sds s); +int auxHumanNodenamePresent(clusterNode *n); +int auxTcpPortSetter(clusterNode *n, void *value, int length); +sds auxTcpPortGetter(clusterNode *n, sds s); +int auxTcpPortPresent(clusterNode *n); +int auxTlsPortSetter(clusterNode *n, void *value, int length); +sds auxTlsPortGetter(clusterNode *n, sds s); +int auxTlsPortPresent(clusterNode *n); +static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen); +void freeClusterLink(clusterLink *link); +int verifyClusterNodeId(const char *name, int length); + +int getNodeDefaultClientPort(clusterNode *n) { + return server.tls_cluster ? n->tls_port : n->tcp_port; +} + +static inline int getNodeDefaultReplicationPort(clusterNode *n) { + return server.tls_replication ? n->tls_port : n->tcp_port; +} + +int clusterNodeClientPort(clusterNode *n, int use_tls) { + return use_tls ? n->tls_port : n->tcp_port; +} + +static inline int defaultClientPort(void) { + return server.tls_cluster ? server.tls_port : server.port; +} + +#define isSlotUnclaimed(slot) \ + (server.cluster->slots[slot] == NULL || \ + bitmapTestBit(server.cluster->owner_not_claiming_slot, slot)) + +#define RCVBUF_INIT_LEN 1024 +#define RCVBUF_MAX_PREALLOC (1<<20) /* 1MB */ + +/* Cluster nodes hash table, mapping nodes addresses 1.2.3.4:6379 to + * clusterNode structures. */ +dictType clusterNodesDictType = { + dictSdsHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + NULL, /* val destructor */ + NULL /* allow to expand */ +}; + +/* Cluster re-addition blacklist. This maps node IDs to the time + * we can re-add this node. The goal is to avoid reading a removed + * node for some time. */ +dictType clusterNodesBlackListDictType = { + dictSdsCaseHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCaseCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + NULL, /* val destructor */ + NULL /* allow to expand */ +}; + +/* Cluster shards hash table, mapping shard id to list of nodes */ +dictType clusterSdsToListType = { + dictSdsHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + dictListDestructor, /* val destructor */ + NULL /* allow to expand */ +}; + +/* Aux fields are introduced in Redis 7.2 to support the persistence + * of various important node properties, such as shard id, in nodes.conf. + * Aux fields take an explicit format of name=value pairs and have no + * intrinsic order among them. Aux fields are always grouped together + * at the end of the second column of each row after the node's IP + * address/port/cluster_port and the optional hostname. Aux fields + * are separated by ','. */ + +/* Aux field setter function prototype + * return C_OK when the update is successful; C_ERR otherwise */ +typedef int (aux_value_setter) (clusterNode* n, void *value, int length); +/* Aux field getter function prototype + * return an sds that is a concatenation of the input sds string and + * the aux value */ +typedef sds (aux_value_getter) (clusterNode* n, sds s); + +typedef int (aux_value_present) (clusterNode* n); + +typedef struct { + char *field; + aux_value_setter *setter; + aux_value_getter *getter; + aux_value_present *isPresent; +} auxFieldHandler; + +/* Assign index to each aux field */ +typedef enum { + af_shard_id, + af_human_nodename, + af_tcp_port, + af_tls_port, + af_count, +} auxFieldIndex; + +/* Note that + * 1. the order of the elements below must match that of their + * indices as defined in auxFieldIndex + * 2. aux name can contain characters that pass the isValidAuxChar check only */ +auxFieldHandler auxFieldHandlers[] = { + {"shard-id", auxShardIdSetter, auxShardIdGetter, auxShardIdPresent}, + {"nodename", auxHumanNodenameSetter, auxHumanNodenameGetter, auxHumanNodenamePresent}, + {"tcp-port", auxTcpPortSetter, auxTcpPortGetter, auxTcpPortPresent}, + {"tls-port", auxTlsPortSetter, auxTlsPortGetter, auxTlsPortPresent}, +}; + +int auxShardIdSetter(clusterNode *n, void *value, int length) { + if (verifyClusterNodeId(value, length) == C_ERR) { + return C_ERR; + } + memcpy(n->shard_id, value, CLUSTER_NAMELEN); + /* if n already has replicas, make sure they all agree + * on the shard id */ + for (int i = 0; i < n->numslaves; i++) { + if (memcmp(n->slaves[i]->shard_id, n->shard_id, CLUSTER_NAMELEN) != 0) { + return C_ERR; + } + } + clusterAddNodeToShard(value, n); + return C_OK; +} + +sds auxShardIdGetter(clusterNode *n, sds s) { + return sdscatprintf(s, "%.40s", n->shard_id); +} + +int auxShardIdPresent(clusterNode *n) { + return strlen(n->shard_id); +} + +int auxHumanNodenameSetter(clusterNode *n, void *value, int length) { + if (n && !strncmp(value, n->human_nodename, length)) { + return C_OK; + } else if (!n && (length == 0)) { + return C_OK; + } + if (n) { + n->human_nodename = sdscpylen(n->human_nodename, value, length); + } else if (sdslen(n->human_nodename) != 0) { + sdsclear(n->human_nodename); + } else { + return C_ERR; + } + return C_OK; +} + +sds auxHumanNodenameGetter(clusterNode *n, sds s) { + return sdscatprintf(s, "%s", n->human_nodename); +} + +int auxHumanNodenamePresent(clusterNode *n) { + return sdslen(n->human_nodename); +} + +int auxTcpPortSetter(clusterNode *n, void *value, int length) { + if (length > 5 || length < 1) { + return C_ERR; + } + char buf[length + 1]; + memcpy(buf, (char*)value, length); + buf[length] = '\0'; + n->tcp_port = atoi(buf); + return (n->tcp_port < 0 || n->tcp_port >= 65536) ? C_ERR : C_OK; +} + +sds auxTcpPortGetter(clusterNode *n, sds s) { + return sdscatprintf(s, "%d", n->tcp_port); +} + +int auxTcpPortPresent(clusterNode *n) { + return n->tcp_port >= 0 && n->tcp_port < 65536; +} + +int auxTlsPortSetter(clusterNode *n, void *value, int length) { + if (length > 5 || length < 1) { + return C_ERR; + } + char buf[length + 1]; + memcpy(buf, (char*)value, length); + buf[length] = '\0'; + n->tls_port = atoi(buf); + return (n->tls_port < 0 || n->tls_port >= 65536) ? C_ERR : C_OK; +} + +sds auxTlsPortGetter(clusterNode *n, sds s) { + return sdscatprintf(s, "%d", n->tls_port); +} + +int auxTlsPortPresent(clusterNode *n) { + return n->tls_port >= 0 && n->tls_port < 65536; +} + +/* clusterLink send queue blocks */ +typedef struct { + size_t totlen; /* Total length of this block including the message */ + int refcount; /* Number of cluster link send msg queues containing the message */ + clusterMsg msg; +} clusterMsgSendBlock; + +/* ----------------------------------------------------------------------------- + * Initialization + * -------------------------------------------------------------------------- */ + +/* Load the cluster config from 'filename'. + * + * If the file does not exist or is zero-length (this may happen because + * when we lock the nodes.conf file, we create a zero-length one for the + * sake of locking if it does not already exist), C_ERR is returned. + * If the configuration was loaded from the file, C_OK is returned. */ +int clusterLoadConfig(char *filename) { + FILE *fp = fopen(filename,"r"); + struct stat sb; + char *line; + int maxline, j; + + if (fp == NULL) { + if (errno == ENOENT) { + return C_ERR; + } else { + serverLog(LL_WARNING, + "Loading the cluster node config from %s: %s", + filename, strerror(errno)); + exit(1); + } + } + + if (redis_fstat(fileno(fp),&sb) == -1) { + serverLog(LL_WARNING, + "Unable to obtain the cluster node config file stat %s: %s", + filename, strerror(errno)); + exit(1); + } + /* Check if the file is zero-length: if so return C_ERR to signal + * we have to write the config. */ + if (sb.st_size == 0) { + fclose(fp); + return C_ERR; + } + + /* Parse the file. Note that single lines of the cluster config file can + * be really long as they include all the hash slots of the node. + * This means in the worst possible case, half of the Redis slots will be + * present in a single line, possibly in importing or migrating state, so + * together with the node ID of the sender/receiver. + * + * To simplify we allocate 1024+CLUSTER_SLOTS*128 bytes per line. */ + maxline = 1024+CLUSTER_SLOTS*128; + line = zmalloc(maxline); + while(fgets(line,maxline,fp) != NULL) { + int argc, aux_argc; + sds *argv, *aux_argv; + clusterNode *n, *master; + char *p, *s; + + /* Skip blank lines, they can be created either by users manually + * editing nodes.conf or by the config writing process if stopped + * before the truncate() call. */ + if (line[0] == '\n' || line[0] == '\0') continue; + + /* Split the line into arguments for processing. */ + argv = sdssplitargs(line,&argc); + if (argv == NULL) goto fmterr; + + /* Handle the special "vars" line. Don't pretend it is the last + * line even if it actually is when generated by Redis. */ + if (strcasecmp(argv[0],"vars") == 0) { + if (!(argc % 2)) goto fmterr; + for (j = 1; j < argc; j += 2) { + if (strcasecmp(argv[j],"currentEpoch") == 0) { + server.cluster->currentEpoch = + strtoull(argv[j+1],NULL,10); + } else if (strcasecmp(argv[j],"lastVoteEpoch") == 0) { + server.cluster->lastVoteEpoch = + strtoull(argv[j+1],NULL,10); + } else { + serverLog(LL_NOTICE, + "Skipping unknown cluster config variable '%s'", + argv[j]); + } + } + sdsfreesplitres(argv,argc); + continue; + } + + /* Regular config lines have at least eight fields */ + if (argc < 8) { + sdsfreesplitres(argv,argc); + goto fmterr; + } + + /* Create this node if it does not exist */ + if (verifyClusterNodeId(argv[0], sdslen(argv[0])) == C_ERR) { + sdsfreesplitres(argv, argc); + goto fmterr; + } + n = clusterLookupNode(argv[0], sdslen(argv[0])); + if (!n) { + n = createClusterNode(argv[0],0); + clusterAddNode(n); + } + /* Format for the node address and auxiliary argument information: + * ip:port[@cport][,hostname][,aux=val]*] */ + + aux_argv = sdssplitlen(argv[1], sdslen(argv[1]), ",", 1, &aux_argc); + if (aux_argv == NULL) { + sdsfreesplitres(argv,argc); + goto fmterr; + } + + /* Hostname is an optional argument that defines the endpoint + * that can be reported to clients instead of IP. */ + if (aux_argc > 1 && sdslen(aux_argv[1]) > 0) { + n->hostname = sdscpy(n->hostname, aux_argv[1]); + } else if (sdslen(n->hostname) != 0) { + sdsclear(n->hostname); + } + + /* All fields after hostname are auxiliary and they take on + * the format of "aux=val" where both aux and val can contain + * characters that pass the isValidAuxChar check only. The order + * of the aux fields is insignificant. */ + int aux_tcp_port = 0; + int aux_tls_port = 0; + for (int i = 2; i < aux_argc; i++) { + int field_argc; + sds *field_argv; + field_argv = sdssplitlen(aux_argv[i], sdslen(aux_argv[i]), "=", 1, &field_argc); + if (field_argv == NULL || field_argc != 2) { + /* Invalid aux field format */ + if (field_argv != NULL) sdsfreesplitres(field_argv, field_argc); + sdsfreesplitres(aux_argv, aux_argc); + sdsfreesplitres(argv,argc); + goto fmterr; + } + + /* Validate that both aux and value contain valid characters only */ + for (unsigned j = 0; j < 2; j++) { + if (!isValidAuxString(field_argv[j],sdslen(field_argv[j]))){ + /* Invalid aux field format */ + sdsfreesplitres(field_argv, field_argc); + sdsfreesplitres(aux_argv, aux_argc); + sdsfreesplitres(argv,argc); + goto fmterr; + } + } + + /* Note that we don't expect lots of aux fields in the foreseeable + * future so a linear search is completely fine. */ + int field_found = 0; + for (unsigned j = 0; j < numElements(auxFieldHandlers); j++) { + if (sdslen(field_argv[0]) != strlen(auxFieldHandlers[j].field) || + memcmp(field_argv[0], auxFieldHandlers[j].field, sdslen(field_argv[0])) != 0) { + continue; + } + field_found = 1; + aux_tcp_port |= j == af_tcp_port; + aux_tls_port |= j == af_tls_port; + if (auxFieldHandlers[j].setter(n, field_argv[1], sdslen(field_argv[1])) != C_OK) { + /* Invalid aux field format */ + sdsfreesplitres(field_argv, field_argc); + sdsfreesplitres(aux_argv, aux_argc); + sdsfreesplitres(argv,argc); + goto fmterr; + } + } + + if (field_found == 0) { + /* Invalid aux field format */ + sdsfreesplitres(field_argv, field_argc); + sdsfreesplitres(aux_argv, aux_argc); + sdsfreesplitres(argv,argc); + goto fmterr; + } + + sdsfreesplitres(field_argv, field_argc); + } + /* Address and port */ + if ((p = strrchr(aux_argv[0],':')) == NULL) { + sdsfreesplitres(aux_argv, aux_argc); + sdsfreesplitres(argv,argc); + goto fmterr; + } + *p = '\0'; + memcpy(n->ip,aux_argv[0],strlen(aux_argv[0])+1); + char *port = p+1; + char *busp = strchr(port,'@'); + if (busp) { + *busp = '\0'; + busp++; + } + /* If neither TCP or TLS port is found in aux field, it is considered + * an old version of nodes.conf file.*/ + if (!aux_tcp_port && !aux_tls_port) { + if (server.tls_cluster) { + n->tls_port = atoi(port); + } else { + n->tcp_port = atoi(port); + } + } else if (!aux_tcp_port) { + n->tcp_port = atoi(port); + } else if (!aux_tls_port) { + n->tls_port = atoi(port); + } + /* In older versions of nodes.conf the "@busport" part is missing. + * In this case we set it to the default offset of 10000 from the + * base port. */ + n->cport = busp ? atoi(busp) : (getNodeDefaultClientPort(n) + CLUSTER_PORT_INCR); + + /* The plaintext port for client in a TLS cluster (n->pport) is not + * stored in nodes.conf. It is received later over the bus protocol. */ + + sdsfreesplitres(aux_argv, aux_argc); + + /* Parse flags */ + p = s = argv[2]; + while(p) { + p = strchr(s,','); + if (p) *p = '\0'; + if (!strcasecmp(s,"myself")) { + serverAssert(server.cluster->myself == NULL); + myself = server.cluster->myself = n; + n->flags |= CLUSTER_NODE_MYSELF; + } else if (!strcasecmp(s,"master")) { + n->flags |= CLUSTER_NODE_MASTER; + } else if (!strcasecmp(s,"slave")) { + n->flags |= CLUSTER_NODE_SLAVE; + } else if (!strcasecmp(s,"fail?")) { + n->flags |= CLUSTER_NODE_PFAIL; + } else if (!strcasecmp(s,"fail")) { + n->flags |= CLUSTER_NODE_FAIL; + n->fail_time = mstime(); + } else if (!strcasecmp(s,"handshake")) { + n->flags |= CLUSTER_NODE_HANDSHAKE; + } else if (!strcasecmp(s,"noaddr")) { + n->flags |= CLUSTER_NODE_NOADDR; + } else if (!strcasecmp(s,"nofailover")) { + n->flags |= CLUSTER_NODE_NOFAILOVER; + } else if (!strcasecmp(s,"noflags")) { + /* nothing to do */ + } else { + serverPanic("Unknown flag in redis cluster config file"); + } + if (p) s = p+1; + } + + /* Get master if any. Set the master and populate master's + * slave list. */ + if (argv[3][0] != '-') { + if (verifyClusterNodeId(argv[3], sdslen(argv[3])) == C_ERR) { + sdsfreesplitres(argv, argc); + goto fmterr; + } + master = clusterLookupNode(argv[3], sdslen(argv[3])); + if (!master) { + master = createClusterNode(argv[3],0); + clusterAddNode(master); + } + /* shard_id can be absent if we are loading a nodes.conf generated + * by an older version of Redis; we should follow the primary's + * shard_id in this case */ + if (auxFieldHandlers[af_shard_id].isPresent(n) == 0) { + memcpy(n->shard_id, master->shard_id, CLUSTER_NAMELEN); + clusterAddNodeToShard(master->shard_id, n); + } else if (clusterGetNodesInMyShard(master) != NULL && + memcmp(master->shard_id, n->shard_id, CLUSTER_NAMELEN) != 0) + { + /* If the primary has been added to a shard, make sure this + * node has the same persisted shard id as the primary. */ + goto fmterr; + } + n->slaveof = master; + clusterNodeAddSlave(master,n); + } else if (auxFieldHandlers[af_shard_id].isPresent(n) == 0) { + /* n is a primary but it does not have a persisted shard_id. + * This happens if we are loading a nodes.conf generated by + * an older version of Redis. We should manually update the + * shard membership in this case */ + clusterAddNodeToShard(n->shard_id, n); + } + + /* Set ping sent / pong received timestamps */ + if (atoi(argv[4])) n->ping_sent = mstime(); + if (atoi(argv[5])) n->pong_received = mstime(); + + /* Set configEpoch for this node. + * If the node is a replica, set its config epoch to 0. + * If it's a primary, load the config epoch from the configuration file. */ + n->configEpoch = (nodeIsSlave(n) && n->slaveof) ? 0 : strtoull(argv[6],NULL,10); + + /* Populate hash slots served by this instance. */ + for (j = 8; j < argc; j++) { + int start, stop; + + if (argv[j][0] == '[') { + /* Here we handle migrating / importing slots */ + int slot; + char direction; + clusterNode *cn; + + p = strchr(argv[j],'-'); + serverAssert(p != NULL); + *p = '\0'; + direction = p[1]; /* Either '>' or '<' */ + slot = atoi(argv[j]+1); + if (slot < 0 || slot >= CLUSTER_SLOTS) { + sdsfreesplitres(argv,argc); + goto fmterr; + } + p += 3; + + char *pr = strchr(p, ']'); + size_t node_len = pr - p; + if (pr == NULL || verifyClusterNodeId(p, node_len) == C_ERR) { + sdsfreesplitres(argv, argc); + goto fmterr; + } + cn = clusterLookupNode(p, CLUSTER_NAMELEN); + if (!cn) { + cn = createClusterNode(p,0); + clusterAddNode(cn); + } + if (direction == '>') { + server.cluster->migrating_slots_to[slot] = cn; + } else { + server.cluster->importing_slots_from[slot] = cn; + } + continue; + } else if ((p = strchr(argv[j],'-')) != NULL) { + *p = '\0'; + start = atoi(argv[j]); + stop = atoi(p+1); + } else { + start = stop = atoi(argv[j]); + } + if (start < 0 || start >= CLUSTER_SLOTS || + stop < 0 || stop >= CLUSTER_SLOTS) + { + sdsfreesplitres(argv,argc); + goto fmterr; + } + while(start <= stop) clusterAddSlot(n, start++); + } + + sdsfreesplitres(argv,argc); + } + /* Config sanity check */ + if (server.cluster->myself == NULL) goto fmterr; + + zfree(line); + fclose(fp); + + serverLog(LL_NOTICE,"Node configuration loaded, I'm %.40s", myself->name); + + /* Something that should never happen: currentEpoch smaller than + * the max epoch found in the nodes configuration. However we handle this + * as some form of protection against manual editing of critical files. */ + if (clusterGetMaxEpoch() > server.cluster->currentEpoch) { + server.cluster->currentEpoch = clusterGetMaxEpoch(); + } + return C_OK; + +fmterr: + serverLog(LL_WARNING, + "Unrecoverable error: corrupted cluster config file \"%s\".", line); + zfree(line); + if (fp) fclose(fp); + exit(1); +} + +/* Cluster node configuration is exactly the same as CLUSTER NODES output. + * + * This function writes the node config and returns 0, on error -1 + * is returned. + * + * Note: we need to write the file in an atomic way from the point of view + * of the POSIX filesystem semantics, so that if the server is stopped + * or crashes during the write, we'll end with either the old file or the + * new one. Since we have the full payload to write available we can use + * a single write to write the whole file. If the pre-existing file was + * bigger we pad our payload with newlines that are anyway ignored and truncate + * the file afterward. */ +int clusterSaveConfig(int do_fsync) { + sds ci,tmpfilename; + size_t content_size,offset = 0; + ssize_t written_bytes; + int fd = -1; + int retval = C_ERR; + + server.cluster->todo_before_sleep &= ~CLUSTER_TODO_SAVE_CONFIG; + + /* Get the nodes description and concatenate our "vars" directive to + * save currentEpoch and lastVoteEpoch. */ + ci = clusterGenNodesDescription(NULL, CLUSTER_NODE_HANDSHAKE, 0); + ci = sdscatprintf(ci,"vars currentEpoch %llu lastVoteEpoch %llu\n", + (unsigned long long) server.cluster->currentEpoch, + (unsigned long long) server.cluster->lastVoteEpoch); + content_size = sdslen(ci); + + /* Create a temp file with the new content. */ + tmpfilename = sdscatfmt(sdsempty(),"%s.tmp-%i-%I", + server.cluster_configfile,(int) getpid(),mstime()); + if ((fd = open(tmpfilename,O_WRONLY|O_CREAT,0644)) == -1) { + serverLog(LL_WARNING,"Could not open temp cluster config file: %s",strerror(errno)); + goto cleanup; + } + + while (offset < content_size) { + written_bytes = write(fd,ci + offset,content_size - offset); + if (written_bytes <= 0) { + if (errno == EINTR) continue; + serverLog(LL_WARNING,"Failed after writing (%zd) bytes to tmp cluster config file: %s", + offset,strerror(errno)); + goto cleanup; + } + offset += written_bytes; + } + + if (do_fsync) { + server.cluster->todo_before_sleep &= ~CLUSTER_TODO_FSYNC_CONFIG; + if (redis_fsync(fd) == -1) { + serverLog(LL_WARNING,"Could not sync tmp cluster config file: %s",strerror(errno)); + goto cleanup; + } + } + + if (rename(tmpfilename, server.cluster_configfile) == -1) { + serverLog(LL_WARNING,"Could not rename tmp cluster config file: %s",strerror(errno)); + goto cleanup; + } + + if (do_fsync) { + if (fsyncFileDir(server.cluster_configfile) == -1) { + serverLog(LL_WARNING,"Could not sync cluster config file dir: %s",strerror(errno)); + goto cleanup; + } + } + retval = C_OK; /* If we reached this point, everything is fine. */ + +cleanup: + if (fd != -1) close(fd); + if (retval) unlink(tmpfilename); + sdsfree(tmpfilename); + sdsfree(ci); + return retval; +} + +void clusterSaveConfigOrDie(int do_fsync) { + if (clusterSaveConfig(do_fsync) == -1) { + serverLog(LL_WARNING,"Fatal: can't update cluster config file."); + exit(1); + } +} + +/* Lock the cluster config using flock(), and retain the file descriptor used to + * acquire the lock so that the file will be locked as long as the process is up. + * + * This works because we always update nodes.conf with a new version + * in-place, reopening the file, and writing to it in place (later adjusting + * the length with ftruncate()). + * + * On success C_OK is returned, otherwise an error is logged and + * the function returns C_ERR to signal a lock was not acquired. */ +int clusterLockConfig(char *filename) { +/* flock() does not exist on Solaris + * and a fcntl-based solution won't help, as we constantly re-open that file, + * which will release _all_ locks anyway + */ +#if !defined(__sun) + /* To lock it, we need to open the file in a way it is created if + * it does not exist, otherwise there is a race condition with other + * processes. */ + int fd = open(filename,O_WRONLY|O_CREAT|O_CLOEXEC,0644); + if (fd == -1) { + serverLog(LL_WARNING, + "Can't open %s in order to acquire a lock: %s", + filename, strerror(errno)); + return C_ERR; + } + + if (flock(fd,LOCK_EX|LOCK_NB) == -1) { + if (errno == EWOULDBLOCK) { + serverLog(LL_WARNING, + "Sorry, the cluster configuration file %s is already used " + "by a different Redis Cluster node. Please make sure that " + "different nodes use different cluster configuration " + "files.", filename); + } else { + serverLog(LL_WARNING, + "Impossible to lock %s: %s", filename, strerror(errno)); + } + close(fd); + return C_ERR; + } + /* Lock acquired: leak the 'fd' by not closing it until shutdown time, so that + * we'll retain the lock to the file as long as the process exists. + * + * After fork, the child process will get the fd opened by the parent process, + * we need save `fd` to `cluster_config_file_lock_fd`, so that in redisFork(), + * it will be closed in the child process. + * If it is not closed, when the main process is killed -9, but the child process + * (redis-aof-rewrite) is still alive, the fd(lock) will still be held by the + * child process, and the main process will fail to get lock, means fail to start. */ + server.cluster_config_file_lock_fd = fd; +#else + UNUSED(filename); +#endif /* __sun */ + + return C_OK; +} + +/* Derives our ports to be announced in the cluster bus. */ +void deriveAnnouncedPorts(int *announced_tcp_port, int *announced_tls_port, + int *announced_cport) { + /* Config overriding announced ports. */ + *announced_tcp_port = server.cluster_announce_port ? + server.cluster_announce_port : server.port; + *announced_tls_port = server.cluster_announce_tls_port ? + server.cluster_announce_tls_port : server.tls_port; + /* Derive cluster bus port. */ + if (server.cluster_announce_bus_port) { + *announced_cport = server.cluster_announce_bus_port; + } else if (server.cluster_port) { + *announced_cport = server.cluster_port; + } else { + *announced_cport = defaultClientPort() + CLUSTER_PORT_INCR; + } +} + +/* Some flags (currently just the NOFAILOVER flag) may need to be updated + * in the "myself" node based on the current configuration of the node, + * that may change at runtime via CONFIG SET. This function changes the + * set of flags in myself->flags accordingly. */ +void clusterUpdateMyselfFlags(void) { + if (!myself) return; + int oldflags = myself->flags; + int nofailover = server.cluster_slave_no_failover ? + CLUSTER_NODE_NOFAILOVER : 0; + myself->flags &= ~CLUSTER_NODE_NOFAILOVER; + myself->flags |= nofailover; + if (myself->flags != oldflags) { + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_UPDATE_STATE); + } +} + + +/* We want to take myself->port/cport/pport in sync with the +* cluster-announce-port/cluster-announce-bus-port/cluster-announce-tls-port option. +* The option can be set at runtime via CONFIG SET. */ +void clusterUpdateMyselfAnnouncedPorts(void) { + if (!myself) return; + deriveAnnouncedPorts(&myself->tcp_port,&myself->tls_port,&myself->cport); +} + +/* We want to take myself->ip in sync with the cluster-announce-ip option. +* The option can be set at runtime via CONFIG SET. */ +void clusterUpdateMyselfIp(void) { + if (!myself) return; + static char *prev_ip = NULL; + char *curr_ip = server.cluster_announce_ip; + int changed = 0; + + if (prev_ip == NULL && curr_ip != NULL) changed = 1; + else if (prev_ip != NULL && curr_ip == NULL) changed = 1; + else if (prev_ip && curr_ip && strcmp(prev_ip,curr_ip)) changed = 1; + + if (changed) { + if (prev_ip) zfree(prev_ip); + prev_ip = curr_ip; + + if (curr_ip) { + /* We always take a copy of the previous IP address, by + * duplicating the string. This way later we can check if + * the address really changed. */ + prev_ip = zstrdup(prev_ip); + redis_strlcpy(myself->ip,server.cluster_announce_ip,NET_IP_STR_LEN); + } else { + myself->ip[0] = '\0'; /* Force autodetection. */ + } + } +} + +/* Update the hostname for the specified node with the provided C string. */ +static void updateAnnouncedHostname(clusterNode *node, char *new) { + /* Previous and new hostname are the same, no need to update. */ + if (new && !strcmp(new, node->hostname)) { + return; + } else if (!new && (sdslen(node->hostname) == 0)) { + return; + } + + if (new) { + node->hostname = sdscpy(node->hostname, new); + } else if (sdslen(node->hostname) != 0) { + sdsclear(node->hostname); + } + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); +} + +static void updateAnnouncedHumanNodename(clusterNode *node, char *new) { + if (new && !strcmp(new, node->human_nodename)) { + return; + } else if (!new && (sdslen(node->human_nodename) == 0)) { + return; + } + + if (new) { + node->human_nodename = sdscpy(node->human_nodename, new); + } else if (sdslen(node->human_nodename) != 0) { + sdsclear(node->human_nodename); + } + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); +} + + +static void updateShardId(clusterNode *node, const char *shard_id) { + if (shard_id && memcmp(node->shard_id, shard_id, CLUSTER_NAMELEN) != 0) { + clusterRemoveNodeFromShard(node); + memcpy(node->shard_id, shard_id, CLUSTER_NAMELEN); + clusterAddNodeToShard(shard_id, node); + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); + } + if (shard_id && myself != node && myself->slaveof == node) { + if (memcmp(myself->shard_id, shard_id, CLUSTER_NAMELEN) != 0) { + /* shard-id can diverge right after a rolling upgrade + * from pre-7.2 releases */ + clusterRemoveNodeFromShard(myself); + memcpy(myself->shard_id, shard_id, CLUSTER_NAMELEN); + clusterAddNodeToShard(shard_id, myself); + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG); + } + } +} + +/* Update my hostname based on server configuration values */ +void clusterUpdateMyselfHostname(void) { + if (!myself) return; + updateAnnouncedHostname(myself, server.cluster_announce_hostname); +} + +void clusterUpdateMyselfHumanNodename(void) { + if (!myself) return; + updateAnnouncedHumanNodename(myself, server.cluster_announce_human_nodename); +} + +void clusterInit(void) { + int saveconf = 0; + + server.cluster = zmalloc(sizeof(struct clusterState)); + server.cluster->myself = NULL; + server.cluster->currentEpoch = 0; + server.cluster->state = CLUSTER_FAIL; + server.cluster->size = 0; + server.cluster->todo_before_sleep = 0; + server.cluster->nodes = dictCreate(&clusterNodesDictType); + server.cluster->shards = dictCreate(&clusterSdsToListType); + server.cluster->nodes_black_list = + dictCreate(&clusterNodesBlackListDictType); + server.cluster->failover_auth_time = 0; + server.cluster->failover_auth_count = 0; + server.cluster->failover_auth_rank = 0; + server.cluster->failover_auth_epoch = 0; + server.cluster->cant_failover_reason = CLUSTER_CANT_FAILOVER_NONE; + server.cluster->lastVoteEpoch = 0; + + /* Initialize stats */ + for (int i = 0; i < CLUSTERMSG_TYPE_COUNT; i++) { + server.cluster->stats_bus_messages_sent[i] = 0; + server.cluster->stats_bus_messages_received[i] = 0; + } + server.cluster->stats_pfail_nodes = 0; + server.cluster->stat_cluster_links_buffer_limit_exceeded = 0; + + memset(server.cluster->slots,0, sizeof(server.cluster->slots)); + clusterCloseAllSlots(); + + memset(server.cluster->owner_not_claiming_slot, 0, sizeof(server.cluster->owner_not_claiming_slot)); + + /* Lock the cluster config file to make sure every node uses + * its own nodes.conf. */ + server.cluster_config_file_lock_fd = -1; + if (clusterLockConfig(server.cluster_configfile) == C_ERR) + exit(1); + + /* Load or create a new nodes configuration. */ + if (clusterLoadConfig(server.cluster_configfile) == C_ERR) { + /* No configuration found. We will just use the random name provided + * by the createClusterNode() function. */ + myself = server.cluster->myself = + createClusterNode(NULL,CLUSTER_NODE_MYSELF|CLUSTER_NODE_MASTER); + serverLog(LL_NOTICE,"No cluster configuration found, I'm %.40s", + myself->name); + clusterAddNode(myself); + clusterAddNodeToShard(myself->shard_id, myself); + saveconf = 1; + } + if (saveconf) clusterSaveConfigOrDie(1); + + /* Port sanity check II + * The other handshake port check is triggered too late to stop + * us from trying to use a too-high cluster port number. */ + int port = defaultClientPort(); + if (!server.cluster_port && port > (65535-CLUSTER_PORT_INCR)) { + serverLog(LL_WARNING, "Redis port number too high. " + "Cluster communication port is 10,000 port " + "numbers higher than your Redis port. " + "Your Redis port number must be 55535 or less."); + exit(1); + } + if (!server.bindaddr_count) { + serverLog(LL_WARNING, "No bind address is configured, but it is required for the Cluster bus."); + exit(1); + } + + /* Set myself->port/cport/pport to my listening ports, we'll just need to + * discover the IP address via MEET messages. */ + deriveAnnouncedPorts(&myself->tcp_port, &myself->tls_port, &myself->cport); + + server.cluster->mf_end = 0; + server.cluster->mf_slave = NULL; + resetManualFailover(); + clusterUpdateMyselfFlags(); + clusterUpdateMyselfIp(); + clusterUpdateMyselfHostname(); + clusterUpdateMyselfHumanNodename(); +} + +void clusterInitLast(void) { + if (connectionIndexByType(connTypeOfCluster()->get_type(NULL)) < 0) { + serverLog(LL_WARNING, "Missing connection type %s, but it is required for the Cluster bus.", connTypeOfCluster()->get_type(NULL)); + exit(1); + } + + int port = defaultClientPort(); + connListener *listener = &server.clistener; + listener->count = 0; + listener->bindaddr = server.bindaddr; + listener->bindaddr_count = server.bindaddr_count; + listener->port = server.cluster_port ? server.cluster_port : port + CLUSTER_PORT_INCR; + listener->ct = connTypeOfCluster(); + if (connListen(listener) == C_ERR ) { + /* Note: the following log text is matched by the test suite. */ + serverLog(LL_WARNING, "Failed listening on port %u (cluster), aborting.", listener->port); + exit(1); + } + + if (createSocketAcceptHandler(&server.clistener, clusterAcceptHandler) != C_OK) { + serverPanic("Unrecoverable error creating Redis Cluster socket accept handler."); + } +} + +/* Reset a node performing a soft or hard reset: + * + * 1) All other nodes are forgotten. + * 2) All the assigned / open slots are released. + * 3) If the node is a slave, it turns into a master. + * 4) Only for hard reset: a new Node ID is generated. + * 5) Only for hard reset: currentEpoch and configEpoch are set to 0. + * 6) The new configuration is saved and the cluster state updated. + * 7) If the node was a slave, the whole data set is flushed away. */ +void clusterReset(int hard) { + dictIterator *di; + dictEntry *de; + int j; + + /* Turn into master. */ + if (nodeIsSlave(myself)) { + clusterSetNodeAsMaster(myself); + replicationUnsetMaster(); + emptyData(-1,EMPTYDB_NO_FLAGS,NULL); + } + + /* Close slots, reset manual failover state. */ + clusterCloseAllSlots(); + resetManualFailover(); + + /* Unassign all the slots. */ + for (j = 0; j < CLUSTER_SLOTS; j++) clusterDelSlot(j); + + /* Recreate shards dict */ + dictEmpty(server.cluster->shards, NULL); + + /* Forget all the nodes, but myself. */ + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + + if (node == myself) continue; + clusterDelNode(node); + } + dictReleaseIterator(di); + + /* Empty the nodes blacklist. */ + dictEmpty(server.cluster->nodes_black_list, NULL); + + /* Hard reset only: set epochs to 0, change node ID. */ + if (hard) { + sds oldname; + + server.cluster->currentEpoch = 0; + server.cluster->lastVoteEpoch = 0; + myself->configEpoch = 0; + serverLog(LL_NOTICE, "configEpoch set to 0 via CLUSTER RESET HARD"); + + /* To change the Node ID we need to remove the old name from the + * nodes table, change the ID, and re-add back with new name. */ + oldname = sdsnewlen(myself->name, CLUSTER_NAMELEN); + dictDelete(server.cluster->nodes,oldname); + sdsfree(oldname); + getRandomHexChars(myself->name, CLUSTER_NAMELEN); + getRandomHexChars(myself->shard_id, CLUSTER_NAMELEN); + clusterAddNode(myself); + serverLog(LL_NOTICE,"Node hard reset, now I'm %.40s", myself->name); + } + + /* Re-populate shards */ + clusterAddNodeToShard(myself->shard_id, myself); + + /* Make sure to persist the new config and update the state. */ + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_UPDATE_STATE| + CLUSTER_TODO_FSYNC_CONFIG); +} + +/* ----------------------------------------------------------------------------- + * CLUSTER communication link + * -------------------------------------------------------------------------- */ +static clusterMsgSendBlock *createClusterMsgSendBlock(int type, uint32_t msglen) { + uint32_t blocklen = msglen + sizeof(clusterMsgSendBlock) - sizeof(clusterMsg); + clusterMsgSendBlock *msgblock = zcalloc(blocklen); + msgblock->refcount = 1; + msgblock->totlen = blocklen; + server.stat_cluster_links_memory += blocklen; + clusterBuildMessageHdr(&msgblock->msg,type,msglen); + return msgblock; +} + +static void clusterMsgSendBlockDecrRefCount(void *node) { + clusterMsgSendBlock *msgblock = (clusterMsgSendBlock*)node; + msgblock->refcount--; + serverAssert(msgblock->refcount >= 0); + if (msgblock->refcount == 0) { + server.stat_cluster_links_memory -= msgblock->totlen; + zfree(msgblock); + } +} + +clusterLink *createClusterLink(clusterNode *node) { + clusterLink *link = zmalloc(sizeof(*link)); + link->ctime = mstime(); + link->send_msg_queue = listCreate(); + listSetFreeMethod(link->send_msg_queue, clusterMsgSendBlockDecrRefCount); + link->head_msg_send_offset = 0; + link->send_msg_queue_mem = sizeof(list); + link->rcvbuf = zmalloc(link->rcvbuf_alloc = RCVBUF_INIT_LEN); + link->rcvbuf_len = 0; + server.stat_cluster_links_memory += link->rcvbuf_alloc + link->send_msg_queue_mem; + link->conn = NULL; + link->node = node; + /* Related node can only possibly be known at link creation time if this is an outbound link */ + link->inbound = (node == NULL); + if (!link->inbound) { + node->link = link; + } + return link; +} + +/* Free a cluster link, but does not free the associated node of course. + * This function will just make sure that the original node associated + * with this link will have the 'link' field set to NULL. */ +void freeClusterLink(clusterLink *link) { + if (link->conn) { + connClose(link->conn); + link->conn = NULL; + } + server.stat_cluster_links_memory -= sizeof(list) + listLength(link->send_msg_queue)*sizeof(listNode); + listRelease(link->send_msg_queue); + server.stat_cluster_links_memory -= link->rcvbuf_alloc; + zfree(link->rcvbuf); + if (link->node) { + if (link->node->link == link) { + serverAssert(!link->inbound); + link->node->link = NULL; + } else if (link->node->inbound_link == link) { + serverAssert(link->inbound); + link->node->inbound_link = NULL; + } + } + zfree(link); +} + +void setClusterNodeToInboundClusterLink(clusterNode *node, clusterLink *link) { + serverAssert(!link->node); + serverAssert(link->inbound); + if (node->inbound_link) { + /* A peer may disconnect and then reconnect with us, and it's not guaranteed that + * we would always process the disconnection of the existing inbound link before + * accepting a new existing inbound link. Therefore, it's possible to have more than + * one inbound link from the same node at the same time. Our cleanup logic assumes + * a one to one relationship between nodes and inbound links, so we need to kill + * one of the links. The existing link is more likely the outdated one, but it's + * possible the other node may need to open another link. */ + serverLog(LL_DEBUG, "Replacing inbound link fd %d from node %.40s with fd %d", + node->inbound_link->conn->fd, node->name, link->conn->fd); + freeClusterLink(node->inbound_link); + } + serverAssert(!node->inbound_link); + node->inbound_link = link; + link->node = node; +} + +static void clusterConnAcceptHandler(connection *conn) { + clusterLink *link; + + if (connGetState(conn) != CONN_STATE_CONNECTED) { + serverLog(LL_VERBOSE, + "Error accepting cluster node connection: %s", connGetLastError(conn)); + connClose(conn); + return; + } + + /* Create a link object we use to handle the connection. + * It gets passed to the readable handler when data is available. + * Initially the link->node pointer is set to NULL as we don't know + * which node is, but the right node is references once we know the + * node identity. */ + link = createClusterLink(NULL); + link->conn = conn; + connSetPrivateData(conn, link); + + /* Register read handler */ + connSetReadHandler(conn, clusterReadHandler); +} + +#define MAX_CLUSTER_ACCEPTS_PER_CALL 1000 +void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { + int cport, cfd; + int max = MAX_CLUSTER_ACCEPTS_PER_CALL; + char cip[NET_IP_STR_LEN]; + int require_auth = TLS_CLIENT_AUTH_YES; + UNUSED(el); + UNUSED(mask); + UNUSED(privdata); + + /* If the server is starting up, don't accept cluster connections: + * UPDATE messages may interact with the database content. */ + if (server.masterhost == NULL && server.loading) return; + + while(max--) { + cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport); + if (cfd == ANET_ERR) { + if (errno != EWOULDBLOCK) + serverLog(LL_VERBOSE, + "Error accepting cluster node: %s", server.neterr); + return; + } + + connection *conn = connCreateAccepted(connTypeOfCluster(), cfd, &require_auth); + + /* Make sure connection is not in an error state */ + if (connGetState(conn) != CONN_STATE_ACCEPTING) { + serverLog(LL_VERBOSE, + "Error creating an accepting connection for cluster node: %s", + connGetLastError(conn)); + connClose(conn); + return; + } + connEnableTcpNoDelay(conn); + connKeepAlive(conn,server.cluster_node_timeout / 1000 * 2); + + /* Use non-blocking I/O for cluster messages. */ + serverLog(LL_VERBOSE,"Accepting cluster node connection from %s:%d", cip, cport); + + /* Accept the connection now. connAccept() may call our handler directly + * or schedule it for later depending on connection implementation. + */ + if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) { + if (connGetState(conn) == CONN_STATE_ERROR) + serverLog(LL_VERBOSE, + "Error accepting cluster node connection: %s", + connGetLastError(conn)); + connClose(conn); + return; + } + } +} + +/* Return the approximated number of sockets we are using in order to + * take the cluster bus connections. */ +unsigned long getClusterConnectionsCount(void) { + /* We decrement the number of nodes by one, since there is the + * "myself" node too in the list. Each node uses two file descriptors, + * one incoming and one outgoing, thus the multiplication by 2. */ + return server.cluster_enabled ? + ((dictSize(server.cluster->nodes)-1)*2) : 0; +} + +/* ----------------------------------------------------------------------------- + * CLUSTER node API + * -------------------------------------------------------------------------- */ + +/* Create a new cluster node, with the specified flags. + * If "nodename" is NULL this is considered a first handshake and a random + * node name is assigned to this node (it will be fixed later when we'll + * receive the first pong). + * + * The node is created and returned to the user, but it is not automatically + * added to the nodes hash table. */ +clusterNode *createClusterNode(char *nodename, int flags) { + clusterNode *node = zmalloc(sizeof(*node)); + + if (nodename) + memcpy(node->name, nodename, CLUSTER_NAMELEN); + else + getRandomHexChars(node->name, CLUSTER_NAMELEN); + getRandomHexChars(node->shard_id, CLUSTER_NAMELEN); + node->ctime = mstime(); + node->configEpoch = 0; + node->flags = flags; + memset(node->slots,0,sizeof(node->slots)); + node->slot_info_pairs = NULL; + node->slot_info_pairs_count = 0; + node->numslots = 0; + node->numslaves = 0; + node->slaves = NULL; + node->slaveof = NULL; + node->last_in_ping_gossip = 0; + node->ping_sent = node->pong_received = 0; + node->data_received = 0; + node->fail_time = 0; + node->link = NULL; + node->inbound_link = NULL; + memset(node->ip,0,sizeof(node->ip)); + node->hostname = sdsempty(); + node->human_nodename = sdsempty(); + node->tcp_port = 0; + node->cport = 0; + node->tls_port = 0; + node->fail_reports = listCreate(); + node->voted_time = 0; + node->orphaned_time = 0; + node->repl_offset_time = 0; + node->repl_offset = 0; + listSetFreeMethod(node->fail_reports,zfree); + return node; +} + +/* This function is called every time we get a failure report from a node. + * The side effect is to populate the fail_reports list (or to update + * the timestamp of an existing report). + * + * 'failing' is the node that is in failure state according to the + * 'sender' node. + * + * The function returns 0 if it just updates a timestamp of an existing + * failure report from the same sender. 1 is returned if a new failure + * report is created. */ +int clusterNodeAddFailureReport(clusterNode *failing, clusterNode *sender) { + list *l = failing->fail_reports; + listNode *ln; + listIter li; + clusterNodeFailReport *fr; + + /* If a failure report from the same sender already exists, just update + * the timestamp. */ + listRewind(l,&li); + while ((ln = listNext(&li)) != NULL) { + fr = ln->value; + if (fr->node == sender) { + fr->time = mstime(); + return 0; + } + } + + /* Otherwise create a new report. */ + fr = zmalloc(sizeof(*fr)); + fr->node = sender; + fr->time = mstime(); + listAddNodeTail(l,fr); + return 1; +} + +/* Remove failure reports that are too old, where too old means reasonably + * older than the global node timeout. Note that anyway for a node to be + * flagged as FAIL we need to have a local PFAIL state that is at least + * older than the global node timeout, so we don't just trust the number + * of failure reports from other nodes. */ +void clusterNodeCleanupFailureReports(clusterNode *node) { + list *l = node->fail_reports; + listNode *ln; + listIter li; + clusterNodeFailReport *fr; + mstime_t maxtime = server.cluster_node_timeout * + CLUSTER_FAIL_REPORT_VALIDITY_MULT; + mstime_t now = mstime(); + + listRewind(l,&li); + while ((ln = listNext(&li)) != NULL) { + fr = ln->value; + if (now - fr->time > maxtime) listDelNode(l,ln); + } +} + +/* Remove the failing report for 'node' if it was previously considered + * failing by 'sender'. This function is called when a node informs us via + * gossip that a node is OK from its point of view (no FAIL or PFAIL flags). + * + * Note that this function is called relatively often as it gets called even + * when there are no nodes failing, and is O(N), however when the cluster is + * fine the failure reports list is empty so the function runs in constant + * time. + * + * The function returns 1 if the failure report was found and removed. + * Otherwise 0 is returned. */ +int clusterNodeDelFailureReport(clusterNode *node, clusterNode *sender) { + list *l = node->fail_reports; + listNode *ln; + listIter li; + clusterNodeFailReport *fr; + + /* Search for a failure report from this sender. */ + listRewind(l,&li); + while ((ln = listNext(&li)) != NULL) { + fr = ln->value; + if (fr->node == sender) break; + } + if (!ln) return 0; /* No failure report from this sender. */ + + /* Remove the failure report. */ + listDelNode(l,ln); + clusterNodeCleanupFailureReports(node); + return 1; +} + +/* Return the number of external nodes that believe 'node' is failing, + * not including this node, that may have a PFAIL or FAIL state for this + * node as well. */ +int clusterNodeFailureReportsCount(clusterNode *node) { + clusterNodeCleanupFailureReports(node); + return listLength(node->fail_reports); +} + +int clusterNodeRemoveSlave(clusterNode *master, clusterNode *slave) { + int j; + + for (j = 0; j < master->numslaves; j++) { + if (master->slaves[j] == slave) { + if ((j+1) < master->numslaves) { + int remaining_slaves = (master->numslaves - j) - 1; + memmove(master->slaves+j,master->slaves+(j+1), + (sizeof(*master->slaves) * remaining_slaves)); + } + master->numslaves--; + if (master->numslaves == 0) + master->flags &= ~CLUSTER_NODE_MIGRATE_TO; + return C_OK; + } + } + return C_ERR; +} + +int clusterNodeAddSlave(clusterNode *master, clusterNode *slave) { + int j; + + /* If it's already a slave, don't add it again. */ + for (j = 0; j < master->numslaves; j++) + if (master->slaves[j] == slave) return C_ERR; + master->slaves = zrealloc(master->slaves, + sizeof(clusterNode*)*(master->numslaves+1)); + master->slaves[master->numslaves] = slave; + master->numslaves++; + master->flags |= CLUSTER_NODE_MIGRATE_TO; + return C_OK; +} + +int clusterCountNonFailingSlaves(clusterNode *n) { + int j, okslaves = 0; + + for (j = 0; j < n->numslaves; j++) + if (!nodeFailed(n->slaves[j])) okslaves++; + return okslaves; +} + +/* Low level cleanup of the node structure. Only called by clusterDelNode(). */ +void freeClusterNode(clusterNode *n) { + sds nodename; + int j; + + /* If the node has associated slaves, we have to set + * all the slaves->slaveof fields to NULL (unknown). */ + for (j = 0; j < n->numslaves; j++) + n->slaves[j]->slaveof = NULL; + + /* Remove this node from the list of slaves of its master. */ + if (nodeIsSlave(n) && n->slaveof) clusterNodeRemoveSlave(n->slaveof,n); + + /* Unlink from the set of nodes. */ + nodename = sdsnewlen(n->name, CLUSTER_NAMELEN); + serverAssert(dictDelete(server.cluster->nodes,nodename) == DICT_OK); + sdsfree(nodename); + sdsfree(n->hostname); + sdsfree(n->human_nodename); + + /* Release links and associated data structures. */ + if (n->link) freeClusterLink(n->link); + if (n->inbound_link) freeClusterLink(n->inbound_link); + listRelease(n->fail_reports); + zfree(n->slaves); + zfree(n); +} + +/* Add a node to the nodes hash table */ +void clusterAddNode(clusterNode *node) { + int retval; + + retval = dictAdd(server.cluster->nodes, + sdsnewlen(node->name,CLUSTER_NAMELEN), node); + serverAssert(retval == DICT_OK); +} + +/* Remove a node from the cluster. The function performs the high level + * cleanup, calling freeClusterNode() for the low level cleanup. + * Here we do the following: + * + * 1) Mark all the slots handled by it as unassigned. + * 2) Remove all the failure reports sent by this node and referenced by + * other nodes. + * 3) Remove the node from the owning shard + * 4) Free the node with freeClusterNode() that will in turn remove it + * from the hash table and from the list of slaves of its master, if + * it is a slave node. + */ +void clusterDelNode(clusterNode *delnode) { + int j; + dictIterator *di; + dictEntry *de; + + /* 1) Mark slots as unassigned. */ + for (j = 0; j < CLUSTER_SLOTS; j++) { + if (server.cluster->importing_slots_from[j] == delnode) + server.cluster->importing_slots_from[j] = NULL; + if (server.cluster->migrating_slots_to[j] == delnode) + server.cluster->migrating_slots_to[j] = NULL; + if (server.cluster->slots[j] == delnode) + clusterDelSlot(j); + } + + /* 2) Remove failure reports. */ + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + + if (node == delnode) continue; + clusterNodeDelFailureReport(node,delnode); + } + dictReleaseIterator(di); + + /* 3) Remove the node from the owning shard */ + clusterRemoveNodeFromShard(delnode); + + /* 4) Free the node, unlinking it from the cluster. */ + freeClusterNode(delnode); +} + +/* Node lookup by name */ +clusterNode *clusterLookupNode(const char *name, int length) { + if (verifyClusterNodeId(name, length) != C_OK) return NULL; + sds s = sdsnewlen(name, length); + dictEntry *de = dictFind(server.cluster->nodes, s); + sdsfree(s); + if (de == NULL) return NULL; + return dictGetVal(de); +} + +/* Get all the nodes in my shard. + * Note that the list returned is not computed on the fly + * via slaveof; rather, it is maintained permanently to + * track the shard membership and its life cycle is tied + * to this Redis process. Therefore, the caller must not + * release the list. */ +list *clusterGetNodesInMyShard(clusterNode *node) { + sds s = sdsnewlen(node->shard_id, CLUSTER_NAMELEN); + dictEntry *de = dictFind(server.cluster->shards,s); + sdsfree(s); + return (de != NULL) ? dictGetVal(de) : NULL; +} + +/* This is only used after the handshake. When we connect a given IP/PORT + * as a result of CLUSTER MEET we don't have the node name yet, so we + * pick a random one, and will fix it when we receive the PONG request using + * this function. */ +void clusterRenameNode(clusterNode *node, char *newname) { + int retval; + sds s = sdsnewlen(node->name, CLUSTER_NAMELEN); + + serverLog(LL_DEBUG,"Renaming node %.40s into %.40s", + node->name, newname); + retval = dictDelete(server.cluster->nodes, s); + sdsfree(s); + serverAssert(retval == DICT_OK); + memcpy(node->name, newname, CLUSTER_NAMELEN); + clusterAddNode(node); + clusterAddNodeToShard(node->shard_id, node); +} + +void clusterAddNodeToShard(const char *shard_id, clusterNode *node) { + sds s = sdsnewlen(shard_id, CLUSTER_NAMELEN); + dictEntry *de = dictFind(server.cluster->shards,s); + if (de == NULL) { + list *l = listCreate(); + listAddNodeTail(l, node); + serverAssert(dictAdd(server.cluster->shards, s, l) == DICT_OK); + } else { + list *l = dictGetVal(de); + if (listSearchKey(l, node) == NULL) { + listAddNodeTail(l, node); + } + sdsfree(s); + } +} + +void clusterRemoveNodeFromShard(clusterNode *node) { + sds s = sdsnewlen(node->shard_id, CLUSTER_NAMELEN); + dictEntry *de = dictFind(server.cluster->shards, s); + if (de != NULL) { + list *l = dictGetVal(de); + listNode *ln = listSearchKey(l, node); + if (ln != NULL) { + listDelNode(l, ln); + } + if (listLength(l) == 0) { + dictDelete(server.cluster->shards, s); + } + } + sdsfree(s); +} + +/* ----------------------------------------------------------------------------- + * CLUSTER config epoch handling + * -------------------------------------------------------------------------- */ + +/* Return the greatest configEpoch found in the cluster, or the current + * epoch if greater than any node configEpoch. */ +uint64_t clusterGetMaxEpoch(void) { + uint64_t max = 0; + dictIterator *di; + dictEntry *de; + + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + if (node->configEpoch > max) max = node->configEpoch; + } + dictReleaseIterator(di); + if (max < server.cluster->currentEpoch) max = server.cluster->currentEpoch; + return max; +} + +/* If this node epoch is zero or is not already the greatest across the + * cluster (from the POV of the local configuration), this function will: + * + * 1) Generate a new config epoch, incrementing the current epoch. + * 2) Assign the new epoch to this node, WITHOUT any consensus. + * 3) Persist the configuration on disk before sending packets with the + * new configuration. + * + * If the new config epoch is generated and assigned, C_OK is returned, + * otherwise C_ERR is returned (since the node has already the greatest + * configuration around) and no operation is performed. + * + * Important note: this function violates the principle that config epochs + * should be generated with consensus and should be unique across the cluster. + * However Redis Cluster uses this auto-generated new config epochs in two + * cases: + * + * 1) When slots are closed after importing. Otherwise resharding would be + * too expensive. + * 2) When CLUSTER FAILOVER is called with options that force a slave to + * failover its master even if there is not master majority able to + * create a new configuration epoch. + * + * Redis Cluster will not explode using this function, even in the case of + * a collision between this node and another node, generating the same + * configuration epoch unilaterally, because the config epoch conflict + * resolution algorithm will eventually move colliding nodes to different + * config epochs. However using this function may violate the "last failover + * wins" rule, so should only be used with care. */ +int clusterBumpConfigEpochWithoutConsensus(void) { + uint64_t maxEpoch = clusterGetMaxEpoch(); + + if (myself->configEpoch == 0 || + myself->configEpoch != maxEpoch) + { + server.cluster->currentEpoch++; + myself->configEpoch = server.cluster->currentEpoch; + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_FSYNC_CONFIG); + serverLog(LL_NOTICE, + "New configEpoch set to %llu", + (unsigned long long) myself->configEpoch); + return C_OK; + } else { + return C_ERR; + } +} + +/* This function is called when this node is a master, and we receive from + * another master a configuration epoch that is equal to our configuration + * epoch. + * + * BACKGROUND + * + * It is not possible that different slaves get the same config + * epoch during a failover election, because the slaves need to get voted + * by a majority. However when we perform a manual resharding of the cluster + * the node will assign a configuration epoch to itself without to ask + * for agreement. Usually resharding happens when the cluster is working well + * and is supervised by the sysadmin, however it is possible for a failover + * to happen exactly while the node we are resharding a slot to assigns itself + * a new configuration epoch, but before it is able to propagate it. + * + * So technically it is possible in this condition that two nodes end with + * the same configuration epoch. + * + * Another possibility is that there are bugs in the implementation causing + * this to happen. + * + * Moreover when a new cluster is created, all the nodes start with the same + * configEpoch. This collision resolution code allows nodes to automatically + * end with a different configEpoch at startup automatically. + * + * In all the cases, we want a mechanism that resolves this issue automatically + * as a safeguard. The same configuration epoch for masters serving different + * set of slots is not harmful, but it is if the nodes end serving the same + * slots for some reason (manual errors or software bugs) without a proper + * failover procedure. + * + * In general we want a system that eventually always ends with different + * masters having different configuration epochs whatever happened, since + * nothing is worse than a split-brain condition in a distributed system. + * + * BEHAVIOR + * + * When this function gets called, what happens is that if this node + * has the lexicographically smaller Node ID compared to the other node + * with the conflicting epoch (the 'sender' node), it will assign itself + * the greatest configuration epoch currently detected among nodes plus 1. + * + * This means that even if there are multiple nodes colliding, the node + * with the greatest Node ID never moves forward, so eventually all the nodes + * end with a different configuration epoch. + */ +void clusterHandleConfigEpochCollision(clusterNode *sender) { + /* Prerequisites: nodes have the same configEpoch and are both masters. */ + if (sender->configEpoch != myself->configEpoch || + !clusterNodeIsMaster(sender) || !clusterNodeIsMaster(myself)) return; + /* Don't act if the colliding node has a smaller Node ID. */ + if (memcmp(sender->name,myself->name,CLUSTER_NAMELEN) <= 0) return; + /* Get the next ID available at the best of this node knowledge. */ + server.cluster->currentEpoch++; + myself->configEpoch = server.cluster->currentEpoch; + clusterSaveConfigOrDie(1); + serverLog(LL_VERBOSE, + "WARNING: configEpoch collision with node %.40s (%s)." + " configEpoch set to %llu", + sender->name,sender->human_nodename, + (unsigned long long) myself->configEpoch); +} + +/* ----------------------------------------------------------------------------- + * CLUSTER nodes blacklist + * + * The nodes blacklist is just a way to ensure that a given node with a given + * Node ID is not re-added before some time elapsed (this time is specified + * in seconds in CLUSTER_BLACKLIST_TTL). + * + * This is useful when we want to remove a node from the cluster completely: + * when CLUSTER FORGET is called, it also puts the node into the blacklist so + * that even if we receive gossip messages from other nodes that still remember + * about the node we want to remove, we don't re-add it before some time. + * + * Currently the CLUSTER_BLACKLIST_TTL is set to 1 minute, this means + * that redis-cli has 60 seconds to send CLUSTER FORGET messages to nodes + * in the cluster without dealing with the problem of other nodes re-adding + * back the node to nodes we already sent the FORGET command to. + * + * The data structure used is a hash table with an sds string representing + * the node ID as key, and the time when it is ok to re-add the node as + * value. + * -------------------------------------------------------------------------- */ + +#define CLUSTER_BLACKLIST_TTL 60 /* 1 minute. */ + + +/* Before of the addNode() or Exists() operations we always remove expired + * entries from the black list. This is an O(N) operation but it is not a + * problem since add / exists operations are called very infrequently and + * the hash table is supposed to contain very little elements at max. + * However without the cleanup during long uptime and with some automated + * node add/removal procedures, entries could accumulate. */ +void clusterBlacklistCleanup(void) { + dictIterator *di; + dictEntry *de; + + di = dictGetSafeIterator(server.cluster->nodes_black_list); + while((de = dictNext(di)) != NULL) { + int64_t expire = dictGetUnsignedIntegerVal(de); + + if (expire < server.unixtime) + dictDelete(server.cluster->nodes_black_list,dictGetKey(de)); + } + dictReleaseIterator(di); +} + +/* Cleanup the blacklist and add a new node ID to the black list. */ +void clusterBlacklistAddNode(clusterNode *node) { + dictEntry *de; + sds id = sdsnewlen(node->name,CLUSTER_NAMELEN); + + clusterBlacklistCleanup(); + if (dictAdd(server.cluster->nodes_black_list,id,NULL) == DICT_OK) { + /* If the key was added, duplicate the sds string representation of + * the key for the next lookup. We'll free it at the end. */ + id = sdsdup(id); + } + de = dictFind(server.cluster->nodes_black_list,id); + dictSetUnsignedIntegerVal(de,time(NULL)+CLUSTER_BLACKLIST_TTL); + sdsfree(id); +} + +/* Return non-zero if the specified node ID exists in the blacklist. + * You don't need to pass an sds string here, any pointer to 40 bytes + * will work. */ +int clusterBlacklistExists(char *nodeid) { + sds id = sdsnewlen(nodeid,CLUSTER_NAMELEN); + int retval; + + clusterBlacklistCleanup(); + retval = dictFind(server.cluster->nodes_black_list,id) != NULL; + sdsfree(id); + return retval; +} + +/* ----------------------------------------------------------------------------- + * CLUSTER messages exchange - PING/PONG and gossip + * -------------------------------------------------------------------------- */ + +/* This function checks if a given node should be marked as FAIL. + * It happens if the following conditions are met: + * + * 1) We received enough failure reports from other master nodes via gossip. + * Enough means that the majority of the masters signaled the node is + * down recently. + * 2) We believe this node is in PFAIL state. + * + * If a failure is detected we also inform the whole cluster about this + * event trying to force every other node to set the FAIL flag for the node. + * + * Note that the form of agreement used here is weak, as we collect the majority + * of masters state during some time, and even if we force agreement by + * propagating the FAIL message, because of partitions we may not reach every + * node. However: + * + * 1) Either we reach the majority and eventually the FAIL state will propagate + * to all the cluster. + * 2) Or there is no majority so no slave promotion will be authorized and the + * FAIL flag will be cleared after some time. + */ +void markNodeAsFailingIfNeeded(clusterNode *node) { + int failures; + int needed_quorum = (server.cluster->size / 2) + 1; + + if (!nodeTimedOut(node)) return; /* We can reach it. */ + if (nodeFailed(node)) return; /* Already FAILing. */ + + failures = clusterNodeFailureReportsCount(node); + /* Also count myself as a voter if I'm a master. */ + if (clusterNodeIsMaster(myself)) failures++; + if (failures < needed_quorum) return; /* No weak agreement from masters. */ + + serverLog(LL_NOTICE, + "Marking node %.40s (%s) as failing (quorum reached).", node->name, node->human_nodename); + + /* Mark the node as failing. */ + node->flags &= ~CLUSTER_NODE_PFAIL; + node->flags |= CLUSTER_NODE_FAIL; + node->fail_time = mstime(); + + /* Broadcast the failing node name to everybody, forcing all the other + * reachable nodes to flag the node as FAIL. + * We do that even if this node is a replica and not a master: anyway + * the failing state is triggered collecting failure reports from masters, + * so here the replica is only helping propagating this status. */ + clusterSendFail(node->name); + clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); +} + +/* This function is called only if a node is marked as FAIL, but we are able + * to reach it again. It checks if there are the conditions to undo the FAIL + * state. */ +void clearNodeFailureIfNeeded(clusterNode *node) { + mstime_t now = mstime(); + + serverAssert(nodeFailed(node)); + + /* For slaves we always clear the FAIL flag if we can contact the + * node again. */ + if (nodeIsSlave(node) || node->numslots == 0) { + serverLog(LL_NOTICE, + "Clear FAIL state for node %.40s (%s):%s is reachable again.", + node->name,node->human_nodename, + nodeIsSlave(node) ? "replica" : "master without slots"); + node->flags &= ~CLUSTER_NODE_FAIL; + clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); + } + + /* If it is a master and... + * 1) The FAIL state is old enough. + * 2) It is yet serving slots from our point of view (not failed over). + * Apparently no one is going to fix these slots, clear the FAIL flag. */ + if (clusterNodeIsMaster(node) && node->numslots > 0 && + (now - node->fail_time) > + (server.cluster_node_timeout * CLUSTER_FAIL_UNDO_TIME_MULT)) + { + serverLog(LL_NOTICE, + "Clear FAIL state for node %.40s (%s): is reachable again and nobody is serving its slots after some time.", + node->name, node->human_nodename); + node->flags &= ~CLUSTER_NODE_FAIL; + clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); + } +} + +/* Return true if we already have a node in HANDSHAKE state matching the + * specified ip address and port number. This function is used in order to + * avoid adding a new handshake node for the same address multiple times. */ +int clusterHandshakeInProgress(char *ip, int port, int cport) { + dictIterator *di; + dictEntry *de; + + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + + if (!nodeInHandshake(node)) continue; + if (!strcasecmp(node->ip,ip) && + getNodeDefaultClientPort(node) == port && + node->cport == cport) break; + } + dictReleaseIterator(di); + return de != NULL; +} + +/* Start a handshake with the specified address if there is not one + * already in progress. Returns non-zero if the handshake was actually + * started. On error zero is returned and errno is set to one of the + * following values: + * + * EAGAIN - There is already a handshake in progress for this address. + * EINVAL - IP or port are not valid. */ +int clusterStartHandshake(char *ip, int port, int cport) { + clusterNode *n; + char norm_ip[NET_IP_STR_LEN]; + struct sockaddr_storage sa; + + /* IP sanity check */ + if (inet_pton(AF_INET,ip, + &(((struct sockaddr_in *)&sa)->sin_addr))) + { + sa.ss_family = AF_INET; + } else if (inet_pton(AF_INET6,ip, + &(((struct sockaddr_in6 *)&sa)->sin6_addr))) + { + sa.ss_family = AF_INET6; + } else { + errno = EINVAL; + return 0; + } + + /* Port sanity check */ + if (port <= 0 || port > 65535 || cport <= 0 || cport > 65535) { + errno = EINVAL; + return 0; + } + + /* Set norm_ip as the normalized string representation of the node + * IP address. */ + memset(norm_ip,0,NET_IP_STR_LEN); + if (sa.ss_family == AF_INET) + inet_ntop(AF_INET, + (void*)&(((struct sockaddr_in *)&sa)->sin_addr), + norm_ip,NET_IP_STR_LEN); + else + inet_ntop(AF_INET6, + (void*)&(((struct sockaddr_in6 *)&sa)->sin6_addr), + norm_ip,NET_IP_STR_LEN); + + if (clusterHandshakeInProgress(norm_ip,port,cport)) { + errno = EAGAIN; + return 0; + } + + /* Add the node with a random address (NULL as first argument to + * createClusterNode()). Everything will be fixed during the + * handshake. */ + n = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE|CLUSTER_NODE_MEET); + memcpy(n->ip,norm_ip,sizeof(n->ip)); + if (server.tls_cluster) { + n->tls_port = port; + } else { + n->tcp_port = port; + } + n->cport = cport; + clusterAddNode(n); + return 1; +} + +static void getClientPortFromClusterMsg(clusterMsg *hdr, int *tls_port, int *tcp_port) { + if (server.tls_cluster) { + *tls_port = ntohs(hdr->port); + *tcp_port = ntohs(hdr->pport); + } else { + *tls_port = ntohs(hdr->pport); + *tcp_port = ntohs(hdr->port); + } +} + +static void getClientPortFromGossip(clusterMsgDataGossip *g, int *tls_port, int *tcp_port) { + if (server.tls_cluster) { + *tls_port = ntohs(g->port); + *tcp_port = ntohs(g->pport); + } else { + *tls_port = ntohs(g->pport); + *tcp_port = ntohs(g->port); + } +} + +/* Process the gossip section of PING or PONG packets. + * Note that this function assumes that the packet is already sanity-checked + * by the caller, not in the content of the gossip section, but in the + * length. */ +void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) { + uint16_t count = ntohs(hdr->count); + clusterMsgDataGossip *g = (clusterMsgDataGossip*) hdr->data.ping.gossip; + clusterNode *sender = link->node ? link->node : clusterLookupNode(hdr->sender, CLUSTER_NAMELEN); + + while(count--) { + uint16_t flags = ntohs(g->flags); + clusterNode *node; + sds ci; + + if (server.verbosity == LL_DEBUG) { + ci = representClusterNodeFlags(sdsempty(), flags); + serverLog(LL_DEBUG,"GOSSIP %.40s %s:%d@%d %s", + g->nodename, + g->ip, + ntohs(g->port), + ntohs(g->cport), + ci); + sdsfree(ci); + } + + /* Convert port and pport into TCP port and TLS port. */ + int msg_tls_port, msg_tcp_port; + getClientPortFromGossip(g, &msg_tls_port, &msg_tcp_port); + + /* Update our state accordingly to the gossip sections */ + node = clusterLookupNode(g->nodename, CLUSTER_NAMELEN); + if (node) { + /* We already know this node. + Handle failure reports, only when the sender is a master. */ + if (sender && clusterNodeIsMaster(sender) && node != myself) { + if (flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) { + if (clusterNodeAddFailureReport(node,sender)) { + serverLog(LL_VERBOSE, + "Node %.40s (%s) reported node %.40s (%s) as not reachable.", + sender->name, sender->human_nodename, node->name, node->human_nodename); + } + markNodeAsFailingIfNeeded(node); + } else { + if (clusterNodeDelFailureReport(node,sender)) { + serverLog(LL_VERBOSE, + "Node %.40s (%s) reported node %.40s (%s) is back online.", + sender->name, sender->human_nodename, node->name, node->human_nodename); + } + } + } + + /* If from our POV the node is up (no failure flags are set), + * we have no pending ping for the node, nor we have failure + * reports for this node, update the last pong time with the + * one we see from the other nodes. */ + if (!(flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) && + node->ping_sent == 0 && + clusterNodeFailureReportsCount(node) == 0) + { + mstime_t pongtime = ntohl(g->pong_received); + pongtime *= 1000; /* Convert back to milliseconds. */ + + /* Replace the pong time with the received one only if + * it's greater than our view but is not in the future + * (with 500 milliseconds tolerance) from the POV of our + * clock. */ + if (pongtime <= (server.mstime+500) && + pongtime > node->pong_received) + { + node->pong_received = pongtime; + } + } + + /* If we already know this node, but it is not reachable, and + * we see a different address in the gossip section of a node that + * can talk with this other node, update the address, disconnect + * the old link if any, so that we'll attempt to connect with the + * new address. */ + if (node->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL) && + !(flags & CLUSTER_NODE_NOADDR) && + !(flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) && + (strcasecmp(node->ip,g->ip) || + node->tls_port != (server.tls_cluster ? ntohs(g->port) : ntohs(g->pport)) || + node->tcp_port != (server.tls_cluster ? ntohs(g->pport) : ntohs(g->port)) || + node->cport != ntohs(g->cport))) + { + if (node->link) freeClusterLink(node->link); + memcpy(node->ip,g->ip,NET_IP_STR_LEN); + node->tcp_port = msg_tcp_port; + node->tls_port = msg_tls_port; + node->cport = ntohs(g->cport); + node->flags &= ~CLUSTER_NODE_NOADDR; + } + } else { + /* If it's not in NOADDR state and we don't have it, we + * add it to our trusted dict with exact nodeid and flag. + * Note that we cannot simply start a handshake against + * this IP/PORT pairs, since IP/PORT can be reused already, + * otherwise we risk joining another cluster. + * + * Note that we require that the sender of this gossip message + * is a well known node in our cluster, otherwise we risk + * joining another cluster. */ + if (sender && + !(flags & CLUSTER_NODE_NOADDR) && + !clusterBlacklistExists(g->nodename)) + { + clusterNode *node; + node = createClusterNode(g->nodename, flags); + memcpy(node->ip,g->ip,NET_IP_STR_LEN); + node->tcp_port = msg_tcp_port; + node->tls_port = msg_tls_port; + node->cport = ntohs(g->cport); + clusterAddNode(node); + clusterAddNodeToShard(node->shard_id, node); + } + } + + /* Next node */ + g++; + } +} + +/* IP -> string conversion. 'buf' is supposed to at least be 46 bytes. + * If 'announced_ip' length is non-zero, it is used instead of extracting + * the IP from the socket peer address. */ +int nodeIp2String(char *buf, clusterLink *link, char *announced_ip) { + if (announced_ip[0] != '\0') { + memcpy(buf,announced_ip,NET_IP_STR_LEN); + buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */ + return C_OK; + } else { + if (connAddrPeerName(link->conn, buf, NET_IP_STR_LEN, NULL) == -1) { + serverLog(LL_NOTICE, "Error converting peer IP to string: %s", + link->conn ? connGetLastError(link->conn) : "no link"); + return C_ERR; + } + return C_OK; + } +} + +/* Update the node address to the IP address that can be extracted + * from link->fd, or if hdr->myip is non empty, to the address the node + * is announcing us. The port is taken from the packet header as well. + * + * If the address or port changed, disconnect the node link so that we'll + * connect again to the new address. + * + * If the ip/port pair are already correct no operation is performed at + * all. + * + * The function returns 0 if the node address is still the same, + * otherwise 1 is returned. */ +int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link, + clusterMsg *hdr) +{ + char ip[NET_IP_STR_LEN] = {0}; + int cport = ntohs(hdr->cport); + int tcp_port, tls_port; + getClientPortFromClusterMsg(hdr, &tls_port, &tcp_port); + + /* We don't proceed if the link is the same as the sender link, as this + * function is designed to see if the node link is consistent with the + * symmetric link that is used to receive PINGs from the node. + * + * As a side effect this function never frees the passed 'link', so + * it is safe to call during packet processing. */ + if (link == node->link) return 0; + + /* If the peer IP is unavailable for some reasons like invalid fd or closed + * link, just give up the update this time, and the update will be retried + * in the next round of PINGs */ + if (nodeIp2String(ip,link,hdr->myip) == C_ERR) return 0; + + if (node->tcp_port == tcp_port && node->cport == cport && node->tls_port == tls_port && + strcmp(ip,node->ip) == 0) return 0; + + /* IP / port is different, update it. */ + memcpy(node->ip,ip,sizeof(ip)); + node->tcp_port = tcp_port; + node->tls_port = tls_port; + node->cport = cport; + if (node->link) freeClusterLink(node->link); + node->flags &= ~CLUSTER_NODE_NOADDR; + serverLog(LL_NOTICE,"Address updated for node %.40s (%s), now %s:%d", + node->name, node->human_nodename, node->ip, getNodeDefaultClientPort(node)); + + /* Check if this is our master and we have to change the + * replication target as well. */ + if (nodeIsSlave(myself) && myself->slaveof == node) + replicationSetMaster(node->ip, getNodeDefaultReplicationPort(node)); + return 1; +} + +/* Reconfigure the specified node 'n' as a master. This function is called when + * a node that we believed to be a slave is now acting as master in order to + * update the state of the node. */ +void clusterSetNodeAsMaster(clusterNode *n) { + if (clusterNodeIsMaster(n)) return; + + if (n->slaveof) { + clusterNodeRemoveSlave(n->slaveof,n); + if (n != myself) n->flags |= CLUSTER_NODE_MIGRATE_TO; + } + n->flags &= ~CLUSTER_NODE_SLAVE; + n->flags |= CLUSTER_NODE_MASTER; + n->slaveof = NULL; + + /* Update config and state. */ + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_UPDATE_STATE); +} + +/* This function is called when we receive a master configuration via a + * PING, PONG or UPDATE packet. What we receive is a node, a configEpoch of the + * node, and the set of slots claimed under this configEpoch. + * + * What we do is to rebind the slots with newer configuration compared to our + * local configuration, and if needed, we turn ourself into a replica of the + * node (see the function comments for more info). + * + * The 'sender' is the node for which we received a configuration update. + * Sometimes it is not actually the "Sender" of the information, like in the + * case we receive the info via an UPDATE packet. */ +void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoch, unsigned char *slots) { + int j; + clusterNode *curmaster = NULL, *newmaster = NULL; + /* The dirty slots list is a list of slots for which we lose the ownership + * while having still keys inside. This usually happens after a failover + * or after a manual cluster reconfiguration operated by the admin. + * + * If the update message is not able to demote a master to slave (in this + * case we'll resync with the master updating the whole key space), we + * need to delete all the keys in the slots we lost ownership. */ + uint16_t dirty_slots[CLUSTER_SLOTS]; + int dirty_slots_count = 0; + + /* We should detect if sender is new master of our shard. + * We will know it if all our slots were migrated to sender, and sender + * has no slots except ours */ + int sender_slots = 0; + int migrated_our_slots = 0; + + /* Here we set curmaster to this node or the node this node + * replicates to if it's a slave. In the for loop we are + * interested to check if slots are taken away from curmaster. */ + curmaster = clusterNodeIsMaster(myself) ? myself : myself->slaveof; + + if (sender == myself) { + serverLog(LL_NOTICE,"Discarding UPDATE message about myself."); + return; + } + + for (j = 0; j < CLUSTER_SLOTS; j++) { + if (bitmapTestBit(slots,j)) { + sender_slots++; + + /* The slot is already bound to the sender of this message. */ + if (server.cluster->slots[j] == sender) { + bitmapClearBit(server.cluster->owner_not_claiming_slot, j); + continue; + } + + /* The slot is in importing state, it should be modified only + * manually via redis-cli (example: a resharding is in progress + * and the migrating side slot was already closed and is advertising + * a new config. We still want the slot to be closed manually). */ + if (server.cluster->importing_slots_from[j]) continue; + + /* We rebind the slot to the new node claiming it if: + * 1) The slot was unassigned or the previous owner no longer owns the slot or + * the new node claims it with a greater configEpoch. + * 2) We are not currently importing the slot. */ + if (isSlotUnclaimed(j) || + server.cluster->slots[j]->configEpoch < senderConfigEpoch) + { + /* Was this slot mine, and still contains keys? Mark it as + * a dirty slot. */ + if (server.cluster->slots[j] == myself && + countKeysInSlot(j) && + sender != myself) + { + dirty_slots[dirty_slots_count] = j; + dirty_slots_count++; + } + + if (server.cluster->slots[j] == curmaster) { + newmaster = sender; + migrated_our_slots++; + } + clusterDelSlot(j); + clusterAddSlot(sender,j); + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_UPDATE_STATE| + CLUSTER_TODO_FSYNC_CONFIG); + } + } else if (server.cluster->slots[j] == sender) { + /* The slot is currently bound to the sender but the sender is no longer + * claiming it. We don't want to unbind the slot yet as it can cause the cluster + * to move to FAIL state and also throw client error. Keeping the slot bound to + * the previous owner will cause a few client side redirects, but won't throw + * any errors. We will keep track of the uncertainty in ownership to avoid + * propagating misinformation about this slot's ownership using UPDATE + * messages. */ + bitmapSetBit(server.cluster->owner_not_claiming_slot, j); + } + } + + /* After updating the slots configuration, don't do any actual change + * in the state of the server if a module disabled Redis Cluster + * keys redirections. */ + if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION) + return; + + /* If at least one slot was reassigned from a node to another node + * with a greater configEpoch, it is possible that: + * 1) We are a master left without slots. This means that we were + * failed over and we should turn into a replica of the new + * master. + * 2) We are a slave and our master is left without slots. We need + * to replicate to the new slots owner. */ + if (newmaster && curmaster->numslots == 0 && + (server.cluster_allow_replica_migration || + sender_slots == migrated_our_slots)) { + serverLog(LL_NOTICE, + "Configuration change detected. Reconfiguring myself " + "as a replica of %.40s (%s)", sender->name, sender->human_nodename); + clusterSetMaster(sender); + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_UPDATE_STATE| + CLUSTER_TODO_FSYNC_CONFIG); + } else if (myself->slaveof && myself->slaveof->slaveof && + /* In some rare case when CLUSTER FAILOVER TAKEOVER is used, it + * can happen that myself is a replica of a replica of myself. If + * this happens, we do nothing to avoid a crash and wait for the + * admin to repair the cluster. */ + myself->slaveof->slaveof != myself) + { + /* Safeguard against sub-replicas. A replica's master can turn itself + * into a replica if its last slot is removed. If no other node takes + * over the slot, there is nothing else to trigger replica migration. */ + serverLog(LL_NOTICE, + "I'm a sub-replica! Reconfiguring myself as a replica of grandmaster %.40s (%s)", + myself->slaveof->slaveof->name, myself->slaveof->slaveof->human_nodename); + clusterSetMaster(myself->slaveof->slaveof); + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_UPDATE_STATE| + CLUSTER_TODO_FSYNC_CONFIG); + } else if (dirty_slots_count) { + /* If we are here, we received an update message which removed + * ownership for certain slots we still have keys about, but still + * we are serving some slots, so this master node was not demoted to + * a slave. + * + * In order to maintain a consistent state between keys and slots + * we need to remove all the keys from the slots we lost. */ + for (j = 0; j < dirty_slots_count; j++) + delKeysInSlot(dirty_slots[j]); + } +} + +/* Cluster ping extensions. + * + * The ping/pong/meet messages support arbitrary extensions to add additional + * metadata to the messages that are sent between the various nodes in the + * cluster. The extensions take the form: + * [ Header length + type (8 bytes) ] + * [ Extension information (Arbitrary length, but must be 8 byte padded) ] + */ + + +/* Returns the length of a given extension */ +static uint32_t getPingExtLength(clusterMsgPingExt *ext) { + return ntohl(ext->length); +} + +/* Returns the initial position of ping extensions. May return an invalid + * address if there are no ping extensions. */ +static clusterMsgPingExt *getInitialPingExt(clusterMsg *hdr, int count) { + clusterMsgPingExt *initial = (clusterMsgPingExt*) &(hdr->data.ping.gossip[count]); + return initial; +} + +/* Given a current ping extension, returns the start of the next extension. May return + * an invalid address if there are no further ping extensions. */ +static clusterMsgPingExt *getNextPingExt(clusterMsgPingExt *ext) { + clusterMsgPingExt *next = (clusterMsgPingExt *) (((char *) ext) + getPingExtLength(ext)); + return next; +} + +/* All PING extensions must be 8-byte aligned */ +uint32_t getAlignedPingExtSize(uint32_t dataSize) { + + return sizeof(clusterMsgPingExt) + EIGHT_BYTE_ALIGN(dataSize); +} + +uint32_t getHostnamePingExtSize(void) { + if (sdslen(myself->hostname) == 0) { + return 0; + } + return getAlignedPingExtSize(sdslen(myself->hostname) + 1); +} + +uint32_t getHumanNodenamePingExtSize(void) { + if (sdslen(myself->human_nodename) == 0) { + return 0; + } + return getAlignedPingExtSize(sdslen(myself->human_nodename) + 1); +} + +uint32_t getShardIdPingExtSize(void) { + return getAlignedPingExtSize(sizeof(clusterMsgPingExtShardId)); +} + +uint32_t getForgottenNodeExtSize(void) { + return getAlignedPingExtSize(sizeof(clusterMsgPingExtForgottenNode)); +} + +void *preparePingExt(clusterMsgPingExt *ext, uint16_t type, uint32_t length) { + ext->type = htons(type); + ext->length = htonl(length); + return &ext->ext[0]; +} + +clusterMsgPingExt *nextPingExt(clusterMsgPingExt *ext) { + return (clusterMsgPingExt *)((char*)ext + ntohl(ext->length)); +} + +/* 1. If a NULL hdr is provided, compute the extension size; + * 2. If a non-NULL hdr is provided, write the hostname ping + * extension at the start of the cursor. This function + * will update the cursor to point to the end of the + * written extension and will return the amount of bytes + * written. */ +uint32_t writePingExt(clusterMsg *hdr, int gossipcount) { + uint16_t extensions = 0; + uint32_t totlen = 0; + clusterMsgPingExt *cursor = NULL; + /* Set the initial extension position */ + if (hdr != NULL) { + cursor = getInitialPingExt(hdr, gossipcount); + } + + /* hostname is optional */ + if (sdslen(myself->hostname) != 0) { + if (cursor != NULL) { + /* Populate hostname */ + clusterMsgPingExtHostname *ext = preparePingExt(cursor, CLUSTERMSG_EXT_TYPE_HOSTNAME, getHostnamePingExtSize()); + memcpy(ext->hostname, myself->hostname, sdslen(myself->hostname)); + + /* Move the write cursor */ + cursor = nextPingExt(cursor); + } + + totlen += getHostnamePingExtSize(); + extensions++; + } + + if (sdslen(myself->human_nodename) != 0) { + if (cursor != NULL) { + /* Populate human_nodename */ + clusterMsgPingExtHumanNodename *ext = preparePingExt(cursor, CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME, getHumanNodenamePingExtSize()); + memcpy(ext->human_nodename, myself->human_nodename, sdslen(myself->human_nodename)); + + /* Move the write cursor */ + cursor = nextPingExt(cursor); + } + + totlen += getHumanNodenamePingExtSize(); + extensions++; + } + + /* Gossip forgotten nodes */ + if (dictSize(server.cluster->nodes_black_list) > 0) { + dictIterator *di = dictGetIterator(server.cluster->nodes_black_list); + dictEntry *de; + while ((de = dictNext(di)) != NULL) { + if (cursor != NULL) { + uint64_t expire = dictGetUnsignedIntegerVal(de); + if ((time_t)expire < server.unixtime) continue; /* already expired */ + uint64_t ttl = expire - server.unixtime; + clusterMsgPingExtForgottenNode *ext = preparePingExt(cursor, CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE, getForgottenNodeExtSize()); + memcpy(ext->name, dictGetKey(de), CLUSTER_NAMELEN); + ext->ttl = htonu64(ttl); + + /* Move the write cursor */ + cursor = nextPingExt(cursor); + } + totlen += getForgottenNodeExtSize(); + extensions++; + } + dictReleaseIterator(di); + } + + /* Populate shard_id */ + if (cursor != NULL) { + clusterMsgPingExtShardId *ext = preparePingExt(cursor, CLUSTERMSG_EXT_TYPE_SHARDID, getShardIdPingExtSize()); + memcpy(ext->shard_id, myself->shard_id, CLUSTER_NAMELEN); + + /* Move the write cursor */ + cursor = nextPingExt(cursor); + } + totlen += getShardIdPingExtSize(); + extensions++; + + if (hdr != NULL) { + if (extensions != 0) { + hdr->mflags[0] |= CLUSTERMSG_FLAG0_EXT_DATA; + } + hdr->extensions = htons(extensions); + } + + return totlen; +} + +/* We previously validated the extensions, so this function just needs to + * handle the extensions. */ +void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) { + clusterNode *sender = link->node ? link->node : clusterLookupNode(hdr->sender, CLUSTER_NAMELEN); + char *ext_hostname = NULL; + char *ext_humannodename = NULL; + char *ext_shardid = NULL; + uint16_t extensions = ntohs(hdr->extensions); + /* Loop through all the extensions and process them */ + clusterMsgPingExt *ext = getInitialPingExt(hdr, ntohs(hdr->count)); + while (extensions--) { + uint16_t type = ntohs(ext->type); + if (type == CLUSTERMSG_EXT_TYPE_HOSTNAME) { + clusterMsgPingExtHostname *hostname_ext = (clusterMsgPingExtHostname *) &(ext->ext[0].hostname); + ext_hostname = hostname_ext->hostname; + } else if (type == CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME) { + clusterMsgPingExtHumanNodename *humannodename_ext = (clusterMsgPingExtHumanNodename *) &(ext->ext[0].human_nodename); + ext_humannodename = humannodename_ext->human_nodename; + } else if (type == CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE) { + clusterMsgPingExtForgottenNode *forgotten_node_ext = &(ext->ext[0].forgotten_node); + clusterNode *n = clusterLookupNode(forgotten_node_ext->name, CLUSTER_NAMELEN); + if (n && n != myself && !(nodeIsSlave(myself) && myself->slaveof == n)) { + sds id = sdsnewlen(forgotten_node_ext->name, CLUSTER_NAMELEN); + dictEntry *de = dictAddOrFind(server.cluster->nodes_black_list, id); + uint64_t expire = server.unixtime + ntohu64(forgotten_node_ext->ttl); + dictSetUnsignedIntegerVal(de, expire); + clusterDelNode(n); + clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE| + CLUSTER_TODO_SAVE_CONFIG); + } + } else if (type == CLUSTERMSG_EXT_TYPE_SHARDID) { + clusterMsgPingExtShardId *shardid_ext = (clusterMsgPingExtShardId *) &(ext->ext[0].shard_id); + ext_shardid = shardid_ext->shard_id; + } else { + /* Unknown type, we will ignore it but log what happened. */ + serverLog(LL_WARNING, "Received unknown extension type %d", type); + } + + /* We know this will be valid since we validated it ahead of time */ + ext = getNextPingExt(ext); + } + + /* If the node did not send us a hostname extension, assume + * they don't have an announced hostname. Otherwise, we'll + * set it now. */ + updateAnnouncedHostname(sender, ext_hostname); + updateAnnouncedHumanNodename(sender, ext_humannodename); + /* If the node did not send us a shard-id extension, it means the sender + * does not support it (old version), node->shard_id is randomly generated. + * A cluster-wide consensus for the node's shard_id is not necessary. + * The key is maintaining consistency of the shard_id on each individual 7.2 node. + * As the cluster progressively upgrades to version 7.2, we can expect the shard_ids + * across all nodes to naturally converge and align. + * + * If sender is a replica, set the shard_id to the shard_id of its master. + * Otherwise, we'll set it now. */ + if (ext_shardid == NULL) ext_shardid = clusterNodeGetMaster(sender)->shard_id; + + updateShardId(sender, ext_shardid); +} + +static clusterNode *getNodeFromLinkAndMsg(clusterLink *link, clusterMsg *hdr) { + clusterNode *sender; + if (link->node && !nodeInHandshake(link->node)) { + /* If the link has an associated node, use that so that we don't have to look it + * up every time, except when the node is still in handshake, the node still has + * a random name thus not truly "known". */ + sender = link->node; + } else { + /* Otherwise, fetch sender based on the message */ + sender = clusterLookupNode(hdr->sender, CLUSTER_NAMELEN); + /* We know the sender node but haven't associate it with the link. This must + * be an inbound link because only for inbound links we didn't know which node + * to associate when they were created. */ + if (sender && !link->node) { + setClusterNodeToInboundClusterLink(sender, link); + } + } + return sender; +} + +/* When this function is called, there is a packet to process starting + * at link->rcvbuf. Releasing the buffer is up to the caller, so this + * function should just handle the higher level stuff of processing the + * packet, modifying the cluster state if needed. + * + * The function returns 1 if the link is still valid after the packet + * was processed, otherwise 0 if the link was freed since the packet + * processing lead to some inconsistency error (for instance a PONG + * received from the wrong sender ID). */ +int clusterProcessPacket(clusterLink *link) { + clusterMsg *hdr = (clusterMsg*) link->rcvbuf; + uint32_t totlen = ntohl(hdr->totlen); + uint16_t type = ntohs(hdr->type); + mstime_t now = mstime(); + + if (type < CLUSTERMSG_TYPE_COUNT) + server.cluster->stats_bus_messages_received[type]++; + serverLog(LL_DEBUG,"--- Processing packet of type %s, %lu bytes", + clusterGetMessageTypeString(type), (unsigned long) totlen); + + /* Perform sanity checks */ + if (totlen < 16) return 1; /* At least signature, version, totlen, count. */ + if (totlen > link->rcvbuf_len) return 1; + + if (ntohs(hdr->ver) != CLUSTER_PROTO_VER) { + /* Can't handle messages of different versions. */ + return 1; + } + + if (type == server.cluster_drop_packet_filter) { + serverLog(LL_WARNING, "Dropping packet that matches debug drop filter"); + return 1; + } + + uint16_t flags = ntohs(hdr->flags); + uint16_t extensions = ntohs(hdr->extensions); + uint64_t senderCurrentEpoch = 0, senderConfigEpoch = 0; + uint32_t explen; /* expected length of this packet */ + clusterNode *sender; + + if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_PONG || + type == CLUSTERMSG_TYPE_MEET) + { + uint16_t count = ntohs(hdr->count); + + explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); + explen += (sizeof(clusterMsgDataGossip)*count); + + /* If there is extension data, which doesn't have a fixed length, + * loop through them and validate the length of it now. */ + if (hdr->mflags[0] & CLUSTERMSG_FLAG0_EXT_DATA) { + clusterMsgPingExt *ext = getInitialPingExt(hdr, count); + while (extensions--) { + uint16_t extlen = getPingExtLength(ext); + if (extlen % 8 != 0) { + serverLog(LL_WARNING, "Received a %s packet without proper padding (%d bytes)", + clusterGetMessageTypeString(type), (int) extlen); + return 1; + } + if ((totlen - explen) < extlen) { + serverLog(LL_WARNING, "Received invalid %s packet with extension data that exceeds " + "total packet length (%lld)", clusterGetMessageTypeString(type), + (unsigned long long) totlen); + return 1; + } + explen += extlen; + ext = getNextPingExt(ext); + } + } + } else if (type == CLUSTERMSG_TYPE_FAIL) { + explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); + explen += sizeof(clusterMsgDataFail); + } else if (type == CLUSTERMSG_TYPE_PUBLISH || type == CLUSTERMSG_TYPE_PUBLISHSHARD) { + explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); + explen += sizeof(clusterMsgDataPublish) - + 8 + + ntohl(hdr->data.publish.msg.channel_len) + + ntohl(hdr->data.publish.msg.message_len); + } else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST || + type == CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK || + type == CLUSTERMSG_TYPE_MFSTART) + { + explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); + } else if (type == CLUSTERMSG_TYPE_UPDATE) { + explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); + explen += sizeof(clusterMsgDataUpdate); + } else if (type == CLUSTERMSG_TYPE_MODULE) { + explen = sizeof(clusterMsg)-sizeof(union clusterMsgData); + explen += sizeof(clusterMsgModule) - + 3 + ntohl(hdr->data.module.msg.len); + } else { + /* We don't know this type of packet, so we assume it's well formed. */ + explen = totlen; + } + + if (totlen != explen) { + serverLog(LL_WARNING, "Received invalid %s packet of length %lld but expected length %lld", + clusterGetMessageTypeString(type), (unsigned long long) totlen, (unsigned long long) explen); + return 1; + } + + sender = getNodeFromLinkAndMsg(link, hdr); + + /* Update the last time we saw any data from this node. We + * use this in order to avoid detecting a timeout from a node that + * is just sending a lot of data in the cluster bus, for instance + * because of Pub/Sub. */ + if (sender) sender->data_received = now; + + if (sender && !nodeInHandshake(sender)) { + /* Update our currentEpoch if we see a newer epoch in the cluster. */ + senderCurrentEpoch = ntohu64(hdr->currentEpoch); + senderConfigEpoch = ntohu64(hdr->configEpoch); + if (senderCurrentEpoch > server.cluster->currentEpoch) + server.cluster->currentEpoch = senderCurrentEpoch; + /* Update the sender configEpoch if it is publishing a newer one. */ + if (senderConfigEpoch > sender->configEpoch) { + sender->configEpoch = senderConfigEpoch; + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_FSYNC_CONFIG); + } + /* Update the replication offset info for this node. */ + sender->repl_offset = ntohu64(hdr->offset); + sender->repl_offset_time = now; + /* If we are a slave performing a manual failover and our master + * sent its offset while already paused, populate the MF state. */ + if (server.cluster->mf_end && + nodeIsSlave(myself) && + myself->slaveof == sender && + hdr->mflags[0] & CLUSTERMSG_FLAG0_PAUSED && + server.cluster->mf_master_offset == -1) + { + server.cluster->mf_master_offset = sender->repl_offset; + clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER); + serverLog(LL_NOTICE, + "Received replication offset for paused " + "master manual failover: %lld", + server.cluster->mf_master_offset); + } + } + + /* Initial processing of PING and MEET requests replying with a PONG. */ + if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_MEET) { + /* We use incoming MEET messages in order to set the address + * for 'myself', since only other cluster nodes will send us + * MEET messages on handshakes, when the cluster joins, or + * later if we changed address, and those nodes will use our + * official address to connect to us. So by obtaining this address + * from the socket is a simple way to discover / update our own + * address in the cluster without it being hardcoded in the config. + * + * However if we don't have an address at all, we update the address + * even with a normal PING packet. If it's wrong it will be fixed + * by MEET later. */ + if ((type == CLUSTERMSG_TYPE_MEET || myself->ip[0] == '\0') && + server.cluster_announce_ip == NULL) + { + char ip[NET_IP_STR_LEN]; + + if (connAddrSockName(link->conn,ip,sizeof(ip),NULL) != -1 && + strcmp(ip,myself->ip)) + { + memcpy(myself->ip,ip,NET_IP_STR_LEN); + serverLog(LL_NOTICE,"IP address for this node updated to %s", + myself->ip); + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); + } + } + + /* Add this node if it is new for us and the msg type is MEET. + * In this stage we don't try to add the node with the right + * flags, slaveof pointer, and so forth, as this details will be + * resolved when we'll receive PONGs from the node. */ + if (!sender && type == CLUSTERMSG_TYPE_MEET) { + clusterNode *node; + + node = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE); + serverAssert(nodeIp2String(node->ip,link,hdr->myip) == C_OK); + getClientPortFromClusterMsg(hdr, &node->tls_port, &node->tcp_port); + node->cport = ntohs(hdr->cport); + clusterAddNode(node); + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); + } + + /* If this is a MEET packet from an unknown node, we still process + * the gossip section here since we have to trust the sender because + * of the message type. */ + if (!sender && type == CLUSTERMSG_TYPE_MEET) + clusterProcessGossipSection(hdr,link); + + /* Anyway reply with a PONG */ + clusterSendPing(link,CLUSTERMSG_TYPE_PONG); + } + + /* PING, PONG, MEET: process config information. */ + if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_PONG || + type == CLUSTERMSG_TYPE_MEET) + { + serverLog(LL_DEBUG,"%s packet received: %.40s", + clusterGetMessageTypeString(type), + link->node ? link->node->name : "NULL"); + if (!link->inbound) { + if (nodeInHandshake(link->node)) { + /* If we already have this node, try to change the + * IP/port of the node with the new one. */ + if (sender) { + serverLog(LL_VERBOSE, + "Handshake: we already know node %.40s (%s), " + "updating the address if needed.", sender->name, sender->human_nodename); + if (nodeUpdateAddressIfNeeded(sender,link,hdr)) + { + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_UPDATE_STATE); + } + /* Free this node as we already have it. This will + * cause the link to be freed as well. */ + clusterDelNode(link->node); + return 0; + } + + /* First thing to do is replacing the random name with the + * right node name if this was a handshake stage. */ + clusterRenameNode(link->node, hdr->sender); + serverLog(LL_DEBUG,"Handshake with node %.40s completed.", + link->node->name); + link->node->flags &= ~CLUSTER_NODE_HANDSHAKE; + link->node->flags |= flags&(CLUSTER_NODE_MASTER|CLUSTER_NODE_SLAVE); + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); + } else if (memcmp(link->node->name,hdr->sender, + CLUSTER_NAMELEN) != 0) + { + /* If the reply has a non matching node ID we + * disconnect this node and set it as not having an associated + * address. */ + serverLog(LL_DEBUG,"PONG contains mismatching sender ID. About node %.40s added %d ms ago, having flags %d", + link->node->name, + (int)(now-(link->node->ctime)), + link->node->flags); + link->node->flags |= CLUSTER_NODE_NOADDR; + link->node->ip[0] = '\0'; + link->node->tcp_port = 0; + link->node->tls_port = 0; + link->node->cport = 0; + freeClusterLink(link); + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); + return 0; + } + } + + /* Copy the CLUSTER_NODE_NOFAILOVER flag from what the sender + * announced. This is a dynamic flag that we receive from the + * sender, and the latest status must be trusted. We need it to + * be propagated because the slave ranking used to understand the + * delay of each slave in the voting process, needs to know + * what are the instances really competing. */ + if (sender) { + int nofailover = flags & CLUSTER_NODE_NOFAILOVER; + sender->flags &= ~CLUSTER_NODE_NOFAILOVER; + sender->flags |= nofailover; + } + + /* Update the node address if it changed. */ + if (sender && type == CLUSTERMSG_TYPE_PING && + !nodeInHandshake(sender) && + nodeUpdateAddressIfNeeded(sender,link,hdr)) + { + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_UPDATE_STATE); + } + + /* Update our info about the node */ + if (!link->inbound && type == CLUSTERMSG_TYPE_PONG) { + link->node->pong_received = now; + link->node->ping_sent = 0; + + /* The PFAIL condition can be reversed without external + * help if it is momentary (that is, if it does not + * turn into a FAIL state). + * + * The FAIL condition is also reversible under specific + * conditions detected by clearNodeFailureIfNeeded(). */ + if (nodeTimedOut(link->node)) { + link->node->flags &= ~CLUSTER_NODE_PFAIL; + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_UPDATE_STATE); + } else if (nodeFailed(link->node)) { + clearNodeFailureIfNeeded(link->node); + } + } + + /* Check for role switch: slave -> master or master -> slave. */ + if (sender) { + if (!memcmp(hdr->slaveof,CLUSTER_NODE_NULL_NAME, + sizeof(hdr->slaveof))) + { + /* Node is a master. */ + clusterSetNodeAsMaster(sender); + } else { + /* Node is a slave. */ + clusterNode *master = clusterLookupNode(hdr->slaveof, CLUSTER_NAMELEN); + + if (clusterNodeIsMaster(sender)) { + /* Master turned into a slave! Reconfigure the node. */ + clusterDelNodeSlots(sender); + sender->flags &= ~(CLUSTER_NODE_MASTER| + CLUSTER_NODE_MIGRATE_TO); + sender->flags |= CLUSTER_NODE_SLAVE; + + /* Update config and state. */ + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_UPDATE_STATE); + } + + /* Master node changed for this slave? */ + if (master && sender->slaveof != master) { + if (sender->slaveof) + clusterNodeRemoveSlave(sender->slaveof,sender); + clusterNodeAddSlave(master,sender); + sender->slaveof = master; + + /* Update the shard_id when a replica is connected to its + * primary in the very first time. */ + updateShardId(sender, master->shard_id); + + /* Update config. */ + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); + } + } + } + + /* Update our info about served slots. + * + * Note: this MUST happen after we update the master/slave state + * so that CLUSTER_NODE_MASTER flag will be set. */ + + /* Many checks are only needed if the set of served slots this + * instance claims is different compared to the set of slots we have + * for it. Check this ASAP to avoid other computational expansive + * checks later. */ + clusterNode *sender_master = NULL; /* Sender or its master if slave. */ + int dirty_slots = 0; /* Sender claimed slots don't match my view? */ + + if (sender) { + sender_master = clusterNodeIsMaster(sender) ? sender : sender->slaveof; + if (sender_master) { + dirty_slots = memcmp(sender_master->slots, + hdr->myslots,sizeof(hdr->myslots)) != 0; + } + } + + /* 1) If the sender of the message is a master, and we detected that + * the set of slots it claims changed, scan the slots to see if we + * need to update our configuration. */ + if (sender && clusterNodeIsMaster(sender) && dirty_slots) + clusterUpdateSlotsConfigWith(sender,senderConfigEpoch,hdr->myslots); + + /* 2) We also check for the reverse condition, that is, the sender + * claims to serve slots we know are served by a master with a + * greater configEpoch. If this happens we inform the sender. + * + * This is useful because sometimes after a partition heals, a + * reappearing master may be the last one to claim a given set of + * hash slots, but with a configuration that other instances know to + * be deprecated. Example: + * + * A and B are master and slave for slots 1,2,3. + * A is partitioned away, B gets promoted. + * B is partitioned away, and A returns available. + * + * Usually B would PING A publishing its set of served slots and its + * configEpoch, but because of the partition B can't inform A of the + * new configuration, so other nodes that have an updated table must + * do it. In this way A will stop to act as a master (or can try to + * failover if there are the conditions to win the election). */ + if (sender && dirty_slots) { + int j; + + for (j = 0; j < CLUSTER_SLOTS; j++) { + if (bitmapTestBit(hdr->myslots,j)) { + if (server.cluster->slots[j] == sender || + isSlotUnclaimed(j)) continue; + if (server.cluster->slots[j]->configEpoch > + senderConfigEpoch) + { + serverLog(LL_VERBOSE, + "Node %.40s has old slots configuration, sending " + "an UPDATE message about %.40s", + sender->name, server.cluster->slots[j]->name); + clusterSendUpdate(sender->link, + server.cluster->slots[j]); + + /* TODO: instead of exiting the loop send every other + * UPDATE packet for other nodes that are the new owner + * of sender's slots. */ + break; + } + } + } + } + + /* If our config epoch collides with the sender's try to fix + * the problem. */ + if (sender && clusterNodeIsMaster(myself) && clusterNodeIsMaster(sender) && + senderConfigEpoch == myself->configEpoch) + { + clusterHandleConfigEpochCollision(sender); + } + + /* Get info from the gossip section */ + if (sender) { + clusterProcessGossipSection(hdr,link); + clusterProcessPingExtensions(hdr,link); + } + } else if (type == CLUSTERMSG_TYPE_FAIL) { + clusterNode *failing; + + if (sender) { + failing = clusterLookupNode(hdr->data.fail.about.nodename, CLUSTER_NAMELEN); + if (failing && + !(failing->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_MYSELF))) + { + serverLog(LL_NOTICE, + "FAIL message received from %.40s (%s) about %.40s (%s)", + hdr->sender, sender->human_nodename, hdr->data.fail.about.nodename, failing->human_nodename); + failing->flags |= CLUSTER_NODE_FAIL; + failing->fail_time = now; + failing->flags &= ~CLUSTER_NODE_PFAIL; + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_UPDATE_STATE); + } + } else { + serverLog(LL_NOTICE, + "Ignoring FAIL message from unknown node %.40s about %.40s", + hdr->sender, hdr->data.fail.about.nodename); + } + } else if (type == CLUSTERMSG_TYPE_PUBLISH || type == CLUSTERMSG_TYPE_PUBLISHSHARD) { + if (!sender) return 1; /* We don't know that node. */ + + robj *channel, *message; + uint32_t channel_len, message_len; + + /* Don't bother creating useless objects if there are no + * Pub/Sub subscribers. */ + if ((type == CLUSTERMSG_TYPE_PUBLISH + && serverPubsubSubscriptionCount() > 0) + || (type == CLUSTERMSG_TYPE_PUBLISHSHARD + && serverPubsubShardSubscriptionCount() > 0)) + { + channel_len = ntohl(hdr->data.publish.msg.channel_len); + message_len = ntohl(hdr->data.publish.msg.message_len); + channel = createStringObject( + (char*)hdr->data.publish.msg.bulk_data,channel_len); + message = createStringObject( + (char*)hdr->data.publish.msg.bulk_data+channel_len, + message_len); + pubsubPublishMessage(channel, message, type == CLUSTERMSG_TYPE_PUBLISHSHARD); + decrRefCount(channel); + decrRefCount(message); + } + } else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST) { + if (!sender) return 1; /* We don't know that node. */ + clusterSendFailoverAuthIfNeeded(sender,hdr); + } else if (type == CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK) { + if (!sender) return 1; /* We don't know that node. */ + /* We consider this vote only if the sender is a master serving + * a non zero number of slots, and its currentEpoch is greater or + * equal to epoch where this node started the election. */ + if (clusterNodeIsMaster(sender) && sender->numslots > 0 && + senderCurrentEpoch >= server.cluster->failover_auth_epoch) + { + server.cluster->failover_auth_count++; + /* Maybe we reached a quorum here, set a flag to make sure + * we check ASAP. */ + clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER); + } + } else if (type == CLUSTERMSG_TYPE_MFSTART) { + /* This message is acceptable only if I'm a master and the sender + * is one of my slaves. */ + if (!sender || sender->slaveof != myself) return 1; + /* Manual failover requested from slaves. Initialize the state + * accordingly. */ + resetManualFailover(); + server.cluster->mf_end = now + CLUSTER_MF_TIMEOUT; + server.cluster->mf_slave = sender; + pauseActions(PAUSE_DURING_FAILOVER, + now + (CLUSTER_MF_TIMEOUT * CLUSTER_MF_PAUSE_MULT), + PAUSE_ACTIONS_CLIENT_WRITE_SET); + serverLog(LL_NOTICE,"Manual failover requested by replica %.40s (%s).", + sender->name, sender->human_nodename); + /* We need to send a ping message to the replica, as it would carry + * `server.cluster->mf_master_offset`, which means the master paused clients + * at offset `server.cluster->mf_master_offset`, so that the replica would + * know that it is safe to set its `server.cluster->mf_can_start` to 1 so as + * to complete failover as quickly as possible. */ + clusterSendPing(link, CLUSTERMSG_TYPE_PING); + } else if (type == CLUSTERMSG_TYPE_UPDATE) { + clusterNode *n; /* The node the update is about. */ + uint64_t reportedConfigEpoch = + ntohu64(hdr->data.update.nodecfg.configEpoch); + + if (!sender) return 1; /* We don't know the sender. */ + n = clusterLookupNode(hdr->data.update.nodecfg.nodename, CLUSTER_NAMELEN); + if (!n) return 1; /* We don't know the reported node. */ + if (n->configEpoch >= reportedConfigEpoch) return 1; /* Nothing new. */ + + /* If in our current config the node is a slave, set it as a master. */ + if (nodeIsSlave(n)) clusterSetNodeAsMaster(n); + + /* Update the node's configEpoch. */ + n->configEpoch = reportedConfigEpoch; + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_FSYNC_CONFIG); + + /* Check the bitmap of served slots and update our + * config accordingly. */ + clusterUpdateSlotsConfigWith(n,reportedConfigEpoch, + hdr->data.update.nodecfg.slots); + } else if (type == CLUSTERMSG_TYPE_MODULE) { + if (!sender) return 1; /* Protect the module from unknown nodes. */ + /* We need to route this message back to the right module subscribed + * for the right message type. */ + uint64_t module_id = hdr->data.module.msg.module_id; /* Endian-safe ID */ + uint32_t len = ntohl(hdr->data.module.msg.len); + uint8_t type = hdr->data.module.msg.type; + unsigned char *payload = hdr->data.module.msg.bulk_data; + moduleCallClusterReceivers(sender->name,module_id,type,payload,len); + } else { + serverLog(LL_WARNING,"Received unknown packet type: %d", type); + } + return 1; +} + +/* This function is called when we detect the link with this node is lost. + We set the node as no longer connected. The Cluster Cron will detect + this connection and will try to get it connected again. + + Instead if the node is a temporary node used to accept a query, we + completely free the node on error. */ +void handleLinkIOError(clusterLink *link) { + freeClusterLink(link); +} + +/* Send the messages queued for the link. */ +void clusterWriteHandler(connection *conn) { + clusterLink *link = connGetPrivateData(conn); + ssize_t nwritten; + size_t totwritten = 0; + + while (totwritten < NET_MAX_WRITES_PER_EVENT && listLength(link->send_msg_queue) > 0) { + listNode *head = listFirst(link->send_msg_queue); + clusterMsgSendBlock *msgblock = (clusterMsgSendBlock*)head->value; + clusterMsg *msg = &msgblock->msg; + size_t msg_offset = link->head_msg_send_offset; + size_t msg_len = ntohl(msg->totlen); + + nwritten = connWrite(conn, (char*)msg + msg_offset, msg_len - msg_offset); + if (nwritten <= 0) { + serverLog(LL_DEBUG,"I/O error writing to node link: %s", + (nwritten == -1) ? connGetLastError(conn) : "short write"); + handleLinkIOError(link); + return; + } + if (msg_offset + nwritten < msg_len) { + /* If full message wasn't written, record the offset + * and continue sending from this point next time */ + link->head_msg_send_offset += nwritten; + return; + } + serverAssert((msg_offset + nwritten) == msg_len); + link->head_msg_send_offset = 0; + + /* Delete the node and update our memory tracking */ + uint32_t blocklen = msgblock->totlen; + listDelNode(link->send_msg_queue, head); + server.stat_cluster_links_memory -= sizeof(listNode); + link->send_msg_queue_mem -= sizeof(listNode) + blocklen; + + totwritten += nwritten; + } + + if (listLength(link->send_msg_queue) == 0) + connSetWriteHandler(link->conn, NULL); +} + +/* A connect handler that gets called when a connection to another node + * gets established. + */ +void clusterLinkConnectHandler(connection *conn) { + clusterLink *link = connGetPrivateData(conn); + clusterNode *node = link->node; + + /* Check if connection succeeded */ + if (connGetState(conn) != CONN_STATE_CONNECTED) { + serverLog(LL_VERBOSE, "Connection with Node %.40s at %s:%d failed: %s", + node->name, node->ip, node->cport, + connGetLastError(conn)); + freeClusterLink(link); + return; + } + + /* Register a read handler from now on */ + connSetReadHandler(conn, clusterReadHandler); + + /* Queue a PING in the new connection ASAP: this is crucial + * to avoid false positives in failure detection. + * + * If the node is flagged as MEET, we send a MEET message instead + * of a PING one, to force the receiver to add us in its node + * table. */ + mstime_t old_ping_sent = node->ping_sent; + clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ? + CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING); + if (old_ping_sent) { + /* If there was an active ping before the link was + * disconnected, we want to restore the ping time, otherwise + * replaced by the clusterSendPing() call. */ + node->ping_sent = old_ping_sent; + } + /* We can clear the flag after the first packet is sent. + * If we'll never receive a PONG, we'll never send new packets + * to this node. Instead after the PONG is received and we + * are no longer in meet/handshake status, we want to send + * normal PING packets. */ + node->flags &= ~CLUSTER_NODE_MEET; + + serverLog(LL_DEBUG,"Connecting with Node %.40s at %s:%d", + node->name, node->ip, node->cport); +} + +/* Read data. Try to read the first field of the header first to check the + * full length of the packet. When a whole packet is in memory this function + * will call the function to process the packet. And so forth. */ +void clusterReadHandler(connection *conn) { + clusterMsg buf[1]; + ssize_t nread; + clusterMsg *hdr; + clusterLink *link = connGetPrivateData(conn); + unsigned int readlen, rcvbuflen; + + while(1) { /* Read as long as there is data to read. */ + rcvbuflen = link->rcvbuf_len; + if (rcvbuflen < 8) { + /* First, obtain the first 8 bytes to get the full message + * length. */ + readlen = 8 - rcvbuflen; + } else { + /* Finally read the full message. */ + hdr = (clusterMsg*) link->rcvbuf; + if (rcvbuflen == 8) { + /* Perform some sanity check on the message signature + * and length. */ + if (memcmp(hdr->sig,"RCmb",4) != 0 || + ntohl(hdr->totlen) < CLUSTERMSG_MIN_LEN) + { + char ip[NET_IP_STR_LEN]; + int port; + if (connAddrPeerName(conn, ip, sizeof(ip), &port) == -1) { + serverLog(LL_WARNING, + "Bad message length or signature received " + "on the Cluster bus."); + } else { + serverLog(LL_WARNING, + "Bad message length or signature received " + "on the Cluster bus from %s:%d", ip, port); + } + handleLinkIOError(link); + return; + } + } + readlen = ntohl(hdr->totlen) - rcvbuflen; + if (readlen > sizeof(buf)) readlen = sizeof(buf); + } + + nread = connRead(conn,buf,readlen); + if (nread == -1 && (connGetState(conn) == CONN_STATE_CONNECTED)) return; /* No more data ready. */ + + if (nread <= 0) { + /* I/O error... */ + serverLog(LL_DEBUG,"I/O error reading from node link: %s", + (nread == 0) ? "connection closed" : connGetLastError(conn)); + handleLinkIOError(link); + return; + } else { + /* Read data and recast the pointer to the new buffer. */ + size_t unused = link->rcvbuf_alloc - link->rcvbuf_len; + if ((size_t)nread > unused) { + size_t required = link->rcvbuf_len + nread; + size_t prev_rcvbuf_alloc = link->rcvbuf_alloc; + /* If less than 1mb, grow to twice the needed size, if larger grow by 1mb. */ + link->rcvbuf_alloc = required < RCVBUF_MAX_PREALLOC ? required * 2: required + RCVBUF_MAX_PREALLOC; + link->rcvbuf = zrealloc(link->rcvbuf, link->rcvbuf_alloc); + server.stat_cluster_links_memory += link->rcvbuf_alloc - prev_rcvbuf_alloc; + } + memcpy(link->rcvbuf + link->rcvbuf_len, buf, nread); + link->rcvbuf_len += nread; + hdr = (clusterMsg*) link->rcvbuf; + rcvbuflen += nread; + } + + /* Total length obtained? Process this packet. */ + if (rcvbuflen >= 8 && rcvbuflen == ntohl(hdr->totlen)) { + if (clusterProcessPacket(link)) { + if (link->rcvbuf_alloc > RCVBUF_INIT_LEN) { + size_t prev_rcvbuf_alloc = link->rcvbuf_alloc; + zfree(link->rcvbuf); + link->rcvbuf = zmalloc(link->rcvbuf_alloc = RCVBUF_INIT_LEN); + server.stat_cluster_links_memory += link->rcvbuf_alloc - prev_rcvbuf_alloc; + } + link->rcvbuf_len = 0; + } else { + return; /* Link no longer valid. */ + } + } + } +} + +/* Put the message block into the link's send queue. + * + * It is guaranteed that this function will never have as a side effect + * the link to be invalidated, so it is safe to call this function + * from event handlers that will do stuff with the same link later. */ +void clusterSendMessage(clusterLink *link, clusterMsgSendBlock *msgblock) { + if (!link) { + return; + } + if (listLength(link->send_msg_queue) == 0 && msgblock->msg.totlen != 0) + connSetWriteHandlerWithBarrier(link->conn, clusterWriteHandler, 1); + + listAddNodeTail(link->send_msg_queue, msgblock); + msgblock->refcount++; + + /* Update memory tracking */ + link->send_msg_queue_mem += sizeof(listNode) + msgblock->totlen; + server.stat_cluster_links_memory += sizeof(listNode); + + /* Populate sent messages stats. */ + uint16_t type = ntohs(msgblock->msg.type); + if (type < CLUSTERMSG_TYPE_COUNT) + server.cluster->stats_bus_messages_sent[type]++; +} + +/* Send a message to all the nodes that are part of the cluster having + * a connected link. + * + * It is guaranteed that this function will never have as a side effect + * some node->link to be invalidated, so it is safe to call this function + * from event handlers that will do stuff with node links later. */ +void clusterBroadcastMessage(clusterMsgSendBlock *msgblock) { + dictIterator *di; + dictEntry *de; + + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + + if (node->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_HANDSHAKE)) + continue; + clusterSendMessage(node->link,msgblock); + } + dictReleaseIterator(di); +} + +/* Build the message header. hdr must point to a buffer at least + * sizeof(clusterMsg) in bytes. */ +static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen) { + uint64_t offset; + clusterNode *master; + + /* If this node is a master, we send its slots bitmap and configEpoch. + * If this node is a slave we send the master's information instead (the + * node is flagged as slave so the receiver knows that it is NOT really + * in charge for this slots. */ + master = (nodeIsSlave(myself) && myself->slaveof) ? + myself->slaveof : myself; + + hdr->ver = htons(CLUSTER_PROTO_VER); + hdr->sig[0] = 'R'; + hdr->sig[1] = 'C'; + hdr->sig[2] = 'm'; + hdr->sig[3] = 'b'; + hdr->type = htons(type); + memcpy(hdr->sender,myself->name,CLUSTER_NAMELEN); + + /* If cluster-announce-ip option is enabled, force the receivers of our + * packets to use the specified address for this node. Otherwise if the + * first byte is zero, they'll do auto discovery. */ + memset(hdr->myip,0,NET_IP_STR_LEN); + if (server.cluster_announce_ip) { + redis_strlcpy(hdr->myip,server.cluster_announce_ip,NET_IP_STR_LEN); + } + + /* Handle cluster-announce-[tls-|bus-]port. */ + int announced_tcp_port, announced_tls_port, announced_cport; + deriveAnnouncedPorts(&announced_tcp_port, &announced_tls_port, &announced_cport); + + memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots)); + memset(hdr->slaveof,0,CLUSTER_NAMELEN); + if (myself->slaveof != NULL) + memcpy(hdr->slaveof,myself->slaveof->name, CLUSTER_NAMELEN); + if (server.tls_cluster) { + hdr->port = htons(announced_tls_port); + hdr->pport = htons(announced_tcp_port); + } else { + hdr->port = htons(announced_tcp_port); + hdr->pport = htons(announced_tls_port); + } + hdr->cport = htons(announced_cport); + hdr->flags = htons(myself->flags); + hdr->state = server.cluster->state; + + /* Set the currentEpoch and configEpochs. */ + hdr->currentEpoch = htonu64(server.cluster->currentEpoch); + hdr->configEpoch = htonu64(master->configEpoch); + + /* Set the replication offset. */ + if (nodeIsSlave(myself)) + offset = replicationGetSlaveOffset(); + else + offset = server.master_repl_offset; + hdr->offset = htonu64(offset); + + /* Set the message flags. */ + if (clusterNodeIsMaster(myself) && server.cluster->mf_end) + hdr->mflags[0] |= CLUSTERMSG_FLAG0_PAUSED; + + hdr->totlen = htonl(msglen); +} + +/* Set the i-th entry of the gossip section in the message pointed by 'hdr' + * to the info of the specified node 'n'. */ +void clusterSetGossipEntry(clusterMsg *hdr, int i, clusterNode *n) { + clusterMsgDataGossip *gossip; + gossip = &(hdr->data.ping.gossip[i]); + memcpy(gossip->nodename,n->name,CLUSTER_NAMELEN); + gossip->ping_sent = htonl(n->ping_sent/1000); + gossip->pong_received = htonl(n->pong_received/1000); + memcpy(gossip->ip,n->ip,sizeof(n->ip)); + if (server.tls_cluster) { + gossip->port = htons(n->tls_port); + gossip->pport = htons(n->tcp_port); + } else { + gossip->port = htons(n->tcp_port); + gossip->pport = htons(n->tls_port); + } + gossip->cport = htons(n->cport); + gossip->flags = htons(n->flags); + gossip->notused1 = 0; +} + +/* Send a PING or PONG packet to the specified node, making sure to add enough + * gossip information. */ +void clusterSendPing(clusterLink *link, int type) { + static unsigned long long cluster_pings_sent = 0; + cluster_pings_sent++; + int gossipcount = 0; /* Number of gossip sections added so far. */ + int wanted; /* Number of gossip sections we want to append if possible. */ + int estlen; /* Upper bound on estimated packet length */ + /* freshnodes is the max number of nodes we can hope to append at all: + * nodes available minus two (ourself and the node we are sending the + * message to). However practically there may be less valid nodes since + * nodes in handshake state, disconnected, are not considered. */ + int freshnodes = dictSize(server.cluster->nodes)-2; + + /* How many gossip sections we want to add? 1/10 of the number of nodes + * and anyway at least 3. Why 1/10? + * + * If we have N masters, with N/10 entries, and we consider that in + * node_timeout we exchange with each other node at least 4 packets + * (we ping in the worst case in node_timeout/2 time, and we also + * receive two pings from the host), we have a total of 8 packets + * in the node_timeout*2 failure reports validity time. So we have + * that, for a single PFAIL node, we can expect to receive the following + * number of failure reports (in the specified window of time): + * + * PROB * GOSSIP_ENTRIES_PER_PACKET * TOTAL_PACKETS: + * + * PROB = probability of being featured in a single gossip entry, + * which is 1 / NUM_OF_NODES. + * ENTRIES = 10. + * TOTAL_PACKETS = 2 * 4 * NUM_OF_MASTERS. + * + * If we assume we have just masters (so num of nodes and num of masters + * is the same), with 1/10 we always get over the majority, and specifically + * 80% of the number of nodes, to account for many masters failing at the + * same time. + * + * Since we have non-voting slaves that lower the probability of an entry + * to feature our node, we set the number of entries per packet as + * 10% of the total nodes we have. */ + wanted = floor(dictSize(server.cluster->nodes)/10); + if (wanted < 3) wanted = 3; + if (wanted > freshnodes) wanted = freshnodes; + + /* Include all the nodes in PFAIL state, so that failure reports are + * faster to propagate to go from PFAIL to FAIL state. */ + int pfail_wanted = server.cluster->stats_pfail_nodes; + + /* Compute the maximum estlen to allocate our buffer. We'll fix the estlen + * later according to the number of gossip sections we really were able + * to put inside the packet. */ + estlen = sizeof(clusterMsg) - sizeof(union clusterMsgData); + estlen += (sizeof(clusterMsgDataGossip)*(wanted + pfail_wanted)); + estlen += writePingExt(NULL, 0); + /* Note: clusterBuildMessageHdr() expects the buffer to be always at least + * sizeof(clusterMsg) or more. */ + if (estlen < (int)sizeof(clusterMsg)) estlen = sizeof(clusterMsg); + clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(type, estlen); + clusterMsg *hdr = &msgblock->msg; + + if (!link->inbound && type == CLUSTERMSG_TYPE_PING) + link->node->ping_sent = mstime(); + + /* Populate the gossip fields */ + int maxiterations = wanted*3; + while(freshnodes > 0 && gossipcount < wanted && maxiterations--) { + dictEntry *de = dictGetRandomKey(server.cluster->nodes); + clusterNode *this = dictGetVal(de); + + /* Don't include this node: the whole packet header is about us + * already, so we just gossip about other nodes. */ + if (this == myself) continue; + + /* PFAIL nodes will be added later. */ + if (this->flags & CLUSTER_NODE_PFAIL) continue; + + /* In the gossip section don't include: + * 1) Nodes in HANDSHAKE state. + * 3) Nodes with the NOADDR flag set. + * 4) Disconnected nodes if they don't have configured slots. + */ + if (this->flags & (CLUSTER_NODE_HANDSHAKE|CLUSTER_NODE_NOADDR) || + (this->link == NULL && this->numslots == 0)) + { + freshnodes--; /* Technically not correct, but saves CPU. */ + continue; + } + + /* Do not add a node we already have. */ + if (this->last_in_ping_gossip == cluster_pings_sent) continue; + + /* Add it */ + clusterSetGossipEntry(hdr,gossipcount,this); + this->last_in_ping_gossip = cluster_pings_sent; + freshnodes--; + gossipcount++; + } + + /* If there are PFAIL nodes, add them at the end. */ + if (pfail_wanted) { + dictIterator *di; + dictEntry *de; + + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL && pfail_wanted > 0) { + clusterNode *node = dictGetVal(de); + if (node->flags & CLUSTER_NODE_HANDSHAKE) continue; + if (node->flags & CLUSTER_NODE_NOADDR) continue; + if (!(node->flags & CLUSTER_NODE_PFAIL)) continue; + clusterSetGossipEntry(hdr,gossipcount,node); + gossipcount++; + /* We take the count of the slots we allocated, since the + * PFAIL stats may not match perfectly with the current number + * of PFAIL nodes. */ + pfail_wanted--; + } + dictReleaseIterator(di); + } + + /* Compute the actual total length and send! */ + uint32_t totlen = 0; + totlen += writePingExt(hdr, gossipcount); + totlen += sizeof(clusterMsg)-sizeof(union clusterMsgData); + totlen += (sizeof(clusterMsgDataGossip)*gossipcount); + serverAssert(gossipcount < USHRT_MAX); + hdr->count = htons(gossipcount); + hdr->totlen = htonl(totlen); + + clusterSendMessage(link,msgblock); + clusterMsgSendBlockDecrRefCount(msgblock); +} + +/* Send a PONG packet to every connected node that's not in handshake state + * and for which we have a valid link. + * + * In Redis Cluster pongs are not used just for failure detection, but also + * to carry important configuration information. So broadcasting a pong is + * useful when something changes in the configuration and we want to make + * the cluster aware ASAP (for instance after a slave promotion). + * + * The 'target' argument specifies the receiving instances using the + * defines below: + * + * CLUSTER_BROADCAST_ALL -> All known instances. + * CLUSTER_BROADCAST_LOCAL_SLAVES -> All slaves in my master-slaves ring. + */ +#define CLUSTER_BROADCAST_ALL 0 +#define CLUSTER_BROADCAST_LOCAL_SLAVES 1 +void clusterBroadcastPong(int target) { + dictIterator *di; + dictEntry *de; + + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + + if (!node->link) continue; + if (node == myself || nodeInHandshake(node)) continue; + if (target == CLUSTER_BROADCAST_LOCAL_SLAVES) { + int local_slave = + nodeIsSlave(node) && node->slaveof && + (node->slaveof == myself || node->slaveof == myself->slaveof); + if (!local_slave) continue; + } + clusterSendPing(node->link,CLUSTERMSG_TYPE_PONG); + } + dictReleaseIterator(di); +} + +/* Create a PUBLISH message block. + * + * Sanitizer suppression: In clusterMsgDataPublish, sizeof(bulk_data) is 8. + * As all the struct is used as a buffer, when more than 8 bytes are copied into + * the 'bulk_data', sanitizer generates an out-of-bounds error which is a false + * positive in this context. */ +REDIS_NO_SANITIZE("bounds") +clusterMsgSendBlock *clusterCreatePublishMsgBlock(robj *channel, robj *message, uint16_t type) { + + uint32_t channel_len, message_len; + + channel = getDecodedObject(channel); + message = getDecodedObject(message); + channel_len = sdslen(channel->ptr); + message_len = sdslen(message->ptr); + + size_t msglen = sizeof(clusterMsg)-sizeof(union clusterMsgData); + msglen += sizeof(clusterMsgDataPublish) - 8 + channel_len + message_len; + clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(type, msglen); + + clusterMsg *hdr = &msgblock->msg; + hdr->data.publish.msg.channel_len = htonl(channel_len); + hdr->data.publish.msg.message_len = htonl(message_len); + memcpy(hdr->data.publish.msg.bulk_data,channel->ptr,sdslen(channel->ptr)); + memcpy(hdr->data.publish.msg.bulk_data+sdslen(channel->ptr), + message->ptr,sdslen(message->ptr)); + + decrRefCount(channel); + decrRefCount(message); + + return msgblock; +} + +/* Send a FAIL message to all the nodes we are able to contact. + * The FAIL message is sent when we detect that a node is failing + * (CLUSTER_NODE_PFAIL) and we also receive a gossip confirmation of this: + * we switch the node state to CLUSTER_NODE_FAIL and ask all the other + * nodes to do the same ASAP. */ +void clusterSendFail(char *nodename) { + uint32_t msglen = sizeof(clusterMsg) - sizeof(union clusterMsgData) + + sizeof(clusterMsgDataFail); + clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(CLUSTERMSG_TYPE_FAIL, msglen); + + clusterMsg *hdr = &msgblock->msg; + memcpy(hdr->data.fail.about.nodename,nodename,CLUSTER_NAMELEN); + + clusterBroadcastMessage(msgblock); + clusterMsgSendBlockDecrRefCount(msgblock); +} + +/* Send an UPDATE message to the specified link carrying the specified 'node' + * slots configuration. The node name, slots bitmap, and configEpoch info + * are included. */ +void clusterSendUpdate(clusterLink *link, clusterNode *node) { + if (link == NULL) return; + + uint32_t msglen = sizeof(clusterMsg) - sizeof(union clusterMsgData) + + sizeof(clusterMsgDataUpdate); + clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(CLUSTERMSG_TYPE_UPDATE, msglen); + + clusterMsg *hdr = &msgblock->msg; + memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN); + hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch); + memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots)); + for (unsigned int i = 0; i < sizeof(node->slots); i++) { + /* Don't advertise slots that the node stopped claiming */ + hdr->data.update.nodecfg.slots[i] = hdr->data.update.nodecfg.slots[i] & (~server.cluster->owner_not_claiming_slot[i]); + } + + clusterSendMessage(link,msgblock); + clusterMsgSendBlockDecrRefCount(msgblock); +} + +/* Send a MODULE message. + * + * If link is NULL, then the message is broadcasted to the whole cluster. */ +void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type, + const char *payload, uint32_t len) { + uint32_t msglen = sizeof(clusterMsg)-sizeof(union clusterMsgData); + msglen += sizeof(clusterMsgModule) - 3 + len; + clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(CLUSTERMSG_TYPE_MODULE, msglen); + + clusterMsg *hdr = &msgblock->msg; + hdr->data.module.msg.module_id = module_id; /* Already endian adjusted. */ + hdr->data.module.msg.type = type; + hdr->data.module.msg.len = htonl(len); + memcpy(hdr->data.module.msg.bulk_data,payload,len); + + if (link) + clusterSendMessage(link,msgblock); + else + clusterBroadcastMessage(msgblock); + + clusterMsgSendBlockDecrRefCount(msgblock); +} + +/* This function gets a cluster node ID string as target, the same way the nodes + * addresses are represented in the modules side, resolves the node, and sends + * the message. If the target is NULL the message is broadcasted. + * + * The function returns C_OK if the target is valid, otherwise C_ERR is + * returned. */ +int clusterSendModuleMessageToTarget(const char *target, uint64_t module_id, uint8_t type, const char *payload, uint32_t len) { + clusterNode *node = NULL; + + if (target != NULL) { + node = clusterLookupNode(target, strlen(target)); + if (node == NULL || node->link == NULL) return C_ERR; + } + + clusterSendModule(target ? node->link : NULL, + module_id, type, payload, len); + return C_OK; +} + +/* ----------------------------------------------------------------------------- + * CLUSTER Pub/Sub support + * + * If `sharded` is 0: + * For now we do very little, just propagating [S]PUBLISH messages across the whole + * cluster. In the future we'll try to get smarter and avoiding propagating those + * messages to hosts without receives for a given channel. + * Otherwise: + * Publish this message across the slot (primary/replica). + * -------------------------------------------------------------------------- */ +void clusterPropagatePublish(robj *channel, robj *message, int sharded) { + clusterMsgSendBlock *msgblock; + + if (!sharded) { + msgblock = clusterCreatePublishMsgBlock(channel, message, CLUSTERMSG_TYPE_PUBLISH); + clusterBroadcastMessage(msgblock); + clusterMsgSendBlockDecrRefCount(msgblock); + return; + } + + listIter li; + listNode *ln; + list *nodes_for_slot = clusterGetNodesInMyShard(server.cluster->myself); + serverAssert(nodes_for_slot != NULL); + listRewind(nodes_for_slot, &li); + msgblock = clusterCreatePublishMsgBlock(channel, message, CLUSTERMSG_TYPE_PUBLISHSHARD); + while((ln = listNext(&li))) { + clusterNode *node = listNodeValue(ln); + if (node->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_HANDSHAKE)) + continue; + clusterSendMessage(node->link,msgblock); + } + clusterMsgSendBlockDecrRefCount(msgblock); +} + +/* ----------------------------------------------------------------------------- + * SLAVE node specific functions + * -------------------------------------------------------------------------- */ + +/* This function sends a FAILOVER_AUTH_REQUEST message to every node in order to + * see if there is the quorum for this slave instance to failover its failing + * master. + * + * Note that we send the failover request to everybody, master and slave nodes, + * but only the masters are supposed to reply to our query. */ +void clusterRequestFailoverAuth(void) { + uint32_t msglen = sizeof(clusterMsg)-sizeof(union clusterMsgData); + clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST, msglen); + + clusterMsg *hdr = &msgblock->msg; + /* If this is a manual failover, set the CLUSTERMSG_FLAG0_FORCEACK bit + * in the header to communicate the nodes receiving the message that + * they should authorized the failover even if the master is working. */ + if (server.cluster->mf_end) hdr->mflags[0] |= CLUSTERMSG_FLAG0_FORCEACK; + clusterBroadcastMessage(msgblock); + clusterMsgSendBlockDecrRefCount(msgblock); +} + +/* Send a FAILOVER_AUTH_ACK message to the specified node. */ +void clusterSendFailoverAuth(clusterNode *node) { + if (!node->link) return; + + uint32_t msglen = sizeof(clusterMsg)-sizeof(union clusterMsgData); + clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK, msglen); + + clusterSendMessage(node->link,msgblock); + clusterMsgSendBlockDecrRefCount(msgblock); +} + +/* Send a MFSTART message to the specified node. */ +void clusterSendMFStart(clusterNode *node) { + if (!node->link) return; + + uint32_t msglen = sizeof(clusterMsg)-sizeof(union clusterMsgData); + clusterMsgSendBlock *msgblock = createClusterMsgSendBlock(CLUSTERMSG_TYPE_MFSTART, msglen); + + clusterSendMessage(node->link,msgblock); + clusterMsgSendBlockDecrRefCount(msgblock); +} + +/* Vote for the node asking for our vote if there are the conditions. */ +void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { + clusterNode *master = node->slaveof; + uint64_t requestCurrentEpoch = ntohu64(request->currentEpoch); + uint64_t requestConfigEpoch = ntohu64(request->configEpoch); + unsigned char *claimed_slots = request->myslots; + int force_ack = request->mflags[0] & CLUSTERMSG_FLAG0_FORCEACK; + int j; + + /* IF we are not a master serving at least 1 slot, we don't have the + * right to vote, as the cluster size in Redis Cluster is the number + * of masters serving at least one slot, and quorum is the cluster + * size + 1 */ + if (nodeIsSlave(myself) || myself->numslots == 0) return; + + /* Request epoch must be >= our currentEpoch. + * Note that it is impossible for it to actually be greater since + * our currentEpoch was updated as a side effect of receiving this + * request, if the request epoch was greater. */ + if (requestCurrentEpoch < server.cluster->currentEpoch) { + serverLog(LL_WARNING, + "Failover auth denied to %.40s (%s): reqEpoch (%llu) < curEpoch(%llu)", + node->name, node->human_nodename, + (unsigned long long) requestCurrentEpoch, + (unsigned long long) server.cluster->currentEpoch); + return; + } + + /* I already voted for this epoch? Return ASAP. */ + if (server.cluster->lastVoteEpoch == server.cluster->currentEpoch) { + serverLog(LL_WARNING, + "Failover auth denied to %.40s (%s): already voted for epoch %llu", + node->name, node->human_nodename, + (unsigned long long) server.cluster->currentEpoch); + return; + } + + /* Node must be a slave and its master down. + * The master can be non failing if the request is flagged + * with CLUSTERMSG_FLAG0_FORCEACK (manual failover). */ + if (clusterNodeIsMaster(node) || master == NULL || + (!nodeFailed(master) && !force_ack)) + { + if (clusterNodeIsMaster(node)) { + serverLog(LL_WARNING, + "Failover auth denied to %.40s (%s): it is a master node", + node->name, node->human_nodename); + } else if (master == NULL) { + serverLog(LL_WARNING, + "Failover auth denied to %.40s (%s): I don't know its master", + node->name, node->human_nodename); + } else if (!nodeFailed(master)) { + serverLog(LL_WARNING, + "Failover auth denied to %.40s (%s): its master is up", + node->name, node->human_nodename); + } + return; + } + + /* We did not voted for a slave about this master for two + * times the node timeout. This is not strictly needed for correctness + * of the algorithm but makes the base case more linear. */ + if (mstime() - node->slaveof->voted_time < server.cluster_node_timeout * 2) + { + serverLog(LL_WARNING, + "Failover auth denied to %.40s %s: " + "can't vote about this master before %lld milliseconds", + node->name, node->human_nodename, + (long long) ((server.cluster_node_timeout*2)- + (mstime() - node->slaveof->voted_time))); + return; + } + + /* The slave requesting the vote must have a configEpoch for the claimed + * slots that is >= the one of the masters currently serving the same + * slots in the current configuration. */ + for (j = 0; j < CLUSTER_SLOTS; j++) { + if (bitmapTestBit(claimed_slots, j) == 0) continue; + if (isSlotUnclaimed(j) || + server.cluster->slots[j]->configEpoch <= requestConfigEpoch) + { + continue; + } + /* If we reached this point we found a slot that in our current slots + * is served by a master with a greater configEpoch than the one claimed + * by the slave requesting our vote. Refuse to vote for this slave. */ + serverLog(LL_WARNING, + "Failover auth denied to %.40s (%s): " + "slot %d epoch (%llu) > reqEpoch (%llu)", + node->name, node->human_nodename, j, + (unsigned long long) server.cluster->slots[j]->configEpoch, + (unsigned long long) requestConfigEpoch); + return; + } + + /* We can vote for this slave. */ + server.cluster->lastVoteEpoch = server.cluster->currentEpoch; + node->slaveof->voted_time = mstime(); + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG); + clusterSendFailoverAuth(node); + serverLog(LL_NOTICE, "Failover auth granted to %.40s (%s) for epoch %llu", + node->name, node->human_nodename, (unsigned long long) server.cluster->currentEpoch); +} + +/* This function returns the "rank" of this instance, a slave, in the context + * of its master-slaves ring. The rank of the slave is given by the number of + * other slaves for the same master that have a better replication offset + * compared to the local one (better means, greater, so they claim more data). + * + * A slave with rank 0 is the one with the greatest (most up to date) + * replication offset, and so forth. Note that because how the rank is computed + * multiple slaves may have the same rank, in case they have the same offset. + * + * The slave rank is used to add a delay to start an election in order to + * get voted and replace a failing master. Slaves with better replication + * offsets are more likely to win. */ +int clusterGetSlaveRank(void) { + long long myoffset; + int j, rank = 0; + clusterNode *master; + + serverAssert(nodeIsSlave(myself)); + master = myself->slaveof; + if (master == NULL) return 0; /* Never called by slaves without master. */ + + myoffset = replicationGetSlaveOffset(); + for (j = 0; j < master->numslaves; j++) + if (master->slaves[j] != myself && + !nodeCantFailover(master->slaves[j]) && + master->slaves[j]->repl_offset > myoffset) rank++; + return rank; +} + +/* This function is called by clusterHandleSlaveFailover() in order to + * let the slave log why it is not able to failover. Sometimes there are + * not the conditions, but since the failover function is called again and + * again, we can't log the same things continuously. + * + * This function works by logging only if a given set of conditions are + * true: + * + * 1) The reason for which the failover can't be initiated changed. + * The reasons also include a NONE reason we reset the state to + * when the slave finds that its master is fine (no FAIL flag). + * 2) Also, the log is emitted again if the master is still down and + * the reason for not failing over is still the same, but more than + * CLUSTER_CANT_FAILOVER_RELOG_PERIOD seconds elapsed. + * 3) Finally, the function only logs if the slave is down for more than + * five seconds + NODE_TIMEOUT. This way nothing is logged when a + * failover starts in a reasonable time. + * + * The function is called with the reason why the slave can't failover + * which is one of the integer macros CLUSTER_CANT_FAILOVER_*. + * + * The function is guaranteed to be called only if 'myself' is a slave. */ +void clusterLogCantFailover(int reason) { + char *msg; + static time_t lastlog_time = 0; + mstime_t nolog_fail_time = server.cluster_node_timeout + 5000; + + /* Don't log if we have the same reason for some time. */ + if (reason == server.cluster->cant_failover_reason && + time(NULL)-lastlog_time < CLUSTER_CANT_FAILOVER_RELOG_PERIOD) + return; + + server.cluster->cant_failover_reason = reason; + + /* We also don't emit any log if the master failed no long ago, the + * goal of this function is to log slaves in a stalled condition for + * a long time. */ + if (myself->slaveof && + nodeFailed(myself->slaveof) && + (mstime() - myself->slaveof->fail_time) < nolog_fail_time) return; + + switch(reason) { + case CLUSTER_CANT_FAILOVER_DATA_AGE: + msg = "Disconnected from master for longer than allowed. " + "Please check the 'cluster-replica-validity-factor' configuration " + "option."; + break; + case CLUSTER_CANT_FAILOVER_WAITING_DELAY: + msg = "Waiting the delay before I can start a new failover."; + break; + case CLUSTER_CANT_FAILOVER_EXPIRED: + msg = "Failover attempt expired."; + break; + case CLUSTER_CANT_FAILOVER_WAITING_VOTES: + msg = "Waiting for votes, but majority still not reached."; + break; + default: + msg = "Unknown reason code."; + break; + } + lastlog_time = time(NULL); + serverLog(LL_NOTICE,"Currently unable to failover: %s", msg); + + int cur_vote = server.cluster->failover_auth_count; + int cur_quorum = (server.cluster->size / 2) + 1; + /* Emits a log when an election is in progress and waiting for votes or when the failover attempt expired. */ + if (reason == CLUSTER_CANT_FAILOVER_WAITING_VOTES || reason == CLUSTER_CANT_FAILOVER_EXPIRED) { + serverLog(LL_NOTICE, "Needed quorum: %d. Number of votes received so far: %d", cur_quorum, cur_vote); + } +} + +/* This function implements the final part of automatic and manual failovers, + * where the slave grabs its master's hash slots, and propagates the new + * configuration. + * + * Note that it's up to the caller to be sure that the node got a new + * configuration epoch already. */ +void clusterFailoverReplaceYourMaster(void) { + int j; + clusterNode *oldmaster = myself->slaveof; + + if (clusterNodeIsMaster(myself) || oldmaster == NULL) return; + + /* 1) Turn this node into a master. */ + clusterSetNodeAsMaster(myself); + replicationUnsetMaster(); + + /* 2) Claim all the slots assigned to our master. */ + for (j = 0; j < CLUSTER_SLOTS; j++) { + if (clusterNodeCoversSlot(oldmaster, j)) { + clusterDelSlot(j); + clusterAddSlot(myself,j); + } + } + + /* 3) Update state and save config. */ + clusterUpdateState(); + clusterSaveConfigOrDie(1); + + /* 4) Pong all the other nodes so that they can update the state + * accordingly and detect that we switched to master role. */ + clusterBroadcastPong(CLUSTER_BROADCAST_ALL); + + /* 5) If there was a manual failover in progress, clear the state. */ + resetManualFailover(); +} + +/* This function is called if we are a slave node and our master serving + * a non-zero amount of hash slots is in FAIL state. + * + * The goal of this function is: + * 1) To check if we are able to perform a failover, is our data updated? + * 2) Try to get elected by masters. + * 3) Perform the failover informing all the other nodes. + */ +void clusterHandleSlaveFailover(void) { + mstime_t data_age; + mstime_t auth_age = mstime() - server.cluster->failover_auth_time; + int needed_quorum = (server.cluster->size / 2) + 1; + int manual_failover = server.cluster->mf_end != 0 && + server.cluster->mf_can_start; + mstime_t auth_timeout, auth_retry_time; + + server.cluster->todo_before_sleep &= ~CLUSTER_TODO_HANDLE_FAILOVER; + + /* Compute the failover timeout (the max time we have to send votes + * and wait for replies), and the failover retry time (the time to wait + * before trying to get voted again). + * + * Timeout is MAX(NODE_TIMEOUT*2,2000) milliseconds. + * Retry is two times the Timeout. + */ + auth_timeout = server.cluster_node_timeout*2; + if (auth_timeout < 2000) auth_timeout = 2000; + auth_retry_time = auth_timeout*2; + + /* Pre conditions to run the function, that must be met both in case + * of an automatic or manual failover: + * 1) We are a slave. + * 2) Our master is flagged as FAIL, or this is a manual failover. + * 3) We don't have the no failover configuration set, and this is + * not a manual failover. + * 4) It is serving slots. */ + if (clusterNodeIsMaster(myself) || + myself->slaveof == NULL || + (!nodeFailed(myself->slaveof) && !manual_failover) || + (server.cluster_slave_no_failover && !manual_failover) || + myself->slaveof->numslots == 0) + { + /* There are no reasons to failover, so we set the reason why we + * are returning without failing over to NONE. */ + server.cluster->cant_failover_reason = CLUSTER_CANT_FAILOVER_NONE; + return; + } + + /* Set data_age to the number of milliseconds we are disconnected from + * the master. */ + if (server.repl_state == REPL_STATE_CONNECTED) { + data_age = (mstime_t)(server.unixtime - server.master->lastinteraction) + * 1000; + } else { + data_age = (mstime_t)(server.unixtime - server.repl_down_since) * 1000; + } + + /* Remove the node timeout from the data age as it is fine that we are + * disconnected from our master at least for the time it was down to be + * flagged as FAIL, that's the baseline. */ + if (data_age > server.cluster_node_timeout) + data_age -= server.cluster_node_timeout; + + /* Check if our data is recent enough according to the slave validity + * factor configured by the user. + * + * Check bypassed for manual failovers. */ + if (server.cluster_slave_validity_factor && + data_age > + (((mstime_t)server.repl_ping_slave_period * 1000) + + (server.cluster_node_timeout * server.cluster_slave_validity_factor))) + { + if (!manual_failover) { + clusterLogCantFailover(CLUSTER_CANT_FAILOVER_DATA_AGE); + return; + } + } + + /* If the previous failover attempt timeout and the retry time has + * elapsed, we can setup a new one. */ + if (auth_age > auth_retry_time) { + server.cluster->failover_auth_time = mstime() + + 500 + /* Fixed delay of 500 milliseconds, let FAIL msg propagate. */ + random() % 500; /* Random delay between 0 and 500 milliseconds. */ + server.cluster->failover_auth_count = 0; + server.cluster->failover_auth_sent = 0; + server.cluster->failover_auth_rank = clusterGetSlaveRank(); + /* We add another delay that is proportional to the slave rank. + * Specifically 1 second * rank. This way slaves that have a probably + * less updated replication offset, are penalized. */ + server.cluster->failover_auth_time += + server.cluster->failover_auth_rank * 1000; + /* However if this is a manual failover, no delay is needed. */ + if (server.cluster->mf_end) { + server.cluster->failover_auth_time = mstime(); + server.cluster->failover_auth_rank = 0; + clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER); + } + serverLog(LL_NOTICE, + "Start of election delayed for %lld milliseconds " + "(rank #%d, offset %lld).", + server.cluster->failover_auth_time - mstime(), + server.cluster->failover_auth_rank, + replicationGetSlaveOffset()); + /* Now that we have a scheduled election, broadcast our offset + * to all the other slaves so that they'll updated their offsets + * if our offset is better. */ + clusterBroadcastPong(CLUSTER_BROADCAST_LOCAL_SLAVES); + return; + } + + /* It is possible that we received more updated offsets from other + * slaves for the same master since we computed our election delay. + * Update the delay if our rank changed. + * + * Not performed if this is a manual failover. */ + if (server.cluster->failover_auth_sent == 0 && + server.cluster->mf_end == 0) + { + int newrank = clusterGetSlaveRank(); + if (newrank > server.cluster->failover_auth_rank) { + long long added_delay = + (newrank - server.cluster->failover_auth_rank) * 1000; + server.cluster->failover_auth_time += added_delay; + server.cluster->failover_auth_rank = newrank; + serverLog(LL_NOTICE, + "Replica rank updated to #%d, added %lld milliseconds of delay.", + newrank, added_delay); + } + } + + /* Return ASAP if we can't still start the election. */ + if (mstime() < server.cluster->failover_auth_time) { + clusterLogCantFailover(CLUSTER_CANT_FAILOVER_WAITING_DELAY); + return; + } + + /* Return ASAP if the election is too old to be valid. */ + if (auth_age > auth_timeout) { + clusterLogCantFailover(CLUSTER_CANT_FAILOVER_EXPIRED); + return; + } + + /* Ask for votes if needed. */ + if (server.cluster->failover_auth_sent == 0) { + server.cluster->currentEpoch++; + server.cluster->failover_auth_epoch = server.cluster->currentEpoch; + serverLog(LL_NOTICE,"Starting a failover election for epoch %llu.", + (unsigned long long) server.cluster->currentEpoch); + clusterRequestFailoverAuth(); + server.cluster->failover_auth_sent = 1; + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| + CLUSTER_TODO_UPDATE_STATE| + CLUSTER_TODO_FSYNC_CONFIG); + return; /* Wait for replies. */ + } + + /* Check if we reached the quorum. */ + if (server.cluster->failover_auth_count >= needed_quorum) { + /* We have the quorum, we can finally failover the master. */ + + serverLog(LL_NOTICE, + "Failover election won: I'm the new master."); + + /* Update my configEpoch to the epoch of the election. */ + if (myself->configEpoch < server.cluster->failover_auth_epoch) { + myself->configEpoch = server.cluster->failover_auth_epoch; + serverLog(LL_NOTICE, + "configEpoch set to %llu after successful failover", + (unsigned long long) myself->configEpoch); + } + + /* Take responsibility for the cluster slots. */ + clusterFailoverReplaceYourMaster(); + } else { + clusterLogCantFailover(CLUSTER_CANT_FAILOVER_WAITING_VOTES); + } +} + +/* ----------------------------------------------------------------------------- + * CLUSTER slave migration + * + * Slave migration is the process that allows a slave of a master that is + * already covered by at least another slave, to "migrate" to a master that + * is orphaned, that is, left with no working slaves. + * ------------------------------------------------------------------------- */ + +/* This function is responsible to decide if this replica should be migrated + * to a different (orphaned) master. It is called by the clusterCron() function + * only if: + * + * 1) We are a slave node. + * 2) It was detected that there is at least one orphaned master in + * the cluster. + * 3) We are a slave of one of the masters with the greatest number of + * slaves. + * + * This checks are performed by the caller since it requires to iterate + * the nodes anyway, so we spend time into clusterHandleSlaveMigration() + * if definitely needed. + * + * The function is called with a pre-computed max_slaves, that is the max + * number of working (not in FAIL state) slaves for a single master. + * + * Additional conditions for migration are examined inside the function. + */ +void clusterHandleSlaveMigration(int max_slaves) { + int j, okslaves = 0; + clusterNode *mymaster = myself->slaveof, *target = NULL, *candidate = NULL; + dictIterator *di; + dictEntry *de; + + /* Step 1: Don't migrate if the cluster state is not ok. */ + if (server.cluster->state != CLUSTER_OK) return; + + /* Step 2: Don't migrate if my master will not be left with at least + * 'migration-barrier' slaves after my migration. */ + if (mymaster == NULL) return; + for (j = 0; j < mymaster->numslaves; j++) + if (!nodeFailed(mymaster->slaves[j]) && + !nodeTimedOut(mymaster->slaves[j])) okslaves++; + if (okslaves <= server.cluster_migration_barrier) return; + + /* Step 3: Identify a candidate for migration, and check if among the + * masters with the greatest number of ok slaves, I'm the one with the + * smallest node ID (the "candidate slave"). + * + * Note: this means that eventually a replica migration will occur + * since slaves that are reachable again always have their FAIL flag + * cleared, so eventually there must be a candidate. + * There is a possible race condition causing multiple + * slaves to migrate at the same time, but this is unlikely to + * happen and relatively harmless when it does. */ + candidate = myself; + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + int okslaves = 0, is_orphaned = 1; + + /* We want to migrate only if this master is working, orphaned, and + * used to have slaves or if failed over a master that had slaves + * (MIGRATE_TO flag). This way we only migrate to instances that were + * supposed to have replicas. */ + if (nodeIsSlave(node) || nodeFailed(node)) is_orphaned = 0; + if (!(node->flags & CLUSTER_NODE_MIGRATE_TO)) is_orphaned = 0; + + /* Check number of working slaves. */ + if (clusterNodeIsMaster(node)) okslaves = clusterCountNonFailingSlaves(node); + if (okslaves > 0) is_orphaned = 0; + + if (is_orphaned) { + if (!target && node->numslots > 0) target = node; + + /* Track the starting time of the orphaned condition for this + * master. */ + if (!node->orphaned_time) node->orphaned_time = mstime(); + } else { + node->orphaned_time = 0; + } + + /* Check if I'm the slave candidate for the migration: attached + * to a master with the maximum number of slaves and with the smallest + * node ID. */ + if (okslaves == max_slaves) { + for (j = 0; j < node->numslaves; j++) { + if (memcmp(node->slaves[j]->name, + candidate->name, + CLUSTER_NAMELEN) < 0) + { + candidate = node->slaves[j]; + } + } + } + } + dictReleaseIterator(di); + + /* Step 4: perform the migration if there is a target, and if I'm the + * candidate, but only if the master is continuously orphaned for a + * couple of seconds, so that during failovers, we give some time to + * the natural slaves of this instance to advertise their switch from + * the old master to the new one. */ + if (target && candidate == myself && + (mstime()-target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY && + !(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER)) + { + serverLog(LL_NOTICE,"Migrating to orphaned master %.40s", + target->name); + clusterSetMaster(target); + } +} + +/* ----------------------------------------------------------------------------- + * CLUSTER manual failover + * + * This are the important steps performed by slaves during a manual failover: + * 1) User send CLUSTER FAILOVER command. The failover state is initialized + * setting mf_end to the millisecond unix time at which we'll abort the + * attempt. + * 2) Slave sends a MFSTART message to the master requesting to pause clients + * for two times the manual failover timeout CLUSTER_MF_TIMEOUT. + * When master is paused for manual failover, it also starts to flag + * packets with CLUSTERMSG_FLAG0_PAUSED. + * 3) Slave waits for master to send its replication offset flagged as PAUSED. + * 4) If slave received the offset from the master, and its offset matches, + * mf_can_start is set to 1, and clusterHandleSlaveFailover() will perform + * the failover as usually, with the difference that the vote request + * will be modified to force masters to vote for a slave that has a + * working master. + * + * From the point of view of the master things are simpler: when a + * PAUSE_CLIENTS packet is received the master sets mf_end as well and + * the sender in mf_slave. During the time limit for the manual failover + * the master will just send PINGs more often to this slave, flagged with + * the PAUSED flag, so that the slave will set mf_master_offset when receiving + * a packet from the master with this flag set. + * + * The goal of the manual failover is to perform a fast failover without + * data loss due to the asynchronous master-slave replication. + * -------------------------------------------------------------------------- */ + +/* Reset the manual failover state. This works for both masters and slaves + * as all the state about manual failover is cleared. + * + * The function can be used both to initialize the manual failover state at + * startup or to abort a manual failover in progress. */ +void resetManualFailover(void) { + if (server.cluster->mf_slave) { + /* We were a master failing over, so we paused clients and related actions. + * Regardless of the outcome we unpause now to allow traffic again. */ + unpauseActions(PAUSE_DURING_FAILOVER); + } + server.cluster->mf_end = 0; /* No manual failover in progress. */ + server.cluster->mf_can_start = 0; + server.cluster->mf_slave = NULL; + server.cluster->mf_master_offset = -1; +} + +/* If a manual failover timed out, abort it. */ +void manualFailoverCheckTimeout(void) { + if (server.cluster->mf_end && server.cluster->mf_end < mstime()) { + serverLog(LL_WARNING,"Manual failover timed out."); + resetManualFailover(); + } +} + +/* This function is called from the cluster cron function in order to go + * forward with a manual failover state machine. */ +void clusterHandleManualFailover(void) { + /* Return ASAP if no manual failover is in progress. */ + if (server.cluster->mf_end == 0) return; + + /* If mf_can_start is non-zero, the failover was already triggered so the + * next steps are performed by clusterHandleSlaveFailover(). */ + if (server.cluster->mf_can_start) return; + + if (server.cluster->mf_master_offset == -1) return; /* Wait for offset... */ + + if (server.cluster->mf_master_offset == replicationGetSlaveOffset()) { + /* Our replication offset matches the master replication offset + * announced after clients were paused. We can start the failover. */ + server.cluster->mf_can_start = 1; + serverLog(LL_NOTICE, + "All master replication stream processed, " + "manual failover can start."); + clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER); + return; + } + clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER); +} + +/* ----------------------------------------------------------------------------- + * CLUSTER cron job + * -------------------------------------------------------------------------- */ + +/* Check if the node is disconnected and re-establish the connection. + * Also update a few stats while we are here, that can be used to make + * better decisions in other part of the code. */ +static int clusterNodeCronHandleReconnect(clusterNode *node, mstime_t handshake_timeout, mstime_t now) { + /* Not interested in reconnecting the link with myself or nodes + * for which we have no address. */ + if (node->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR)) return 1; + + if (node->flags & CLUSTER_NODE_PFAIL) + server.cluster->stats_pfail_nodes++; + + /* A Node in HANDSHAKE state has a limited lifespan equal to the + * configured node timeout. */ + if (nodeInHandshake(node) && now - node->ctime > handshake_timeout) { + clusterDelNode(node); + return 1; + } + + if (node->link == NULL) { + clusterLink *link = createClusterLink(node); + link->conn = connCreate(connTypeOfCluster()); + connSetPrivateData(link->conn, link); + if (connConnect(link->conn, node->ip, node->cport, server.bind_source_addr, + clusterLinkConnectHandler) == C_ERR) { + /* We got a synchronous error from connect before + * clusterSendPing() had a chance to be called. + * If node->ping_sent is zero, failure detection can't work, + * so we claim we actually sent a ping now (that will + * be really sent as soon as the link is obtained). */ + if (node->ping_sent == 0) node->ping_sent = mstime(); + serverLog(LL_DEBUG, "Unable to connect to " + "Cluster Node [%s]:%d -> %s", node->ip, + node->cport, server.neterr); + + freeClusterLink(link); + return 0; + } + } + return 0; +} + +static void freeClusterLinkOnBufferLimitReached(clusterLink *link) { + if (link == NULL || server.cluster_link_msg_queue_limit_bytes == 0) { + return; + } + + unsigned long long mem_link = link->send_msg_queue_mem; + if (mem_link > server.cluster_link_msg_queue_limit_bytes) { + serverLog(LL_WARNING, "Freeing cluster link(%s node %.40s, used memory: %llu) due to " + "exceeding send buffer memory limit.", link->inbound ? "from" : "to", + link->node ? link->node->name : "", mem_link); + freeClusterLink(link); + server.cluster->stat_cluster_links_buffer_limit_exceeded++; + } +} + +/* Free outbound link to a node if its send buffer size exceeded limit. */ +static void clusterNodeCronFreeLinkOnBufferLimitReached(clusterNode *node) { + freeClusterLinkOnBufferLimitReached(node->link); + freeClusterLinkOnBufferLimitReached(node->inbound_link); +} + +/* This is executed 10 times every second */ +void clusterCron(void) { + dictIterator *di; + dictEntry *de; + int update_state = 0; + int orphaned_masters; /* How many masters there are without ok slaves. */ + int max_slaves; /* Max number of ok slaves for a single master. */ + int this_slaves; /* Number of ok slaves for our master (if we are slave). */ + mstime_t min_pong = 0, now = mstime(); + clusterNode *min_pong_node = NULL; + static unsigned long long iteration = 0; + mstime_t handshake_timeout; + + iteration++; /* Number of times this function was called so far. */ + + clusterUpdateMyselfHostname(); + + /* The handshake timeout is the time after which a handshake node that was + * not turned into a normal node is removed from the nodes. Usually it is + * just the NODE_TIMEOUT value, but when NODE_TIMEOUT is too small we use + * the value of 1 second. */ + handshake_timeout = server.cluster_node_timeout; + if (handshake_timeout < 1000) handshake_timeout = 1000; + + /* Clear so clusterNodeCronHandleReconnect can count the number of nodes in PFAIL. */ + server.cluster->stats_pfail_nodes = 0; + /* Run through some of the operations we want to do on each cluster node. */ + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + /* We free the inbound or outboud link to the node if the link has an + * oversized message send queue and immediately try reconnecting. */ + clusterNodeCronFreeLinkOnBufferLimitReached(node); + /* The protocol is that function(s) below return non-zero if the node was + * terminated. + */ + if(clusterNodeCronHandleReconnect(node, handshake_timeout, now)) continue; + } + dictReleaseIterator(di); + + /* Ping some random node 1 time every 10 iterations, so that we usually ping + * one random node every second. */ + if (!(iteration % 10)) { + int j; + + /* Check a few random nodes and ping the one with the oldest + * pong_received time. */ + for (j = 0; j < 5; j++) { + de = dictGetRandomKey(server.cluster->nodes); + clusterNode *this = dictGetVal(de); + + /* Don't ping nodes disconnected or with a ping currently active. */ + if (this->link == NULL || this->ping_sent != 0) continue; + if (this->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_HANDSHAKE)) + continue; + if (min_pong_node == NULL || min_pong > this->pong_received) { + min_pong_node = this; + min_pong = this->pong_received; + } + } + if (min_pong_node) { + serverLog(LL_DEBUG,"Pinging node %.40s", min_pong_node->name); + clusterSendPing(min_pong_node->link, CLUSTERMSG_TYPE_PING); + } + } + + /* Iterate nodes to check if we need to flag something as failing. + * This loop is also responsible to: + * 1) Check if there are orphaned masters (masters without non failing + * slaves). + * 2) Count the max number of non failing slaves for a single master. + * 3) Count the number of slaves for our master, if we are a slave. */ + orphaned_masters = 0; + max_slaves = 0; + this_slaves = 0; + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + now = mstime(); /* Use an updated time at every iteration. */ + + if (node->flags & + (CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE)) + continue; + + /* Orphaned master check, useful only if the current instance + * is a slave that may migrate to another master. */ + if (nodeIsSlave(myself) && clusterNodeIsMaster(node) && !nodeFailed(node)) { + int okslaves = clusterCountNonFailingSlaves(node); + + /* A master is orphaned if it is serving a non-zero number of + * slots, have no working slaves, but used to have at least one + * slave, or failed over a master that used to have slaves. */ + if (okslaves == 0 && node->numslots > 0 && + node->flags & CLUSTER_NODE_MIGRATE_TO) + { + orphaned_masters++; + } + if (okslaves > max_slaves) max_slaves = okslaves; + if (myself->slaveof == node) + this_slaves = okslaves; + } + + /* If we are not receiving any data for more than half the cluster + * timeout, reconnect the link: maybe there is a connection + * issue even if the node is alive. */ + mstime_t ping_delay = now - node->ping_sent; + mstime_t data_delay = now - node->data_received; + if (node->link && /* is connected */ + now - node->link->ctime > + server.cluster_node_timeout && /* was not already reconnected */ + node->ping_sent && /* we already sent a ping */ + /* and we are waiting for the pong more than timeout/2 */ + ping_delay > server.cluster_node_timeout/2 && + /* and in such interval we are not seeing any traffic at all. */ + data_delay > server.cluster_node_timeout/2) + { + /* Disconnect the link, it will be reconnected automatically. */ + freeClusterLink(node->link); + } + + /* If we have currently no active ping in this instance, and the + * received PONG is older than half the cluster timeout, send + * a new ping now, to ensure all the nodes are pinged without + * a too big delay. */ + mstime_t ping_interval = server.cluster_ping_interval ? + server.cluster_ping_interval : server.cluster_node_timeout/2; + if (node->link && + node->ping_sent == 0 && + (now - node->pong_received) > ping_interval) + { + clusterSendPing(node->link, CLUSTERMSG_TYPE_PING); + continue; + } + + /* If we are a master and one of the slaves requested a manual + * failover, ping it continuously. */ + if (server.cluster->mf_end && + clusterNodeIsMaster(myself) && + server.cluster->mf_slave == node && + node->link) + { + clusterSendPing(node->link, CLUSTERMSG_TYPE_PING); + continue; + } + + /* Check only if we have an active ping for this instance. */ + if (node->ping_sent == 0) continue; + + /* Check if this node looks unreachable. + * Note that if we already received the PONG, then node->ping_sent + * is zero, so can't reach this code at all, so we don't risk of + * checking for a PONG delay if we didn't sent the PING. + * + * We also consider every incoming data as proof of liveness, since + * our cluster bus link is also used for data: under heavy data + * load pong delays are possible. */ + mstime_t node_delay = (ping_delay < data_delay) ? ping_delay : + data_delay; + + if (node_delay > server.cluster_node_timeout) { + /* Timeout reached. Set the node as possibly failing if it is + * not already in this state. */ + if (!(node->flags & (CLUSTER_NODE_PFAIL|CLUSTER_NODE_FAIL))) { + node->flags |= CLUSTER_NODE_PFAIL; + update_state = 1; + if (clusterNodeIsMaster(myself) && server.cluster->size == 1) { + markNodeAsFailingIfNeeded(node); + } else { + serverLog(LL_DEBUG,"*** NODE %.40s possibly failing", node->name); + } + } + } + } + dictReleaseIterator(di); + + /* If we are a slave node but the replication is still turned off, + * enable it if we know the address of our master and it appears to + * be up. */ + if (nodeIsSlave(myself) && + server.masterhost == NULL && + myself->slaveof && + nodeHasAddr(myself->slaveof)) + { + replicationSetMaster(myself->slaveof->ip, getNodeDefaultReplicationPort(myself->slaveof)); + } + + /* Abort a manual failover if the timeout is reached. */ + manualFailoverCheckTimeout(); + + if (nodeIsSlave(myself)) { + clusterHandleManualFailover(); + if (!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER)) + clusterHandleSlaveFailover(); + /* If there are orphaned slaves, and we are a slave among the masters + * with the max number of non-failing slaves, consider migrating to + * the orphaned masters. Note that it does not make sense to try + * a migration if there is no master with at least *two* working + * slaves. */ + if (orphaned_masters && max_slaves >= 2 && this_slaves == max_slaves && + server.cluster_allow_replica_migration) + clusterHandleSlaveMigration(max_slaves); + } + + if (update_state || server.cluster->state == CLUSTER_FAIL) + clusterUpdateState(); +} + +/* This function is called before the event handler returns to sleep for + * events. It is useful to perform operations that must be done ASAP in + * reaction to events fired but that are not safe to perform inside event + * handlers, or to perform potentially expansive tasks that we need to do + * a single time before replying to clients. */ +void clusterBeforeSleep(void) { + int flags = server.cluster->todo_before_sleep; + + /* Reset our flags (not strictly needed since every single function + * called for flags set should be able to clear its flag). */ + server.cluster->todo_before_sleep = 0; + + if (flags & CLUSTER_TODO_HANDLE_MANUALFAILOVER) { + /* Handle manual failover as soon as possible so that won't have a 100ms + * as it was handled only in clusterCron */ + if(nodeIsSlave(myself)) { + clusterHandleManualFailover(); + if (!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER)) + clusterHandleSlaveFailover(); + } + } else if (flags & CLUSTER_TODO_HANDLE_FAILOVER) { + /* Handle failover, this is needed when it is likely that there is already + * the quorum from masters in order to react fast. */ + clusterHandleSlaveFailover(); + } + + /* Update the cluster state. */ + if (flags & CLUSTER_TODO_UPDATE_STATE) + clusterUpdateState(); + + /* Save the config, possibly using fsync. */ + if (flags & CLUSTER_TODO_SAVE_CONFIG) { + int fsync = flags & CLUSTER_TODO_FSYNC_CONFIG; + clusterSaveConfigOrDie(fsync); + } +} + +void clusterDoBeforeSleep(int flags) { + server.cluster->todo_before_sleep |= flags; +} + +/* ----------------------------------------------------------------------------- + * Slots management + * -------------------------------------------------------------------------- */ + +/* Test bit 'pos' in a generic bitmap. Return 1 if the bit is set, + * otherwise 0. */ +int bitmapTestBit(unsigned char *bitmap, int pos) { + off_t byte = pos/8; + int bit = pos&7; + return (bitmap[byte] & (1<nodes); + dictEntry *de; + int slaves = 0; + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + + if (nodeIsSlave(node)) continue; + slaves += node->numslaves; + } + dictReleaseIterator(di); + return slaves != 0; +} + +/* Set the slot bit and return the old value. */ +int clusterNodeSetSlotBit(clusterNode *n, int slot) { + int old = bitmapTestBit(n->slots,slot); + if (!old) { + bitmapSetBit(n->slots,slot); + n->numslots++; + /* When a master gets its first slot, even if it has no slaves, + * it gets flagged with MIGRATE_TO, that is, the master is a valid + * target for replicas migration, if and only if at least one of + * the other masters has slaves right now. + * + * Normally masters are valid targets of replica migration if: + * 1. The used to have slaves (but no longer have). + * 2. They are slaves failing over a master that used to have slaves. + * + * However new masters with slots assigned are considered valid + * migration targets if the rest of the cluster is not a slave-less. + * + * See https://github.com/redis/redis/issues/3043 for more info. */ + if (n->numslots == 1 && clusterMastersHaveSlaves()) + n->flags |= CLUSTER_NODE_MIGRATE_TO; + } + return old; +} + +/* Clear the slot bit and return the old value. */ +int clusterNodeClearSlotBit(clusterNode *n, int slot) { + int old = bitmapTestBit(n->slots,slot); + if (old) { + bitmapClearBit(n->slots,slot); + n->numslots--; + } + return old; +} + +/* Return the slot bit from the cluster node structure. */ +int clusterNodeCoversSlot(clusterNode *n, int slot) { + return bitmapTestBit(n->slots,slot); +} + +/* Add the specified slot to the list of slots that node 'n' will + * serve. Return C_OK if the operation ended with success. + * If the slot is already assigned to another instance this is considered + * an error and C_ERR is returned. */ +int clusterAddSlot(clusterNode *n, int slot) { + if (server.cluster->slots[slot]) return C_ERR; + clusterNodeSetSlotBit(n,slot); + server.cluster->slots[slot] = n; + return C_OK; +} + +/* Delete the specified slot marking it as unassigned. + * Returns C_OK if the slot was assigned, otherwise if the slot was + * already unassigned C_ERR is returned. */ +int clusterDelSlot(int slot) { + clusterNode *n = server.cluster->slots[slot]; + + if (!n) return C_ERR; + + /* Cleanup the channels in master/replica as part of slot deletion. */ + removeChannelsInSlot(slot); + /* Clear the slot bit. */ + serverAssert(clusterNodeClearSlotBit(n,slot) == 1); + server.cluster->slots[slot] = NULL; + /* Make owner_not_claiming_slot flag consistent with slot ownership information. */ + bitmapClearBit(server.cluster->owner_not_claiming_slot, slot); + return C_OK; +} + +/* Delete all the slots associated with the specified node. + * The number of deleted slots is returned. */ +int clusterDelNodeSlots(clusterNode *node) { + int deleted = 0, j; + + for (j = 0; j < CLUSTER_SLOTS; j++) { + if (clusterNodeCoversSlot(node, j)) { + clusterDelSlot(j); + deleted++; + } + } + return deleted; +} + +/* Clear the migrating / importing state for all the slots. + * This is useful at initialization and when turning a master into slave. */ +void clusterCloseAllSlots(void) { + memset(server.cluster->migrating_slots_to,0, + sizeof(server.cluster->migrating_slots_to)); + memset(server.cluster->importing_slots_from,0, + sizeof(server.cluster->importing_slots_from)); +} + +/* ----------------------------------------------------------------------------- + * Cluster state evaluation function + * -------------------------------------------------------------------------- */ + +/* The following are defines that are only used in the evaluation function + * and are based on heuristics. Actually the main point about the rejoin and + * writable delay is that they should be a few orders of magnitude larger + * than the network latency. */ +#define CLUSTER_MAX_REJOIN_DELAY 5000 +#define CLUSTER_MIN_REJOIN_DELAY 500 +#define CLUSTER_WRITABLE_DELAY 2000 + +void clusterUpdateState(void) { + int j, new_state; + int reachable_masters = 0; + static mstime_t among_minority_time; + static mstime_t first_call_time = 0; + + server.cluster->todo_before_sleep &= ~CLUSTER_TODO_UPDATE_STATE; + + /* If this is a master node, wait some time before turning the state + * into OK, since it is not a good idea to rejoin the cluster as a writable + * master, after a reboot, without giving the cluster a chance to + * reconfigure this node. Note that the delay is calculated starting from + * the first call to this function and not since the server start, in order + * to not count the DB loading time. */ + if (first_call_time == 0) first_call_time = mstime(); + if (clusterNodeIsMaster(myself) && + server.cluster->state == CLUSTER_FAIL && + mstime() - first_call_time < CLUSTER_WRITABLE_DELAY) return; + + /* Start assuming the state is OK. We'll turn it into FAIL if there + * are the right conditions. */ + new_state = CLUSTER_OK; + + /* Check if all the slots are covered. */ + if (server.cluster_require_full_coverage) { + for (j = 0; j < CLUSTER_SLOTS; j++) { + if (server.cluster->slots[j] == NULL || + server.cluster->slots[j]->flags & (CLUSTER_NODE_FAIL)) + { + new_state = CLUSTER_FAIL; + break; + } + } + } + + /* Compute the cluster size, that is the number of master nodes + * serving at least a single slot. + * + * At the same time count the number of reachable masters having + * at least one slot. */ + { + dictIterator *di; + dictEntry *de; + + server.cluster->size = 0; + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + + if (clusterNodeIsMaster(node) && node->numslots) { + server.cluster->size++; + if ((node->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) == 0) + reachable_masters++; + } + } + dictReleaseIterator(di); + } + + /* If we are in a minority partition, change the cluster state + * to FAIL. */ + { + int needed_quorum = (server.cluster->size / 2) + 1; + + if (reachable_masters < needed_quorum) { + new_state = CLUSTER_FAIL; + among_minority_time = mstime(); + } + } + + /* Log a state change */ + if (new_state != server.cluster->state) { + mstime_t rejoin_delay = server.cluster_node_timeout; + + /* If the instance is a master and was partitioned away with the + * minority, don't let it accept queries for some time after the + * partition heals, to make sure there is enough time to receive + * a configuration update. */ + if (rejoin_delay > CLUSTER_MAX_REJOIN_DELAY) + rejoin_delay = CLUSTER_MAX_REJOIN_DELAY; + if (rejoin_delay < CLUSTER_MIN_REJOIN_DELAY) + rejoin_delay = CLUSTER_MIN_REJOIN_DELAY; + + if (new_state == CLUSTER_OK && + clusterNodeIsMaster(myself) && + mstime() - among_minority_time < rejoin_delay) + { + return; + } + + /* Change the state and log the event. */ + serverLog(new_state == CLUSTER_OK ? LL_NOTICE : LL_WARNING, + "Cluster state changed: %s", + new_state == CLUSTER_OK ? "ok" : "fail"); + server.cluster->state = new_state; + } +} + +/* This function is called after the node startup in order to verify that data + * loaded from disk is in agreement with the cluster configuration: + * + * 1) If we find keys about hash slots we have no responsibility for, the + * following happens: + * A) If no other node is in charge according to the current cluster + * configuration, we add these slots to our node. + * B) If according to our config other nodes are already in charge for + * this slots, we set the slots as IMPORTING from our point of view + * in order to justify we have those slots, and in order to make + * redis-cli aware of the issue, so that it can try to fix it. + * 2) If we find data in a DB different than DB0 we return C_ERR to + * signal the caller it should quit the server with an error message + * or take other actions. + * + * The function always returns C_OK even if it will try to correct + * the error described in "1". However if data is found in DB different + * from DB0, C_ERR is returned. + * + * The function also uses the logging facility in order to warn the user + * about desynchronizations between the data we have in memory and the + * cluster configuration. */ +int verifyClusterConfigWithData(void) { + int j; + int update_config = 0; + + /* Return ASAP if a module disabled cluster redirections. In that case + * every master can store keys about every possible hash slot. */ + if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION) + return C_OK; + + /* If this node is a slave, don't perform the check at all as we + * completely depend on the replication stream. */ + if (nodeIsSlave(myself)) return C_OK; + + /* Make sure we only have keys in DB0. */ + for (j = 1; j < server.dbnum; j++) { + if (dbSize(&server.db[j], DB_MAIN)) return C_ERR; + } + + /* Check that all the slots we see populated memory have a corresponding + * entry in the cluster table. Otherwise fix the table. */ + for (j = 0; j < CLUSTER_SLOTS; j++) { + if (!countKeysInSlot(j)) continue; /* No keys in this slot. */ + /* Check if we are assigned to this slot or if we are importing it. + * In both cases check the next slot as the configuration makes + * sense. */ + if (server.cluster->slots[j] == myself || + server.cluster->importing_slots_from[j] != NULL) continue; + + /* If we are here data and cluster config don't agree, and we have + * slot 'j' populated even if we are not importing it, nor we are + * assigned to this slot. Fix this condition. */ + + update_config++; + /* Case A: slot is unassigned. Take responsibility for it. */ + if (server.cluster->slots[j] == NULL) { + serverLog(LL_NOTICE, "I have keys for unassigned slot %d. " + "Taking responsibility for it.",j); + clusterAddSlot(myself,j); + } else { + serverLog(LL_NOTICE, "I have keys for slot %d, but the slot is " + "assigned to another node. " + "Setting it to importing state.",j); + server.cluster->importing_slots_from[j] = server.cluster->slots[j]; + } + } + if (update_config) clusterSaveConfigOrDie(1); + return C_OK; +} + +/* Remove all the shard channel related information not owned by the current shard. */ +static inline void removeAllNotOwnedShardChannelSubscriptions(void) { + if (!server.shard_channel_count) return; + clusterNode *currmaster = clusterNodeIsMaster(myself) ? myself : myself->slaveof; + for (int j = 0; j < CLUSTER_SLOTS; j++) { + if (server.cluster->slots[j] != currmaster) { + removeChannelsInSlot(j); + } + } +} + +/* ----------------------------------------------------------------------------- + * SLAVE nodes handling + * -------------------------------------------------------------------------- */ + +/* Set the specified node 'n' as master for this node. + * If this node is currently a master, it is turned into a slave. */ +void clusterSetMaster(clusterNode *n) { + serverAssert(n != myself); + serverAssert(myself->numslots == 0); + + if (clusterNodeIsMaster(myself)) { + myself->flags &= ~(CLUSTER_NODE_MASTER|CLUSTER_NODE_MIGRATE_TO); + myself->flags |= CLUSTER_NODE_SLAVE; + clusterCloseAllSlots(); + } else { + if (myself->slaveof) + clusterNodeRemoveSlave(myself->slaveof,myself); + } + myself->slaveof = n; + updateShardId(myself, n->shard_id); + clusterNodeAddSlave(n,myself); + replicationSetMaster(n->ip, getNodeDefaultReplicationPort(n)); + removeAllNotOwnedShardChannelSubscriptions(); + resetManualFailover(); +} + +/* ----------------------------------------------------------------------------- + * Nodes to string representation functions. + * -------------------------------------------------------------------------- */ + +struct redisNodeFlags { + uint16_t flag; + char *name; +}; + +static struct redisNodeFlags redisNodeFlagsTable[] = { + {CLUSTER_NODE_MYSELF, "myself,"}, + {CLUSTER_NODE_MASTER, "master,"}, + {CLUSTER_NODE_SLAVE, "slave,"}, + {CLUSTER_NODE_PFAIL, "fail?,"}, + {CLUSTER_NODE_FAIL, "fail,"}, + {CLUSTER_NODE_HANDSHAKE, "handshake,"}, + {CLUSTER_NODE_NOADDR, "noaddr,"}, + {CLUSTER_NODE_NOFAILOVER, "nofailover,"} +}; + +/* Concatenate the comma separated list of node flags to the given SDS + * string 'ci'. */ +sds representClusterNodeFlags(sds ci, uint16_t flags) { + size_t orig_len = sdslen(ci); + int i, size = sizeof(redisNodeFlagsTable)/sizeof(struct redisNodeFlags); + for (i = 0; i < size; i++) { + struct redisNodeFlags *nodeflag = redisNodeFlagsTable + i; + if (flags & nodeflag->flag) ci = sdscat(ci, nodeflag->name); + } + /* If no flag was added, add the "noflags" special flag. */ + if (sdslen(ci) == orig_len) ci = sdscat(ci,"noflags,"); + sdsIncrLen(ci,-1); /* Remove trailing comma. */ + return ci; +} + +/* Concatenate the slot ownership information to the given SDS string 'ci'. + * If the slot ownership is in a contiguous block, it's represented as start-end pair, + * else each slot is added separately. */ +sds representSlotInfo(sds ci, uint16_t *slot_info_pairs, int slot_info_pairs_count) { + for (int i = 0; i< slot_info_pairs_count; i+=2) { + unsigned long start = slot_info_pairs[i]; + unsigned long end = slot_info_pairs[i+1]; + if (start == end) { + ci = sdscatfmt(ci, " %i", start); + } else { + ci = sdscatfmt(ci, " %i-%i", start, end); + } + } + return ci; +} + +/* Generate a csv-alike representation of the specified cluster node. + * See clusterGenNodesDescription() top comment for more information. + * + * The function returns the string representation as an SDS string. */ +sds clusterGenNodeDescription(client *c, clusterNode *node, int tls_primary) { + int j, start; + sds ci; + int port = clusterNodeClientPort(node, tls_primary); + + /* Node coordinates */ + ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN); + ci = sdscatfmt(ci," %s:%i@%i", + node->ip, + port, + node->cport); + if (sdslen(node->hostname) != 0) { + ci = sdscatfmt(ci,",%s", node->hostname); + } + /* Don't expose aux fields to any clients yet but do allow them + * to be persisted to nodes.conf */ + if (c == NULL) { + if (sdslen(node->hostname) == 0) { + ci = sdscatfmt(ci,",", 1); + } + for (int i = af_count-1; i >=0; i--) { + if ((tls_primary && i == af_tls_port) || (!tls_primary && i == af_tcp_port)) { + continue; + } + if (auxFieldHandlers[i].isPresent(node)) { + ci = sdscatprintf(ci, ",%s=", auxFieldHandlers[i].field); + ci = auxFieldHandlers[i].getter(node, ci); + } + } + } + + /* Flags */ + ci = sdscatlen(ci," ",1); + ci = representClusterNodeFlags(ci, node->flags); + + /* Slave of... or just "-" */ + ci = sdscatlen(ci," ",1); + if (node->slaveof) + ci = sdscatlen(ci,node->slaveof->name,CLUSTER_NAMELEN); + else + ci = sdscatlen(ci,"-",1); + + unsigned long long nodeEpoch = node->configEpoch; + if (nodeIsSlave(node) && node->slaveof) { + nodeEpoch = node->slaveof->configEpoch; + } + /* Latency from the POV of this node, config epoch, link status */ + ci = sdscatfmt(ci," %I %I %U %s", + (long long) node->ping_sent, + (long long) node->pong_received, + nodeEpoch, + (node->link || node->flags & CLUSTER_NODE_MYSELF) ? + "connected" : "disconnected"); + + /* Slots served by this instance. If we already have slots info, + * append it directly, otherwise, generate slots only if it has. */ + if (node->slot_info_pairs) { + ci = representSlotInfo(ci, node->slot_info_pairs, node->slot_info_pairs_count); + } else if (node->numslots > 0) { + start = -1; + for (j = 0; j < CLUSTER_SLOTS; j++) { + int bit; + + if ((bit = clusterNodeCoversSlot(node, j)) != 0) { + if (start == -1) start = j; + } + if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) { + if (bit && j == CLUSTER_SLOTS-1) j++; + + if (start == j-1) { + ci = sdscatfmt(ci," %i",start); + } else { + ci = sdscatfmt(ci," %i-%i",start,j-1); + } + start = -1; + } + } + } + + /* Just for MYSELF node we also dump info about slots that + * we are migrating to other instances or importing from other + * instances. */ + if (node->flags & CLUSTER_NODE_MYSELF) { + for (j = 0; j < CLUSTER_SLOTS; j++) { + if (server.cluster->migrating_slots_to[j]) { + ci = sdscatprintf(ci," [%d->-%.40s]",j, + server.cluster->migrating_slots_to[j]->name); + } else if (server.cluster->importing_slots_from[j]) { + ci = sdscatprintf(ci," [%d-<-%.40s]",j, + server.cluster->importing_slots_from[j]->name); + } + } + } + return ci; +} + +/* Generate the slot topology for all nodes and store the string representation + * in the slots_info struct on the node. This is used to improve the efficiency + * of clusterGenNodesDescription() because it removes looping of the slot space + * for generating the slot info for each node individually. */ +void clusterGenNodesSlotsInfo(int filter) { + clusterNode *n = NULL; + int start = -1; + + for (int i = 0; i <= CLUSTER_SLOTS; i++) { + /* Find start node and slot id. */ + if (n == NULL) { + if (i == CLUSTER_SLOTS) break; + n = server.cluster->slots[i]; + start = i; + continue; + } + + /* Generate slots info when occur different node with start + * or end of slot. */ + if (i == CLUSTER_SLOTS || n != server.cluster->slots[i]) { + if (!(n->flags & filter)) { + if (!n->slot_info_pairs) { + n->slot_info_pairs = zmalloc(2 * n->numslots * sizeof(uint16_t)); + } + serverAssert((n->slot_info_pairs_count + 1) < (2 * n->numslots)); + n->slot_info_pairs[n->slot_info_pairs_count++] = start; + n->slot_info_pairs[n->slot_info_pairs_count++] = i-1; + } + if (i == CLUSTER_SLOTS) break; + n = server.cluster->slots[i]; + start = i; + } + } +} + +void clusterFreeNodesSlotsInfo(clusterNode *n) { + zfree(n->slot_info_pairs); + n->slot_info_pairs = NULL; + n->slot_info_pairs_count = 0; +} + +/* Generate a csv-alike representation of the nodes we are aware of, + * including the "myself" node, and return an SDS string containing the + * representation (it is up to the caller to free it). + * + * All the nodes matching at least one of the node flags specified in + * "filter" are excluded from the output, so using zero as a filter will + * include all the known nodes in the representation, including nodes in + * the HANDSHAKE state. + * + * Setting tls_primary to 1 to put TLS port in the main : + * field and put TCP port in aux field, instead of the opposite way. + * + * The representation obtained using this function is used for the output + * of the CLUSTER NODES function, and as format for the cluster + * configuration file (nodes.conf) for a given node. */ +sds clusterGenNodesDescription(client *c, int filter, int tls_primary) { + sds ci = sdsempty(), ni; + dictIterator *di; + dictEntry *de; + + /* Generate all nodes slots info firstly. */ + clusterGenNodesSlotsInfo(filter); + + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + + if (node->flags & filter) continue; + ni = clusterGenNodeDescription(c, node, tls_primary); + ci = sdscatsds(ci,ni); + sdsfree(ni); + ci = sdscatlen(ci,"\n",1); + + /* Release slots info. */ + clusterFreeNodesSlotsInfo(node); + } + dictReleaseIterator(di); + return ci; +} + +/* Add to the output buffer of the given client the description of the given cluster link. + * The description is a map with each entry being an attribute of the link. */ +void addReplyClusterLinkDescription(client *c, clusterLink *link) { + addReplyMapLen(c, 6); + + addReplyBulkCString(c, "direction"); + addReplyBulkCString(c, link->inbound ? "from" : "to"); + + /* addReplyClusterLinkDescription is only called for links that have been + * associated with nodes. The association is always bi-directional, so + * in addReplyClusterLinkDescription, link->node should never be NULL. */ + serverAssert(link->node); + sds node_name = sdsnewlen(link->node->name, CLUSTER_NAMELEN); + addReplyBulkCString(c, "node"); + addReplyBulkCString(c, node_name); + sdsfree(node_name); + + addReplyBulkCString(c, "create-time"); + addReplyLongLong(c, link->ctime); + + char events[3], *p; + p = events; + if (link->conn) { + if (connHasReadHandler(link->conn)) *p++ = 'r'; + if (connHasWriteHandler(link->conn)) *p++ = 'w'; + } + *p = '\0'; + addReplyBulkCString(c, "events"); + addReplyBulkCString(c, events); + + addReplyBulkCString(c, "send-buffer-allocated"); + addReplyLongLong(c, link->send_msg_queue_mem); + + addReplyBulkCString(c, "send-buffer-used"); + addReplyLongLong(c, link->send_msg_queue_mem); +} + +/* Add to the output buffer of the given client an array of cluster link descriptions, + * with array entry being a description of a single current cluster link. */ +void addReplyClusterLinksDescription(client *c) { + dictIterator *di; + dictEntry *de; + void *arraylen_ptr = NULL; + int num_links = 0; + + arraylen_ptr = addReplyDeferredLen(c); + + di = dictGetSafeIterator(server.cluster->nodes); + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + if (node->link) { + num_links++; + addReplyClusterLinkDescription(c, node->link); + } + if (node->inbound_link) { + num_links++; + addReplyClusterLinkDescription(c, node->inbound_link); + } + } + dictReleaseIterator(di); + + setDeferredArrayLen(c, arraylen_ptr, num_links); +} + +/* ----------------------------------------------------------------------------- + * CLUSTER command + * -------------------------------------------------------------------------- */ + +const char *clusterGetMessageTypeString(int type) { + switch(type) { + case CLUSTERMSG_TYPE_PING: return "ping"; + case CLUSTERMSG_TYPE_PONG: return "pong"; + case CLUSTERMSG_TYPE_MEET: return "meet"; + case CLUSTERMSG_TYPE_FAIL: return "fail"; + case CLUSTERMSG_TYPE_PUBLISH: return "publish"; + case CLUSTERMSG_TYPE_PUBLISHSHARD: return "publishshard"; + case CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST: return "auth-req"; + case CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK: return "auth-ack"; + case CLUSTERMSG_TYPE_UPDATE: return "update"; + case CLUSTERMSG_TYPE_MFSTART: return "mfstart"; + case CLUSTERMSG_TYPE_MODULE: return "module"; + } + return "unknown"; +} + +int getSlotOrReply(client *c, robj *o) { + long long slot; + + if (getLongLongFromObject(o,&slot) != C_OK || + slot < 0 || slot >= CLUSTER_SLOTS) + { + addReplyError(c,"Invalid or out of range slot"); + return -1; + } + return (int) slot; +} + +int checkSlotAssignmentsOrReply(client *c, unsigned char *slots, int del, int start_slot, int end_slot) { + int slot; + for (slot = start_slot; slot <= end_slot; slot++) { + if (del && server.cluster->slots[slot] == NULL) { + addReplyErrorFormat(c,"Slot %d is already unassigned", slot); + return C_ERR; + } else if (!del && server.cluster->slots[slot]) { + addReplyErrorFormat(c,"Slot %d is already busy", slot); + return C_ERR; + } + if (slots[slot]++ == 1) { + addReplyErrorFormat(c,"Slot %d specified multiple times",(int)slot); + return C_ERR; + } + } + return C_OK; +} + +void clusterUpdateSlots(client *c, unsigned char *slots, int del) { + int j; + for (j = 0; j < CLUSTER_SLOTS; j++) { + if (slots[j]) { + int retval; + + /* If this slot was set as importing we can clear this + * state as now we are the real owner of the slot. */ + if (server.cluster->importing_slots_from[j]) + server.cluster->importing_slots_from[j] = NULL; + + retval = del ? clusterDelSlot(j) : + clusterAddSlot(myself,j); + serverAssertWithInfo(c,NULL,retval == C_OK); + } + } +} + +/* Add detailed information of a node to the output buffer of the given client. */ +void addNodeDetailsToShardReply(client *c, clusterNode *node) { + int reply_count = 0; + void *node_replylen = addReplyDeferredLen(c); + addReplyBulkCString(c, "id"); + addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN); + reply_count++; + + if (node->tcp_port) { + addReplyBulkCString(c, "port"); + addReplyLongLong(c, node->tcp_port); + reply_count++; + } + + if (node->tls_port) { + addReplyBulkCString(c, "tls-port"); + addReplyLongLong(c, node->tls_port); + reply_count++; + } + + addReplyBulkCString(c, "ip"); + addReplyBulkCString(c, node->ip); + reply_count++; + + addReplyBulkCString(c, "endpoint"); + addReplyBulkCString(c, clusterNodePreferredEndpoint(node)); + reply_count++; + + if (sdslen(node->hostname) != 0) { + addReplyBulkCString(c, "hostname"); + addReplyBulkCBuffer(c, node->hostname, sdslen(node->hostname)); + reply_count++; + } + + long long node_offset; + if (node->flags & CLUSTER_NODE_MYSELF) { + node_offset = nodeIsSlave(node) ? replicationGetSlaveOffset() : server.master_repl_offset; + } else { + node_offset = node->repl_offset; + } + + addReplyBulkCString(c, "role"); + addReplyBulkCString(c, nodeIsSlave(node) ? "replica" : "master"); + reply_count++; + + addReplyBulkCString(c, "replication-offset"); + addReplyLongLong(c, node_offset); + reply_count++; + + addReplyBulkCString(c, "health"); + const char *health_msg = NULL; + if (nodeFailed(node)) { + health_msg = "fail"; + } else if (nodeIsSlave(node) && node_offset == 0) { + health_msg = "loading"; + } else { + health_msg = "online"; + } + addReplyBulkCString(c, health_msg); + reply_count++; + + setDeferredMapLen(c, node_replylen, reply_count); +} + +/* Add the shard reply of a single shard based off the given primary node. */ +void addShardReplyForClusterShards(client *c, list *nodes) { + serverAssert(listLength(nodes) > 0); + clusterNode *n = listNodeValue(listFirst(nodes)); + addReplyMapLen(c, 2); + addReplyBulkCString(c, "slots"); + + /* Use slot_info_pairs from the primary only */ + n = clusterNodeGetMaster(n); + + if (n->slot_info_pairs != NULL) { + serverAssert((n->slot_info_pairs_count % 2) == 0); + addReplyArrayLen(c, n->slot_info_pairs_count); + for (int i = 0; i < n->slot_info_pairs_count; i++) + addReplyLongLong(c, (unsigned long)n->slot_info_pairs[i]); + } else { + /* If no slot info pair is provided, the node owns no slots */ + addReplyArrayLen(c, 0); + } + + addReplyBulkCString(c, "nodes"); + addReplyArrayLen(c, listLength(nodes)); + listIter li; + listRewind(nodes, &li); + for (listNode *ln = listNext(&li); ln != NULL; ln = listNext(&li)) { + clusterNode *n = listNodeValue(ln); + addNodeDetailsToShardReply(c, n); + clusterFreeNodesSlotsInfo(n); + } +} + +/* Add to the output buffer of the given client, an array of slot (start, end) + * pair owned by the shard, also the primary and set of replica(s) along with + * information about each node. */ +void clusterCommandShards(client *c) { + addReplyArrayLen(c, dictSize(server.cluster->shards)); + /* This call will add slot_info_pairs to all nodes */ + clusterGenNodesSlotsInfo(0); + dictIterator *di = dictGetSafeIterator(server.cluster->shards); + for(dictEntry *de = dictNext(di); de != NULL; de = dictNext(di)) { + addShardReplyForClusterShards(c, dictGetVal(de)); + } + dictReleaseIterator(di); +} + +sds genClusterInfoString(void) { + sds info = sdsempty(); + char *statestr[] = {"ok","fail"}; + int slots_assigned = 0, slots_ok = 0, slots_pfail = 0, slots_fail = 0; + uint64_t myepoch; + int j; + + for (j = 0; j < CLUSTER_SLOTS; j++) { + clusterNode *n = server.cluster->slots[j]; + + if (n == NULL) continue; + slots_assigned++; + if (nodeFailed(n)) { + slots_fail++; + } else if (nodeTimedOut(n)) { + slots_pfail++; + } else { + slots_ok++; + } + } + + myepoch = (nodeIsSlave(myself) && myself->slaveof) ? + myself->slaveof->configEpoch : myself->configEpoch; + + info = sdscatprintf(info, + "cluster_state:%s\r\n" + "cluster_slots_assigned:%d\r\n" + "cluster_slots_ok:%d\r\n" + "cluster_slots_pfail:%d\r\n" + "cluster_slots_fail:%d\r\n" + "cluster_known_nodes:%lu\r\n" + "cluster_size:%d\r\n" + "cluster_current_epoch:%llu\r\n" + "cluster_my_epoch:%llu\r\n" + , statestr[server.cluster->state], + slots_assigned, + slots_ok, + slots_pfail, + slots_fail, + dictSize(server.cluster->nodes), + server.cluster->size, + (unsigned long long) server.cluster->currentEpoch, + (unsigned long long) myepoch + ); + + /* Show stats about messages sent and received. */ + long long tot_msg_sent = 0; + long long tot_msg_received = 0; + + for (int i = 0; i < CLUSTERMSG_TYPE_COUNT; i++) { + if (server.cluster->stats_bus_messages_sent[i] == 0) continue; + tot_msg_sent += server.cluster->stats_bus_messages_sent[i]; + info = sdscatprintf(info, + "cluster_stats_messages_%s_sent:%lld\r\n", + clusterGetMessageTypeString(i), + server.cluster->stats_bus_messages_sent[i]); + } + info = sdscatprintf(info, + "cluster_stats_messages_sent:%lld\r\n", tot_msg_sent); + + for (int i = 0; i < CLUSTERMSG_TYPE_COUNT; i++) { + if (server.cluster->stats_bus_messages_received[i] == 0) continue; + tot_msg_received += server.cluster->stats_bus_messages_received[i]; + info = sdscatprintf(info, + "cluster_stats_messages_%s_received:%lld\r\n", + clusterGetMessageTypeString(i), + server.cluster->stats_bus_messages_received[i]); + } + info = sdscatprintf(info, + "cluster_stats_messages_received:%lld\r\n", tot_msg_received); + + info = sdscatprintf(info, + "total_cluster_links_buffer_limit_exceeded:%llu\r\n", + server.cluster->stat_cluster_links_buffer_limit_exceeded); + + return info; +} + + +void removeChannelsInSlot(unsigned int slot) { + if (countChannelsInSlot(slot) == 0) return; + + pubsubShardUnsubscribeAllChannelsInSlot(slot); +} + + + +/* Remove all the keys in the specified hash slot. + * The number of removed items is returned. */ +unsigned int delKeysInSlot(unsigned int hashslot) { + unsigned int j = 0; + + dictIterator *iter = NULL; + dictEntry *de = NULL; + iter = dictGetSafeIterator(server.db->dict[hashslot]); + while((de = dictNext(iter)) != NULL) { + enterExecutionUnit(1, 0); + sds sdskey = dictGetKey(de); + robj *key = createStringObject(sdskey, sdslen(sdskey)); + dbDelete(&server.db[0], key); + propagateDeletion(&server.db[0], key, server.lazyfree_lazy_server_del); + signalModifiedKey(NULL, &server.db[0], key); + /* The keys are not actually logically deleted from the database, just moved to another node. + * The modules needs to know that these keys are no longer available locally, so just send the + * keyspace notification to the modules, but not to clients. */ + moduleNotifyKeyspaceEvent(NOTIFY_GENERIC, "del", key, server.db[0].id); + exitExecutionUnit(); + postExecutionUnitOperations(); + decrRefCount(key); + j++; + server.dirty++; + } + dictReleaseIterator(iter); + + return j; +} + +/* Get the count of the channels for a given slot. */ +unsigned int countChannelsInSlot(unsigned int hashslot) { + dict *d = server.pubsubshard_channels[hashslot]; + return d ? dictSize(d) : 0; +} + +int clusterNodeIsMyself(clusterNode *n) { + return n == server.cluster->myself; +} + +clusterNode *getMyClusterNode(void) { + return server.cluster->myself; +} + +int clusterManualFailoverTimeLimit(void) { + return server.cluster->mf_end; +} + +int getClusterSize(void) { + return dictSize(server.cluster->nodes); +} + +int getMyShardSlotCount(void) { + if (!nodeIsSlave(server.cluster->myself)) { + return server.cluster->myself->numslots; + } else if (server.cluster->myself->slaveof) { + return server.cluster->myself->slaveof->numslots; + } else { + return 0; + } +} + +char **getClusterNodesList(size_t *numnodes) { + size_t count = dictSize(server.cluster->nodes); + char **ids = zmalloc((count+1)*CLUSTER_NAMELEN); + dictIterator *di = dictGetIterator(server.cluster->nodes); + dictEntry *de; + int j = 0; + while((de = dictNext(di)) != NULL) { + clusterNode *node = dictGetVal(de); + if (node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE)) continue; + ids[j] = zmalloc(CLUSTER_NAMELEN); + memcpy(ids[j],node->name,CLUSTER_NAMELEN); + j++; + } + *numnodes = j; + ids[j] = NULL; /* Null term so that FreeClusterNodesList does not need + * to also get the count argument. */ + dictReleaseIterator(di); + return ids; +} + +int clusterNodeIsMaster(clusterNode *n) { + return n->flags & CLUSTER_NODE_MASTER; +} + +int handleDebugClusterCommand(client *c) { + if (strcasecmp(c->argv[1]->ptr, "CLUSTERLINK") || + strcasecmp(c->argv[2]->ptr, "KILL") || + c->argc != 5) { + return 0; + } + + if (!server.cluster_enabled) { + addReplyError(c, "Debug option only available for cluster mode enabled setup!"); + return 1; + } + + /* Find the node. */ + clusterNode *n = clusterLookupNode(c->argv[4]->ptr, sdslen(c->argv[4]->ptr)); + if (!n) { + addReplyErrorFormat(c, "Unknown node %s", (char *) c->argv[4]->ptr); + return 1; + } + + /* Terminate the link based on the direction or all. */ + if (!strcasecmp(c->argv[3]->ptr, "from")) { + freeClusterLink(n->inbound_link); + } else if (!strcasecmp(c->argv[3]->ptr, "to")) { + freeClusterLink(n->link); + } else if (!strcasecmp(c->argv[3]->ptr, "all")) { + freeClusterLink(n->link); + freeClusterLink(n->inbound_link); + } else { + addReplyErrorFormat(c, "Unknown direction %s", (char *) c->argv[3]->ptr); + } + addReply(c, shared.ok); + + return 1; +} + +int clusterNodePending(clusterNode *node) { + return node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE); +} + +char *clusterNodeIp(clusterNode *node) { + return node->ip; +} + +int clusterNodeIsSlave(clusterNode *node) { + return node->flags & CLUSTER_NODE_SLAVE; +} + +clusterNode *clusterNodeGetSlaveof(clusterNode *node) { + return node->slaveof; +} + +clusterNode *clusterNodeGetMaster(clusterNode *node) { + while (node->slaveof != NULL) node = node->slaveof; + return node; +} + +char *clusterNodeGetName(clusterNode *node) { + return node->name; +} + +int clusterNodeTimedOut(clusterNode *node) { + return nodeTimedOut(node); +} + +int clusterNodeIsFailing(clusterNode *node) { + return nodeFailed(node); +} + +int clusterNodeIsNoFailover(clusterNode *node) { + return node->flags & CLUSTER_NODE_NOFAILOVER; +} + +const char **clusterDebugCommandExtendedHelp(void) { + static const char *help[] = { + "CLUSTERLINK KILL ", + " Kills the link based on the direction to/from (both) with the provided node.", + NULL + }; + + return help; +} + +char *clusterNodeGetShardId(clusterNode *node) { + return node->shard_id; +} + +int clusterCommandSpecial(client *c) { + if (!strcasecmp(c->argv[1]->ptr,"meet") && (c->argc == 4 || c->argc == 5)) { + /* CLUSTER MEET [cport] */ + long long port, cport; + + if (getLongLongFromObject(c->argv[3], &port) != C_OK) { + addReplyErrorFormat(c,"Invalid base port specified: %s", + (char*)c->argv[3]->ptr); + return 1; + } + + if (c->argc == 5) { + if (getLongLongFromObject(c->argv[4], &cport) != C_OK) { + addReplyErrorFormat(c,"Invalid bus port specified: %s", + (char*)c->argv[4]->ptr); + return 1; + } + } else { + cport = port + CLUSTER_PORT_INCR; + } + + if (clusterStartHandshake(c->argv[2]->ptr,port,cport) == 0 && + errno == EINVAL) + { + addReplyErrorFormat(c,"Invalid node address specified: %s:%s", + (char*)c->argv[2]->ptr, (char*)c->argv[3]->ptr); + } else { + addReply(c,shared.ok); + } + } else if (!strcasecmp(c->argv[1]->ptr,"flushslots") && c->argc == 2) { + /* CLUSTER FLUSHSLOTS */ + if (dbSize(&server.db[0], DB_MAIN) != 0) { + addReplyError(c,"DB must be empty to perform CLUSTER FLUSHSLOTS."); + return 1; + } + clusterDelNodeSlots(myself); + clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); + addReply(c,shared.ok); + } else if ((!strcasecmp(c->argv[1]->ptr,"addslots") || + !strcasecmp(c->argv[1]->ptr,"delslots")) && c->argc >= 3) { + /* CLUSTER ADDSLOTS [slot] ... */ + /* CLUSTER DELSLOTS [slot] ... */ + int j, slot; + unsigned char *slots = zmalloc(CLUSTER_SLOTS); + int del = !strcasecmp(c->argv[1]->ptr,"delslots"); + + memset(slots,0,CLUSTER_SLOTS); + /* Check that all the arguments are parseable.*/ + for (j = 2; j < c->argc; j++) { + if ((slot = getSlotOrReply(c,c->argv[j])) == C_ERR) { + zfree(slots); + return 1; + } + } + /* Check that the slots are not already busy. */ + for (j = 2; j < c->argc; j++) { + slot = getSlotOrReply(c,c->argv[j]); + if (checkSlotAssignmentsOrReply(c, slots, del, slot, slot) == C_ERR) { + zfree(slots); + return 1; + } + } + clusterUpdateSlots(c, slots, del); + zfree(slots); + clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); + addReply(c,shared.ok); + } else if ((!strcasecmp(c->argv[1]->ptr,"addslotsrange") || + !strcasecmp(c->argv[1]->ptr,"delslotsrange")) && c->argc >= 4) { + if (c->argc % 2 == 1) { + addReplyErrorArity(c); + return 1; + } + /* CLUSTER ADDSLOTSRANGE [ ...] */ + /* CLUSTER DELSLOTSRANGE [ ...] */ + int j, startslot, endslot; + unsigned char *slots = zmalloc(CLUSTER_SLOTS); + int del = !strcasecmp(c->argv[1]->ptr,"delslotsrange"); + + memset(slots,0,CLUSTER_SLOTS); + /* Check that all the arguments are parseable and that all the + * slots are not already busy. */ + for (j = 2; j < c->argc; j += 2) { + if ((startslot = getSlotOrReply(c,c->argv[j])) == C_ERR) { + zfree(slots); + return 1; + } + if ((endslot = getSlotOrReply(c,c->argv[j+1])) == C_ERR) { + zfree(slots); + return 1; + } + if (startslot > endslot) { + addReplyErrorFormat(c,"start slot number %d is greater than end slot number %d", startslot, endslot); + zfree(slots); + return 1; + } + + if (checkSlotAssignmentsOrReply(c, slots, del, startslot, endslot) == C_ERR) { + zfree(slots); + return 1; + } + } + clusterUpdateSlots(c, slots, del); + zfree(slots); + clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); + addReply(c,shared.ok); + } else if (!strcasecmp(c->argv[1]->ptr,"setslot") && c->argc >= 4) { + /* SETSLOT 10 MIGRATING */ + /* SETSLOT 10 IMPORTING */ + /* SETSLOT 10 STABLE */ + /* SETSLOT 10 NODE */ + int slot; + clusterNode *n; + + if (nodeIsSlave(myself)) { + addReplyError(c,"Please use SETSLOT only with masters."); + return 1; + } + + if ((slot = getSlotOrReply(c, c->argv[2])) == -1) return 1; + + if (!strcasecmp(c->argv[3]->ptr,"migrating") && c->argc == 5) { + if (server.cluster->slots[slot] != myself) { + addReplyErrorFormat(c,"I'm not the owner of hash slot %u",slot); + return 1; + } + n = clusterLookupNode(c->argv[4]->ptr, sdslen(c->argv[4]->ptr)); + if (n == NULL) { + addReplyErrorFormat(c,"I don't know about node %s", + (char*)c->argv[4]->ptr); + return 1; + } + if (nodeIsSlave(n)) { + addReplyError(c,"Target node is not a master"); + return 1; + } + server.cluster->migrating_slots_to[slot] = n; + } else if (!strcasecmp(c->argv[3]->ptr,"importing") && c->argc == 5) { + if (server.cluster->slots[slot] == myself) { + addReplyErrorFormat(c, + "I'm already the owner of hash slot %u",slot); + return 1; + } + n = clusterLookupNode(c->argv[4]->ptr, sdslen(c->argv[4]->ptr)); + if (n == NULL) { + addReplyErrorFormat(c,"I don't know about node %s", + (char*)c->argv[4]->ptr); + return 1; + } + if (nodeIsSlave(n)) { + addReplyError(c,"Target node is not a master"); + return 1; + } + server.cluster->importing_slots_from[slot] = n; + } else if (!strcasecmp(c->argv[3]->ptr,"stable") && c->argc == 4) { + /* CLUSTER SETSLOT STABLE */ + server.cluster->importing_slots_from[slot] = NULL; + server.cluster->migrating_slots_to[slot] = NULL; + } else if (!strcasecmp(c->argv[3]->ptr,"node") && c->argc == 5) { + /* CLUSTER SETSLOT NODE */ + n = clusterLookupNode(c->argv[4]->ptr, sdslen(c->argv[4]->ptr)); + if (!n) { + addReplyErrorFormat(c,"Unknown node %s", + (char*)c->argv[4]->ptr); + return 1; + } + if (nodeIsSlave(n)) { + addReplyError(c,"Target node is not a master"); + return 1; + } + /* If this hash slot was served by 'myself' before to switch + * make sure there are no longer local keys for this hash slot. */ + if (server.cluster->slots[slot] == myself && n != myself) { + if (countKeysInSlot(slot) != 0) { + addReplyErrorFormat(c, + "Can't assign hashslot %d to a different node " + "while I still hold keys for this hash slot.", slot); + return 1; + } + } + /* If this slot is in migrating status but we have no keys + * for it assigning the slot to another node will clear + * the migrating status. */ + if (countKeysInSlot(slot) == 0 && + server.cluster->migrating_slots_to[slot]) + server.cluster->migrating_slots_to[slot] = NULL; + + int slot_was_mine = server.cluster->slots[slot] == myself; + clusterDelSlot(slot); + clusterAddSlot(n,slot); + + /* If we are a master left without slots, we should turn into a + * replica of the new master. */ + if (slot_was_mine && + n != myself && + myself->numslots == 0 && + server.cluster_allow_replica_migration) { + serverLog(LL_NOTICE, + "Configuration change detected. Reconfiguring myself " + "as a replica of %.40s (%s)", n->name, n->human_nodename); + clusterSetMaster(n); + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | + CLUSTER_TODO_UPDATE_STATE | + CLUSTER_TODO_FSYNC_CONFIG); + } + + /* If this node was importing this slot, assigning the slot to + * itself also clears the importing status. */ + if (n == myself && + server.cluster->importing_slots_from[slot]) { + /* This slot was manually migrated, set this node configEpoch + * to a new epoch so that the new version can be propagated + * by the cluster. + * + * Note that if this ever results in a collision with another + * node getting the same configEpoch, for example because a + * failover happens at the same time we close the slot, the + * configEpoch collision resolution will fix it assigning + * a different epoch to each node. */ + if (clusterBumpConfigEpochWithoutConsensus() == C_OK) { + serverLog(LL_NOTICE, + "configEpoch updated after importing slot %d", slot); + } + server.cluster->importing_slots_from[slot] = NULL; + /* After importing this slot, let the other nodes know as + * soon as possible. */ + clusterBroadcastPong(CLUSTER_BROADCAST_ALL); + } + } else { + addReplyError(c, + "Invalid CLUSTER SETSLOT action or number of arguments. Try CLUSTER HELP"); + return 1; + } + clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_UPDATE_STATE); + addReply(c,shared.ok); + } else if (!strcasecmp(c->argv[1]->ptr,"bumpepoch") && c->argc == 2) { + /* CLUSTER BUMPEPOCH */ + int retval = clusterBumpConfigEpochWithoutConsensus(); + sds reply = sdscatprintf(sdsempty(),"+%s %llu\r\n", + (retval == C_OK) ? "BUMPED" : "STILL", + (unsigned long long) myself->configEpoch); + addReplySds(c,reply); + } else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) { + int retval = clusterSaveConfig(1); + + if (retval == 0) + addReply(c,shared.ok); + else + addReplyErrorFormat(c,"error saving the cluster node config: %s", + strerror(errno)); + } else if (!strcasecmp(c->argv[1]->ptr,"forget") && c->argc == 3) { + /* CLUSTER FORGET */ + clusterNode *n = clusterLookupNode(c->argv[2]->ptr, sdslen(c->argv[2]->ptr)); + if (!n) { + if (clusterBlacklistExists((char*)c->argv[2]->ptr)) + /* Already forgotten. The deletion may have been gossipped by + * another node, so we pretend it succeeded. */ + addReply(c,shared.ok); + else + addReplyErrorFormat(c,"Unknown node %s", (char*)c->argv[2]->ptr); + return 1; + } else if (n == myself) { + addReplyError(c,"I tried hard but I can't forget myself..."); + return 1; + } else if (nodeIsSlave(myself) && myself->slaveof == n) { + addReplyError(c,"Can't forget my master!"); + return 1; + } + clusterBlacklistAddNode(n); + clusterDelNode(n); + clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE| + CLUSTER_TODO_SAVE_CONFIG); + addReply(c,shared.ok); + } else if (!strcasecmp(c->argv[1]->ptr,"replicate") && c->argc == 3) { + /* CLUSTER REPLICATE */ + /* Lookup the specified node in our table. */ + clusterNode *n = clusterLookupNode(c->argv[2]->ptr, sdslen(c->argv[2]->ptr)); + if (!n) { + addReplyErrorFormat(c,"Unknown node %s", (char*)c->argv[2]->ptr); + return 1; + } + + /* I can't replicate myself. */ + if (n == myself) { + addReplyError(c,"Can't replicate myself"); + return 1; + } + + /* Can't replicate a slave. */ + if (nodeIsSlave(n)) { + addReplyError(c,"I can only replicate a master, not a replica."); + return 1; + } + + /* If the instance is currently a master, it should have no assigned + * slots nor keys to accept to replicate some other node. + * Slaves can switch to another master without issues. */ + if (clusterNodeIsMaster(myself) && + (myself->numslots != 0 || dbSize(&server.db[0], DB_MAIN) != 0)) { + addReplyError(c, + "To set a master the node must be empty and " + "without assigned slots."); + return 1; + } + + /* Set the master. */ + clusterSetMaster(n); + clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); + addReply(c,shared.ok); + } else if (!strcasecmp(c->argv[1]->ptr,"count-failure-reports") && + c->argc == 3) + { + /* CLUSTER COUNT-FAILURE-REPORTS */ + clusterNode *n = clusterLookupNode(c->argv[2]->ptr, sdslen(c->argv[2]->ptr)); + + if (!n) { + addReplyErrorFormat(c,"Unknown node %s", (char*)c->argv[2]->ptr); + return 1; + } else { + addReplyLongLong(c,clusterNodeFailureReportsCount(n)); + } + } else if (!strcasecmp(c->argv[1]->ptr,"failover") && + (c->argc == 2 || c->argc == 3)) + { + /* CLUSTER FAILOVER [FORCE|TAKEOVER] */ + int force = 0, takeover = 0; + + if (c->argc == 3) { + if (!strcasecmp(c->argv[2]->ptr,"force")) { + force = 1; + } else if (!strcasecmp(c->argv[2]->ptr,"takeover")) { + takeover = 1; + force = 1; /* Takeover also implies force. */ + } else { + addReplyErrorObject(c,shared.syntaxerr); + return 1; + } + } + + /* Check preconditions. */ + if (clusterNodeIsMaster(myself)) { + addReplyError(c,"You should send CLUSTER FAILOVER to a replica"); + return 1; + } else if (myself->slaveof == NULL) { + addReplyError(c,"I'm a replica but my master is unknown to me"); + return 1; + } else if (!force && + (nodeFailed(myself->slaveof) || + myself->slaveof->link == NULL)) + { + addReplyError(c,"Master is down or failed, " + "please use CLUSTER FAILOVER FORCE"); + return 1; + } + resetManualFailover(); + server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT; + + if (takeover) { + /* A takeover does not perform any initial check. It just + * generates a new configuration epoch for this node without + * consensus, claims the master's slots, and broadcast the new + * configuration. */ + serverLog(LL_NOTICE,"Taking over the master (user request)."); + clusterBumpConfigEpochWithoutConsensus(); + clusterFailoverReplaceYourMaster(); + } else if (force) { + /* If this is a forced failover, we don't need to talk with our + * master to agree about the offset. We just failover taking over + * it without coordination. */ + serverLog(LL_NOTICE,"Forced failover user request accepted."); + server.cluster->mf_can_start = 1; + } else { + serverLog(LL_NOTICE,"Manual failover user request accepted."); + clusterSendMFStart(myself->slaveof); + } + addReply(c,shared.ok); + } else if (!strcasecmp(c->argv[1]->ptr,"set-config-epoch") && c->argc == 3) + { + /* CLUSTER SET-CONFIG-EPOCH + * + * The user is allowed to set the config epoch only when a node is + * totally fresh: no config epoch, no other known node, and so forth. + * This happens at cluster creation time to start with a cluster where + * every node has a different node ID, without to rely on the conflicts + * resolution system which is too slow when a big cluster is created. */ + long long epoch; + + if (getLongLongFromObjectOrReply(c,c->argv[2],&epoch,NULL) != C_OK) + return 1; + + if (epoch < 0) { + addReplyErrorFormat(c,"Invalid config epoch specified: %lld",epoch); + } else if (dictSize(server.cluster->nodes) > 1) { + addReplyError(c,"The user can assign a config epoch only when the " + "node does not know any other node."); + } else if (myself->configEpoch != 0) { + addReplyError(c,"Node config epoch is already non-zero"); + } else { + myself->configEpoch = epoch; + serverLog(LL_NOTICE, + "configEpoch set to %llu via CLUSTER SET-CONFIG-EPOCH", + (unsigned long long) myself->configEpoch); + + if (server.cluster->currentEpoch < (uint64_t)epoch) + server.cluster->currentEpoch = epoch; + /* No need to fsync the config here since in the unlucky event + * of a failure to persist the config, the conflict resolution code + * will assign a unique config to this node. */ + clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE| + CLUSTER_TODO_SAVE_CONFIG); + addReply(c,shared.ok); + } + } else if (!strcasecmp(c->argv[1]->ptr,"reset") && + (c->argc == 2 || c->argc == 3)) + { + /* CLUSTER RESET [SOFT|HARD] */ + int hard = 0; + + /* Parse soft/hard argument. Default is soft. */ + if (c->argc == 3) { + if (!strcasecmp(c->argv[2]->ptr,"hard")) { + hard = 1; + } else if (!strcasecmp(c->argv[2]->ptr,"soft")) { + hard = 0; + } else { + addReplyErrorObject(c,shared.syntaxerr); + return 1; + } + } + + /* Slaves can be reset while containing data, but not master nodes + * that must be empty. */ + if (clusterNodeIsMaster(myself) && dbSize(c->db, DB_MAIN) != 0) { + addReplyError(c,"CLUSTER RESET can't be called with " + "master nodes containing keys"); + return 1; + } + clusterReset(hard); + addReply(c,shared.ok); + } else if (!strcasecmp(c->argv[1]->ptr,"links") && c->argc == 2) { + /* CLUSTER LINKS */ + addReplyClusterLinksDescription(c); + } else { + return 0; + } + + return 1; +} + +const char **clusterCommandExtendedHelp(void) { + static const char *help[] = { + "ADDSLOTS [ ...]", + " Assign slots to current node.", + "ADDSLOTSRANGE [ ...]", + " Assign slots which are between and to current node.", + "BUMPEPOCH", + " Advance the cluster config epoch.", + "COUNT-FAILURE-REPORTS ", + " Return number of failure reports for .", + "DELSLOTS [ ...]", + " Delete slots information from current node.", + "DELSLOTSRANGE [ ...]", + " Delete slots information which are between and from current node.", + "FAILOVER [FORCE|TAKEOVER]", + " Promote current replica node to being a master.", + "FORGET ", + " Remove a node from the cluster.", + "FLUSHSLOTS", + " Delete current node own slots information.", + "MEET []", + " Connect nodes into a working cluster.", + "REPLICATE ", + " Configure current node as replica to .", + "RESET [HARD|SOFT]", + " Reset current node (default: soft).", + "SET-CONFIG-EPOCH ", + " Set config epoch of current node.", + "SETSLOT (IMPORTING |MIGRATING |STABLE|NODE )", + " Set slot state.", + "SAVECONFIG", + " Force saving cluster configuration on disk.", + "LINKS", + " Return information about all network links between this node and its peers.", + " Output format is an array where each array element is a map containing attributes of a link", + NULL + }; + + return help; +} + +int clusterNodeNumSlaves(clusterNode *node) { + return node->numslaves; +} + +clusterNode *clusterNodeGetSlave(clusterNode *node, int slave_idx) { + return node->slaves[slave_idx]; +} + +clusterNode *getMigratingSlotDest(int slot) { + return server.cluster->migrating_slots_to[slot]; +} + +clusterNode *getImportingSlotSource(int slot) { + return server.cluster->importing_slots_from[slot]; +} + +int isClusterHealthy(void) { + return server.cluster->state == CLUSTER_OK; +} + +clusterNode *getNodeBySlot(int slot) { + return server.cluster->slots[slot]; +} + +char *clusterNodeHostname(clusterNode *node) { + return node->hostname; +} + +long long clusterNodeReplOffset(clusterNode *node) { + return node->repl_offset; +} + +const char *clusterNodePreferredEndpoint(clusterNode *n) { + char *hostname = clusterNodeHostname(n); + switch (server.cluster_preferred_endpoint_type) { + case CLUSTER_ENDPOINT_TYPE_IP: + return clusterNodeIp(n); + case CLUSTER_ENDPOINT_TYPE_HOSTNAME: + return (hostname != NULL && hostname[0] != '\0') ? hostname : "?"; + case CLUSTER_ENDPOINT_TYPE_UNKNOWN_ENDPOINT: + return ""; + } + return "unknown"; +} + +int clusterAllowFailoverCmd(client *c) { + if (!server.cluster_enabled) { + return 1; + } + addReplyError(c,"FAILOVER not allowed in cluster mode. " + "Use CLUSTER FAILOVER command instead."); + return 0; +} + +void clusterPromoteSelfToMaster(void) { + replicationUnsetMaster(); +} diff --git a/tests/support/cluster_util.tcl b/tests/support/cluster_util.tcl index 2e3611e1ee..5160466474 100644 --- a/tests/support/cluster_util.tcl +++ b/tests/support/cluster_util.tcl @@ -199,3 +199,30 @@ proc are_hostnames_propagated {match_string} { } return 1 } + +proc wait_node_marked_fail {ref_node_index instance_id_to_check} { + wait_for_condition 1000 50 { + [check_cluster_node_mark fail $ref_node_index $instance_id_to_check] + } else { + fail "Replica node never marked as FAIL ('fail')" + } +} + +proc wait_node_marked_pfail {ref_node_index instance_id_to_check} { + wait_for_condition 1000 50 { + [check_cluster_node_mark fail\? $ref_node_index $instance_id_to_check] + } else { + fail "Replica node never marked as PFAIL ('fail?')" + } +} + +proc check_cluster_node_mark {flag ref_node_index instance_id_to_check} { + set nodes [get_cluster_nodes $ref_node_index] + + foreach n $nodes { + if {[dict get $n id] eq $instance_id_to_check} { + return [cluster_has_flag $n $flag] + } + } + fail "Unable to find instance id in cluster nodes. ID: $instance_id_to_check" +} diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl index 59cb465c15..1f88b56567 100644 --- a/tests/test_helper.tcl +++ b/tests/test_helper.tcl @@ -103,6 +103,7 @@ set ::all_tests { unit/cluster/slot-ownership unit/cluster/links unit/cluster/cluster-response-tls + unit/cluster/failure-marking } # Index to the next test to run in the ::all_tests list. set ::next_test 0 diff --git a/tests/unit/cluster/failure-marking.tcl b/tests/unit/cluster/failure-marking.tcl new file mode 100644 index 0000000000..c4746c8264 --- /dev/null +++ b/tests/unit/cluster/failure-marking.tcl @@ -0,0 +1,53 @@ +# Test a single primary can mark replica as `fail` +start_cluster 1 1 {tags {external:skip cluster}} { + + test "Verify that single primary marks replica as failed" { + set primary [srv -0 client] + + set replica1 [srv -1 client] + set replica1_pid [srv -1 pid] + set replica1_instance_id [dict get [cluster_get_myself 1] id] + + assert {[lindex [$primary role] 0] eq {master}} + assert {[lindex [$replica1 role] 0] eq {slave}} + + wait_for_sync $replica1 + + pause_process $replica1_pid + + wait_node_marked_fail 0 $replica1_instance_id + } +} + +# Test multiple primaries wait for a quorum and then mark a replica as `fail` +start_cluster 2 1 {tags {external:skip cluster}} { + + test "Verify that multiple primaries mark replica as failed" { + set primary1 [srv -0 client] + + set primary2 [srv -1 client] + set primary2_pid [srv -1 pid] + + set replica1 [srv -2 client] + set replica1_pid [srv -2 pid] + set replica1_instance_id [dict get [cluster_get_myself 2] id] + + assert {[lindex [$primary1 role] 0] eq {master}} + assert {[lindex [$primary2 role] 0] eq {master}} + assert {[lindex [$replica1 role] 0] eq {slave}} + + wait_for_sync $replica1 + + pause_process $replica1_pid + + # Pause other primary to allow time for pfail flag to appear + pause_process $primary2_pid + + wait_node_marked_pfail 0 $replica1_instance_id + + # Resume other primary and wait for to show replica as failed + resume_process $primary2_pid + + wait_node_marked_fail 0 $replica1_instance_id + } +} From 9fdf32c222e1666b7aa88ef1c353b42ec1e6a358 Mon Sep 17 00:00:00 2001 From: "debing.sun" Date: Wed, 7 Feb 2024 00:21:28 +0800 Subject: [PATCH 2/7] Prevent LSET command from causing quicklist plain node size to exceed 4GB (#12955) Fix #12864 The main reason for this crash is that when replacing a element of a quicklist packed node with lpReplace() method, if the final size is larger than 4GB, lpReplace() will fail and returns NULL, causing `node->entry` to be incorrectly set to NULL. Since the inserted data is not a large element, we can't just replace it like a large element, first quicklistInsertAfter() and then quicklistDelIndex(), because the current node may be merged and invalidated in quicklistInsertAfter(). The solution of this PR: When replacing a node fails (listpack exceeds 4GB), split the current node, create a new node to put in the middle, and try to merge them. This is the same as inserting a large element. In the worst case, its size will not exceed 4GB. Signed-off-by: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com> --- src/quicklist.c | 54 ++++++++++++++++++++++++++++++---------- tests/unit/type/list.tcl | 13 ++++++++++ 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/quicklist.c b/src/quicklist.c index 301a2166ee..f8cb62e236 100644 --- a/src/quicklist.c +++ b/src/quicklist.c @@ -104,6 +104,9 @@ quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name) quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node); void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm); +quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset, int after); +void _quicklistMergeNodes(quicklist *quicklist, quicklistNode *center); + /* Simple way to give quicklistEntry structs default values with one call. */ #define initEntry(e) \ do { \ @@ -529,19 +532,25 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a, (node)->sz = lpBytes((node)->entry); \ } while (0) -static quicklistNode* __quicklistCreatePlainNode(void *value, size_t sz) { +static quicklistNode* __quicklistCreateNode(int container, void *value, size_t sz) { quicklistNode *new_node = quicklistCreateNode(); - new_node->entry = zmalloc(sz); - new_node->container = QUICKLIST_NODE_CONTAINER_PLAIN; - memcpy(new_node->entry, value, sz); + new_node->container = container; + if (container == QUICKLIST_NODE_CONTAINER_PLAIN) { + new_node->entry = zmalloc(sz); + memcpy(new_node->entry, value, sz); + } else { + new_node->entry = lpPrepend(lpNew(0), value, sz); + } new_node->sz = sz; new_node->count++; return new_node; } static void __quicklistInsertPlainNode(quicklist *quicklist, quicklistNode *old_node, - void *value, size_t sz, int after) { - __quicklistInsertNode(quicklist, old_node, __quicklistCreatePlainNode(value, sz), after); + void *value, size_t sz, int after) +{ + quicklistNode *new_node = __quicklistCreateNode(QUICKLIST_NODE_CONTAINER_PLAIN, value, sz); + __quicklistInsertNode(quicklist, old_node, new_node, after); quicklist->count++; } @@ -741,9 +750,13 @@ void quicklistReplaceEntry(quicklistIter *iter, quicklistEntry *entry, void *data, size_t sz) { quicklist* quicklist = iter->quicklist; + quicklistNode *node = entry->node; + unsigned char *newentry; - if (likely(!QL_NODE_IS_PLAIN(entry->node) && !isLargeElement(sz))) { - entry->node->entry = lpReplace(entry->node->entry, &entry->zi, data, sz); + if (likely(!QL_NODE_IS_PLAIN(entry->node) && !isLargeElement(sz) && + (newentry = lpReplace(entry->node->entry, &entry->zi, data, sz)) != NULL)) + { + entry->node->entry = newentry; quicklistNodeUpdateSz(entry->node); /* quicklistNext() and quicklistGetIteratorEntryAtIdx() provide an uncompressed node */ quicklistCompress(quicklist, entry->node); @@ -758,15 +771,30 @@ void quicklistReplaceEntry(quicklistIter *iter, quicklistEntry *entry, quicklistInsertAfter(iter, entry, data, sz); __quicklistDelNode(quicklist, entry->node); } - } else { - entry->node->dont_compress = 1; /* Prevent compression in quicklistInsertAfter() */ - quicklistInsertAfter(iter, entry, data, sz); + } else { /* The node is full or data is a large element */ + quicklistNode *split_node = NULL, *new_node; + node->dont_compress = 1; /* Prevent compression in __quicklistInsertNode() */ + + /* If the entry is not at the tail, split the node at the entry's offset. */ + if (entry->offset != node->count - 1 && entry->offset != -1) + split_node = _quicklistSplitNode(node, entry->offset, 1); + + /* Create a new node and insert it after the original node. + * If the original node was split, insert the split node after the new node. */ + new_node = __quicklistCreateNode(isLargeElement(sz) ? + QUICKLIST_NODE_CONTAINER_PLAIN : QUICKLIST_NODE_CONTAINER_PACKED, data, sz); + __quicklistInsertNode(quicklist, node, new_node, 1); + if (split_node) __quicklistInsertNode(quicklist, new_node, split_node, 1); + quicklist->count++; + + /* Delete the replaced element. */ if (entry->node->count == 1) { __quicklistDelNode(quicklist, entry->node); } else { unsigned char *p = lpSeek(entry->node->entry, -1); quicklistDelIndex(quicklist, entry->node, &p); entry->node->dont_compress = 0; /* Re-enable compression */ + _quicklistMergeNodes(quicklist, entry->node); quicklistCompress(quicklist, entry->node); quicklistCompress(quicklist, entry->node->next); } @@ -1002,7 +1030,7 @@ REDIS_STATIC void _quicklistInsert(quicklistIter *iter, quicklistEntry *entry, } else { quicklistDecompressNodeForUse(node); new_node = _quicklistSplitNode(node, entry->offset, after); - quicklistNode *entry_node = __quicklistCreatePlainNode(value, sz); + quicklistNode *entry_node = __quicklistCreateNode(QUICKLIST_NODE_CONTAINER_PLAIN, value, sz); __quicklistInsertNode(quicklist, node, entry_node, after); __quicklistInsertNode(quicklist, entry_node, new_node, after); quicklist->count++; @@ -3224,7 +3252,7 @@ int quicklistTest(int argc, char *argv[], int flags) { memcpy(s, "helloworld", 10); memcpy(s + sz - 10, "1234567890", 10); - quicklistNode *node = __quicklistCreatePlainNode(s, sz); + quicklistNode *node = __quicklistCreateNode(QUICKLIST_NODE_CONTAINER_PLAIN, s, sz); /* Just to avoid triggering the assertion in __quicklistCompressNode(), * it disables the passing of quicklist head or tail node. */ diff --git a/tests/unit/type/list.tcl b/tests/unit/type/list.tcl index 993b6d1352..e47e3bdafb 100644 --- a/tests/unit/type/list.tcl +++ b/tests/unit/type/list.tcl @@ -220,6 +220,7 @@ start_server [list overrides [list save ""] ] { # checking LSET in case ziplist needs to be split test {Test LSET with packed is split in the middle} { + set original_config [config_get_set list-max-listpack-size 4] r flushdb r debug quicklist-packed-threshold 5b r RPUSH lst "aa" @@ -227,6 +228,7 @@ start_server [list overrides [list save ""] ] { r RPUSH lst "cc" r RPUSH lst "dd" r RPUSH lst "ee" + assert_encoding quicklist lst r lset lst 2 [string repeat e 10] assert_equal [r lpop lst] "aa" assert_equal [r lpop lst] "bb" @@ -234,6 +236,7 @@ start_server [list overrides [list save ""] ] { assert_equal [r lpop lst] "dd" assert_equal [r lpop lst] "ee" r debug quicklist-packed-threshold 0 + r config set list-max-listpack-size $original_config } {OK} {needs:debug} @@ -381,6 +384,16 @@ if {[lindex [r config get proto-max-bulk-len] 1] == 10000000000} { assert_equal [read_big_bulk {r rpop lst}] $str_length } {} {large-memory} + test {Test LSET on plain nodes with large elements under packed_threshold over 4GB} { + r flushdb + r rpush lst a b c d e + for {set i 0} {$i < 5} {incr i} { + r write "*4\r\n\$4\r\nlset\r\n\$3\r\nlst\r\n\$1\r\n$i\r\n" + write_big_bulk 1000000000 + } + r ping + } {PONG} {large-memory} + test {Test LMOVE on plain nodes over 4GB} { r flushdb r RPUSH lst2{t} "aa" From 181c1c85b0294a7c57b64339a05eae68f39acd74 Mon Sep 17 00:00:00 2001 From: Binbin Date: Tue, 30 Jan 2024 17:32:59 +0800 Subject: [PATCH 3/7] Fix blocking commands timeout is reset due to re-processing command (#13004) In #11012, we will reprocess command when client is unblocked on keys, in some blocking commands, for example, in the XREADGROUP BLOCK scenario, because of the re-processing command, we will recalculate the block timeout, causing the blocking time to be reset. This commit add a new CLIENT_REPROCESSING_COMMAND clent flag, explicitly let the command know that it is being re-processed, later in blockForKeys we will not reset the timeout. Affected BLOCK cases: - list / zset / stream, added test cases for each. Unaffected cases: - module (never re-process the commands). - WAIT / WAITAOF (never re-process the commands). Fixes #12998. Signed-off-by: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com> --- src/blocked.c | 8 +- src/module.c | 5923 ++++++++++++++-------------- src/server.c | 10 +- src/server.h | 1 + tests/unit/type/list.tcl | 28 + tests/unit/type/stream-cgroups.tcl | 30 +- tests/unit/type/zset.tcl | 28 + 7 files changed, 3105 insertions(+), 2923 deletions(-) diff --git a/src/blocked.c b/src/blocked.c index 6ad4667dba..7b48fcab63 100644 --- a/src/blocked.c +++ b/src/blocked.c @@ -370,7 +370,12 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo list *l; int j; - c->bstate.timeout = timeout; + if (!(c->flags & CLIENT_REPROCESSING_COMMAND)) { + /* If the client is re-processing the command, we do not set the timeout + * because we need to retain the client's original timeout. */ + c->bstate.timeout = timeout; + } + for (j = 0; j < numkeys; j++) { /* If the key already exists in the dictionary ignore it. */ if (!(client_blocked_entry = dictAddRaw(c->bstate.keys,keys[j],NULL))) { @@ -392,7 +397,6 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo listAddNodeTail(l,c); dictSetVal(c->bstate.keys,client_blocked_entry,listLast(l)); - /* We need to add the key to blocking_keys_unblock_on_nokey, if the client * wants to be awakened if key is deleted (like XREADGROUP) */ if (unblock_on_nokey) { diff --git a/src/module.c b/src/module.c index e86183c588..03b5564ed5 100644 --- a/src/module.c +++ b/src/module.c @@ -33,8 +33,8 @@ * The comments in this file are used to generate the API documentation on the * Redis website. * - * Each function starting with VM_ and preceded by a block comment is included - * in the API documentation. To hide a VM_ function, put a blank line between + * Each function starting with RM_ and preceded by a block comment is included + * in the API documentation. To hide an RM_ function, put a blank line between * the comment and the function definition or put the comment inside the * function body. * @@ -47,7 +47,7 @@ * the generated a API documentation. * * The documentation comments may contain markdown formatting. Some automatic - * replacements are done, such as the replacement of RM with ValkeyModule in + * replacements are done, such as the replacement of RM with RedisModule in * function names. For details, see the script src/modules/gendoc.rb. * -------------------------------------------------------------------------- */ @@ -59,7 +59,6 @@ #include "script.h" #include "call_reply.h" #include "hdr_histogram.h" -#include "valkeymodule.h" #include #include #include @@ -72,8 +71,8 @@ * pointers that have an API the module can call with them) * -------------------------------------------------------------------------- */ -struct ValkeyModuleInfoCtx { - struct ValkeyModule *module; +struct RedisModuleInfoCtx { + struct RedisModule *module; dict *requested_sections; sds info; /* info string we collected so far */ int sections; /* number of sections we collected so far */ @@ -85,13 +84,13 @@ struct ValkeyModuleInfoCtx { * the server.sharedapi dictionary, mapping names of APIs exported by * modules for other modules to use, to their structure specifying the * function pointer that can be called. */ -struct ValkeyModuleSharedAPI { +struct RedisModuleSharedAPI { void *func; - ValkeyModule *module; + RedisModule *module; }; -typedef struct ValkeyModuleSharedAPI ValkeyModuleSharedAPI; +typedef struct RedisModuleSharedAPI RedisModuleSharedAPI; -dict *modules; /* Hash table of modules. SDS -> ValkeyModule ptr.*/ +dict *modules; /* Hash table of modules. SDS -> RedisModule ptr.*/ /* Entries in the context->amqueue array, representing objects to free * when the callback returns. */ @@ -101,12 +100,12 @@ struct AutoMemEntry { }; /* AutoMemEntry type field values. */ -#define VALKEYMODULE_AM_KEY 0 -#define VALKEYMODULE_AM_STRING 1 -#define VALKEYMODULE_AM_REPLY 2 -#define VALKEYMODULE_AM_FREED 3 /* Explicitly freed by user already. */ -#define VALKEYMODULE_AM_DICT 4 -#define VALKEYMODULE_AM_INFO 5 +#define REDISMODULE_AM_KEY 0 +#define REDISMODULE_AM_STRING 1 +#define REDISMODULE_AM_REPLY 2 +#define REDISMODULE_AM_FREED 3 /* Explicitly freed by user already. */ +#define REDISMODULE_AM_DICT 4 +#define REDISMODULE_AM_INFO 5 /* The pool allocator block. Redis Modules can allocate memory via this special * allocator that will automatically release it all once the callback returns. @@ -121,15 +120,15 @@ struct AutoMemEntry { * Allocations are always rounded to the size of the void pointer in order * to always return aligned memory chunks. */ -#define VALKEYMODULE_POOL_ALLOC_MIN_SIZE (1024*8) -#define VALKEYMODULE_POOL_ALLOC_ALIGN (sizeof(void*)) +#define REDISMODULE_POOL_ALLOC_MIN_SIZE (1024*8) +#define REDISMODULE_POOL_ALLOC_ALIGN (sizeof(void*)) -typedef struct ValkeyModulePoolAllocBlock { +typedef struct RedisModulePoolAllocBlock { uint32_t size; uint32_t used; - struct ValkeyModulePoolAllocBlock *next; + struct RedisModulePoolAllocBlock *next; char memory[]; -} ValkeyModulePoolAllocBlock; +} RedisModulePoolAllocBlock; /* This structure represents the context in which Redis modules operate. * Most APIs module can access, get a pointer to the context, so that the API @@ -139,56 +138,56 @@ typedef struct ValkeyModulePoolAllocBlock { * Note that not all the context structure is always filled with actual values * but only the fields needed in a given context. */ -struct ValkeyModuleBlockedClient; -struct ValkeyModuleUser; +struct RedisModuleBlockedClient; +struct RedisModuleUser; -struct ValkeyModuleCtx { +struct RedisModuleCtx { void *getapifuncptr; /* NOTE: Must be the first field. */ - struct ValkeyModule *module; /* Module reference. */ + struct RedisModule *module; /* Module reference. */ client *client; /* Client calling a command. */ - struct ValkeyModuleBlockedClient *blocked_client; /* Blocked client for + struct RedisModuleBlockedClient *blocked_client; /* Blocked client for thread safe context. */ struct AutoMemEntry *amqueue; /* Auto memory queue of objects to free. */ int amqueue_len; /* Number of slots in amqueue. */ int amqueue_used; /* Number of used slots in amqueue. */ - int flags; /* VALKEYMODULE_CTX_... flags. */ - void **postponed_arrays; /* To set with VM_ReplySetArrayLength(). */ + int flags; /* REDISMODULE_CTX_... flags. */ + void **postponed_arrays; /* To set with RM_ReplySetArrayLength(). */ int postponed_arrays_count; /* Number of entries in postponed_arrays. */ void *blocked_privdata; /* Privdata set when unblocking a client. */ - ValkeyModuleString *blocked_ready_key; /* Key ready when the reply callback + RedisModuleString *blocked_ready_key; /* Key ready when the reply callback gets called for clients blocked on keys. */ - /* Used if there is the VALKEYMODULE_CTX_KEYS_POS_REQUEST or - * VALKEYMODULE_CTX_CHANNEL_POS_REQUEST flag set. */ + /* Used if there is the REDISMODULE_CTX_KEYS_POS_REQUEST or + * REDISMODULE_CTX_CHANNEL_POS_REQUEST flag set. */ getKeysResult *keys_result; - struct ValkeyModulePoolAllocBlock *pa_head; + struct RedisModulePoolAllocBlock *pa_head; long long next_yield_time; - const struct ValkeyModuleUser *user; /* ValkeyModuleUser commands executed via - VM_Call should be executed as, if set */ + const struct RedisModuleUser *user; /* RedisModuleUser commands executed via + RM_Call should be executed as, if set */ }; -typedef struct ValkeyModuleCtx ValkeyModuleCtx; - -#define VALKEYMODULE_CTX_NONE (0) -#define VALKEYMODULE_CTX_AUTO_MEMORY (1<<0) -#define VALKEYMODULE_CTX_KEYS_POS_REQUEST (1<<1) -#define VALKEYMODULE_CTX_BLOCKED_REPLY (1<<2) -#define VALKEYMODULE_CTX_BLOCKED_TIMEOUT (1<<3) -#define VALKEYMODULE_CTX_THREAD_SAFE (1<<4) -#define VALKEYMODULE_CTX_BLOCKED_DISCONNECTED (1<<5) -#define VALKEYMODULE_CTX_TEMP_CLIENT (1<<6) /* Return client object to the pool +typedef struct RedisModuleCtx RedisModuleCtx; + +#define REDISMODULE_CTX_NONE (0) +#define REDISMODULE_CTX_AUTO_MEMORY (1<<0) +#define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<1) +#define REDISMODULE_CTX_BLOCKED_REPLY (1<<2) +#define REDISMODULE_CTX_BLOCKED_TIMEOUT (1<<3) +#define REDISMODULE_CTX_THREAD_SAFE (1<<4) +#define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<5) +#define REDISMODULE_CTX_TEMP_CLIENT (1<<6) /* Return client object to the pool when the context is destroyed */ -#define VALKEYMODULE_CTX_NEW_CLIENT (1<<7) /* Free client object when the +#define REDISMODULE_CTX_NEW_CLIENT (1<<7) /* Free client object when the context is destroyed */ -#define VALKEYMODULE_CTX_CHANNELS_POS_REQUEST (1<<8) -#define VALKEYMODULE_CTX_COMMAND (1<<9) /* Context created to serve a command from call() or AOF (which calls cmd->proc directly) */ +#define REDISMODULE_CTX_CHANNELS_POS_REQUEST (1<<8) +#define REDISMODULE_CTX_COMMAND (1<<9) /* Context created to serve a command from call() or AOF (which calls cmd->proc directly) */ -/* This represents a Redis key opened with VM_OpenKey(). */ -struct ValkeyModuleKey { - ValkeyModuleCtx *ctx; +/* This represents a Redis key opened with RM_OpenKey(). */ +struct RedisModuleKey { + RedisModuleCtx *ctx; redisDb *db; robj *key; /* Key name object. */ robj *value; /* Value object, or NULL if the key was not found. */ @@ -203,7 +202,7 @@ struct ValkeyModuleKey { } list; struct { /* Zset iterator, use only if value->type == OBJ_ZSET */ - uint32_t type; /* VALKEYMODULE_ZSET_RANGE_* */ + uint32_t type; /* REDISMODULE_ZSET_RANGE_* */ zrangespec rs; /* Score range. */ zlexrangespec lrs; /* Lex range. */ uint32_t start; /* Start pos for positional ranges. */ @@ -221,69 +220,69 @@ struct ValkeyModuleKey { } u; }; -/* ValkeyModuleKey 'ztype' values. */ -#define VALKEYMODULE_ZSET_RANGE_NONE 0 /* This must always be 0. */ -#define VALKEYMODULE_ZSET_RANGE_LEX 1 -#define VALKEYMODULE_ZSET_RANGE_SCORE 2 -#define VALKEYMODULE_ZSET_RANGE_POS 3 +/* RedisModuleKey 'ztype' values. */ +#define REDISMODULE_ZSET_RANGE_NONE 0 /* This must always be 0. */ +#define REDISMODULE_ZSET_RANGE_LEX 1 +#define REDISMODULE_ZSET_RANGE_SCORE 2 +#define REDISMODULE_ZSET_RANGE_POS 3 /* Function pointer type of a function representing a command inside * a Redis module. */ -struct ValkeyModuleBlockedClient; -typedef int (*ValkeyModuleCmdFunc) (ValkeyModuleCtx *ctx, void **argv, int argc); -typedef int (*ValkeyModuleAuthCallback)(ValkeyModuleCtx *ctx, void *username, void *password, ValkeyModuleString **err); -typedef void (*ValkeyModuleDisconnectFunc) (ValkeyModuleCtx *ctx, struct ValkeyModuleBlockedClient *bc); +struct RedisModuleBlockedClient; +typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, void **argv, int argc); +typedef int (*RedisModuleAuthCallback)(RedisModuleCtx *ctx, void *username, void *password, RedisModuleString **err); +typedef void (*RedisModuleDisconnectFunc) (RedisModuleCtx *ctx, struct RedisModuleBlockedClient *bc); /* This struct holds the information about a command registered by a module.*/ -struct ValkeyModuleCommand { - struct ValkeyModule *module; - ValkeyModuleCmdFunc func; +struct RedisModuleCommand { + struct RedisModule *module; + RedisModuleCmdFunc func; struct redisCommand *rediscmd; }; -typedef struct ValkeyModuleCommand ValkeyModuleCommand; +typedef struct RedisModuleCommand RedisModuleCommand; -#define VALKEYMODULE_REPLYFLAG_NONE 0 -#define VALKEYMODULE_REPLYFLAG_TOPARSE (1<<0) /* Protocol must be parsed. */ -#define VALKEYMODULE_REPLYFLAG_NESTED (1<<1) /* Nested reply object. No proto +#define REDISMODULE_REPLYFLAG_NONE 0 +#define REDISMODULE_REPLYFLAG_TOPARSE (1<<0) /* Protocol must be parsed. */ +#define REDISMODULE_REPLYFLAG_NESTED (1<<1) /* Nested reply object. No proto or struct free. */ -/* Reply of VM_Call() function. The function is filled in a lazy +/* Reply of RM_Call() function. The function is filled in a lazy * way depending on the function called on the reply structure. By default * only the type, proto and protolen are filled. */ -typedef struct CallReply ValkeyModuleCallReply; +typedef struct CallReply RedisModuleCallReply; /* Structure to hold the module auth callback & the Module implementing it. */ -typedef struct ValkeyModuleAuthCtx { - struct ValkeyModule *module; - ValkeyModuleAuthCallback auth_cb; -} ValkeyModuleAuthCtx; +typedef struct RedisModuleAuthCtx { + struct RedisModule *module; + RedisModuleAuthCallback auth_cb; +} RedisModuleAuthCtx; /* Structure representing a blocked client. We get a pointer to such * an object when blocking from modules. */ -typedef struct ValkeyModuleBlockedClient { +typedef struct RedisModuleBlockedClient { client *client; /* Pointer to the blocked client. or NULL if the client was destroyed during the life of this object. */ - ValkeyModule *module; /* Module blocking the client. */ - ValkeyModuleCmdFunc reply_callback; /* Reply callback on normal completion.*/ - ValkeyModuleAuthCallback auth_reply_cb; /* Reply callback on completing blocking + RedisModule *module; /* Module blocking the client. */ + RedisModuleCmdFunc reply_callback; /* Reply callback on normal completion.*/ + RedisModuleAuthCallback auth_reply_cb; /* Reply callback on completing blocking module authentication. */ - ValkeyModuleCmdFunc timeout_callback; /* Reply callback on timeout. */ - ValkeyModuleDisconnectFunc disconnect_callback; /* Called on disconnection.*/ - void (*free_privdata)(ValkeyModuleCtx*,void*);/* privdata cleanup callback.*/ + RedisModuleCmdFunc timeout_callback; /* Reply callback on timeout. */ + RedisModuleDisconnectFunc disconnect_callback; /* Called on disconnection.*/ + void (*free_privdata)(RedisModuleCtx*,void*);/* privdata cleanup callback.*/ void *privdata; /* Module private data that may be used by the reply or timeout callback. It is set via the - ValkeyModule_UnblockClient() API. */ + RedisModule_UnblockClient() API. */ client *thread_safe_ctx_client; /* Fake client to be used for thread safe context so that no lock is required. */ client *reply_client; /* Fake client used to accumulate replies in thread safe contexts. */ int dbid; /* Database number selected by the original client. */ - int blocked_on_keys; /* If blocked via VM_BlockClientOnKeys(). */ + int blocked_on_keys; /* If blocked via RM_BlockClientOnKeys(). */ int unblocked; /* Already on the moduleUnblocked list. */ monotime background_timer; /* Timer tracking the start of background work */ uint64_t background_duration; /* Current command background time duration. Used for measuring latency of blocking cmds */ -} ValkeyModuleBlockedClient; +} RedisModuleBlockedClient; /* This is a list of Module Auth Contexts. Each time a Module registers a callback, a new ctx is * added to this list. Multiple modules can register auth callbacks and the same Module can have @@ -307,35 +306,34 @@ static size_t moduleTempClientMinCount = 0; /* Min client count in pool since * 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 (*ValkeyModuleNotificationFunc) (ValkeyModuleCtx *ctx, int type, const char *event, ValkeyModuleString *key); +typedef int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key); /* Function pointer type for post jobs */ -typedef void (*ValkeyModulePostNotificationJobFunc) (ValkeyModuleCtx *ctx, void *pd); +typedef void (*RedisModulePostNotificationJobFunc) (RedisModuleCtx *ctx, void *pd); /* Keyspace notification subscriber information. - * See VM_SubscribeToKeyspaceEvents() for more information. */ -typedef struct ValkeyModuleKeyspaceSubscriber { + * See RM_SubscribeToKeyspaceEvents() for more information. */ +typedef struct RedisModuleKeyspaceSubscriber { /* The module subscribed to the event */ - ValkeyModule *module; + RedisModule *module; /* Notification callback in the module*/ - ValkeyModuleNotificationFunc notify_callback; + 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; -} ValkeyModuleKeyspaceSubscriber; +} RedisModuleKeyspaceSubscriber; -typedef struct ValkeyModulePostExecUnitJob { +typedef struct RedisModulePostExecUnitJob { /* The module subscribed to the event */ - ValkeyModule *module; - ValkeyModulePostNotificationJobFunc callback; + RedisModule *module; + RedisModulePostNotificationJobFunc callback; void *pd; void (*free_pd)(void*); int dbid; -} ValkeyModulePostExecUnitJob; +} RedisModulePostExecUnitJob; /* The module keyspace notification subscribers list */ static list *moduleKeyspaceSubscribers; @@ -344,168 +342,172 @@ static list *moduleKeyspaceSubscribers; static list *modulePostExecUnitJobs; /* Data structures related to the exported dictionary data structure. */ -typedef struct ValkeyModuleDict { +typedef struct RedisModuleDict { rax *rax; /* The radix tree. */ -} ValkeyModuleDict; +} RedisModuleDict; -typedef struct ValkeyModuleDictIter { - ValkeyModuleDict *dict; +typedef struct RedisModuleDictIter { + RedisModuleDict *dict; raxIterator ri; -} ValkeyModuleDictIter; +} RedisModuleDictIter; -typedef struct ValkeyModuleCommandFilterCtx { - ValkeyModuleString **argv; +typedef struct RedisModuleCommandFilterCtx { + RedisModuleString **argv; int argv_len; int argc; client *c; -} ValkeyModuleCommandFilterCtx; +} RedisModuleCommandFilterCtx; -typedef void (*ValkeyModuleCommandFilterFunc) (ValkeyModuleCommandFilterCtx *filter); +typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter); -typedef struct ValkeyModuleCommandFilter { +typedef struct RedisModuleCommandFilter { /* The module that registered the filter */ - ValkeyModule *module; + RedisModule *module; /* Filter callback function */ - ValkeyModuleCommandFilterFunc callback; - /* VALKEYMODULE_CMDFILTER_* flags */ + RedisModuleCommandFilterFunc callback; + /* REDISMODULE_CMDFILTER_* flags */ int flags; -} ValkeyModuleCommandFilter; +} RedisModuleCommandFilter; /* Registered filters */ static list *moduleCommandFilters; -typedef void (*ValkeyModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data); +typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data); -static struct ValkeyModuleForkInfo { - ValkeyModuleForkDoneHandler done_handler; +static struct RedisModuleForkInfo { + RedisModuleForkDoneHandler done_handler; void* done_handler_user_data; } moduleForkInfo = {0}; -typedef struct ValkeyModuleServerInfoData { +typedef struct RedisModuleServerInfoData { rax *rax; /* parsed info data. */ -} ValkeyModuleServerInfoData; +} RedisModuleServerInfoData; /* Flags for moduleCreateArgvFromUserFormat(). */ -#define VALKEYMODULE_ARGV_REPLICATE (1<<0) -#define VALKEYMODULE_ARGV_NO_AOF (1<<1) -#define VALKEYMODULE_ARGV_NO_REPLICAS (1<<2) -#define VALKEYMODULE_ARGV_RESP_3 (1<<3) -#define VALKEYMODULE_ARGV_RESP_AUTO (1<<4) -#define VALKEYMODULE_ARGV_RUN_AS_USER (1<<5) -#define VALKEYMODULE_ARGV_SCRIPT_MODE (1<<6) -#define VALKEYMODULE_ARGV_NO_WRITES (1<<7) -#define VALKEYMODULE_ARGV_CALL_REPLIES_AS_ERRORS (1<<8) -#define VALKEYMODULE_ARGV_RESPECT_DENY_OOM (1<<9) -#define VALKEYMODULE_ARGV_DRY_RUN (1<<10) -#define VALKEYMODULE_ARGV_ALLOW_BLOCK (1<<11) +#define REDISMODULE_ARGV_REPLICATE (1<<0) +#define REDISMODULE_ARGV_NO_AOF (1<<1) +#define REDISMODULE_ARGV_NO_REPLICAS (1<<2) +#define REDISMODULE_ARGV_RESP_3 (1<<3) +#define REDISMODULE_ARGV_RESP_AUTO (1<<4) +#define REDISMODULE_ARGV_RUN_AS_USER (1<<5) +#define REDISMODULE_ARGV_SCRIPT_MODE (1<<6) +#define REDISMODULE_ARGV_NO_WRITES (1<<7) +#define REDISMODULE_ARGV_CALL_REPLIES_AS_ERRORS (1<<8) +#define REDISMODULE_ARGV_RESPECT_DENY_OOM (1<<9) +#define REDISMODULE_ARGV_DRY_RUN (1<<10) +#define REDISMODULE_ARGV_ALLOW_BLOCK (1<<11) /* Determine whether Redis should signalModifiedKey implicitly. * In case 'ctx' has no 'module' member (and therefore no module->options), * we assume default behavior, that is, Redis signals. - * (see VM_GetThreadSafeContext) */ + * (see RM_GetThreadSafeContext) */ #define SHOULD_SIGNAL_MODIFIED_KEYS(ctx) \ - ((ctx)->module? !((ctx)->module->options & VALKEYMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED) : 1) + ((ctx)->module? !((ctx)->module->options & REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED) : 1) /* Server events hooks data structures and defines: this modules API * allow modules to subscribe to certain events in Redis, such as * the start and end of an RDB or AOF save, the change of role in replication, * and similar other events. */ -typedef struct ValkeyModuleEventListener { - ValkeyModule *module; - ValkeyModuleEvent event; - ValkeyModuleEventCallback callback; -} ValkeyModuleEventListener; +typedef struct RedisModuleEventListener { + RedisModule *module; + RedisModuleEvent event; + RedisModuleEventCallback callback; +} RedisModuleEventListener; -list *ValkeyModule_EventListeners; /* Global list of all the active events. */ +list *RedisModule_EventListeners; /* Global list of all the active events. */ /* Data structures related to the redis module users */ -/* This is the object returned by VM_CreateModuleUser(). The module API is +/* This is the object returned by RM_CreateModuleUser(). The module API is * able to create users, set ACLs to such users, and later authenticate * clients using such newly created users. */ -typedef struct ValkeyModuleUser { +typedef struct RedisModuleUser { user *user; /* Reference to the real redis user */ int free_user; /* Indicates that user should also be freed when this object is freed */ -} ValkeyModuleUser; +} RedisModuleUser; /* This is a structure used to export some meta-information such as dbid to the module. */ -typedef struct ValkeyModuleKeyOptCtx { +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 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 as `copy2`, 'from_dbid' and 'to_dbid' are both valid. */ -} ValkeyModuleKeyOptCtx; +} RedisModuleKeyOptCtx; /* Data structures related to redis module configurations */ -/* The function signatures for module config get callbacks. These are identical to the ones exposed in valkeymodule.h. */ -typedef ValkeyModuleString * (*ValkeyModuleConfigGetStringFunc)(const char *name, void *privdata); -typedef long long (*ValkeyModuleConfigGetNumericFunc)(const char *name, void *privdata); -typedef int (*ValkeyModuleConfigGetBoolFunc)(const char *name, void *privdata); -typedef int (*ValkeyModuleConfigGetEnumFunc)(const char *name, void *privdata); -/* The function signatures for module config set callbacks. These are identical to the ones exposed in valkeymodule.h. */ -typedef int (*ValkeyModuleConfigSetStringFunc)(const char *name, ValkeyModuleString *val, void *privdata, ValkeyModuleString **err); -typedef int (*ValkeyModuleConfigSetNumericFunc)(const char *name, long long val, void *privdata, ValkeyModuleString **err); -typedef int (*ValkeyModuleConfigSetBoolFunc)(const char *name, int val, void *privdata, ValkeyModuleString **err); -typedef int (*ValkeyModuleConfigSetEnumFunc)(const char *name, int val, void *privdata, ValkeyModuleString **err); -/* Apply signature, identical to valkeymodule.h */ -typedef int (*ValkeyModuleConfigApplyFunc)(ValkeyModuleCtx *ctx, void *privdata, ValkeyModuleString **err); +/* The function signatures for module config get callbacks. These are identical to the ones exposed in redismodule.h. */ +typedef RedisModuleString * (*RedisModuleConfigGetStringFunc)(const char *name, void *privdata); +typedef long long (*RedisModuleConfigGetNumericFunc)(const char *name, void *privdata); +typedef int (*RedisModuleConfigGetBoolFunc)(const char *name, void *privdata); +typedef int (*RedisModuleConfigGetEnumFunc)(const char *name, void *privdata); +/* The function signatures for module config set callbacks. These are identical to the ones exposed in redismodule.h. */ +typedef int (*RedisModuleConfigSetStringFunc)(const char *name, RedisModuleString *val, void *privdata, RedisModuleString **err); +typedef int (*RedisModuleConfigSetNumericFunc)(const char *name, long long val, void *privdata, RedisModuleString **err); +typedef int (*RedisModuleConfigSetBoolFunc)(const char *name, int val, void *privdata, RedisModuleString **err); +typedef int (*RedisModuleConfigSetEnumFunc)(const char *name, int val, void *privdata, RedisModuleString **err); +/* Apply signature, identical to redismodule.h */ +typedef int (*RedisModuleConfigApplyFunc)(RedisModuleCtx *ctx, void *privdata, RedisModuleString **err); /* Struct representing a module config. These are stored in a list in the module struct */ struct ModuleConfig { sds name; /* Name of config without the module name appended to the front */ void *privdata; /* Optional data passed into the module config callbacks */ union get_fn { /* The get callback specified by the module */ - ValkeyModuleConfigGetStringFunc get_string; - ValkeyModuleConfigGetNumericFunc get_numeric; - ValkeyModuleConfigGetBoolFunc get_bool; - ValkeyModuleConfigGetEnumFunc get_enum; + RedisModuleConfigGetStringFunc get_string; + RedisModuleConfigGetNumericFunc get_numeric; + RedisModuleConfigGetBoolFunc get_bool; + RedisModuleConfigGetEnumFunc get_enum; } get_fn; union set_fn { /* The set callback specified by the module */ - ValkeyModuleConfigSetStringFunc set_string; - ValkeyModuleConfigSetNumericFunc set_numeric; - ValkeyModuleConfigSetBoolFunc set_bool; - ValkeyModuleConfigSetEnumFunc set_enum; + RedisModuleConfigSetStringFunc set_string; + RedisModuleConfigSetNumericFunc set_numeric; + RedisModuleConfigSetBoolFunc set_bool; + RedisModuleConfigSetEnumFunc set_enum; } set_fn; - ValkeyModuleConfigApplyFunc apply_fn; - ValkeyModule *module; + RedisModuleConfigApplyFunc apply_fn; + RedisModule *module; }; -typedef struct ValkeyModuleAsyncRMCallPromise{ +typedef struct RedisModuleAsyncRMCallPromise{ size_t ref_count; void *private_data; - ValkeyModule *module; - ValkeyModuleOnUnblocked on_unblocked; + RedisModule *module; + RedisModuleOnUnblocked on_unblocked; client *c; - ValkeyModuleCtx *ctx; -} ValkeyModuleAsyncRMCallPromise; + RedisModuleCtx *ctx; +} RedisModuleAsyncRMCallPromise; /* -------------------------------------------------------------------------- * Prototypes * -------------------------------------------------------------------------- */ -void VM_FreeCallReply(ValkeyModuleCallReply *reply); -void VM_CloseKey(ValkeyModuleKey *key); -void autoMemoryCollect(ValkeyModuleCtx *ctx); +void RM_FreeCallReply(RedisModuleCallReply *reply); +void RM_CloseKey(RedisModuleKey *key); +void autoMemoryCollect(RedisModuleCtx *ctx); robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int *argcp, int *flags, va_list ap); -void VM_ZsetRangeStop(ValkeyModuleKey *kp); -static void zsetKeyReset(ValkeyModuleKey *key); -static void moduleInitKeyTypeSpecific(ValkeyModuleKey *key); -void VM_FreeDict(ValkeyModuleCtx *ctx, ValkeyModuleDict *d); -void VM_FreeServerInfo(ValkeyModuleCtx *ctx, ValkeyModuleServerInfoData *data); - -/* Helpers for VM_SetCommandInfo. */ -static int moduleValidateCommandInfo(const ValkeyModuleCommandInfo *info); +void RM_ZsetRangeStop(RedisModuleKey *kp); +static void zsetKeyReset(RedisModuleKey *key); +static void moduleInitKeyTypeSpecific(RedisModuleKey *key); +void RM_FreeDict(RedisModuleCtx *ctx, RedisModuleDict *d); +void RM_FreeServerInfo(RedisModuleCtx *ctx, RedisModuleServerInfoData *data); + +/* Helpers for RM_SetCommandInfo. */ +static int moduleValidateCommandInfo(const RedisModuleCommandInfo *info); static int64_t moduleConvertKeySpecsFlags(int64_t flags, int from_api); -static int moduleValidateCommandArgs(ValkeyModuleCommandArg *args, - const ValkeyModuleCommandInfoVersion *version); -static struct redisCommandArg *moduleCopyCommandArgs(ValkeyModuleCommandArg *args, - const ValkeyModuleCommandInfoVersion *version); -static redisCommandArgType moduleConvertArgType(ValkeyModuleCommandArgType type, int *error); +static int moduleValidateCommandArgs(RedisModuleCommandArg *args, + const RedisModuleCommandInfoVersion *version); +static struct redisCommandArg *moduleCopyCommandArgs(RedisModuleCommandArg *args, + const RedisModuleCommandInfoVersion *version); +static redisCommandArgType moduleConvertArgType(RedisModuleCommandArgType type, int *error); static int moduleConvertArgFlags(int flags); -void moduleCreateContext(ValkeyModuleCtx *out_ctx, ValkeyModule *module, int ctx_flags); +void moduleCreateContext(RedisModuleCtx *out_ctx, RedisModule *module, int ctx_flags); + +/* Common helper functions. */ +int moduleVerifyResourceName(const char *name); + /* -------------------------------------------------------------------------- * ## Heap allocation raw functions * @@ -518,10 +520,10 @@ void moduleCreateContext(ValkeyModuleCtx *out_ctx, ValkeyModule *module, int ctx * and in general is taken into account as memory allocated by Redis. * You should avoid using malloc(). * This function panics if unable to allocate enough memory. */ -void *VM_Alloc(size_t bytes) { +void *RM_Alloc(size_t bytes) { /* Use 'zmalloc_usable()' instead of 'zmalloc()' to allow the compiler * to recognize the additional memory size, which means that modules can - * use the memory reported by 'VM_MallocUsableSize()' safely. In theory this + * use the memory reported by 'RM_MallocUsableSize()' safely. In theory this * isn't really needed since this API can't be inlined (not even for embedded * modules like TLS (we use function pointers for module APIs), and the API doesn't * have the malloc_size attribute, but it's hard to predict how smart future compilers @@ -529,9 +531,9 @@ void *VM_Alloc(size_t bytes) { return zmalloc_usable(bytes,NULL); } -/* Similar to VM_Alloc, but returns NULL in case of allocation failure, instead +/* Similar to RM_Alloc, but returns NULL in case of allocation failure, instead * of panicking. */ -void *VM_TryAlloc(size_t bytes) { +void *RM_TryAlloc(size_t bytes) { return ztrymalloc_usable(bytes,NULL); } @@ -539,24 +541,36 @@ void *VM_TryAlloc(size_t bytes) { * Redis INFO memory, used for keys eviction according to maxmemory settings * and in general is taken into account as memory allocated by Redis. * You should avoid using calloc() directly. */ -void *VM_Calloc(size_t nmemb, size_t size) { +void *RM_Calloc(size_t nmemb, size_t size) { return zcalloc_usable(nmemb*size,NULL); } -/* Use like realloc() for memory obtained with ValkeyModule_Alloc(). */ -void* VM_Realloc(void *ptr, size_t bytes) { +/* Similar to RM_Calloc, but returns NULL in case of allocation failure, instead + * of panicking. */ +void *RM_TryCalloc(size_t nmemb, size_t size) { + return ztrycalloc_usable(nmemb*size,NULL); +} + +/* Use like realloc() for memory obtained with RedisModule_Alloc(). */ +void* RM_Realloc(void *ptr, size_t bytes) { return zrealloc_usable(ptr,bytes,NULL); } -/* Use like free() for memory obtained by ValkeyModule_Alloc() and - * ValkeyModule_Realloc(). However you should never try to free with - * ValkeyModule_Free() memory allocated with malloc() inside your module. */ -void VM_Free(void *ptr) { +/* Similar to RM_Realloc, but returns NULL in case of allocation failure, + * instead of panicking. */ +void *RM_TryRealloc(void *ptr, size_t bytes) { + return ztryrealloc_usable(ptr,bytes,NULL); +} + +/* Use like free() for memory obtained by RedisModule_Alloc() and + * RedisModule_Realloc(). However you should never try to free with + * RedisModule_Free() memory allocated with malloc() inside your module. */ +void RM_Free(void *ptr) { zfree(ptr); } -/* Like strdup() but returns memory allocated with ValkeyModule_Alloc(). */ -char *VM_Strdup(const char *str) { +/* Like strdup() but returns memory allocated with RedisModule_Alloc(). */ +char *RM_Strdup(const char *str) { return zstrdup(str); } @@ -565,8 +579,8 @@ char *VM_Strdup(const char *str) { * -------------------------------------------------------------------------- */ /* Release the chain of blocks used for pool allocations. */ -void poolAllocRelease(ValkeyModuleCtx *ctx) { - ValkeyModulePoolAllocBlock *head = ctx->pa_head, *next; +void poolAllocRelease(RedisModuleCtx *ctx) { + RedisModulePoolAllocBlock *head = ctx->pa_head, *next; while(head != NULL) { next = head->next; @@ -588,14 +602,14 @@ void poolAllocRelease(ValkeyModuleCtx *ctx) { * pool allocator is not a good idea. * * The function returns NULL if `bytes` is 0. */ -void *VM_PoolAlloc(ValkeyModuleCtx *ctx, size_t bytes) { +void *RM_PoolAlloc(RedisModuleCtx *ctx, size_t bytes) { if (bytes == 0) return NULL; - ValkeyModulePoolAllocBlock *b = ctx->pa_head; + RedisModulePoolAllocBlock *b = ctx->pa_head; size_t left = b ? b->size - b->used : 0; /* Fix alignment. */ if (left >= bytes) { - size_t alignment = VALKEYMODULE_POOL_ALLOC_ALIGN; + size_t alignment = REDISMODULE_POOL_ALLOC_ALIGN; while (bytes < alignment && alignment/2 >= bytes) alignment /= 2; if (b->used % alignment) b->used += alignment - (b->used % alignment); @@ -604,7 +618,7 @@ void *VM_PoolAlloc(ValkeyModuleCtx *ctx, size_t bytes) { /* Create a new block if needed. */ if (left < bytes) { - size_t blocksize = VALKEYMODULE_POOL_ALLOC_MIN_SIZE; + size_t blocksize = REDISMODULE_POOL_ALLOC_MIN_SIZE; if (blocksize < bytes) blocksize = bytes; b = zmalloc(sizeof(*b) + blocksize); b->size = blocksize; @@ -637,12 +651,12 @@ client *moduleAllocTempClient(void) { return c; } -static void freeValkeyModuleAsyncRMCallPromise(ValkeyModuleAsyncRMCallPromise *promise) { +static void freeRedisModuleAsyncRMCallPromise(RedisModuleAsyncRMCallPromise *promise) { if (--promise->ref_count > 0) { return; } /* When the promise is finally freed it can not have a client attached to it. - * Either releasing the client or VM_CallReplyPromiseAbort would have removed it. */ + * Either releasing the client or RM_CallReplyPromiseAbort would have removed it. */ serverAssert(!promise->c); zfree(promise); } @@ -662,9 +676,9 @@ void moduleReleaseTempClient(client *c) { c->user = NULL; /* Root user */ c->cmd = c->lastcmd = c->realcmd = NULL; if (c->bstate.async_rm_call_handle) { - ValkeyModuleAsyncRMCallPromise *promise = c->bstate.async_rm_call_handle; + RedisModuleAsyncRMCallPromise *promise = c->bstate.async_rm_call_handle; promise->c = NULL; /* Remove the client from the promise so it will no longer be possible to abort it. */ - freeValkeyModuleAsyncRMCallPromise(promise); + freeRedisModuleAsyncRMCallPromise(promise); c->bstate.async_rm_call_handle = NULL; } moduleTempClients[moduleTempClientCount++] = c; @@ -674,44 +688,44 @@ void moduleReleaseTempClient(client *c) { * opened for writing where the `.value` member is set to NULL because the * key was found to be non existing. * - * On success VALKEYMODULE_OK is returned and the key is populated with + * On success REDISMODULE_OK is returned and the key is populated with * the value of the specified type. The function fails and returns - * VALKEYMODULE_ERR if: + * REDISMODULE_ERR if: * * 1. The key is not open for writing. * 2. The key is not empty. * 3. The specified type is unknown. */ -int moduleCreateEmptyKey(ValkeyModuleKey *key, int type) { +int moduleCreateEmptyKey(RedisModuleKey *key, int type) { robj *obj; /* The key must be open for writing and non existing to proceed. */ - if (!(key->mode & VALKEYMODULE_WRITE) || key->value) - return VALKEYMODULE_ERR; + if (!(key->mode & REDISMODULE_WRITE) || key->value) + return REDISMODULE_ERR; switch(type) { - case VALKEYMODULE_KEYTYPE_LIST: + case REDISMODULE_KEYTYPE_LIST: obj = createListListpackObject(); break; - case VALKEYMODULE_KEYTYPE_ZSET: + case REDISMODULE_KEYTYPE_ZSET: obj = createZsetListpackObject(); break; - case VALKEYMODULE_KEYTYPE_HASH: + case REDISMODULE_KEYTYPE_HASH: obj = createHashObject(); break; - case VALKEYMODULE_KEYTYPE_STREAM: + case REDISMODULE_KEYTYPE_STREAM: obj = createStreamObject(); break; - default: return VALKEYMODULE_ERR; + default: return REDISMODULE_ERR; } dbAdd(key->db,key->key,obj); key->value = obj; moduleInitKeyTypeSpecific(key); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Frees key->iter and sets it to NULL. */ -static void moduleFreeKeyIterator(ValkeyModuleKey *key) { +static void moduleFreeKeyIterator(RedisModuleKey *key) { serverAssert(key->iter != NULL); switch (key->value->type) { case OBJ_LIST: listTypeReleaseIterator(key->iter); break; @@ -727,7 +741,7 @@ static void moduleFreeKeyIterator(ValkeyModuleKey *key) { /* Callback for listTypeTryConversion(). * Frees list iterator and sets it to NULL. */ static void moduleFreeListIterator(void *data) { - ValkeyModuleKey *key = (ValkeyModuleKey*)data; + RedisModuleKey *key = (RedisModuleKey*)data; serverAssert(key->value->type == OBJ_LIST); if (key->iter) moduleFreeKeyIterator(key); } @@ -742,8 +756,8 @@ static void moduleFreeListIterator(void *data) { * * The function returns 1 if the key value object is found empty and is * deleted, otherwise 0 is returned. */ -int moduleDelKeyIfEmpty(ValkeyModuleKey *key) { - if (!(key->mode & VALKEYMODULE_WRITE) || key->value == NULL) return 0; +int moduleDelKeyIfEmpty(RedisModuleKey *key) { + if (!(key->mode & REDISMODULE_WRITE) || key->value == NULL) return 0; int isempty; robj *o = key->value; @@ -769,24 +783,24 @@ int moduleDelKeyIfEmpty(ValkeyModuleKey *key) { /* -------------------------------------------------------------------------- * Service API exported to modules * - * Note that all the exported APIs are called VM_ in the core - * and ValkeyModule_ in the module side (defined as function - * pointers in valkeymodule.h). In this way the dynamic linker does not + * Note that all the exported APIs are called RM_ in the core + * and RedisModule_ in the module side (defined as function + * pointers in redismodule.h). In this way the dynamic linker does not * mess with our global function pointers, overriding it with the symbols * defined in the main executable having the same names. * -------------------------------------------------------------------------- */ -int VM_GetApi(const char *funcname, void **targetPtrPtr) { +int RM_GetApi(const char *funcname, void **targetPtrPtr) { /* Lookup the requested module API and store the function pointer into the - * target pointer. The function returns VALKEYMODULE_ERR if there is no such - * named API, otherwise VALKEYMODULE_OK. + * target pointer. The function returns REDISMODULE_ERR if there is no such + * named API, otherwise REDISMODULE_OK. * * This function is not meant to be used by modules developer, it is only - * used implicitly by including valkeymodule.h. */ + * used implicitly by including redismodule.h. */ dictEntry *he = dictFind(server.moduleapi, funcname); - if (!he) return VALKEYMODULE_ERR; + if (!he) return REDISMODULE_ERR; *targetPtrPtr = dictGetVal(he); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } void modulePostExecutionUnitOperations(void) { @@ -803,9 +817,9 @@ void modulePostExecutionUnitOperations(void) { } /* Free the context after the user function was called. */ -void moduleFreeContext(ValkeyModuleCtx *ctx) { +void moduleFreeContext(RedisModuleCtx *ctx) { /* See comment in moduleCreateContext */ - if (!(ctx->flags & (VALKEYMODULE_CTX_THREAD_SAFE|VALKEYMODULE_CTX_COMMAND))) { + if (!(ctx->flags & (REDISMODULE_CTX_THREAD_SAFE|REDISMODULE_CTX_COMMAND))) { exitExecutionUnit(); postExecutionUnitOperations(); } @@ -816,8 +830,8 @@ void moduleFreeContext(ValkeyModuleCtx *ctx) { ctx->postponed_arrays_count = 0; serverLog(LL_WARNING, "API misuse detected in module %s: " - "ValkeyModule_ReplyWith*(VALKEYMODULE_POSTPONED_LEN) " - "not matched by the same number of ValkeyModule_SetReply*Len() " + "RedisModule_ReplyWith*(REDISMODULE_POSTPONED_LEN) " + "not matched by the same number of RedisModule_SetReply*Len() " "calls.", ctx->module->name); } @@ -825,13 +839,13 @@ void moduleFreeContext(ValkeyModuleCtx *ctx) { * If this context created a new client (e.g detached context), we free it. * If the client is assigned manually, e.g ctx->client = someClientInstance, * none of these flags will be set and we do not attempt to free it. */ - if (ctx->flags & VALKEYMODULE_CTX_TEMP_CLIENT) + if (ctx->flags & REDISMODULE_CTX_TEMP_CLIENT) moduleReleaseTempClient(ctx->client); - else if (ctx->flags & VALKEYMODULE_CTX_NEW_CLIENT) + else if (ctx->flags & REDISMODULE_CTX_NEW_CLIENT) freeClient(ctx->client); } -static CallReply *moduleParseReply(client *c, ValkeyModuleCtx *ctx) { +static CallReply *moduleParseReply(client *c, RedisModuleCtx *ctx) { /* Convert the result of the Redis command into a module reply. */ sds proto = sdsnewlen(c->buf,c->bufpos); c->bufpos = 0; @@ -847,15 +861,15 @@ static CallReply *moduleParseReply(client *c, ValkeyModuleCtx *ctx) { } void moduleCallCommandUnblockedHandler(client *c) { - ValkeyModuleCtx ctx; - ValkeyModuleAsyncRMCallPromise *promise = c->bstate.async_rm_call_handle; + RedisModuleCtx ctx; + RedisModuleAsyncRMCallPromise *promise = c->bstate.async_rm_call_handle; serverAssert(promise); - ValkeyModule *module = promise->module; + RedisModule *module = promise->module; if (!promise->on_unblocked) { moduleReleaseTempClient(c); return; /* module did not set any unblock callback. */ } - moduleCreateContext(&ctx, module, VALKEYMODULE_CTX_TEMP_CLIENT); + moduleCreateContext(&ctx, module, REDISMODULE_CTX_TEMP_CLIENT); selectDb(ctx.client, c->db->id); CallReply *reply = moduleParseReply(c, NULL); @@ -869,18 +883,18 @@ void moduleCallCommandUnblockedHandler(client *c) { /* Create a module ctx and keep track of the nesting level. * - * Note: When creating ctx for threads (VM_GetThreadSafeContext and - * VM_GetDetachedThreadSafeContext) we do not bump up the nesting level + * Note: When creating ctx for threads (RM_GetThreadSafeContext and + * RM_GetDetachedThreadSafeContext) we do not bump up the nesting level * because we only need to track of nesting level in the main thread * (only the main thread uses propagatePendingCommands) */ -void moduleCreateContext(ValkeyModuleCtx *out_ctx, ValkeyModule *module, int ctx_flags) { - memset(out_ctx, 0 ,sizeof(ValkeyModuleCtx)); - out_ctx->getapifuncptr = (void*)(unsigned long)&VM_GetApi; +void moduleCreateContext(RedisModuleCtx *out_ctx, RedisModule *module, int ctx_flags) { + memset(out_ctx, 0 ,sizeof(RedisModuleCtx)); + out_ctx->getapifuncptr = (void*)(unsigned long)&RM_GetApi; out_ctx->module = module; out_ctx->flags = ctx_flags; - if (ctx_flags & VALKEYMODULE_CTX_TEMP_CLIENT) + if (ctx_flags & REDISMODULE_CTX_TEMP_CLIENT) out_ctx->client = moduleAllocTempClient(); - else if (ctx_flags & VALKEYMODULE_CTX_NEW_CLIENT) + else if (ctx_flags & REDISMODULE_CTX_NEW_CLIENT) out_ctx->client = createClient(NULL); /* Calculate the initial yield time for long blocked contexts. @@ -902,17 +916,17 @@ void moduleCreateContext(ValkeyModuleCtx *out_ctx, ValkeyModule *module, int ctx * call() and in the latter we don't care about execution_nesting * 2. If we are running in a thread (execution_nesting will be dealt with * when locking/unlocking the GIL) */ - if (!(ctx_flags & (VALKEYMODULE_CTX_THREAD_SAFE|VALKEYMODULE_CTX_COMMAND))) { + if (!(ctx_flags & (REDISMODULE_CTX_THREAD_SAFE|REDISMODULE_CTX_COMMAND))) { enterExecutionUnit(1, 0); } } /* This Redis command binds the normal Redis command invocation with commands * exported by modules. */ -void ValkeyModuleCommandDispatcher(client *c) { - ValkeyModuleCommand *cp = c->cmd->module_cmd; - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, cp->module, VALKEYMODULE_CTX_COMMAND); +void RedisModuleCommandDispatcher(client *c) { + RedisModuleCommand *cp = c->cmd->module_cmd; + RedisModuleCtx ctx; + moduleCreateContext(&ctx, cp->module, REDISMODULE_CTX_COMMAND); ctx.client = c; cp->func(&ctx,(void**)c->argv,c->argc); @@ -943,18 +957,18 @@ void ValkeyModuleCommandDispatcher(client *c) { * * In order to accomplish its work, the module command is called, flagging * the context in a way that the command can recognize this is a special - * "get keys" call by calling ValkeyModule_IsKeysPositionRequest(ctx). */ + * "get keys" call by calling RedisModule_IsKeysPositionRequest(ctx). */ int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { - ValkeyModuleCommand *cp = cmd->module_cmd; - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, cp->module, VALKEYMODULE_CTX_KEYS_POS_REQUEST); + RedisModuleCommand *cp = cmd->module_cmd; + RedisModuleCtx ctx; + moduleCreateContext(&ctx, cp->module, REDISMODULE_CTX_KEYS_POS_REQUEST); /* Initialize getKeysResult */ getKeysPrepareResult(result, MAX_KEYS_BUFFER); ctx.keys_result = result; cp->func(&ctx,(void**)argv,argc); - /* We currently always use the array allocated by VM_KeyAtPos() and don't try + /* We currently always use the array allocated by RM_KeyAtPos() and don't try * to optimize for the pre-allocated buffer. */ moduleFreeContext(&ctx); @@ -965,16 +979,16 @@ int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, * moduleGetCommandKeysViaAPI, for modules that declare "getchannels-api" * during registration. Unlike keys, this is the only way to declare channels. */ int moduleGetCommandChannelsViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) { - ValkeyModuleCommand *cp = cmd->module_cmd; - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, cp->module, VALKEYMODULE_CTX_CHANNELS_POS_REQUEST); + RedisModuleCommand *cp = cmd->module_cmd; + RedisModuleCtx ctx; + moduleCreateContext(&ctx, cp->module, REDISMODULE_CTX_CHANNELS_POS_REQUEST); /* Initialize getKeysResult */ getKeysPrepareResult(result, MAX_KEYS_BUFFER); ctx.keys_result = result; cp->func(&ctx,(void**)argv,argc); - /* We currently always use the array allocated by VM_RM_ChannelAtPosWithFlags() and don't try + /* We currently always use the array allocated by RM_RM_ChannelAtPosWithFlags() and don't try * to optimize for the pre-allocated buffer. */ moduleFreeContext(&ctx); return result->numkeys; @@ -991,32 +1005,32 @@ int moduleGetCommandChannelsViaAPI(struct redisCommand *cmd, robj **argv, int ar /* Return non-zero if a module command, that was declared with the * flag "getkeys-api", is called in a special way to get the keys positions * and not to get executed. Otherwise zero is returned. */ -int VM_IsKeysPositionRequest(ValkeyModuleCtx *ctx) { - return (ctx->flags & VALKEYMODULE_CTX_KEYS_POS_REQUEST) != 0; +int RM_IsKeysPositionRequest(RedisModuleCtx *ctx) { + return (ctx->flags & REDISMODULE_CTX_KEYS_POS_REQUEST) != 0; } /* When a module command is called in order to obtain the position of * keys, since it was flagged as "getkeys-api" during the registration, * the command implementation checks for this special call using the - * ValkeyModule_IsKeysPositionRequest() API and uses this function in + * RedisModule_IsKeysPositionRequest() API and uses this function in * order to report keys. * - * The supported flags are the ones used by VM_SetCommandInfo, see VALKEYMODULE_CMD_KEY_*. + * The supported flags are the ones used by RM_SetCommandInfo, see REDISMODULE_CMD_KEY_*. * * * The following is an example of how it could be used: * - * if (ValkeyModule_IsKeysPositionRequest(ctx)) { - * ValkeyModule_KeyAtPosWithFlags(ctx, 2, VALKEYMODULE_CMD_KEY_RO | VALKEYMODULE_CMD_KEY_ACCESS); - * ValkeyModule_KeyAtPosWithFlags(ctx, 1, VALKEYMODULE_CMD_KEY_RW | VALKEYMODULE_CMD_KEY_UPDATE | VALKEYMODULE_CMD_KEY_ACCESS); + * if (RedisModule_IsKeysPositionRequest(ctx)) { + * RedisModule_KeyAtPosWithFlags(ctx, 2, REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_ACCESS); + * RedisModule_KeyAtPosWithFlags(ctx, 1, REDISMODULE_CMD_KEY_RW | REDISMODULE_CMD_KEY_UPDATE | REDISMODULE_CMD_KEY_ACCESS); * } * * Note: in the example above the get keys API could have been handled by key-specs (preferred). * Implementing the getkeys-api is required only when is it not possible to declare key-specs that cover all keys. * */ -void VM_KeyAtPosWithFlags(ValkeyModuleCtx *ctx, int pos, int flags) { - if (!(ctx->flags & VALKEYMODULE_CTX_KEYS_POS_REQUEST) || !ctx->keys_result) return; +void RM_KeyAtPosWithFlags(RedisModuleCtx *ctx, int pos, int flags) { + if (!(ctx->flags & REDISMODULE_CTX_KEYS_POS_REQUEST) || !ctx->keys_result) return; if (pos <= 0) return; getKeysResult *res = ctx->keys_result; @@ -1032,50 +1046,50 @@ void VM_KeyAtPosWithFlags(ValkeyModuleCtx *ctx, int pos, int flags) { res->numkeys++; } -/* This API existed before VM_KeyAtPosWithFlags was added, now deprecated and +/* This API existed before RM_KeyAtPosWithFlags was added, now deprecated and * can be used for compatibility with older versions, before key-specs and flags * were introduced. */ -void VM_KeyAtPos(ValkeyModuleCtx *ctx, int pos) { +void RM_KeyAtPos(RedisModuleCtx *ctx, int pos) { /* Default flags require full access */ int flags = moduleConvertKeySpecsFlags(CMD_KEY_FULL_ACCESS, 0); - VM_KeyAtPosWithFlags(ctx, pos, flags); + RM_KeyAtPosWithFlags(ctx, pos, flags); } /* Return non-zero if a module command, that was declared with the * flag "getchannels-api", is called in a special way to get the channel positions * and not to get executed. Otherwise zero is returned. */ -int VM_IsChannelsPositionRequest(ValkeyModuleCtx *ctx) { - return (ctx->flags & VALKEYMODULE_CTX_CHANNELS_POS_REQUEST) != 0; +int RM_IsChannelsPositionRequest(RedisModuleCtx *ctx) { + return (ctx->flags & REDISMODULE_CTX_CHANNELS_POS_REQUEST) != 0; } /* When a module command is called in order to obtain the position of * channels, since it was flagged as "getchannels-api" during the * registration, the command implementation checks for this special call - * using the ValkeyModule_IsChannelsPositionRequest() API and uses this + * using the RedisModule_IsChannelsPositionRequest() API and uses this * function in order to report the channels. * * The supported flags are: - * * VALKEYMODULE_CMD_CHANNEL_SUBSCRIBE: This command will subscribe to the channel. - * * VALKEYMODULE_CMD_CHANNEL_UNSUBSCRIBE: This command will unsubscribe from this channel. - * * VALKEYMODULE_CMD_CHANNEL_PUBLISH: This command will publish to this channel. - * * VALKEYMODULE_CMD_CHANNEL_PATTERN: Instead of acting on a specific channel, will act on any + * * 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 * channel specified by the pattern. This is the same access * 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: * - * if (ValkeyModule_IsChannelsPositionRequest(ctx)) { - * ValkeyModule_ChannelAtPosWithFlags(ctx, 1, VALKEYMODULE_CMD_CHANNEL_SUBSCRIBE | VALKEYMODULE_CMD_CHANNEL_PATTERN); - * ValkeyModule_ChannelAtPosWithFlags(ctx, 1, VALKEYMODULE_CMD_CHANNEL_PUBLISH); + * if (RedisModule_IsChannelsPositionRequest(ctx)) { + * RedisModule_ChannelAtPosWithFlags(ctx, 1, REDISMODULE_CMD_CHANNEL_SUBSCRIBE | REDISMODULE_CMD_CHANNEL_PATTERN); + * RedisModule_ChannelAtPosWithFlags(ctx, 1, REDISMODULE_CMD_CHANNEL_PUBLISH); * } * * Note: One usage of declaring channels is for evaluating ACL permissions. In this context, * unsubscribing is always allowed, so commands will only be checked against subscribe and - * publish permissions. This is preferred over using VM_ACLCheckChannelPermissions, since + * publish permissions. This is preferred over using RM_ACLCheckChannelPermissions, since * it allows the ACLs to be checked before the command is executed. */ -void VM_ChannelAtPosWithFlags(ValkeyModuleCtx *ctx, int pos, int flags) { - if (!(ctx->flags & VALKEYMODULE_CTX_CHANNELS_POS_REQUEST) || !ctx->keys_result) return; +void RM_ChannelAtPosWithFlags(RedisModuleCtx *ctx, int pos, int flags) { + if (!(ctx->flags & REDISMODULE_CTX_CHANNELS_POS_REQUEST) || !ctx->keys_result) return; if (pos <= 0) return; getKeysResult *res = ctx->keys_result; @@ -1087,10 +1101,10 @@ void VM_ChannelAtPosWithFlags(ValkeyModuleCtx *ctx, int pos, int flags) { } int new_flags = 0; - if (flags & VALKEYMODULE_CMD_CHANNEL_SUBSCRIBE) new_flags |= CMD_CHANNEL_SUBSCRIBE; - if (flags & VALKEYMODULE_CMD_CHANNEL_UNSUBSCRIBE) new_flags |= CMD_CHANNEL_UNSUBSCRIBE; - if (flags & VALKEYMODULE_CMD_CHANNEL_PUBLISH) new_flags |= CMD_CHANNEL_PUBLISH; - if (flags & VALKEYMODULE_CMD_CHANNEL_PATTERN) new_flags |= CMD_CHANNEL_PATTERN; + if (flags & REDISMODULE_CMD_CHANNEL_SUBSCRIBE) new_flags |= CMD_CHANNEL_SUBSCRIBE; + if (flags & REDISMODULE_CMD_CHANNEL_UNSUBSCRIBE) new_flags |= CMD_CHANNEL_UNSUBSCRIBE; + if (flags & REDISMODULE_CMD_CHANNEL_PUBLISH) new_flags |= CMD_CHANNEL_PUBLISH; + if (flags & REDISMODULE_CMD_CHANNEL_PATTERN) new_flags |= CMD_CHANNEL_PATTERN; res->keys[res->numkeys].pos = pos; res->keys[res->numkeys].flags = new_flags; @@ -1117,7 +1131,7 @@ int isCommandNameValid(const char *name) { return 1; } -/* Helper for VM_CreateCommand(). Turns a string representing command +/* Helper for RM_CreateCommand(). Turns a string representing command * flags into the command flags used by the Redis core. * * It returns the set of flags, or -1 if unknown flags are found. */ @@ -1154,29 +1168,29 @@ int64_t commandFlagsFromString(char *s) { return flags; } -ValkeyModuleCommand *moduleCreateCommandProxy(struct ValkeyModule *module, sds declared_name, sds fullname, ValkeyModuleCmdFunc cmdfunc, int64_t flags, int firstkey, int lastkey, int keystep); +RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds declared_name, sds fullname, RedisModuleCmdFunc cmdfunc, int64_t flags, int firstkey, int lastkey, int keystep); /* Register a new command in the Redis server, that will be handled by - * calling the function pointer 'cmdfunc' using the ValkeyModule calling + * calling the function pointer 'cmdfunc' using the RedisModule calling * convention. * - * The function returns VALKEYMODULE_ERR in these cases: - * - If creation of module command is called outside the ValkeyModule_OnLoad. + * The function returns REDISMODULE_ERR in these cases: + * - If creation of module command is called outside the RedisModule_OnLoad. * - The specified command is already busy. * - The command name contains some chars that are not allowed. * - A set of invalid flags were passed. * - * Otherwise VALKEYMODULE_OK is returned and the new command is registered. + * Otherwise REDISMODULE_OK is returned and the new command is registered. * * This function must be called during the initialization of the module - * inside the ValkeyModule_OnLoad() function. Calling this function outside + * inside the RedisModule_OnLoad() function. Calling this function outside * of the initialization function is not defined. * * The command function type is the following: * - * int MyCommand_RedisCommand(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc); + * int MyCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); * - * And is supposed to always return VALKEYMODULE_OK. + * And is supposed to always return REDISMODULE_OK. * * The set of flags 'strflags' specify the behavior of the command, and should * be passed as a C string composed of space separated words, like for @@ -1228,7 +1242,7 @@ ValkeyModuleCommand *moduleCreateCommandProxy(struct ValkeyModule *module, sds d * * **"blocking"**: The command has the potential to block the client. * * **"allow-busy"**: Permit the command while the server is blocked either by * a script or by a slow module command, see - * VM_Yield. + * RM_Yield. * * **"getchannels-api"**: The command implements the interface to return * the arguments that are channels. * @@ -1250,32 +1264,32 @@ ValkeyModuleCommand *moduleCreateCommandProxy(struct ValkeyModule *module, sds d * NOTE: The scheme described above serves a limited purpose and can * only be used to find keys that exist at constant indices. * For non-trivial key arguments, you may pass 0,0,0 and use - * ValkeyModule_SetCommandInfo to set key specs using a more advanced scheme and use - * ValkeyModule_SetCommandACLCategories to set Redis ACL categories of the commands. */ -int VM_CreateCommand(ValkeyModuleCtx *ctx, const char *name, ValkeyModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) { + * RedisModule_SetCommandInfo to set key specs using a more advanced scheme and use + * RedisModule_SetCommandACLCategories to set Redis ACL categories of the commands. */ +int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) { if (!ctx->module->onload) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; int64_t flags = strflags ? commandFlagsFromString((char*)strflags) : 0; - if (flags == -1) return VALKEYMODULE_ERR; + if (flags == -1) return REDISMODULE_ERR; if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; /* Check if the command name is valid. */ if (!isCommandNameValid(name)) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; /* Check if the command name is busy. */ if (lookupCommandByCString(name) != NULL) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; sds declared_name = sdsnew(name); - ValkeyModuleCommand *cp = moduleCreateCommandProxy(ctx->module, declared_name, sdsdup(declared_name), cmdfunc, flags, firstkey, lastkey, keystep); + RedisModuleCommand *cp = moduleCreateCommandProxy(ctx->module, declared_name, sdsdup(declared_name), cmdfunc, flags, firstkey, lastkey, keystep); cp->rediscmd->arity = cmdfunc ? -1 : -2; /* Default value, can be changed later via dedicated API */ serverAssert(dictAdd(server.commands, sdsdup(declared_name), cp->rediscmd) == DICT_OK); serverAssert(dictAdd(server.orig_commands, sdsdup(declared_name), cp->rediscmd) == DICT_OK); cp->rediscmd->id = ACLGetCommandID(declared_name); /* ID used for ACL. */ - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* A proxy that help create a module command / subcommand. @@ -1285,9 +1299,9 @@ int VM_CreateCommand(ValkeyModuleCtx *ctx, const char *name, ValkeyModuleCmdFunc * * Function will take the ownership of both 'declared_name' and 'fullname' SDS. */ -ValkeyModuleCommand *moduleCreateCommandProxy(struct ValkeyModule *module, sds declared_name, sds fullname, ValkeyModuleCmdFunc cmdfunc, int64_t flags, int firstkey, int lastkey, int keystep) { +RedisModuleCommand *moduleCreateCommandProxy(struct RedisModule *module, sds declared_name, sds fullname, RedisModuleCmdFunc cmdfunc, int64_t flags, int firstkey, int lastkey, int keystep) { struct redisCommand *rediscmd; - ValkeyModuleCommand *cp; + RedisModuleCommand *cp; /* Create a command "proxy", which is a structure that is referenced * in the command table, so that the generic command that works as @@ -1300,7 +1314,7 @@ ValkeyModuleCommand *moduleCreateCommandProxy(struct ValkeyModule *module, sds d cp->rediscmd->declared_name = declared_name; /* SDS for module commands */ cp->rediscmd->fullname = fullname; cp->rediscmd->group = COMMAND_GROUP_MODULE; - cp->rediscmd->proc = ValkeyModuleCommandDispatcher; + cp->rediscmd->proc = RedisModuleCommandDispatcher; cp->rediscmd->flags = flags | CMD_MODULE; cp->rediscmd->module_cmd = cp; if (firstkey != 0) { @@ -1336,101 +1350,101 @@ ValkeyModuleCommand *moduleCreateCommandProxy(struct ValkeyModule *module, sds d * * The command is not a module command * * The command doesn't belong to the calling module */ -ValkeyModuleCommand *VM_GetCommand(ValkeyModuleCtx *ctx, const char *name) { +RedisModuleCommand *RM_GetCommand(RedisModuleCtx *ctx, const char *name) { struct redisCommand *cmd = lookupCommandByCString(name); if (!cmd || !(cmd->flags & CMD_MODULE)) return NULL; - ValkeyModuleCommand *cp = cmd->module_cmd; + RedisModuleCommand *cp = cmd->module_cmd; if (cp->module != ctx->module) return NULL; return cp; } -/* Very similar to ValkeyModule_CreateCommand except that it is used to create +/* Very similar to RedisModule_CreateCommand except that it is used to create * a subcommand, associated with another, container, command. * * Example: If a module has a configuration command, MODULE.CONFIG, then * GET and SET should be individual subcommands, while MODULE.CONFIG is * a command, but should not be registered with a valid `funcptr`: * - * if (ValkeyModule_CreateCommand(ctx,"module.config",NULL,"",0,0,0) == VALKEYMODULE_ERR) - * return VALKEYMODULE_ERR; + * if (RedisModule_CreateCommand(ctx,"module.config",NULL,"",0,0,0) == REDISMODULE_ERR) + * return REDISMODULE_ERR; * - * ValkeyModuleCommand *parent = ValkeyModule_GetCommand(ctx,,"module.config"); + * RedisModuleCommand *parent = RedisModule_GetCommand(ctx,,"module.config"); * - * if (ValkeyModule_CreateSubcommand(parent,"set",cmd_config_set,"",0,0,0) == VALKEYMODULE_ERR) - * return VALKEYMODULE_ERR; + * if (RedisModule_CreateSubcommand(parent,"set",cmd_config_set,"",0,0,0) == REDISMODULE_ERR) + * return REDISMODULE_ERR; * - * if (ValkeyModule_CreateSubcommand(parent,"get",cmd_config_get,"",0,0,0) == VALKEYMODULE_ERR) - * return VALKEYMODULE_ERR; + * if (RedisModule_CreateSubcommand(parent,"get",cmd_config_get,"",0,0,0) == REDISMODULE_ERR) + * return REDISMODULE_ERR; * - * Returns VALKEYMODULE_OK on success and VALKEYMODULE_ERR in case of the following errors: + * Returns REDISMODULE_OK on success and REDISMODULE_ERR in case of the following errors: * * * Error while parsing `strflags` * * Command is marked as `no-cluster` but cluster mode is enabled * * `parent` is already a subcommand (we do not allow more than one level of command nesting) - * * `parent` is a command with an implementation (ValkeyModuleCmdFunc) (A parent command should be a pure container of subcommands) + * * `parent` is a command with an implementation (RedisModuleCmdFunc) (A parent command should be a pure container of subcommands) * * `parent` already has a subcommand called `name` - * * Creating a subcommand is called outside of ValkeyModule_OnLoad. + * * Creating a subcommand is called outside of RedisModule_OnLoad. */ -int VM_CreateSubcommand(ValkeyModuleCommand *parent, const char *name, ValkeyModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) { +int RM_CreateSubcommand(RedisModuleCommand *parent, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) { if (!parent->module->onload) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; int64_t flags = strflags ? commandFlagsFromString((char*)strflags) : 0; - if (flags == -1) return VALKEYMODULE_ERR; + if (flags == -1) return REDISMODULE_ERR; if ((flags & CMD_MODULE_NO_CLUSTER) && server.cluster_enabled) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; struct redisCommand *parent_cmd = parent->rediscmd; if (parent_cmd->parent) - return VALKEYMODULE_ERR; /* We don't allow more than one level of subcommands */ + return REDISMODULE_ERR; /* We don't allow more than one level of subcommands */ - ValkeyModuleCommand *parent_cp = parent_cmd->module_cmd; + RedisModuleCommand *parent_cp = parent_cmd->module_cmd; if (parent_cp->func) - return VALKEYMODULE_ERR; /* A parent command should be a pure container of subcommands */ + return REDISMODULE_ERR; /* A parent command should be a pure container of subcommands */ /* Check if the command name is valid. */ if (!isCommandNameValid(name)) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; /* Check if the command name is busy within the parent command. */ sds declared_name = sdsnew(name); if (parent_cmd->subcommands_dict && lookupSubcommand(parent_cmd, declared_name) != NULL) { sdsfree(declared_name); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } sds fullname = catSubCommandFullname(parent_cmd->fullname, name); - ValkeyModuleCommand *cp = moduleCreateCommandProxy(parent->module, declared_name, fullname, cmdfunc, flags, firstkey, lastkey, keystep); + RedisModuleCommand *cp = moduleCreateCommandProxy(parent->module, declared_name, fullname, cmdfunc, flags, firstkey, lastkey, keystep); cp->rediscmd->arity = -2; commandAddSubcommand(parent_cmd, cp->rediscmd, name); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Accessors of array elements of structs where the element size is stored * separately in the version struct. */ -static ValkeyModuleCommandHistoryEntry * -moduleCmdHistoryEntryAt(const ValkeyModuleCommandInfoVersion *version, - ValkeyModuleCommandHistoryEntry *entries, int index) { +static RedisModuleCommandHistoryEntry * +moduleCmdHistoryEntryAt(const RedisModuleCommandInfoVersion *version, + RedisModuleCommandHistoryEntry *entries, int index) { off_t offset = index * version->sizeof_historyentry; - return (ValkeyModuleCommandHistoryEntry *)((char *)(entries) + offset); + return (RedisModuleCommandHistoryEntry *)((char *)(entries) + offset); } -static ValkeyModuleCommandKeySpec * -moduleCmdKeySpecAt(const ValkeyModuleCommandInfoVersion *version, - ValkeyModuleCommandKeySpec *keyspecs, int index) { +static RedisModuleCommandKeySpec * +moduleCmdKeySpecAt(const RedisModuleCommandInfoVersion *version, + RedisModuleCommandKeySpec *keyspecs, int index) { off_t offset = index * version->sizeof_keyspec; - return (ValkeyModuleCommandKeySpec *)((char *)(keyspecs) + offset); + return (RedisModuleCommandKeySpec *)((char *)(keyspecs) + offset); } -static ValkeyModuleCommandArg * -moduleCmdArgAt(const ValkeyModuleCommandInfoVersion *version, - const ValkeyModuleCommandArg *args, int index) { +static RedisModuleCommandArg * +moduleCmdArgAt(const RedisModuleCommandInfoVersion *version, + const RedisModuleCommandArg *args, int index) { off_t offset = index * version->sizeof_arg; - return (ValkeyModuleCommandArg *)((char *)(args) + offset); + return (RedisModuleCommandArg *)((char *)(args) + offset); } /* Recursively populate the args structure (setting num_args to the number of @@ -1448,6 +1462,45 @@ int populateArgsStructure(struct redisCommandArg *args) { return count; } +/* 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. + * Any module can register a command to any added categories using RedisModule_SetCommandACLCategories. + * + * Returns: + * - 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. + * - ENOMEM if the number of categories reached the max limit of 64 categories. + */ +int RM_AddACLCategory(RedisModuleCtx *ctx, const char *name) { + if (!ctx->module->onload) { + errno = EINVAL; + return REDISMODULE_ERR; + } + + if (moduleVerifyResourceName(name) == REDISMODULE_ERR) { + errno = EINVAL; + return REDISMODULE_ERR; + } + + if (ACLGetCommandCategoryFlagByName(name)) { + errno = EBUSY; + return REDISMODULE_ERR; + } + + if (ACLAddCommandCategory(name, 0)) { + ctx->module->num_acl_categories_added++; + return REDISMODULE_OK; + } else { + errno = ENOMEM; + return REDISMODULE_ERR; + } +} + /* Helper for categoryFlagsFromString(). Attempts to find an acl flag representing the provided flag string * and adds that flag to acl_categories_flags if a match is found. * @@ -1462,7 +1515,7 @@ int matchAclCategoryFlag(char *flag, int64_t *acl_categories_flags) { return 0; /* Unrecognized */ } -/* Helper for VM_SetCommandACLCategories(). Turns a string representing acl category +/* 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 * the module commands by acl categories. * @@ -1483,26 +1536,26 @@ int64_t categoryFlagsFromString(char *aclflags) { return acl_categories_flags; } -/* ValkeyModule_SetCommandACLCategories can be used to set ACL categories to module +/* 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 * slow ACL categories. * - * On success VALKEYMODULE_OK is returned. On error VALKEYMODULE_ERR is returned. + * On success REDISMODULE_OK is returned. On error REDISMODULE_ERR is returned. * - * This function can only be called during the ValkeyModule_OnLoad function. If called + * This function can only be called during the RedisModule_OnLoad function. If called * outside of this function, an error is returned. */ -int VM_SetCommandACLCategories(ValkeyModuleCommand *command, const char *aclflags) { - if (!command || !command->module || !command->module->onload) return VALKEYMODULE_ERR; +int RM_SetCommandACLCategories(RedisModuleCommand *command, const char *aclflags) { + if (!command || !command->module || !command->module->onload) return REDISMODULE_ERR; int64_t categories_flags = aclflags ? categoryFlagsFromString((char*)aclflags) : 0; - if (categories_flags == -1) return VALKEYMODULE_ERR; + if (categories_flags == -1) return REDISMODULE_ERR; struct redisCommand *rcmd = command->rediscmd; rcmd->acl_categories = categories_flags; /* ACL categories flags for module command */ command->module->num_commands_with_acl_categories++; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Set additional command information. @@ -1511,26 +1564,26 @@ int VM_SetCommandACLCategories(ValkeyModuleCommand *command, const char *aclflag * ACL and is used to filter commands with the wrong number of arguments before * the call reaches the module code. * - * This function can be called after creating a command using VM_CreateCommand - * and fetching the command pointer using VM_GetCommand. The information can + * This function can be called after creating a command using RM_CreateCommand + * and fetching the command pointer using RM_GetCommand. The information can * only be set once for each command and has the following structure: * - * typedef struct ValkeyModuleCommandInfo { - * const ValkeyModuleCommandInfoVersion *version; + * typedef struct RedisModuleCommandInfo { + * const RedisModuleCommandInfoVersion *version; * const char *summary; * const char *complexity; * const char *since; - * ValkeyModuleCommandHistoryEntry *history; + * RedisModuleCommandHistoryEntry *history; * const char *tips; * int arity; - * ValkeyModuleCommandKeySpec *key_specs; - * ValkeyModuleCommandArg *args; - * } ValkeyModuleCommandInfo; + * RedisModuleCommandKeySpec *key_specs; + * RedisModuleCommandArg *args; + * } RedisModuleCommandInfo; * * All fields except `version` are optional. Explanation of the fields: * * - `version`: This field enables compatibility with different Redis versions. - * Always set this field to VALKEYMODULE_COMMAND_INFO_VERSION. + * Always set this field to REDISMODULE_COMMAND_INFO_VERSION. * * - `summary`: A short description of the command (optional). * @@ -1539,7 +1592,7 @@ int VM_SetCommandACLCategories(ValkeyModuleCommand *command, const char *aclflag * - `since`: The version where the command was introduced (optional). * Note: The version specified should be the module's, not Redis version. * - * - `history`: An array of ValkeyModuleCommandHistoryEntry (optional), which is + * - `history`: An array of RedisModuleCommandHistoryEntry (optional), which is * a struct with the following fields: * * const char *since; @@ -1560,9 +1613,9 @@ int VM_SetCommandACLCategories(ValkeyModuleCommand *command, const char *aclflag * omitted arity field) is equivalent to -2 if the command has sub commands * and -1 otherwise. * - * - `key_specs`: An array of ValkeyModuleCommandKeySpec, terminated by an + * - `key_specs`: An array of RedisModuleCommandKeySpec, terminated by an * element memset to zero. This is a scheme that tries to describe the - * positions of key arguments better than the old VM_CreateCommand arguments + * positions of key arguments better than the old RM_CreateCommand arguments * `firstkey`, `lastkey`, `keystep` and is needed if those three are not * enough to describe the key positions. There are two steps to retrieve key * positions: *begin search* (BS) in which index should find the first key and @@ -1570,21 +1623,21 @@ int VM_SetCommandACLCategories(ValkeyModuleCommand *command, const char *aclflag * will which arguments are keys. Additionally, there are key specific flags. * * Key-specs cause the triplet (firstkey, lastkey, keystep) given in - * VM_CreateCommand to be recomputed, but it is still useful to provide - * these three parameters in VM_CreateCommand, to better support old Redis - * versions where VM_SetCommandInfo is not available. + * RM_CreateCommand to be recomputed, but it is still useful to provide + * these three parameters in RM_CreateCommand, to better support old Redis + * versions where RM_SetCommandInfo is not available. * * Note that key-specs don't fully replace the "getkeys-api" (see - * VM_CreateCommand, VM_IsKeysPositionRequest and VM_KeyAtPosWithFlags) so + * RM_CreateCommand, RM_IsKeysPositionRequest and RM_KeyAtPosWithFlags) so * it may be a good idea to supply both key-specs and implement the * getkeys-api. * * A key-spec has the following structure: * - * typedef struct ValkeyModuleCommandKeySpec { + * typedef struct RedisModuleCommandKeySpec { * const char *notes; * uint64_t flags; - * ValkeyModuleKeySpecBeginSearchType begin_search_type; + * RedisModuleKeySpecBeginSearchType begin_search_type; * union { * struct { * int pos; @@ -1594,7 +1647,7 @@ int VM_SetCommandACLCategories(ValkeyModuleCommand *command, const char *aclflag * int startfrom; * } keyword; * } bs; - * ValkeyModuleKeySpecFindKeysType find_keys_type; + * RedisModuleKeySpecFindKeysType find_keys_type; * union { * struct { * int lastkey; @@ -1607,9 +1660,9 @@ int VM_SetCommandACLCategories(ValkeyModuleCommand *command, const char *aclflag * int keystep; * } keynum; * } fk; - * } ValkeyModuleCommandKeySpec; + * } RedisModuleCommandKeySpec; * - * Explanation of the fields of ValkeyModuleCommandKeySpec: + * Explanation of the fields of RedisModuleCommandKeySpec: * * * `notes`: Optional notes or clarifications about this key spec. * @@ -1618,34 +1671,34 @@ int VM_SetCommandACLCategories(ValkeyModuleCommand *command, const char *aclflag * * `begin_search_type`: This describes how the first key is discovered. * There are two ways to determine the first key: * - * * `VALKEYMODULE_KSPEC_BS_UNKNOWN`: There is no way to tell where the + * * `REDISMODULE_KSPEC_BS_UNKNOWN`: There is no way to tell where the * key args start. - * * `VALKEYMODULE_KSPEC_BS_INDEX`: Key args start at a constant index. - * * `VALKEYMODULE_KSPEC_BS_KEYWORD`: Key args start just after a + * * `REDISMODULE_KSPEC_BS_INDEX`: Key args start at a constant index. + * * `REDISMODULE_KSPEC_BS_KEYWORD`: Key args start just after a * specific keyword. * * * `bs`: This is a union in which the `index` or `keyword` branch is used * depending on the value of the `begin_search_type` field. * * * `bs.index.pos`: The index from which we start the search for keys. - * (`VALKEYMODULE_KSPEC_BS_INDEX` only.) + * (`REDISMODULE_KSPEC_BS_INDEX` only.) * * * `bs.keyword.keyword`: The keyword (string) that indicates the - * beginning of key arguments. (`VALKEYMODULE_KSPEC_BS_KEYWORD` only.) + * beginning of key arguments. (`REDISMODULE_KSPEC_BS_KEYWORD` only.) * * * `bs.keyword.startfrom`: An index in argv from which to start * searching. Can be negative, which means start search from the end, * in reverse. Example: -2 means to start in reverse from the - * penultimate argument. (`VALKEYMODULE_KSPEC_BS_KEYWORD` only.) + * penultimate argument. (`REDISMODULE_KSPEC_BS_KEYWORD` only.) * * * `find_keys_type`: After the "begin search", this describes which * arguments are keys. The strategies are: * - * * `VALKEYMODULE_KSPEC_BS_UNKNOWN`: There is no way to tell where the + * * `REDISMODULE_KSPEC_BS_UNKNOWN`: There is no way to tell where the * key args are located. - * * `VALKEYMODULE_KSPEC_FK_RANGE`: Keys end at a specific index (or + * * `REDISMODULE_KSPEC_FK_RANGE`: Keys end at a specific index (or * relative to the last argument). - * * `VALKEYMODULE_KSPEC_FK_KEYNUM`: There's an argument that contains + * * `REDISMODULE_KSPEC_FK_KEYNUM`: There's an argument that contains * the number of key args somewhere before the keys themselves. * * `find_keys_type` and `fk` can be omitted if this keyspec describes @@ -1654,7 +1707,7 @@ int VM_SetCommandACLCategories(ValkeyModuleCommand *command, const char *aclflag * * `fk`: This is a union in which the `range` or `keynum` branch is used * depending on the value of the `find_keys_type` field. * - * * `fk.range` (for `VALKEYMODULE_KSPEC_FK_RANGE`): A struct with the + * * `fk.range` (for `REDISMODULE_KSPEC_FK_RANGE`): A struct with the * following fields: * * * `lastkey`: Index of the last key relative to the result of the @@ -1669,7 +1722,7 @@ int VM_SetCommandACLCategories(ValkeyModuleCommand *command, const char *aclflag * by a factor. 0 and 1 mean no limit. 2 means 1/2 of the * remaining args, 3 means 1/3, and so on. * - * * `fk.keynum` (for `VALKEYMODULE_KSPEC_FK_KEYNUM`): A struct with the + * * `fk.keynum` (for `REDISMODULE_KSPEC_FK_KEYNUM`): A struct with the * following fields: * * * `keynumidx`: Index of the argument containing the number of @@ -1690,16 +1743,16 @@ int VM_SetCommandACLCategories(ValkeyModuleCommand *command, const char *aclflag * that's not distinctly deletion, overwrite or read-only would be marked as * RW. * - * * `VALKEYMODULE_CMD_KEY_RO`: Read-Only. Reads the value of the key, but + * * `REDISMODULE_CMD_KEY_RO`: Read-Only. Reads the value of the key, but * doesn't necessarily return it. * - * * `VALKEYMODULE_CMD_KEY_RW`: Read-Write. Modifies the data stored in the + * * `REDISMODULE_CMD_KEY_RW`: Read-Write. Modifies the data stored in the * value of the key or its metadata. * - * * `VALKEYMODULE_CMD_KEY_OW`: Overwrite. Overwrites the data stored in the + * * `REDISMODULE_CMD_KEY_OW`: Overwrite. Overwrites the data stored in the * value of the key. * - * * `VALKEYMODULE_CMD_KEY_RM`: Deletes the key. + * * `REDISMODULE_CMD_KEY_RM`: Deletes the key. * * The next four refer to *user data inside the value of the key*, not the * metadata like LRU, type, cardinality. It refers to the logical operation @@ -1709,107 +1762,107 @@ int VM_SetCommandACLCategories(ValkeyModuleCommand *command, const char *aclflag * combined with one of the write operations INSERT, DELETE or UPDATE. Any * write that's not an INSERT or a DELETE would be UPDATE. * - * * `VALKEYMODULE_CMD_KEY_ACCESS`: Returns, copies or uses the user data + * * `REDISMODULE_CMD_KEY_ACCESS`: Returns, copies or uses the user data * from the value of the key. * - * * `VALKEYMODULE_CMD_KEY_UPDATE`: Updates data to the value, new value may + * * `REDISMODULE_CMD_KEY_UPDATE`: Updates data to the value, new value may * depend on the old value. * - * * `VALKEYMODULE_CMD_KEY_INSERT`: Adds data to the value with no chance of + * * `REDISMODULE_CMD_KEY_INSERT`: Adds data to the value with no chance of * modification or deletion of existing data. * - * * `VALKEYMODULE_CMD_KEY_DELETE`: Explicitly deletes some content from the + * * `REDISMODULE_CMD_KEY_DELETE`: Explicitly deletes some content from the * value of the key. * * Other flags: * - * * `VALKEYMODULE_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. * - * * `VALKEYMODULE_CMD_KEY_INCOMPLETE`: The keyspec might not point out all + * * `REDISMODULE_CMD_KEY_INCOMPLETE`: The keyspec might not point out all * the keys it should cover. * - * * `VALKEYMODULE_CMD_KEY_VARIABLE_FLAGS`: Some keys might have different + * * `REDISMODULE_CMD_KEY_VARIABLE_FLAGS`: Some keys might have different * flags depending on arguments. * - * - `args`: An array of ValkeyModuleCommandArg, terminated by an element memset - * to zero. ValkeyModuleCommandArg is a structure with at the fields described + * - `args`: An array of RedisModuleCommandArg, terminated by an element memset + * to zero. RedisModuleCommandArg is a structure with at the fields described * below. * - * typedef struct ValkeyModuleCommandArg { + * typedef struct RedisModuleCommandArg { * const char *name; - * ValkeyModuleCommandArgType type; + * RedisModuleCommandArgType type; * int key_spec_index; * const char *token; * const char *summary; * const char *since; * int flags; - * struct ValkeyModuleCommandArg *subargs; - * } ValkeyModuleCommandArg; + * struct RedisModuleCommandArg *subargs; + * } RedisModuleCommandArg; * * Explanation of the fields: * * * `name`: Name of the argument. * * * `type`: The type of the argument. See below for details. The types - * `VALKEYMODULE_ARG_TYPE_ONEOF` and `VALKEYMODULE_ARG_TYPE_BLOCK` require + * `REDISMODULE_ARG_TYPE_ONEOF` and `REDISMODULE_ARG_TYPE_BLOCK` require * an argument to have sub-arguments, i.e. `subargs`. * - * * `key_spec_index`: If the `type` is `VALKEYMODULE_ARG_TYPE_KEY` you must + * * `key_spec_index`: If the `type` is `REDISMODULE_ARG_TYPE_KEY` you must * provide the index of the key-spec associated with this argument. See * `key_specs` above. If the argument is not a key, you may specify -1. * * * `token`: The token preceding the argument (optional). Example: the * argument `seconds` in `SET` has a token `EX`. If the argument consists * of only a token (for example `NX` in `SET`) the type should be - * `VALKEYMODULE_ARG_TYPE_PURE_TOKEN` and `value` should be NULL. + * `REDISMODULE_ARG_TYPE_PURE_TOKEN` and `value` should be NULL. * * * `summary`: A short description of the argument (optional). * * * `since`: The first version which included this argument (optional). * - * * `flags`: A bitwise or of the macros `VALKEYMODULE_CMD_ARG_*`. See below. + * * `flags`: A bitwise or of the macros `REDISMODULE_CMD_ARG_*`. See below. * * * `value`: The display-value of the argument. This string is what should * be displayed when creating the command syntax from the output of * `COMMAND`. If `token` is not NULL, it should also be displayed. * - * Explanation of `ValkeyModuleCommandArgType`: + * Explanation of `RedisModuleCommandArgType`: * - * * `VALKEYMODULE_ARG_TYPE_STRING`: String argument. - * * `VALKEYMODULE_ARG_TYPE_INTEGER`: Integer argument. - * * `VALKEYMODULE_ARG_TYPE_DOUBLE`: Double-precision float argument. - * * `VALKEYMODULE_ARG_TYPE_KEY`: String argument representing a keyname. - * * `VALKEYMODULE_ARG_TYPE_PATTERN`: String, but regex pattern. - * * `VALKEYMODULE_ARG_TYPE_UNIX_TIME`: Integer, but Unix timestamp. - * * `VALKEYMODULE_ARG_TYPE_PURE_TOKEN`: Argument doesn't have a placeholder. + * * `REDISMODULE_ARG_TYPE_STRING`: String argument. + * * `REDISMODULE_ARG_TYPE_INTEGER`: Integer argument. + * * `REDISMODULE_ARG_TYPE_DOUBLE`: Double-precision float argument. + * * `REDISMODULE_ARG_TYPE_KEY`: String argument representing a keyname. + * * `REDISMODULE_ARG_TYPE_PATTERN`: String, but regex pattern. + * * `REDISMODULE_ARG_TYPE_UNIX_TIME`: Integer, but Unix timestamp. + * * `REDISMODULE_ARG_TYPE_PURE_TOKEN`: Argument doesn't have a placeholder. * It's just a token without a value. Example: the `KEEPTTL` option of the * `SET` command. - * * `VALKEYMODULE_ARG_TYPE_ONEOF`: Used when the user can choose only one of + * * `REDISMODULE_ARG_TYPE_ONEOF`: Used when the user can choose only one of * a few sub-arguments. Requires `subargs`. Example: the `NX` and `XX` * options of `SET`. - * * `VALKEYMODULE_ARG_TYPE_BLOCK`: Used when one wants to group together + * * `REDISMODULE_ARG_TYPE_BLOCK`: Used when one wants to group together * several sub-arguments, usually to apply something on all of them, like * making the entire group "optional". Requires `subargs`. Example: the * `LIMIT offset count` parameters in `ZRANGE`. * * Explanation of the command argument flags: * - * * `VALKEYMODULE_CMD_ARG_OPTIONAL`: The argument is optional (like GET in + * * `REDISMODULE_CMD_ARG_OPTIONAL`: The argument is optional (like GET in * the SET command). - * * `VALKEYMODULE_CMD_ARG_MULTIPLE`: The argument may repeat itself (like + * * `REDISMODULE_CMD_ARG_MULTIPLE`: The argument may repeat itself (like * key in DEL). - * * `VALKEYMODULE_CMD_ARG_MULTIPLE_TOKEN`: The argument may repeat itself, + * * `REDISMODULE_CMD_ARG_MULTIPLE_TOKEN`: The argument may repeat itself, * and so does its token (like `GET pattern` in SORT). * - * On success VALKEYMODULE_OK is returned. On error VALKEYMODULE_ERR is returned + * On success REDISMODULE_OK is returned. On error REDISMODULE_ERR is returned * and `errno` is set to EINVAL if invalid info was provided or EEXIST if info * has already been set. If the info is invalid, a warning is logged explaining * which part of the info is invalid and why. */ -int VM_SetCommandInfo(ValkeyModuleCommand *command, const ValkeyModuleCommandInfo *info) { +int RM_SetCommandInfo(RedisModuleCommand *command, const RedisModuleCommandInfo *info) { if (!moduleValidateCommandInfo(info)) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } struct redisCommand *cmd = command->rediscmd; @@ -1824,14 +1877,14 @@ int VM_SetCommandInfo(ValkeyModuleCommand *command, const ValkeyModuleCommandInf cmd->key_specs[0].begin_search_type == KSPEC_BS_INDEX && cmd->key_specs[0].find_keys_type == KSPEC_FK_RANGE))) { errno = EEXIST; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } if (info->summary) cmd->summary = zstrdup(info->summary); if (info->complexity) cmd->complexity = zstrdup(info->complexity); if (info->since) cmd->since = zstrdup(info->since); - const ValkeyModuleCommandInfoVersion *version = info->version; + const RedisModuleCommandInfoVersion *version = info->version; if (info->history) { size_t count = 0; while (moduleCmdHistoryEntryAt(version, info->history, count)->since) @@ -1839,7 +1892,7 @@ int VM_SetCommandInfo(ValkeyModuleCommand *command, const ValkeyModuleCommandInf serverAssert(count < SIZE_MAX / sizeof(commandHistory)); cmd->history = zmalloc(sizeof(commandHistory) * (count + 1)); for (size_t j = 0; j < count; j++) { - ValkeyModuleCommandHistoryEntry *entry = + RedisModuleCommandHistoryEntry *entry = moduleCmdHistoryEntryAt(version, info->history, j); cmd->history[j].since = zstrdup(entry->since); cmd->history[j].changes = zstrdup(entry->changes); @@ -1874,22 +1927,22 @@ int VM_SetCommandInfo(ValkeyModuleCommand *command, const ValkeyModuleCommandInf zfree(cmd->key_specs); cmd->key_specs = zmalloc(sizeof(keySpec) * count); - /* Copy the contents of the ValkeyModuleCommandKeySpec array. */ + /* Copy the contents of the RedisModuleCommandKeySpec array. */ cmd->key_specs_num = count; for (size_t j = 0; j < count; j++) { - ValkeyModuleCommandKeySpec *spec = + RedisModuleCommandKeySpec *spec = moduleCmdKeySpecAt(version, info->key_specs, j); cmd->key_specs[j].notes = spec->notes ? zstrdup(spec->notes) : NULL; cmd->key_specs[j].flags = moduleConvertKeySpecsFlags(spec->flags, 1); switch (spec->begin_search_type) { - case VALKEYMODULE_KSPEC_BS_UNKNOWN: + case REDISMODULE_KSPEC_BS_UNKNOWN: cmd->key_specs[j].begin_search_type = KSPEC_BS_UNKNOWN; break; - case VALKEYMODULE_KSPEC_BS_INDEX: + case REDISMODULE_KSPEC_BS_INDEX: cmd->key_specs[j].begin_search_type = KSPEC_BS_INDEX; cmd->key_specs[j].bs.index.pos = spec->bs.index.pos; break; - case VALKEYMODULE_KSPEC_BS_KEYWORD: + case REDISMODULE_KSPEC_BS_KEYWORD: cmd->key_specs[j].begin_search_type = KSPEC_BS_KEYWORD; cmd->key_specs[j].bs.keyword.keyword = zstrdup(spec->bs.keyword.keyword); cmd->key_specs[j].bs.keyword.startfrom = spec->bs.keyword.startfrom; @@ -1900,23 +1953,23 @@ int VM_SetCommandInfo(ValkeyModuleCommand *command, const ValkeyModuleCommandInf } switch (spec->find_keys_type) { - case VALKEYMODULE_KSPEC_FK_OMITTED: + case REDISMODULE_KSPEC_FK_OMITTED: /* Omitted field is shorthand to say that it's a single key. */ cmd->key_specs[j].find_keys_type = KSPEC_FK_RANGE; cmd->key_specs[j].fk.range.lastkey = 0; cmd->key_specs[j].fk.range.keystep = 1; cmd->key_specs[j].fk.range.limit = 0; break; - case VALKEYMODULE_KSPEC_FK_UNKNOWN: + case REDISMODULE_KSPEC_FK_UNKNOWN: cmd->key_specs[j].find_keys_type = KSPEC_FK_UNKNOWN; break; - case VALKEYMODULE_KSPEC_FK_RANGE: + case REDISMODULE_KSPEC_FK_RANGE: cmd->key_specs[j].find_keys_type = KSPEC_FK_RANGE; cmd->key_specs[j].fk.range.lastkey = spec->fk.range.lastkey; cmd->key_specs[j].fk.range.keystep = spec->fk.range.keystep; cmd->key_specs[j].fk.range.limit = spec->fk.range.limit; break; - case VALKEYMODULE_KSPEC_FK_KEYNUM: + case REDISMODULE_KSPEC_FK_KEYNUM: cmd->key_specs[j].find_keys_type = KSPEC_FK_KEYNUM; cmd->key_specs[j].fk.keynum.keynumidx = spec->fk.keynum.keynumidx; cmd->key_specs[j].fk.keynum.firstkey = spec->fk.keynum.firstkey; @@ -1942,7 +1995,7 @@ int VM_SetCommandInfo(ValkeyModuleCommand *command, const ValkeyModuleCommandInf /* Fields added in future versions to be added here, under conditions like * `if (info->version >= 2) { access version 2 fields here }` */ - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Returns 1 if v is a power of two, 0 otherwise. */ @@ -1951,8 +2004,8 @@ static inline int isPowerOfTwo(uint64_t v) { } /* Returns 1 if the command info is valid and 0 otherwise. */ -static int moduleValidateCommandInfo(const ValkeyModuleCommandInfo *info) { - const ValkeyModuleCommandInfoVersion *version = info->version; +static int moduleValidateCommandInfo(const RedisModuleCommandInfo *info) { + const RedisModuleCommandInfoVersion *version = info->version; if (!version) { serverLog(LL_WARNING, "Invalid command info: version missing"); return 0; @@ -1980,7 +2033,7 @@ static int moduleValidateCommandInfo(const ValkeyModuleCommandInfo *info) { moduleCmdKeySpecAt(version, info->key_specs, j)->begin_search_type; j++) { - ValkeyModuleCommandKeySpec *spec = + RedisModuleCommandKeySpec *spec = moduleCmdKeySpecAt(version, info->key_specs, j); if (j >= INT_MAX) { serverLog(LL_WARNING, "Invalid command info: Too many key specs"); @@ -1990,11 +2043,11 @@ static int moduleValidateCommandInfo(const ValkeyModuleCommandInfo *info) { /* Flags. Exactly one flag in a group is set if and only if the * masked bits is a power of two. */ uint64_t key_flags = - VALKEYMODULE_CMD_KEY_RO | VALKEYMODULE_CMD_KEY_RW | - VALKEYMODULE_CMD_KEY_OW | VALKEYMODULE_CMD_KEY_RM; + REDISMODULE_CMD_KEY_RO | REDISMODULE_CMD_KEY_RW | + REDISMODULE_CMD_KEY_OW | REDISMODULE_CMD_KEY_RM; uint64_t write_flags = - VALKEYMODULE_CMD_KEY_INSERT | VALKEYMODULE_CMD_KEY_DELETE | - VALKEYMODULE_CMD_KEY_UPDATE; + REDISMODULE_CMD_KEY_INSERT | REDISMODULE_CMD_KEY_DELETE | + REDISMODULE_CMD_KEY_UPDATE; if (!isPowerOfTwo(spec->flags & key_flags)) { serverLog(LL_WARNING, "Invalid command info: key_specs[%zd].flags: " @@ -2011,9 +2064,9 @@ static int moduleValidateCommandInfo(const ValkeyModuleCommandInfo *info) { } switch (spec->begin_search_type) { - case VALKEYMODULE_KSPEC_BS_UNKNOWN: break; - case VALKEYMODULE_KSPEC_BS_INDEX: break; - case VALKEYMODULE_KSPEC_BS_KEYWORD: + case REDISMODULE_KSPEC_BS_UNKNOWN: break; + case REDISMODULE_KSPEC_BS_INDEX: break; + case REDISMODULE_KSPEC_BS_KEYWORD: if (spec->bs.keyword.keyword == NULL) { serverLog(LL_WARNING, "Invalid command info: key_specs[%zd].bs.keyword.keyword " @@ -2030,10 +2083,10 @@ static int moduleValidateCommandInfo(const ValkeyModuleCommandInfo *info) { /* Validate find_keys_type. */ switch (spec->find_keys_type) { - case VALKEYMODULE_KSPEC_FK_OMITTED: break; /* short for RANGE {0,1,0} */ - case VALKEYMODULE_KSPEC_FK_UNKNOWN: break; - case VALKEYMODULE_KSPEC_FK_RANGE: break; - case VALKEYMODULE_KSPEC_FK_KEYNUM: break; + case REDISMODULE_KSPEC_FK_OMITTED: break; /* short for RANGE {0,1,0} */ + case REDISMODULE_KSPEC_FK_UNKNOWN: break; + case REDISMODULE_KSPEC_FK_RANGE: break; + case REDISMODULE_KSPEC_FK_KEYNUM: break; default: serverLog(LL_WARNING, "Invalid command info: key_specs[%zd].find_keys_type: " @@ -2047,22 +2100,22 @@ static int moduleValidateCommandInfo(const ValkeyModuleCommandInfo *info) { return moduleValidateCommandArgs(info->args, version); } -/* When from_api is true, converts from VALKEYMODULE_CMD_KEY_* flags to CMD_KEY_* flags. - * When from_api is false, converts from CMD_KEY_* flags to VALKEYMODULE_CMD_KEY_* flags. */ +/* When from_api is true, converts from REDISMODULE_CMD_KEY_* flags to CMD_KEY_* flags. + * When from_api is false, converts from CMD_KEY_* flags to REDISMODULE_CMD_KEY_* flags. */ static int64_t moduleConvertKeySpecsFlags(int64_t flags, int from_api) { int64_t out = 0; int64_t map[][2] = { - {VALKEYMODULE_CMD_KEY_RO, CMD_KEY_RO}, - {VALKEYMODULE_CMD_KEY_RW, CMD_KEY_RW}, - {VALKEYMODULE_CMD_KEY_OW, CMD_KEY_OW}, - {VALKEYMODULE_CMD_KEY_RM, CMD_KEY_RM}, - {VALKEYMODULE_CMD_KEY_ACCESS, CMD_KEY_ACCESS}, - {VALKEYMODULE_CMD_KEY_INSERT, CMD_KEY_INSERT}, - {VALKEYMODULE_CMD_KEY_UPDATE, CMD_KEY_UPDATE}, - {VALKEYMODULE_CMD_KEY_DELETE, CMD_KEY_DELETE}, - {VALKEYMODULE_CMD_KEY_NOT_KEY, CMD_KEY_NOT_KEY}, - {VALKEYMODULE_CMD_KEY_INCOMPLETE, CMD_KEY_INCOMPLETE}, - {VALKEYMODULE_CMD_KEY_VARIABLE_FLAGS, CMD_KEY_VARIABLE_FLAGS}, + {REDISMODULE_CMD_KEY_RO, CMD_KEY_RO}, + {REDISMODULE_CMD_KEY_RW, CMD_KEY_RW}, + {REDISMODULE_CMD_KEY_OW, CMD_KEY_OW}, + {REDISMODULE_CMD_KEY_RM, CMD_KEY_RM}, + {REDISMODULE_CMD_KEY_ACCESS, CMD_KEY_ACCESS}, + {REDISMODULE_CMD_KEY_INSERT, CMD_KEY_INSERT}, + {REDISMODULE_CMD_KEY_UPDATE, CMD_KEY_UPDATE}, + {REDISMODULE_CMD_KEY_DELETE, CMD_KEY_DELETE}, + {REDISMODULE_CMD_KEY_NOT_KEY, CMD_KEY_NOT_KEY}, + {REDISMODULE_CMD_KEY_INCOMPLETE, CMD_KEY_INCOMPLETE}, + {REDISMODULE_CMD_KEY_VARIABLE_FLAGS, CMD_KEY_VARIABLE_FLAGS}, {0,0}}; int from_idx = from_api ? 0 : 1, to_idx = !from_idx; @@ -2071,13 +2124,13 @@ static int64_t moduleConvertKeySpecsFlags(int64_t flags, int from_api) { return out; } -/* Validates an array of ValkeyModuleCommandArg. Returns 1 if it's valid and 0 if +/* Validates an array of RedisModuleCommandArg. Returns 1 if it's valid and 0 if * it's invalid. */ -static int moduleValidateCommandArgs(ValkeyModuleCommandArg *args, - const ValkeyModuleCommandInfoVersion *version) { +static int moduleValidateCommandArgs(RedisModuleCommandArg *args, + const RedisModuleCommandInfoVersion *version) { if (args == NULL) return 1; /* Missing args is OK. */ for (size_t j = 0; moduleCmdArgAt(version, args, j)->name != NULL; j++) { - ValkeyModuleCommandArg *arg = moduleCmdArgAt(version, args, j); + RedisModuleCommandArg *arg = moduleCmdArgAt(version, args, j); int arg_type_error = 0; moduleConvertArgType(arg->type, &arg_type_error); if (arg_type_error) { @@ -2086,14 +2139,14 @@ static int moduleValidateCommandArgs(ValkeyModuleCommandArg *args, arg->name, arg->type); return 0; } - if (arg->type == VALKEYMODULE_ARG_TYPE_PURE_TOKEN && !arg->token) { + if (arg->type == REDISMODULE_ARG_TYPE_PURE_TOKEN && !arg->token) { serverLog(LL_WARNING, "Invalid command info: Argument \"%s\": " "token required when type is PURE_TOKEN", args[j].name); return 0; } - if (arg->type == VALKEYMODULE_ARG_TYPE_KEY) { + if (arg->type == REDISMODULE_ARG_TYPE_KEY) { if (arg->key_spec_index < 0) { serverLog(LL_WARNING, "Invalid command info: Argument \"%s\": " @@ -2111,15 +2164,15 @@ static int moduleValidateCommandArgs(ValkeyModuleCommandArg *args, return 0; } - if (arg->flags & ~(_VALKEYMODULE_CMD_ARG_NEXT - 1)) { + if (arg->flags & ~(_REDISMODULE_CMD_ARG_NEXT - 1)) { serverLog(LL_WARNING, "Invalid command info: Argument \"%s\": Invalid flags", arg->name); return 0; } - if (arg->type == VALKEYMODULE_ARG_TYPE_ONEOF || - arg->type == VALKEYMODULE_ARG_TYPE_BLOCK) + if (arg->type == REDISMODULE_ARG_TYPE_ONEOF || + arg->type == REDISMODULE_ARG_TYPE_BLOCK) { if (arg->subargs == NULL) { serverLog(LL_WARNING, @@ -2142,20 +2195,20 @@ static int moduleValidateCommandArgs(ValkeyModuleCommandArg *args, return 1; } -/* Converts an array of ValkeyModuleCommandArg into a freshly allocated array of +/* Converts an array of RedisModuleCommandArg into a freshly allocated array of * struct redisCommandArg. */ -static struct redisCommandArg *moduleCopyCommandArgs(ValkeyModuleCommandArg *args, - const ValkeyModuleCommandInfoVersion *version) { +static struct redisCommandArg *moduleCopyCommandArgs(RedisModuleCommandArg *args, + const RedisModuleCommandInfoVersion *version) { size_t count = 0; while (moduleCmdArgAt(version, args, count)->name) count++; serverAssert(count < SIZE_MAX / sizeof(struct redisCommandArg)); struct redisCommandArg *realargs = zcalloc((count+1) * sizeof(redisCommandArg)); for (size_t j = 0; j < count; j++) { - ValkeyModuleCommandArg *arg = moduleCmdArgAt(version, args, j); + RedisModuleCommandArg *arg = moduleCmdArgAt(version, args, j); realargs[j].name = zstrdup(arg->name); realargs[j].type = moduleConvertArgType(arg->type, NULL); - if (arg->type == VALKEYMODULE_ARG_TYPE_KEY) + if (arg->type == REDISMODULE_ARG_TYPE_KEY) realargs[j].key_spec_index = arg->key_spec_index; else realargs[j].key_spec_index = -1; @@ -2170,18 +2223,18 @@ static struct redisCommandArg *moduleCopyCommandArgs(ValkeyModuleCommandArg *arg return realargs; } -static redisCommandArgType moduleConvertArgType(ValkeyModuleCommandArgType type, int *error) { +static redisCommandArgType moduleConvertArgType(RedisModuleCommandArgType type, int *error) { if (error) *error = 0; switch (type) { - case VALKEYMODULE_ARG_TYPE_STRING: return ARG_TYPE_STRING; - case VALKEYMODULE_ARG_TYPE_INTEGER: return ARG_TYPE_INTEGER; - case VALKEYMODULE_ARG_TYPE_DOUBLE: return ARG_TYPE_DOUBLE; - case VALKEYMODULE_ARG_TYPE_KEY: return ARG_TYPE_KEY; - case VALKEYMODULE_ARG_TYPE_PATTERN: return ARG_TYPE_PATTERN; - case VALKEYMODULE_ARG_TYPE_UNIX_TIME: return ARG_TYPE_UNIX_TIME; - case VALKEYMODULE_ARG_TYPE_PURE_TOKEN: return ARG_TYPE_PURE_TOKEN; - case VALKEYMODULE_ARG_TYPE_ONEOF: return ARG_TYPE_ONEOF; - case VALKEYMODULE_ARG_TYPE_BLOCK: return ARG_TYPE_BLOCK; + case REDISMODULE_ARG_TYPE_STRING: return ARG_TYPE_STRING; + case REDISMODULE_ARG_TYPE_INTEGER: return ARG_TYPE_INTEGER; + case REDISMODULE_ARG_TYPE_DOUBLE: return ARG_TYPE_DOUBLE; + case REDISMODULE_ARG_TYPE_KEY: return ARG_TYPE_KEY; + case REDISMODULE_ARG_TYPE_PATTERN: return ARG_TYPE_PATTERN; + case REDISMODULE_ARG_TYPE_UNIX_TIME: return ARG_TYPE_UNIX_TIME; + case REDISMODULE_ARG_TYPE_PURE_TOKEN: return ARG_TYPE_PURE_TOKEN; + case REDISMODULE_ARG_TYPE_ONEOF: return ARG_TYPE_ONEOF; + case REDISMODULE_ARG_TYPE_BLOCK: return ARG_TYPE_BLOCK; default: if (error) *error = 1; return -1; @@ -2190,24 +2243,24 @@ static redisCommandArgType moduleConvertArgType(ValkeyModuleCommandArgType type, static int moduleConvertArgFlags(int flags) { int realflags = 0; - if (flags & VALKEYMODULE_CMD_ARG_OPTIONAL) realflags |= CMD_ARG_OPTIONAL; - if (flags & VALKEYMODULE_CMD_ARG_MULTIPLE) realflags |= CMD_ARG_MULTIPLE; - if (flags & VALKEYMODULE_CMD_ARG_MULTIPLE_TOKEN) realflags |= CMD_ARG_MULTIPLE_TOKEN; + if (flags & REDISMODULE_CMD_ARG_OPTIONAL) realflags |= CMD_ARG_OPTIONAL; + if (flags & REDISMODULE_CMD_ARG_MULTIPLE) realflags |= CMD_ARG_MULTIPLE; + if (flags & REDISMODULE_CMD_ARG_MULTIPLE_TOKEN) realflags |= CMD_ARG_MULTIPLE_TOKEN; return realflags; } -/* Return `struct ValkeyModule *` as `void *` to avoid exposing it outside of module.c. */ +/* Return `struct RedisModule *` as `void *` to avoid exposing it outside of module.c. */ void *moduleGetHandleByName(char *modulename) { return dictFetchValue(modules,modulename); } /* Returns 1 if `cmd` is a command of the module `modulename`. 0 otherwise. */ int moduleIsModuleCommand(void *module_handle, struct redisCommand *cmd) { - if (cmd->proc != ValkeyModuleCommandDispatcher) + if (cmd->proc != RedisModuleCommandDispatcher) return 0; if (module_handle == NULL) return 0; - ValkeyModuleCommand *cp = cmd->module_cmd; + RedisModuleCommand *cp = cmd->module_cmd; return (cp->module == module_handle); } @@ -2225,12 +2278,12 @@ void moduleListFree(void *config) { zfree(config); } -void VM_SetModuleAttribs(ValkeyModuleCtx *ctx, const char *name, int ver, int apiver) { - /* Called by VM_Init() to setup the `ctx->module` structure. +void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int apiver) { + /* Called by RM_Init() to setup the `ctx->module` structure. * * This is an internal function, Redis modules developers don't need * to use it. */ - ValkeyModule *module; + RedisModule *module; if (ctx->module != NULL) return; module = zmalloc(sizeof(*module)); @@ -2253,12 +2306,13 @@ void VM_SetModuleAttribs(ValkeyModuleCtx *ctx, const char *name, int ver, int ap module->loadmod = NULL; module->num_commands_with_acl_categories = 0; module->onload = 1; + module->num_acl_categories_added = 0; ctx->module = module; } /* Return non-zero if the module name is busy. * Otherwise zero is returned. */ -int VM_IsModuleNameBusy(const char *name) { +int RM_IsModuleNameBusy(const char *name) { sds modulename = sdsnew(name); dictEntry *de = dictFind(modules,modulename); sdsfree(modulename); @@ -2266,52 +2320,58 @@ int VM_IsModuleNameBusy(const char *name) { } /* Return the current UNIX time in milliseconds. */ -mstime_t VM_Milliseconds(void) { +mstime_t RM_Milliseconds(void) { return mstime(); } /* Return counter of micro-seconds relative to an arbitrary point in time. */ -uint64_t VM_MonotonicMicroseconds(void) { +uint64_t RM_MonotonicMicroseconds(void) { return getMonotonicUs(); } /* Return the current UNIX time in microseconds */ -ustime_t VM_Microseconds(void) { +ustime_t RM_Microseconds(void) { return ustime(); } /* Return the cached UNIX time in microseconds. * It is updated in the server cron job and before executing a command. * It is useful for complex call stacks, such as a command causing a - * key space notification, causing a module to execute a ValkeyModule_Call, + * key space notification, causing a module to execute a RedisModule_Call, * causing another notification, etc. * It makes sense that all this callbacks would use the same clock. */ -ustime_t VM_CachedMicroseconds(void) { +ustime_t RM_CachedMicroseconds(void) { return server.ustime; } /* Mark a point in time that will be used as the start time to calculate - * the elapsed execution time when VM_BlockedClientMeasureTimeEnd() is called. + * the elapsed execution time when RM_BlockedClientMeasureTimeEnd() is called. * Within the same command, you can call multiple times - * VM_BlockedClientMeasureTimeStart() and VM_BlockedClientMeasureTimeEnd() + * RM_BlockedClientMeasureTimeStart() and RM_BlockedClientMeasureTimeEnd() * to accumulate independent time intervals to the background duration. - * This method always return VALKEYMODULE_OK. */ -int VM_BlockedClientMeasureTimeStart(ValkeyModuleBlockedClient *bc) { + * 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) { elapsedStart(&(bc->background_timer)); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Mark a point in time that will be used as the end time * to calculate the elapsed execution time. - * On success VALKEYMODULE_OK is returned. - * This method only returns VALKEYMODULE_ERR if no start time was - * previously defined ( meaning RM_BlockedClientMeasureTimeStart was not called ). */ -int VM_BlockedClientMeasureTimeEnd(ValkeyModuleBlockedClient *bc) { - // If the counter is 0 then we haven't called VM_BlockedClientMeasureTimeStart + * 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) { + // If the counter is 0 then we haven't called RM_BlockedClientMeasureTimeStart if (!bc->background_timer) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; bc->background_duration += elapsedUs(bc->background_timer); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* This API allows modules to let Redis process background tasks, and some @@ -2319,14 +2379,14 @@ int VM_BlockedClientMeasureTimeEnd(ValkeyModuleBlockedClient *bc) { * The module can call this API periodically. * The flags is a bit mask of these: * - * - `VALKEYMODULE_YIELD_FLAG_NONE`: No special flags, can perform some background + * - `REDISMODULE_YIELD_FLAG_NONE`: No special flags, can perform some background * operations, but not process client commands. - * - `VALKEYMODULE_YIELD_FLAG_CLIENTS`: Redis can also process client commands. + * - `REDISMODULE_YIELD_FLAG_CLIENTS`: Redis can also process client commands. * * The `busy_reply` argument is optional, and can be used to control the verbose * error string after the `-BUSY` error code. * - * When the `VALKEYMODULE_YIELD_FLAG_CLIENTS` is used, Redis will only start + * When the `REDISMODULE_YIELD_FLAG_CLIENTS` is used, Redis will only start * processing client commands after the time defined by the * `busy-reply-threshold` config, in which case Redis will start rejecting most * commands with `-BUSY` error, but allow the ones marked with the `allow-busy` @@ -2335,9 +2395,9 @@ int VM_BlockedClientMeasureTimeEnd(ValkeyModuleBlockedClient *bc) { * loading (in the `rdb_load` callback, in which case it'll reject commands with * the -LOADING error) */ -void VM_Yield(ValkeyModuleCtx *ctx, int flags, const char *busy_reply) { +void RM_Yield(RedisModuleCtx *ctx, int flags, const char *busy_reply) { static int yield_nesting = 0; - /* Avoid nested calls to VM_Yield */ + /* Avoid nested calls to RM_Yield */ if (yield_nesting) return; yield_nesting++; @@ -2360,11 +2420,37 @@ void VM_Yield(ValkeyModuleCtx *ctx, int flags, const char *busy_reply) { if (server.current_client) protectClient(server.current_client); } - if (flags & VALKEYMODULE_YIELD_FLAG_CLIENTS) + if (flags & REDISMODULE_YIELD_FLAG_CLIENTS) server.busy_module_yield_flags |= BUSY_MODULE_YIELD_CLIENTS; /* Let redis process events */ - processEventsWhileBlocked(); + if (!pthread_equal(server.main_thread_id, pthread_self())) { + /* If we are not in the main thread, we defer event loop processing to the main thread + * after the main thread enters acquiring GIL state in order to protect the event + * loop (ae.c) and avoid potential race conditions. */ + + int acquiring; + atomicGet(server.module_gil_acquring, acquiring); + if (!acquiring) { + /* If the main thread has not yet entered the acquiring GIL state, + * we attempt to wake it up and exit without waiting for it to + * acquire the GIL. This avoids blocking the caller, allowing them to + * continue with unfinished tasks before the next yield. + * We assume the caller keeps the GIL locked. */ + if (write(server.module_pipe[1],"A",1) != 1) { + /* Ignore the error, this is best-effort. */ + } + } else { + /* Release the GIL, yielding CPU to give the main thread an opportunity to start + * event processing, and then acquire the GIL again until the main thread releases it. */ + moduleReleaseGIL(); + sched_yield(); + moduleAcquireGIL(); + } + } else { + /* If we are in the main thread, we can safely process events. */ + processEventsWhileBlocked(); + } server.busy_module_yield_reply = prev_busy_module_yield_reply; /* Possibly restore the previous flags in case of two nested contexts @@ -2381,28 +2467,28 @@ void VM_Yield(ValkeyModuleCtx *ctx, int flags, const char *busy_reply) { /* Set flags defining capabilities or behavior bit flags. * - * VALKEYMODULE_OPTIONS_HANDLE_IO_ERRORS: + * REDISMODULE_OPTIONS_HANDLE_IO_ERRORS: * Generally, modules don't need to bother with this, as the process will just * terminate if a read error happens, however, setting this flag would allow * repl-diskless-load to work if enabled. - * The module should use ValkeyModule_IsIOError after reads, before using the + * The module should use RedisModule_IsIOError after reads, before using the * data that was read, and in case of error, propagate it upwards, and also be * able to release the partially populated value and all it's allocations. * - * VALKEYMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED: - * See VM_SignalModifiedKey(). + * REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED: + * See RM_SignalModifiedKey(). * - * VALKEYMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD: + * 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. * - * VALKEYMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS: + * REDISMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS: * Declare that the module wants to get nested key-space notifications. * By default, Redis will not fire key-space notifications that happened inside * a key-space notification callback. This flag allows to change this behavior * and fire nested key-space notifications. Notice: if enabled, the module * should protected itself from infinite recursion. */ -void VM_SetModuleOptions(ValkeyModuleCtx *ctx, int options) { +void RM_SetModuleOptions(RedisModuleCtx *ctx, int options) { ctx->module->options = options; } @@ -2410,12 +2496,12 @@ void VM_SetModuleOptions(ValkeyModuleCtx *ctx, int options) { * and client side caching). * * This is done automatically when a key opened for writing is closed, unless - * the option VALKEYMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED has been set using - * VM_SetModuleOptions(). + * the option REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED has been set using + * RM_SetModuleOptions(). */ -int VM_SignalModifiedKey(ValkeyModuleCtx *ctx, ValkeyModuleString *keyname) { +int RM_SignalModifiedKey(RedisModuleCtx *ctx, RedisModuleString *keyname) { signalModifiedKey(ctx->client,ctx->client->db,keyname); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* -------------------------------------------------------------------------- @@ -2431,19 +2517,19 @@ int VM_SignalModifiedKey(ValkeyModuleCtx *ctx, ValkeyModuleString *keyname) { * keys, call replies and Redis string objects once the command returns. In most * cases this eliminates the need of calling the following functions: * - * 1. ValkeyModule_CloseKey() - * 2. ValkeyModule_FreeCallReply() - * 3. ValkeyModule_FreeString() + * 1. RedisModule_CloseKey() + * 2. RedisModule_FreeCallReply() + * 3. RedisModule_FreeString() * * These functions can still be used with automatic memory management enabled, * to optimize loops that make numerous allocations for example. */ -void VM_AutoMemory(ValkeyModuleCtx *ctx) { - ctx->flags |= VALKEYMODULE_CTX_AUTO_MEMORY; +void RM_AutoMemory(RedisModuleCtx *ctx) { + ctx->flags |= REDISMODULE_CTX_AUTO_MEMORY; } /* Add a new object to release automatically when the callback returns. */ -void autoMemoryAdd(ValkeyModuleCtx *ctx, int type, void *ptr) { - if (!(ctx->flags & VALKEYMODULE_CTX_AUTO_MEMORY)) return; +void autoMemoryAdd(RedisModuleCtx *ctx, int type, void *ptr) { + if (!(ctx->flags & REDISMODULE_CTX_AUTO_MEMORY)) return; if (ctx->amqueue_used == ctx->amqueue_len) { ctx->amqueue_len *= 2; if (ctx->amqueue_len < 16) ctx->amqueue_len = 16; @@ -2459,8 +2545,8 @@ void autoMemoryAdd(ValkeyModuleCtx *ctx, int type, void *ptr) { * * The function returns 1 if the object was actually found in the auto memory * pool, otherwise 0 is returned. */ -int autoMemoryFreed(ValkeyModuleCtx *ctx, int type, void *ptr) { - if (!(ctx->flags & VALKEYMODULE_CTX_AUTO_MEMORY)) return 0; +int autoMemoryFreed(RedisModuleCtx *ctx, int type, void *ptr) { + if (!(ctx->flags & REDISMODULE_CTX_AUTO_MEMORY)) return 0; int count = (ctx->amqueue_used+1)/2; for (int j = 0; j < count; j++) { @@ -2471,7 +2557,7 @@ int autoMemoryFreed(ValkeyModuleCtx *ctx, int type, void *ptr) { if (ctx->amqueue[i].type == type && ctx->amqueue[i].ptr == ptr) { - ctx->amqueue[i].type = VALKEYMODULE_AM_FREED; + ctx->amqueue[i].type = REDISMODULE_AM_FREED; /* Switch the freed element and the last element, to avoid growing * the queue unnecessarily if we allocate/free in a loop */ @@ -2490,24 +2576,24 @@ int autoMemoryFreed(ValkeyModuleCtx *ctx, int type, void *ptr) { } /* Release all the objects in queue. */ -void autoMemoryCollect(ValkeyModuleCtx *ctx) { - if (!(ctx->flags & VALKEYMODULE_CTX_AUTO_MEMORY)) return; +void autoMemoryCollect(RedisModuleCtx *ctx) { + if (!(ctx->flags & REDISMODULE_CTX_AUTO_MEMORY)) return; /* Clear the AUTO_MEMORY flag from the context, otherwise the functions * we call to free the resources, will try to scan the auto release * queue to mark the entries as freed. */ - ctx->flags &= ~VALKEYMODULE_CTX_AUTO_MEMORY; + ctx->flags &= ~REDISMODULE_CTX_AUTO_MEMORY; int j; for (j = 0; j < ctx->amqueue_used; j++) { void *ptr = ctx->amqueue[j].ptr; switch(ctx->amqueue[j].type) { - case VALKEYMODULE_AM_STRING: decrRefCount(ptr); break; - case VALKEYMODULE_AM_REPLY: VM_FreeCallReply(ptr); break; - case VALKEYMODULE_AM_KEY: VM_CloseKey(ptr); break; - case VALKEYMODULE_AM_DICT: VM_FreeDict(NULL,ptr); break; - case VALKEYMODULE_AM_INFO: VM_FreeServerInfo(NULL,ptr); break; + case REDISMODULE_AM_STRING: decrRefCount(ptr); break; + case REDISMODULE_AM_REPLY: RM_FreeCallReply(ptr); break; + case REDISMODULE_AM_KEY: RM_CloseKey(ptr); break; + case REDISMODULE_AM_DICT: RM_FreeDict(NULL,ptr); break; + case REDISMODULE_AM_INFO: RM_FreeServerInfo(NULL,ptr); break; } } - ctx->flags |= VALKEYMODULE_CTX_AUTO_MEMORY; + ctx->flags |= REDISMODULE_CTX_AUTO_MEMORY; zfree(ctx->amqueue); ctx->amqueue = NULL; ctx->amqueue_len = 0; @@ -2519,7 +2605,7 @@ void autoMemoryCollect(ValkeyModuleCtx *ctx) { * -------------------------------------------------------------------------- */ /* Create a new module string object. The returned string must be freed - * with ValkeyModule_FreeString(), unless automatic memory is enabled. + * with RedisModule_FreeString(), unless automatic memory is enabled. * * The string is created by copying the `len` bytes starting * at `ptr`. No reference is retained to the passed buffer. @@ -2528,21 +2614,21 @@ void autoMemoryCollect(ValkeyModuleCtx *ctx) { * a string out of the context scope. However in that case, the automatic * memory management will not be available, and the string memory must be * managed manually. */ -ValkeyModuleString *VM_CreateString(ValkeyModuleCtx *ctx, const char *ptr, size_t len) { - ValkeyModuleString *o = createStringObject(ptr,len); - if (ctx != NULL) autoMemoryAdd(ctx,VALKEYMODULE_AM_STRING,o); +RedisModuleString *RM_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len) { + RedisModuleString *o = createStringObject(ptr,len); + if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o); return o; } /* Create a new module string object from a printf format and arguments. - * The returned string must be freed with ValkeyModule_FreeString(), unless + * The returned string must be freed with RedisModule_FreeString(), unless * automatic memory is enabled. * * The string is created using the sds formatter function sdscatvprintf(). * * The passed context 'ctx' may be NULL if necessary, see the - * ValkeyModule_CreateString() documentation for more info. */ -ValkeyModuleString *VM_CreateStringPrintf(ValkeyModuleCtx *ctx, const char *fmt, ...) { + * RedisModule_CreateString() documentation for more info. */ +RedisModuleString *RM_CreateStringPrintf(RedisModuleCtx *ctx, const char *fmt, ...) { sds s = sdsempty(); va_list ap; @@ -2550,90 +2636,90 @@ ValkeyModuleString *VM_CreateStringPrintf(ValkeyModuleCtx *ctx, const char *fmt, s = sdscatvprintf(s, fmt, ap); va_end(ap); - ValkeyModuleString *o = createObject(OBJ_STRING, s); - if (ctx != NULL) autoMemoryAdd(ctx,VALKEYMODULE_AM_STRING,o); + RedisModuleString *o = createObject(OBJ_STRING, s); + if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o); return o; } -/* Like ValkeyModule_CreateString(), but creates a string starting from a `long long` +/* Like RedisModule_CreateString(), but creates a string starting from a `long long` * integer instead of taking a buffer and its length. * - * The returned string must be released with ValkeyModule_FreeString() or by + * The returned string must be released with RedisModule_FreeString() or by * enabling automatic memory management. * * The passed context 'ctx' may be NULL if necessary, see the - * ValkeyModule_CreateString() documentation for more info. */ -ValkeyModuleString *VM_CreateStringFromLongLong(ValkeyModuleCtx *ctx, long long ll) { + * RedisModule_CreateString() documentation for more info. */ +RedisModuleString *RM_CreateStringFromLongLong(RedisModuleCtx *ctx, long long ll) { char buf[LONG_STR_SIZE]; size_t len = ll2string(buf,sizeof(buf),ll); - return VM_CreateString(ctx,buf,len); + return RM_CreateString(ctx,buf,len); } -/* Like ValkeyModule_CreateString(), but creates a string starting from a `unsigned long long` +/* Like RedisModule_CreateString(), but creates a string starting from a `unsigned long long` * integer instead of taking a buffer and its length. * - * The returned string must be released with ValkeyModule_FreeString() or by + * The returned string must be released with RedisModule_FreeString() or by * enabling automatic memory management. * * The passed context 'ctx' may be NULL if necessary, see the - * ValkeyModule_CreateString() documentation for more info. */ -ValkeyModuleString *VM_CreateStringFromULongLong(ValkeyModuleCtx *ctx, unsigned long long ull) { + * RedisModule_CreateString() documentation for more info. */ +RedisModuleString *RM_CreateStringFromULongLong(RedisModuleCtx *ctx, unsigned long long ull) { char buf[LONG_STR_SIZE]; size_t len = ull2string(buf,sizeof(buf),ull); - return VM_CreateString(ctx,buf,len); + return RM_CreateString(ctx,buf,len); } -/* Like ValkeyModule_CreateString(), but creates a string starting from a double +/* Like RedisModule_CreateString(), but creates a string starting from a double * instead of taking a buffer and its length. * - * The returned string must be released with ValkeyModule_FreeString() or by + * The returned string must be released with RedisModule_FreeString() or by * enabling automatic memory management. */ -ValkeyModuleString *VM_CreateStringFromDouble(ValkeyModuleCtx *ctx, double d) { +RedisModuleString *RM_CreateStringFromDouble(RedisModuleCtx *ctx, double d) { char buf[MAX_D2STRING_CHARS]; size_t len = d2string(buf,sizeof(buf),d); - return VM_CreateString(ctx,buf,len); + return RM_CreateString(ctx,buf,len); } -/* Like ValkeyModule_CreateString(), but creates a string starting from a long +/* Like RedisModule_CreateString(), but creates a string starting from a long * double. * - * The returned string must be released with ValkeyModule_FreeString() or by + * The returned string must be released with RedisModule_FreeString() or by * enabling automatic memory management. * * The passed context 'ctx' may be NULL if necessary, see the - * ValkeyModule_CreateString() documentation for more info. */ -ValkeyModuleString *VM_CreateStringFromLongDouble(ValkeyModuleCtx *ctx, long double ld, int humanfriendly) { + * RedisModule_CreateString() documentation for more info. */ +RedisModuleString *RM_CreateStringFromLongDouble(RedisModuleCtx *ctx, long double ld, int humanfriendly) { char buf[MAX_LONG_DOUBLE_CHARS]; size_t len = ld2string(buf,sizeof(buf),ld, (humanfriendly ? LD_STR_HUMAN : LD_STR_AUTO)); - return VM_CreateString(ctx,buf,len); + return RM_CreateString(ctx,buf,len); } -/* Like ValkeyModule_CreateString(), but creates a string starting from another - * ValkeyModuleString. +/* Like RedisModule_CreateString(), but creates a string starting from another + * RedisModuleString. * - * The returned string must be released with ValkeyModule_FreeString() or by + * The returned string must be released with RedisModule_FreeString() or by * enabling automatic memory management. * * The passed context 'ctx' may be NULL if necessary, see the - * ValkeyModule_CreateString() documentation for more info. */ -ValkeyModuleString *VM_CreateStringFromString(ValkeyModuleCtx *ctx, const ValkeyModuleString *str) { - ValkeyModuleString *o = dupStringObject(str); - if (ctx != NULL) autoMemoryAdd(ctx,VALKEYMODULE_AM_STRING,o); + * RedisModule_CreateString() documentation for more info. */ +RedisModuleString *RM_CreateStringFromString(RedisModuleCtx *ctx, const RedisModuleString *str) { + RedisModuleString *o = dupStringObject(str); + if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o); return o; } /* Creates a string from a stream ID. The returned string must be released with - * ValkeyModule_FreeString(), unless automatic memory is enabled. + * RedisModule_FreeString(), unless automatic memory is enabled. * * The passed context `ctx` may be NULL if necessary. See the - * ValkeyModule_CreateString() documentation for more info. */ -ValkeyModuleString *VM_CreateStringFromStreamID(ValkeyModuleCtx *ctx, const ValkeyModuleStreamID *id) { + * RedisModule_CreateString() documentation for more info. */ +RedisModuleString *RM_CreateStringFromStreamID(RedisModuleCtx *ctx, const RedisModuleStreamID *id) { streamID streamid = {id->ms, id->seq}; - ValkeyModuleString *o = createObjectFromStreamID(&streamid); - if (ctx != NULL) autoMemoryAdd(ctx, VALKEYMODULE_AM_STRING, o); + RedisModuleString *o = createObjectFromStreamID(&streamid); + if (ctx != NULL) autoMemoryAdd(ctx, REDISMODULE_AM_STRING, o); return o; } @@ -2648,17 +2734,20 @@ ValkeyModuleString *VM_CreateStringFromStreamID(ValkeyModuleCtx *ctx, const Valk * pass ctx as NULL when releasing the string (but passing a context will not * create any issue). Strings created with a context should be freed also passing * the context, so if you want to free a string out of context later, make sure - * to create it using a NULL context. */ -void VM_FreeString(ValkeyModuleCtx *ctx, ValkeyModuleString *str) { + * to create it using a NULL context. + * + * This API is not thread safe, access to these retained strings (if they originated + * from a client command arguments) must be done with GIL locked. */ +void RM_FreeString(RedisModuleCtx *ctx, RedisModuleString *str) { decrRefCount(str); - if (ctx != NULL) autoMemoryFreed(ctx,VALKEYMODULE_AM_STRING,str); + if (ctx != NULL) autoMemoryFreed(ctx,REDISMODULE_AM_STRING,str); } /* Every call to this function, will make the string 'str' requiring - * an additional call to ValkeyModule_FreeString() in order to really + * an additional call to RedisModule_FreeString() in order to really * free the string. Note that the automatic freeing of the string obtained * enabling modules automatic memory management counts for one - * ValkeyModule_FreeString() call (it is just executed automatically). + * RedisModule_FreeString() call (it is just executed automatically). * * Normally you want to call this function when, at the same time * the following conditions are true: @@ -2680,14 +2769,17 @@ void VM_FreeString(ValkeyModuleCtx *ctx, ValkeyModuleString *str) { * It is possible to call this function with a NULL context. * * When strings are going to be retained for an extended duration, it is good - * practice to also call ValkeyModule_TrimStringAllocation() in order to + * practice to also call RedisModule_TrimStringAllocation() in order to * optimize memory usage. * * Threaded modules that reference retained strings from other threads *must* * explicitly trim the allocation as soon as the string is retained. Not doing - * so may result with automatic trimming which is not thread safe. */ -void VM_RetainString(ValkeyModuleCtx *ctx, ValkeyModuleString *str) { - if (ctx == NULL || !autoMemoryFreed(ctx,VALKEYMODULE_AM_STRING,str)) { + * so may result with automatic trimming which is not thread safe. + * + * This API is not thread safe, access to these retained strings (if they originated + * from a client command arguments) must be done with GIL locked. */ +void RM_RetainString(RedisModuleCtx *ctx, RedisModuleString *str) { + if (ctx == NULL || !autoMemoryFreed(ctx,REDISMODULE_AM_STRING,str)) { /* Increment the string reference counting only if we can't * just remove the object from the list of objects that should * be reclaimed. Why we do that, instead of just incrementing @@ -2696,41 +2788,44 @@ void VM_RetainString(ValkeyModuleCtx *ctx, ValkeyModuleString *str) { * value? Because this way we ensure that the object refcount * value is 1 (instead of going to 2 to be dropped later to 1) * after the call to this function. This is needed for functions - * like ValkeyModule_StringAppendBuffer() to work. */ + * like RedisModule_StringAppendBuffer() to work. */ incrRefCount(str); } } /** -* This function can be used instead of ValkeyModule_RetainString(). +* This function can be used instead of RedisModule_RetainString(). * The main difference between the two is that this function will always -* succeed, whereas ValkeyModule_RetainString() may fail because of an +* succeed, whereas RedisModule_RetainString() may fail because of an * assertion. * -* The function returns a pointer to ValkeyModuleString, which is owned -* by the caller. It requires a call to ValkeyModule_FreeString() to free +* The function returns a pointer to RedisModuleString, which is owned +* by the caller. It requires a call to RedisModule_FreeString() to free * the string when automatic memory management is disabled for the context. * When automatic memory management is enabled, you can either call -* ValkeyModule_FreeString() or let the automation free it. +* RedisModule_FreeString() or let the automation free it. * -* This function is more efficient than ValkeyModule_CreateStringFromString() +* This function is more efficient than RedisModule_CreateStringFromString() * because whenever possible, it avoids copying the underlying -* ValkeyModuleString. The disadvantage of using this function is that it -* might not be possible to use ValkeyModule_StringAppendBuffer() on the -* returned ValkeyModuleString. +* RedisModuleString. The disadvantage of using this function is that it +* might not be possible to use RedisModule_StringAppendBuffer() on the +* returned RedisModuleString. * * It is possible to call this function with a NULL context. * * When strings are going to be held for an extended duration, it is good - * practice to also call ValkeyModule_TrimStringAllocation() in order to + * practice to also call RedisModule_TrimStringAllocation() in order to * optimize memory usage. * * Threaded modules that reference held strings from other threads *must* * explicitly trim the allocation as soon as the string is held. Not doing - * so may result with automatic trimming which is not thread safe. */ -ValkeyModuleString* VM_HoldString(ValkeyModuleCtx *ctx, ValkeyModuleString *str) { + * so may result with automatic trimming which is not thread safe. + * + * This API is not thread safe, access to these retained strings (if they originated + * from a client command arguments) must be done with GIL locked. */ +RedisModuleString* RM_HoldString(RedisModuleCtx *ctx, RedisModuleString *str) { if (str->refcount == OBJ_STATIC_REFCOUNT) { - return VM_CreateStringFromString(ctx, str); + return RM_CreateStringFromString(ctx, str); } incrRefCount(str); @@ -2742,27 +2837,27 @@ ValkeyModuleString* VM_HoldString(ValkeyModuleCtx *ctx, ValkeyModuleString *str) * object in the auto memory free function. * * Why we can not do the same trick of just remove the object - * from the auto memory (like in VM_RetainString)? + * from the auto memory (like in RM_RetainString)? * This code shows the issue: * - * VM_AutoMemory(ctx); - * str1 = VM_CreateString(ctx, "test", 4); - * str2 = VM_HoldString(ctx, str1); - * VM_FreeString(str1); - * VM_FreeString(str2); + * RM_AutoMemory(ctx); + * str1 = RM_CreateString(ctx, "test", 4); + * str2 = RM_HoldString(ctx, str1); + * RM_FreeString(str1); + * RM_FreeString(str2); * - * If after the VM_HoldString we would just remove the string from + * If after the RM_HoldString we would just remove the string from * the auto memory, this example will cause access to a freed memory - * on 'VM_FreeString(str2);' because the String will be free - * on 'VM_FreeString(str1);'. + * on 'RM_FreeString(str2);' because the String will be free + * on 'RM_FreeString(str1);'. * * So it's safer to just increase the ref count * and add the String to auto memory again. * - * The limitation is that it is not possible to use ValkeyModule_StringAppendBuffer + * The limitation is that it is not possible to use RedisModule_StringAppendBuffer * on the String. */ - autoMemoryAdd(ctx,VALKEYMODULE_AM_STRING,str); + autoMemoryAdd(ctx,REDISMODULE_AM_STRING,str); } return str; } @@ -2770,7 +2865,7 @@ ValkeyModuleString* VM_HoldString(ValkeyModuleCtx *ctx, ValkeyModuleString *str) /* Given a string module object, this function returns the string pointer * and length of the string. The returned pointer and length should only * be used for read only accesses and never modified. */ -const char *VM_StringPtrLen(const ValkeyModuleString *str, size_t *len) { +const char *RM_StringPtrLen(const RedisModuleString *str, size_t *len) { if (str == NULL) { const char *errmsg = "(NULL string reply referenced in module)"; if (len) *len = strlen(errmsg); @@ -2785,64 +2880,64 @@ const char *VM_StringPtrLen(const ValkeyModuleString *str, size_t *len) { * ------------------------------------------------------------------------- */ /* Convert the string into a `long long` integer, storing it at `*ll`. - * Returns VALKEYMODULE_OK on success. If the string can't be parsed - * as a valid, strict `long long` (no spaces before/after), VALKEYMODULE_ERR + * Returns REDISMODULE_OK on success. If the string can't be parsed + * as a valid, strict `long long` (no spaces before/after), REDISMODULE_ERR * is returned. */ -int VM_StringToLongLong(const ValkeyModuleString *str, long long *ll) { - return string2ll(str->ptr,sdslen(str->ptr),ll) ? VALKEYMODULE_OK : - VALKEYMODULE_ERR; +int RM_StringToLongLong(const RedisModuleString *str, long long *ll) { + return string2ll(str->ptr,sdslen(str->ptr),ll) ? REDISMODULE_OK : + REDISMODULE_ERR; } /* Convert the string into a `unsigned long long` integer, storing it at `*ull`. - * Returns VALKEYMODULE_OK on success. If the string can't be parsed - * as a valid, strict `unsigned long long` (no spaces before/after), VALKEYMODULE_ERR + * Returns REDISMODULE_OK on success. If the string can't be parsed + * as a valid, strict `unsigned long long` (no spaces before/after), REDISMODULE_ERR * is returned. */ -int VM_StringToULongLong(const ValkeyModuleString *str, unsigned long long *ull) { - return string2ull(str->ptr,ull) ? VALKEYMODULE_OK : VALKEYMODULE_ERR; +int RM_StringToULongLong(const RedisModuleString *str, unsigned long long *ull) { + return string2ull(str->ptr,ull) ? REDISMODULE_OK : REDISMODULE_ERR; } /* Convert the string into a double, storing it at `*d`. - * Returns VALKEYMODULE_OK on success or VALKEYMODULE_ERR if the string is + * Returns REDISMODULE_OK on success or REDISMODULE_ERR if the string is * not a valid string representation of a double value. */ -int VM_StringToDouble(const ValkeyModuleString *str, double *d) { +int RM_StringToDouble(const RedisModuleString *str, double *d) { int retval = getDoubleFromObject(str,d); - return (retval == C_OK) ? VALKEYMODULE_OK : VALKEYMODULE_ERR; + return (retval == C_OK) ? REDISMODULE_OK : REDISMODULE_ERR; } /* Convert the string into a long double, storing it at `*ld`. - * Returns VALKEYMODULE_OK on success or VALKEYMODULE_ERR if the string is + * Returns REDISMODULE_OK on success or REDISMODULE_ERR if the string is * not a valid string representation of a double value. */ -int VM_StringToLongDouble(const ValkeyModuleString *str, long double *ld) { +int RM_StringToLongDouble(const RedisModuleString *str, long double *ld) { int retval = string2ld(str->ptr,sdslen(str->ptr),ld); - return retval ? VALKEYMODULE_OK : VALKEYMODULE_ERR; + return retval ? REDISMODULE_OK : REDISMODULE_ERR; } /* Convert the string into a stream ID, storing it at `*id`. - * Returns VALKEYMODULE_OK on success and returns VALKEYMODULE_ERR if the string + * Returns REDISMODULE_OK on success and returns REDISMODULE_ERR if the string * is not a valid string representation of a stream ID. The special IDs "+" and * "-" are allowed. */ -int VM_StringToStreamID(const ValkeyModuleString *str, ValkeyModuleStreamID *id) { +int RM_StringToStreamID(const RedisModuleString *str, RedisModuleStreamID *id) { streamID streamid; if (streamParseID(str, &streamid) == C_OK) { id->ms = streamid.ms; id->seq = streamid.seq; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } else { - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } } /* Compare two string objects, returning -1, 0 or 1 respectively if * a < b, a == b, a > b. Strings are compared byte by byte as two * binary blobs without any encoding care / collation attempt. */ -int VM_StringCompare(const ValkeyModuleString *a, const ValkeyModuleString *b) { +int RM_StringCompare(const RedisModuleString *a, const RedisModuleString *b) { return compareStringObjects(a,b); } /* Return the (possibly modified in encoding) input 'str' object if * the string is unshared, otherwise NULL is returned. */ -ValkeyModuleString *moduleAssertUnsharedString(ValkeyModuleString *str) { +RedisModuleString *moduleAssertUnsharedString(RedisModuleString *str) { if (str->refcount != 1) { serverLog(LL_WARNING, "Module attempted to use an in-place string modify operation " @@ -2865,18 +2960,18 @@ ValkeyModuleString *moduleAssertUnsharedString(ValkeyModuleString *str) { /* Append the specified buffer to the string 'str'. The string must be a * string created by the user that is referenced only a single time, otherwise - * VALKEYMODULE_ERR is returned and the operation is not performed. */ -int VM_StringAppendBuffer(ValkeyModuleCtx *ctx, ValkeyModuleString *str, const char *buf, size_t len) { + * REDISMODULE_ERR is returned and the operation is not performed. */ +int RM_StringAppendBuffer(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len) { UNUSED(ctx); str = moduleAssertUnsharedString(str); - if (str == NULL) return VALKEYMODULE_ERR; + if (str == NULL) return REDISMODULE_ERR; str->ptr = sdscatlen(str->ptr,buf,len); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* Trim possible excess memory allocated for a ValkeyModuleString. +/* Trim possible excess memory allocated for a RedisModuleString. * - * Sometimes a ValkeyModuleString may have more memory allocated for + * Sometimes a RedisModuleString may have more memory allocated for * it than required, typically for argv arguments that were constructed * from network buffers. This function optimizes such strings by reallocating * their memory, which is useful for strings that are not short lived but @@ -2897,7 +2992,7 @@ int VM_StringAppendBuffer(ValkeyModuleCtx *ctx, ValkeyModuleString *str, const c * in a race condition with the auto-trim, which could result with * data corruption. */ -void VM_TrimStringAllocation(ValkeyModuleString *str) { +void RM_TrimStringAllocation(RedisModuleString *str) { if (!str) return; trimStringObjectIfNeeded(str, 1); } @@ -2907,11 +3002,11 @@ void VM_TrimStringAllocation(ValkeyModuleString *str) { * * These functions are used for sending replies to the client. * - * Most functions always return VALKEYMODULE_OK so you can use it with + * Most functions always return REDISMODULE_OK so you can use it with * 'return' in order to return from the command implementation with: * * if (... some condition ...) - * return ValkeyModule_ReplyWithLongLong(ctx,mycount); + * return RedisModule_ReplyWithLongLong(ctx,mycount); * * ### Reply with collection functions * @@ -2921,24 +3016,24 @@ void VM_TrimStringAllocation(ValkeyModuleString *str) { * * When producing collections with a number of elements that is not known * beforehand, the function can be called with a special flag - * VALKEYMODULE_POSTPONED_LEN (VALKEYMODULE_POSTPONED_ARRAY_LEN in the past), - * and the actual number of elements can be later set with VM_ReplySet*Length() + * REDISMODULE_POSTPONED_LEN (REDISMODULE_POSTPONED_ARRAY_LEN in the past), + * and the actual number of elements can be later set with RM_ReplySet*Length() * call (which will set the latest "open" count if there are multiple ones). * -------------------------------------------------------------------------- */ /* Send an error about the number of arguments given to the command, - * citing the command name in the error message. Returns VALKEYMODULE_OK. + * citing the command name in the error message. Returns REDISMODULE_OK. * * Example: * - * if (argc != 3) return ValkeyModule_WrongArity(ctx); + * if (argc != 3) return RedisModule_WrongArity(ctx); */ -int VM_WrongArity(ValkeyModuleCtx *ctx) { +int RM_WrongArity(RedisModuleCtx *ctx) { addReplyErrorArity(ctx->client); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* Return the client object the `VM_Reply*` functions should target. +/* Return the client object the `RM_Reply*` functions should target. * Normally this is just `ctx->client`, that is the client that called * the module command, however in the case of thread safe contexts there * is no directly associated client (since it would not be safe to access @@ -2952,8 +3047,8 @@ int VM_WrongArity(ValkeyModuleCtx *ctx) { * context of a thread safe context that was not initialized with a blocked * client object. Other contexts without associated clients are the ones * initialized to run the timers callbacks. */ -client *moduleGetReplyClient(ValkeyModuleCtx *ctx) { - if (ctx->flags & VALKEYMODULE_CTX_THREAD_SAFE) { +client *moduleGetReplyClient(RedisModuleCtx *ctx) { + if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) { if (ctx->blocked_client) return ctx->blocked_client->reply_client; else @@ -2968,12 +3063,12 @@ client *moduleGetReplyClient(ValkeyModuleCtx *ctx) { } /* Send an integer reply to the client, with the specified `long long` value. - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithLongLong(ValkeyModuleCtx *ctx, long long ll) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyLongLong(c,ll); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Reply with the error 'err'. @@ -2982,19 +3077,19 @@ int VM_ReplyWithLongLong(ValkeyModuleCtx *ctx, long long ll) { * the initial error code. The function only provides the initial "-", so * the usage is, for example: * - * ValkeyModule_ReplyWithError(ctx,"ERR Wrong Type"); + * RedisModule_ReplyWithError(ctx,"ERR Wrong Type"); * * and not just: * - * ValkeyModule_ReplyWithError(ctx,"Wrong Type"); + * RedisModule_ReplyWithError(ctx,"Wrong Type"); * - * The function always returns VALKEYMODULE_OK. + * The function always returns REDISMODULE_OK. */ -int VM_ReplyWithError(ValkeyModuleCtx *ctx, const char *err) { +int RM_ReplyWithError(RedisModuleCtx *ctx, const char *err) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyErrorFormat(c,"-%s",err); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Reply with the error create from a printf format and arguments. @@ -3003,17 +3098,17 @@ int VM_ReplyWithError(ValkeyModuleCtx *ctx, const char *err) { * the initial error code. The function only provides the initial "-", so * the usage is, for example: * - * ValkeyModule_ReplyWithErrorFormat(ctx,"ERR Wrong Type: %s",type); + * RedisModule_ReplyWithErrorFormat(ctx,"ERR Wrong Type: %s",type); * * and not just: * - * ValkeyModule_ReplyWithErrorFormat(ctx,"Wrong Type: %s",type); + * RedisModule_ReplyWithErrorFormat(ctx,"Wrong Type: %s",type); * - * The function always returns VALKEYMODULE_OK. + * The function always returns REDISMODULE_OK. */ -int VM_ReplyWithErrorFormat(ValkeyModuleCtx *ctx, const char *fmt, ...) { +int RM_ReplyWithErrorFormat(RedisModuleCtx *ctx, const char *fmt, ...) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; int len = strlen(fmt) + 2; /* 1 for the \0 and 1 for the hyphen */ char *hyphenfmt = zmalloc(len); @@ -3026,21 +3121,21 @@ int VM_ReplyWithErrorFormat(ValkeyModuleCtx *ctx, const char *fmt, ...) { zfree(hyphenfmt); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Reply with a simple string (`+... \r\n` in RESP protocol). This replies * are suitable only when sending a small non-binary string with small * overhead, like "OK" or similar replies. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithSimpleString(ValkeyModuleCtx *ctx, const char *msg) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyProto(c,"+",1); addReplyProto(c,msg,strlen(msg)); addReplyProto(c,"\r\n",2); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } #define COLLECTION_REPLY_ARRAY 1 @@ -3048,10 +3143,10 @@ int VM_ReplyWithSimpleString(ValkeyModuleCtx *ctx, const char *msg) { #define COLLECTION_REPLY_SET 3 #define COLLECTION_REPLY_ATTRIBUTE 4 -int moduleReplyWithCollection(ValkeyModuleCtx *ctx, long len, int type) { +int moduleReplyWithCollection(RedisModuleCtx *ctx, long len, int type) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; - if (len == VALKEYMODULE_POSTPONED_LEN) { + if (c == NULL) return REDISMODULE_OK; + if (len == REDISMODULE_POSTPONED_LEN) { ctx->postponed_arrays = zrealloc(ctx->postponed_arrays,sizeof(void*)* (ctx->postponed_arrays_count+1)); ctx->postponed_arrays[ctx->postponed_arrays_count] = @@ -3091,7 +3186,7 @@ int moduleReplyWithCollection(ValkeyModuleCtx *ctx, long len, int type) { serverPanic("Invalid module reply type %d", type); } } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Reply with an array type of 'len' elements. @@ -3100,10 +3195,10 @@ int moduleReplyWithCollection(ValkeyModuleCtx *ctx, long len, int type) { * `ReplyWith*` style functions in order to emit the elements of the array. * See Reply APIs section for more details. * - * Use VM_ReplySetArrayLength() to set deferred length. + * Use RM_ReplySetArrayLength() to set deferred length. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithArray(ValkeyModuleCtx *ctx, long len) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) { return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_ARRAY); } @@ -3117,10 +3212,10 @@ int VM_ReplyWithArray(ValkeyModuleCtx *ctx, long len) { * If the connected client is using RESP2, the reply will be converted to a flat * array. * - * Use VM_ReplySetMapLength() to set deferred length. + * Use RM_ReplySetMapLength() to set deferred length. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithMap(ValkeyModuleCtx *ctx, long len) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithMap(RedisModuleCtx *ctx, long len) { return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_MAP); } @@ -3134,10 +3229,10 @@ int VM_ReplyWithMap(ValkeyModuleCtx *ctx, long len) { * If the connected client is using RESP2, the reply will be converted to an * array type. * - * Use VM_ReplySetSetLength() to set deferred length. + * Use RM_ReplySetSetLength() to set deferred length. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithSet(ValkeyModuleCtx *ctx, long len) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithSet(RedisModuleCtx *ctx, long len) { return moduleReplyWithCollection(ctx, len, COLLECTION_REPLY_SET); } @@ -3149,12 +3244,12 @@ int VM_ReplyWithSet(ValkeyModuleCtx *ctx, long len) { * `ReplyWith*` style functions in order to emit the elements of the attribute map. * See Reply APIs section for more details. * - * Use VM_ReplySetAttributeLength() to set deferred length. + * Use RM_ReplySetAttributeLength() to set deferred length. * - * Not supported by RESP2 and will return VALKEYMODULE_ERR, otherwise - * the function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithAttribute(ValkeyModuleCtx *ctx, long len) { - if (ctx->client->resp == 2) return VALKEYMODULE_ERR; + * 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); } @@ -3164,34 +3259,34 @@ int VM_ReplyWithAttribute(ValkeyModuleCtx *ctx, long len) { * * Note: In RESP3 there's no difference between Null reply and * NullArray reply, so to prevent ambiguity it's better to avoid - * using this API and use ValkeyModule_ReplyWithNull instead. + * using this API and use RedisModule_ReplyWithNull instead. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithNullArray(ValkeyModuleCtx *ctx) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithNullArray(RedisModuleCtx *ctx) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyNullArray(c); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Reply to the client with an empty array. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithEmptyArray(ValkeyModuleCtx *ctx) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithEmptyArray(RedisModuleCtx *ctx) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReply(c,shared.emptyarray); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -void moduleReplySetCollectionLength(ValkeyModuleCtx *ctx, long len, int type) { +void moduleReplySetCollectionLength(RedisModuleCtx *ctx, long len, int type) { client *c = moduleGetReplyClient(ctx); if (c == NULL) return; if (ctx->postponed_arrays_count == 0) { serverLog(LL_WARNING, "API misuse detected in module %s: " - "ValkeyModule_ReplySet*Length() called without previous " - "ValkeyModule_ReplyWith*(ctx,VALKEYMODULE_POSTPONED_LEN) " + "RedisModule_ReplySet*Length() called without previous " + "RedisModule_ReplyWith*(ctx,REDISMODULE_POSTPONED_LEN) " "call.", ctx->module->name); return; } @@ -3218,8 +3313,8 @@ void moduleReplySetCollectionLength(ValkeyModuleCtx *ctx, long len, int type) { } } -/* When ValkeyModule_ReplyWithArray() is used with the argument - * VALKEYMODULE_POSTPONED_LEN, because we don't know beforehand the number +/* When RedisModule_ReplyWithArray() is used with the argument + * REDISMODULE_POSTPONED_LEN, because we don't know beforehand the number * of items we are going to output as elements of the array, this function * will take care to set the array length. * @@ -3230,115 +3325,115 @@ void moduleReplySetCollectionLength(ValkeyModuleCtx *ctx, long len, int type) { * For example in order to output an array like [1,[10,20,30]] we * could write: * - * ValkeyModule_ReplyWithArray(ctx,VALKEYMODULE_POSTPONED_LEN); - * ValkeyModule_ReplyWithLongLong(ctx,1); - * ValkeyModule_ReplyWithArray(ctx,VALKEYMODULE_POSTPONED_LEN); - * ValkeyModule_ReplyWithLongLong(ctx,10); - * ValkeyModule_ReplyWithLongLong(ctx,20); - * ValkeyModule_ReplyWithLongLong(ctx,30); - * ValkeyModule_ReplySetArrayLength(ctx,3); // Set len of 10,20,30 array. - * ValkeyModule_ReplySetArrayLength(ctx,2); // Set len of top array + * RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_LEN); + * RedisModule_ReplyWithLongLong(ctx,1); + * RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_LEN); + * RedisModule_ReplyWithLongLong(ctx,10); + * RedisModule_ReplyWithLongLong(ctx,20); + * RedisModule_ReplyWithLongLong(ctx,30); + * RedisModule_ReplySetArrayLength(ctx,3); // Set len of 10,20,30 array. + * RedisModule_ReplySetArrayLength(ctx,2); // Set len of top array * * Note that in the above example there is no reason to postpone the array * length, since we produce a fixed number of elements, but in the practice * the code may use an iterator or other ways of creating the output so * that is not easy to calculate in advance the number of elements. */ -void VM_ReplySetArrayLength(ValkeyModuleCtx *ctx, long len) { +void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) { moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_ARRAY); } -/* Very similar to ValkeyModule_ReplySetArrayLength except `len` should +/* Very similar to RedisModule_ReplySetArrayLength except `len` should * exactly half of the number of `ReplyWith*` functions called in the * context of the map. * Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. */ -void VM_ReplySetMapLength(ValkeyModuleCtx *ctx, long len) { +void RM_ReplySetMapLength(RedisModuleCtx *ctx, long len) { moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_MAP); } -/* Very similar to ValkeyModule_ReplySetArrayLength +/* Very similar to RedisModule_ReplySetArrayLength * Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. */ -void VM_ReplySetSetLength(ValkeyModuleCtx *ctx, long len) { +void RM_ReplySetSetLength(RedisModuleCtx *ctx, long len) { moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_SET); } -/* Very similar to ValkeyModule_ReplySetMapLength +/* Very similar to RedisModule_ReplySetMapLength * Visit https://github.com/antirez/RESP3/blob/master/spec.md for more info about RESP3. * - * Must not be called if VM_ReplyWithAttribute returned an error. */ -void VM_ReplySetAttributeLength(ValkeyModuleCtx *ctx, long len) { + * Must not be called if RM_ReplyWithAttribute returned an error. */ +void RM_ReplySetAttributeLength(RedisModuleCtx *ctx, long len) { if (ctx->client->resp == 2) return; moduleReplySetCollectionLength(ctx, len, COLLECTION_REPLY_ATTRIBUTE); } /* Reply with a bulk string, taking in input a C buffer pointer and length. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithStringBuffer(ValkeyModuleCtx *ctx, const char *buf, size_t len) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyBulkCBuffer(c,(char*)buf,len); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Reply with a bulk string, taking in input a C buffer pointer that is * assumed to be null-terminated. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithCString(ValkeyModuleCtx *ctx, const char *buf) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithCString(RedisModuleCtx *ctx, const char *buf) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyBulkCString(c,(char*)buf); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* Reply with a bulk string, taking in input a ValkeyModuleString object. +/* Reply with a bulk string, taking in input a RedisModuleString object. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithString(ValkeyModuleCtx *ctx, ValkeyModuleString *str) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyBulk(c,str); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Reply with an empty string. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithEmptyString(ValkeyModuleCtx *ctx) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithEmptyString(RedisModuleCtx *ctx) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReply(c,shared.emptybulk); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Reply with a binary safe string, which should not be escaped or filtered * taking in input a C buffer pointer, length and a 3 character type/extension. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithVerbatimStringType(ValkeyModuleCtx *ctx, const char *buf, size_t len, const char *ext) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithVerbatimStringType(RedisModuleCtx *ctx, const char *buf, size_t len, const char *ext) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyVerbatim(c, buf, len, ext); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Reply with a binary safe string, which should not be escaped or filtered * taking in input a C buffer pointer and length. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithVerbatimString(ValkeyModuleCtx *ctx, const char *buf, size_t len) { - return VM_ReplyWithVerbatimStringType(ctx, buf, len, "txt"); + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithVerbatimString(RedisModuleCtx *ctx, const char *buf, size_t len) { + return RM_ReplyWithVerbatimStringType(ctx, buf, len, "txt"); } /* Reply to the client with a NULL. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithNull(ValkeyModuleCtx *ctx) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithNull(RedisModuleCtx *ctx) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyNull(c); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Reply with a RESP3 Boolean type. @@ -3347,34 +3442,34 @@ int VM_ReplyWithNull(ValkeyModuleCtx *ctx) { * In RESP3, this is boolean type * In RESP2, it's a string response of "1" and "0" for true and false respectively. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithBool(ValkeyModuleCtx *ctx, int b) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithBool(RedisModuleCtx *ctx, int b) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyBool(c,b); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* Reply exactly what a Redis command returned us with ValkeyModule_Call(). - * This function is useful when we use ValkeyModule_Call() in order to +/* Reply exactly what a Redis command returned us with RedisModule_Call(). + * This function is useful when we use RedisModule_Call() in order to * execute some command, as we want to reply to the client exactly the * same reply we obtained by the command. * * Return: - * - VALKEYMODULE_OK on success. - * - VALKEYMODULE_ERR if the given reply is in RESP3 format but the client expects RESP2. + * - REDISMODULE_OK on success. + * - REDISMODULE_ERR if the given reply is in RESP3 format but the client expects RESP2. * In case of an error, it's the module writer responsibility to translate the reply * to RESP2 (or handle it differently by returning an error). Notice that for * module writer convenience, it is possible to pass `0` as a parameter to the fmt - * argument of `VM_Call` so that the ValkeyModuleCallReply will return in the same + * argument of `RM_Call` so that the RedisModuleCallReply will return in the same * protocol (RESP2 or RESP3) as set in the current client's context. */ -int VM_ReplyWithCallReply(ValkeyModuleCtx *ctx, ValkeyModuleCallReply *reply) { +int RM_ReplyWithCallReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; if (c->resp == 2 && callReplyIsResp3(reply)) { /* The reply is in RESP3 format and the client is RESP2, * so it isn't possible to send this reply to the client. */ - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } size_t proto_len; const char *proto = callReplyGetProto(reply, &proto_len); @@ -3388,7 +3483,7 @@ int VM_ReplyWithCallReply(ValkeyModuleCtx *ctx, ValkeyModuleCallReply *reply) { list *errors = callReplyDeferredErrorList(reply); if (errors) deferredAfterErrorReply(c, errors); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Reply with a RESP3 Double type. @@ -3397,17 +3492,17 @@ int VM_ReplyWithCallReply(ValkeyModuleCtx *ctx, ValkeyModuleCallReply *reply) { * Send a string reply obtained converting the double 'd' into a bulk string. * This function is basically equivalent to converting a double into * a string into a C buffer, and then calling the function - * ValkeyModule_ReplyWithStringBuffer() with the buffer and length. + * 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 * that the user will have to parse. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithDouble(ValkeyModuleCtx *ctx, double d) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithDouble(RedisModuleCtx *ctx, double d) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyDouble(c,d); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Reply with a RESP3 BigNumber type. @@ -3417,27 +3512,27 @@ int VM_ReplyWithDouble(ValkeyModuleCtx *ctx, double d) { * 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. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithBigNumber(ValkeyModuleCtx *ctx, const char *bignum, size_t len) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithBigNumber(RedisModuleCtx *ctx, const char *bignum, size_t len) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyBigNum(c, bignum, len); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Send a string reply obtained converting the long double 'ld' into a bulk * string. This function is basically equivalent to converting a long double * into a string into a C buffer, and then calling the function - * ValkeyModule_ReplyWithStringBuffer() with the buffer and length. + * RedisModule_ReplyWithStringBuffer() with the buffer and length. * The double string uses human readable formatting (see * `addReplyHumanLongDouble` in networking.c). * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplyWithLongDouble(ValkeyModuleCtx *ctx, long double ld) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplyWithLongDouble(RedisModuleCtx *ctx, long double ld) { client *c = moduleGetReplyClient(ctx); - if (c == NULL) return VALKEYMODULE_OK; + if (c == NULL) return REDISMODULE_OK; addReplyHumanLongDouble(c, ld); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* -------------------------------------------------------------------------- @@ -3449,17 +3544,17 @@ int VM_ReplyWithLongDouble(ValkeyModuleCtx *ctx, long double ld) { * * The replicated commands are always wrapped into the MULTI/EXEC that * contains all the commands replicated in a given module command - * execution. However the commands replicated with ValkeyModule_Call() - * are the first items, the ones replicated with ValkeyModule_Replicate() + * execution. However the commands replicated with RedisModule_Call() + * are the first items, the ones replicated with RedisModule_Replicate() * will all follow before the EXEC. * * Modules should try to use one interface or the other. * - * This command follows exactly the same interface of ValkeyModule_Call(), + * This command follows exactly the same interface of RedisModule_Call(), * so a set of format specifiers must be passed, followed by arguments * matching the provided format specifiers. * - * Please refer to ValkeyModule_Call() for more information. + * Please refer to RedisModule_Call() for more information. * * Using the special "A" and "R" modifiers, the caller can exclude either * the AOF or the replicas from the propagation of the specified command. @@ -3479,29 +3574,29 @@ int VM_ReplyWithLongDouble(ValkeyModuleCtx *ctx, long double ld) { * * #### Return value * - * The command returns VALKEYMODULE_ERR if the format specifiers are invalid + * The command returns REDISMODULE_ERR if the format specifiers are invalid * or the command name does not belong to a known command. */ -int VM_Replicate(ValkeyModuleCtx *ctx, const char *cmdname, const char *fmt, ...) { +int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) { struct redisCommand *cmd; robj **argv = NULL; int argc = 0, flags = 0, j; va_list ap; cmd = lookupCommandByCString((char*)cmdname); - if (!cmd) return VALKEYMODULE_ERR; + if (!cmd) return REDISMODULE_ERR; /* Create the client and dispatch the command. */ va_start(ap, fmt); argv = moduleCreateArgvFromUserFormat(cmdname,fmt,&argc,&flags,ap); va_end(ap); - if (argv == NULL) return VALKEYMODULE_ERR; + if (argv == NULL) return REDISMODULE_ERR; /* Select the propagation target. Usually is AOF + replicas, however * the caller can exclude one or the other using the "A" or "R" * modifiers. */ int target = 0; - if (!(flags & VALKEYMODULE_ARGV_NO_AOF)) target |= PROPAGATE_AOF; - if (!(flags & VALKEYMODULE_ARGV_NO_REPLICAS)) target |= PROPAGATE_REPL; + if (!(flags & REDISMODULE_ARGV_NO_AOF)) target |= PROPAGATE_AOF; + if (!(flags & REDISMODULE_ARGV_NO_REPLICAS)) target |= PROPAGATE_REPL; alsoPropagate(ctx->client->db->id,argv,argc,target); @@ -3509,7 +3604,7 @@ int VM_Replicate(ValkeyModuleCtx *ctx, const char *cmdname, const char *fmt, ... for (j = 0; j < argc; j++) decrRefCount(argv[j]); zfree(argv); server.dirty++; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* This function will replicate the command exactly as it was invoked @@ -3522,13 +3617,13 @@ int VM_Replicate(ValkeyModuleCtx *ctx, const char *cmdname, const char *fmt, ... * the command can just be re-executed to deterministically re-create the * new state starting from the old one. * - * The function always returns VALKEYMODULE_OK. */ -int VM_ReplicateVerbatim(ValkeyModuleCtx *ctx) { + * The function always returns REDISMODULE_OK. */ +int RM_ReplicateVerbatim(RedisModuleCtx *ctx) { alsoPropagate(ctx->client->db->id, ctx->client->argv,ctx->client->argc, PROPAGATE_AOF|PROPAGATE_REPL); server.dirty++; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* -------------------------------------------------------------------------- @@ -3550,20 +3645,20 @@ int VM_ReplicateVerbatim(ValkeyModuleCtx *ctx) { * After obtaining the ID, it is possible to check if the command execution * is actually happening in the context of AOF loading, using this macro: * - * if (ValkeyModule_IsAOFClient(ValkeyModule_GetClientId(ctx)) { + * if (RedisModule_IsAOFClient(RedisModule_GetClientId(ctx)) { * // Handle it differently. * } */ -unsigned long long VM_GetClientId(ValkeyModuleCtx *ctx) { +unsigned long long RM_GetClientId(RedisModuleCtx *ctx) { if (ctx->client == NULL) return 0; return ctx->client->id; } /* Return the ACL user name used by the client with the specified client ID. - * Client ID can be obtained with VM_GetClientId() API. If the client does not + * Client ID can be obtained with RM_GetClientId() API. If the client does not * exist, NULL is returned and errno is set to ENOENT. If the client isn't * using an ACL user, NULL is returned and errno is set to ENOTSUP */ -ValkeyModuleString *VM_GetClientUserNameById(ValkeyModuleCtx *ctx, uint64_t id) { +RedisModuleString *RM_GetClientUserNameById(RedisModuleCtx *ctx, uint64_t id) { client *client = lookupClientByID(id); if (client == NULL) { errno = ENOENT; @@ -3577,52 +3672,52 @@ ValkeyModuleString *VM_GetClientUserNameById(ValkeyModuleCtx *ctx, uint64_t id) sds name = sdsnew(client->user->name); robj *str = createObject(OBJ_STRING, name); - autoMemoryAdd(ctx, VALKEYMODULE_AM_STRING, str); + autoMemoryAdd(ctx, REDISMODULE_AM_STRING, str); return str; } -/* This is a helper for VM_GetClientInfoById() and other functions: given +/* This is a helper for RM_GetClientInfoById() and other functions: given * a client, it populates the client info structure with the appropriate * fields depending on the version provided. If the version is not valid - * then VALKEYMODULE_ERR is returned. Otherwise the function returns - * VALKEYMODULE_OK and the structure pointed by 'ci' gets populated. */ + * then REDISMODULE_ERR is returned. Otherwise the function returns + * REDISMODULE_OK and the structure pointed by 'ci' gets populated. */ int modulePopulateClientInfoStructure(void *ci, client *client, int structver) { - if (structver != 1) return VALKEYMODULE_ERR; + if (structver != 1) return REDISMODULE_ERR; - ValkeyModuleClientInfoV1 *ci1 = ci; + RedisModuleClientInfoV1 *ci1 = ci; memset(ci1,0,sizeof(*ci1)); ci1->version = structver; if (client->flags & CLIENT_MULTI) - ci1->flags |= VALKEYMODULE_CLIENTINFO_FLAG_MULTI; + ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_MULTI; if (client->flags & CLIENT_PUBSUB) - ci1->flags |= VALKEYMODULE_CLIENTINFO_FLAG_PUBSUB; + ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_PUBSUB; if (client->flags & CLIENT_UNIX_SOCKET) - ci1->flags |= VALKEYMODULE_CLIENTINFO_FLAG_UNIXSOCKET; + ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_UNIXSOCKET; if (client->flags & CLIENT_TRACKING) - ci1->flags |= VALKEYMODULE_CLIENTINFO_FLAG_TRACKING; + ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_TRACKING; if (client->flags & CLIENT_BLOCKED) - ci1->flags |= VALKEYMODULE_CLIENTINFO_FLAG_BLOCKED; + ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_BLOCKED; if (client->conn->type == connectionTypeTls()) - ci1->flags |= VALKEYMODULE_CLIENTINFO_FLAG_SSL; + ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_SSL; int port; connAddrPeerName(client->conn,ci1->addr,sizeof(ci1->addr),&port); ci1->port = port; ci1->db = client->db->id; ci1->id = client->id; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* This is a helper for moduleFireServerEvent() and other functions: * It populates the replication info structure with the appropriate * fields depending on the version provided. If the version is not valid - * then VALKEYMODULE_ERR is returned. Otherwise the function returns - * VALKEYMODULE_OK and the structure pointed by 'ri' gets populated. */ + * then REDISMODULE_ERR is returned. Otherwise the function returns + * REDISMODULE_OK and the structure pointed by 'ri' gets populated. */ int modulePopulateReplicationInfoStructure(void *ri, int structver) { - if (structver != 1) return VALKEYMODULE_ERR; + if (structver != 1) return REDISMODULE_ERR; - ValkeyModuleReplicationInfoV1 *ri1 = ri; + RedisModuleReplicationInfoV1 *ri1 = ri; memset(ri1,0,sizeof(*ri1)); ri1->version = structver; ri1->master = server.masterhost==NULL; @@ -3632,20 +3727,20 @@ int modulePopulateReplicationInfoStructure(void *ri, int structver) { ri1->replid2 = server.replid2; ri1->repl1_offset = server.master_repl_offset; ri1->repl2_offset = server.second_replid_offset; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Return information about the client with the specified ID (that was - * previously obtained via the ValkeyModule_GetClientId() API). If the - * client exists, VALKEYMODULE_OK is returned, otherwise VALKEYMODULE_ERR + * previously obtained via the RedisModule_GetClientId() API). If the + * client exists, REDISMODULE_OK is returned, otherwise REDISMODULE_ERR * is returned. * * When the client exist and the `ci` pointer is not NULL, but points to - * a structure of type ValkeyModuleClientInfoV1, previously initialized with - * the correct VALKEYMODULE_CLIENTINFO_INITIALIZER_V1, the structure is populated + * a structure of type RedisModuleClientInfoV1, previously initialized with + * the correct REDISMODULE_CLIENTINFO_INITIALIZER_V1, the structure is populated * with the following fields: * - * uint64_t flags; // VALKEYMODULE_CLIENTINFO_FLAG_* + * uint64_t flags; // REDISMODULE_CLIENTINFO_FLAG_* * uint64_t id; // Client ID * char addr[46]; // IPv4 or IPv6 address. * uint16_t port; // TCP port. @@ -3658,12 +3753,12 @@ int modulePopulateReplicationInfoStructure(void *ri, int structver) { * * With flags having the following meaning: * - * VALKEYMODULE_CLIENTINFO_FLAG_SSL Client using SSL connection. - * VALKEYMODULE_CLIENTINFO_FLAG_PUBSUB Client in Pub/Sub mode. - * VALKEYMODULE_CLIENTINFO_FLAG_BLOCKED Client blocked in command. - * VALKEYMODULE_CLIENTINFO_FLAG_TRACKING Client with keys tracking on. - * VALKEYMODULE_CLIENTINFO_FLAG_UNIXSOCKET Client using unix domain socket. - * VALKEYMODULE_CLIENTINFO_FLAG_MULTI Client in MULTI state. + * REDISMODULE_CLIENTINFO_FLAG_SSL Client using SSL connection. + * REDISMODULE_CLIENTINFO_FLAG_PUBSUB Client in Pub/Sub mode. + * REDISMODULE_CLIENTINFO_FLAG_BLOCKED Client blocked in command. + * REDISMODULE_CLIENTINFO_FLAG_TRACKING Client with keys tracking on. + * REDISMODULE_CLIENTINFO_FLAG_UNIXSOCKET Client using unix domain socket. + * REDISMODULE_CLIENTINFO_FLAG_MULTI Client in MULTI state. * * However passing NULL is a way to just check if the client exists in case * we are not interested in any additional information. @@ -3671,16 +3766,16 @@ int modulePopulateReplicationInfoStructure(void *ri, int structver) { * This is the correct usage when we want the client info structure * returned: * - * ValkeyModuleClientInfo ci = VALKEYMODULE_CLIENTINFO_INITIALIZER; - * int retval = ValkeyModule_GetClientInfoById(&ci,client_id); - * if (retval == VALKEYMODULE_OK) { + * RedisModuleClientInfo ci = REDISMODULE_CLIENTINFO_INITIALIZER; + * int retval = RedisModule_GetClientInfoById(&ci,client_id); + * if (retval == REDISMODULE_OK) { * printf("Address: %s\n", ci.addr); * } */ -int VM_GetClientInfoById(void *ci, uint64_t id) { +int RM_GetClientInfoById(void *ci, uint64_t id) { client *client = lookupClientByID(id); - if (client == NULL) return VALKEYMODULE_ERR; - if (ci == NULL) return VALKEYMODULE_OK; + if (client == NULL) return REDISMODULE_ERR; + if (ci == NULL) return REDISMODULE_OK; /* Fill the info structure if passed. */ uint64_t structver = ((uint64_t*)ci)[0]; @@ -3691,50 +3786,50 @@ int VM_GetClientInfoById(void *ci, uint64_t id) { * * If the client ID does not exist or if the client has no name associated with * it, NULL is returned. */ -ValkeyModuleString *VM_GetClientNameById(ValkeyModuleCtx *ctx, uint64_t id) { +RedisModuleString *RM_GetClientNameById(RedisModuleCtx *ctx, uint64_t id) { client *client = lookupClientByID(id); if (client == NULL || client->name == NULL) return NULL; robj *name = client->name; incrRefCount(name); - autoMemoryAdd(ctx, VALKEYMODULE_AM_STRING, name); + autoMemoryAdd(ctx, REDISMODULE_AM_STRING, name); return name; } /* Sets the name of the client with the given ID. This is equivalent to the client calling * `CLIENT SETNAME name`. * - * Returns VALKEYMODULE_OK on success. On failure, VALKEYMODULE_ERR is returned + * Returns REDISMODULE_OK on success. On failure, REDISMODULE_ERR is returned * and errno is set as follows: * * - ENOENT if the client does not exist * - EINVAL if the name contains invalid characters */ -int VM_SetClientNameById(uint64_t id, ValkeyModuleString *name) { +int RM_SetClientNameById(uint64_t id, RedisModuleString *name) { client *client = lookupClientByID(id); if (client == NULL) { errno = ENOENT; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } if (clientSetName(client, name, NULL) == C_ERR) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Publish a message to subscribers (see PUBLISH command). */ -int VM_PublishMessage(ValkeyModuleCtx *ctx, ValkeyModuleString *channel, ValkeyModuleString *message) { +int RM_PublishMessage(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) { UNUSED(ctx); return pubsubPublishMessageAndPropagateToCluster(channel, message, 0); } /* Publish a message to shard-subscribers (see SPUBLISH command). */ -int VM_PublishMessageShard(ValkeyModuleCtx *ctx, ValkeyModuleString *channel, ValkeyModuleString *message) { +int RM_PublishMessageShard(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) { UNUSED(ctx); return pubsubPublishMessageAndPropagateToCluster(channel, message, 1); } /* Return the currently selected DB. */ -int VM_GetSelectedDb(ValkeyModuleCtx *ctx) { +int RM_GetSelectedDb(RedisModuleCtx *ctx) { return ctx->client->db->id; } @@ -3750,151 +3845,151 @@ int VM_GetSelectedDb(ValkeyModuleCtx *ctx) { * * Available flags and their meaning: * - * * VALKEYMODULE_CTX_FLAGS_LUA: The command is running in a Lua script + * * REDISMODULE_CTX_FLAGS_LUA: The command is running in a Lua script * - * * VALKEYMODULE_CTX_FLAGS_MULTI: The command is running inside a transaction + * * REDISMODULE_CTX_FLAGS_MULTI: The command is running inside a transaction * - * * VALKEYMODULE_CTX_FLAGS_REPLICATED: The command was sent over the replication + * * REDISMODULE_CTX_FLAGS_REPLICATED: The command was sent over the replication * link by the MASTER * - * * VALKEYMODULE_CTX_FLAGS_PRIMARY: The Redis instance is a primary + * * REDISMODULE_CTX_FLAGS_MASTER: The Redis instance is a master * - * * VALKEYMODULE_CTX_FLAGS_REPLICA: The Redis instance is a replica + * * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a slave * - * * VALKEYMODULE_CTX_FLAGS_READONLY: The Redis instance is read-only + * * REDISMODULE_CTX_FLAGS_READONLY: The Redis instance is read-only * - * * VALKEYMODULE_CTX_FLAGS_CLUSTER: The Redis instance is in cluster mode + * * REDISMODULE_CTX_FLAGS_CLUSTER: The Redis instance is in cluster mode * - * * VALKEYMODULE_CTX_FLAGS_AOF: The Redis instance has AOF enabled + * * REDISMODULE_CTX_FLAGS_AOF: The Redis instance has AOF enabled * - * * VALKEYMODULE_CTX_FLAGS_RDB: The instance has RDB enabled + * * REDISMODULE_CTX_FLAGS_RDB: The instance has RDB enabled * - * * VALKEYMODULE_CTX_FLAGS_MAXMEMORY: The instance has Maxmemory set + * * REDISMODULE_CTX_FLAGS_MAXMEMORY: The instance has Maxmemory set * - * * VALKEYMODULE_CTX_FLAGS_EVICT: Maxmemory is set and has an eviction + * * REDISMODULE_CTX_FLAGS_EVICT: Maxmemory is set and has an eviction * policy that may delete keys * - * * VALKEYMODULE_CTX_FLAGS_OOM: Redis is out of memory according to the + * * REDISMODULE_CTX_FLAGS_OOM: Redis is out of memory according to the * maxmemory setting. * - * * VALKEYMODULE_CTX_FLAGS_OOM_WARNING: Less than 25% of memory remains before + * * REDISMODULE_CTX_FLAGS_OOM_WARNING: Less than 25% of memory remains before * reaching the maxmemory level. * - * * VALKEYMODULE_CTX_FLAGS_LOADING: Server is loading RDB/AOF + * * REDISMODULE_CTX_FLAGS_LOADING: Server is loading RDB/AOF * - * * VALKEYMODULE_CTX_FLAGS_REPLICA_IS_STALE: No active link with the master. + * * REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE: No active link with the master. * - * * VALKEYMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING: The replica is trying to + * * REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING: The replica is trying to * connect with the master. * - * * VALKEYMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING: Master -> Replica RDB + * * REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING: Master -> Replica RDB * transfer is in progress. * - * * VALKEYMODULE_CTX_FLAGS_REPLICA_IS_ONLINE: The replica has an active link + * * REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE: The replica has an active link * with its master. This is the * contrary of STALE state. * - * * VALKEYMODULE_CTX_FLAGS_ACTIVE_CHILD: There is currently some background + * * REDISMODULE_CTX_FLAGS_ACTIVE_CHILD: There is currently some background * process active (RDB, AUX or module). * - * * VALKEYMODULE_CTX_FLAGS_MULTI_DIRTY: The next EXEC will fail due to dirty + * * REDISMODULE_CTX_FLAGS_MULTI_DIRTY: The next EXEC will fail due to dirty * CAS (touched keys). * - * * VALKEYMODULE_CTX_FLAGS_IS_CHILD: Redis is currently running inside + * * REDISMODULE_CTX_FLAGS_IS_CHILD: Redis is currently running inside * background child process. * - * * VALKEYMODULE_CTX_FLAGS_RESP3: Indicate the that client attached to this + * * REDISMODULE_CTX_FLAGS_RESP3: Indicate the that client attached to this * context is using RESP3. * - * * VALKEYMODULE_CTX_FLAGS_SERVER_STARTUP: The Redis instance is starting + * * REDISMODULE_CTX_FLAGS_SERVER_STARTUP: The Redis instance is starting */ -int VM_GetContextFlags(ValkeyModuleCtx *ctx) { +int RM_GetContextFlags(RedisModuleCtx *ctx) { int flags = 0; /* Client specific flags */ if (ctx) { if (ctx->client) { if (ctx->client->flags & CLIENT_DENY_BLOCKING) - flags |= VALKEYMODULE_CTX_FLAGS_DENY_BLOCKING; + flags |= REDISMODULE_CTX_FLAGS_DENY_BLOCKING; /* Module command received from MASTER, is replicated. */ if (ctx->client->flags & CLIENT_MASTER) - flags |= VALKEYMODULE_CTX_FLAGS_REPLICATED; + flags |= REDISMODULE_CTX_FLAGS_REPLICATED; if (ctx->client->resp == 3) { - flags |= VALKEYMODULE_CTX_FLAGS_RESP3; + flags |= REDISMODULE_CTX_FLAGS_RESP3; } } /* For DIRTY flags, we need the blocked client if used */ client *c = ctx->blocked_client ? ctx->blocked_client->client : ctx->client; if (c && (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC))) { - flags |= VALKEYMODULE_CTX_FLAGS_MULTI_DIRTY; + flags |= REDISMODULE_CTX_FLAGS_MULTI_DIRTY; } } if (scriptIsRunning()) - flags |= VALKEYMODULE_CTX_FLAGS_LUA; + flags |= REDISMODULE_CTX_FLAGS_LUA; if (server.in_exec) - flags |= VALKEYMODULE_CTX_FLAGS_MULTI; + flags |= REDISMODULE_CTX_FLAGS_MULTI; if (server.cluster_enabled) - flags |= VALKEYMODULE_CTX_FLAGS_CLUSTER; + flags |= REDISMODULE_CTX_FLAGS_CLUSTER; if (server.async_loading) - flags |= VALKEYMODULE_CTX_FLAGS_ASYNC_LOADING; + flags |= REDISMODULE_CTX_FLAGS_ASYNC_LOADING; else if (server.loading) - flags |= VALKEYMODULE_CTX_FLAGS_LOADING; + flags |= REDISMODULE_CTX_FLAGS_LOADING; /* Maxmemory and eviction policy */ if (server.maxmemory > 0 && (!server.masterhost || !server.repl_slave_ignore_maxmemory)) { - flags |= VALKEYMODULE_CTX_FLAGS_MAXMEMORY; + flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY; if (server.maxmemory_policy != MAXMEMORY_NO_EVICTION) - flags |= VALKEYMODULE_CTX_FLAGS_EVICT; + flags |= REDISMODULE_CTX_FLAGS_EVICT; } /* Persistence flags */ if (server.aof_state != AOF_OFF) - flags |= VALKEYMODULE_CTX_FLAGS_AOF; + flags |= REDISMODULE_CTX_FLAGS_AOF; if (server.saveparamslen > 0) - flags |= VALKEYMODULE_CTX_FLAGS_RDB; + flags |= REDISMODULE_CTX_FLAGS_RDB; /* Replication flags */ if (server.masterhost == NULL) { - flags |= VALKEYMODULE_CTX_FLAGS_PRIMARY; + flags |= REDISMODULE_CTX_FLAGS_MASTER; } else { - flags |= VALKEYMODULE_CTX_FLAGS_REPLICA; + flags |= REDISMODULE_CTX_FLAGS_SLAVE; if (server.repl_slave_ro) - flags |= VALKEYMODULE_CTX_FLAGS_READONLY; + flags |= REDISMODULE_CTX_FLAGS_READONLY; /* Replica state flags. */ if (server.repl_state == REPL_STATE_CONNECT || server.repl_state == REPL_STATE_CONNECTING) { - flags |= VALKEYMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING; + flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING; } else if (server.repl_state == REPL_STATE_TRANSFER) { - flags |= VALKEYMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING; + flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING; } else if (server.repl_state == REPL_STATE_CONNECTED) { - flags |= VALKEYMODULE_CTX_FLAGS_REPLICA_IS_ONLINE; + flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE; } if (server.repl_state != REPL_STATE_CONNECTED) - flags |= VALKEYMODULE_CTX_FLAGS_REPLICA_IS_STALE; + flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE; } /* OOM flag. */ float level; int retval = getMaxmemoryState(NULL,NULL,NULL,&level); - if (retval == C_ERR) flags |= VALKEYMODULE_CTX_FLAGS_OOM; - if (level > 0.75) flags |= VALKEYMODULE_CTX_FLAGS_OOM_WARNING; + if (retval == C_ERR) flags |= REDISMODULE_CTX_FLAGS_OOM; + if (level > 0.75) flags |= REDISMODULE_CTX_FLAGS_OOM_WARNING; /* Presence of children processes. */ - if (hasActiveChildProcess()) flags |= VALKEYMODULE_CTX_FLAGS_ACTIVE_CHILD; - if (server.in_fork_child) flags |= VALKEYMODULE_CTX_FLAGS_IS_CHILD; + if (hasActiveChildProcess()) flags |= REDISMODULE_CTX_FLAGS_ACTIVE_CHILD; + if (server.in_fork_child) flags |= REDISMODULE_CTX_FLAGS_IS_CHILD; /* Non-empty server.loadmodule_queue means that Redis is starting. */ if (listLength(server.loadmodule_queue) > 0) - flags |= VALKEYMODULE_CTX_FLAGS_SERVER_STARTUP; + flags |= REDISMODULE_CTX_FLAGS_SERVER_STARTUP; return flags; } @@ -3906,8 +4001,8 @@ int VM_GetContextFlags(ValkeyModuleCtx *ctx) { * replication offset, match the one of the master. When this happens, it is * safe to failover the master without data loss. * - * However modules may generate traffic by calling ValkeyModule_Call() with - * the "!" flag, or by calling ValkeyModule_Replicate(), in a context outside + * However modules may generate traffic by calling RedisModule_Call() with + * the "!" flag, or by calling RedisModule_Replicate(), in a context outside * commands execution, for instance in timeout callbacks, threads safe * contexts, and so forth. When modules will generate too much traffic, it * will be hard for the master and replicas offset to match, because there @@ -3919,7 +4014,7 @@ int VM_GetContextFlags(ValkeyModuleCtx *ctx) { * garbage collection tasks, or that do writes and replicate such writes * periodically in timer callbacks or other periodic callbacks. */ -int VM_AvoidReplicaTraffic(void) { +int RM_AvoidReplicaTraffic(void) { return !!(isPausedActionsWithUpdate(PAUSE_ACTION_REPLICA)); } @@ -3931,26 +4026,26 @@ int VM_AvoidReplicaTraffic(void) { * returns. * * If the module command wishes to change something in a different DB and - * returns back to the original one, it should call ValkeyModule_GetSelectedDb() + * returns back to the original one, it should call RedisModule_GetSelectedDb() * before in order to restore the old DB number before returning. */ -int VM_SelectDb(ValkeyModuleCtx *ctx, int newid) { +int RM_SelectDb(RedisModuleCtx *ctx, int newid) { int retval = selectDb(ctx->client,newid); - return (retval == C_OK) ? VALKEYMODULE_OK : VALKEYMODULE_ERR; + return (retval == C_OK) ? REDISMODULE_OK : REDISMODULE_ERR; } /* Check if a key exists, without affecting its last access time. * - * This is equivalent to calling VM_OpenKey with the mode VALKEYMODULE_READ | - * VALKEYMODULE_OPEN_KEY_NOTOUCH, then checking if NULL was returned and, if not, - * calling VM_CloseKey on the opened key. + * This is equivalent to calling RM_OpenKey with the mode REDISMODULE_READ | + * REDISMODULE_OPEN_KEY_NOTOUCH, then checking if NULL was returned and, if not, + * calling RM_CloseKey on the opened key. */ -int VM_KeyExists(ValkeyModuleCtx *ctx, robj *keyname) { +int RM_KeyExists(RedisModuleCtx *ctx, robj *keyname) { robj *value = lookupKeyReadWithFlags(ctx->client->db, keyname, LOOKUP_NOTOUCH); return (value != NULL); } -/* Initialize a ValkeyModuleKey struct */ -static void moduleInitKey(ValkeyModuleKey *kp, ValkeyModuleCtx *ctx, robj *keyname, robj *value, int mode){ +/* Initialize a RedisModuleKey struct */ +static void moduleInitKey(RedisModuleKey *kp, RedisModuleCtx *ctx, robj *keyname, robj *value, int mode){ kp->ctx = ctx; kp->db = ctx->client->db; kp->key = keyname; @@ -3962,7 +4057,7 @@ static void moduleInitKey(ValkeyModuleKey *kp, ValkeyModuleCtx *ctx, robj *keyna } /* Initialize the type-specific part of the key. Only when key has a value. */ -static void moduleInitKeyTypeSpecific(ValkeyModuleKey *key) { +static void moduleInitKeyTypeSpecific(RedisModuleKey *key) { switch (key->value->type) { case OBJ_ZSET: zsetKeyReset(key); break; case OBJ_STREAM: key->u.stream.signalready = 0; break; @@ -3974,33 +4069,33 @@ static void moduleInitKeyTypeSpecific(ValkeyModuleKey *key) { * operations on the key. * * The return value is the handle representing the key, that must be - * closed with VM_CloseKey(). + * closed with RM_CloseKey(). * - * If the key does not exist and VALKEYMODULE_WRITE mode is requested, the handle + * If the key does not exist and REDISMODULE_WRITE mode is requested, the handle * is still returned, since it is possible to perform operations on * a yet not existing key (that will be created, for example, after - * a list push operation). If the mode is just VALKEYMODULE_READ instead, and the + * a list push operation). If the mode is just REDISMODULE_READ instead, and the * key does not exist, NULL is returned. However it is still safe to - * call ValkeyModule_CloseKey() and ValkeyModule_KeyType() on a NULL + * call RedisModule_CloseKey() and RedisModule_KeyType() on a NULL * value. * * Extra flags that can be pass to the API under the mode argument: - * * VALKEYMODULE_OPEN_KEY_NOTOUCH - Avoid touching the LRU/LFU of the key when opened. - * * VALKEYMODULE_OPEN_KEY_NONOTIFY - Don't trigger keyspace event on key misses. - * * VALKEYMODULE_OPEN_KEY_NOSTATS - Don't update keyspace hits/misses counters. - * * VALKEYMODULE_OPEN_KEY_NOEXPIRE - Avoid deleting lazy expired keys. - * * VALKEYMODULE_OPEN_KEY_NOEFFECTS - Avoid any effects from fetching the key. */ -ValkeyModuleKey *VM_OpenKey(ValkeyModuleCtx *ctx, robj *keyname, int mode) { - ValkeyModuleKey *kp; + * * REDISMODULE_OPEN_KEY_NOTOUCH - Avoid touching the LRU/LFU of the key when opened. + * * REDISMODULE_OPEN_KEY_NONOTIFY - Don't trigger keyspace event on key misses. + * * REDISMODULE_OPEN_KEY_NOSTATS - Don't update keyspace hits/misses counters. + * * REDISMODULE_OPEN_KEY_NOEXPIRE - Avoid deleting lazy expired keys. + * * REDISMODULE_OPEN_KEY_NOEFFECTS - Avoid any effects from fetching the key. */ +RedisModuleKey *RM_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode) { + RedisModuleKey *kp; robj *value; int flags = 0; - flags |= (mode & VALKEYMODULE_OPEN_KEY_NOTOUCH? LOOKUP_NOTOUCH: 0); - flags |= (mode & VALKEYMODULE_OPEN_KEY_NONOTIFY? LOOKUP_NONOTIFY: 0); - flags |= (mode & VALKEYMODULE_OPEN_KEY_NOSTATS? LOOKUP_NOSTATS: 0); - flags |= (mode & VALKEYMODULE_OPEN_KEY_NOEXPIRE? LOOKUP_NOEXPIRE: 0); - flags |= (mode & VALKEYMODULE_OPEN_KEY_NOEFFECTS? LOOKUP_NOEFFECTS: 0); + flags |= (mode & REDISMODULE_OPEN_KEY_NOTOUCH? LOOKUP_NOTOUCH: 0); + flags |= (mode & REDISMODULE_OPEN_KEY_NONOTIFY? LOOKUP_NONOTIFY: 0); + flags |= (mode & REDISMODULE_OPEN_KEY_NOSTATS? LOOKUP_NOSTATS: 0); + flags |= (mode & REDISMODULE_OPEN_KEY_NOEXPIRE? LOOKUP_NOEXPIRE: 0); + flags |= (mode & REDISMODULE_OPEN_KEY_NOEFFECTS? LOOKUP_NOEFFECTS: 0); - if (mode & VALKEYMODULE_WRITE) { + if (mode & REDISMODULE_WRITE) { value = lookupKeyWriteWithFlags(ctx->client->db,keyname, flags); } else { value = lookupKeyReadWithFlags(ctx->client->db,keyname, flags); @@ -4012,7 +4107,7 @@ ValkeyModuleKey *VM_OpenKey(ValkeyModuleCtx *ctx, robj *keyname, int mode) { /* Setup the key handle. */ kp = zmalloc(sizeof(*kp)); moduleInitKey(kp, ctx, keyname, value, mode); - autoMemoryAdd(ctx,VALKEYMODULE_AM_KEY,kp); + autoMemoryAdd(ctx,REDISMODULE_AM_KEY,kp); return kp; } @@ -4022,31 +4117,31 @@ ValkeyModuleKey *VM_OpenKey(ValkeyModuleCtx *ctx, robj *keyname, int mode) { * by the redis server version in use. * Example: * - * int supportedMode = VM_GetOpenKeyModesAll(); - * if (supportedMode & VALKEYMODULE_OPEN_KEY_NOTOUCH) { - * // VALKEYMODULE_OPEN_KEY_NOTOUCH is supported + * int supportedMode = RM_GetOpenKeyModesAll(); + * if (supportedMode & REDISMODULE_OPEN_KEY_NOTOUCH) { + * // REDISMODULE_OPEN_KEY_NOTOUCH is supported * } else{ - * // VALKEYMODULE_OPEN_KEY_NOTOUCH is not supported + * // REDISMODULE_OPEN_KEY_NOTOUCH is not supported * } */ -int VM_GetOpenKeyModesAll(void) { - return _VALKEYMODULE_OPEN_KEY_ALL; +int RM_GetOpenKeyModesAll(void) { + return _REDISMODULE_OPEN_KEY_ALL; } -/* Destroy a ValkeyModuleKey struct (freeing is the responsibility of the caller). */ -static void moduleCloseKey(ValkeyModuleKey *key) { +/* Destroy a RedisModuleKey struct (freeing is the responsibility of the caller). */ +static void moduleCloseKey(RedisModuleKey *key) { int signal = SHOULD_SIGNAL_MODIFIED_KEYS(key->ctx); - if ((key->mode & VALKEYMODULE_WRITE) && signal) + if ((key->mode & REDISMODULE_WRITE) && signal) signalModifiedKey(key->ctx->client,key->db,key->key); if (key->value) { if (key->iter) moduleFreeKeyIterator(key); switch (key->value->type) { case OBJ_ZSET: - VM_ZsetRangeStop(key); + RM_ZsetRangeStop(key); break; case OBJ_STREAM: if (key->u.stream.signalready) - /* One or more VM_StreamAdd() have been done. */ + /* One or more RM_StreamAdd() have been done. */ signalKeyAsReady(key->db, key->key, OBJ_STREAM); break; } @@ -4056,28 +4151,28 @@ static void moduleCloseKey(ValkeyModuleKey *key) { } /* Close a key handle. */ -void VM_CloseKey(ValkeyModuleKey *key) { +void RM_CloseKey(RedisModuleKey *key) { if (key == NULL) return; moduleCloseKey(key); - autoMemoryFreed(key->ctx,VALKEYMODULE_AM_KEY,key); + autoMemoryFreed(key->ctx,REDISMODULE_AM_KEY,key); zfree(key); } /* Return the type of the key. If the key pointer is NULL then - * VALKEYMODULE_KEYTYPE_EMPTY is returned. */ -int VM_KeyType(ValkeyModuleKey *key) { - if (key == NULL || key->value == NULL) return VALKEYMODULE_KEYTYPE_EMPTY; + * REDISMODULE_KEYTYPE_EMPTY is returned. */ +int RM_KeyType(RedisModuleKey *key) { + if (key == NULL || key->value == NULL) return REDISMODULE_KEYTYPE_EMPTY; /* We map between defines so that we are free to change the internal * defines as desired. */ switch(key->value->type) { - case OBJ_STRING: return VALKEYMODULE_KEYTYPE_STRING; - case OBJ_LIST: return VALKEYMODULE_KEYTYPE_LIST; - case OBJ_SET: return VALKEYMODULE_KEYTYPE_SET; - case OBJ_ZSET: return VALKEYMODULE_KEYTYPE_ZSET; - case OBJ_HASH: return VALKEYMODULE_KEYTYPE_HASH; - case OBJ_MODULE: return VALKEYMODULE_KEYTYPE_MODULE; - case OBJ_STREAM: return VALKEYMODULE_KEYTYPE_STREAM; - default: return VALKEYMODULE_KEYTYPE_EMPTY; + case OBJ_STRING: return REDISMODULE_KEYTYPE_STRING; + case OBJ_LIST: return REDISMODULE_KEYTYPE_LIST; + case OBJ_SET: return REDISMODULE_KEYTYPE_SET; + case OBJ_ZSET: return REDISMODULE_KEYTYPE_ZSET; + case OBJ_HASH: return REDISMODULE_KEYTYPE_HASH; + case OBJ_MODULE: return REDISMODULE_KEYTYPE_MODULE; + case OBJ_STREAM: return REDISMODULE_KEYTYPE_STREAM; + default: return REDISMODULE_KEYTYPE_EMPTY; } } @@ -4086,7 +4181,7 @@ int VM_KeyType(ValkeyModuleKey *key) { * is the number of elements (just counting keys for hashes). * * If the key pointer is NULL or the key is empty, zero is returned. */ -size_t VM_ValueLength(ValkeyModuleKey *key) { +size_t RM_ValueLength(RedisModuleKey *key) { if (key == NULL || key->value == NULL) return 0; switch(key->value->type) { case OBJ_STRING: return stringObjectLen(key->value); @@ -4101,150 +4196,150 @@ size_t VM_ValueLength(ValkeyModuleKey *key) { /* If the key is open for writing, remove it, and setup the key to * accept new writes as an empty key (that will be created on demand). - * On success VALKEYMODULE_OK is returned. If the key is not open for - * writing VALKEYMODULE_ERR is returned. */ -int VM_DeleteKey(ValkeyModuleKey *key) { - if (!(key->mode & VALKEYMODULE_WRITE)) return VALKEYMODULE_ERR; + * On success REDISMODULE_OK is returned. If the key is not open for + * writing REDISMODULE_ERR is returned. */ +int RM_DeleteKey(RedisModuleKey *key) { + if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; if (key->value) { dbDelete(key->db,key->key); key->value = NULL; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* If the key is open for writing, unlink it (that is delete it in a * non-blocking way, not reclaiming memory immediately) and setup the key to * accept new writes as an empty key (that will be created on demand). - * On success VALKEYMODULE_OK is returned. If the key is not open for - * writing VALKEYMODULE_ERR is returned. */ -int VM_UnlinkKey(ValkeyModuleKey *key) { - if (!(key->mode & VALKEYMODULE_WRITE)) return VALKEYMODULE_ERR; + * On success REDISMODULE_OK is returned. If the key is not open for + * writing REDISMODULE_ERR is returned. */ +int RM_UnlinkKey(RedisModuleKey *key) { + if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; if (key->value) { dbAsyncDelete(key->db,key->key); key->value = NULL; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Return the key expire value, as milliseconds of remaining TTL. * If no TTL is associated with the key or if the key is empty, - * VALKEYMODULE_NO_EXPIRE is returned. */ -mstime_t VM_GetExpire(ValkeyModuleKey *key) { + * REDISMODULE_NO_EXPIRE is returned. */ +mstime_t RM_GetExpire(RedisModuleKey *key) { mstime_t expire = getExpire(key->db,key->key); if (expire == -1 || key->value == NULL) - return VALKEYMODULE_NO_EXPIRE; + return REDISMODULE_NO_EXPIRE; expire -= commandTimeSnapshot(); return expire >= 0 ? expire : 0; } /* Set a new expire for the key. If the special expire - * VALKEYMODULE_NO_EXPIRE is set, the expire is cancelled if there was + * 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 number of milliseconds of TTL the key should have. * - * The function returns VALKEYMODULE_OK on success or VALKEYMODULE_ERR if + * The function returns REDISMODULE_OK on success or REDISMODULE_ERR if * the key was not open for writing or is an empty key. */ -int VM_SetExpire(ValkeyModuleKey *key, mstime_t expire) { - if (!(key->mode & VALKEYMODULE_WRITE) || key->value == NULL || (expire < 0 && expire != VALKEYMODULE_NO_EXPIRE)) - return VALKEYMODULE_ERR; - if (expire != VALKEYMODULE_NO_EXPIRE) { +int RM_SetExpire(RedisModuleKey *key, mstime_t expire) { + if (!(key->mode & REDISMODULE_WRITE) || key->value == NULL || (expire < 0 && expire != REDISMODULE_NO_EXPIRE)) + return REDISMODULE_ERR; + if (expire != REDISMODULE_NO_EXPIRE) { expire += commandTimeSnapshot(); setExpire(key->ctx->client,key->db,key->key,expire); } else { removeExpire(key->db,key->key); } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Return the key expire value, as absolute Unix timestamp. * If no TTL is associated with the key or if the key is empty, - * VALKEYMODULE_NO_EXPIRE is returned. */ -mstime_t VM_GetAbsExpire(ValkeyModuleKey *key) { + * REDISMODULE_NO_EXPIRE is returned. */ +mstime_t RM_GetAbsExpire(RedisModuleKey *key) { mstime_t expire = getExpire(key->db,key->key); if (expire == -1 || key->value == NULL) - return VALKEYMODULE_NO_EXPIRE; + return REDISMODULE_NO_EXPIRE; return expire; } /* Set a new expire for the key. If the special expire - * VALKEYMODULE_NO_EXPIRE is set, the expire is cancelled if there was + * 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. * - * The function returns VALKEYMODULE_OK on success or VALKEYMODULE_ERR if + * The function returns REDISMODULE_OK on success or REDISMODULE_ERR if * the key was not open for writing or is an empty key. */ -int VM_SetAbsExpire(ValkeyModuleKey *key, mstime_t expire) { - if (!(key->mode & VALKEYMODULE_WRITE) || key->value == NULL || (expire < 0 && expire != VALKEYMODULE_NO_EXPIRE)) - return VALKEYMODULE_ERR; - if (expire != VALKEYMODULE_NO_EXPIRE) { +int RM_SetAbsExpire(RedisModuleKey *key, mstime_t expire) { + if (!(key->mode & REDISMODULE_WRITE) || key->value == NULL || (expire < 0 && expire != REDISMODULE_NO_EXPIRE)) + return REDISMODULE_ERR; + if (expire != REDISMODULE_NO_EXPIRE) { setExpire(key->ctx->client,key->db,key->key,expire); } else { removeExpire(key->db,key->key); } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Performs similar operation to FLUSHALL, and optionally start a new AOF file (if enabled) * If restart_aof is true, you must make sure the command that triggered this call is not * propagated to the AOF file. * When async is set to true, db contents will be freed by a background thread. */ -void VM_ResetDataset(int restart_aof, int async) { +void RM_ResetDataset(int restart_aof, int async) { if (restart_aof && server.aof_state != AOF_OFF) stopAppendOnly(); flushAllDataAndResetRDB((async? EMPTYDB_ASYNC: EMPTYDB_NO_FLAGS) | EMPTYDB_NOFUNCTIONS); if (server.aof_enabled && restart_aof) restartAOFAfterSYNC(); } /* Returns the number of keys in the current db. */ -unsigned long long VM_DbSize(ValkeyModuleCtx *ctx) { - return dictSize(ctx->client->db->dict); +unsigned long long RM_DbSize(RedisModuleCtx *ctx) { + return dbSize(ctx->client->db, DB_MAIN); } /* Returns a name of a random key, or NULL if current db is empty. */ -ValkeyModuleString *VM_RandomKey(ValkeyModuleCtx *ctx) { +RedisModuleString *RM_RandomKey(RedisModuleCtx *ctx) { robj *key = dbRandomKey(ctx->client->db); - autoMemoryAdd(ctx,VALKEYMODULE_AM_STRING,key); + autoMemoryAdd(ctx,REDISMODULE_AM_STRING,key); return key; } /* Returns the name of the key currently being processed. */ -const ValkeyModuleString *VM_GetKeyNameFromOptCtx(ValkeyModuleKeyOptCtx *ctx) { +const RedisModuleString *RM_GetKeyNameFromOptCtx(RedisModuleKeyOptCtx *ctx) { return ctx->from_key; } /* Returns the name of the target key currently being processed. */ -const ValkeyModuleString *VM_GetToKeyNameFromOptCtx(ValkeyModuleKeyOptCtx *ctx) { +const RedisModuleString *RM_GetToKeyNameFromOptCtx(RedisModuleKeyOptCtx *ctx) { return ctx->to_key; } /* Returns the dbid currently being processed. */ -int VM_GetDbIdFromOptCtx(ValkeyModuleKeyOptCtx *ctx) { +int RM_GetDbIdFromOptCtx(RedisModuleKeyOptCtx *ctx) { return ctx->from_dbid; } /* Returns the target dbid currently being processed. */ -int VM_GetToDbIdFromOptCtx(ValkeyModuleKeyOptCtx *ctx) { +int RM_GetToDbIdFromOptCtx(RedisModuleKeyOptCtx *ctx) { return ctx->to_dbid; } /* -------------------------------------------------------------------------- * ## Key API for String type * - * See also VM_ValueLength(), which returns the length of a string. + * See also RM_ValueLength(), which returns the length of a string. * -------------------------------------------------------------------------- */ /* If the key is open for writing, set the specified string 'str' as the * value of the key, deleting the old value if any. - * On success VALKEYMODULE_OK is returned. If the key is not open for - * writing or there is an active iterator, VALKEYMODULE_ERR is returned. */ -int VM_StringSet(ValkeyModuleKey *key, ValkeyModuleString *str) { - if (!(key->mode & VALKEYMODULE_WRITE) || key->iter) return VALKEYMODULE_ERR; - VM_DeleteKey(key); + * On success REDISMODULE_OK is returned. If the key is not open for + * writing or there is an active iterator, REDISMODULE_ERR is returned. */ +int RM_StringSet(RedisModuleKey *key, RedisModuleString *str) { + if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR; + RM_DeleteKey(key); setKey(key->ctx->client,key->db,key->key,str,SETKEY_NO_SIGNAL); key->value = str; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Prepare the key associated string value for DMA access, and returns @@ -4253,8 +4348,8 @@ int VM_StringSet(ValkeyModuleKey *key, ValkeyModuleString *str) { * * The 'mode' is composed by bitwise OR-ing the following flags: * - * VALKEYMODULE_READ -- Read access - * VALKEYMODULE_WRITE -- Write access + * REDISMODULE_READ -- Read access + * REDISMODULE_WRITE -- Write access * * If the DMA is not requested for writing, the pointer returned should * only be accessed in a read-only fashion. @@ -4267,16 +4362,16 @@ int VM_StringSet(ValkeyModuleKey *key, ValkeyModuleString *str) { * the pointer is obtained, for all the time we want to use DMA access * to read or modify the string. * - * 2. Each time VM_StringTruncate() is called, to continue with the DMA - * access, VM_StringDMA() should be called again to re-obtain + * 2. Each time RM_StringTruncate() is called, to continue with the DMA + * access, RM_StringDMA() should be called again to re-obtain * a new pointer and length. * * 3. If the returned pointer is not NULL, but the length is zero, no * byte can be touched (the string is empty, or the key itself is empty) - * so a VM_StringTruncate() call should be used if there is to enlarge + * so a RM_StringTruncate() call should be used if there is to enlarge * the string, and later call StringDMA() again to get the pointer. */ -char *VM_StringDMA(ValkeyModuleKey *key, size_t *len, int mode) { +char *RM_StringDMA(RedisModuleKey *key, size_t *len, int mode) { /* We need to return *some* pointer for empty keys, we just return * a string literal pointer, that is the advantage to be mapped into * a read only memory page, so the module will segfault if a write @@ -4291,7 +4386,7 @@ char *VM_StringDMA(ValkeyModuleKey *key, size_t *len, int mode) { /* For write access, and even for read access if the object is encoded, * we unshare the string (that has the side effect of decoding it). */ - if ((mode & VALKEYMODULE_WRITE) || key->value->encoding != OBJ_ENCODING_RAW) + if ((mode & REDISMODULE_WRITE) || key->value->encoding != OBJ_ENCODING_RAW) key->value = dbUnshareStringValue(key->db, key->key, key->value); *len = sdslen(key->value->ptr); @@ -4301,23 +4396,23 @@ char *VM_StringDMA(ValkeyModuleKey *key, size_t *len, int mode) { /* If the key is open for writing and is of string type, resize it, padding * with zero bytes if the new length is greater than the old one. * - * After this call, VM_StringDMA() must be called again to continue + * After this call, RM_StringDMA() must be called again to continue * DMA access with the new pointer. * - * The function returns VALKEYMODULE_OK on success, and VALKEYMODULE_ERR on + * The function returns REDISMODULE_OK on success, and REDISMODULE_ERR on * error, that is, the key is not open for writing, is not a string * or resizing for more than 512 MB is requested. * * If the key is empty, a string key is created with the new string value * unless the new length value requested is zero. */ -int VM_StringTruncate(ValkeyModuleKey *key, size_t newlen) { - if (!(key->mode & VALKEYMODULE_WRITE)) return VALKEYMODULE_ERR; - if (key->value && key->value->type != OBJ_STRING) return VALKEYMODULE_ERR; - if (newlen > 512*1024*1024) return VALKEYMODULE_ERR; +int RM_StringTruncate(RedisModuleKey *key, size_t newlen) { + if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; + if (key->value && key->value->type != OBJ_STRING) return REDISMODULE_ERR; + if (newlen > 512*1024*1024) return REDISMODULE_ERR; - /* Empty key and new len set to 0. Just return VALKEYMODULE_OK without + /* Empty key and new len set to 0. Just return REDISMODULE_OK without * doing anything. */ - if (key->value == NULL && newlen == 0) return VALKEYMODULE_OK; + if (key->value == NULL && newlen == 0) return REDISMODULE_OK; if (key->value == NULL) { /* Empty key: create it with the new size. */ @@ -4338,7 +4433,7 @@ int VM_StringTruncate(ValkeyModuleKey *key, size_t newlen) { key->value->ptr = sdsRemoveFreeSpace(key->value->ptr, 0); } } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* -------------------------------------------------------------------------- @@ -4352,28 +4447,28 @@ int VM_StringTruncate(ValkeyModuleKey *key, size_t newlen) { * * This enables iteration to be done efficiently using a simple for loop: * - * long n = VM_ValueLength(key); + * long n = RM_ValueLength(key); * for (long i = 0; i < n; i++) { - * ValkeyModuleString *elem = ValkeyModule_ListGet(key, i); + * RedisModuleString *elem = RedisModule_ListGet(key, i); * // Do stuff... * } * - * Note that after modifying a list using VM_ListPop, VM_ListSet or - * VM_ListInsert, the internal iterator is invalidated so the next operation + * Note that after modifying a list using RM_ListPop, RM_ListSet or + * RM_ListInsert, the internal iterator is invalidated so the next operation * will require a linear seek. * - * Modifying a list in any another way, for example using VM_Call(), while a key + * Modifying a list in any another way, for example using RM_Call(), while a key * is open will confuse the internal iterator and may cause trouble if the key * is used after such modifications. The key must be reopened in this case. * - * See also VM_ValueLength(), which returns the length of a list. + * See also RM_ValueLength(), which returns the length of a list. * -------------------------------------------------------------------------- */ /* Seeks the key's internal list iterator to the given index. On success, 1 is * returned and key->iter, key->u.list.entry and key->u.list.index are set. On * failure, 0 is returned and errno is set as required by the list API * functions. */ -int moduleListIteratorSeek(ValkeyModuleKey *key, long index, int mode) { +int moduleListIteratorSeek(RedisModuleKey *key, long index, int mode) { if (!key) { errno = EINVAL; return 0; @@ -4418,9 +4513,9 @@ int moduleListIteratorSeek(ValkeyModuleKey *key, long index, int mode) { } /* Push an element into a list, on head or tail depending on 'where' argument - * (VALKEYMODULE_LIST_HEAD or VALKEYMODULE_LIST_TAIL). If the key refers to an - * empty key opened for writing, the key is created. On success, VALKEYMODULE_OK - * is returned. On failure, VALKEYMODULE_ERR is returned and `errno` is set as + * (REDISMODULE_LIST_HEAD or REDISMODULE_LIST_TAIL). If the key refers to an + * empty key opened for writing, the key is created. On success, REDISMODULE_OK + * is returned. On failure, REDISMODULE_ERR is returned and `errno` is set as * follows: * * - EINVAL if key or ele is NULL. @@ -4428,33 +4523,33 @@ int moduleListIteratorSeek(ValkeyModuleKey *key, long index, int mode) { * - EBADF if the key is not opened for writing. * * Note: Before Redis 7.0, `errno` was not set by this function. */ -int VM_ListPush(ValkeyModuleKey *key, int where, ValkeyModuleString *ele) { +int RM_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele) { if (!key || !ele) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (key->value != NULL && key->value->type != OBJ_LIST) { errno = ENOTSUP; - return VALKEYMODULE_ERR; - } if (!(key->mode & VALKEYMODULE_WRITE)) { + return REDISMODULE_ERR; + } if (!(key->mode & REDISMODULE_WRITE)) { errno = EBADF; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - if (!(key->mode & VALKEYMODULE_WRITE)) return VALKEYMODULE_ERR; - if (key->value && key->value->type != OBJ_LIST) return VALKEYMODULE_ERR; + if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; + if (key->value && key->value->type != OBJ_LIST) return REDISMODULE_ERR; if (key->iter) moduleFreeKeyIterator(key); - if (key->value == NULL) moduleCreateEmptyKey(key,VALKEYMODULE_KEYTYPE_LIST); + if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_LIST); listTypeTryConversionAppend(key->value, &ele, 0, 0, moduleFreeListIterator, key); listTypePush(key->value, ele, - (where == VALKEYMODULE_LIST_HEAD) ? LIST_HEAD : LIST_TAIL); - return VALKEYMODULE_OK; + (where == REDISMODULE_LIST_HEAD) ? LIST_HEAD : LIST_TAIL); + return REDISMODULE_OK; } /* Pop an element from the list, and returns it as a module string object - * that the user should be free with VM_FreeString() or by enabling + * that the user should be free with RM_FreeString() or by enabling * automatic memory. The `where` argument specifies if the element should be - * popped from the beginning or the end of the list (VALKEYMODULE_LIST_HEAD or - * VALKEYMODULE_LIST_TAIL). On failure, the command returns NULL and sets + * popped from the beginning or the end of the list (REDISMODULE_LIST_HEAD or + * REDISMODULE_LIST_TAIL). On failure, the command returns NULL and sets * `errno` as follows: * * - EINVAL if key is NULL. @@ -4462,30 +4557,30 @@ int VM_ListPush(ValkeyModuleKey *key, int where, ValkeyModuleString *ele) { * - EBADF if the key is not opened for writing. * * Note: Before Redis 7.0, `errno` was not set by this function. */ -ValkeyModuleString *VM_ListPop(ValkeyModuleKey *key, int where) { +RedisModuleString *RM_ListPop(RedisModuleKey *key, int where) { if (!key) { errno = EINVAL; return NULL; } else if (key->value == NULL || key->value->type != OBJ_LIST) { errno = ENOTSUP; return NULL; - } else if (!(key->mode & VALKEYMODULE_WRITE)) { + } else if (!(key->mode & REDISMODULE_WRITE)) { errno = EBADF; return NULL; } if (key->iter) moduleFreeKeyIterator(key); robj *ele = listTypePop(key->value, - (where == VALKEYMODULE_LIST_HEAD) ? LIST_HEAD : LIST_TAIL); + (where == REDISMODULE_LIST_HEAD) ? LIST_HEAD : LIST_TAIL); robj *decoded = getDecodedObject(ele); decrRefCount(ele); if (!moduleDelKeyIfEmpty(key)) listTypeTryConversion(key->value, LIST_CONV_SHRINKING, moduleFreeListIterator, key); - autoMemoryAdd(key->ctx,VALKEYMODULE_AM_STRING,decoded); + autoMemoryAdd(key->ctx,REDISMODULE_AM_STRING,decoded); return decoded; } /* Returns the element at index `index` in the list stored at `key`, like the - * LINDEX command. The element should be free'd using VM_FreeString() or using + * LINDEX command. The element should be free'd using RM_FreeString() or using * automatic memory management. * * The index is zero-based, so 0 means the first element, 1 the second element @@ -4501,12 +4596,12 @@ ValkeyModuleString *VM_ListPop(ValkeyModuleKey *key, int where) { * - EBADF if the key is not opened for reading. * - EDOM if the index is not a valid index in the list. */ -ValkeyModuleString *VM_ListGet(ValkeyModuleKey *key, long index) { - if (moduleListIteratorSeek(key, index, VALKEYMODULE_READ)) { +RedisModuleString *RM_ListGet(RedisModuleKey *key, long index) { + if (moduleListIteratorSeek(key, index, REDISMODULE_READ)) { robj *elem = listTypeGet(&key->u.list.entry); robj *decoded = getDecodedObject(elem); decrRefCount(elem); - autoMemoryAdd(key->ctx, VALKEYMODULE_AM_STRING, decoded); + autoMemoryAdd(key->ctx, REDISMODULE_AM_STRING, decoded); return decoded; } else { return NULL; @@ -4520,7 +4615,7 @@ ValkeyModuleString *VM_ListGet(ValkeyModuleKey *key, long index) { * tail of the list. Here, -1 means the last element, -2 means the penultimate * and so forth. * - * On success, VALKEYMODULE_OK is returned. On failure, VALKEYMODULE_ERR is + * On success, REDISMODULE_OK is returned. On failure, REDISMODULE_ERR is * returned and `errno` is set as follows: * * - EINVAL if key or value is NULL. @@ -4528,24 +4623,24 @@ ValkeyModuleString *VM_ListGet(ValkeyModuleKey *key, long index) { * - EBADF if the key is not opened for writing. * - EDOM if the index is not a valid index in the list. */ -int VM_ListSet(ValkeyModuleKey *key, long index, ValkeyModuleString *value) { +int RM_ListSet(RedisModuleKey *key, long index, RedisModuleString *value) { if (!value) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } if (!key->value || key->value->type != OBJ_LIST) { errno = ENOTSUP; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } listTypeTryConversionAppend(key->value, &value, 0, 0, moduleFreeListIterator, key); - if (moduleListIteratorSeek(key, index, VALKEYMODULE_WRITE)) { + if (moduleListIteratorSeek(key, index, REDISMODULE_WRITE)) { listTypeReplace(&key->u.list.entry, value); /* A note in quicklist.c forbids use of iterator after insert, so * probably also after replace. */ moduleFreeKeyIterator(key); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } else { - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } } @@ -4556,7 +4651,7 @@ int VM_ListSet(ValkeyModuleKey *key, long index, ValkeyModuleString *value) { * tail of the list. Here, -1 means the last element, -2 means the penultimate * and so forth. The index is the element's index after inserting it. * - * On success, VALKEYMODULE_OK is returned. On failure, VALKEYMODULE_ERR is + * On success, REDISMODULE_OK is returned. On failure, REDISMODULE_ERR is * returned and `errno` is set as follows: * * - EINVAL if key or value is NULL. @@ -4564,41 +4659,41 @@ int VM_ListSet(ValkeyModuleKey *key, long index, ValkeyModuleString *value) { * - EBADF if the key is not opened for writing. * - EDOM if the index is not a valid index in the list. */ -int VM_ListInsert(ValkeyModuleKey *key, long index, ValkeyModuleString *value) { +int RM_ListInsert(RedisModuleKey *key, long index, RedisModuleString *value) { if (!value) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (key != NULL && key->value == NULL && (index == 0 || index == -1)) { /* Insert in empty key => push. */ - return VM_ListPush(key, VALKEYMODULE_LIST_TAIL, value); + return RM_ListPush(key, REDISMODULE_LIST_TAIL, value); } else if (key != NULL && key->value != NULL && key->value->type == OBJ_LIST && (index == (long)listTypeLength(key->value) || index == -1)) { /* Insert after the last element => push tail. */ - return VM_ListPush(key, VALKEYMODULE_LIST_TAIL, value); + return RM_ListPush(key, REDISMODULE_LIST_TAIL, value); } else if (key != NULL && key->value != NULL && key->value->type == OBJ_LIST && (index == 0 || index == -(long)listTypeLength(key->value) - 1)) { /* Insert before the first element => push head. */ - return VM_ListPush(key, VALKEYMODULE_LIST_HEAD, value); + return RM_ListPush(key, REDISMODULE_LIST_HEAD, value); } listTypeTryConversionAppend(key->value, &value, 0, 0, moduleFreeListIterator, key); - if (moduleListIteratorSeek(key, index, VALKEYMODULE_WRITE)) { + if (moduleListIteratorSeek(key, index, REDISMODULE_WRITE)) { int where = index < 0 ? LIST_TAIL : LIST_HEAD; listTypeInsert(&key->u.list.entry, value, where); /* A note in quicklist.c forbids use of iterator after insert. */ moduleFreeKeyIterator(key); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } else { - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } } /* Removes an element at the given index. The index is 0-based. A negative index * can also be used, counting from the end of the list. * - * On success, VALKEYMODULE_OK is returned. On failure, VALKEYMODULE_ERR is + * On success, REDISMODULE_OK is returned. On failure, REDISMODULE_ERR is * returned and `errno` is set as follows: * * - EINVAL if key or value is NULL. @@ -4606,12 +4701,12 @@ int VM_ListInsert(ValkeyModuleKey *key, long index, ValkeyModuleString *value) { * - EBADF if the key is not opened for writing. * - EDOM if the index is not a valid index in the list. */ -int VM_ListDelete(ValkeyModuleKey *key, long index) { - if (moduleListIteratorSeek(key, index, VALKEYMODULE_WRITE)) { +int RM_ListDelete(RedisModuleKey *key, long index) { + if (moduleListIteratorSeek(key, index, REDISMODULE_WRITE)) { listTypeDelete(key->iter, &key->u.list.entry); - if (moduleDelKeyIfEmpty(key)) return VALKEYMODULE_OK; + if (moduleDelKeyIfEmpty(key)) return REDISMODULE_OK; listTypeTryConversion(key->value, LIST_CONV_SHRINKING, moduleFreeListIterator, key); - if (!key->iter) return VALKEYMODULE_OK; /* Return ASAP if iterator has been freed */ + if (!key->iter) return REDISMODULE_OK; /* Return ASAP if iterator has been freed */ if (listTypeNext(key->iter, &key->u.list.entry)) { /* After delete entry at position 'index', we need to update * 'key->u.list.index' according to the following cases: @@ -4629,35 +4724,35 @@ int VM_ListDelete(ValkeyModuleKey *key, long index) { /* Reset list iterator if the next entry doesn't exist. */ moduleFreeKeyIterator(key); } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } else { - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } } /* -------------------------------------------------------------------------- * ## Key API for Sorted Set type * - * See also VM_ValueLength(), which returns the length of a sorted set. + * See also RM_ValueLength(), which returns the length of a sorted set. * -------------------------------------------------------------------------- */ /* Conversion from/to public flags of the Modules API and our private flags, * so that we have everything decoupled. */ int moduleZsetAddFlagsToCoreFlags(int flags) { int retflags = 0; - if (flags & VALKEYMODULE_ZADD_XX) retflags |= ZADD_IN_XX; - if (flags & VALKEYMODULE_ZADD_NX) retflags |= ZADD_IN_NX; - if (flags & VALKEYMODULE_ZADD_GT) retflags |= ZADD_IN_GT; - if (flags & VALKEYMODULE_ZADD_LT) retflags |= ZADD_IN_LT; + if (flags & REDISMODULE_ZADD_XX) retflags |= ZADD_IN_XX; + if (flags & REDISMODULE_ZADD_NX) retflags |= ZADD_IN_NX; + if (flags & REDISMODULE_ZADD_GT) retflags |= ZADD_IN_GT; + if (flags & REDISMODULE_ZADD_LT) retflags |= ZADD_IN_LT; return retflags; } /* See previous function comment. */ int moduleZsetAddFlagsFromCoreFlags(int flags) { int retflags = 0; - if (flags & ZADD_OUT_ADDED) retflags |= VALKEYMODULE_ZADD_ADDED; - if (flags & ZADD_OUT_UPDATED) retflags |= VALKEYMODULE_ZADD_UPDATED; - if (flags & ZADD_OUT_NOP) retflags |= VALKEYMODULE_ZADD_NOP; + if (flags & ZADD_OUT_ADDED) retflags |= REDISMODULE_ZADD_ADDED; + if (flags & ZADD_OUT_UPDATED) retflags |= REDISMODULE_ZADD_UPDATED; + if (flags & ZADD_OUT_NOP) retflags |= REDISMODULE_ZADD_NOP; return retflags; } @@ -4673,72 +4768,72 @@ int moduleZsetAddFlagsFromCoreFlags(int flags) { * * The input flags are: * - * VALKEYMODULE_ZADD_XX: Element must already exist. Do nothing otherwise. - * VALKEYMODULE_ZADD_NX: Element must not exist. Do nothing otherwise. - * VALKEYMODULE_ZADD_GT: If element exists, new score must be greater than the current score. + * 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. * Do nothing otherwise. Can optionally be combined with XX. - * VALKEYMODULE_ZADD_LT: If element exists, new score must be less than the current score. + * REDISMODULE_ZADD_LT: If element exists, new score must be less than the current score. * Do nothing otherwise. Can optionally be combined with XX. * * The output flags are: * - * VALKEYMODULE_ZADD_ADDED: The new element was added to the sorted set. - * VALKEYMODULE_ZADD_UPDATED: The score of the element was updated. - * VALKEYMODULE_ZADD_NOP: No operation was performed because XX or NX flags. + * REDISMODULE_ZADD_ADDED: The new element was added to the sorted set. + * REDISMODULE_ZADD_UPDATED: The score of the element was updated. + * REDISMODULE_ZADD_NOP: No operation was performed because XX or NX flags. * - * On success the function returns VALKEYMODULE_OK. On the following errors - * VALKEYMODULE_ERR is returned: + * On success the function returns REDISMODULE_OK. On the following errors + * REDISMODULE_ERR is returned: * * * The key was not opened for writing. * * The key is of the wrong type. * * 'score' double value is not a number (NaN). */ -int VM_ZsetAdd(ValkeyModuleKey *key, double score, ValkeyModuleString *ele, int *flagsptr) { +int RM_ZsetAdd(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr) { int in_flags = 0, out_flags = 0; - if (!(key->mode & VALKEYMODULE_WRITE)) return VALKEYMODULE_ERR; - if (key->value && key->value->type != OBJ_ZSET) return VALKEYMODULE_ERR; - if (key->value == NULL) moduleCreateEmptyKey(key,VALKEYMODULE_KEYTYPE_ZSET); + if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; + if (key->value && key->value->type != OBJ_ZSET) return REDISMODULE_ERR; + if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_ZSET); if (flagsptr) in_flags = moduleZsetAddFlagsToCoreFlags(*flagsptr); if (zsetAdd(key->value,score,ele->ptr,in_flags,&out_flags,NULL) == 0) { if (flagsptr) *flagsptr = 0; moduleDelKeyIfEmpty(key); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } if (flagsptr) *flagsptr = moduleZsetAddFlagsFromCoreFlags(out_flags); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* This function works exactly like VM_ZsetAdd(), but instead of setting +/* This function works exactly like RM_ZsetAdd(), but instead of setting * a new score, the score of the existing element is incremented, or if the * element does not already exist, it is added assuming the old score was * zero. * * The input and output flags, and the return value, have the same exact * meaning, with the only difference that this function will return - * VALKEYMODULE_ERR even when 'score' is a valid double number, but adding it + * REDISMODULE_ERR even when 'score' is a valid double number, but adding it * to the existing score results into a NaN (not a number) condition. * * This function has an additional field 'newscore', if not NULL is filled * with the new score of the element after the increment, if no error * is returned. */ -int VM_ZsetIncrby(ValkeyModuleKey *key, double score, ValkeyModuleString *ele, int *flagsptr, double *newscore) { +int RM_ZsetIncrby(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore) { int in_flags = 0, out_flags = 0; - if (!(key->mode & VALKEYMODULE_WRITE)) return VALKEYMODULE_ERR; - if (key->value && key->value->type != OBJ_ZSET) return VALKEYMODULE_ERR; - if (key->value == NULL) moduleCreateEmptyKey(key,VALKEYMODULE_KEYTYPE_ZSET); + if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; + if (key->value && key->value->type != OBJ_ZSET) return REDISMODULE_ERR; + if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_ZSET); if (flagsptr) in_flags = moduleZsetAddFlagsToCoreFlags(*flagsptr); in_flags |= ZADD_IN_INCR; if (zsetAdd(key->value,score,ele->ptr,in_flags,&out_flags,newscore) == 0) { if (flagsptr) *flagsptr = 0; moduleDelKeyIfEmpty(key); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } if (flagsptr) *flagsptr = moduleZsetAddFlagsFromCoreFlags(out_flags); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Remove the specified element from the sorted set. - * The function returns VALKEYMODULE_OK on success, and VALKEYMODULE_ERR + * The function returns REDISMODULE_OK on success, and REDISMODULE_ERR * on one of the following conditions: * * * The key was not opened for writing. @@ -4755,48 +4850,48 @@ int VM_ZsetIncrby(ValkeyModuleKey *key, double score, ValkeyModuleString *ele, i * to know if the element was really removed. * * Empty keys will be handled correctly by doing nothing. */ -int VM_ZsetRem(ValkeyModuleKey *key, ValkeyModuleString *ele, int *deleted) { - if (!(key->mode & VALKEYMODULE_WRITE)) return VALKEYMODULE_ERR; - if (key->value && key->value->type != OBJ_ZSET) return VALKEYMODULE_ERR; +int RM_ZsetRem(RedisModuleKey *key, RedisModuleString *ele, int *deleted) { + if (!(key->mode & REDISMODULE_WRITE)) return REDISMODULE_ERR; + if (key->value && key->value->type != OBJ_ZSET) return REDISMODULE_ERR; if (key->value != NULL && zsetDel(key->value,ele->ptr)) { if (deleted) *deleted = 1; moduleDelKeyIfEmpty(key); } else { if (deleted) *deleted = 0; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* On success retrieve the double score associated at the sorted set element - * 'ele' and returns VALKEYMODULE_OK. Otherwise VALKEYMODULE_ERR is returned + * 'ele' and returns REDISMODULE_OK. Otherwise REDISMODULE_ERR is returned * to signal one of the following conditions: * * * There is no such element 'ele' in the sorted set. * * The key is not a sorted set. * * The key is an open empty key. */ -int VM_ZsetScore(ValkeyModuleKey *key, ValkeyModuleString *ele, double *score) { - if (key->value == NULL) return VALKEYMODULE_ERR; - if (key->value->type != OBJ_ZSET) return VALKEYMODULE_ERR; - if (zsetScore(key->value,ele->ptr,score) == C_ERR) return VALKEYMODULE_ERR; - return VALKEYMODULE_OK; +int RM_ZsetScore(RedisModuleKey *key, RedisModuleString *ele, double *score) { + if (key->value == NULL) return REDISMODULE_ERR; + if (key->value->type != OBJ_ZSET) return REDISMODULE_ERR; + if (zsetScore(key->value,ele->ptr,score) == C_ERR) return REDISMODULE_ERR; + return REDISMODULE_OK; } /* -------------------------------------------------------------------------- * ## Key API for Sorted Set iterator * -------------------------------------------------------------------------- */ -void zsetKeyReset(ValkeyModuleKey *key) { - key->u.zset.type = VALKEYMODULE_ZSET_RANGE_NONE; +void zsetKeyReset(RedisModuleKey *key) { + key->u.zset.type = REDISMODULE_ZSET_RANGE_NONE; key->u.zset.current = NULL; key->u.zset.er = 1; } /* Stop a sorted set iteration. */ -void VM_ZsetRangeStop(ValkeyModuleKey *key) { +void RM_ZsetRangeStop(RedisModuleKey *key) { if (!key->value || key->value->type != OBJ_ZSET) return; /* Free resources if needed. */ - if (key->u.zset.type == VALKEYMODULE_ZSET_RANGE_LEX) + if (key->u.zset.type == REDISMODULE_ZSET_RANGE_LEX) zslFreeLexRange(&key->u.zset.lrs); /* Setup sensible values so that misused iteration API calls when an * iterator is not active will result into something more sensible @@ -4805,22 +4900,22 @@ void VM_ZsetRangeStop(ValkeyModuleKey *key) { } /* Return the "End of range" flag value to signal the end of the iteration. */ -int VM_ZsetRangeEndReached(ValkeyModuleKey *key) { +int RM_ZsetRangeEndReached(RedisModuleKey *key) { if (!key->value || key->value->type != OBJ_ZSET) return 1; return key->u.zset.er; } -/* Helper function for VM_ZsetFirstInScoreRange() and VM_ZsetLastInScoreRange(). +/* Helper function for RM_ZsetFirstInScoreRange() and RM_ZsetLastInScoreRange(). * Setup the sorted set iteration according to the specified score range * (see the functions calling it for more info). If 'first' is true the * first element in the range is used as a starting point for the iterator - * otherwise the last. Return VALKEYMODULE_OK on success otherwise - * VALKEYMODULE_ERR. */ -int zsetInitScoreRange(ValkeyModuleKey *key, double min, double max, int minex, int maxex, int first) { - if (!key->value || key->value->type != OBJ_ZSET) return VALKEYMODULE_ERR; + * otherwise the last. Return REDISMODULE_OK on success otherwise + * REDISMODULE_ERR. */ +int zsetInitScoreRange(RedisModuleKey *key, double min, double max, int minex, int maxex, int first) { + if (!key->value || key->value->type != OBJ_ZSET) return REDISMODULE_ERR; - VM_ZsetRangeStop(key); - key->u.zset.type = VALKEYMODULE_ZSET_RANGE_SCORE; + RM_ZsetRangeStop(key); + key->u.zset.type = REDISMODULE_ZSET_RANGE_SCORE; key->u.zset.er = 0; /* Setup the range structure used by the sorted set core implementation @@ -4837,63 +4932,63 @@ int zsetInitScoreRange(ValkeyModuleKey *key, double min, double max, int minex, } else if (key->value->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = key->value->ptr; zskiplist *zsl = zs->zsl; - key->u.zset.current = first ? zslFirstInRange(zsl,zrs) : - zslLastInRange(zsl,zrs); + key->u.zset.current = first ? zslNthInRange(zsl,zrs,0) : + zslNthInRange(zsl,zrs,-1); } else { serverPanic("Unsupported zset encoding"); } if (key->u.zset.current == NULL) key->u.zset.er = 1; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Setup a sorted set iterator seeking the first element in the specified - * range. Returns VALKEYMODULE_OK if the iterator was correctly initialized - * otherwise VALKEYMODULE_ERR is returned in the following conditions: + * range. Returns REDISMODULE_OK if the iterator was correctly initialized + * otherwise REDISMODULE_ERR is returned in the following conditions: * * 1. The value stored at key is not a sorted set or the key is empty. * * The range is specified according to the two double values 'min' and 'max'. * Both can be infinite using the following two macros: * - * * VALKEYMODULE_POSITIVE_INFINITE for positive infinite value - * * VALKEYMODULE_NEGATIVE_INFINITE for negative infinite value + * * REDISMODULE_POSITIVE_INFINITE for positive infinite value + * * REDISMODULE_NEGATIVE_INFINITE for negative infinite value * * 'minex' and 'maxex' parameters, if true, respectively setup a range * where the min and max value are exclusive (not included) instead of * inclusive. */ -int VM_ZsetFirstInScoreRange(ValkeyModuleKey *key, double min, double max, int minex, int maxex) { +int RM_ZsetFirstInScoreRange(RedisModuleKey *key, double min, double max, int minex, int maxex) { return zsetInitScoreRange(key,min,max,minex,maxex,1); } -/* Exactly like ValkeyModule_ZsetFirstInScoreRange() but the last element of +/* Exactly like RedisModule_ZsetFirstInScoreRange() but the last element of * the range is selected for the start of the iteration instead. */ -int VM_ZsetLastInScoreRange(ValkeyModuleKey *key, double min, double max, int minex, int maxex) { +int RM_ZsetLastInScoreRange(RedisModuleKey *key, double min, double max, int minex, int maxex) { return zsetInitScoreRange(key,min,max,minex,maxex,0); } -/* Helper function for VM_ZsetFirstInLexRange() and VM_ZsetLastInLexRange(). +/* Helper function for RM_ZsetFirstInLexRange() and RM_ZsetLastInLexRange(). * Setup the sorted set iteration according to the specified lexicographical * range (see the functions calling it for more info). If 'first' is true the * first element in the range is used as a starting point for the iterator - * otherwise the last. Return VALKEYMODULE_OK on success otherwise - * VALKEYMODULE_ERR. + * otherwise the last. Return REDISMODULE_OK on success otherwise + * REDISMODULE_ERR. * * Note that this function takes 'min' and 'max' in the same form of the * Redis ZRANGEBYLEX command. */ -int zsetInitLexRange(ValkeyModuleKey *key, ValkeyModuleString *min, ValkeyModuleString *max, int first) { - if (!key->value || key->value->type != OBJ_ZSET) return VALKEYMODULE_ERR; +int zsetInitLexRange(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max, int first) { + if (!key->value || key->value->type != OBJ_ZSET) return REDISMODULE_ERR; - VM_ZsetRangeStop(key); + RM_ZsetRangeStop(key); key->u.zset.er = 0; /* Setup the range structure used by the sorted set core implementation * in order to seek at the specified element. */ zlexrangespec *zlrs = &key->u.zset.lrs; - if (zslParseLexRange(min, max, zlrs) == C_ERR) return VALKEYMODULE_ERR; + if (zslParseLexRange(min, max, zlrs) == C_ERR) return REDISMODULE_ERR; /* Set the range type to lex only after successfully parsing the range, * otherwise we don't want the zlexrangespec to be freed. */ - key->u.zset.type = VALKEYMODULE_ZSET_RANGE_LEX; + key->u.zset.type = REDISMODULE_ZSET_RANGE_LEX; if (key->value->encoding == OBJ_ENCODING_LISTPACK) { key->u.zset.current = first ? zzlFirstInLexRange(key->value->ptr,zlrs) : @@ -4901,43 +4996,43 @@ int zsetInitLexRange(ValkeyModuleKey *key, ValkeyModuleString *min, ValkeyModule } else if (key->value->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = key->value->ptr; zskiplist *zsl = zs->zsl; - key->u.zset.current = first ? zslFirstInLexRange(zsl,zlrs) : - zslLastInLexRange(zsl,zlrs); + key->u.zset.current = first ? zslNthInLexRange(zsl,zlrs,0) : + zslNthInLexRange(zsl,zlrs,-1); } else { serverPanic("Unsupported zset encoding"); } if (key->u.zset.current == NULL) key->u.zset.er = 1; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Setup a sorted set iterator seeking the first element in the specified - * lexicographical range. Returns VALKEYMODULE_OK if the iterator was correctly - * initialized otherwise VALKEYMODULE_ERR is returned in the + * lexicographical range. Returns REDISMODULE_OK if the iterator was correctly + * initialized otherwise REDISMODULE_ERR is returned in the * following conditions: * * 1. The value stored at key is not a sorted set or the key is empty. * 2. The lexicographical range 'min' and 'max' format is invalid. * - * 'min' and 'max' should be provided as two ValkeyModuleString objects + * 'min' and 'max' should be provided as two RedisModuleString objects * in the same format as the parameters passed to the ZRANGEBYLEX command. * The function does not take ownership of the objects, so they can be released * ASAP after the iterator is setup. */ -int VM_ZsetFirstInLexRange(ValkeyModuleKey *key, ValkeyModuleString *min, ValkeyModuleString *max) { +int RM_ZsetFirstInLexRange(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max) { return zsetInitLexRange(key,min,max,1); } -/* Exactly like ValkeyModule_ZsetFirstInLexRange() but the last element of +/* Exactly like RedisModule_ZsetFirstInLexRange() but the last element of * the range is selected for the start of the iteration instead. */ -int VM_ZsetLastInLexRange(ValkeyModuleKey *key, ValkeyModuleString *min, ValkeyModuleString *max) { +int RM_ZsetLastInLexRange(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max) { return zsetInitLexRange(key,min,max,0); } /* Return the current sorted set element of an active sorted set iterator * or NULL if the range specified in the iterator does not include any * element. */ -ValkeyModuleString *VM_ZsetRangeCurrentElement(ValkeyModuleKey *key, double *score) { - ValkeyModuleString *str; +RedisModuleString *RM_ZsetRangeCurrentElement(RedisModuleKey *key, double *score) { + RedisModuleString *str; if (!key->value || key->value->type != OBJ_ZSET) return NULL; if (key->u.zset.current == NULL) return NULL; @@ -4957,14 +5052,14 @@ ValkeyModuleString *VM_ZsetRangeCurrentElement(ValkeyModuleKey *key, double *sco } else { serverPanic("Unsupported zset encoding"); } - autoMemoryAdd(key->ctx,VALKEYMODULE_AM_STRING,str); + autoMemoryAdd(key->ctx,REDISMODULE_AM_STRING,str); return str; } /* Go to the next element of the sorted set iterator. Returns 1 if there was * a next element, 0 if we are already at the latest element or the range * does not include any item at all. */ -int VM_ZsetRangeNext(ValkeyModuleKey *key) { +int RM_ZsetRangeNext(RedisModuleKey *key) { if (!key->value || key->value->type != OBJ_ZSET) return 0; if (!key->u.zset.type || !key->u.zset.current) return 0; /* No active iterator. */ @@ -4979,7 +5074,7 @@ int VM_ZsetRangeNext(ValkeyModuleKey *key) { return 0; } else { /* Are we still within the range? */ - if (key->u.zset.type == VALKEYMODULE_ZSET_RANGE_SCORE) { + if (key->u.zset.type == REDISMODULE_ZSET_RANGE_SCORE) { /* Fetch the next element score for the * range check. */ unsigned char *saved_next = next; @@ -4990,7 +5085,7 @@ int VM_ZsetRangeNext(ValkeyModuleKey *key) { return 0; } next = saved_next; - } else if (key->u.zset.type == VALKEYMODULE_ZSET_RANGE_LEX) { + } else if (key->u.zset.type == REDISMODULE_ZSET_RANGE_LEX) { if (!zzlLexValueLteMax(next,&key->u.zset.lrs)) { key->u.zset.er = 1; return 0; @@ -5006,12 +5101,12 @@ int VM_ZsetRangeNext(ValkeyModuleKey *key) { return 0; } else { /* Are we still within the range? */ - if (key->u.zset.type == VALKEYMODULE_ZSET_RANGE_SCORE && + if (key->u.zset.type == REDISMODULE_ZSET_RANGE_SCORE && !zslValueLteMax(next->score,&key->u.zset.rs)) { key->u.zset.er = 1; return 0; - } else if (key->u.zset.type == VALKEYMODULE_ZSET_RANGE_LEX) { + } else if (key->u.zset.type == REDISMODULE_ZSET_RANGE_LEX) { if (!zslLexValueLteMax(next->ele,&key->u.zset.lrs)) { key->u.zset.er = 1; return 0; @@ -5028,7 +5123,7 @@ int VM_ZsetRangeNext(ValkeyModuleKey *key) { /* Go to the previous element of the sorted set iterator. Returns 1 if there was * a previous element, 0 if we are already at the first element or the range * does not include any item at all. */ -int VM_ZsetRangePrev(ValkeyModuleKey *key) { +int RM_ZsetRangePrev(RedisModuleKey *key) { if (!key->value || key->value->type != OBJ_ZSET) return 0; if (!key->u.zset.type || !key->u.zset.current) return 0; /* No active iterator. */ @@ -5043,7 +5138,7 @@ int VM_ZsetRangePrev(ValkeyModuleKey *key) { return 0; } else { /* Are we still within the range? */ - if (key->u.zset.type == VALKEYMODULE_ZSET_RANGE_SCORE) { + if (key->u.zset.type == REDISMODULE_ZSET_RANGE_SCORE) { /* Fetch the previous element score for the * range check. */ unsigned char *saved_prev = prev; @@ -5054,7 +5149,7 @@ int VM_ZsetRangePrev(ValkeyModuleKey *key) { return 0; } prev = saved_prev; - } else if (key->u.zset.type == VALKEYMODULE_ZSET_RANGE_LEX) { + } else if (key->u.zset.type == REDISMODULE_ZSET_RANGE_LEX) { if (!zzlLexValueGteMin(prev,&key->u.zset.lrs)) { key->u.zset.er = 1; return 0; @@ -5070,12 +5165,12 @@ int VM_ZsetRangePrev(ValkeyModuleKey *key) { return 0; } else { /* Are we still within the range? */ - if (key->u.zset.type == VALKEYMODULE_ZSET_RANGE_SCORE && + if (key->u.zset.type == REDISMODULE_ZSET_RANGE_SCORE && !zslValueGteMin(prev->score,&key->u.zset.rs)) { key->u.zset.er = 1; return 0; - } else if (key->u.zset.type == VALKEYMODULE_ZSET_RANGE_LEX) { + } else if (key->u.zset.type == REDISMODULE_ZSET_RANGE_LEX) { if (!zslLexValueGteMin(prev->ele,&key->u.zset.lrs)) { key->u.zset.er = 1; return 0; @@ -5092,7 +5187,7 @@ int VM_ZsetRangePrev(ValkeyModuleKey *key) { /* -------------------------------------------------------------------------- * ## Key API for Hash type * - * See also VM_ValueLength(), which returns the number of fields in a hash. + * See also RM_ValueLength(), which returns the number of fields in a hash. * -------------------------------------------------------------------------- */ /* Set the field of the specified hash field to the specified value. @@ -5100,33 +5195,33 @@ int VM_ZsetRangePrev(ValkeyModuleKey *key) { * hash value, in order to set the specified field. * * The function is variadic and the user must specify pairs of field - * names and values, both as ValkeyModuleString pointers (unless the + * names and values, both as RedisModuleString pointers (unless the * CFIELD option is set, see later). At the end of the field/value-ptr pairs, * NULL must be specified as last argument to signal the end of the arguments * in the variadic function. * * Example to set the hash argv[1] to the value argv[2]: * - * ValkeyModule_HashSet(key,VALKEYMODULE_HASH_NONE,argv[1],argv[2],NULL); + * RedisModule_HashSet(key,REDISMODULE_HASH_NONE,argv[1],argv[2],NULL); * * The function can also be used in order to delete fields (if they exist) - * by setting them to the specified value of VALKEYMODULE_HASH_DELETE: + * by setting them to the specified value of REDISMODULE_HASH_DELETE: * - * ValkeyModule_HashSet(key,VALKEYMODULE_HASH_NONE,argv[1], - * VALKEYMODULE_HASH_DELETE,NULL); + * RedisModule_HashSet(key,REDISMODULE_HASH_NONE,argv[1], + * REDISMODULE_HASH_DELETE,NULL); * * The behavior of the command changes with the specified flags, that can be - * set to VALKEYMODULE_HASH_NONE if no special behavior is needed. + * set to REDISMODULE_HASH_NONE if no special behavior is needed. * - * VALKEYMODULE_HASH_NX: The operation is performed only if the field was not + * REDISMODULE_HASH_NX: The operation is performed only if the field was not * already existing in the hash. - * VALKEYMODULE_HASH_XX: The operation is performed only if the field was + * REDISMODULE_HASH_XX: The operation is performed only if the field was * already existing, so that a new value could be * associated to an existing filed, but no new fields * are created. - * VALKEYMODULE_HASH_CFIELDS: The field names passed are null terminated C - * strings instead of ValkeyModuleString objects. - * VALKEYMODULE_HASH_COUNT_ALL: Include the number of inserted fields in the + * REDISMODULE_HASH_CFIELDS: The field names passed are null terminated C + * strings instead of RedisModuleString objects. + * REDISMODULE_HASH_COUNT_ALL: Include the number of inserted fields in the * returned number, in addition to the number of * updated and deleted fields. (Added in Redis * 6.2.) @@ -5134,18 +5229,18 @@ int VM_ZsetRangePrev(ValkeyModuleKey *key) { * Unless NX is specified, the command overwrites the old field value with * the new one. * - * When using VALKEYMODULE_HASH_CFIELDS, field names are reported using + * When using REDISMODULE_HASH_CFIELDS, field names are reported using * normal C strings, so for example to delete the field "foo" the following * code can be used: * - * ValkeyModule_HashSet(key,VALKEYMODULE_HASH_CFIELDS,"foo", - * VALKEYMODULE_HASH_DELETE,NULL); + * RedisModule_HashSet(key,REDISMODULE_HASH_CFIELDS,"foo", + * REDISMODULE_HASH_DELETE,NULL); * * Return value: * * The number of fields existing in the hash prior to the call, which have been * updated (its old value has been replaced by a new value) or deleted. If the - * flag VALKEYMODULE_HASH_COUNT_ALL is set, inserted fields not previously + * flag REDISMODULE_HASH_COUNT_ALL is set, inserted fields not previously * existing in the hash are also counted. * * If the return value is zero, `errno` is set (since Redis 6.2) as follows: @@ -5162,53 +5257,53 @@ int VM_ZsetRangePrev(ValkeyModuleKey *key) { * between Redis 6.2 and older versions. Modules that use it should determine * the Redis version and handle it accordingly. */ -int VM_HashSet(ValkeyModuleKey *key, int flags, ...) { +int RM_HashSet(RedisModuleKey *key, int flags, ...) { va_list ap; - if (!key || (flags & ~(VALKEYMODULE_HASH_NX | - VALKEYMODULE_HASH_XX | - VALKEYMODULE_HASH_CFIELDS | - VALKEYMODULE_HASH_COUNT_ALL))) { + if (!key || (flags & ~(REDISMODULE_HASH_NX | + REDISMODULE_HASH_XX | + REDISMODULE_HASH_CFIELDS | + REDISMODULE_HASH_COUNT_ALL))) { errno = EINVAL; return 0; } else if (key->value && key->value->type != OBJ_HASH) { errno = ENOTSUP; return 0; - } else if (!(key->mode & VALKEYMODULE_WRITE)) { + } else if (!(key->mode & REDISMODULE_WRITE)) { errno = EBADF; return 0; } - if (key->value == NULL) moduleCreateEmptyKey(key,VALKEYMODULE_KEYTYPE_HASH); + if (key->value == NULL) moduleCreateEmptyKey(key,REDISMODULE_KEYTYPE_HASH); int count = 0; va_start(ap, flags); while(1) { - ValkeyModuleString *field, *value; + RedisModuleString *field, *value; /* Get the field and value objects. */ - if (flags & VALKEYMODULE_HASH_CFIELDS) { + if (flags & REDISMODULE_HASH_CFIELDS) { char *cfield = va_arg(ap,char*); if (cfield == NULL) break; field = createRawStringObject(cfield,strlen(cfield)); } else { - field = va_arg(ap,ValkeyModuleString*); + field = va_arg(ap,RedisModuleString*); if (field == NULL) break; } - value = va_arg(ap,ValkeyModuleString*); + value = va_arg(ap,RedisModuleString*); /* Handle XX and NX */ - if (flags & (VALKEYMODULE_HASH_XX|VALKEYMODULE_HASH_NX)) { + if (flags & (REDISMODULE_HASH_XX|REDISMODULE_HASH_NX)) { int exists = hashTypeExists(key->value, field->ptr); - if (((flags & VALKEYMODULE_HASH_XX) && !exists) || - ((flags & VALKEYMODULE_HASH_NX) && exists)) + if (((flags & REDISMODULE_HASH_XX) && !exists) || + ((flags & REDISMODULE_HASH_NX) && exists)) { - if (flags & VALKEYMODULE_HASH_CFIELDS) decrRefCount(field); + if (flags & REDISMODULE_HASH_CFIELDS) decrRefCount(field); continue; } } - /* Handle deletion if value is VALKEYMODULE_HASH_DELETE. */ - if (value == VALKEYMODULE_HASH_DELETE) { + /* Handle deletion if value is REDISMODULE_HASH_DELETE. */ + if (value == REDISMODULE_HASH_DELETE) { count += hashTypeDelete(key->value, field->ptr); - if (flags & VALKEYMODULE_HASH_CFIELDS) decrRefCount(field); + if (flags & REDISMODULE_HASH_CFIELDS) decrRefCount(field); continue; } @@ -5216,17 +5311,17 @@ int VM_HashSet(ValkeyModuleKey *key, int flags, ...) { /* If CFIELDS is active, we can pass the ownership of the * SDS object to the low level function that sets the field * to avoid a useless copy. */ - if (flags & VALKEYMODULE_HASH_CFIELDS) + if (flags & REDISMODULE_HASH_CFIELDS) low_flags |= HASH_SET_TAKE_FIELD; robj *argv[2] = {field,value}; hashTypeTryConversion(key->value,argv,0,1); int updated = hashTypeSet(key->value, field->ptr, value->ptr, low_flags); - count += (flags & VALKEYMODULE_HASH_COUNT_ALL) ? 1 : updated; + count += (flags & REDISMODULE_HASH_COUNT_ALL) ? 1 : updated; /* If CFIELDS is active, SDS string ownership is now of hashTypeSet(), * however we still have to release the 'field' object shell. */ - if (flags & VALKEYMODULE_HASH_CFIELDS) { + if (flags & REDISMODULE_HASH_CFIELDS) { field->ptr = NULL; /* Prevent the SDS string from being freed. */ decrRefCount(field); } @@ -5238,73 +5333,73 @@ int VM_HashSet(ValkeyModuleKey *key, int flags, ...) { } /* Get fields from a hash value. This function is called using a variable - * number of arguments, alternating a field name (as a ValkeyModuleString - * pointer) with a pointer to a ValkeyModuleString pointer, that is set to the + * number of arguments, alternating a field name (as a RedisModuleString + * pointer) with a pointer to a RedisModuleString pointer, that is set to the * value of the field if the field exists, or NULL if the field does not exist. * At the end of the field/value-ptr pairs, NULL must be specified as last * argument to signal the end of the arguments in the variadic function. * * This is an example usage: * - * ValkeyModuleString *first, *second; - * ValkeyModule_HashGet(mykey,VALKEYMODULE_HASH_NONE,argv[1],&first, + * RedisModuleString *first, *second; + * RedisModule_HashGet(mykey,REDISMODULE_HASH_NONE,argv[1],&first, * argv[2],&second,NULL); * - * As with ValkeyModule_HashSet() the behavior of the command can be specified - * passing flags different than VALKEYMODULE_HASH_NONE: + * As with RedisModule_HashSet() the behavior of the command can be specified + * passing flags different than REDISMODULE_HASH_NONE: * - * VALKEYMODULE_HASH_CFIELDS: field names as null terminated C strings. + * REDISMODULE_HASH_CFIELDS: field names as null terminated C strings. * - * VALKEYMODULE_HASH_EXISTS: instead of setting the value of the field - * expecting a ValkeyModuleString pointer to pointer, the function just + * REDISMODULE_HASH_EXISTS: instead of setting the value of the field + * 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. * - * Example of VALKEYMODULE_HASH_CFIELDS: + * Example of REDISMODULE_HASH_CFIELDS: * - * ValkeyModuleString *username, *hashedpass; - * ValkeyModule_HashGet(mykey,VALKEYMODULE_HASH_CFIELDS,"username",&username,"hp",&hashedpass, NULL); + * RedisModuleString *username, *hashedpass; + * RedisModule_HashGet(mykey,REDISMODULE_HASH_CFIELDS,"username",&username,"hp",&hashedpass, NULL); * - * Example of VALKEYMODULE_HASH_EXISTS: + * Example of REDISMODULE_HASH_EXISTS: * * int exists; - * ValkeyModule_HashGet(mykey,VALKEYMODULE_HASH_EXISTS,argv[1],&exists,NULL); + * RedisModule_HashGet(mykey,REDISMODULE_HASH_EXISTS,argv[1],&exists,NULL); * - * The function returns VALKEYMODULE_OK on success and VALKEYMODULE_ERR if + * The function returns REDISMODULE_OK on success and REDISMODULE_ERR if * the key is not a hash value. * * Memory management: * - * The returned ValkeyModuleString objects should be released with - * ValkeyModule_FreeString(), or by enabling automatic memory management. + * The returned RedisModuleString objects should be released with + * RedisModule_FreeString(), or by enabling automatic memory management. */ -int VM_HashGet(ValkeyModuleKey *key, int flags, ...) { +int RM_HashGet(RedisModuleKey *key, int flags, ...) { va_list ap; - if (key->value && key->value->type != OBJ_HASH) return VALKEYMODULE_ERR; + if (key->value && key->value->type != OBJ_HASH) return REDISMODULE_ERR; va_start(ap, flags); while(1) { - ValkeyModuleString *field, **valueptr; + RedisModuleString *field, **valueptr; int *existsptr; /* Get the field object and the value pointer to pointer. */ - if (flags & VALKEYMODULE_HASH_CFIELDS) { + if (flags & REDISMODULE_HASH_CFIELDS) { char *cfield = va_arg(ap,char*); if (cfield == NULL) break; field = createRawStringObject(cfield,strlen(cfield)); } else { - field = va_arg(ap,ValkeyModuleString*); + field = va_arg(ap,RedisModuleString*); if (field == NULL) break; } /* Query the hash for existence or value object. */ - if (flags & VALKEYMODULE_HASH_EXISTS) { + if (flags & REDISMODULE_HASH_EXISTS) { existsptr = va_arg(ap,int*); if (key->value) *existsptr = hashTypeExists(key->value,field->ptr); else *existsptr = 0; } else { - valueptr = va_arg(ap,ValkeyModuleString**); + valueptr = va_arg(ap,RedisModuleString**); if (key->value) { *valueptr = hashTypeGetValueObject(key->value,field->ptr); if (*valueptr) { @@ -5313,17 +5408,17 @@ int VM_HashGet(ValkeyModuleKey *key, int flags, ...) { *valueptr = decoded; } if (*valueptr) - autoMemoryAdd(key->ctx,VALKEYMODULE_AM_STRING,*valueptr); + autoMemoryAdd(key->ctx,REDISMODULE_AM_STRING,*valueptr); } else { *valueptr = NULL; } } /* Cleanup */ - if (flags & VALKEYMODULE_HASH_CFIELDS) decrRefCount(field); + if (flags & REDISMODULE_HASH_CFIELDS) decrRefCount(field); } va_end(ap); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* -------------------------------------------------------------------------- @@ -5331,23 +5426,23 @@ int VM_HashGet(ValkeyModuleKey *key, int flags, ...) { * * For an introduction to streams, see https://redis.io/topics/streams-intro. * - * The type ValkeyModuleStreamID, which is used in stream functions, is a struct + * The type RedisModuleStreamID, which is used in stream functions, is a struct * with two 64-bit fields and is defined as * - * typedef struct ValkeyModuleStreamID { + * typedef struct RedisModuleStreamID { * uint64_t ms; * uint64_t seq; - * } ValkeyModuleStreamID; + * } RedisModuleStreamID; * - * See also VM_ValueLength(), which returns the length of a stream, and the - * conversion functions VM_StringToStreamID() and VM_CreateStringFromStreamID(). + * See also RM_ValueLength(), which returns the length of a stream, and the + * conversion functions RM_StringToStreamID() and RM_CreateStringFromStreamID(). * -------------------------------------------------------------------------- */ /* Adds an entry to a stream. Like XADD without trimming. * * - `key`: The key where the stream is (or will be) stored * - `flags`: A bit field of - * - `VALKEYMODULE_STREAM_ADD_AUTOID`: Assign a stream ID automatically, like + * - `REDISMODULE_STREAM_ADD_AUTOID`: Assign a stream ID automatically, like * `*` in the XADD command. * - `id`: If the `AUTOID` flag is set, this is where the assigned ID is * returned. Can be NULL if `AUTOID` is set, if you don't care to receive the @@ -5356,8 +5451,8 @@ int VM_HashGet(ValkeyModuleKey *key, int flags, ...) { * fields and values. * - `numfields`: The number of field-value pairs in `argv`. * - * Returns VALKEYMODULE_OK if an entry has been added. On failure, - * VALKEYMODULE_ERR is returned and `errno` is set as follows: + * Returns REDISMODULE_OK if an entry has been added. On failure, + * REDISMODULE_ERR is returned and `errno` is set as follows: * * - EINVAL if called with invalid arguments * - ENOTSUP if the key refers to a value of a type other than stream @@ -5367,29 +5462,29 @@ int VM_HashGet(ValkeyModuleKey *key, int flags, ...) { * - EFBIG if the stream has reached the last possible ID * - ERANGE if the elements are too large to be stored. */ -int VM_StreamAdd(ValkeyModuleKey *key, int flags, ValkeyModuleStreamID *id, ValkeyModuleString **argv, long numfields) { +int RM_StreamAdd(RedisModuleKey *key, int flags, RedisModuleStreamID *id, RedisModuleString **argv, long numfields) { /* Validate args */ if (!key || (numfields != 0 && !argv) || /* invalid key or argv */ - (flags & ~(VALKEYMODULE_STREAM_ADD_AUTOID)) || /* invalid flags */ - (!(flags & VALKEYMODULE_STREAM_ADD_AUTOID) && !id)) { /* id required */ + (flags & ~(REDISMODULE_STREAM_ADD_AUTOID)) || /* invalid flags */ + (!(flags & REDISMODULE_STREAM_ADD_AUTOID) && !id)) { /* id required */ errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (key->value && key->value->type != OBJ_STREAM) { errno = ENOTSUP; /* wrong type */ - return VALKEYMODULE_ERR; - } else if (!(key->mode & VALKEYMODULE_WRITE)) { + return REDISMODULE_ERR; + } else if (!(key->mode & REDISMODULE_WRITE)) { errno = EBADF; /* key not open for writing */ - return VALKEYMODULE_ERR; - } else if (!(flags & VALKEYMODULE_STREAM_ADD_AUTOID) && + return REDISMODULE_ERR; + } else if (!(flags & REDISMODULE_STREAM_ADD_AUTOID) && id->ms == 0 && id->seq == 0) { errno = EDOM; /* ID out of range */ - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } /* Create key if necessary */ int created = 0; if (key->value == NULL) { - moduleCreateEmptyKey(key, VALKEYMODULE_KEYTYPE_STREAM); + moduleCreateEmptyKey(key, REDISMODULE_KEYTYPE_STREAM); created = 1; } @@ -5397,13 +5492,13 @@ int VM_StreamAdd(ValkeyModuleKey *key, int flags, ValkeyModuleStreamID *id, Valk if (s->last_id.ms == UINT64_MAX && s->last_id.seq == UINT64_MAX) { /* The stream has reached the last possible ID */ errno = EFBIG; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } streamID added_id; streamID use_id; streamID *use_id_ptr = NULL; - if (!(flags & VALKEYMODULE_STREAM_ADD_AUTOID)) { + if (!(flags & REDISMODULE_STREAM_ADD_AUTOID)) { use_id.ms = id->ms; use_id.seq = id->seq; use_id_ptr = &use_id; @@ -5414,7 +5509,7 @@ int VM_StreamAdd(ValkeyModuleKey *key, int flags, ValkeyModuleStreamID *id, Valk * the elements are too large to be stored. either way, errno is already * set by streamAppendItem. */ if (created) moduleDelKeyIfEmpty(key); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } /* Postponed signalKeyAsReady(). Done implicitly by moduleCreateEmptyKey() * so not needed if the stream has just been created. */ @@ -5425,7 +5520,7 @@ int VM_StreamAdd(ValkeyModuleKey *key, int flags, ValkeyModuleStreamID *id, Valk id->seq = added_id.seq; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Deletes an entry from a stream. @@ -5433,7 +5528,7 @@ int VM_StreamAdd(ValkeyModuleKey *key, int flags, ValkeyModuleStreamID *id, Valk * - `key`: A key opened for writing, with no stream iterator started. * - `id`: The stream ID of the entry to delete. * - * Returns VALKEYMODULE_OK on success. On failure, VALKEYMODULE_ERR is returned + * Returns REDISMODULE_OK on success. On failure, REDISMODULE_ERR is returned * and `errno` is set as follows: * * - EINVAL if called with invalid arguments @@ -5443,44 +5538,44 @@ int VM_StreamAdd(ValkeyModuleKey *key, int flags, ValkeyModuleStreamID *id, Valk * associated with the key * - ENOENT if no entry with the given stream ID exists * - * See also VM_StreamIteratorDelete() for deleting the current entry while + * See also RM_StreamIteratorDelete() for deleting the current entry while * iterating using a stream iterator. */ -int VM_StreamDelete(ValkeyModuleKey *key, ValkeyModuleStreamID *id) { +int RM_StreamDelete(RedisModuleKey *key, RedisModuleStreamID *id) { if (!key || !id) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (!key->value || key->value->type != OBJ_STREAM) { errno = ENOTSUP; /* wrong type */ - return VALKEYMODULE_ERR; - } else if (!(key->mode & VALKEYMODULE_WRITE) || + return REDISMODULE_ERR; + } else if (!(key->mode & REDISMODULE_WRITE) || key->iter != NULL) { errno = EBADF; /* key not opened for writing or iterator started */ - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } stream *s = key->value->ptr; streamID streamid = {id->ms, id->seq}; if (streamDeleteItem(s, &streamid)) { - return VALKEYMODULE_OK; + return REDISMODULE_OK; } else { errno = ENOENT; /* no entry with this id */ - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } } /* Sets up a stream iterator. * - * - `key`: The stream key opened for reading using ValkeyModule_OpenKey(). + * - `key`: The stream key opened for reading using RedisModule_OpenKey(). * - `flags`: - * - `VALKEYMODULE_STREAM_ITERATOR_EXCLUSIVE`: Don't include `start` and `end` + * - `REDISMODULE_STREAM_ITERATOR_EXCLUSIVE`: Don't include `start` and `end` * in the iterated range. - * - `VALKEYMODULE_STREAM_ITERATOR_REVERSE`: Iterate in reverse order, starting + * - `REDISMODULE_STREAM_ITERATOR_REVERSE`: Iterate in reverse order, starting * from the `end` of the range. * - `start`: The lower bound of the range. Use NULL for the beginning of the * stream. * - `end`: The upper bound of the range. Use NULL for the end of the stream. * - * Returns VALKEYMODULE_OK on success. On failure, VALKEYMODULE_ERR is returned + * Returns REDISMODULE_OK on success. On failure, REDISMODULE_ERR is returned * and `errno` is set as follows: * * - EINVAL if called with invalid arguments @@ -5490,76 +5585,76 @@ int VM_StreamDelete(ValkeyModuleKey *key, ValkeyModuleStreamID *id) { * already associated with the key * - EDOM if `start` or `end` is outside the valid range * - * Returns VALKEYMODULE_OK on success and VALKEYMODULE_ERR if the key doesn't + * Returns REDISMODULE_OK on success and REDISMODULE_ERR if the key doesn't * refer to a stream or if invalid arguments were given. * - * The stream IDs are retrieved using ValkeyModule_StreamIteratorNextID() and + * The stream IDs are retrieved using RedisModule_StreamIteratorNextID() and * for each stream ID, the fields and values are retrieved using - * ValkeyModule_StreamIteratorNextField(). The iterator is freed by calling - * ValkeyModule_StreamIteratorStop(). + * RedisModule_StreamIteratorNextField(). The iterator is freed by calling + * RedisModule_StreamIteratorStop(). * * Example (error handling omitted): * - * ValkeyModule_StreamIteratorStart(key, 0, startid_ptr, endid_ptr); - * ValkeyModuleStreamID id; + * RedisModule_StreamIteratorStart(key, 0, startid_ptr, endid_ptr); + * RedisModuleStreamID id; * long numfields; - * while (ValkeyModule_StreamIteratorNextID(key, &id, &numfields) == - * VALKEYMODULE_OK) { - * ValkeyModuleString *field, *value; - * while (ValkeyModule_StreamIteratorNextField(key, &field, &value) == - * VALKEYMODULE_OK) { + * while (RedisModule_StreamIteratorNextID(key, &id, &numfields) == + * REDISMODULE_OK) { + * RedisModuleString *field, *value; + * while (RedisModule_StreamIteratorNextField(key, &field, &value) == + * REDISMODULE_OK) { * // * // ... Do stuff ... * // - * ValkeyModule_FreeString(ctx, field); - * ValkeyModule_FreeString(ctx, value); + * RedisModule_FreeString(ctx, field); + * RedisModule_FreeString(ctx, value); * } * } - * ValkeyModule_StreamIteratorStop(key); + * RedisModule_StreamIteratorStop(key); */ -int VM_StreamIteratorStart(ValkeyModuleKey *key, int flags, ValkeyModuleStreamID *start, ValkeyModuleStreamID *end) { +int RM_StreamIteratorStart(RedisModuleKey *key, int flags, RedisModuleStreamID *start, RedisModuleStreamID *end) { /* check args */ if (!key || - (flags & ~(VALKEYMODULE_STREAM_ITERATOR_EXCLUSIVE | - VALKEYMODULE_STREAM_ITERATOR_REVERSE))) { + (flags & ~(REDISMODULE_STREAM_ITERATOR_EXCLUSIVE | + REDISMODULE_STREAM_ITERATOR_REVERSE))) { errno = EINVAL; /* key missing or invalid flags */ - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (!key->value || key->value->type != OBJ_STREAM) { errno = ENOTSUP; - return VALKEYMODULE_ERR; /* not a stream */ + return REDISMODULE_ERR; /* not a stream */ } else if (key->iter) { errno = EBADF; /* iterator already started */ - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } /* define range for streamIteratorStart() */ streamID lower, upper; if (start) lower = (streamID){start->ms, start->seq}; if (end) upper = (streamID){end->ms, end->seq}; - if (flags & VALKEYMODULE_STREAM_ITERATOR_EXCLUSIVE) { + if (flags & REDISMODULE_STREAM_ITERATOR_EXCLUSIVE) { if ((start && streamIncrID(&lower) != C_OK) || (end && streamDecrID(&upper) != C_OK)) { errno = EDOM; /* end is 0-0 or start is MAX-MAX? */ - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } } /* create iterator */ stream *s = key->value->ptr; - int rev = flags & VALKEYMODULE_STREAM_ITERATOR_REVERSE; + int rev = flags & REDISMODULE_STREAM_ITERATOR_REVERSE; streamIterator *si = zmalloc(sizeof(*si)); streamIteratorStart(si, s, start ? &lower : NULL, end ? &upper : NULL, rev); key->iter = si; - key->u.stream.currentid.ms = 0; /* for VM_StreamIteratorDelete() */ + key->u.stream.currentid.ms = 0; /* for RM_StreamIteratorDelete() */ key->u.stream.currentid.seq = 0; - key->u.stream.numfieldsleft = 0; /* for VM_StreamIteratorNextField() */ - return VALKEYMODULE_OK; + key->u.stream.numfieldsleft = 0; /* for RM_StreamIteratorNextField() */ + return REDISMODULE_OK; } -/* Stops a stream iterator created using ValkeyModule_StreamIteratorStart() and +/* Stops a stream iterator created using RedisModule_StreamIteratorStart() and * reclaims its memory. * - * Returns VALKEYMODULE_OK on success. On failure, VALKEYMODULE_ERR is returned + * Returns REDISMODULE_OK on success. On failure, REDISMODULE_ERR is returned * and `errno` is set as follows: * * - EINVAL if called with a NULL key @@ -5568,34 +5663,34 @@ int VM_StreamIteratorStart(ValkeyModuleKey *key, int flags, ValkeyModuleStreamID * - EBADF if the key was not opened for writing or if no stream iterator is * associated with the key */ -int VM_StreamIteratorStop(ValkeyModuleKey *key) { +int RM_StreamIteratorStop(RedisModuleKey *key) { if (!key) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (!key->value || key->value->type != OBJ_STREAM) { errno = ENOTSUP; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (!key->iter) { errno = EBADF; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } streamIteratorStop(key->iter); zfree(key->iter); key->iter = NULL; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Finds the next stream entry and returns its stream ID and the number of * fields. * * - `key`: Key for which a stream iterator has been started using - * ValkeyModule_StreamIteratorStart(). + * RedisModule_StreamIteratorStart(). * - `id`: The stream ID returned. NULL if you don't care. * - `numfields`: The number of fields in the found stream entry. NULL if you * don't care. * - * Returns VALKEYMODULE_OK and sets `*id` and `*numfields` if an entry was found. - * On failure, VALKEYMODULE_ERR is returned and `errno` is set as follows: + * Returns REDISMODULE_OK and sets `*id` and `*numfields` if an entry was found. + * On failure, REDISMODULE_ERR is returned and `errno` is set as follows: * * - EINVAL if called with a NULL key * - ENOTSUP if the key refers to a value of a type other than stream or if the @@ -5603,23 +5698,23 @@ int VM_StreamIteratorStop(ValkeyModuleKey *key) { * - EBADF if no stream iterator is associated with the key * - ENOENT if there are no more entries in the range of the iterator * - * In practice, if VM_StreamIteratorNextID() is called after a successful call - * to VM_StreamIteratorStart() and with the same key, it is safe to assume that - * an VALKEYMODULE_ERR return value means that there are no more entries. + * In practice, if RM_StreamIteratorNextID() is called after a successful call + * to RM_StreamIteratorStart() and with the same key, it is safe to assume that + * an REDISMODULE_ERR return value means that there are no more entries. * - * Use ValkeyModule_StreamIteratorNextField() to retrieve the fields and values. - * See the example at ValkeyModule_StreamIteratorStart(). + * Use RedisModule_StreamIteratorNextField() to retrieve the fields and values. + * See the example at RedisModule_StreamIteratorStart(). */ -int VM_StreamIteratorNextID(ValkeyModuleKey *key, ValkeyModuleStreamID *id, long *numfields) { +int RM_StreamIteratorNextID(RedisModuleKey *key, RedisModuleStreamID *id, long *numfields) { if (!key) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (!key->value || key->value->type != OBJ_STREAM) { errno = ENOTSUP; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (!key->iter) { errno = EBADF; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } streamIterator *si = key->iter; int64_t *num_ptr = &key->u.stream.numfieldsleft; @@ -5630,29 +5725,29 @@ int VM_StreamIteratorNextID(ValkeyModuleKey *key, ValkeyModuleStreamID *id, long id->seq = streamid_ptr->seq; } if (numfields) *numfields = *num_ptr; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } else { /* No entry found. */ - key->u.stream.currentid.ms = 0; /* for VM_StreamIteratorDelete() */ + key->u.stream.currentid.ms = 0; /* for RM_StreamIteratorDelete() */ key->u.stream.currentid.seq = 0; - key->u.stream.numfieldsleft = 0; /* for VM_StreamIteratorNextField() */ + key->u.stream.numfieldsleft = 0; /* for RM_StreamIteratorNextField() */ errno = ENOENT; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } } /* Retrieves the next field of the current stream ID and its corresponding value * in a stream iteration. This function should be called repeatedly after calling - * ValkeyModule_StreamIteratorNextID() to fetch each field-value pair. + * RedisModule_StreamIteratorNextID() to fetch each field-value pair. * * - `key`: Key where a stream iterator has been started. * - `field_ptr`: This is where the field is returned. * - `value_ptr`: This is where the value is returned. * - * Returns VALKEYMODULE_OK and points `*field_ptr` and `*value_ptr` to freshly - * allocated ValkeyModuleString objects. The string objects are freed + * Returns REDISMODULE_OK and points `*field_ptr` and `*value_ptr` to freshly + * allocated RedisModuleString objects. The string objects are freed * automatically when the callback finishes if automatic memory is enabled. On - * failure, VALKEYMODULE_ERR is returned and `errno` is set as follows: + * failure, REDISMODULE_ERR is returned and `errno` is set as follows: * * - EINVAL if called with a NULL key * - ENOTSUP if the key refers to a value of a type other than stream or if the @@ -5660,25 +5755,25 @@ int VM_StreamIteratorNextID(ValkeyModuleKey *key, ValkeyModuleStreamID *id, long * - EBADF if no stream iterator is associated with the key * - ENOENT if there are no more fields in the current stream entry * - * In practice, if VM_StreamIteratorNextField() is called after a successful - * call to VM_StreamIteratorNextID() and with the same key, it is safe to assume - * that an VALKEYMODULE_ERR return value means that there are no more fields. + * In practice, if RM_StreamIteratorNextField() is called after a successful + * call to RM_StreamIteratorNextID() and with the same key, it is safe to assume + * that an REDISMODULE_ERR return value means that there are no more fields. * - * See the example at ValkeyModule_StreamIteratorStart(). + * See the example at RedisModule_StreamIteratorStart(). */ -int VM_StreamIteratorNextField(ValkeyModuleKey *key, ValkeyModuleString **field_ptr, ValkeyModuleString **value_ptr) { +int RM_StreamIteratorNextField(RedisModuleKey *key, RedisModuleString **field_ptr, RedisModuleString **value_ptr) { if (!key) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (!key->value || key->value->type != OBJ_STREAM) { errno = ENOTSUP; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (!key->iter) { errno = EBADF; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (key->u.stream.numfieldsleft <= 0) { errno = ENOENT; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } streamIterator *si = key->iter; unsigned char *field, *value; @@ -5686,22 +5781,22 @@ int VM_StreamIteratorNextField(ValkeyModuleKey *key, ValkeyModuleString **field_ streamIteratorGetField(si, &field, &value, &field_len, &value_len); if (field_ptr) { *field_ptr = createRawStringObject((char *)field, field_len); - autoMemoryAdd(key->ctx, VALKEYMODULE_AM_STRING, *field_ptr); + autoMemoryAdd(key->ctx, REDISMODULE_AM_STRING, *field_ptr); } if (value_ptr) { *value_ptr = createRawStringObject((char *)value, value_len); - autoMemoryAdd(key->ctx, VALKEYMODULE_AM_STRING, *value_ptr); + autoMemoryAdd(key->ctx, REDISMODULE_AM_STRING, *value_ptr); } key->u.stream.numfieldsleft--; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Deletes the current stream entry while iterating. * - * This function can be called after VM_StreamIteratorNextID() or after any - * calls to VM_StreamIteratorNextField(). + * This function can be called after RM_StreamIteratorNextID() or after any + * calls to RM_StreamIteratorNextField(). * - * Returns VALKEYMODULE_OK on success. On failure, VALKEYMODULE_ERR is returned + * Returns REDISMODULE_OK on success. On failure, REDISMODULE_ERR is returned * and `errno` is set as follows: * * - EINVAL if key is NULL @@ -5709,34 +5804,34 @@ int VM_StreamIteratorNextField(ValkeyModuleKey *key, ValkeyModuleString **field_ * - EBADF if the key is not opened for writing, if no iterator has been started * - ENOENT if the iterator has no current stream entry */ -int VM_StreamIteratorDelete(ValkeyModuleKey *key) { +int RM_StreamIteratorDelete(RedisModuleKey *key) { if (!key) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (!key->value || key->value->type != OBJ_STREAM) { errno = ENOTSUP; - return VALKEYMODULE_ERR; - } else if (!(key->mode & VALKEYMODULE_WRITE) || !key->iter) { + return REDISMODULE_ERR; + } else if (!(key->mode & REDISMODULE_WRITE) || !key->iter) { errno = EBADF; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } else if (key->u.stream.currentid.ms == 0 && key->u.stream.currentid.seq == 0) { errno = ENOENT; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } streamIterator *si = key->iter; streamIteratorRemoveEntry(si, &key->u.stream.currentid); key->u.stream.currentid.ms = 0; /* Make sure repeated Delete() fails */ key->u.stream.currentid.seq = 0; key->u.stream.numfieldsleft = 0; /* Make sure NextField() fails */ - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Trim a stream by length, similar to XTRIM with MAXLEN. * * - `key`: Key opened for writing. * - `flags`: A bitfield of - * - `VALKEYMODULE_STREAM_TRIM_APPROX`: Trim less if it improves performance, + * - `REDISMODULE_STREAM_TRIM_APPROX`: Trim less if it improves performance, * like XTRIM with `~`. * - `length`: The number of stream entries to keep after trimming. * @@ -5747,18 +5842,18 @@ int VM_StreamIteratorDelete(ValkeyModuleKey *key) { * - ENOTSUP if the key is empty or of a type other than stream * - EBADF if the key is not opened for writing */ -long long VM_StreamTrimByLength(ValkeyModuleKey *key, int flags, long long length) { - if (!key || (flags & ~(VALKEYMODULE_STREAM_TRIM_APPROX)) || length < 0) { +long long RM_StreamTrimByLength(RedisModuleKey *key, int flags, long long length) { + if (!key || (flags & ~(REDISMODULE_STREAM_TRIM_APPROX)) || length < 0) { errno = EINVAL; return -1; } else if (!key->value || key->value->type != OBJ_STREAM) { errno = ENOTSUP; return -1; - } else if (!(key->mode & VALKEYMODULE_WRITE)) { + } else if (!(key->mode & REDISMODULE_WRITE)) { errno = EBADF; return -1; } - int approx = flags & VALKEYMODULE_STREAM_TRIM_APPROX ? 1 : 0; + int approx = flags & REDISMODULE_STREAM_TRIM_APPROX ? 1 : 0; return streamTrimByLength((stream *)key->value->ptr, length, approx); } @@ -5766,7 +5861,7 @@ long long VM_StreamTrimByLength(ValkeyModuleKey *key, int flags, long long lengt * * - `key`: Key opened for writing. * - `flags`: A bitfield of - * - `VALKEYMODULE_STREAM_TRIM_APPROX`: Trim less if it improves performance, + * - `REDISMODULE_STREAM_TRIM_APPROX`: Trim less if it improves performance, * like XTRIM with `~`. * - `id`: The smallest stream ID to keep after trimming. * @@ -5777,18 +5872,18 @@ long long VM_StreamTrimByLength(ValkeyModuleKey *key, int flags, long long lengt * - ENOTSUP if the key is empty or of a type other than stream * - EBADF if the key is not opened for writing */ -long long VM_StreamTrimByID(ValkeyModuleKey *key, int flags, ValkeyModuleStreamID *id) { - if (!key || (flags & ~(VALKEYMODULE_STREAM_TRIM_APPROX)) || !id) { +long long RM_StreamTrimByID(RedisModuleKey *key, int flags, RedisModuleStreamID *id) { + if (!key || (flags & ~(REDISMODULE_STREAM_TRIM_APPROX)) || !id) { errno = EINVAL; return -1; } else if (!key->value || key->value->type != OBJ_STREAM) { errno = ENOTSUP; return -1; - } else if (!(key->mode & VALKEYMODULE_WRITE)) { + } else if (!(key->mode & REDISMODULE_WRITE)) { errno = EBADF; return -1; } - int approx = flags & VALKEYMODULE_STREAM_TRIM_APPROX ? 1 : 0; + int approx = flags & REDISMODULE_STREAM_TRIM_APPROX ? 1 : 0; streamID minid = (streamID){id->ms, id->seq}; return streamTrimByID((stream *)key->value->ptr, minid, approx); } @@ -5796,161 +5891,161 @@ long long VM_StreamTrimByID(ValkeyModuleKey *key, int flags, ValkeyModuleStreamI /* -------------------------------------------------------------------------- * ## Calling Redis commands from modules * - * VM_Call() sends a command to Redis. The remaining functions handle the reply. + * RM_Call() sends a command to Redis. The remaining functions handle the reply. * -------------------------------------------------------------------------- */ -void moduleParseCallReply_Int(ValkeyModuleCallReply *reply); -void moduleParseCallReply_BulkString(ValkeyModuleCallReply *reply); -void moduleParseCallReply_SimpleString(ValkeyModuleCallReply *reply); -void moduleParseCallReply_Array(ValkeyModuleCallReply *reply); +void moduleParseCallReply_Int(RedisModuleCallReply *reply); +void moduleParseCallReply_BulkString(RedisModuleCallReply *reply); +void moduleParseCallReply_SimpleString(RedisModuleCallReply *reply); +void moduleParseCallReply_Array(RedisModuleCallReply *reply); /* Free a Call reply and all the nested replies it contains if it's an * array. */ -void VM_FreeCallReply(ValkeyModuleCallReply *reply) { +void RM_FreeCallReply(RedisModuleCallReply *reply) { /* This is a wrapper for the recursive free reply function. This is needed * in order to have the first level function to return on nested replies, * but only if called by the module API. */ - ValkeyModuleCtx *ctx = NULL; - if(callReplyType(reply) == VALKEYMODULE_REPLY_PROMISE) { - ValkeyModuleAsyncRMCallPromise *promise = callReplyGetPrivateData(reply); + RedisModuleCtx *ctx = NULL; + if(callReplyType(reply) == REDISMODULE_REPLY_PROMISE) { + RedisModuleAsyncRMCallPromise *promise = callReplyGetPrivateData(reply); ctx = promise->ctx; - freeValkeyModuleAsyncRMCallPromise(promise); + freeRedisModuleAsyncRMCallPromise(promise); } else { ctx = callReplyGetPrivateData(reply); } freeCallReply(reply); if (ctx) { - autoMemoryFreed(ctx,VALKEYMODULE_AM_REPLY,reply); + autoMemoryFreed(ctx,REDISMODULE_AM_REPLY,reply); } } /* Return the reply type as one of the following: * - * - VALKEYMODULE_REPLY_UNKNOWN - * - VALKEYMODULE_REPLY_STRING - * - VALKEYMODULE_REPLY_ERROR - * - VALKEYMODULE_REPLY_INTEGER - * - VALKEYMODULE_REPLY_ARRAY - * - VALKEYMODULE_REPLY_NULL - * - VALKEYMODULE_REPLY_MAP - * - VALKEYMODULE_REPLY_SET - * - VALKEYMODULE_REPLY_BOOL - * - VALKEYMODULE_REPLY_DOUBLE - * - VALKEYMODULE_REPLY_BIG_NUMBER - * - VALKEYMODULE_REPLY_VERBATIM_STRING - * - VALKEYMODULE_REPLY_ATTRIBUTE - * - VALKEYMODULE_REPLY_PROMISE */ -int VM_CallReplyType(ValkeyModuleCallReply *reply) { + * - REDISMODULE_REPLY_UNKNOWN + * - REDISMODULE_REPLY_STRING + * - REDISMODULE_REPLY_ERROR + * - REDISMODULE_REPLY_INTEGER + * - REDISMODULE_REPLY_ARRAY + * - REDISMODULE_REPLY_NULL + * - REDISMODULE_REPLY_MAP + * - REDISMODULE_REPLY_SET + * - REDISMODULE_REPLY_BOOL + * - REDISMODULE_REPLY_DOUBLE + * - REDISMODULE_REPLY_BIG_NUMBER + * - REDISMODULE_REPLY_VERBATIM_STRING + * - REDISMODULE_REPLY_ATTRIBUTE + * - REDISMODULE_REPLY_PROMISE */ +int RM_CallReplyType(RedisModuleCallReply *reply) { return callReplyType(reply); } /* Return the reply type length, where applicable. */ -size_t VM_CallReplyLength(ValkeyModuleCallReply *reply) { +size_t RM_CallReplyLength(RedisModuleCallReply *reply) { return callReplyGetLen(reply); } /* Return the 'idx'-th nested call reply element of an array reply, or NULL * if the reply type is wrong or the index is out of range. */ -ValkeyModuleCallReply *VM_CallReplyArrayElement(ValkeyModuleCallReply *reply, size_t idx) { +RedisModuleCallReply *RM_CallReplyArrayElement(RedisModuleCallReply *reply, size_t idx) { return callReplyGetArrayElement(reply, idx); } /* Return the `long long` of an integer reply. */ -long long VM_CallReplyInteger(ValkeyModuleCallReply *reply) { +long long RM_CallReplyInteger(RedisModuleCallReply *reply) { return callReplyGetLongLong(reply); } /* Return the double value of a double reply. */ -double VM_CallReplyDouble(ValkeyModuleCallReply *reply) { +double RM_CallReplyDouble(RedisModuleCallReply *reply) { return callReplyGetDouble(reply); } /* Return the big number value of a big number reply. */ -const char *VM_CallReplyBigNumber(ValkeyModuleCallReply *reply, size_t *len) { +const char *RM_CallReplyBigNumber(RedisModuleCallReply *reply, size_t *len) { return callReplyGetBigNumber(reply, len); } /* Return the value of a verbatim string reply, * An optional output argument can be given to get verbatim reply format. */ -const char *VM_CallReplyVerbatim(ValkeyModuleCallReply *reply, size_t *len, const char **format) { +const char *RM_CallReplyVerbatim(RedisModuleCallReply *reply, size_t *len, const char **format) { return callReplyGetVerbatim(reply, len, format); } /* Return the Boolean value of a Boolean reply. */ -int VM_CallReplyBool(ValkeyModuleCallReply *reply) { +int RM_CallReplyBool(RedisModuleCallReply *reply) { return callReplyGetBool(reply); } /* Return the 'idx'-th nested call reply element of a set reply, or NULL * if the reply type is wrong or the index is out of range. */ -ValkeyModuleCallReply *VM_CallReplySetElement(ValkeyModuleCallReply *reply, size_t idx) { +RedisModuleCallReply *RM_CallReplySetElement(RedisModuleCallReply *reply, size_t idx) { return callReplyGetSetElement(reply, idx); } /* Retrieve the 'idx'-th key and value of a map reply. * * Returns: - * - VALKEYMODULE_OK on success. - * - VALKEYMODULE_ERR if idx out of range or if the reply type is wrong. + * - REDISMODULE_OK on success. + * - REDISMODULE_ERR if idx out of range or if the reply type is wrong. * * The `key` and `value` arguments are used to return by reference, and may be * NULL if not required. */ -int VM_CallReplyMapElement(ValkeyModuleCallReply *reply, size_t idx, ValkeyModuleCallReply **key, ValkeyModuleCallReply **val) { +int RM_CallReplyMapElement(RedisModuleCallReply *reply, size_t idx, RedisModuleCallReply **key, RedisModuleCallReply **val) { if (callReplyGetMapElement(reply, idx, key, val) == C_OK){ - return VALKEYMODULE_OK; + return REDISMODULE_OK; } - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } /* Return the attribute of the given reply, or NULL if no attribute exists. */ -ValkeyModuleCallReply *VM_CallReplyAttribute(ValkeyModuleCallReply *reply) { +RedisModuleCallReply *RM_CallReplyAttribute(RedisModuleCallReply *reply) { return callReplyGetAttribute(reply); } /* Retrieve the 'idx'-th key and value of an attribute reply. * * Returns: - * - VALKEYMODULE_OK on success. - * - VALKEYMODULE_ERR if idx out of range or if the reply type is wrong. + * - REDISMODULE_OK on success. + * - REDISMODULE_ERR if idx out of range or if the reply type is wrong. * * The `key` and `value` arguments are used to return by reference, and may be * NULL if not required. */ -int VM_CallReplyAttributeElement(ValkeyModuleCallReply *reply, size_t idx, ValkeyModuleCallReply **key, ValkeyModuleCallReply **val) { +int RM_CallReplyAttributeElement(RedisModuleCallReply *reply, size_t idx, RedisModuleCallReply **key, RedisModuleCallReply **val) { if (callReplyGetAttributeElement(reply, idx, key, val) == C_OK){ - return VALKEYMODULE_OK; + return REDISMODULE_OK; } - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } -/* Set unblock handler (callback and private data) on the given promise ValkeyModuleCallReply. - * The given reply must be of promise type (VALKEYMODULE_REPLY_PROMISE). */ -void VM_CallReplyPromiseSetUnblockHandler(ValkeyModuleCallReply *reply, ValkeyModuleOnUnblocked on_unblock, void *private_data) { - ValkeyModuleAsyncRMCallPromise *promise = callReplyGetPrivateData(reply); +/* Set unblock handler (callback and private data) on the given promise RedisModuleCallReply. + * The given reply must be of promise type (REDISMODULE_REPLY_PROMISE). */ +void RM_CallReplyPromiseSetUnblockHandler(RedisModuleCallReply *reply, RedisModuleOnUnblocked on_unblock, void *private_data) { + RedisModuleAsyncRMCallPromise *promise = callReplyGetPrivateData(reply); promise->on_unblocked = on_unblock; promise->private_data = private_data; } -/* Abort the execution of a given promise ValkeyModuleCallReply. - * return REDMODULE_OK in case the abort was done successfully and VALKEYMODULE_ERR +/* Abort the execution of a given promise RedisModuleCallReply. + * return REDMODULE_OK in case the abort was done successfully and REDISMODULE_ERR * if its not possible to abort the execution (execution already finished). * In case the execution was aborted (REDMODULE_OK was returned), the private_data out parameter - * will be set with the value of the private data that was given on 'VM_CallReplyPromiseSetUnblockHandler' + * will be set with the value of the private data that was given on 'RM_CallReplyPromiseSetUnblockHandler' * so the caller will be able to release the private data. * * If the execution was aborted successfully, it is promised that the unblock handler will not be called. * That said, it is possible that the abort operation will successes but the operation will still continue. * This can happened if, for example, a module implements some blocking command and does not respect the * disconnect callback. For pure Redis commands this can not happened.*/ -int VM_CallReplyPromiseAbort(ValkeyModuleCallReply *reply, void **private_data) { - ValkeyModuleAsyncRMCallPromise *promise = callReplyGetPrivateData(reply); - if (!promise->c) return VALKEYMODULE_ERR; /* Promise can not be aborted, either already aborted or already finished. */ - if (!(promise->c->flags & CLIENT_BLOCKED)) return VALKEYMODULE_ERR; /* Client is not blocked anymore, can not abort it. */ +int RM_CallReplyPromiseAbort(RedisModuleCallReply *reply, void **private_data) { + RedisModuleAsyncRMCallPromise *promise = callReplyGetPrivateData(reply); + if (!promise->c) return REDISMODULE_ERR; /* Promise can not be aborted, either already aborted or already finished. */ + if (!(promise->c->flags & CLIENT_BLOCKED)) return REDISMODULE_ERR; /* Client is not blocked anymore, can not abort it. */ /* Client is still blocked, remove it from any blocking state and release it. */ if (private_data) *private_data = promise->private_data; @@ -5958,11 +6053,11 @@ int VM_CallReplyPromiseAbort(ValkeyModuleCallReply *reply, void **private_data) promise->on_unblocked = NULL; unblockClient(promise->c, 0); moduleReleaseTempClient(promise->c); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Return the pointer and length of a string or error reply. */ -const char *VM_CallReplyStringPtr(ValkeyModuleCallReply *reply, size_t *len) { +const char *RM_CallReplyStringPtr(RedisModuleCallReply *reply, size_t *len) { size_t private_len; if (!len) len = &private_len; return callReplyGetString(reply, len); @@ -5970,45 +6065,45 @@ const char *VM_CallReplyStringPtr(ValkeyModuleCallReply *reply, size_t *len) { /* Return a new string object from a call reply of type string, error or * integer. Otherwise (wrong reply type) return NULL. */ -ValkeyModuleString *VM_CreateStringFromCallReply(ValkeyModuleCallReply *reply) { - ValkeyModuleCtx* ctx = callReplyGetPrivateData(reply); +RedisModuleString *RM_CreateStringFromCallReply(RedisModuleCallReply *reply) { + RedisModuleCtx* ctx = callReplyGetPrivateData(reply); size_t len; const char *str; switch(callReplyType(reply)) { - case VALKEYMODULE_REPLY_STRING: - case VALKEYMODULE_REPLY_ERROR: + case REDISMODULE_REPLY_STRING: + case REDISMODULE_REPLY_ERROR: str = callReplyGetString(reply, &len); - return VM_CreateString(ctx, str, len); - case VALKEYMODULE_REPLY_INTEGER: { + return RM_CreateString(ctx, str, len); + case REDISMODULE_REPLY_INTEGER: { char buf[64]; int len = ll2string(buf,sizeof(buf),callReplyGetLongLong(reply)); - return VM_CreateString(ctx ,buf,len); + return RM_CreateString(ctx ,buf,len); } default: return NULL; } } -/* Modifies the user that VM_Call will use (e.g. for ACL checks) */ -void VM_SetContextUser(ValkeyModuleCtx *ctx, const ValkeyModuleUser *user) { +/* Modifies the user that RM_Call will use (e.g. for ACL checks) */ +void RM_SetContextUser(RedisModuleCtx *ctx, const RedisModuleUser *user) { ctx->user = user; } /* Returns an array of robj pointers, by parsing the format specifier "fmt" as described for - * the VM_Call(), VM_Replicate() and other module APIs. Populates *argcp with the number of + * the RM_Call(), RM_Replicate() and other module APIs. Populates *argcp with the number of * items (which equals to the length of the allocated argv). * * The integer pointed by 'flags' is populated with flags according * to special modifiers in "fmt". * - * "!" -> VALKEYMODULE_ARGV_REPLICATE - * "A" -> VALKEYMODULE_ARGV_NO_AOF - * "R" -> VALKEYMODULE_ARGV_NO_REPLICAS - * "3" -> VALKEYMODULE_ARGV_RESP_3 - * "0" -> VALKEYMODULE_ARGV_RESP_AUTO - * "C" -> VALKEYMODULE_ARGV_RUN_AS_USER - * "M" -> VALKEYMODULE_ARGV_RESPECT_DENY_OOM - * "K" -> VALKEYMODULE_ARGV_ALLOW_BLOCK + * "!" -> REDISMODULE_ARGV_REPLICATE + * "A" -> REDISMODULE_ARGV_NO_AOF + * "R" -> REDISMODULE_ARGV_NO_REPLICAS + * "3" -> REDISMODULE_ARGV_RESP_3 + * "0" -> REDISMODULE_ARGV_RESP_AUTO + * "C" -> REDISMODULE_ARGV_RUN_AS_USER + * "M" -> REDISMODULE_ARGV_RESPECT_DENY_OOM + * "K" -> REDISMODULE_ARGV_ALLOW_BLOCK * * On error (format specifier error) NULL is returned and nothing is * allocated. On success the argument vector is returned. */ @@ -6062,29 +6157,29 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int argv[argc++] = v[i]; } } else if (*p == '!') { - if (flags) (*flags) |= VALKEYMODULE_ARGV_REPLICATE; + if (flags) (*flags) |= REDISMODULE_ARGV_REPLICATE; } else if (*p == 'A') { - if (flags) (*flags) |= VALKEYMODULE_ARGV_NO_AOF; + if (flags) (*flags) |= REDISMODULE_ARGV_NO_AOF; } else if (*p == 'R') { - if (flags) (*flags) |= VALKEYMODULE_ARGV_NO_REPLICAS; + if (flags) (*flags) |= REDISMODULE_ARGV_NO_REPLICAS; } else if (*p == '3') { - if (flags) (*flags) |= VALKEYMODULE_ARGV_RESP_3; + if (flags) (*flags) |= REDISMODULE_ARGV_RESP_3; } else if (*p == '0') { - if (flags) (*flags) |= VALKEYMODULE_ARGV_RESP_AUTO; + if (flags) (*flags) |= REDISMODULE_ARGV_RESP_AUTO; } else if (*p == 'C') { - if (flags) (*flags) |= VALKEYMODULE_ARGV_RUN_AS_USER; + if (flags) (*flags) |= REDISMODULE_ARGV_RUN_AS_USER; } else if (*p == 'S') { - if (flags) (*flags) |= VALKEYMODULE_ARGV_SCRIPT_MODE; + if (flags) (*flags) |= REDISMODULE_ARGV_SCRIPT_MODE; } else if (*p == 'W') { - if (flags) (*flags) |= VALKEYMODULE_ARGV_NO_WRITES; + if (flags) (*flags) |= REDISMODULE_ARGV_NO_WRITES; } else if (*p == 'M') { - if (flags) (*flags) |= VALKEYMODULE_ARGV_RESPECT_DENY_OOM; + if (flags) (*flags) |= REDISMODULE_ARGV_RESPECT_DENY_OOM; } else if (*p == 'E') { - if (flags) (*flags) |= VALKEYMODULE_ARGV_CALL_REPLIES_AS_ERRORS; + if (flags) (*flags) |= REDISMODULE_ARGV_CALL_REPLIES_AS_ERRORS; } else if (*p == 'D') { - if (flags) (*flags) |= (VALKEYMODULE_ARGV_DRY_RUN | VALKEYMODULE_ARGV_CALL_REPLIES_AS_ERRORS); + if (flags) (*flags) |= (REDISMODULE_ARGV_DRY_RUN | REDISMODULE_ARGV_CALL_REPLIES_AS_ERRORS); } else if (*p == 'K') { - if (flags) (*flags) |= VALKEYMODULE_ARGV_ALLOW_BLOCK; + if (flags) (*flags) |= REDISMODULE_ARGV_ALLOW_BLOCK; } else { goto fmterr; } @@ -6112,22 +6207,22 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int * argument that is the buffer's length. * * `c` -- The argument is a pointer to a plain C string (null-terminated). * * `l` -- The argument is a `long long` integer. - * * `s` -- The argument is a ValkeyModuleString. - * * `v` -- The argument(s) is a vector of ValkeyModuleString. + * * `s` -- The argument is a RedisModuleString. + * * `v` -- The argument(s) is a vector of RedisModuleString. * * `!` -- Sends the Redis command and its arguments to replicas and AOF. * * `A` -- Suppress AOF propagation, send only to replicas (requires `!`). * * `R` -- Suppress replicas propagation, send only to AOF (requires `!`). * * `3` -- Return a RESP3 reply. This will change the command reply. * e.g., HGETALL returns a map instead of a flat array. * * `0` -- Return the reply in auto mode, i.e. the reply format will be the - * same as the client attached to the given ValkeyModuleCtx. This will + * same as the client attached to the given RedisModuleCtx. This will * probably used when you want to pass the reply directly to the client. * * `C` -- Run a command as the user attached to the context. * User is either attached automatically via the client that directly - * issued the command and created the context or via VM_SetContextUser. + * issued the command and created the context or via RM_SetContextUser. * If the context is not directly created by an issued command (such as a - * background context and no user was set on it via VM_SetContextUser, - * VM_Call will fail. + * background context and no user was set on it via RM_SetContextUser, + * RM_Call will fail. * Checks if the command can be executed according to ACL rules and causes * the command to run as the determined user, so that any future user * dependent activity, such as ACL checks within scripts will proceed as @@ -6141,7 +6236,7 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int * or when the server is unable to persist to the disk. * * `W` -- Do not allow to run any write command (flagged with the `write` flag). * * `M` -- Do not allow `deny-oom` flagged commands when over the memory limit. - * * `E` -- Return error as ValkeyModuleCallReply. If there is an error before + * * `E` -- Return error as RedisModuleCallReply. If there is an error before * invoking the command, the error is returned using errno mechanism. * This flag allows to get the error also as an error CallReply with * relevant error message. @@ -6150,36 +6245,36 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int * return with a CallReply object denoting the error, as if it was called with * the 'E' code. * * 'K' -- Allow running blocking commands. If enabled and the command gets blocked, a - * special VALKEYMODULE_REPLY_PROMISE will be returned. This reply type + * special REDISMODULE_REPLY_PROMISE will be returned. This reply type * indicates that the command was blocked and the reply will be given asynchronously. * The module can use this reply object to set a handler which will be called when - * the command gets unblocked using ValkeyModule_CallReplyPromiseSetUnblockHandler. + * the command gets unblocked using RedisModule_CallReplyPromiseSetUnblockHandler. * The handler must be set immediately after the command invocation (without releasing * the Redis lock in between). If the handler is not set, the blocking command will * still continue its execution but the reply will be ignored (fire and forget), * notice that this is dangerous in case of role change, as explained below. - * The module can use ValkeyModule_CallReplyPromiseAbort to abort the command invocation - * if it was not yet finished (see ValkeyModule_CallReplyPromiseAbort documentation for more + * The module can use RedisModule_CallReplyPromiseAbort to abort the command invocation + * if it was not yet finished (see RedisModule_CallReplyPromiseAbort documentation for more * details). It is also the module's responsibility to abort the execution on role change, either by using * server event (to get notified when the instance becomes a replica) or relying on the disconnect * callback of the original client. Failing to do so can result in a write operation on a replica. * Unlike other call replies, promise call reply **must** be freed while the Redis GIL is locked. * Notice that on unblocking, the only promise is that the unblock handler will be called, - * If the blocking VM_Call caused the module to also block some real client (using VM_BlockClient), + * If the blocking RM_Call caused the module to also block some real client (using RM_BlockClient), * it is the module responsibility to unblock this client on the unblock handler. * On the unblock handler it is only allowed to perform the following: - * * Calling additional Redis commands using VM_Call - * * Open keys using VM_OpenKey + * * Calling additional Redis commands using RM_Call + * * Open keys using RM_OpenKey * * Replicate data to the replica or AOF * * Specifically, it is not allowed to call any Redis module API which are client related such as: - * * VM_Reply* API's - * * VM_BlockClient - * * VM_GetCurrentUserName + * * RM_Reply* API's + * * RM_BlockClient + * * RM_GetCurrentUserName * * * **...**: The actual arguments to the Redis command. * - * On success a ValkeyModuleCallReply object is returned, otherwise + * On success a RedisModuleCallReply object is returned, otherwise * NULL is returned and errno is set to the following values: * * * EBADF: wrong format specifier. @@ -6196,53 +6291,53 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int * * Example code fragment: * - * reply = ValkeyModule_Call(ctx,"INCRBY","sc",argv[1],"10"); - * if (ValkeyModule_CallReplyType(reply) == VALKEYMODULE_REPLY_INTEGER) { - * long long myval = ValkeyModule_CallReplyInteger(reply); + * reply = RedisModule_Call(ctx,"INCRBY","sc",argv[1],"10"); + * if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_INTEGER) { + * long long myval = RedisModule_CallReplyInteger(reply); * // Do something with myval. * } * * This API is documented here: https://redis.io/topics/modules-intro */ -ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const char *fmt, ...) { +RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) { client *c = NULL; robj **argv = NULL; int argc = 0, flags = 0; va_list ap; - ValkeyModuleCallReply *reply = NULL; + RedisModuleCallReply *reply = NULL; int replicate = 0; /* Replicate this command? */ - int error_as_call_replies = 0; /* return errors as ValkeyModuleCallReply object */ + int error_as_call_replies = 0; /* return errors as RedisModuleCallReply object */ uint64_t cmd_flags; /* Handle arguments. */ va_start(ap, fmt); argv = moduleCreateArgvFromUserFormat(cmdname,fmt,&argc,&flags,ap); - replicate = flags & VALKEYMODULE_ARGV_REPLICATE; - error_as_call_replies = flags & VALKEYMODULE_ARGV_CALL_REPLIES_AS_ERRORS; + replicate = flags & REDISMODULE_ARGV_REPLICATE; + error_as_call_replies = flags & REDISMODULE_ARGV_CALL_REPLIES_AS_ERRORS; va_end(ap); c = moduleAllocTempClient(); - if (!(flags & VALKEYMODULE_ARGV_ALLOW_BLOCK)) { + if (!(flags & REDISMODULE_ARGV_ALLOW_BLOCK)) { /* We do not want to allow block, the module do not expect it */ c->flags |= CLIENT_DENY_BLOCKING; } c->db = ctx->client->db; c->argv = argv; - /* We have to assign argv_len, which is equal to argc in that case (VM_Call) + /* We have to assign argv_len, which is equal to argc in that case (RM_Call) * because we may be calling a command that uses rewriteClientCommandArgument */ c->argc = c->argv_len = argc; c->resp = 2; - if (flags & VALKEYMODULE_ARGV_RESP_3) { + if (flags & REDISMODULE_ARGV_RESP_3) { c->resp = 3; - } else if (flags & VALKEYMODULE_ARGV_RESP_AUTO) { + } else if (flags & REDISMODULE_ARGV_RESP_AUTO) { /* Auto mode means to take the same protocol as the ctx client. */ c->resp = ctx->client->resp; } if (ctx->module) ctx->module->in_call++; user *user = NULL; - if (flags & VALKEYMODULE_ARGV_RUN_AS_USER) { + if (flags & REDISMODULE_ARGV_RUN_AS_USER) { user = ctx->user ? ctx->user->user : ctx->client->user; if (!user) { errno = ENOTSUP; @@ -6289,7 +6384,7 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const cmd_flags = getCommandFlags(c); - if (flags & VALKEYMODULE_ARGV_SCRIPT_MODE) { + if (flags & REDISMODULE_ARGV_SCRIPT_MODE) { /* Basically on script mode we want to only allow commands that can * be executed on scripts (CMD_NOSCRIPT is not set on the command flags) */ if (cmd_flags & CMD_NOSCRIPT) { @@ -6302,10 +6397,10 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const } } - if (flags & VALKEYMODULE_ARGV_RESPECT_DENY_OOM && server.maxmemory) { + if (flags & REDISMODULE_ARGV_RESPECT_DENY_OOM && server.maxmemory) { if (cmd_flags & CMD_DENYOOM) { int oom_state; - if (ctx->flags & VALKEYMODULE_CTX_THREAD_SAFE) { + if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) { /* On background thread we can not count on server.pre_command_oom_state. * Because it is only set on the main thread, in such case we will check * the actual memory usage. */ @@ -6323,11 +6418,11 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const } } } else { - /* if we aren't OOM checking in VM_Call, we want further executions from this client to also not fail on OOM */ + /* if we aren't OOM checking in RM_Call, we want further executions from this client to also not fail on OOM */ c->flags |= CLIENT_ALLOW_OOM; } - if (flags & VALKEYMODULE_ARGV_NO_WRITES) { + if (flags & REDISMODULE_ARGV_NO_WRITES) { if (cmd_flags & CMD_WRITE) { errno = ENOSPC; if (error_as_call_replies) { @@ -6340,7 +6435,7 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const } /* Script mode tests */ - if (flags & VALKEYMODULE_ARGV_SCRIPT_MODE) { + if (flags & REDISMODULE_ARGV_SCRIPT_MODE) { if (cmd_flags & CMD_WRITE) { /* on script mode, if a command is a write command, * We will not run it if we encounter disk error @@ -6391,10 +6486,10 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const /* Check if the user can run this command according to the current * ACLs. * - * If VM_SetContextUser has set a user, that user is used, otherwise + * If RM_SetContextUser has set a user, that user is used, otherwise * use the attached client's user. If there is no attached client user and no manually * set user, an error will be returned */ - if (flags & VALKEYMODULE_ARGV_RUN_AS_USER) { + if (flags & REDISMODULE_ARGV_RUN_AS_USER) { int acl_errpos; int acl_retval; @@ -6423,7 +6518,7 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const c->flags &= ~(CLIENT_READONLY|CLIENT_ASKING); c->flags |= ctx->client->flags & (CLIENT_READONLY|CLIENT_ASKING); if (getNodeByQuery(c,c->cmd,c->argv,c->argc,NULL,&error_code) != - server.cluster->myself) + getMyClusterNode()) { sds msg = NULL; if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) { @@ -6449,15 +6544,15 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const } } - if (flags & VALKEYMODULE_ARGV_DRY_RUN) { + if (flags & REDISMODULE_ARGV_DRY_RUN) { goto cleanup; } /* We need to use a global replication_allowed flag in order to prevent - * replication of nested VM_Calls. Example: - * 1. module1.foo does VM_Call of module2.bar without replication (i.e. no '!') - * 2. module2.bar internally calls VM_Call of INCR with '!' - * 3. at the end of module1.foo we call VM_ReplicateVerbatim + * replication of nested RM_Calls. Example: + * 1. module1.foo does RM_Call of module2.bar without replication (i.e. no '!') + * 2. module2.bar internally calls RM_Call of INCR with '!' + * 3. at the end of module1.foo we call RM_ReplicateVerbatim * We want the replica/AOF to see only module1.foo and not the INCR from module2.bar */ int prev_replication_allowed = server.replication_allowed; server.replication_allowed = replicate && server.replication_allowed; @@ -6465,19 +6560,19 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const /* Run the command */ int call_flags = CMD_CALL_FROM_MODULE; if (replicate) { - if (!(flags & VALKEYMODULE_ARGV_NO_AOF)) + if (!(flags & REDISMODULE_ARGV_NO_AOF)) call_flags |= CMD_CALL_PROPAGATE_AOF; - if (!(flags & VALKEYMODULE_ARGV_NO_REPLICAS)) + if (!(flags & REDISMODULE_ARGV_NO_REPLICAS)) call_flags |= CMD_CALL_PROPAGATE_REPL; } call(c,call_flags); server.replication_allowed = prev_replication_allowed; if (c->flags & CLIENT_BLOCKED) { - serverAssert(flags & VALKEYMODULE_ARGV_ALLOW_BLOCK); + serverAssert(flags & REDISMODULE_ARGV_ALLOW_BLOCK); serverAssert(ctx->module); - ValkeyModuleAsyncRMCallPromise *promise = zmalloc(sizeof(ValkeyModuleAsyncRMCallPromise)); - *promise = (ValkeyModuleAsyncRMCallPromise) { + RedisModuleAsyncRMCallPromise *promise = zmalloc(sizeof(RedisModuleAsyncRMCallPromise)); + *promise = (RedisModuleAsyncRMCallPromise) { /* We start with ref_count value of 2 because this object is held * by the promise CallReply and the fake client that was used to execute the command. */ .ref_count = 2, @@ -6485,7 +6580,7 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const .on_unblocked = NULL, .private_data = NULL, .c = c, - .ctx = (ctx->flags & VALKEYMODULE_CTX_AUTO_MEMORY) ? ctx : NULL, + .ctx = (ctx->flags & REDISMODULE_CTX_AUTO_MEMORY) ? ctx : NULL, }; reply = callReplyCreatePromise(promise); c->bstate.async_rm_call_handle = promise; @@ -6499,11 +6594,11 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const } c = NULL; /* Make sure not to free the client */ } else { - reply = moduleParseReply(c, (ctx->flags & VALKEYMODULE_CTX_AUTO_MEMORY) ? ctx : NULL); + reply = moduleParseReply(c, (ctx->flags & REDISMODULE_CTX_AUTO_MEMORY) ? ctx : NULL); } cleanup: - if (reply) autoMemoryAdd(ctx,VALKEYMODULE_AM_REPLY,reply); + if (reply) autoMemoryAdd(ctx,REDISMODULE_AM_REPLY,reply); if (ctx->module) ctx->module->in_call--; if (c) moduleReleaseTempClient(c); return reply; @@ -6511,7 +6606,7 @@ ValkeyModuleCallReply *VM_Call(ValkeyModuleCtx *ctx, const char *cmdname, const /* Return a pointer, and a length, to the protocol returned by the command * that returned the reply object. */ -const char *VM_CallReplyProto(ValkeyModuleCallReply *reply, size_t *len) { +const char *RM_CallReplyProto(RedisModuleCallReply *reply, size_t *len) { return callReplyGetProto(reply, len); } @@ -6579,7 +6674,7 @@ moduleType *moduleTypeLookupModuleByNameInternal(const char *name, int ignore_ca dictEntry *de; while ((de = dictNext(di)) != NULL) { - struct ValkeyModule *module = dictGetVal(de); + struct RedisModule *module = dictGetVal(de); listIter li; listNode *ln; @@ -6629,7 +6724,7 @@ moduleType *moduleTypeLookupModuleByID(uint64_t id) { dictEntry *de; while ((de = dictNext(di)) != NULL && mt == NULL) { - struct ValkeyModule *module = dictGetVal(de); + struct RedisModule *module = dictGetVal(de); listIter li; listNode *ln; @@ -6678,9 +6773,9 @@ const char *moduleTypeModuleName(moduleType *mt) { /* Return the module name from a module command */ const char *moduleNameFromCommand(struct redisCommand *cmd) { - serverAssert(cmd->proc == ValkeyModuleCommandDispatcher); + serverAssert(cmd->proc == RedisModuleCommandDispatcher); - ValkeyModuleCommand *cp = cmd->module_cmd; + RedisModuleCommand *cp = cmd->module_cmd; return cp->module->name; } @@ -6696,7 +6791,7 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj } void *newval = NULL; if (mt->copy2 != NULL) { - ValkeyModuleKeyOptCtx ctx = {fromkey, tokey, c->db->id, todb}; + RedisModuleKeyOptCtx ctx = {fromkey, tokey, c->db->id, todb}; newval = mt->copy2(&ctx, mv->value); } else { newval = mt->copy(fromkey, tokey, mv->value); @@ -6731,12 +6826,12 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj * callback is able to check the encver value and act accordingly. * The encver must be a positive value between 0 and 1023. * - * * **typemethods_ptr** is a pointer to a ValkeyModuleTypeMethods structure + * * **typemethods_ptr** is a pointer to a RedisModuleTypeMethods structure * that should be populated with the methods callbacks and structure * version, like in the following example: * - * ValkeyModuleTypeMethods tm = { - * .version = VALKEYMODULE_TYPE_METHOD_VERSION, + * RedisModuleTypeMethods tm = { + * .version = REDISMODULE_TYPE_METHOD_VERSION, * .rdb_load = myType_RDBLoadCallBack, * .rdb_save = myType_RDBSaveCallBack, * .aof_rewrite = myType_AOFRewriteCallBack, @@ -6765,9 +6860,9 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj * * **digest**: A callback function pointer that is used for `DEBUG DIGEST`. * * **free**: A callback function pointer that can free a type value. * * **aux_save**: A callback function pointer that saves out of keyspace data to RDB files. - * 'when' argument is either VALKEYMODULE_AUX_BEFORE_RDB or VALKEYMODULE_AUX_AFTER_RDB. + * 'when' argument is either REDISMODULE_AUX_BEFORE_RDB or REDISMODULE_AUX_AFTER_RDB. * * **aux_load**: A callback function pointer that loads out of keyspace data from RDB files. - * Similar to aux_save, returns VALKEYMODULE_OK on success, and ERR otherwise. + * 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 @@ -6775,7 +6870,7 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj * * **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 - * ValkeyModuleEvent_FlushDB to hook into that. + * 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. * In addition, hints about the names of the source and destination keys is provided. @@ -6784,28 +6879,28 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj * 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 VM_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 - * iterating as long as VM_DefragShouldStop() returns a zero value, and return a + * iterating as long as RM_DefragShouldStop() returns a zero value, and return a * zero value if finished or non-zero value if more work is left to be done. If more work - * needs to be done, VM_DefragCursorSet() and VM_DefragCursorGet() can be used to track + * needs to be done, RM_DefragCursorSet() and RM_DefragCursorGet() can be used to track * this work across different calls. * Normally, the defrag mechanism invokes the callback without a time limit, so - * VM_DefragShouldStop() always returns zero. The "late defrag" mechanism which has + * RM_DefragShouldStop() always returns zero. The "late defrag" mechanism which has * a time limit and provides cursor support is used only for keys that are determined * to have significant internal complexity. To determine this, the defrag mechanism * uses the free_effort callback and the 'active-defrag-max-scan-fields' config directive. * NOTE: The value is passed as a `void**` and the function is expected to update the * pointer if the top-level value pointer is defragmented and consequently changes. * - * * **mem_usage2**: Similar to `mem_usage`, but provides the `ValkeyModuleKeyOptCtx` parameter + * * **mem_usage2**: Similar to `mem_usage`, but provides the `RedisModuleKeyOptCtx` parameter * so that meta information such as key name and db id can be obtained, and * the `sample_size` for size estimation (see MEMORY USAGE command). - * * **free_effort2**: Similar to `free_effort`, but provides the `ValkeyModuleKeyOptCtx` parameter + * * **free_effort2**: Similar to `free_effort`, but provides the `RedisModuleKeyOptCtx` parameter * so that meta information such as key name and db id can be obtained. - * * **unlink2**: Similar to `unlink`, but provides the `ValkeyModuleKeyOptCtx` parameter + * * **unlink2**: Similar to `unlink`, but provides the `RedisModuleKeyOptCtx` parameter * so that meta information such as key name and db id can be obtained. - * * **copy2**: Similar to `copy`, but provides the `ValkeyModuleKeyOptCtx` parameter + * * **copy2**: Similar to `copy`, but provides the `RedisModuleKeyOptCtx` parameter * so that meta information such as key names and db ids can be obtained. * * **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 @@ -6814,23 +6909,23 @@ robj *moduleTypeDupOrReply(client *c, robj *fromkey, robj *tokey, int todb, robj * Note: the module name "AAAAAAAAA" is reserved and produces an error, it * happens to be pretty lame as well. * - * If ValkeyModule_CreateDataType() is called outside of ValkeyModule_OnLoad() function, + * If RedisModule_CreateDataType() is called outside of RedisModule_OnLoad() function, * there is already a module registering a type with the same name, * or if the module name or encver is invalid, NULL is returned. * Otherwise the new type is registered into Redis, and a reference of - * type ValkeyModuleType is returned: the caller of the function should store + * type RedisModuleType is returned: the caller of the function should store * this reference into a global variable to make future use of it in the * modules type API, since a single module may register multiple types. * Example code fragment: * - * static ValkeyModuleType *BalancedTreeType; + * static RedisModuleType *BalancedTreeType; * - * int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx) { + * int RedisModule_OnLoad(RedisModuleCtx *ctx) { * // some code here ... - * BalancedTreeType = VM_CreateDataType(...); + * BalancedTreeType = RM_CreateDataType(...); * } */ -moduleType *VM_CreateDataType(ValkeyModuleCtx *ctx, const char *name, int encver, void *typemethods_ptr) { +moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver, void *typemethods_ptr) { if (!ctx->module->onload) return NULL; uint64_t id = moduleTypeEncodeId(name,encver); @@ -6906,41 +7001,41 @@ moduleType *VM_CreateDataType(ValkeyModuleCtx *ctx, const char *name, int encver /* If the key is open for writing, set the specified module type object * as the value of the key, deleting the old value if any. - * On success VALKEYMODULE_OK is returned. If the key is not open for - * writing or there is an active iterator, VALKEYMODULE_ERR is returned. */ -int VM_ModuleTypeSetValue(ValkeyModuleKey *key, moduleType *mt, void *value) { - if (!(key->mode & VALKEYMODULE_WRITE) || key->iter) return VALKEYMODULE_ERR; - VM_DeleteKey(key); + * On success REDISMODULE_OK is returned. If the key is not open for + * writing or there is an active iterator, REDISMODULE_ERR is returned. */ +int RM_ModuleTypeSetValue(RedisModuleKey *key, moduleType *mt, void *value) { + if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR; + RM_DeleteKey(key); robj *o = createModuleObject(mt,value); setKey(key->ctx->client,key->db,key->key,o,SETKEY_NO_SIGNAL); decrRefCount(o); key->value = o; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* Assuming ValkeyModule_KeyType() returned VALKEYMODULE_KEYTYPE_MODULE on +/* Assuming RedisModule_KeyType() returned REDISMODULE_KEYTYPE_MODULE on * the key, returns the module type pointer of the value stored at key. * * If the key is NULL, is not associated with a module type, or is empty, * then NULL is returned instead. */ -moduleType *VM_ModuleTypeGetType(ValkeyModuleKey *key) { +moduleType *RM_ModuleTypeGetType(RedisModuleKey *key) { if (key == NULL || key->value == NULL || - VM_KeyType(key) != VALKEYMODULE_KEYTYPE_MODULE) return NULL; + RM_KeyType(key) != REDISMODULE_KEYTYPE_MODULE) return NULL; moduleValue *mv = key->value->ptr; return mv->type; } -/* Assuming ValkeyModule_KeyType() returned VALKEYMODULE_KEYTYPE_MODULE on +/* Assuming RedisModule_KeyType() returned REDISMODULE_KEYTYPE_MODULE on * the key, returns the module type low-level value stored at key, as - * it was set by the user via ValkeyModule_ModuleTypeSetValue(). + * it was set by the user via RedisModule_ModuleTypeSetValue(). * * If the key is NULL, is not associated with a module type, or is empty, * then NULL is returned instead. */ -void *VM_ModuleTypeGetValue(ValkeyModuleKey *key) { +void *RM_ModuleTypeGetValue(RedisModuleKey *key) { if (key == NULL || key->value == NULL || - VM_KeyType(key) != VALKEYMODULE_KEYTYPE_MODULE) return NULL; + RM_KeyType(key) != REDISMODULE_KEYTYPE_MODULE) return NULL; moduleValue *mv = key->value->ptr; return mv->value; } @@ -6952,8 +7047,8 @@ void *VM_ModuleTypeGetValue(ValkeyModuleKey *key) { /* Called when there is a load error in the context of a module. On some * modules this cannot be recovered, but if the module declared capability * to handle errors, we'll raise a flag rather than exiting. */ -void moduleRDBLoadError(ValkeyModuleIO *io) { - if (io->type->module->options & VALKEYMODULE_OPTIONS_HANDLE_IO_ERRORS) { +void moduleRDBLoadError(RedisModuleIO *io) { + if (io->type->module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS) { io->error = 1; return; } @@ -6969,16 +7064,16 @@ void moduleRDBLoadError(ValkeyModuleIO *io) { } /* Returns 0 if there's at least one registered data type that did not declare - * VALKEYMODULE_OPTIONS_HANDLE_IO_ERRORS, in which case diskless loading should + * REDISMODULE_OPTIONS_HANDLE_IO_ERRORS, in which case diskless loading should * be avoided since it could cause data loss. */ int moduleAllDatatypesHandleErrors(void) { dictIterator *di = dictGetIterator(modules); dictEntry *de; while ((de = dictNext(di)) != NULL) { - struct ValkeyModule *module = dictGetVal(de); + struct RedisModule *module = dictGetVal(de); if (listLength(module->types) && - !(module->options & VALKEYMODULE_OPTIONS_HANDLE_IO_ERRORS)) + !(module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS)) { dictReleaseIterator(di); return 0; @@ -6988,7 +7083,7 @@ int moduleAllDatatypesHandleErrors(void) { return 1; } -/* Returns 0 if module did not declare VALKEYMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD, in which case +/* Returns 0 if module did not declare REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD, in which case * diskless async loading should be avoided because module doesn't know there can be traffic during * database full resynchronization. */ int moduleAllModulesHandleReplAsyncLoad(void) { @@ -6996,8 +7091,8 @@ int moduleAllModulesHandleReplAsyncLoad(void) { dictEntry *de; while ((de = dictNext(di)) != NULL) { - struct ValkeyModule *module = dictGetVal(de); - if (!(module->options & VALKEYMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD)) { + struct RedisModule *module = dictGetVal(de); + if (!(module->options & REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD)) { dictReleaseIterator(di); return 0; } @@ -7007,13 +7102,13 @@ int moduleAllModulesHandleReplAsyncLoad(void) { } /* Returns true if any previous IO API failed. - * for `Load*` APIs the VALKEYMODULE_OPTIONS_HANDLE_IO_ERRORS flag must be set with - * ValkeyModule_SetModuleOptions first. */ -int VM_IsIOError(ValkeyModuleIO *io) { + * for `Load*` APIs the REDISMODULE_OPTIONS_HANDLE_IO_ERRORS flag must be set with + * RedisModule_SetModuleOptions first. */ +int RM_IsIOError(RedisModuleIO *io) { return io->error; } -static int flushValkeyModuleIOBuffer(ValkeyModuleIO *io) { +static int flushRedisModuleIOBuffer(RedisModuleIO *io) { if (!io->pre_flush_buffer) return 0; /* We have data that must be flushed before saving the current data. @@ -7029,9 +7124,9 @@ static int flushValkeyModuleIOBuffer(ValkeyModuleIO *io) { /* Save an unsigned 64 bit value into the RDB file. This function should only * be called in the context of the rdb_save method of modules implementing new * data types. */ -void VM_SaveUnsigned(ValkeyModuleIO *io, uint64_t value) { +void RM_SaveUnsigned(RedisModuleIO *io, uint64_t value) { if (io->error) return; - if (flushValkeyModuleIOBuffer(io) == -1) goto saveerr; + if (flushRedisModuleIOBuffer(io) == -1) goto saveerr; /* Save opcode. */ int retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_UINT); if (retval == -1) goto saveerr; @@ -7049,7 +7144,7 @@ void VM_SaveUnsigned(ValkeyModuleIO *io, uint64_t value) { /* Load an unsigned 64 bit value from the RDB file. This function should only * be called in the context of the `rdb_load` method of modules implementing * new data types. */ -uint64_t VM_LoadUnsigned(ValkeyModuleIO *io) { +uint64_t RM_LoadUnsigned(RedisModuleIO *io) { if (io->error) return 0; uint64_t opcode = rdbLoadLen(io->rio,NULL); if (opcode != RDB_MODULE_OPCODE_UINT) goto loaderr; @@ -7063,29 +7158,29 @@ uint64_t VM_LoadUnsigned(ValkeyModuleIO *io) { return 0; } -/* Like ValkeyModule_SaveUnsigned() but for signed 64 bit values. */ -void VM_SaveSigned(ValkeyModuleIO *io, int64_t value) { +/* Like RedisModule_SaveUnsigned() but for signed 64 bit values. */ +void RM_SaveSigned(RedisModuleIO *io, int64_t value) { union {uint64_t u; int64_t i;} conv; conv.i = value; - VM_SaveUnsigned(io,conv.u); + RM_SaveUnsigned(io,conv.u); } -/* Like ValkeyModule_LoadUnsigned() but for signed 64 bit values. */ -int64_t VM_LoadSigned(ValkeyModuleIO *io) { +/* Like RedisModule_LoadUnsigned() but for signed 64 bit values. */ +int64_t RM_LoadSigned(RedisModuleIO *io) { union {uint64_t u; int64_t i;} conv; - conv.u = VM_LoadUnsigned(io); + conv.u = RM_LoadUnsigned(io); return conv.i; } /* In the context of the rdb_save method of a module type, saves a - * string into the RDB file taking as input a ValkeyModuleString. + * string into the RDB file taking as input a RedisModuleString. * - * The string can be later loaded with ValkeyModule_LoadString() or + * The string can be later loaded with RedisModule_LoadString() or * other Load family functions expecting a serialized string inside * the RDB file. */ -void VM_SaveString(ValkeyModuleIO *io, ValkeyModuleString *s) { +void RM_SaveString(RedisModuleIO *io, RedisModuleString *s) { if (io->error) return; - if (flushValkeyModuleIOBuffer(io) == -1) goto saveerr; + if (flushRedisModuleIOBuffer(io) == -1) goto saveerr; /* Save opcode. */ ssize_t retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_STRING); if (retval == -1) goto saveerr; @@ -7100,11 +7195,11 @@ void VM_SaveString(ValkeyModuleIO *io, ValkeyModuleString *s) { io->error = 1; } -/* Like ValkeyModule_SaveString() but takes a raw C pointer and length +/* Like RedisModule_SaveString() but takes a raw C pointer and length * as input. */ -void VM_SaveStringBuffer(ValkeyModuleIO *io, const char *str, size_t len) { +void RM_SaveStringBuffer(RedisModuleIO *io, const char *str, size_t len) { if (io->error) return; - if (flushValkeyModuleIOBuffer(io) == -1) goto saveerr; + if (flushRedisModuleIOBuffer(io) == -1) goto saveerr; /* Save opcode. */ ssize_t retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_STRING); if (retval == -1) goto saveerr; @@ -7119,8 +7214,8 @@ void VM_SaveStringBuffer(ValkeyModuleIO *io, const char *str, size_t len) { io->error = 1; } -/* Implements VM_LoadString() and VM_LoadStringBuffer() */ -void *moduleLoadString(ValkeyModuleIO *io, int plain, size_t *lenptr) { +/* Implements RM_LoadString() and RM_LoadStringBuffer() */ +void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) { if (io->error) return NULL; uint64_t opcode = rdbLoadLen(io->rio,NULL); if (opcode != RDB_MODULE_OPCODE_STRING) goto loaderr; @@ -7135,35 +7230,35 @@ void *moduleLoadString(ValkeyModuleIO *io, int plain, size_t *lenptr) { } /* In the context of the rdb_load method of a module data type, loads a string - * from the RDB file, that was previously saved with ValkeyModule_SaveString() + * from the RDB file, that was previously saved with RedisModule_SaveString() * functions family. * - * The returned string is a newly allocated ValkeyModuleString object, and - * the user should at some point free it with a call to ValkeyModule_FreeString(). + * The returned string is a newly allocated RedisModuleString object, and + * the user should at some point free it with a call to RedisModule_FreeString(). * - * If the data structure does not store strings as ValkeyModuleString objects, - * the similar function ValkeyModule_LoadStringBuffer() could be used instead. */ -ValkeyModuleString *VM_LoadString(ValkeyModuleIO *io) { + * If the data structure does not store strings as RedisModuleString objects, + * the similar function RedisModule_LoadStringBuffer() could be used instead. */ +RedisModuleString *RM_LoadString(RedisModuleIO *io) { return moduleLoadString(io,0,NULL); } -/* Like ValkeyModule_LoadString() but returns a heap allocated string that - * was allocated with ValkeyModule_Alloc(), and can be resized or freed with - * ValkeyModule_Realloc() or ValkeyModule_Free(). +/* Like RedisModule_LoadString() but returns a heap allocated string that + * was allocated with RedisModule_Alloc(), and can be resized or freed with + * RedisModule_Realloc() or RedisModule_Free(). * * The size of the string is stored at '*lenptr' if not NULL. * The returned string is not automatically NULL terminated, it is loaded * exactly as it was stored inside the RDB file. */ -char *VM_LoadStringBuffer(ValkeyModuleIO *io, size_t *lenptr) { +char *RM_LoadStringBuffer(RedisModuleIO *io, size_t *lenptr) { return moduleLoadString(io,1,lenptr); } /* In the context of the rdb_save method of a module data type, saves a double * value to the RDB file. The double can be a valid number, a NaN or infinity. - * It is possible to load back the value with ValkeyModule_LoadDouble(). */ -void VM_SaveDouble(ValkeyModuleIO *io, double value) { + * It is possible to load back the value with RedisModule_LoadDouble(). */ +void RM_SaveDouble(RedisModuleIO *io, double value) { if (io->error) return; - if (flushValkeyModuleIOBuffer(io) == -1) goto saveerr; + if (flushRedisModuleIOBuffer(io) == -1) goto saveerr; /* Save opcode. */ int retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_DOUBLE); if (retval == -1) goto saveerr; @@ -7179,8 +7274,8 @@ void VM_SaveDouble(ValkeyModuleIO *io, double value) { } /* In the context of the rdb_save method of a module data type, loads back the - * double value saved by ValkeyModule_SaveDouble(). */ -double VM_LoadDouble(ValkeyModuleIO *io) { + * double value saved by RedisModule_SaveDouble(). */ +double RM_LoadDouble(RedisModuleIO *io) { if (io->error) return 0; uint64_t opcode = rdbLoadLen(io->rio,NULL); if (opcode != RDB_MODULE_OPCODE_DOUBLE) goto loaderr; @@ -7196,10 +7291,10 @@ double VM_LoadDouble(ValkeyModuleIO *io) { /* In the context of the rdb_save method of a module data type, saves a float * value to the RDB file. The float can be a valid number, a NaN or infinity. - * It is possible to load back the value with ValkeyModule_LoadFloat(). */ -void VM_SaveFloat(ValkeyModuleIO *io, float value) { + * It is possible to load back the value with RedisModule_LoadFloat(). */ +void RM_SaveFloat(RedisModuleIO *io, float value) { if (io->error) return; - if (flushValkeyModuleIOBuffer(io) == -1) goto saveerr; + if (flushRedisModuleIOBuffer(io) == -1) goto saveerr; /* Save opcode. */ int retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_FLOAT); if (retval == -1) goto saveerr; @@ -7215,8 +7310,8 @@ void VM_SaveFloat(ValkeyModuleIO *io, float value) { } /* In the context of the rdb_save method of a module data type, loads back the - * float value saved by ValkeyModule_SaveFloat(). */ -float VM_LoadFloat(ValkeyModuleIO *io) { + * float value saved by RedisModule_SaveFloat(). */ +float RM_LoadFloat(RedisModuleIO *io) { if (io->error) return 0; uint64_t opcode = rdbLoadLen(io->rio,NULL); if (opcode != RDB_MODULE_OPCODE_FLOAT) goto loaderr; @@ -7232,26 +7327,26 @@ float VM_LoadFloat(ValkeyModuleIO *io) { /* In the context of the rdb_save method of a module data type, saves a long double * value to the RDB file. The double can be a valid number, a NaN or infinity. - * It is possible to load back the value with ValkeyModule_LoadLongDouble(). */ -void VM_SaveLongDouble(ValkeyModuleIO *io, long double value) { + * It is possible to load back the value with RedisModule_LoadLongDouble(). */ +void RM_SaveLongDouble(RedisModuleIO *io, long double value) { if (io->error) return; char buf[MAX_LONG_DOUBLE_CHARS]; /* Long double has different number of bits in different platforms, so we * save it as a string type. */ size_t len = ld2string(buf,sizeof(buf),value,LD_STR_HEX); - VM_SaveStringBuffer(io,buf,len); + RM_SaveStringBuffer(io,buf,len); } /* In the context of the rdb_save method of a module data type, loads back the - * long double value saved by ValkeyModule_SaveLongDouble(). */ -long double VM_LoadLongDouble(ValkeyModuleIO *io) { + * long double value saved by RedisModule_SaveLongDouble(). */ +long double RM_LoadLongDouble(RedisModuleIO *io) { if (io->error) return 0; long double value; size_t len; - char* str = VM_LoadStringBuffer(io,&len); + char* str = RM_LoadStringBuffer(io,&len); if (!str) return 0; string2ld(str,len,&value); - VM_Free(str); + RM_Free(str); return value; } @@ -7263,7 +7358,7 @@ ssize_t rdbSaveModulesAux(rio *rdb, int when) { dictEntry *de; while ((de = dictNext(di)) != NULL) { - struct ValkeyModule *module = dictGetVal(de); + struct RedisModule *module = dictGetVal(de); listIter li; listNode *ln; @@ -7292,7 +7387,7 @@ ssize_t rdbSaveModulesAux(rio *rdb, int when) { /* Add a new element to the digest. This function can be called multiple times * one element after the other, for all the elements that constitute a given * data structure. The function call must be followed by the call to - * `ValkeyModule_DigestEndSequence` eventually, when all the elements that are + * `RedisModule_DigestEndSequence` eventually, when all the elements that are * always in a given order are added. See the Redis Modules data types * documentation for more info. However this is a quick example that uses Redis * data types as an example. @@ -7327,20 +7422,20 @@ ssize_t rdbSaveModulesAux(rio *rdb, int when) { * EndSequence(); * */ -void VM_DigestAddStringBuffer(ValkeyModuleDigest *md, const char *ele, size_t len) { +void RM_DigestAddStringBuffer(RedisModuleDigest *md, const char *ele, size_t len) { mixDigest(md->o,ele,len); } -/* Like `ValkeyModule_DigestAddStringBuffer()` but takes a `long long` as input +/* Like `RedisModule_DigestAddStringBuffer()` but takes a `long long` as input * that gets converted into a string before adding it to the digest. */ -void VM_DigestAddLongLong(ValkeyModuleDigest *md, long long ll) { +void RM_DigestAddLongLong(RedisModuleDigest *md, long long ll) { char buf[LONG_STR_SIZE]; size_t len = ll2string(buf,sizeof(buf),ll); mixDigest(md->o,buf,len); } -/* See the documentation for `ValkeyModule_DigestAddElement()`. */ -void VM_DigestEndSequence(ValkeyModuleDigest *md) { +/* See the documentation for `RedisModule_DigestAddElement()`. */ +void RM_DigestEndSequence(RedisModuleDigest *md) { xorDigest(md->x,md->o,sizeof(md->o)); memset(md->o,0,sizeof(md->o)); } @@ -7352,22 +7447,22 @@ void VM_DigestEndSequence(ValkeyModuleDigest *md) { * implement in order to allow a module to arbitrarily serialize/de-serialize * keys, similar to how the Redis 'DUMP' and 'RESTORE' commands are implemented. * - * Modules should generally use the VALKEYMODULE_OPTIONS_HANDLE_IO_ERRORS flag and + * Modules should generally use the REDISMODULE_OPTIONS_HANDLE_IO_ERRORS flag and * make sure the de-serialization code properly checks and handles IO errors * (freeing allocated buffers and returning a NULL). * * If this is NOT done, Redis will handle corrupted (or just truncated) serialized * data by producing an error message and terminating the process. */ -void *VM_LoadDataTypeFromStringEncver(const ValkeyModuleString *str, const moduleType *mt, int encver) { +void *RM_LoadDataTypeFromStringEncver(const RedisModuleString *str, const moduleType *mt, int encver) { rio payload; - ValkeyModuleIO io; + RedisModuleIO io; void *ret; rioInitWithBuffer(&payload, str->ptr); moduleInitIOContext(io,(moduleType *)mt,&payload,NULL,-1); - /* All VM_Save*() calls always write a version 2 compatible format, so we + /* All RM_Save*() calls always write a version 2 compatible format, so we * need to make sure we read the same. */ ret = mt->rdb_load(&io,encver); @@ -7378,23 +7473,23 @@ void *VM_LoadDataTypeFromStringEncver(const ValkeyModuleString *str, const modul return ret; } -/* Similar to VM_LoadDataTypeFromStringEncver, original version of the API, kept +/* Similar to RM_LoadDataTypeFromStringEncver, original version of the API, kept * for backward compatibility. */ -void *VM_LoadDataTypeFromString(const ValkeyModuleString *str, const moduleType *mt) { - return VM_LoadDataTypeFromStringEncver(str, mt, 0); +void *RM_LoadDataTypeFromString(const RedisModuleString *str, const moduleType *mt) { + return RM_LoadDataTypeFromStringEncver(str, mt, 0); } /* Encode a module data type 'mt' value 'data' into serialized form, and return it - * as a newly allocated ValkeyModuleString. + * as a newly allocated RedisModuleString. * * This call basically reuses the 'rdb_save' callback which module data types * implement in order to allow a module to arbitrarily serialize/de-serialize * keys, similar to how the Redis 'DUMP' and 'RESTORE' commands are implemented. */ -ValkeyModuleString *VM_SaveDataTypeToString(ValkeyModuleCtx *ctx, void *data, const moduleType *mt) { +RedisModuleString *RM_SaveDataTypeToString(RedisModuleCtx *ctx, void *data, const moduleType *mt) { rio payload; - ValkeyModuleIO io; + RedisModuleIO io; rioInitWithBuffer(&payload,sdsempty()); moduleInitIOContext(io,(moduleType *)mt,&payload,NULL,-1); @@ -7407,18 +7502,18 @@ ValkeyModuleString *VM_SaveDataTypeToString(ValkeyModuleCtx *ctx, void *data, co return NULL; } else { robj *str = createObject(OBJ_STRING,payload.io.buffer.ptr); - if (ctx != NULL) autoMemoryAdd(ctx,VALKEYMODULE_AM_STRING,str); + if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_STRING,str); return str; } } /* Returns the name of the key currently being processed. */ -const ValkeyModuleString *VM_GetKeyNameFromDigest(ValkeyModuleDigest *dig) { +const RedisModuleString *RM_GetKeyNameFromDigest(RedisModuleDigest *dig) { return dig->key; } /* Returns the database id of the key currently being processed. */ -int VM_GetDbIdFromDigest(ValkeyModuleDigest *dig) { +int RM_GetDbIdFromDigest(RedisModuleDigest *dig) { return dig->dbid; } /* -------------------------------------------------------------------------- @@ -7427,10 +7522,10 @@ int VM_GetDbIdFromDigest(ValkeyModuleDigest *dig) { /* Emits a command into the AOF during the AOF rewriting process. This function * is only called in the context of the aof_rewrite method of data types exported - * by a module. The command works exactly like ValkeyModule_Call() in the way + * by a module. The command works exactly like RedisModule_Call() in the way * the parameters are passed, but it does not return anything as the error * handling is performed by Redis itself. */ -void VM_EmitAOF(ValkeyModuleIO *io, const char *cmdname, const char *fmt, ...) { +void RM_EmitAOF(RedisModuleIO *io, const char *cmdname, const char *fmt, ...) { if (io->error) return; struct redisCommand *cmd; robj **argv = NULL; @@ -7455,7 +7550,7 @@ void VM_EmitAOF(ValkeyModuleIO *io, const char *cmdname, const char *fmt, ...) { if (argv == NULL) { serverLog(LL_WARNING, "Fatal: AOF method for module data type '%s' tried to " - "call ValkeyModule_EmitAOF() with wrong format specifiers '%s'", + "call RedisModule_EmitAOF() with wrong format specifiers '%s'", io->type->name, fmt); io->error = 1; errno = EINVAL; @@ -7480,34 +7575,34 @@ void VM_EmitAOF(ValkeyModuleIO *io, const char *cmdname, const char *fmt, ...) { * ## IO context handling * -------------------------------------------------------------------------- */ -ValkeyModuleCtx *VM_GetContextFromIO(ValkeyModuleIO *io) { +RedisModuleCtx *RM_GetContextFromIO(RedisModuleIO *io) { if (io->ctx) return io->ctx; /* Can't have more than one... */ - io->ctx = zmalloc(sizeof(ValkeyModuleCtx)); - moduleCreateContext(io->ctx, io->type->module, VALKEYMODULE_CTX_NONE); + io->ctx = zmalloc(sizeof(RedisModuleCtx)); + moduleCreateContext(io->ctx, io->type->module, REDISMODULE_CTX_NONE); return io->ctx; } /* Returns the name of the key currently being processed. * There is no guarantee that the key name is always available, so this may return NULL. */ -const ValkeyModuleString *VM_GetKeyNameFromIO(ValkeyModuleIO *io) { +const RedisModuleString *RM_GetKeyNameFromIO(RedisModuleIO *io) { return io->key; } -/* Returns a ValkeyModuleString with the name of the key from ValkeyModuleKey. */ -const ValkeyModuleString *VM_GetKeyNameFromModuleKey(ValkeyModuleKey *key) { +/* Returns a RedisModuleString with the name of the key from RedisModuleKey. */ +const RedisModuleString *RM_GetKeyNameFromModuleKey(RedisModuleKey *key) { return key ? key->key : NULL; } -/* Returns a database id of the key from ValkeyModuleKey. */ -int VM_GetDbIdFromModuleKey(ValkeyModuleKey *key) { +/* Returns a database id of the key from RedisModuleKey. */ +int RM_GetDbIdFromModuleKey(RedisModuleKey *key) { return key ? key->db->id : -1; } /* Returns the database id of the key currently being processed. * There is no guarantee that this info is always available, so this may return -1. */ -int VM_GetDbIdFromIO(ValkeyModuleIO *io) { +int RM_GetDbIdFromIO(RedisModuleIO *io) { return io->dbid; } @@ -7517,11 +7612,11 @@ int VM_GetDbIdFromIO(ValkeyModuleIO *io) { /* This is the low level function implementing both: * - * VM_Log() - * VM_LogIOError() + * RM_Log() + * RM_LogIOError() * */ -void moduleLogRaw(ValkeyModule *module, const char *levelstr, const char *fmt, va_list ap) { +void moduleLogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_list ap) { char msg[LOG_MAX_LEN]; size_t name_len; int level; @@ -7543,10 +7638,10 @@ void moduleLogRaw(ValkeyModule *module, const char *levelstr, const char *fmt, v * printf-alike specifiers, while level is a string describing the log * level to use when emitting the log, and must be one of the following: * - * * "debug" (`VALKEYMODULE_LOGLEVEL_DEBUG`) - * * "verbose" (`VALKEYMODULE_LOGLEVEL_VERBOSE`) - * * "notice" (`VALKEYMODULE_LOGLEVEL_NOTICE`) - * * "warning" (`VALKEYMODULE_LOGLEVEL_WARNING`) + * * "debug" (`REDISMODULE_LOGLEVEL_DEBUG`) + * * "verbose" (`REDISMODULE_LOGLEVEL_VERBOSE`) + * * "notice" (`REDISMODULE_LOGLEVEL_NOTICE`) + * * "warning" (`REDISMODULE_LOGLEVEL_WARNING`) * * If the specified log level is invalid, verbose is used by default. * There is a fixed limit to the length of the log line this function is able @@ -7557,7 +7652,7 @@ void moduleLogRaw(ValkeyModule *module, const char *levelstr, const char *fmt, v * caller for instance threads or callbacks, in which case a generic "module" * will be used instead of the module name. */ -void VM_Log(ValkeyModuleCtx *ctx, const char *levelstr, const char *fmt, ...) { +void RM_Log(RedisModuleCtx *ctx, const char *levelstr, const char *fmt, ...) { va_list ap; va_start(ap, fmt); moduleLogRaw(ctx? ctx->module: NULL,levelstr,fmt,ap); @@ -7569,7 +7664,7 @@ void VM_Log(ValkeyModuleCtx *ctx, const char *levelstr, const char *fmt, ...) { * This function should be used when a callback is returning a critical * error to the caller since cannot load or save the data for some * critical reason. */ -void VM_LogIOError(ValkeyModuleIO *io, const char *levelstr, const char *fmt, ...) { +void RM_LogIOError(RedisModuleIO *io, const char *levelstr, const char *fmt, ...) { va_list ap; va_start(ap, fmt); moduleLogRaw(io->type->module,levelstr,fmt,ap); @@ -7578,20 +7673,20 @@ void VM_LogIOError(ValkeyModuleIO *io, const char *levelstr, const char *fmt, .. /* Redis-like assert function. * - * The macro `ValkeyModule_Assert(expression)` is recommended, rather than + * The macro `RedisModule_Assert(expression)` is recommended, rather than * calling this function directly. * * A failed assertion will shut down the server and produce logging information * that looks identical to information generated by Redis itself. */ -void VM__Assert(const char *estr, const char *file, int line) { +void RM__Assert(const char *estr, const char *file, int line) { _serverAssert(estr, file, line); } /* Allows adding event to the latency monitor to be observed by the LATENCY * command. The call is skipped if the latency is smaller than the configured * latency-monitor-threshold. */ -void VM_LatencyAddSample(const char *event, mstime_t latency) { +void RM_LatencyAddSample(const char *event, mstime_t latency) { if (latency >= server.latency_monitor_threshold) latencyAddSample(event, latency); } @@ -7613,18 +7708,18 @@ void VM_LatencyAddSample(const char *event, mstime_t latency) { * is a pending threaded operation involving the blocked client, we'll know * that the client no longer exists and no reply callback should be called. * - * The structure ValkeyModuleBlockedClient will be always deallocated when + * The structure RedisModuleBlockedClient will be always deallocated when * running the list of clients blocked by a module that need to be unblocked. */ void unblockClientFromModule(client *c) { - ValkeyModuleBlockedClient *bc = c->bstate.module_blocked_handle; + RedisModuleBlockedClient *bc = c->bstate.module_blocked_handle; /* Call the disconnection callback if any. Note that * bc->disconnect_callback is set to NULL if the client gets disconnected * by the module itself or because of a timeout, so the callback will NOT * get called if this is not an actual disconnection event. */ if (bc->disconnect_callback) { - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, bc->module, VALKEYMODULE_CTX_NONE); + RedisModuleCtx ctx; + moduleCreateContext(&ctx, bc->module, REDISMODULE_CTX_NONE); ctx.blocked_privdata = bc->privdata; ctx.client = bc->client; bc->disconnect_callback(&ctx,bc); @@ -7633,19 +7728,19 @@ void unblockClientFromModule(client *c) { /* If we made it here and client is still blocked it means that the command * timed-out, client was killed or disconnected and disconnect_callback was - * not implemented (or it was, but VM_UnblockClient was not called from + * not implemented (or it was, but RM_UnblockClient was not called from * within it, as it should). * We must call moduleUnblockClient in order to free privdata and - * ValkeyModuleBlockedClient. + * RedisModuleBlockedClient. * * Note that we only do that for clients that are blocked on keys, for which - * the contract is that the module should not call VM_UnblockClient under + * the contract is that the module should not call RM_UnblockClient under * normal circumstances. * Clients implementing threads and working with private data should be - * aware that calling VM_UnblockClient for every blocked client is their + * aware that calling RM_UnblockClient for every blocked client is their * responsibility, and if they fail to do so memory may leak. Ideally they * should implement the disconnect and timeout callbacks and call - * VM_UnblockClient, but any other way is also acceptable. */ + * RM_UnblockClient, but any other way is also acceptable. */ if (bc->blocked_on_keys && !bc->unblocked) moduleUnblockClient(c); @@ -7653,36 +7748,36 @@ void unblockClientFromModule(client *c) { } /* Block a client in the context of a module: this function implements both - * VM_BlockClient() and VM_BlockClientOnKeys() depending on the fact the + * RM_BlockClient() and RM_BlockClientOnKeys() depending on the fact the * keys are passed or not. * * When not blocking for keys, the keys, numkeys, and privdata parameters are * not needed. The privdata in that case must be NULL, since later is - * VM_UnblockClient() that will provide some private data that the reply + * RM_UnblockClient() that will provide some private data that the reply * callback will receive. * - * Instead when blocking for keys, normally VM_UnblockClient() will not be + * Instead when blocking for keys, normally RM_UnblockClient() will not be * called (because the client will unblock when the key is modified), so * 'privdata' should be provided in that case, so that once the client is * unlocked and the reply callback is called, it will receive its associated * private data. * - * Even when blocking on keys, VM_UnblockClient() can be called however, but + * Even when blocking on keys, RM_UnblockClient() can be called however, but * in that case the privdata argument is disregarded, because we pass the * reply callback the privdata that is set here while blocking. * */ -ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx, ValkeyModuleCmdFunc reply_callback, - ValkeyModuleAuthCallback auth_reply_callback, - ValkeyModuleCmdFunc timeout_callback, void (*free_privdata)(ValkeyModuleCtx*,void*), - long long timeout_ms, ValkeyModuleString **keys, int numkeys, void *privdata, +RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, + RedisModuleAuthCallback auth_reply_callback, + RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), + long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata, int flags) { client *c = ctx->client; int islua = scriptIsRunning(); int ismulti = server.in_exec; - c->bstate.module_blocked_handle = zmalloc(sizeof(ValkeyModuleBlockedClient)); - ValkeyModuleBlockedClient *bc = c->bstate.module_blocked_handle; + c->bstate.module_blocked_handle = zmalloc(sizeof(RedisModuleBlockedClient)); + RedisModuleBlockedClient *bc = c->bstate.module_blocked_handle; ctx->module->blocked_clients++; /* We need to handle the invalid operation of calling modules blocking @@ -7694,7 +7789,7 @@ ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx, ValkeyModuleC bc->reply_callback = reply_callback; bc->auth_reply_cb = auth_reply_callback; bc->timeout_callback = timeout_callback; - bc->disconnect_callback = NULL; /* Set by VM_SetDisconnectCallback() */ + bc->disconnect_callback = NULL; /* Set by RM_SetDisconnectCallback() */ bc->free_privdata = free_privdata; bc->privdata = privdata; bc->reply_client = moduleAllocTempClient(); @@ -7707,15 +7802,15 @@ ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx, ValkeyModuleC bc->background_timer = 0; bc->background_duration = 0; - c->bstate.timeout = 0; + mstime_t timeout = 0; if (timeout_ms) { mstime_t now = mstime(); - if (timeout_ms > LLONG_MAX - now) { + if (timeout_ms > LLONG_MAX - now) { c->bstate.module_blocked_handle = NULL; addReplyError(c, "timeout is out of range"); /* 'timeout_ms+now' would overflow */ return bc; } - c->bstate.timeout = timeout_ms + now; + timeout = timeout_ms + now; } if (islua || ismulti) { @@ -7723,7 +7818,7 @@ ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx, ValkeyModuleC addReplyError(c, islua ? "Blocking module command called from Lua script" : "Blocking module command called from transaction"); - } else if (ctx->flags & VALKEYMODULE_CTX_BLOCKED_REPLY) { + } else if (ctx->flags & REDISMODULE_CTX_BLOCKED_REPLY) { c->bstate.module_blocked_handle = NULL; addReplyError(c, "Blocking module command called from a Reply callback context"); } else if (!auth_reply_callback && clientHasModuleAuthInProgress(c)) { @@ -7731,7 +7826,7 @@ ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx, ValkeyModuleC addReplyError(c, "Clients undergoing module based authentication can only be blocked on auth"); } else { if (keys) { - blockForKeys(c,BLOCKED_MODULE,keys,numkeys,c->bstate.timeout,flags&VALKEYMODULE_BLOCK_UNBLOCK_DELETED); + blockForKeys(c,BLOCKED_MODULE,keys,numkeys,timeout,flags&REDISMODULE_BLOCK_UNBLOCK_DELETED); } else { blockClient(c,BLOCKED_MODULE); } @@ -7746,17 +7841,17 @@ ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx, ValkeyModuleC * (with AUTH field provided) commands are called. * The callbacks will be called with a module context along with a username and a password, and are * expected to take one of the following actions: - * (1) Authenticate - Use the VM_AuthenticateClient* API and return VALKEYMODULE_AUTH_HANDLED. + * (1) Authenticate - Use the RM_AuthenticateClient* API and return REDISMODULE_AUTH_HANDLED. * This will immediately end the auth chain as successful and add the OK reply. - * (2) Deny Authentication - Return VALKEYMODULE_AUTH_HANDLED without authenticating or blocking the + * (2) Deny Authentication - Return REDISMODULE_AUTH_HANDLED without authenticating or blocking the * client. Optionally, `err` can be set to a custom error message and `err` will be automatically * freed by the server. * This will immediately end the auth chain as unsuccessful and add the ERR reply. - * (3) Block a client on authentication - Use the VM_BlockClientOnAuth API and return - * VALKEYMODULE_AUTH_HANDLED. Here, the client will be blocked until the VM_UnblockClient API is used - * which will trigger the auth reply callback (provided through the VM_BlockClientOnAuth). + * (3) Block a client on authentication - Use the RM_BlockClientOnAuth API and return + * REDISMODULE_AUTH_HANDLED. Here, the client will be blocked until the RM_UnblockClient API is used + * which will trigger the auth reply callback (provided through the RM_BlockClientOnAuth). * In this reply callback, the Module should authenticate, deny or skip handling authentication. - * (4) Skip handling Authentication - Return VALKEYMODULE_AUTH_NOT_HANDLED without blocking the + * (4) Skip handling Authentication - Return REDISMODULE_AUTH_NOT_HANDLED without blocking the * client. This will allow the engine to attempt the next module auth callback. * If none of the callbacks authenticate or deny auth, then password based auth is attempted and * will authenticate or add failure logs and reply to the clients accordingly. @@ -7766,44 +7861,44 @@ ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx, ValkeyModuleC * * The following is an example of how non-blocking module based authentication can be used: * - * int auth_cb(ValkeyModuleCtx *ctx, ValkeyModuleString *username, ValkeyModuleString *password, ValkeyModuleString **err) { - * const char *user = ValkeyModule_StringPtrLen(username, NULL); - * const char *pwd = ValkeyModule_StringPtrLen(password, NULL); + * int auth_cb(RedisModuleCtx *ctx, RedisModuleString *username, RedisModuleString *password, RedisModuleString **err) { + * const char *user = RedisModule_StringPtrLen(username, NULL); + * const char *pwd = RedisModule_StringPtrLen(password, NULL); * if (!strcmp(user,"foo") && !strcmp(pwd,"valid_password")) { - * ValkeyModule_AuthenticateClientWithACLUser(ctx, "foo", 3, NULL, NULL, NULL); - * return VALKEYMODULE_AUTH_HANDLED; + * RedisModule_AuthenticateClientWithACLUser(ctx, "foo", 3, NULL, NULL, NULL); + * return REDISMODULE_AUTH_HANDLED; * } * * else if (!strcmp(user,"foo") && !strcmp(pwd,"wrong_password")) { - * ValkeyModuleString *log = ValkeyModule_CreateString(ctx, "Module Auth", 11); - * ValkeyModule_ACLAddLogEntryByUserName(ctx, username, log, VALKEYMODULE_ACL_LOG_AUTH); - * ValkeyModule_FreeString(ctx, log); + * RedisModuleString *log = RedisModule_CreateString(ctx, "Module Auth", 11); + * RedisModule_ACLAddLogEntryByUserName(ctx, username, log, REDISMODULE_ACL_LOG_AUTH); + * RedisModule_FreeString(ctx, log); * const char *err_msg = "Auth denied by Misc Module."; - * *err = ValkeyModule_CreateString(ctx, err_msg, strlen(err_msg)); - * return VALKEYMODULE_AUTH_HANDLED; + * *err = RedisModule_CreateString(ctx, err_msg, strlen(err_msg)); + * return REDISMODULE_AUTH_HANDLED; * } - * return VALKEYMODULE_AUTH_NOT_HANDLED; + * return REDISMODULE_AUTH_NOT_HANDLED; * } * - * int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) { - * if (ValkeyModule_Init(ctx,"authmodule",1,VALKEYMODULE_APIVER_1)== VALKEYMODULE_ERR) - * return VALKEYMODULE_ERR; - * ValkeyModule_RegisterAuthCallback(ctx, auth_cb); - * return VALKEYMODULE_OK; + * int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + * if (RedisModule_Init(ctx,"authmodule",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR) + * return REDISMODULE_ERR; + * RedisModule_RegisterAuthCallback(ctx, auth_cb); + * return REDISMODULE_OK; * } */ -void VM_RegisterAuthCallback(ValkeyModuleCtx *ctx, ValkeyModuleAuthCallback cb) { - ValkeyModuleAuthCtx *auth_ctx = zmalloc(sizeof(ValkeyModuleAuthCtx)); +void RM_RegisterAuthCallback(RedisModuleCtx *ctx, RedisModuleAuthCallback cb) { + RedisModuleAuthCtx *auth_ctx = zmalloc(sizeof(RedisModuleAuthCtx)); auth_ctx->module = ctx->module; auth_ctx->auth_cb = cb; listAddNodeHead(moduleAuthCallbacks, auth_ctx); } /* Helper function to invoke the free private data callback of a Module blocked client. */ -void moduleInvokeFreePrivDataCallback(client *c, ValkeyModuleBlockedClient *bc) { +void moduleInvokeFreePrivDataCallback(client *c, RedisModuleBlockedClient *bc) { if (bc->privdata && bc->free_privdata) { - ValkeyModuleCtx ctx; - int ctx_flags = c == NULL ? VALKEYMODULE_CTX_BLOCKED_DISCONNECTED : VALKEYMODULE_CTX_NONE; + RedisModuleCtx ctx; + int ctx_flags = c == NULL ? REDISMODULE_CTX_BLOCKED_DISCONNECTED : REDISMODULE_CTX_NONE; moduleCreateContext(&ctx, bc->module, ctx_flags); ctx.blocked_privdata = bc->privdata; ctx.client = bc->client; @@ -7813,12 +7908,12 @@ void moduleInvokeFreePrivDataCallback(client *c, ValkeyModuleBlockedClient *bc) } /* Unregisters all the module auth callbacks that have been registered by this Module. */ -void moduleUnregisterAuthCBs(ValkeyModule *module) { +void moduleUnregisterAuthCBs(RedisModule *module) { listIter li; listNode *ln; listRewind(moduleAuthCallbacks, &li); while ((ln = listNext(&li))) { - ValkeyModuleAuthCtx *ctx = listNodeValue(ln); + RedisModuleAuthCtx *ctx = listNodeValue(ln); if (ctx->module == module) { listDelNode(moduleAuthCallbacks, ln); zfree(ctx); @@ -7830,11 +7925,11 @@ void moduleUnregisterAuthCBs(ValkeyModule *module) { * Returns the result of the module auth callback. */ int attemptNextAuthCb(client *c, robj *username, robj *password, robj **err) { int handle_next_callback = c->module_auth_ctx == NULL; - ValkeyModuleAuthCtx *cur_auth_ctx = NULL; + RedisModuleAuthCtx *cur_auth_ctx = NULL; listNode *ln; listIter li; listRewind(moduleAuthCallbacks, &li); - int result = VALKEYMODULE_AUTH_NOT_HANDLED; + int result = REDISMODULE_AUTH_NOT_HANDLED; while((ln = listNext(&li))) { cur_auth_ctx = listNodeValue(ln); /* Skip over the previously attempted auth contexts. */ @@ -7844,32 +7939,32 @@ int attemptNextAuthCb(client *c, robj *username, robj *password, robj **err) { } /* Remove the module auth complete flag before we attempt the next cb. */ c->flags &= ~CLIENT_MODULE_AUTH_HAS_RESULT; - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, cur_auth_ctx->module, VALKEYMODULE_CTX_NONE); + RedisModuleCtx ctx; + moduleCreateContext(&ctx, cur_auth_ctx->module, REDISMODULE_CTX_NONE); ctx.client = c; *err = NULL; c->module_auth_ctx = cur_auth_ctx; result = cur_auth_ctx->auth_cb(&ctx, username, password, err); moduleFreeContext(&ctx); - if (result == VALKEYMODULE_AUTH_HANDLED) break; + if (result == REDISMODULE_AUTH_HANDLED) break; /* If Auth was not handled (allowed/denied/blocked) by the Module, try the next auth cb. */ } return result; } /* Helper function to handle a reprocessed unblocked auth client. - * Returns VALKEYMODULE_AUTH_NOT_HANDLED if the client was not reprocessed after a blocking module + * Returns REDISMODULE_AUTH_NOT_HANDLED if the client was not reprocessed after a blocking module * auth operation. * Otherwise, we attempt the auth reply callback & the free priv data callback, update fields and * return the result of the reply callback. */ int attemptBlockedAuthReplyCallback(client *c, robj *username, robj *password, robj **err) { - int result = VALKEYMODULE_AUTH_NOT_HANDLED; + int result = REDISMODULE_AUTH_NOT_HANDLED; if (!c->module_blocked_client) return result; - ValkeyModuleBlockedClient *bc = (ValkeyModuleBlockedClient *) c->module_blocked_client; + RedisModuleBlockedClient *bc = (RedisModuleBlockedClient *) c->module_blocked_client; bc->client = c; if (bc->auth_reply_cb) { - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, bc->module, VALKEYMODULE_CTX_BLOCKED_REPLY); + RedisModuleCtx ctx; + moduleCreateContext(&ctx, bc->module, REDISMODULE_CTX_BLOCKED_REPLY); ctx.blocked_privdata = bc->privdata; ctx.blocked_ready_key = NULL; ctx.client = bc->client; @@ -7886,7 +7981,7 @@ int attemptBlockedAuthReplyCallback(client *c, robj *username, robj *password, r } /* Helper function to attempt Module based authentication through module auth callbacks. - * Here, the Module is expected to authenticate the client using the ValkeyModule APIs and to add ACL + * Here, the Module is expected to authenticate the client using the RedisModule APIs and to add ACL * logs in case of errors. * Returns one of the following codes: * AUTH_OK - Indicates that a module handled and authenticated the client. @@ -7898,16 +7993,16 @@ int attemptBlockedAuthReplyCallback(client *c, robj *username, robj *password, r int checkModuleAuthentication(client *c, robj *username, robj *password, robj **err) { if (!listLength(moduleAuthCallbacks)) return AUTH_NOT_HANDLED; int result = attemptBlockedAuthReplyCallback(c, username, password, err); - if (result == VALKEYMODULE_AUTH_NOT_HANDLED) { + if (result == REDISMODULE_AUTH_NOT_HANDLED) { result = attemptNextAuthCb(c, username, password, err); } if (c->flags & CLIENT_BLOCKED) { - /* Modules are expected to return VALKEYMODULE_AUTH_HANDLED when blocking clients. */ - serverAssert(result == VALKEYMODULE_AUTH_HANDLED); + /* Modules are expected to return REDISMODULE_AUTH_HANDLED when blocking clients. */ + serverAssert(result == REDISMODULE_AUTH_HANDLED); return AUTH_BLOCKED; } c->module_auth_ctx = NULL; - if (result == VALKEYMODULE_AUTH_NOT_HANDLED) { + if (result == REDISMODULE_AUTH_NOT_HANDLED) { c->flags &= ~CLIENT_MODULE_AUTH_HAS_RESULT; return AUTH_NOT_HANDLED; } @@ -7921,26 +8016,26 @@ int checkModuleAuthentication(client *c, robj *username, robj *password, robj ** /* This function is called from module.c in order to check if a module * blocked for BLOCKED_MODULE and subtype 'on keys' (bc->blocked_on_keys true) * can really be unblocked, since the module was able to serve the client. - * If the callback returns VALKEYMODULE_OK, then the client can be unblocked, + * If the callback returns REDISMODULE_OK, then the client can be unblocked, * otherwise the client remains blocked and we'll retry again when one of * the keys it blocked for becomes "ready" again. * This function returns 1 if client was served (and should be unblocked) */ int moduleTryServeClientBlockedOnKey(client *c, robj *key) { int served = 0; - ValkeyModuleBlockedClient *bc = c->bstate.module_blocked_handle; + RedisModuleBlockedClient *bc = c->bstate.module_blocked_handle; /* Protect against re-processing: don't serve clients that are already - * in the unblocking list for any reason (including VM_UnblockClient() + * in the unblocking list for any reason (including RM_UnblockClient() * explicit call). See #6798. */ if (bc->unblocked) return 0; - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, bc->module, VALKEYMODULE_CTX_BLOCKED_REPLY); + RedisModuleCtx ctx; + moduleCreateContext(&ctx, bc->module, REDISMODULE_CTX_BLOCKED_REPLY); ctx.blocked_ready_key = key; ctx.blocked_privdata = bc->privdata; ctx.client = bc->client; ctx.blocked_client = bc; - if (bc->reply_callback(&ctx,(void**)c->argv,c->argc) == VALKEYMODULE_OK) + if (bc->reply_callback(&ctx,(void**)c->argv,c->argc) == REDISMODULE_OK) served = 1; moduleFreeContext(&ctx); return served; @@ -7948,30 +8043,30 @@ int moduleTryServeClientBlockedOnKey(client *c, robj *key) { /* Block a client in the context of a blocking command, returning a handle * which will be used, later, in order to unblock the client with a call to - * ValkeyModule_UnblockClient(). The arguments specify callback functions + * RedisModule_UnblockClient(). The arguments specify callback functions * and a timeout after which the client is unblocked. * * The callbacks are called in the following contexts: * - * reply_callback: called after a successful ValkeyModule_UnblockClient() + * reply_callback: called after a successful RedisModule_UnblockClient() * call in order to reply to the client and unblock it. * * timeout_callback: called when the timeout is reached or if `CLIENT UNBLOCK` * is invoked, in order to send an error to the client. * * free_privdata: called in order to free the private data that is passed - * by ValkeyModule_UnblockClient() call. + * by RedisModule_UnblockClient() call. * - * Note: ValkeyModule_UnblockClient should be called for every blocked client, + * Note: RedisModule_UnblockClient should be called for every blocked client, * even if client was killed, timed-out or disconnected. Failing to do so * will result in memory leaks. * - * There are some cases where ValkeyModule_BlockClient() cannot be used: + * There are some cases where RedisModule_BlockClient() cannot be used: * * 1. If the client is a Lua script. * 2. If the client is executing a MULTI block. * - * In these cases, a call to ValkeyModule_BlockClient() will **not** block the + * In these cases, a call to RedisModule_BlockClient() will **not** block the * client, but instead produce a specific error reply. * * A module that registers a timeout_callback function can also be unblocked @@ -7982,26 +8077,26 @@ int moduleTryServeClientBlockedOnKey(client *c, robj *key) { * * Measuring background time: By default the time spent in the blocked command * is not account for the total command duration. To include such time you should - * use VM_BlockedClientMeasureTimeStart() and VM_BlockedClientMeasureTimeEnd() one, + * use RM_BlockedClientMeasureTimeStart() and RM_BlockedClientMeasureTimeEnd() one, * or multiple times within the blocking command background work. */ -ValkeyModuleBlockedClient *VM_BlockClient(ValkeyModuleCtx *ctx, ValkeyModuleCmdFunc reply_callback, - ValkeyModuleCmdFunc timeout_callback, void (*free_privdata)(ValkeyModuleCtx*,void*), +RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, + RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) { return moduleBlockClient(ctx,reply_callback,NULL,timeout_callback,free_privdata,timeout_ms, NULL,0,NULL,0); } /* Block the current client for module authentication in the background. If module auth is not in - * progress on the client, the API returns NULL. Otherwise, the client is blocked and the VM_BlockedClient - * is returned similar to the VM_BlockClient API. + * progress on the client, the API returns NULL. Otherwise, the client is blocked and the RM_BlockedClient + * is returned similar to the RM_BlockClient API. * Note: Only use this API from the context of a module auth callback. */ -ValkeyModuleBlockedClient *VM_BlockClientOnAuth(ValkeyModuleCtx *ctx, ValkeyModuleAuthCallback reply_callback, - void (*free_privdata)(ValkeyModuleCtx*,void*)) { +RedisModuleBlockedClient *RM_BlockClientOnAuth(RedisModuleCtx *ctx, RedisModuleAuthCallback reply_callback, + void (*free_privdata)(RedisModuleCtx*,void*)) { if (!clientHasModuleAuthInProgress(ctx->client)) { addReplyError(ctx->client, "Module blocking client on auth when not currently undergoing module authentication"); return NULL; } - ValkeyModuleBlockedClient *bc = moduleBlockClient(ctx,NULL,reply_callback,NULL,free_privdata,0, NULL,0,NULL,0); + RedisModuleBlockedClient *bc = moduleBlockClient(ctx,NULL,reply_callback,NULL,free_privdata,0, NULL,0,NULL,0); if (ctx->client->flags & CLIENT_BLOCKED) { ctx->client->flags |= CLIENT_PENDING_COMMAND; } @@ -8009,16 +8104,16 @@ ValkeyModuleBlockedClient *VM_BlockClientOnAuth(ValkeyModuleCtx *ctx, ValkeyModu } /* Get the private data that was previusely set on a blocked client */ -void *VM_BlockClientGetPrivateData(ValkeyModuleBlockedClient *blocked_client) { +void *RM_BlockClientGetPrivateData(RedisModuleBlockedClient *blocked_client) { return blocked_client->privdata; } /* Set private data on a blocked client */ -void VM_BlockClientSetPrivateData(ValkeyModuleBlockedClient *blocked_client, void *private_data) { +void RM_BlockClientSetPrivateData(RedisModuleBlockedClient *blocked_client, void *private_data) { blocked_client->privdata = private_data; } -/* This call is similar to ValkeyModule_BlockClient(), however in this case we +/* This call is similar to RedisModule_BlockClient(), however in this case we * don't just block the client, but also ask Redis to unblock it automatically * once certain keys become "ready", that is, contain more data. * @@ -8036,31 +8131,31 @@ void VM_BlockClientSetPrivateData(ValkeyModuleBlockedClient *blocked_client, voi * on a list key, an RPUSH command may unblock our client and so forth. * 2. If you are implementing your native data type, or if you want to add new * unblocking conditions in addition to "1", you can call the modules API - * ValkeyModule_SignalKeyAsReady(). + * RedisModule_SignalKeyAsReady(). * * Anyway we can't be sure if the client should be unblocked just because the * key is signaled as ready: for instance a successive operation may change the * key, or a client in queue before this one can be served, modifying the key * as well and making it empty again. So when a client is blocked with - * ValkeyModule_BlockClientOnKeys() the reply callback is not called after - * VM_UnblockClient() is called, but every time a key is signaled as ready: - * if the reply callback can serve the client, it returns VALKEYMODULE_OK - * and the client is unblocked, otherwise it will return VALKEYMODULE_ERR + * RedisModule_BlockClientOnKeys() the reply callback is not called after + * RM_UnblockClient() is called, but every time a key is signaled as ready: + * if the reply callback can serve the client, it returns REDISMODULE_OK + * and the client is unblocked, otherwise it will return REDISMODULE_ERR * and we'll try again later. * * The reply callback can access the key that was signaled as ready by - * calling the API ValkeyModule_GetBlockedClientReadyKey(), that returns - * just the string name of the key as a ValkeyModuleString object. + * calling the API RedisModule_GetBlockedClientReadyKey(), that returns + * just the string name of the key as a RedisModuleString object. * * Thanks to this system we can setup complex blocking scenarios, like * unblocking a client only if a list contains at least 5 items or other * more fancy logics. * - * Note that another difference with ValkeyModule_BlockClient(), is that here + * Note that another difference with RedisModule_BlockClient(), is that here * we pass the private data directly when blocking the client: it will * be accessible later in the reply callback. Normally when blocking with - * ValkeyModule_BlockClient() the private data to reply to the client is - * passed when calling ValkeyModule_UnblockClient() but here the unblocking + * RedisModule_BlockClient() the private data to reply to the client is + * passed when calling RedisModule_UnblockClient() but here the unblocking * is performed by Redis itself, so we need to have some private data before * hand. The private data is used to store any information about the specific * unblocking operation that you are implementing. Such information will be @@ -8069,44 +8164,44 @@ void VM_BlockClientSetPrivateData(ValkeyModuleBlockedClient *blocked_client, voi * However the reply callback will be able to access the argument vector of * the command, so the private data is often not needed. * - * Note: Under normal circumstances ValkeyModule_UnblockClient should not be + * Note: Under normal circumstances RedisModule_UnblockClient should not be * called for clients that are blocked on keys (Either the key will * become ready or a timeout will occur). If for some reason you do want - * to call ValkeyModule_UnblockClient it is possible: Client will be + * to call RedisModule_UnblockClient it is possible: Client will be * handled as if it were timed-out (You must implement the timeout * callback in that case). */ -ValkeyModuleBlockedClient *VM_BlockClientOnKeys(ValkeyModuleCtx *ctx, ValkeyModuleCmdFunc reply_callback, - ValkeyModuleCmdFunc timeout_callback, void (*free_privdata)(ValkeyModuleCtx*,void*), - long long timeout_ms, ValkeyModuleString **keys, int numkeys, void *privdata) { +RedisModuleBlockedClient *RM_BlockClientOnKeys(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, + RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), + long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata) { return moduleBlockClient(ctx,reply_callback,NULL,timeout_callback,free_privdata,timeout_ms, keys,numkeys,privdata,0); } -/* Same as ValkeyModule_BlockClientOnKeys, but can take VALKEYMODULE_BLOCK_* flags - * Can be either VALKEYMODULE_BLOCK_UNBLOCK_DEFAULT, which means default behavior (same - * as calling ValkeyModule_BlockClientOnKeys) +/* Same as RedisModule_BlockClientOnKeys, but can take REDISMODULE_BLOCK_* flags + * Can be either REDISMODULE_BLOCK_UNBLOCK_DEFAULT, which means default behavior (same + * as calling RedisModule_BlockClientOnKeys) * * The flags is a bit mask of these: * - * - `VALKEYMODULE_BLOCK_UNBLOCK_DELETED`: The clients should to be awakened in case any of `keys` are deleted. + * - `REDISMODULE_BLOCK_UNBLOCK_DELETED`: The clients should to be awakened in case any of `keys` are deleted. * Mostly useful for commands that require the key to exist (like XREADGROUP) */ -ValkeyModuleBlockedClient *VM_BlockClientOnKeysWithFlags(ValkeyModuleCtx *ctx, ValkeyModuleCmdFunc reply_callback, - ValkeyModuleCmdFunc timeout_callback, void (*free_privdata)(ValkeyModuleCtx*,void*), - long long timeout_ms, ValkeyModuleString **keys, int numkeys, void *privdata, +RedisModuleBlockedClient *RM_BlockClientOnKeysWithFlags(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, + RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), + long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata, int flags) { return moduleBlockClient(ctx,reply_callback,NULL,timeout_callback,free_privdata,timeout_ms, keys,numkeys,privdata,flags); } /* This function is used in order to potentially unblock a client blocked - * on keys with ValkeyModule_BlockClientOnKeys(). When this function is called, + * on keys with RedisModule_BlockClientOnKeys(). When this function is called, * all the clients blocked for this key will get their reply_callback called. */ -void VM_SignalKeyAsReady(ValkeyModuleCtx *ctx, ValkeyModuleString *key) { +void RM_SignalKeyAsReady(RedisModuleCtx *ctx, RedisModuleString *key) { signalKeyAsReady(ctx->client->db, key, OBJ_MODULE); } -/* Implements VM_UnblockClient() and moduleUnblockClient(). */ -int moduleUnblockClientByHandle(ValkeyModuleBlockedClient *bc, void *privdata) { +/* Implements RM_UnblockClient() and moduleUnblockClient(). */ +int moduleUnblockClientByHandle(RedisModuleBlockedClient *bc, void *privdata) { pthread_mutex_lock(&moduleUnblockedClientsMutex); if (!bc->blocked_on_keys) bc->privdata = privdata; bc->unblocked = 1; @@ -8117,24 +8212,24 @@ int moduleUnblockClientByHandle(ValkeyModuleBlockedClient *bc, void *privdata) { } listAddNodeTail(moduleUnblockedClients,bc); pthread_mutex_unlock(&moduleUnblockedClientsMutex); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* This API is used by the Redis core to unblock a client that was blocked * by a module. */ void moduleUnblockClient(client *c) { - ValkeyModuleBlockedClient *bc = c->bstate.module_blocked_handle; + RedisModuleBlockedClient *bc = c->bstate.module_blocked_handle; moduleUnblockClientByHandle(bc,NULL); } /* Return true if the client 'c' was blocked by a module using - * VM_BlockClientOnKeys(). */ + * RM_BlockClientOnKeys(). */ int moduleClientIsBlockedOnKeys(client *c) { - ValkeyModuleBlockedClient *bc = c->bstate.module_blocked_handle; + RedisModuleBlockedClient *bc = c->bstate.module_blocked_handle; return bc->blocked_on_keys; } -/* Unblock a client blocked by `ValkeyModule_BlockedClient`. This will trigger +/* Unblock a client blocked by `RedisModule_BlockedClient`. This will trigger * the reply callbacks to be called in order to reply to the client. * The 'privdata' argument will be accessible by the reply callback, so * the caller of this function can pass any value that is needed in order to @@ -8147,38 +8242,38 @@ int moduleClientIsBlockedOnKeys(client *c) { * Note 1: this function can be called from threads spawned by the module. * * Note 2: when we unblock a client that is blocked for keys using the API - * ValkeyModule_BlockClientOnKeys(), the privdata argument here is not used. + * RedisModule_BlockClientOnKeys(), the privdata argument here is not used. * Unblocking a client that was blocked for keys using this API will still * require the client to get some reply, so the function will use the * "timeout" handler in order to do so (The privdata provided in - * ValkeyModule_BlockClientOnKeys() is accessible from the timeout - * callback via VM_GetBlockedClientPrivateData). */ -int VM_UnblockClient(ValkeyModuleBlockedClient *bc, void *privdata) { + * RedisModule_BlockClientOnKeys() is accessible from the timeout + * callback via RM_GetBlockedClientPrivateData). */ +int RM_UnblockClient(RedisModuleBlockedClient *bc, void *privdata) { if (bc->blocked_on_keys) { /* In theory the user should always pass the timeout handler as an * argument, but better to be safe than sorry. */ - if (bc->timeout_callback == NULL) return VALKEYMODULE_ERR; - if (bc->unblocked) return VALKEYMODULE_OK; - if (bc->client) moduleBlockedClientTimedOut(bc->client); + if (bc->timeout_callback == NULL) return REDISMODULE_ERR; + if (bc->unblocked) return REDISMODULE_OK; + if (bc->client) moduleBlockedClientTimedOut(bc->client, 1); } moduleUnblockClientByHandle(bc,privdata); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Abort a blocked client blocking operation: the client will be unblocked * without firing any callback. */ -int VM_AbortBlock(ValkeyModuleBlockedClient *bc) { +int RM_AbortBlock(RedisModuleBlockedClient *bc) { bc->reply_callback = NULL; bc->disconnect_callback = NULL; bc->auth_reply_cb = NULL; - return VM_UnblockClient(bc,NULL); + return RM_UnblockClient(bc,NULL); } /* Set a callback that will be called if a blocked client disconnects - * before the module has a chance to call ValkeyModule_UnblockClient() + * before the module has a chance to call RedisModule_UnblockClient() * * Usually what you want to do there, is to cleanup your module state - * so that you can call ValkeyModule_UnblockClient() safely, otherwise + * so that you can call RedisModule_UnblockClient() safely, otherwise * the client will remain blocked forever if the timeout is large. * * Notes: @@ -8190,21 +8285,21 @@ int VM_AbortBlock(ValkeyModuleBlockedClient *bc) { * a timeout. In such a case, the client is unblocked automatically * and the timeout callback is called. */ -void VM_SetDisconnectCallback(ValkeyModuleBlockedClient *bc, ValkeyModuleDisconnectFunc callback) { +void RM_SetDisconnectCallback(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback) { bc->disconnect_callback = callback; } /* This function will check the moduleUnblockedClients queue in order to * call the reply callback and really unblock the client. * - * Clients end into this list because of calls to VM_UnblockClient(), + * Clients end into this list because of calls to RM_UnblockClient(), * however it is possible that while the module was doing work for the * blocked client, it was terminated by Redis (for timeout or other reasons). - * When this happens the ValkeyModuleBlockedClient structure in the queue + * When this happens the RedisModuleBlockedClient structure in the queue * will have the 'client' field set to NULL. */ void moduleHandleBlockedClients(void) { listNode *ln; - ValkeyModuleBlockedClient *bc; + RedisModuleBlockedClient *bc; pthread_mutex_lock(&moduleUnblockedClientsMutex); while (listLength(moduleUnblockedClients)) { @@ -8219,14 +8314,14 @@ void moduleHandleBlockedClients(void) { /* Call the reply callback if the client is valid and we have * any callback. However the callback is not called if the client - * was blocked on keys (VM_BlockClientOnKeys()), because we already + * was blocked on keys (RM_BlockClientOnKeys()), because we already * called such callback in moduleTryServeClientBlockedOnKey() when * the key was signaled as ready. */ long long prev_error_replies = server.stat_total_error_replies; uint64_t reply_us = 0; if (c && !bc->blocked_on_keys && bc->reply_callback) { - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, bc->module, VALKEYMODULE_CTX_BLOCKED_REPLY); + RedisModuleCtx ctx; + moduleCreateContext(&ctx, bc->module, REDISMODULE_CTX_BLOCKED_REPLY); ctx.blocked_privdata = bc->privdata; ctx.blocked_ready_key = NULL; ctx.client = bc->client; @@ -8258,8 +8353,10 @@ void moduleHandleBlockedClients(void) { * This needs to be out of the reply callback above given that a * module might not define any callback and still do blocking ops. */ - if (c && !clientHasModuleAuthInProgress(c) && !bc->blocked_on_keys) { - updateStatsOnUnblock(c, bc->background_duration, reply_us, server.stat_total_error_replies != prev_error_replies); + if (c && !clientHasModuleAuthInProgress(c)) { + int had_errors = c->deferred_reply_errors ? !!listLength(c->deferred_reply_errors) : + (server.stat_total_error_replies != prev_error_replies); + updateStatsOnUnblock(c, bc->background_duration, reply_us, had_errors); } if (c != NULL) { @@ -8277,7 +8374,7 @@ void moduleHandleBlockedClients(void) { * if there are pending replies here. This is needed since * during a non blocking command the client may receive output. */ if (!clientHasModuleAuthInProgress(c) && clientHasPendingReplies(c) && - !(c->flags & CLIENT_PENDING_WRITE)) + !(c->flags & CLIENT_PENDING_WRITE) && c->conn) { c->flags |= CLIENT_PENDING_WRITE; listLinkNodeHead(server.clients_pending_write, &c->clients_pending_write_node); @@ -8305,31 +8402,46 @@ int moduleBlockedClientMayTimeout(client *c) { if (c->bstate.btype != BLOCKED_MODULE) return 1; - ValkeyModuleBlockedClient *bc = c->bstate.module_blocked_handle; + RedisModuleBlockedClient *bc = c->bstate.module_blocked_handle; return (bc && bc->timeout_callback != NULL); } /* 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. */ -void moduleBlockedClientTimedOut(client *c) { - ValkeyModuleBlockedClient *bc = c->bstate.module_blocked_handle; + * API to unblock the client and the memory will be released. + * + * If this function is called from a module, we handle the timeout callback + * and the update of the unblock status in a thread-safe manner to avoid race + * conditions with the main thread. + * If this function is called from the main thread, we must handle the unblocking + * of the client synchronously. This ensures that we can reply to the client before + * resetClient() is called. */ +void moduleBlockedClientTimedOut(client *c, int from_module) { + RedisModuleBlockedClient *bc = c->bstate.module_blocked_handle; /* Protect against re-processing: don't serve clients that are already - * in the unblocking list for any reason (including VM_UnblockClient() + * in the unblocking list for any reason (including RM_UnblockClient() * explicit call). See #6798. */ if (bc->unblocked) return; - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, bc->module, VALKEYMODULE_CTX_BLOCKED_TIMEOUT); + RedisModuleCtx ctx; + int flags = REDISMODULE_CTX_BLOCKED_TIMEOUT; + if (from_module) flags |= REDISMODULE_CTX_THREAD_SAFE; + moduleCreateContext(&ctx, bc->module, flags); ctx.client = bc->client; ctx.blocked_client = bc; ctx.blocked_privdata = bc->privdata; - long long prev_error_replies = server.stat_total_error_replies; + + long long prev_error_replies; + if (!from_module) + prev_error_replies = server.stat_total_error_replies; + bc->timeout_callback(&ctx,(void**)c->argv,c->argc); moduleFreeContext(&ctx); - updateStatsOnUnblock(c, bc->background_duration, 0, server.stat_total_error_replies != prev_error_replies); + + if (!from_module) + updateStatsOnUnblock(c, bc->background_duration, 0, server.stat_total_error_replies != prev_error_replies); /* For timeout events, we do not want to call the disconnect callback, * because the blocked client will be automatically disconnected in @@ -8339,24 +8451,24 @@ void moduleBlockedClientTimedOut(client *c) { /* Return non-zero if a module command was called in order to fill the * reply for a blocked client. */ -int VM_IsBlockedReplyRequest(ValkeyModuleCtx *ctx) { - return (ctx->flags & VALKEYMODULE_CTX_BLOCKED_REPLY) != 0; +int RM_IsBlockedReplyRequest(RedisModuleCtx *ctx) { + return (ctx->flags & REDISMODULE_CTX_BLOCKED_REPLY) != 0; } /* Return non-zero if a module command was called in order to fill the * reply for a blocked client that timed out. */ -int VM_IsBlockedTimeoutRequest(ValkeyModuleCtx *ctx) { - return (ctx->flags & VALKEYMODULE_CTX_BLOCKED_TIMEOUT) != 0; +int RM_IsBlockedTimeoutRequest(RedisModuleCtx *ctx) { + return (ctx->flags & REDISMODULE_CTX_BLOCKED_TIMEOUT) != 0; } -/* Get the private data set by ValkeyModule_UnblockClient() */ -void *VM_GetBlockedClientPrivateData(ValkeyModuleCtx *ctx) { +/* Get the private data set by RedisModule_UnblockClient() */ +void *RM_GetBlockedClientPrivateData(RedisModuleCtx *ctx) { return ctx->blocked_privdata; } /* Get the key that is ready when the reply callback is called in the context - * of a client blocked by ValkeyModule_BlockClientOnKeys(). */ -ValkeyModuleString *VM_GetBlockedClientReadyKey(ValkeyModuleCtx *ctx) { + * of a client blocked by RedisModule_BlockClientOnKeys(). */ +RedisModuleString *RM_GetBlockedClientReadyKey(RedisModuleCtx *ctx) { return ctx->blocked_ready_key; } @@ -8364,15 +8476,15 @@ ValkeyModuleString *VM_GetBlockedClientReadyKey(ValkeyModuleCtx *ctx) { * This is useful in the reply and timeout callbacks of blocked clients, * before sometimes the module has the blocked client handle references * around, and wants to cleanup it. */ -ValkeyModuleBlockedClient *VM_GetBlockedClientHandle(ValkeyModuleCtx *ctx) { +RedisModuleBlockedClient *RM_GetBlockedClientHandle(RedisModuleCtx *ctx) { return ctx->blocked_client; } /* Return true if when the free callback of a blocked client is called, * the reason for the client to be unblocked is that it disconnected * while it was blocked. */ -int VM_BlockedClientDisconnected(ValkeyModuleCtx *ctx) { - return (ctx->flags & VALKEYMODULE_CTX_BLOCKED_DISCONNECTED) != 0; +int RM_BlockedClientDisconnected(RedisModuleCtx *ctx) { + return (ctx->flags & REDISMODULE_CTX_BLOCKED_DISCONNECTED) != 0; } /* -------------------------------------------------------------------------- @@ -8382,27 +8494,27 @@ int VM_BlockedClientDisconnected(ValkeyModuleCtx *ctx) { /* Return a context which can be used inside threads to make Redis context * calls with certain modules APIs. If 'bc' is not NULL then the module will * be bound to a blocked client, and it will be possible to use the - * `ValkeyModule_Reply*` family of functions to accumulate a reply for when the + * `RedisModule_Reply*` family of functions to accumulate a reply for when the * client will be unblocked. Otherwise the thread safe context will be * detached by a specific client. * * To call non-reply APIs, the thread safe context must be prepared with: * - * ValkeyModule_ThreadSafeContextLock(ctx); + * RedisModule_ThreadSafeContextLock(ctx); * ... make your call here ... - * ValkeyModule_ThreadSafeContextUnlock(ctx); + * RedisModule_ThreadSafeContextUnlock(ctx); * - * This is not needed when using `ValkeyModule_Reply*` functions, assuming + * This is not needed when using `RedisModule_Reply*` functions, assuming * that a blocked client was used when the context was created, otherwise - * no ValkeyModule_Reply* call should be made at all. + * no RedisModule_Reply* call should be made at all. * * NOTE: If you're creating a detached thread safe context (bc is NULL), - * consider using `VM_GetDetachedThreadSafeContext` which will also retain + * consider using `RM_GetDetachedThreadSafeContext` which will also retain * the module ID and thus be more useful for logging. */ -ValkeyModuleCtx *VM_GetThreadSafeContext(ValkeyModuleBlockedClient *bc) { - ValkeyModuleCtx *ctx = zmalloc(sizeof(*ctx)); - ValkeyModule *module = bc ? bc->module : NULL; - int flags = VALKEYMODULE_CTX_THREAD_SAFE; +RedisModuleCtx *RM_GetThreadSafeContext(RedisModuleBlockedClient *bc) { + RedisModuleCtx *ctx = zmalloc(sizeof(*ctx)); + RedisModule *module = bc ? bc->module : NULL; + int flags = REDISMODULE_CTX_THREAD_SAFE; /* Creating a new client object is costly. To avoid that, we have an * internal pool of client objects. In blockClient(), a client object is @@ -8414,7 +8526,7 @@ ValkeyModuleCtx *VM_GetThreadSafeContext(ValkeyModuleBlockedClient *bc) { * Assuming creating detached context is rare and not that performance * critical, we avoid synchronizing access to the client pool by creating * a new client */ - if (!bc) flags |= VALKEYMODULE_CTX_NEW_CLIENT; + if (!bc) flags |= REDISMODULE_CTX_NEW_CLIENT; moduleCreateContext(ctx, module, flags); /* Even when the context is associated with a blocked client, we can't * access it safely from another thread, so we use a fake client here @@ -8437,17 +8549,17 @@ ValkeyModuleCtx *VM_GetThreadSafeContext(ValkeyModuleBlockedClient *bc) { * * This is useful for modules that wish to hold a global context over * a long term, for purposes such as logging. */ -ValkeyModuleCtx *VM_GetDetachedThreadSafeContext(ValkeyModuleCtx *ctx) { - ValkeyModuleCtx *new_ctx = zmalloc(sizeof(*new_ctx)); +RedisModuleCtx *RM_GetDetachedThreadSafeContext(RedisModuleCtx *ctx) { + RedisModuleCtx *new_ctx = zmalloc(sizeof(*new_ctx)); /* We create a new client object for the detached context. - * See VM_GetThreadSafeContext() for more information */ + * See RM_GetThreadSafeContext() for more information */ moduleCreateContext(new_ctx, ctx->module, - VALKEYMODULE_CTX_THREAD_SAFE|VALKEYMODULE_CTX_NEW_CLIENT); + REDISMODULE_CTX_THREAD_SAFE|REDISMODULE_CTX_NEW_CLIENT); return new_ctx; } /* Release a thread safe context. */ -void VM_FreeThreadSafeContext(ValkeyModuleCtx *ctx) { +void RM_FreeThreadSafeContext(RedisModuleCtx *ctx) { moduleFreeContext(ctx); zfree(ctx); } @@ -8457,35 +8569,35 @@ void moduleGILAfterLock(void) { * code block which already opened a context. */ serverAssert(server.execution_nesting == 0); /* Bump up the nesting level to prevent immediate propagation - * of possible VM_Call from th thread */ + * of possible RM_Call from th thread */ enterExecutionUnit(1, 0); } /* Acquire the server lock before executing a thread safe API call. - * This is not needed for `ValkeyModule_Reply*` calls when there is + * This is not needed for `RedisModule_Reply*` calls when there is * a blocked client connected to the thread safe context. */ -void VM_ThreadSafeContextLock(ValkeyModuleCtx *ctx) { +void RM_ThreadSafeContextLock(RedisModuleCtx *ctx) { UNUSED(ctx); moduleAcquireGIL(); moduleGILAfterLock(); } -/* Similar to VM_ThreadSafeContextLock but this function +/* Similar to RM_ThreadSafeContextLock but this function * would not block if the server lock is already acquired. * - * If successful (lock acquired) VALKEYMODULE_OK is returned, - * otherwise VALKEYMODULE_ERR is returned and errno is set + * If successful (lock acquired) REDISMODULE_OK is returned, + * otherwise REDISMODULE_ERR is returned and errno is set * accordingly. */ -int VM_ThreadSafeContextTryLock(ValkeyModuleCtx *ctx) { +int RM_ThreadSafeContextTryLock(RedisModuleCtx *ctx) { UNUSED(ctx); int res = moduleTryAcquireGIL(); if(res != 0) { errno = res; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } moduleGILAfterLock(); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } void moduleGILBeforeUnlock(void) { @@ -8501,7 +8613,7 @@ void moduleGILBeforeUnlock(void) { } /* Release the server lock after a thread safe API call was executed. */ -void VM_ThreadSafeContextUnlock(ValkeyModuleCtx *ctx) { +void RM_ThreadSafeContextUnlock(RedisModuleCtx *ctx) { UNUSED(ctx); moduleGILBeforeUnlock(); moduleReleaseGIL(); @@ -8532,32 +8644,32 @@ void moduleReleaseGIL(void) { * etc), and the subscriber callback receives only events that match a specific * mask of event types. * - * When subscribing to notifications with ValkeyModule_SubscribeToKeyspaceEvents + * 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: * - * - VALKEYMODULE_NOTIFY_GENERIC: Generic commands like DEL, EXPIRE, RENAME - * - VALKEYMODULE_NOTIFY_STRING: String events - * - VALKEYMODULE_NOTIFY_LIST: List events - * - VALKEYMODULE_NOTIFY_SET: Set events - * - VALKEYMODULE_NOTIFY_HASH: Hash events - * - VALKEYMODULE_NOTIFY_ZSET: Sorted Set events - * - VALKEYMODULE_NOTIFY_EXPIRED: Expiration events - * - VALKEYMODULE_NOTIFY_EVICTED: Eviction events - * - VALKEYMODULE_NOTIFY_STREAM: Stream events - * - VALKEYMODULE_NOTIFY_MODULE: Module types events - * - VALKEYMODULE_NOTIFY_KEYMISS: Key-miss events + * - 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_STREAM: Stream events + * - REDISMODULE_NOTIFY_MODULE: Module types events + * - REDISMODULE_NOTIFY_KEYMISS: Key-miss events * Notice, key-miss event is the only type * of event that is fired from within a read command. - * Performing VM_Call with a write command from within + * Performing RM_Call with a write command from within * this notification is wrong and discourage. It will * cause the read command that trigger the event to be * replicated to the AOF/Replica. - * - VALKEYMODULE_NOTIFY_ALL: All events (Excluding VALKEYMODULE_NOTIFY_KEYMISS) - * - VALKEYMODULE_NOTIFY_LOADED: A special notification available only for modules, + * - REDISMODULE_NOTIFY_ALL: All events (Excluding REDISMODULE_NOTIFY_KEYMISS) + * - REDISMODULE_NOTIFY_LOADED: A special notification available only for modules, * indicates that the key was loaded from persistence. * Notice, when this event fires, the given key - * can not be retained, use VM_CreateStringFromString + * can not be retained, use RM_CreateStringFromString * instead. * * We do not distinguish between key events and keyspace events, and it is up @@ -8565,9 +8677,9 @@ void moduleReleaseGIL(void) { * * The subscriber signature is: * - * int (*ValkeyModuleNotificationFunc) (ValkeyModuleCtx *ctx, int type, + * int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type, * const char *event, - * ValkeyModuleString *key); + * 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 @@ -8577,7 +8689,7 @@ void moduleReleaseGIL(void) { * used to send anything to the client, and has the db number where the event * occurred as its selected db number. * - * Notice that it is not necessary to enable notifications in valkey.conf for + * Notice that it is not necessary to enable notifications in redis.conf for * module notifications to work. * * Warning: the notification callbacks are performed in a synchronous manner, @@ -8588,19 +8700,19 @@ void moduleReleaseGIL(void) { * that the notification code will be executed in the middle on Redis logic * (commands logic, eviction, expire). Changing the key space while the logic * runs is dangerous and discouraged. In order to react to key space events with - * write actions, please refer to `VM_AddPostNotificationJob`. + * write actions, please refer to `RM_AddPostNotificationJob`. * * See https://redis.io/topics/notifications for more information. */ -int VM_SubscribeToKeyspaceEvents(ValkeyModuleCtx *ctx, int types, ValkeyModuleNotificationFunc callback) { - ValkeyModuleKeyspaceSubscriber *sub = zmalloc(sizeof(*sub)); +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; listAddNodeTail(moduleKeyspaceSubscribers, sub); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } void firePostExecutionUnitJobs(void) { @@ -8612,11 +8724,11 @@ void firePostExecutionUnitJobs(void) { enterExecutionUnit(0, 0); while (listLength(modulePostExecUnitJobs) > 0) { listNode *ln = listFirst(modulePostExecUnitJobs); - ValkeyModulePostExecUnitJob *job = listNodeValue(ln); + RedisModulePostExecUnitJob *job = listNodeValue(ln); listDelNode(modulePostExecUnitJobs, ln); - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, job->module, VALKEYMODULE_CTX_TEMP_CLIENT); + RedisModuleCtx ctx; + moduleCreateContext(&ctx, job->module, REDISMODULE_CTX_TEMP_CLIENT); selectDb(ctx.client, job->dbid); job->callback(&ctx, job->pd); @@ -8629,8 +8741,8 @@ void firePostExecutionUnitJobs(void) { } /* When running inside a key space notification callback, it is dangerous and highly discouraged to perform any write - * operation (See `VM_SubscribeToKeyspaceEvents`). In order to still perform write actions in this scenario, - * Redis provides `VM_AddPostNotificationJob` API. The API allows to register a job callback which Redis will call + * operation (See `RM_SubscribeToKeyspaceEvents`). In order to still perform write actions in this scenario, + * Redis provides `RM_AddPostNotificationJob` API. The API allows to register a job callback which Redis will call * when the following condition are promised to be fulfilled: * 1. It is safe to perform any write operation. * 2. The job will be called atomically along side the key space notification. @@ -8643,13 +8755,13 @@ void firePostExecutionUnitJobs(void) { * * 'free_pd' can be NULL and in such case will not be used. * - * Return VALKEYMODULE_OK on success and VALKEYMODULE_ERR if was called while loading data from disk (AOF or RDB) or + * Return REDISMODULE_OK on success and REDISMODULE_ERR if was called while loading data from disk (AOF or RDB) or * if the instance is a readonly replica. */ -int VM_AddPostNotificationJob(ValkeyModuleCtx *ctx, ValkeyModulePostNotificationJobFunc callback, void *privdata, void (*free_privdata)(void*)) { +int RM_AddPostNotificationJob(RedisModuleCtx *ctx, RedisModulePostNotificationJobFunc callback, void *privdata, void (*free_privdata)(void*)) { if (server.loading|| (server.masterhost && server.repl_slave_ro)) { - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - ValkeyModulePostExecUnitJob *job = zmalloc(sizeof(*job)); + RedisModulePostExecUnitJob *job = zmalloc(sizeof(*job)); job->module = ctx->module; job->callback = callback; job->pd = privdata; @@ -8657,21 +8769,21 @@ int VM_AddPostNotificationJob(ValkeyModuleCtx *ctx, ValkeyModulePostNotification job->dbid = ctx->client->db->id; listAddNodeTail(modulePostExecUnitJobs, job); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Get the configured bitmap of notify-keyspace-events (Could be used - * for additional filtering in ValkeyModuleNotificationFunc) */ -int VM_GetNotifyKeyspaceEvents(void) { + * for additional filtering in RedisModuleNotificationFunc) */ +int RM_GetNotifyKeyspaceEvents(void) { return server.notify_keyspace_events; } /* Expose notifyKeyspaceEvent to modules */ -int VM_NotifyKeyspaceEvent(ValkeyModuleCtx *ctx, int type, const char *event, ValkeyModuleString *key) { +int RM_NotifyKeyspaceEvent(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) { if (!ctx || !ctx->client) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; notifyKeyspaceEvent(type, (char *)event, key, ctx->client->db->id); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Dispatcher for keyspace notifications to module subscriber functions. @@ -8683,7 +8795,7 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) /* Ugly hack to handle modules which use write commands from within * notify_callback, which they should NOT do! - * Modules should use ValkeyModules_AddPostNotificationJob instead. + * Modules should use RedisModules_AddPostNotificationJob instead. * * Anyway, we want any propagated commands from within notify_callback * to be propagated inside a MULTI/EXEC together with the original @@ -8708,23 +8820,24 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) type &= ~(NOTIFY_KEYEVENT | NOTIFY_KEYSPACE); while((ln = listNext(&li))) { - ValkeyModuleKeyspaceSubscriber *sub = ln->value; + RedisModuleKeyspaceSubscriber *sub = ln->value; /* Only notify subscribers on events matching the registration, * and avoid subscribers triggering themselves */ if ((sub->event_mask & type) && - (sub->active == 0 || (sub->module->options & VALKEYMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS))) { - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, sub->module, VALKEYMODULE_CTX_TEMP_CLIENT); + (sub->active == 0 || (sub->module->options & REDISMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS))) { + RedisModuleCtx ctx; + moduleCreateContext(&ctx, sub->module, REDISMODULE_CTX_TEMP_CLIENT); selectDb(ctx.client, dbid); /* mark the handler as active to avoid reentrant loops. * If the subscriber performs an action triggering itself, * it will not be notified about it. */ + int prev_active = sub->active; sub->active = 1; server.lazy_expire_disabled++; sub->notify_callback(&ctx, type, event, key); server.lazy_expire_disabled--; - sub->active = 0; + sub->active = prev_active; moduleFreeContext(&ctx); } } @@ -8733,12 +8846,12 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) } /* Unsubscribe any notification subscribers this module has upon unloading */ -void moduleUnsubscribeNotifications(ValkeyModule *module) { +void moduleUnsubscribeNotifications(RedisModule *module) { listIter li; listNode *ln; listRewind(moduleKeyspaceSubscribers,&li); while((ln = listNext(&li))) { - ValkeyModuleKeyspaceSubscriber *sub = ln->value; + RedisModuleKeyspaceSubscriber *sub = ln->value; if (sub->module == module) { listDelNode(moduleKeyspaceSubscribers, ln); zfree(sub); @@ -8751,15 +8864,15 @@ void moduleUnsubscribeNotifications(ValkeyModule *module) { * -------------------------------------------------------------------------- */ /* The Cluster message callback function pointer type. */ -typedef void (*ValkeyModuleClusterMessageReceiver)(ValkeyModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len); +typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len); /* This structure identifies a registered caller: it must match a given module * ID, for a given message type. The callback function is just the function * that was registered as receiver. */ typedef struct moduleClusterReceiver { uint64_t module_id; - ValkeyModuleClusterMessageReceiver callback; - struct ValkeyModule *module; + RedisModuleClusterMessageReceiver callback; + struct RedisModule *module; struct moduleClusterReceiver *next; } moduleClusterReceiver; @@ -8767,7 +8880,7 @@ typedef struct moduleClusterNodeInfo { int flags; char ip[NET_IP_STR_LEN]; int port; - char master_id[40]; /* Only if flags & VALKEYMODULE_NODE_PRIMARY is true. */ + char master_id[40]; /* Only if flags & REDISMODULE_NODE_MASTER is true. */ } mdouleClusterNodeInfo; /* We have an array of message types: each bucket is a linked list of @@ -8779,8 +8892,8 @@ void moduleCallClusterReceivers(const char *sender_id, uint64_t module_id, uint8 moduleClusterReceiver *r = clusterReceivers[type]; while(r) { if (r->module_id == module_id) { - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, r->module, VALKEYMODULE_CTX_TEMP_CLIENT); + RedisModuleCtx ctx; + moduleCreateContext(&ctx, r->module, REDISMODULE_CTX_TEMP_CLIENT); r->callback(&ctx,sender_id,type,payload,len); moduleFreeContext(&ctx); return; @@ -8794,7 +8907,7 @@ void moduleCallClusterReceivers(const char *sender_id, uint64_t module_id, uint8 * with the one provided, otherwise if the callback is set to NULL and there * is already a callback for this function, the callback is unregistered * (so this API call is also used in order to delete the receiver). */ -void VM_RegisterClusterMessageReceiver(ValkeyModuleCtx *ctx, uint8_t type, ValkeyModuleClusterMessageReceiver callback) { +void RM_RegisterClusterMessageReceiver(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback) { if (!server.cluster_enabled) return; uint64_t module_id = moduleTypeEncodeId(ctx->module->name,0); @@ -8832,129 +8945,112 @@ void VM_RegisterClusterMessageReceiver(ValkeyModuleCtx *ctx, uint8_t type, Valke } /* Send a message to all the nodes in the cluster if `target` is NULL, otherwise - * at the specified target, which is a VALKEYMODULE_NODE_ID_LEN bytes node ID, as + * at the specified target, which is a REDISMODULE_NODE_ID_LEN bytes node ID, as * returned by the receiver callback or by the nodes iteration functions. * - * The function returns VALKEYMODULE_OK if the message was successfully sent, + * The function returns REDISMODULE_OK if the message was successfully sent, * otherwise if the node is not connected or such node ID does not map to any - * known cluster node, VALKEYMODULE_ERR is returned. */ -int VM_SendClusterMessage(ValkeyModuleCtx *ctx, const char *target_id, uint8_t type, const char *msg, uint32_t len) { - if (!server.cluster_enabled) return VALKEYMODULE_ERR; + * known cluster node, REDISMODULE_ERR is returned. */ +int RM_SendClusterMessage(RedisModuleCtx *ctx, const char *target_id, uint8_t type, const char *msg, uint32_t len) { + if (!server.cluster_enabled) return REDISMODULE_ERR; uint64_t module_id = moduleTypeEncodeId(ctx->module->name,0); if (clusterSendModuleMessageToTarget(target_id,module_id,type,msg,len) == C_OK) - return VALKEYMODULE_OK; + return REDISMODULE_OK; else - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } /* Return an array of string pointers, each string pointer points to a cluster - * node ID of exactly VALKEYMODULE_NODE_ID_LEN bytes (without any null term). + * node ID of exactly REDISMODULE_NODE_ID_LEN bytes (without any null term). * The number of returned node IDs is stored into `*numnodes`. * However if this function is called by a module not running an a Redis * instance with Redis Cluster enabled, NULL is returned instead. * - * The IDs returned can be used with ValkeyModule_GetClusterNodeInfo() in order + * The IDs returned can be used with RedisModule_GetClusterNodeInfo() in order * to get more information about single node. * * The array returned by this function must be freed using the function - * ValkeyModule_FreeClusterNodesList(). + * RedisModule_FreeClusterNodesList(). * * Example: * * size_t count, j; - * char **ids = ValkeyModule_GetClusterNodesList(ctx,&count); + * char **ids = RedisModule_GetClusterNodesList(ctx,&count); * for (j = 0; j < count; j++) { - * ValkeyModule_Log(ctx,"notice","Node %.*s", - * VALKEYMODULE_NODE_ID_LEN,ids[j]); + * RedisModule_Log(ctx,"notice","Node %.*s", + * REDISMODULE_NODE_ID_LEN,ids[j]); * } - * ValkeyModule_FreeClusterNodesList(ids); + * RedisModule_FreeClusterNodesList(ids); */ -char **VM_GetClusterNodesList(ValkeyModuleCtx *ctx, size_t *numnodes) { +char **RM_GetClusterNodesList(RedisModuleCtx *ctx, size_t *numnodes) { UNUSED(ctx); if (!server.cluster_enabled) return NULL; - size_t count = dictSize(server.cluster->nodes); - char **ids = zmalloc((count+1)*VALKEYMODULE_NODE_ID_LEN); - dictIterator *di = dictGetIterator(server.cluster->nodes); - dictEntry *de; - int j = 0; - while((de = dictNext(di)) != NULL) { - clusterNode *node = dictGetVal(de); - if (node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE)) continue; - ids[j] = zmalloc(VALKEYMODULE_NODE_ID_LEN); - memcpy(ids[j],node->name,VALKEYMODULE_NODE_ID_LEN); - j++; - } - *numnodes = j; - ids[j] = NULL; /* Null term so that FreeClusterNodesList does not need - * to also get the count argument. */ - dictReleaseIterator(di); - return ids; + return getClusterNodesList(numnodes); } -/* Free the node list obtained with ValkeyModule_GetClusterNodesList. */ -void VM_FreeClusterNodesList(char **ids) { +/* Free the node list obtained with RedisModule_GetClusterNodesList. */ +void RM_FreeClusterNodesList(char **ids) { if (ids == NULL) return; for (int j = 0; ids[j]; j++) zfree(ids[j]); zfree(ids); } -/* Return this node ID (VALKEYMODULE_CLUSTER_ID_LEN bytes) or NULL if the cluster +/* Return this node ID (REDISMODULE_CLUSTER_ID_LEN bytes) or NULL if the cluster * is disabled. */ -const char *VM_GetMyClusterID(void) { +const char *RM_GetMyClusterID(void) { if (!server.cluster_enabled) return NULL; - return server.cluster->myself->name; + return getMyClusterId(); } /* Return the number of nodes in the cluster, regardless of their state * (handshake, noaddress, ...) so that the number of active nodes may actually * be smaller, but not greater than this number. If the instance is not in * cluster mode, zero is returned. */ -size_t VM_GetClusterSize(void) { +size_t RM_GetClusterSize(void) { if (!server.cluster_enabled) return 0; - return dictSize(server.cluster->nodes); + return getClusterSize(); } /* Populate the specified info for the node having as ID the specified 'id', - * then returns VALKEYMODULE_OK. Otherwise if the format of node ID is invalid - * or the node ID does not exist from the POV of this local node, VALKEYMODULE_ERR + * then returns REDISMODULE_OK. Otherwise if the format of node ID is invalid + * or the node ID does not exist from the POV of this local node, REDISMODULE_ERR * is returned. * * The arguments `ip`, `master_id`, `port` and `flags` can be NULL in case we don't * need to populate back certain info. If an `ip` and `master_id` (only populated * if the instance is a slave) are specified, they point to buffers holding - * at least VALKEYMODULE_NODE_ID_LEN bytes. The strings written back as `ip` + * at least REDISMODULE_NODE_ID_LEN bytes. The strings written back as `ip` * and `master_id` are not null terminated. * * The list of flags reported is the following: * - * * VALKEYMODULE_NODE_MYSELF: This node - * * VALKEYMODULE_NODE_PRIMARY: The node is a primary - * * VALKEYMODULE_NODE_REPLICA: The node is a replica - * * VALKEYMODULE_NODE_PFAIL: We see the node as failing - * * VALKEYMODULE_NODE_FAIL: The cluster agrees the node is failing - * * VALKEYMODULE_NODE_NOFAILOVER: The slave is configured to never failover + * * REDISMODULE_NODE_MYSELF: This node + * * REDISMODULE_NODE_MASTER: The node is a master + * * REDISMODULE_NODE_SLAVE: The node is a replica + * * REDISMODULE_NODE_PFAIL: We see the node as failing + * * REDISMODULE_NODE_FAIL: The cluster agrees the node is failing + * * REDISMODULE_NODE_NOFAILOVER: The slave is configured to never failover */ -int VM_GetClusterNodeInfo(ValkeyModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags) { +int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags) { UNUSED(ctx); clusterNode *node = clusterLookupNode(id, strlen(id)); - if (node == NULL || - node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE)) + if (node == NULL || clusterNodePending(node)) { - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - if (ip) redis_strlcpy(ip,node->ip,NET_IP_STR_LEN); + if (ip) redis_strlcpy(ip, clusterNodeIp(node),NET_IP_STR_LEN); if (master_id) { /* If the information is not available, the function will set the * field to zero bytes, so that when the field can't be populated the * function kinda remains predictable. */ - if (node->flags & CLUSTER_NODE_SLAVE && node->slaveof) - memcpy(master_id,node->slaveof->name,VALKEYMODULE_NODE_ID_LEN); + if (clusterNodeIsSlave(node) && clusterNodeGetSlaveof(node)) + memcpy(master_id, clusterNodeGetName(clusterNodeGetSlaveof(node)) ,REDISMODULE_NODE_ID_LEN); else - memset(master_id,0,VALKEYMODULE_NODE_ID_LEN); + memset(master_id,0,REDISMODULE_NODE_ID_LEN); } if (port) *port = getNodeDefaultClientPort(node); @@ -8962,14 +9058,14 @@ int VM_GetClusterNodeInfo(ValkeyModuleCtx *ctx, const char *id, char *ip, char * * we can provide binary compatibility. */ if (flags) { *flags = 0; - if (node->flags & CLUSTER_NODE_MYSELF) *flags |= VALKEYMODULE_NODE_MYSELF; - if (node->flags & CLUSTER_NODE_MASTER) *flags |= VALKEYMODULE_NODE_PRIMARY; - if (node->flags & CLUSTER_NODE_SLAVE) *flags |= VALKEYMODULE_NODE_REPLICA; - if (node->flags & CLUSTER_NODE_PFAIL) *flags |= VALKEYMODULE_NODE_PFAIL; - if (node->flags & CLUSTER_NODE_FAIL) *flags |= VALKEYMODULE_NODE_FAIL; - if (node->flags & CLUSTER_NODE_NOFAILOVER) *flags |= VALKEYMODULE_NODE_NOFAILOVER; + if (clusterNodeIsMyself(node)) *flags |= REDISMODULE_NODE_MYSELF; + if (clusterNodeIsMaster(node)) *flags |= REDISMODULE_NODE_MASTER; + if (clusterNodeIsSlave(node)) *flags |= REDISMODULE_NODE_SLAVE; + if (clusterNodeTimedOut(node)) *flags |= REDISMODULE_NODE_PFAIL; + if (clusterNodeIsFailing(node)) *flags |= REDISMODULE_NODE_FAIL; + if (clusterNodeIsNoFailover(node)) *flags |= REDISMODULE_NODE_NOFAILOVER; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Set Redis Cluster flags in order to change the normal behavior of @@ -8990,11 +9086,11 @@ int VM_GetClusterNodeInfo(ValkeyModuleCtx *ctx, const char *id, char *ip, char * * partitioning according to the Redis Cluster algorithm. * Slots information will still be propagated across the * cluster, but without effect. */ -void VM_SetClusterFlags(ValkeyModuleCtx *ctx, uint64_t flags) { +void RM_SetClusterFlags(RedisModuleCtx *ctx, uint64_t flags) { UNUSED(ctx); - if (flags & VALKEYMODULE_CLUSTER_FLAG_NO_FAILOVER) + if (flags & REDISMODULE_CLUSTER_FLAG_NO_FAILOVER) server.cluster_module_flags |= CLUSTER_MODULE_FLAG_NO_FAILOVER; - if (flags & VALKEYMODULE_CLUSTER_FLAG_NO_REDIRECTION) + if (flags & REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION) server.cluster_module_flags |= CLUSTER_MODULE_FLAG_NO_REDIRECTION; } @@ -9020,15 +9116,15 @@ void VM_SetClusterFlags(ValkeyModuleCtx *ctx, uint64_t flags) { 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 void (*ValkeyModuleTimerProc)(ValkeyModuleCtx *ctx, void *data); +typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data); /* The timer descriptor, stored as value in the radix tree. */ -typedef struct ValkeyModuleTimer { - ValkeyModule *module; /* Module reference. */ - ValkeyModuleTimerProc callback; /* The callback to invoke on expire. */ +typedef struct RedisModuleTimer { + RedisModule *module; /* Module reference. */ + RedisModuleTimerProc callback; /* The callback to invoke on expire. */ void *data; /* Private data for the callback. */ int dbid; /* Database number selected by the original client. */ -} ValkeyModuleTimer; +} RedisModuleTimer; /* This is the timer handler that is called by the main event loop. We schedule * this timer to be called when the nearest of our module timers will expire. */ @@ -9049,9 +9145,9 @@ int moduleTimerHandler(struct aeEventLoop *eventLoop, long long id, void *client memcpy(&expiretime,ri.key,sizeof(expiretime)); expiretime = ntohu64(expiretime); if (now >= expiretime) { - ValkeyModuleTimer *timer = ri.data; - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx,timer->module,VALKEYMODULE_CTX_TEMP_CLIENT); + RedisModuleTimer *timer = ri.data; + RedisModuleCtx ctx; + moduleCreateContext(&ctx,timer->module,REDISMODULE_CTX_TEMP_CLIENT); selectDb(ctx.client, timer->dbid); timer->callback(&ctx,timer->data); moduleFreeContext(&ctx); @@ -9084,7 +9180,7 @@ int moduleTimerHandler(struct aeEventLoop *eventLoop, long long id, void *client * 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. * Note that for the common use case of a repeating timer (Re-registration - * of the timer inside the ValkeyModuleTimerProc callback) it matters when + * 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'. @@ -9092,8 +9188,8 @@ int moduleTimerHandler(struct aeEventLoop *eventLoop, long long id, void *client * there will 'period' milliseconds gaps between events. * (If the time it takes to execute 'callback' is negligible the two * statements above mean the same) */ -ValkeyModuleTimerID VM_CreateTimer(ValkeyModuleCtx *ctx, mstime_t period, ValkeyModuleTimerProc callback, void *data) { - ValkeyModuleTimer *timer = zmalloc(sizeof(*timer)); +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; @@ -9103,7 +9199,7 @@ ValkeyModuleTimerID VM_CreateTimer(ValkeyModuleCtx *ctx, mstime_t period, Valkey while(1) { key = htonu64(expiretime); - if (raxFind(Timers, (unsigned char*)&key,sizeof(key)) == raxNotFound) { + if (!raxFind(Timers, (unsigned char*)&key,sizeof(key),NULL)) { raxInsert(Timers,(unsigned char*)&key,sizeof(key),timer,NULL); break; } else { @@ -9137,48 +9233,54 @@ ValkeyModuleTimerID VM_CreateTimer(ValkeyModuleCtx *ctx, mstime_t period, Valkey return key; } -/* Stop a timer, returns VALKEYMODULE_OK if the timer was found, belonged to the - * calling module, and was stopped, otherwise VALKEYMODULE_ERR is returned. +/* Stop a timer, returns REDISMODULE_OK if the timer was found, belonged to the + * calling module, and was stopped, 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 VM_StopTimer(ValkeyModuleCtx *ctx, ValkeyModuleTimerID id, void **data) { - ValkeyModuleTimer *timer = raxFind(Timers,(unsigned char*)&id,sizeof(id)); - if (timer == raxNotFound || timer->module != ctx->module) - return VALKEYMODULE_ERR; +int RM_StopTimer(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data) { + void *result; + if (!raxFind(Timers,(unsigned char*)&id,sizeof(id),&result)) + return REDISMODULE_ERR; + RedisModuleTimer *timer = result; + if (timer->module != ctx->module) + return REDISMODULE_ERR; if (data) *data = timer->data; raxRemove(Timers,(unsigned char*)&id,sizeof(id),NULL); zfree(timer); - return VALKEYMODULE_OK; + 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 VALKEYMODULE_ERR, otherwise - * VALKEYMODULE_OK is returned. The arguments remaining or data can be NULL if + * no information is returned and the function returns REDISMODULE_ERR, otherwise + * REDISMODULE_OK is returned. The arguments remaining or data can be NULL if * the caller does not need certain information. */ -int VM_GetTimerInfo(ValkeyModuleCtx *ctx, ValkeyModuleTimerID id, uint64_t *remaining, void **data) { - ValkeyModuleTimer *timer = raxFind(Timers,(unsigned char*)&id,sizeof(id)); - if (timer == raxNotFound || timer->module != ctx->module) - return VALKEYMODULE_ERR; +int RM_GetTimerInfo(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data) { + void *result; + if (!raxFind(Timers,(unsigned char*)&id,sizeof(id),&result)) + return REDISMODULE_ERR; + RedisModuleTimer *timer = result; + if (timer->module != ctx->module) + return REDISMODULE_ERR; if (remaining) { int64_t rem = ntohu64(id)-ustime(); if (rem < 0) rem = 0; *remaining = rem/1000; /* Scale to milliseconds. */ } if (data) *data = timer->data; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Query timers to see if any timer belongs to the module. * Return 1 if any timer was found, otherwise 0 would be returned. */ -int moduleHoldsTimer(struct ValkeyModule *module) { +int moduleHoldsTimer(struct RedisModule *module) { raxIterator iter; int found = 0; raxStart(&iter,Timers); raxSeek(&iter,"^",NULL,0); while (raxNext(&iter)) { - ValkeyModuleTimer *timer = iter.data; + RedisModuleTimer *timer = iter.data; if (timer->module == module) { found = 1; break; @@ -9193,13 +9295,13 @@ int moduleHoldsTimer(struct ValkeyModule *module) { * --------------------------------------------------------------------------*/ typedef struct EventLoopData { - ValkeyModuleEventLoopFunc rFunc; - ValkeyModuleEventLoopFunc wFunc; + RedisModuleEventLoopFunc rFunc; + RedisModuleEventLoopFunc wFunc; void *user_data; } EventLoopData; typedef struct EventLoopOneShot { - ValkeyModuleEventLoopOneShotFunc func; + RedisModuleEventLoopOneShotFunc func; void *user_data; } EventLoopOneShot; @@ -9208,9 +9310,9 @@ static pthread_mutex_t moduleEventLoopMutex = PTHREAD_MUTEX_INITIALIZER; static int eventLoopToAeMask(int mask) { int aeMask = 0; - if (mask & VALKEYMODULE_EVENTLOOP_READABLE) + if (mask & REDISMODULE_EVENTLOOP_READABLE) aeMask |= AE_READABLE; - if (mask & VALKEYMODULE_EVENTLOOP_WRITABLE) + if (mask & REDISMODULE_EVENTLOOP_WRITABLE) aeMask |= AE_WRITABLE; return aeMask; } @@ -9218,9 +9320,9 @@ static int eventLoopToAeMask(int mask) { static int eventLoopFromAeMask(int ae_mask) { int mask = 0; if (ae_mask & AE_READABLE) - mask |= VALKEYMODULE_EVENTLOOP_READABLE; + mask |= REDISMODULE_EVENTLOOP_READABLE; if (ae_mask & AE_WRITABLE) - mask |= VALKEYMODULE_EVENTLOOP_WRITABLE; + mask |= REDISMODULE_EVENTLOOP_WRITABLE; return mask; } @@ -9240,12 +9342,12 @@ static void eventLoopCbWritable(struct aeEventLoop *ae, int fd, void *user_data, * * * `mask` must be one of the following values: * - * * `VALKEYMODULE_EVENTLOOP_READABLE` - * * `VALKEYMODULE_EVENTLOOP_WRITABLE` - * * `VALKEYMODULE_EVENTLOOP_READABLE | VALKEYMODULE_EVENTLOOP_WRITABLE` + * * `REDISMODULE_EVENTLOOP_READABLE` + * * `REDISMODULE_EVENTLOOP_WRITABLE` + * * `REDISMODULE_EVENTLOOP_READABLE | REDISMODULE_EVENTLOOP_WRITABLE` * - * On success VALKEYMODULE_OK is returned, otherwise - * VALKEYMODULE_ERR is returned and errno is set to the following values: + * On success REDISMODULE_OK is returned, otherwise + * REDISMODULE_ERR is returned and errno is set to the following values: * * * ERANGE: `fd` is negative or higher than `maxclients` Redis config. * * EINVAL: `callback` is NULL or `mask` value is invalid. @@ -9259,23 +9361,23 @@ static void eventLoopCbWritable(struct aeEventLoop *ae, int fd, void *user_data, * int bytes = read(fd,buf,sizeof(buf)); * printf("Read %d bytes \n", bytes); * } - * VM_EventLoopAdd(fd, VALKEYMODULE_EVENTLOOP_READABLE, onReadable, NULL); + * RM_EventLoopAdd(fd, REDISMODULE_EVENTLOOP_READABLE, onReadable, NULL); */ -int VM_EventLoopAdd(int fd, int mask, ValkeyModuleEventLoopFunc func, void *user_data) { +int RM_EventLoopAdd(int fd, int mask, RedisModuleEventLoopFunc func, void *user_data) { if (fd < 0 || fd >= aeGetSetSize(server.el)) { errno = ERANGE; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - if (!func || mask & ~(VALKEYMODULE_EVENTLOOP_READABLE | - VALKEYMODULE_EVENTLOOP_WRITABLE)) { + if (!func || mask & ~(REDISMODULE_EVENTLOOP_READABLE | + REDISMODULE_EVENTLOOP_WRITABLE)) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } /* We are going to register stub callbacks to 'ae' for two reasons: * - * - "ae" callback signature is different from ValkeyModuleEventLoopCallback, + * - "ae" callback signature is different from RedisModuleEventLoopCallback, * that will be handled it in our stub callbacks. * - We need to remap 'mask' value to provide binary compatibility. * @@ -9288,7 +9390,7 @@ int VM_EventLoopAdd(int fd, int mask, ValkeyModuleEventLoopFunc func, void *user data = zcalloc(sizeof(*data)); aeFileProc *aeProc; - if (mask & VALKEYMODULE_EVENTLOOP_READABLE) + if (mask & REDISMODULE_EVENTLOOP_READABLE) aeProc = eventLoopCbReadable; else aeProc = eventLoopCbWritable; @@ -9298,43 +9400,43 @@ int VM_EventLoopAdd(int fd, int mask, ValkeyModuleEventLoopFunc func, void *user if (aeCreateFileEvent(server.el, fd, aeMask, aeProc, data) != AE_OK) { if (aeGetFileEvents(server.el, fd) == AE_NONE) zfree(data); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } data->user_data = user_data; - if (mask & VALKEYMODULE_EVENTLOOP_READABLE) + if (mask & REDISMODULE_EVENTLOOP_READABLE) data->rFunc = func; - if (mask & VALKEYMODULE_EVENTLOOP_WRITABLE) + if (mask & REDISMODULE_EVENTLOOP_WRITABLE) data->wFunc = func; errno = 0; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Delete a pipe / socket event from the event loop. * * * `mask` must be one of the following values: * - * * `VALKEYMODULE_EVENTLOOP_READABLE` - * * `VALKEYMODULE_EVENTLOOP_WRITABLE` - * * `VALKEYMODULE_EVENTLOOP_READABLE | VALKEYMODULE_EVENTLOOP_WRITABLE` + * * `REDISMODULE_EVENTLOOP_READABLE` + * * `REDISMODULE_EVENTLOOP_WRITABLE` + * * `REDISMODULE_EVENTLOOP_READABLE | REDISMODULE_EVENTLOOP_WRITABLE` * - * On success VALKEYMODULE_OK is returned, otherwise - * VALKEYMODULE_ERR is returned and errno is set to the following values: + * On success REDISMODULE_OK is returned, otherwise + * REDISMODULE_ERR is returned and errno is set to the following values: * * * ERANGE: `fd` is negative or higher than `maxclients` Redis config. * * EINVAL: `mask` value is invalid. */ -int VM_EventLoopDel(int fd, int mask) { +int RM_EventLoopDel(int fd, int mask) { if (fd < 0 || fd >= aeGetSetSize(server.el)) { errno = ERANGE; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - if (mask & ~(VALKEYMODULE_EVENTLOOP_READABLE | - VALKEYMODULE_EVENTLOOP_WRITABLE)) { + if (mask & ~(REDISMODULE_EVENTLOOP_READABLE | + REDISMODULE_EVENTLOOP_WRITABLE)) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } /* After deleting the event, if fd does not have any registered event @@ -9345,17 +9447,17 @@ int VM_EventLoopDel(int fd, int mask) { zfree(data); errno = 0; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* This function can be called from other threads to trigger callback on Redis - * main thread. On success VALKEYMODULE_OK is returned. If `func` is NULL - * VALKEYMODULE_ERR is returned and errno is set to EINVAL. + * main thread. On success REDISMODULE_OK is returned. If `func` is NULL + * REDISMODULE_ERR is returned and errno is set to EINVAL. */ -int VM_EventLoopAddOneShot(ValkeyModuleEventLoopOneShotFunc func, void *user_data) { +int RM_EventLoopAddOneShot(RedisModuleEventLoopOneShotFunc func, void *user_data) { if (!func) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } EventLoopOneShot *oneshot = zmalloc(sizeof(*oneshot)); @@ -9372,7 +9474,7 @@ int VM_EventLoopAddOneShot(ValkeyModuleEventLoopOneShotFunc func, void *user_dat } errno = 0; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* This function will check the moduleEventLoopOneShots queue in order to @@ -9443,7 +9545,7 @@ void revokeClientAuthentication(client *c) { /* Cleanup all clients that have been authenticated with this module. This * is called from onUnload() to give the module a chance to cleanup any * resources associated with clients it has authenticated. */ -static void moduleFreeAuthenticatedClients(ValkeyModule *module) { +static void moduleFreeAuthenticatedClients(RedisModule *module) { listIter li; listNode *ln; listRewind(server.clients,&li); @@ -9451,7 +9553,7 @@ static void moduleFreeAuthenticatedClients(ValkeyModule *module) { client *c = listNodeValue(ln); if (!c->auth_module) continue; - ValkeyModule *auth_module = (ValkeyModule *) c->auth_module; + RedisModule *auth_module = (RedisModule *) c->auth_module; if (auth_module == module) { revokeClientAuthentication(c); } @@ -9460,9 +9562,9 @@ static void moduleFreeAuthenticatedClients(ValkeyModule *module) { /* Creates a Redis ACL user that the module can use to authenticate a client. * After obtaining the user, the module should set what such user can do - * using the VM_SetUserACL() function. Once configured, the user + * using the RM_SetUserACL() function. Once configured, the user * can be used in order to authenticate a connection, with the specified - * ACL rules, using the ValkeyModule_AuthClientWithUser() function. + * ACL rules, using the RedisModule_AuthClientWithUser() function. * * Note that: * @@ -9473,13 +9575,13 @@ static void moduleFreeAuthenticatedClients(ValkeyModule *module) { * * The created user can be used to authenticate multiple Redis connections. * * The caller can later free the user using the function - * VM_FreeModuleUser(). When this function is called, if there are + * RM_FreeModuleUser(). When this function is called, if there are * still clients authenticated with this user, they are disconnected. * The function to free the user should only be used when the caller really * wants to invalidate the user to define a new one with different * capabilities. */ -ValkeyModuleUser *VM_CreateModuleUser(const char *name) { - ValkeyModuleUser *new_user = zmalloc(sizeof(ValkeyModuleUser)); +RedisModuleUser *RM_CreateModuleUser(const char *name) { + RedisModuleUser *new_user = zmalloc(sizeof(RedisModuleUser)); new_user->user = ACLCreateUnlinkedUser(); new_user->free_user = 1; @@ -9490,33 +9592,33 @@ ValkeyModuleUser *VM_CreateModuleUser(const char *name) { } /* Frees a given user and disconnects all of the clients that have been - * authenticated with it. See VM_CreateModuleUser for detailed usage.*/ -int VM_FreeModuleUser(ValkeyModuleUser *user) { + * authenticated with it. See RM_CreateModuleUser for detailed usage.*/ +int RM_FreeModuleUser(RedisModuleUser *user) { if (user->free_user) ACLFreeUserAndKillClients(user->user); zfree(user); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Sets the permissions of a user created through the redis module * interface. The syntax is the same as ACL SETUSER, so refer to the - * documentation in acl.c for more information. See VM_CreateModuleUser + * documentation in acl.c for more information. See RM_CreateModuleUser * for detailed usage. * - * Returns VALKEYMODULE_OK on success and VALKEYMODULE_ERR on failure + * Returns REDISMODULE_OK on success and REDISMODULE_ERR on failure * and will set an errno describing why the operation failed. */ -int VM_SetModuleUserACL(ValkeyModuleUser *user, const char* acl) { +int RM_SetModuleUserACL(RedisModuleUser *user, const char* acl) { return ACLSetUser(user->user, acl, -1); } /* Sets the permission of a user with a complete ACL string, such as one * would use on the redis ACL SETUSER command line API. This differs from - * VM_SetModuleUserACL, which only takes single ACL operations at a time. + * RM_SetModuleUserACL, which only takes single ACL operations at a time. * - * Returns VALKEYMODULE_OK on success and VALKEYMODULE_ERR on failure - * if a ValkeyModuleString is provided in error, a string describing the error + * Returns REDISMODULE_OK on success and REDISMODULE_ERR on failure + * if a RedisModuleString is provided in error, a string describing the error * will be returned */ -int VM_SetModuleUserACLString(ValkeyModuleCtx *ctx, ValkeyModuleUser *user, const char *acl, ValkeyModuleString **error) { +int RM_SetModuleUserACLString(RedisModuleCtx *ctx, RedisModuleUser *user, const char *acl, RedisModuleString **error) { serverAssert(user != NULL); int argc; @@ -9529,55 +9631,55 @@ int VM_SetModuleUserACLString(ValkeyModuleCtx *ctx, ValkeyModuleUser *user, cons if (err) { if (error) { *error = createObject(OBJ_STRING, err); - if (ctx != NULL) autoMemoryAdd(ctx, VALKEYMODULE_AM_STRING, *error); + if (ctx != NULL) autoMemoryAdd(ctx, REDISMODULE_AM_STRING, *error); } else { sdsfree(err); } - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Get the ACL string for a given user - * Returns a ValkeyModuleString + * Returns a RedisModuleString */ -ValkeyModuleString *VM_GetModuleUserACLString(ValkeyModuleUser *user) { +RedisModuleString *RM_GetModuleUserACLString(RedisModuleUser *user) { serverAssert(user != NULL); return ACLDescribeUser(user->user); } /* Retrieve the user name of the client connection behind the current context. - * The user name can be used later, in order to get a ValkeyModuleUser. - * See more information in VM_GetModuleUserFromUserName. + * The user name can be used later, in order to get a RedisModuleUser. + * See more information in RM_GetModuleUserFromUserName. * - * The returned string must be released with ValkeyModule_FreeString() or by + * The returned string must be released with RedisModule_FreeString() or by * enabling automatic memory management. */ -ValkeyModuleString *VM_GetCurrentUserName(ValkeyModuleCtx *ctx) { - return VM_CreateString(ctx,ctx->client->user->name,sdslen(ctx->client->user->name)); +RedisModuleString *RM_GetCurrentUserName(RedisModuleCtx *ctx) { + return RM_CreateString(ctx,ctx->client->user->name,sdslen(ctx->client->user->name)); } -/* A ValkeyModuleUser can be used to check if command, key or channel can be executed or +/* A RedisModuleUser can be used to check if command, key or channel can be executed or * accessed according to the ACLs rules associated with that user. - * When a Module wants to do ACL checks on a general ACL user (not created by VM_CreateModuleUser), - * it can get the ValkeyModuleUser from this API, based on the user name retrieved by VM_GetCurrentUserName. + * When a Module wants to do ACL checks on a general ACL user (not created by RM_CreateModuleUser), + * it can get the RedisModuleUser from this API, based on the user name retrieved by RM_GetCurrentUserName. * - * Since a general ACL user can be deleted at any time, this ValkeyModuleUser should be used only in the context + * Since a general ACL user can be deleted at any time, this RedisModuleUser should be used only in the context * where this function was called. In order to do ACL checks out of that context, the Module can store the user name, * and call this API at any other context. * * Returns NULL if the user is disabled or the user does not exist. - * The caller should later free the user using the function VM_FreeModuleUser().*/ -ValkeyModuleUser *VM_GetModuleUserFromUserName(ValkeyModuleString *name) { + * The caller should later free the user using the function RM_FreeModuleUser().*/ +RedisModuleUser *RM_GetModuleUserFromUserName(RedisModuleString *name) { /* First, verify that the user exist */ user *acl_user = ACLGetUserByName(name->ptr, sdslen(name->ptr)); if (acl_user == NULL) { return NULL; } - ValkeyModuleUser *new_user = zmalloc(sizeof(ValkeyModuleUser)); + RedisModuleUser *new_user = zmalloc(sizeof(RedisModuleUser)); new_user->user = acl_user; new_user->free_user = 0; return new_user; @@ -9585,136 +9687,136 @@ ValkeyModuleUser *VM_GetModuleUserFromUserName(ValkeyModuleString *name) { /* Checks if the command can be executed by the user, according to the ACLs associated with it. * - * On success a VALKEYMODULE_OK is returned, otherwise - * VALKEYMODULE_ERR is returned and errno is set to the following values: + * On success a REDISMODULE_OK is returned, otherwise + * REDISMODULE_ERR is returned and errno is set to the following values: * * * ENOENT: Specified command does not exist. * * EACCES: Command cannot be executed, according to ACL rules */ -int VM_ACLCheckCommandPermissions(ValkeyModuleUser *user, ValkeyModuleString **argv, int argc) { +int RM_ACLCheckCommandPermissions(RedisModuleUser *user, RedisModuleString **argv, int argc) { int keyidxptr; struct redisCommand *cmd; /* Find command */ if ((cmd = lookupCommand(argv, argc)) == NULL) { errno = ENOENT; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } if (ACLCheckAllUserCommandPerm(user->user, cmd, argv, argc, &keyidxptr) != ACL_OK) { errno = EACCES; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Check if the key can be accessed by the user according to the ACLs attached to the user * and the flags representing the key access. The flags are the same that are used in the - * keyspec for logical operations. These flags are documented in ValkeyModule_SetCommandInfo as - * the VALKEYMODULE_CMD_KEY_ACCESS, VALKEYMODULE_CMD_KEY_UPDATE, VALKEYMODULE_CMD_KEY_INSERT, - * and VALKEYMODULE_CMD_KEY_DELETE flags. + * 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 VALKEYMODULE_OK is returned, otherwise - * VALKEYMODULE_ERR is returned and errno is set to one of the following values: + * 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. */ -int VM_ACLCheckKeyPermissions(ValkeyModuleUser *user, ValkeyModuleString *key, int flags) { - const int allow_mask = (VALKEYMODULE_CMD_KEY_ACCESS - | VALKEYMODULE_CMD_KEY_INSERT - | VALKEYMODULE_CMD_KEY_DELETE - | VALKEYMODULE_CMD_KEY_UPDATE); +int RM_ACLCheckKeyPermissions(RedisModuleUser *user, RedisModuleString *key, int flags) { + const int allow_mask = (REDISMODULE_CMD_KEY_ACCESS + | REDISMODULE_CMD_KEY_INSERT + | REDISMODULE_CMD_KEY_DELETE + | REDISMODULE_CMD_KEY_UPDATE); if ((flags & allow_mask) != flags) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } int keyspec_flags = moduleConvertKeySpecsFlags(flags, 0); if (ACLUserCheckKeyPerm(user->user, key->ptr, sdslen(key->ptr), keyspec_flags) != ACL_OK) { errno = EACCES; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Check if the pubsub channel can be accessed by the user based off of the given - * access flags. See VM_ChannelAtPosWithFlags for more information about the + * access flags. See RM_ChannelAtPosWithFlags for more information about the * possible flags that can be passed in. * - * If the user is able to access the pubsub channel then VALKEYMODULE_OK is returned, otherwise - * VALKEYMODULE_ERR is returned and errno is set to one of the following values: + * 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. */ -int VM_ACLCheckChannelPermissions(ValkeyModuleUser *user, ValkeyModuleString *ch, int flags) { - const int allow_mask = (VALKEYMODULE_CMD_CHANNEL_PUBLISH - | VALKEYMODULE_CMD_CHANNEL_SUBSCRIBE - | VALKEYMODULE_CMD_CHANNEL_UNSUBSCRIBE - | VALKEYMODULE_CMD_CHANNEL_PATTERN); +int RM_ACLCheckChannelPermissions(RedisModuleUser *user, RedisModuleString *ch, int flags) { + const int allow_mask = (REDISMODULE_CMD_CHANNEL_PUBLISH + | REDISMODULE_CMD_CHANNEL_SUBSCRIBE + | REDISMODULE_CMD_CHANNEL_UNSUBSCRIBE + | REDISMODULE_CMD_CHANNEL_PATTERN); if ((flags & allow_mask) != flags) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } /* Unsubscribe permissions are currently always allowed. */ - if (flags & VALKEYMODULE_CMD_CHANNEL_UNSUBSCRIBE){ - return VALKEYMODULE_OK; + if (flags & REDISMODULE_CMD_CHANNEL_UNSUBSCRIBE){ + return REDISMODULE_OK; } - int is_pattern = flags & VALKEYMODULE_CMD_CHANNEL_PATTERN; + int is_pattern = flags & REDISMODULE_CMD_CHANNEL_PATTERN; if (ACLUserCheckChannelPerm(user->user, ch->ptr, is_pattern) != ACL_OK) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* Helper function to map a ValkeyModuleACLLogEntryReason to ACL Log entry reason. */ -int moduleGetACLLogEntryReason(ValkeyModuleACLLogEntryReason reason) { +/* Helper function to map a RedisModuleACLLogEntryReason to ACL Log entry reason. */ +int moduleGetACLLogEntryReason(RedisModuleACLLogEntryReason reason) { int acl_reason = 0; switch (reason) { - case VALKEYMODULE_ACL_LOG_AUTH: acl_reason = ACL_DENIED_AUTH; break; - case VALKEYMODULE_ACL_LOG_KEY: acl_reason = ACL_DENIED_KEY; break; - case VALKEYMODULE_ACL_LOG_CHANNEL: acl_reason = ACL_DENIED_CHANNEL; break; - case VALKEYMODULE_ACL_LOG_CMD: acl_reason = ACL_DENIED_CMD; break; + case REDISMODULE_ACL_LOG_AUTH: acl_reason = ACL_DENIED_AUTH; break; + case REDISMODULE_ACL_LOG_KEY: acl_reason = ACL_DENIED_KEY; break; + case REDISMODULE_ACL_LOG_CHANNEL: acl_reason = ACL_DENIED_CHANNEL; break; + case REDISMODULE_ACL_LOG_CMD: acl_reason = ACL_DENIED_CMD; break; default: break; } return acl_reason; } /* Adds a new entry in the ACL log. - * Returns VALKEYMODULE_OK on success and VALKEYMODULE_ERR on error. + * Returns REDISMODULE_OK on success and REDISMODULE_ERR on error. * * For more information about ACL log, please refer to https://redis.io/commands/acl-log */ -int VM_ACLAddLogEntry(ValkeyModuleCtx *ctx, ValkeyModuleUser *user, ValkeyModuleString *object, ValkeyModuleACLLogEntryReason reason) { +int RM_ACLAddLogEntry(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleString *object, RedisModuleACLLogEntryReason reason) { int acl_reason = moduleGetACLLogEntryReason(reason); - if (!acl_reason) return VALKEYMODULE_ERR; + if (!acl_reason) return REDISMODULE_ERR; addACLLogEntry(ctx->client, acl_reason, ACL_LOG_CTX_MODULE, -1, user->user->name, sdsdup(object->ptr)); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* Adds a new entry in the ACL log with the `username` ValkeyModuleString provided. - * Returns VALKEYMODULE_OK on success and VALKEYMODULE_ERR on error. +/* Adds a new entry in the ACL log with the `username` RedisModuleString provided. + * Returns REDISMODULE_OK on success and REDISMODULE_ERR on error. * * For more information about ACL log, please refer to https://redis.io/commands/acl-log */ -int VM_ACLAddLogEntryByUserName(ValkeyModuleCtx *ctx, ValkeyModuleString *username, ValkeyModuleString *object, ValkeyModuleACLLogEntryReason reason) { +int RM_ACLAddLogEntryByUserName(RedisModuleCtx *ctx, RedisModuleString *username, RedisModuleString *object, RedisModuleACLLogEntryReason reason) { int acl_reason = moduleGetACLLogEntryReason(reason); - if (!acl_reason) return VALKEYMODULE_ERR; + if (!acl_reason) return REDISMODULE_ERR; addACLLogEntry(ctx->client, acl_reason, ACL_LOG_CTX_MODULE, -1, username->ptr, sdsdup(object->ptr)); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Authenticate the client associated with the context with - * the provided user. Returns VALKEYMODULE_OK on success and - * VALKEYMODULE_ERR on error. + * the provided user. Returns REDISMODULE_OK on success and + * REDISMODULE_ERR on error. * * This authentication can be tracked with the optional callback and private * data fields. The callback will be called whenever the user of the client @@ -9726,20 +9828,20 @@ int VM_ACLAddLogEntryByUserName(ValkeyModuleCtx *ctx, ValkeyModuleString *userna * * If client_id is not NULL, it will be filled with the id of the client * that was authenticated. This can be used with the - * VM_DeauthenticateAndCloseClient() API in order to deauthenticate a + * RM_DeauthenticateAndCloseClient() API in order to deauthenticate a * previously authenticated client if the authentication is no longer valid. * * For expensive authentication operations, it is recommended to block the * client and do the authentication in the background and then attach the user * to the client in a threadsafe context. */ -static int authenticateClientWithUser(ValkeyModuleCtx *ctx, user *user, ValkeyModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) { +static int authenticateClientWithUser(RedisModuleCtx *ctx, user *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) { if (user->flags & USER_FLAG_DISABLED) { - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } /* Avoid settings which are meaningless and will be lost */ if (!ctx->client || (ctx->client->flags & CLIENT_MODULE)) { - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } moduleNotifyUserChanged(ctx->client); @@ -9761,29 +9863,29 @@ static int authenticateClientWithUser(ValkeyModuleCtx *ctx, user *user, ValkeyMo *client_id = ctx->client->id; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Authenticate the current context's user with the provided redis acl user. - * Returns VALKEYMODULE_ERR if the user is disabled. + * Returns REDISMODULE_ERR if the user is disabled. * * See authenticateClientWithUser for information about callback, client_id, * and general usage for authentication. */ -int VM_AuthenticateClientWithUser(ValkeyModuleCtx *ctx, ValkeyModuleUser *module_user, ValkeyModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) { +int RM_AuthenticateClientWithUser(RedisModuleCtx *ctx, RedisModuleUser *module_user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) { return authenticateClientWithUser(ctx, module_user->user, callback, privdata, client_id); } /* Authenticate the current context's user with the provided redis acl user. - * Returns VALKEYMODULE_ERR if the user is disabled or the user does not exist. + * Returns REDISMODULE_ERR if the user is disabled or the user does not exist. * * See authenticateClientWithUser for information about callback, client_id, * and general usage for authentication. */ -int VM_AuthenticateClientWithACLUser(ValkeyModuleCtx *ctx, const char *name, size_t len, ValkeyModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) { +int RM_AuthenticateClientWithACLUser(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) { user *acl_user = ACLGetUserByName(name, len); if (!acl_user) { - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } return authenticateClientWithUser(ctx, acl_user, callback, privdata, client_id); } @@ -9791,23 +9893,23 @@ int VM_AuthenticateClientWithACLUser(ValkeyModuleCtx *ctx, const char *name, siz /* Deauthenticate and close the client. The client resources will not be * immediately freed, but will be cleaned up in a background job. This is * the recommended way to deauthenticate a client since most clients can't - * handle users becoming deauthenticated. Returns VALKEYMODULE_ERR when the - * client doesn't exist and VALKEYMODULE_OK when the operation was successful. + * handle users becoming deauthenticated. Returns REDISMODULE_ERR when the + * client doesn't exist and REDISMODULE_OK when the operation was successful. * - * The client ID is returned from the VM_AuthenticateClientWithUser and - * VM_AuthenticateClientWithACLUser APIs, but can be obtained through + * The client ID is returned from the RM_AuthenticateClientWithUser and + * RM_AuthenticateClientWithACLUser APIs, but can be obtained through * the CLIENT api or through server events. * * This function is not thread safe, and must be executed within the context * of a command or thread safe context. */ -int VM_DeauthenticateAndCloseClient(ValkeyModuleCtx *ctx, uint64_t client_id) { +int RM_DeauthenticateAndCloseClient(RedisModuleCtx *ctx, uint64_t client_id) { UNUSED(ctx); client *c = lookupClientByID(client_id); - if (c == NULL) return VALKEYMODULE_ERR; + if (c == NULL) return REDISMODULE_ERR; /* Revoke also marks client to be closed ASAP */ revokeClientAuthentication(c); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Redact the client command argument specified at the given position. Redacted arguments @@ -9817,21 +9919,21 @@ int VM_DeauthenticateAndCloseClient(ValkeyModuleCtx *ctx, uint64_t client_id) { * * Note that the command name, position 0, can not be redacted. * - * Returns VALKEYMODULE_OK if the argument was redacted and VALKEYMODULE_ERR if there + * 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 VM_RedactClientCommandArgument(ValkeyModuleCtx *ctx, int pos) { +int RM_RedactClientCommandArgument(RedisModuleCtx *ctx, int pos) { if (!ctx || !ctx->client || pos <= 0 || ctx->client->argc <= pos) { - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } redactClientCommandArgument(ctx->client, pos); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Return the X.509 client-side certificate used by the client to authenticate * this connection. * - * The return value is an allocated ValkeyModuleString that is a X.509 certificate + * The return value is an allocated RedisModuleString that is a X.509 certificate * encoded in PEM (Base64) format. It should be freed (or auto-freed) by the caller. * * A NULL value is returned in the following conditions: @@ -9840,15 +9942,15 @@ int VM_RedactClientCommandArgument(ValkeyModuleCtx *ctx, int pos) { * - Connection is not a TLS connection * - Connection is a TLS connection but no client certificate was used */ -ValkeyModuleString *VM_GetClientCertificate(ValkeyModuleCtx *ctx, uint64_t client_id) { +RedisModuleString *RM_GetClientCertificate(RedisModuleCtx *ctx, uint64_t client_id) { client *c = lookupClientByID(client_id); if (c == NULL) return NULL; sds cert = connGetPeerCert(c->conn); if (!cert) return NULL; - ValkeyModuleString *s = createObject(OBJ_STRING, cert); - if (ctx != NULL) autoMemoryAdd(ctx, VALKEYMODULE_AM_STRING, s); + RedisModuleString *s = createObject(OBJ_STRING, cert); + if (ctx != NULL) autoMemoryAdd(ctx, REDISMODULE_AM_STRING, s); return s; } @@ -9874,51 +9976,51 @@ ValkeyModuleString *VM_GetClientCertificate(ValkeyModuleCtx *ctx, uint64_t clien * reclaim the dictionary memory, as well as the strings returned by the * Next / Prev dictionary iterator calls. */ -ValkeyModuleDict *VM_CreateDict(ValkeyModuleCtx *ctx) { - struct ValkeyModuleDict *d = zmalloc(sizeof(*d)); +RedisModuleDict *RM_CreateDict(RedisModuleCtx *ctx) { + struct RedisModuleDict *d = zmalloc(sizeof(*d)); d->rax = raxNew(); - if (ctx != NULL) autoMemoryAdd(ctx,VALKEYMODULE_AM_DICT,d); + if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_DICT,d); return d; } -/* Free a dictionary created with VM_CreateDict(). You need to pass the +/* Free a dictionary created with RM_CreateDict(). You need to pass the * context pointer 'ctx' only if the dictionary was created using the * context instead of passing NULL. */ -void VM_FreeDict(ValkeyModuleCtx *ctx, ValkeyModuleDict *d) { - if (ctx != NULL) autoMemoryFreed(ctx,VALKEYMODULE_AM_DICT,d); +void RM_FreeDict(RedisModuleCtx *ctx, RedisModuleDict *d) { + if (ctx != NULL) autoMemoryFreed(ctx,REDISMODULE_AM_DICT,d); raxFree(d->rax); zfree(d); } /* Return the size of the dictionary (number of keys). */ -uint64_t VM_DictSize(ValkeyModuleDict *d) { +uint64_t RM_DictSize(RedisModuleDict *d) { return raxSize(d->rax); } /* Store the specified key into the dictionary, setting its value to the * pointer 'ptr'. If the key was added with success, since it did not - * already exist, VALKEYMODULE_OK is returned. Otherwise if the key already - * exists the function returns VALKEYMODULE_ERR. */ -int VM_DictSetC(ValkeyModuleDict *d, void *key, size_t keylen, void *ptr) { + * already exist, REDISMODULE_OK is returned. Otherwise if the key already + * exists the function returns REDISMODULE_ERR. */ +int RM_DictSetC(RedisModuleDict *d, void *key, size_t keylen, void *ptr) { int retval = raxTryInsert(d->rax,key,keylen,ptr,NULL); - return (retval == 1) ? VALKEYMODULE_OK : VALKEYMODULE_ERR; + return (retval == 1) ? REDISMODULE_OK : REDISMODULE_ERR; } -/* Like ValkeyModule_DictSetC() but will replace the key with the new +/* Like RedisModule_DictSetC() but will replace the key with the new * value if the key already exists. */ -int VM_DictReplaceC(ValkeyModuleDict *d, void *key, size_t keylen, void *ptr) { +int RM_DictReplaceC(RedisModuleDict *d, void *key, size_t keylen, void *ptr) { int retval = raxInsert(d->rax,key,keylen,ptr,NULL); - return (retval == 1) ? VALKEYMODULE_OK : VALKEYMODULE_ERR; + return (retval == 1) ? REDISMODULE_OK : REDISMODULE_ERR; } -/* Like ValkeyModule_DictSetC() but takes the key as a ValkeyModuleString. */ -int VM_DictSet(ValkeyModuleDict *d, ValkeyModuleString *key, void *ptr) { - return VM_DictSetC(d,key->ptr,sdslen(key->ptr),ptr); +/* Like RedisModule_DictSetC() but takes the key as a RedisModuleString. */ +int RM_DictSet(RedisModuleDict *d, RedisModuleString *key, void *ptr) { + return RM_DictSetC(d,key->ptr,sdslen(key->ptr),ptr); } -/* Like ValkeyModule_DictReplaceC() but takes the key as a ValkeyModuleString. */ -int VM_DictReplace(ValkeyModuleDict *d, ValkeyModuleString *key, void *ptr) { - return VM_DictReplaceC(d,key->ptr,sdslen(key->ptr),ptr); +/* Like RedisModule_DictReplaceC() but takes the key as a RedisModuleString. */ +int RM_DictReplace(RedisModuleDict *d, RedisModuleString *key, void *ptr) { + return RM_DictReplaceC(d,key->ptr,sdslen(key->ptr),ptr); } /* Return the value stored at the specified key. The function returns NULL @@ -9926,32 +10028,33 @@ int VM_DictReplace(ValkeyModuleDict *d, ValkeyModuleString *key, void *ptr) { * NULL at key. So, optionally, if the 'nokey' pointer is not NULL, it will * be set by reference to 1 if the key does not exist, or to 0 if the key * exists. */ -void *VM_DictGetC(ValkeyModuleDict *d, void *key, size_t keylen, int *nokey) { - void *res = raxFind(d->rax,key,keylen); - if (nokey) *nokey = (res == raxNotFound); - return (res == raxNotFound) ? NULL : res; +void *RM_DictGetC(RedisModuleDict *d, void *key, size_t keylen, int *nokey) { + void *res = NULL; + int found = raxFind(d->rax,key,keylen,&res); + if (nokey) *nokey = !found; + return res; } -/* Like ValkeyModule_DictGetC() but takes the key as a ValkeyModuleString. */ -void *VM_DictGet(ValkeyModuleDict *d, ValkeyModuleString *key, int *nokey) { - return VM_DictGetC(d,key->ptr,sdslen(key->ptr),nokey); +/* Like RedisModule_DictGetC() but takes the key as a RedisModuleString. */ +void *RM_DictGet(RedisModuleDict *d, RedisModuleString *key, int *nokey) { + return RM_DictGetC(d,key->ptr,sdslen(key->ptr),nokey); } -/* Remove the specified key from the dictionary, returning VALKEYMODULE_OK if - * the key was found and deleted, or VALKEYMODULE_ERR if instead there was +/* Remove the specified key from the dictionary, returning REDISMODULE_OK if + * the key was found and deleted, or REDISMODULE_ERR if instead there was * no such key in the dictionary. When the operation is successful, if * 'oldval' is not NULL, then '*oldval' is set to the value stored at the * key before it was deleted. Using this feature it is possible to get * a pointer to the value (for instance in order to release it), without - * having to call ValkeyModule_DictGet() before deleting the key. */ -int VM_DictDelC(ValkeyModuleDict *d, void *key, size_t keylen, void *oldval) { + * having to call RedisModule_DictGet() before deleting the key. */ +int RM_DictDelC(RedisModuleDict *d, void *key, size_t keylen, void *oldval) { int retval = raxRemove(d->rax,key,keylen,oldval); - return retval ? VALKEYMODULE_OK : VALKEYMODULE_ERR; + return retval ? REDISMODULE_OK : REDISMODULE_ERR; } -/* Like ValkeyModule_DictDelC() but gets the key as a ValkeyModuleString. */ -int VM_DictDel(ValkeyModuleDict *d, ValkeyModuleString *key, void *oldval) { - return VM_DictDelC(d,key->ptr,sdslen(key->ptr),oldval); +/* Like RedisModule_DictDelC() but gets the key as a RedisModuleString. */ +int RM_DictDel(RedisModuleDict *d, RedisModuleString *key, void *oldval) { + return RM_DictDelC(d,key->ptr,sdslen(key->ptr),oldval); } /* Return an iterator, setup in order to start iterating from the specified @@ -9971,45 +10074,45 @@ int VM_DictDel(ValkeyModuleDict *d, ValkeyModuleString *key, void *oldval) { * just pass NULL with a length of 0. * * If the element to start the iteration cannot be seeked based on the - * key and operator passed, ValkeyModule_DictNext() / Prev() will just return - * VALKEYMODULE_ERR at the first call, otherwise they'll produce elements. + * key and operator passed, RedisModule_DictNext() / Prev() will just return + * REDISMODULE_ERR at the first call, otherwise they'll produce elements. */ -ValkeyModuleDictIter *VM_DictIteratorStartC(ValkeyModuleDict *d, const char *op, void *key, size_t keylen) { - ValkeyModuleDictIter *di = zmalloc(sizeof(*di)); +RedisModuleDictIter *RM_DictIteratorStartC(RedisModuleDict *d, const char *op, void *key, size_t keylen) { + RedisModuleDictIter *di = zmalloc(sizeof(*di)); di->dict = d; raxStart(&di->ri,d->rax); raxSeek(&di->ri,op,key,keylen); return di; } -/* Exactly like ValkeyModule_DictIteratorStartC, but the key is passed as a - * ValkeyModuleString. */ -ValkeyModuleDictIter *VM_DictIteratorStart(ValkeyModuleDict *d, const char *op, ValkeyModuleString *key) { - return VM_DictIteratorStartC(d,op,key->ptr,sdslen(key->ptr)); +/* Exactly like RedisModule_DictIteratorStartC, but the key is passed as a + * RedisModuleString. */ +RedisModuleDictIter *RM_DictIteratorStart(RedisModuleDict *d, const char *op, RedisModuleString *key) { + return RM_DictIteratorStartC(d,op,key->ptr,sdslen(key->ptr)); } -/* Release the iterator created with ValkeyModule_DictIteratorStart(). This call +/* Release the iterator created with RedisModule_DictIteratorStart(). This call * is mandatory otherwise a memory leak is introduced in the module. */ -void VM_DictIteratorStop(ValkeyModuleDictIter *di) { +void RM_DictIteratorStop(RedisModuleDictIter *di) { raxStop(&di->ri); zfree(di); } -/* After its creation with ValkeyModule_DictIteratorStart(), it is possible to +/* After its creation with RedisModule_DictIteratorStart(), it is possible to * change the currently selected element of the iterator by using this * API call. The result based on the operator and key is exactly like - * the function ValkeyModule_DictIteratorStart(), however in this case the - * return value is just VALKEYMODULE_OK in case the seeked element was found, - * or VALKEYMODULE_ERR in case it was not possible to seek the specified + * the function RedisModule_DictIteratorStart(), however in this case the + * return value is just REDISMODULE_OK in case the seeked element was found, + * or REDISMODULE_ERR in case it was not possible to seek the specified * element. It is possible to reseek an iterator as many times as you want. */ -int VM_DictIteratorReseekC(ValkeyModuleDictIter *di, const char *op, void *key, size_t keylen) { +int RM_DictIteratorReseekC(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) { return raxSeek(&di->ri,op,key,keylen); } -/* Like ValkeyModule_DictIteratorReseekC() but takes the key as a - * ValkeyModuleString. */ -int VM_DictIteratorReseek(ValkeyModuleDictIter *di, const char *op, ValkeyModuleString *key) { - return VM_DictIteratorReseekC(di,op,key->ptr,sdslen(key->ptr)); +/* Like RedisModule_DictIteratorReseekC() but takes the key as a + * RedisModuleString. */ +int RM_DictIteratorReseek(RedisModuleDictIter *di, const char *op, RedisModuleString *key) { + return RM_DictIteratorReseekC(di,op,key->ptr,sdslen(key->ptr)); } /* Return the current item of the dictionary iterator `di` and steps to the @@ -10018,14 +10121,14 @@ int VM_DictIteratorReseek(ValkeyModuleDictIter *di, const char *op, ValkeyModule * to a string representing the key is provided, and the `*keylen` length * is set by reference (if keylen is not NULL). The `*dataptr`, if not NULL * is set to the value of the pointer stored at the returned key as auxiliary - * data (as set by the ValkeyModule_DictSet API). + * data (as set by the RedisModule_DictSet API). * * Usage example: * * ... create the iterator here ... * char *key; * void *data; - * while((key = ValkeyModule_DictNextC(iter,&keylen,&data)) != NULL) { + * while((key = RedisModule_DictNextC(iter,&keylen,&data)) != NULL) { * printf("%.*s %p\n", (int)keylen, key, data); * } * @@ -10037,71 +10140,71 @@ int VM_DictIteratorReseek(ValkeyModuleDictIter *di, const char *op, ValkeyModule * The validity of the returned pointer is until the next call to the * next/prev iterator step. Also the pointer is no longer valid once the * iterator is released. */ -void *VM_DictNextC(ValkeyModuleDictIter *di, size_t *keylen, void **dataptr) { +void *RM_DictNextC(RedisModuleDictIter *di, size_t *keylen, void **dataptr) { if (!raxNext(&di->ri)) return NULL; if (keylen) *keylen = di->ri.key_len; if (dataptr) *dataptr = di->ri.data; return di->ri.key; } -/* This function is exactly like ValkeyModule_DictNext() but after returning +/* This function is exactly like RedisModule_DictNext() but after returning * the currently selected element in the iterator, it selects the previous * element (lexicographically smaller) instead of the next one. */ -void *VM_DictPrevC(ValkeyModuleDictIter *di, size_t *keylen, void **dataptr) { +void *RM_DictPrevC(RedisModuleDictIter *di, size_t *keylen, void **dataptr) { if (!raxPrev(&di->ri)) return NULL; if (keylen) *keylen = di->ri.key_len; if (dataptr) *dataptr = di->ri.data; return di->ri.key; } -/* Like ValkeyModuleNextC(), but instead of returning an internally allocated +/* Like RedisModuleNextC(), but instead of returning an internally allocated * buffer and key length, it returns directly a module string object allocated * in the specified context 'ctx' (that may be NULL exactly like for the main - * API ValkeyModule_CreateString). + * API RedisModule_CreateString). * * The returned string object should be deallocated after use, either manually * or by using a context that has automatic memory management active. */ -ValkeyModuleString *VM_DictNext(ValkeyModuleCtx *ctx, ValkeyModuleDictIter *di, void **dataptr) { +RedisModuleString *RM_DictNext(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr) { size_t keylen; - void *key = VM_DictNextC(di,&keylen,dataptr); + void *key = RM_DictNextC(di,&keylen,dataptr); if (key == NULL) return NULL; - return VM_CreateString(ctx,key,keylen); + return RM_CreateString(ctx,key,keylen); } -/* Like ValkeyModule_DictNext() but after returning the currently selected +/* Like RedisModule_DictNext() but after returning the currently selected * element in the iterator, it selects the previous element (lexicographically * smaller) instead of the next one. */ -ValkeyModuleString *VM_DictPrev(ValkeyModuleCtx *ctx, ValkeyModuleDictIter *di, void **dataptr) { +RedisModuleString *RM_DictPrev(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr) { size_t keylen; - void *key = VM_DictPrevC(di,&keylen,dataptr); + void *key = RM_DictPrevC(di,&keylen,dataptr); if (key == NULL) return NULL; - return VM_CreateString(ctx,key,keylen); + return RM_CreateString(ctx,key,keylen); } /* Compare the element currently pointed by the iterator to the specified * element given by key/keylen, according to the operator 'op' (the set of - * valid operators are the same valid for ValkeyModule_DictIteratorStart). - * If the comparison is successful the command returns VALKEYMODULE_OK - * otherwise VALKEYMODULE_ERR is returned. + * valid operators are the same valid for RedisModule_DictIteratorStart). + * If the comparison is successful the command returns REDISMODULE_OK + * otherwise REDISMODULE_ERR is returned. * * This is useful when we want to just emit a lexicographical range, so * in the loop, as we iterate elements, we can also check if we are still * on range. * - * The function return VALKEYMODULE_ERR if the iterator reached the + * The function return REDISMODULE_ERR if the iterator reached the * end of elements condition as well. */ -int VM_DictCompareC(ValkeyModuleDictIter *di, const char *op, void *key, size_t keylen) { - if (raxEOF(&di->ri)) return VALKEYMODULE_ERR; +int RM_DictCompareC(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) { + if (raxEOF(&di->ri)) return REDISMODULE_ERR; int res = raxCompare(&di->ri,op,key,keylen); - return res ? VALKEYMODULE_OK : VALKEYMODULE_ERR; + return res ? REDISMODULE_OK : REDISMODULE_ERR; } -/* Like ValkeyModule_DictCompareC but gets the key to compare with the current - * iterator key as a ValkeyModuleString. */ -int VM_DictCompare(ValkeyModuleDictIter *di, const char *op, ValkeyModuleString *key) { - if (raxEOF(&di->ri)) return VALKEYMODULE_ERR; +/* Like RedisModule_DictCompareC but gets the key to compare with the current + * iterator key as a RedisModuleString. */ +int RM_DictCompare(RedisModuleDictIter *di, const char *op, RedisModuleString *key) { + if (raxEOF(&di->ri)) return REDISMODULE_ERR; int res = raxCompare(&di->ri,op,key->ptr,sdslen(key->ptr)); - return res ? VALKEYMODULE_OK : VALKEYMODULE_ERR; + return res ? REDISMODULE_OK : REDISMODULE_ERR; } @@ -10111,20 +10214,20 @@ int VM_DictCompare(ValkeyModuleDictIter *di, const char *op, ValkeyModuleString * ## Modules Info fields * -------------------------------------------------------------------------- */ -int VM_InfoEndDictField(ValkeyModuleInfoCtx *ctx); +int RM_InfoEndDictField(RedisModuleInfoCtx *ctx); /* Used to start a new section, before adding any fields. the section name will * be prefixed by `_` and must only include A-Z,a-z,0-9. * NULL or empty string indicates the default section (only ``) is used. - * When return value is VALKEYMODULE_ERR, the section should and will be skipped. */ -int VM_InfoAddSection(ValkeyModuleInfoCtx *ctx, const char *name) { + * When return value is REDISMODULE_ERR, the section should and will be skipped. */ +int RM_InfoAddSection(RedisModuleInfoCtx *ctx, const char *name) { sds full_name = sdsdup(ctx->module->name); if (name != NULL && strlen(name) > 0) full_name = sdscatfmt(full_name, "_%s", name); /* Implicitly end dicts, instead of returning an error which is likely un checked. */ if (ctx->in_dict_field) - VM_InfoEndDictField(ctx); + RM_InfoEndDictField(ctx); /* proceed only if: * 1) no section was requested (emit all) @@ -10136,25 +10239,25 @@ int VM_InfoAddSection(ValkeyModuleInfoCtx *ctx, const char *name) { { sdsfree(full_name); ctx->in_section = 0; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } } if (ctx->sections++) ctx->info = sdscat(ctx->info,"\r\n"); ctx->info = sdscatfmt(ctx->info, "# %S\r\n", full_name); ctx->in_section = 1; sdsfree(full_name); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Starts a dict field, similar to the ones in INFO KEYSPACE. Use normal - * ValkeyModule_InfoAddField* functions to add the items to this field, and - * terminate with ValkeyModule_InfoEndDictField. */ -int VM_InfoBeginDictField(ValkeyModuleInfoCtx *ctx, const char *name) { + * RedisModule_InfoAddField* functions to add the items to this field, and + * terminate with RedisModule_InfoEndDictField. */ +int RM_InfoBeginDictField(RedisModuleInfoCtx *ctx, const char *name) { if (!ctx->in_section) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; /* Implicitly end dicts, instead of returning an error which is likely un checked. */ if (ctx->in_dict_field) - VM_InfoEndDictField(ctx); + RM_InfoEndDictField(ctx); char *tmpmodname, *tmpname; ctx->info = sdscatfmt(ctx->info, "%s_%s:", @@ -10163,123 +10266,123 @@ int VM_InfoBeginDictField(ValkeyModuleInfoCtx *ctx, const char *name) { if (tmpmodname != NULL) zfree(tmpmodname); if (tmpname != NULL) zfree(tmpname); ctx->in_dict_field = 1; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* Ends a dict field, see ValkeyModule_InfoBeginDictField */ -int VM_InfoEndDictField(ValkeyModuleInfoCtx *ctx) { +/* Ends a dict field, see RedisModule_InfoBeginDictField */ +int RM_InfoEndDictField(RedisModuleInfoCtx *ctx) { if (!ctx->in_dict_field) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; /* trim the last ',' if found. */ if (ctx->info[sdslen(ctx->info)-1]==',') sdsIncrLen(ctx->info, -1); ctx->info = sdscat(ctx->info, "\r\n"); ctx->in_dict_field = 0; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* Used by ValkeyModuleInfoFunc to add info fields. +/* Used by RedisModuleInfoFunc to add info fields. * Each field will be automatically prefixed by `_`. * Field names or values must not include `\r\n` or `:`. */ -int VM_InfoAddFieldString(ValkeyModuleInfoCtx *ctx, const char *field, ValkeyModuleString *value) { +int RM_InfoAddFieldString(RedisModuleInfoCtx *ctx, const char *field, RedisModuleString *value) { if (!ctx->in_section) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; if (ctx->in_dict_field) { ctx->info = sdscatfmt(ctx->info, "%s=%S,", field, (sds)value->ptr); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } ctx->info = sdscatfmt(ctx->info, "%s_%s:%S\r\n", ctx->module->name, field, (sds)value->ptr); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* See ValkeyModule_InfoAddFieldString(). */ -int VM_InfoAddFieldCString(ValkeyModuleInfoCtx *ctx, const char *field, const char *value) { +/* See RedisModule_InfoAddFieldString(). */ +int RM_InfoAddFieldCString(RedisModuleInfoCtx *ctx, const char *field, const char *value) { if (!ctx->in_section) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; if (ctx->in_dict_field) { ctx->info = sdscatfmt(ctx->info, "%s=%s,", field, value); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } ctx->info = sdscatfmt(ctx->info, "%s_%s:%s\r\n", ctx->module->name, field, value); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* See ValkeyModule_InfoAddFieldString(). */ -int VM_InfoAddFieldDouble(ValkeyModuleInfoCtx *ctx, const char *field, double value) { +/* See RedisModule_InfoAddFieldString(). */ +int RM_InfoAddFieldDouble(RedisModuleInfoCtx *ctx, const char *field, double value) { if (!ctx->in_section) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; if (ctx->in_dict_field) { ctx->info = sdscatprintf(ctx->info, "%s=%.17g,", field, value); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } ctx->info = sdscatprintf(ctx->info, "%s_%s:%.17g\r\n", ctx->module->name, field, value); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* See ValkeyModule_InfoAddFieldString(). */ -int VM_InfoAddFieldLongLong(ValkeyModuleInfoCtx *ctx, const char *field, long long value) { +/* See RedisModule_InfoAddFieldString(). */ +int RM_InfoAddFieldLongLong(RedisModuleInfoCtx *ctx, const char *field, long long value) { if (!ctx->in_section) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; if (ctx->in_dict_field) { ctx->info = sdscatfmt(ctx->info, "%s=%I,", field, value); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } ctx->info = sdscatfmt(ctx->info, "%s_%s:%I\r\n", ctx->module->name, field, value); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* See ValkeyModule_InfoAddFieldString(). */ -int VM_InfoAddFieldULongLong(ValkeyModuleInfoCtx *ctx, const char *field, unsigned long long value) { +/* See RedisModule_InfoAddFieldString(). */ +int RM_InfoAddFieldULongLong(RedisModuleInfoCtx *ctx, const char *field, unsigned long long value) { if (!ctx->in_section) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; if (ctx->in_dict_field) { ctx->info = sdscatfmt(ctx->info, "%s=%U,", field, value); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } ctx->info = sdscatfmt(ctx->info, "%s_%s:%U\r\n", ctx->module->name, field, value); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Registers callback for the INFO command. The callback should add INFO fields - * by calling the `ValkeyModule_InfoAddField*()` functions. */ -int VM_RegisterInfoFunc(ValkeyModuleCtx *ctx, ValkeyModuleInfoFunc cb) { + * by calling the `RedisModule_InfoAddField*()` functions. */ +int RM_RegisterInfoFunc(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) { ctx->module->info_cb = cb; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } sds modulesCollectInfo(sds info, dict *sections_dict, int for_crash_report, int sections) { @@ -10287,14 +10390,14 @@ sds modulesCollectInfo(sds info, dict *sections_dict, int for_crash_report, int dictEntry *de; while ((de = dictNext(di)) != NULL) { - struct ValkeyModule *module = dictGetVal(de); + struct RedisModule *module = dictGetVal(de); if (!module->info_cb) continue; - ValkeyModuleInfoCtx info_ctx = {module, sections_dict, info, sections, 0, 0}; + RedisModuleInfoCtx info_ctx = {module, sections_dict, info, sections, 0, 0}; module->info_cb(&info_ctx, for_crash_report); /* Implicitly end dicts (no way to handle errors, and we must add the newline). */ if (info_ctx.in_dict_field) - VM_InfoEndDictField(&info_ctx); + RM_InfoEndDictField(&info_ctx); info = info_ctx.info; sections = info_ctx.sections; } @@ -10305,13 +10408,13 @@ sds modulesCollectInfo(sds info, dict *sections_dict, int for_crash_report, int /* Get information about the server similar to the one that returns from the * INFO command. This function takes an optional 'section' argument that may * be NULL. The return value holds the output and can be used with - * ValkeyModule_ServerInfoGetField and alike to get the individual fields. - * When done, it needs to be freed with ValkeyModule_FreeServerInfo or with the + * RedisModule_ServerInfoGetField and alike to get the individual fields. + * When done, it needs to be freed with RedisModule_FreeServerInfo or with the * automatic memory management mechanism if enabled. */ -ValkeyModuleServerInfoData *VM_GetServerInfo(ValkeyModuleCtx *ctx, const char *section) { - struct ValkeyModuleServerInfoData *d = zmalloc(sizeof(*d)); +RedisModuleServerInfoData *RM_GetServerInfo(RedisModuleCtx *ctx, const char *section) { + struct RedisModuleServerInfoData *d = zmalloc(sizeof(*d)); d->rax = raxNew(); - if (ctx != NULL) autoMemoryAdd(ctx,VALKEYMODULE_AM_INFO,d); + if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_INFO,d); int all = 0, everything = 0; robj *argv[1]; argv[0] = section ? createStringObject(section, strlen(section)) : NULL; @@ -10337,85 +10440,90 @@ ValkeyModuleServerInfoData *VM_GetServerInfo(ValkeyModuleCtx *ctx, const char *s return d; } -/* Free data created with VM_GetServerInfo(). You need to pass the +/* Free data created with RM_GetServerInfo(). You need to pass the * context pointer 'ctx' only if the dictionary was created using the * context instead of passing NULL. */ -void VM_FreeServerInfo(ValkeyModuleCtx *ctx, ValkeyModuleServerInfoData *data) { - if (ctx != NULL) autoMemoryFreed(ctx,VALKEYMODULE_AM_INFO,data); +void RM_FreeServerInfo(RedisModuleCtx *ctx, RedisModuleServerInfoData *data) { + if (ctx != NULL) autoMemoryFreed(ctx,REDISMODULE_AM_INFO,data); raxFreeWithCallback(data->rax, (void(*)(void*))sdsfree); zfree(data); } -/* Get the value of a field from data collected with VM_GetServerInfo(). You +/* Get the value of a field from data collected with RM_GetServerInfo(). You * need to pass the context pointer 'ctx' only if you want to use auto memory * mechanism to release the returned string. Return value will be NULL if the * field was not found. */ -ValkeyModuleString *VM_ServerInfoGetField(ValkeyModuleCtx *ctx, ValkeyModuleServerInfoData *data, const char* field) { - sds val = raxFind(data->rax, (unsigned char *)field, strlen(field)); - if (val == raxNotFound) return NULL; - ValkeyModuleString *o = createStringObject(val,sdslen(val)); - if (ctx != NULL) autoMemoryAdd(ctx,VALKEYMODULE_AM_STRING,o); +RedisModuleString *RM_ServerInfoGetField(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field) { + void *result; + if (!raxFind(data->rax, (unsigned char *)field, strlen(field), &result)) + return NULL; + sds val = result; + RedisModuleString *o = createStringObject(val,sdslen(val)); + if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o); return o; } -/* Similar to VM_ServerInfoGetField, but returns a char* which should not be freed but the caller. */ -const char *VM_ServerInfoGetFieldC(ValkeyModuleServerInfoData *data, const char* field) { - sds val = raxFind(data->rax, (unsigned char *)field, strlen(field)); - if (val == raxNotFound) return NULL; - return val; +/* Similar to RM_ServerInfoGetField, but returns a char* which should not be freed but the caller. */ +const char *RM_ServerInfoGetFieldC(RedisModuleServerInfoData *data, const char* field) { + void *result = NULL; + raxFind(data->rax, (unsigned char *)field, strlen(field), &result); + return result; } -/* Get the value of a field from data collected with VM_GetServerInfo(). If the +/* Get the value of a field from data collected with RM_GetServerInfo(). If the * field is not found, or is not numerical or out of range, return value will be - * 0, and the optional out_err argument will be set to VALKEYMODULE_ERR. */ -long long VM_ServerInfoGetFieldSigned(ValkeyModuleServerInfoData *data, const char* field, int *out_err) { + * 0, and the optional out_err argument will be set to REDISMODULE_ERR. */ +long long RM_ServerInfoGetFieldSigned(RedisModuleServerInfoData *data, const char* field, int *out_err) { long long ll; - sds val = raxFind(data->rax, (unsigned char *)field, strlen(field)); - if (val == raxNotFound) { - if (out_err) *out_err = VALKEYMODULE_ERR; + void *result; + if (!raxFind(data->rax, (unsigned char *)field, strlen(field), &result)) { + if (out_err) *out_err = REDISMODULE_ERR; return 0; } + sds val = result; if (!string2ll(val,sdslen(val),&ll)) { - if (out_err) *out_err = VALKEYMODULE_ERR; + if (out_err) *out_err = REDISMODULE_ERR; return 0; } - if (out_err) *out_err = VALKEYMODULE_OK; + if (out_err) *out_err = REDISMODULE_OK; return ll; } -/* Get the value of a field from data collected with VM_GetServerInfo(). If the +/* Get the value of a field from data collected with RM_GetServerInfo(). If the * field is not found, or is not numerical or out of range, return value will be - * 0, and the optional out_err argument will be set to VALKEYMODULE_ERR. */ -unsigned long long VM_ServerInfoGetFieldUnsigned(ValkeyModuleServerInfoData *data, const char* field, int *out_err) { + * 0, and the optional out_err argument will be set to REDISMODULE_ERR. */ +unsigned long long RM_ServerInfoGetFieldUnsigned(RedisModuleServerInfoData *data, const char* field, int *out_err) { unsigned long long ll; - sds val = raxFind(data->rax, (unsigned char *)field, strlen(field)); - if (val == raxNotFound) { - if (out_err) *out_err = VALKEYMODULE_ERR; + void *result; + if (!raxFind(data->rax, (unsigned char *)field, strlen(field), &result)) { + if (out_err) *out_err = REDISMODULE_ERR; return 0; } + sds val = result; if (!string2ull(val,&ll)) { - if (out_err) *out_err = VALKEYMODULE_ERR; + if (out_err) *out_err = REDISMODULE_ERR; return 0; } - if (out_err) *out_err = VALKEYMODULE_OK; + if (out_err) *out_err = REDISMODULE_OK; return ll; } -/* Get the value of a field from data collected with VM_GetServerInfo(). If the +/* Get the value of a field from data collected with RM_GetServerInfo(). If the * field is not found, or is not a double, return value will be 0, and the - * optional out_err argument will be set to VALKEYMODULE_ERR. */ -double VM_ServerInfoGetFieldDouble(ValkeyModuleServerInfoData *data, const char* field, int *out_err) { + * optional out_err argument will be set to REDISMODULE_ERR. */ +double RM_ServerInfoGetFieldDouble(RedisModuleServerInfoData *data, const char* field, int *out_err) { double dbl; - sds val = raxFind(data->rax, (unsigned char *)field, strlen(field)); - if (val == raxNotFound) { - if (out_err) *out_err = VALKEYMODULE_ERR; + void *result; + if (!raxFind(data->rax, (unsigned char *)field, strlen(field), &result)) { + if (out_err) *out_err = REDISMODULE_ERR; return 0; } + sds val = result; if (!string2d(val,sdslen(val),&dbl)) { - if (out_err) *out_err = VALKEYMODULE_ERR; + if (out_err) *out_err = REDISMODULE_ERR; return 0; } - if (out_err) *out_err = VALKEYMODULE_OK; + if (out_err) *out_err = REDISMODULE_OK; return dbl; } @@ -10427,14 +10535,14 @@ double VM_ServerInfoGetFieldDouble(ValkeyModuleServerInfoData *data, const char* * initialized seed. This function is fast so can be used to generate * many bytes without any effect on the operating system entropy pool. * Currently this function is not thread safe. */ -void VM_GetRandomBytes(unsigned char *dst, size_t len) { +void RM_GetRandomBytes(unsigned char *dst, size_t len) { getRandomBytes(dst,len); } -/* Like ValkeyModule_GetRandomBytes() but instead of setting the string to +/* Like RedisModule_GetRandomBytes() but instead of setting the string to * random bytes the string is set to random characters in the in the * hex charset [0-9a-f]. */ -void VM_GetRandomHexChars(char *dst, size_t len) { +void RM_GetRandomHexChars(char *dst, size_t len) { getRandomHexChars(dst,len); } @@ -10444,25 +10552,25 @@ void VM_GetRandomHexChars(char *dst, size_t len) { /* This function is called by a module in order to export some API with a * given name. Other modules will be able to use this API by calling the - * symmetrical function VM_GetSharedAPI() and casting the return value to + * symmetrical function RM_GetSharedAPI() and casting the return value to * the right function pointer. * - * The function will return VALKEYMODULE_OK if the name is not already taken, - * otherwise VALKEYMODULE_ERR will be returned and no operation will be + * The function will return REDISMODULE_OK if the name is not already taken, + * otherwise REDISMODULE_ERR will be returned and no operation will be * performed. * * IMPORTANT: the apiname argument should be a string literal with static * lifetime. The API relies on the fact that it will always be valid in * the future. */ -int VM_ExportSharedAPI(ValkeyModuleCtx *ctx, const char *apiname, void *func) { - ValkeyModuleSharedAPI *sapi = zmalloc(sizeof(*sapi)); +int RM_ExportSharedAPI(RedisModuleCtx *ctx, const char *apiname, void *func) { + RedisModuleSharedAPI *sapi = zmalloc(sizeof(*sapi)); sapi->module = ctx->module; sapi->func = func; if (dictAdd(server.sharedapi, (char*)apiname, sapi) != DICT_OK) { zfree(sapi); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Request an exported API pointer. The return value is just a void pointer @@ -10492,16 +10600,16 @@ int VM_ExportSharedAPI(ValkeyModuleCtx *ctx, const char *apiname, void *func) { * static int api_loaded = 0; * if (api_loaded != 0) return 1; // APIs already resolved. * - * myFunctionPointer = ValkeyModule_GetSharedAPI("..."); + * myFunctionPointer = RedisModule_GetSharedAPI("..."); * if (myFunctionPointer == NULL) return 0; * * return 1; * } */ -void *VM_GetSharedAPI(ValkeyModuleCtx *ctx, const char *apiname) { +void *RM_GetSharedAPI(RedisModuleCtx *ctx, const char *apiname) { dictEntry *de = dictFind(server.sharedapi, apiname); if (de == NULL) return NULL; - ValkeyModuleSharedAPI *sapi = dictGetVal(de); + RedisModuleSharedAPI *sapi = dictGetVal(de); if (listSearchKey(sapi->module->usedby,ctx->module) == NULL) { listAddNodeTail(sapi->module->usedby,ctx->module); listAddNodeTail(ctx->module->using,sapi->module); @@ -10515,13 +10623,13 @@ void *VM_GetSharedAPI(ValkeyModuleCtx *ctx, const char *apiname) { * used by other modules. * * The number of unregistered APIs is returned. */ -int moduleUnregisterSharedAPI(ValkeyModule *module) { +int moduleUnregisterSharedAPI(RedisModule *module) { int count = 0; dictIterator *di = dictGetSafeIterator(server.sharedapi); dictEntry *de; while ((de = dictNext(di)) != NULL) { const char *apiname = dictGetKey(de); - ValkeyModuleSharedAPI *sapi = dictGetVal(de); + RedisModuleSharedAPI *sapi = dictGetVal(de); if (sapi->module == module) { dictDelete(server.sharedapi,apiname); zfree(sapi); @@ -10536,14 +10644,14 @@ int moduleUnregisterSharedAPI(ValkeyModule *module) { * This is usually called when a module is unloaded. * * Returns the number of modules this module was using APIs from. */ -int moduleUnregisterUsedAPI(ValkeyModule *module) { +int moduleUnregisterUsedAPI(RedisModule *module) { listIter li; listNode *ln; int count = 0; listRewind(module->using,&li); while((ln = listNext(&li))) { - ValkeyModule *used = ln->value; + RedisModule *used = ln->value; listNode *ln = listSearchKey(used->usedby,module); if (ln) { listDelNode(used->usedby,ln); @@ -10557,14 +10665,14 @@ int moduleUnregisterUsedAPI(ValkeyModule *module) { * This is called when a module is being unloaded. * * Returns the number of filters unregistered. */ -int moduleUnregisterFilters(ValkeyModule *module) { +int moduleUnregisterFilters(RedisModule *module) { listIter li; listNode *ln; int count = 0; listRewind(module->filters,&li); while((ln = listNext(&li))) { - ValkeyModuleCommandFilter *filter = ln->value; + RedisModuleCommandFilter *filter = ln->value; listNode *ln = listSearchKey(moduleCommandFilters,filter); if (ln) { listDelNode(moduleCommandFilters,ln); @@ -10589,18 +10697,18 @@ int moduleUnregisterFilters(ValkeyModule *module) { * filter applies in all execution paths including: * * 1. Invocation by a client. - * 2. Invocation through `ValkeyModule_Call()` by any module. + * 2. Invocation through `RedisModule_Call()` by any module. * 3. Invocation through Lua `redis.call()`. * 4. Replication of a command from a master. * * The filter executes in a special filter context, which is different and more - * limited than a ValkeyModuleCtx. Because the filter affects any command, it + * limited than a RedisModuleCtx. Because the filter affects any command, it * must be implemented in a very efficient way to reduce the performance impact * on Redis. All Redis Module API calls that require a valid context (such as - * `ValkeyModule_Call()`, `ValkeyModule_OpenKey()`, etc.) are not supported in a + * `RedisModule_Call()`, `RedisModule_OpenKey()`, etc.) are not supported in a * filter context. * - * The `ValkeyModuleCommandFilterCtx` can be used to inspect or modify the + * The `RedisModuleCommandFilterCtx` can be used to inspect or modify the * executed command and its arguments. As the filter executes before Redis * begins processing the command, any change will affect the way the command is * processed. For example, a module can override Redis commands this way: @@ -10614,12 +10722,12 @@ int moduleUnregisterFilters(ValkeyModule *module) { * and therefore executes the module's own command. * * Note that in the above use case, if `MODULE.SET` itself uses - * `ValkeyModule_Call()` the filter will be applied on that call as well. If - * that is not desired, the `VALKEYMODULE_CMDFILTER_NOSELF` flag can be set when + * `RedisModule_Call()` the filter will be applied on that call as well. If + * that is not desired, the `REDISMODULE_CMDFILTER_NOSELF` flag can be set when * registering the filter. * - * The `VALKEYMODULE_CMDFILTER_NOSELF` flag prevents execution flows that - * originate from the module's own `VM_Call()` from reaching the filter. This + * The `REDISMODULE_CMDFILTER_NOSELF` flag prevents execution flows that + * originate from the module's own `RM_Call()` from reaching the filter. This * flag is effective for all execution flows, including nested ones, as long as * the execution begins from the module's command context or a thread-safe * context that is associated with a blocking command. @@ -10630,8 +10738,8 @@ int moduleUnregisterFilters(ValkeyModule *module) { * If multiple filters are registered (by the same or different modules), they * are executed in the order of registration. */ -ValkeyModuleCommandFilter *VM_RegisterCommandFilter(ValkeyModuleCtx *ctx, ValkeyModuleCommandFilterFunc callback, int flags) { - ValkeyModuleCommandFilter *filter = zmalloc(sizeof(*filter)); +RedisModuleCommandFilter *RM_RegisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc callback, int flags) { + RedisModuleCommandFilter *filter = zmalloc(sizeof(*filter)); filter->module = ctx->module; filter->callback = callback; filter->flags = flags; @@ -10643,23 +10751,23 @@ ValkeyModuleCommandFilter *VM_RegisterCommandFilter(ValkeyModuleCtx *ctx, Valkey /* Unregister a command filter. */ -int VM_UnregisterCommandFilter(ValkeyModuleCtx *ctx, ValkeyModuleCommandFilter *filter) { +int RM_UnregisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter) { listNode *ln; /* A module can only remove its own filters */ - if (filter->module != ctx->module) return VALKEYMODULE_ERR; + if (filter->module != ctx->module) return REDISMODULE_ERR; ln = listSearchKey(moduleCommandFilters,filter); - if (!ln) return VALKEYMODULE_ERR; + if (!ln) return REDISMODULE_ERR; listDelNode(moduleCommandFilters,ln); ln = listSearchKey(ctx->module->filters,filter); - if (!ln) return VALKEYMODULE_ERR; /* Shouldn't happen */ + if (!ln) return REDISMODULE_ERR; /* Shouldn't happen */ listDelNode(ctx->module->filters,ln); zfree(filter); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } void moduleCallCommandFilters(client *c) { @@ -10669,7 +10777,7 @@ void moduleCallCommandFilters(client *c) { listNode *ln; listRewind(moduleCommandFilters,&li); - ValkeyModuleCommandFilterCtx filter = { + RedisModuleCommandFilterCtx filter = { .argv = c->argv, .argv_len = c->argv_len, .argc = c->argc, @@ -10677,12 +10785,12 @@ void moduleCallCommandFilters(client *c) { }; while((ln = listNext(&li))) { - ValkeyModuleCommandFilter *f = ln->value; + RedisModuleCommandFilter *f = ln->value; - /* Skip filter if VALKEYMODULE_CMDFILTER_NOSELF is set and module is + /* Skip filter if REDISMODULE_CMDFILTER_NOSELF is set and module is * currently processing a command. */ - if ((f->flags & VALKEYMODULE_CMDFILTER_NOSELF) && f->module->in_call) continue; + if ((f->flags & REDISMODULE_CMDFILTER_NOSELF) && f->module->in_call) continue; /* Call filter */ f->callback(&filter); @@ -10696,7 +10804,7 @@ void moduleCallCommandFilters(client *c) { /* Return the number of arguments a filtered command has. The number of * arguments include the command itself. */ -int VM_CommandFilterArgsCount(ValkeyModuleCommandFilterCtx *fctx) +int RM_CommandFilterArgsCount(RedisModuleCommandFilterCtx *fctx) { return fctx->argc; } @@ -10704,26 +10812,26 @@ int VM_CommandFilterArgsCount(ValkeyModuleCommandFilterCtx *fctx) /* Return the specified command argument. The first argument (position 0) is * the command itself, and the rest are user-provided args. */ -ValkeyModuleString *VM_CommandFilterArgGet(ValkeyModuleCommandFilterCtx *fctx, int pos) +RedisModuleString *RM_CommandFilterArgGet(RedisModuleCommandFilterCtx *fctx, int pos) { if (pos < 0 || pos >= fctx->argc) return NULL; return fctx->argv[pos]; } /* Modify the filtered command by inserting a new argument at the specified - * position. The specified ValkeyModuleString argument may be used by Redis + * position. The specified RedisModuleString argument may be used by Redis * after the filter context is destroyed, so it must not be auto-memory * allocated, freed or used elsewhere. */ -int VM_CommandFilterArgInsert(ValkeyModuleCommandFilterCtx *fctx, int pos, ValkeyModuleString *arg) +int RM_CommandFilterArgInsert(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) { int i; - if (pos < 0 || pos > fctx->argc) return VALKEYMODULE_ERR; + if (pos < 0 || pos > fctx->argc) return REDISMODULE_ERR; if (fctx->argv_len < fctx->argc+1) { fctx->argv_len = fctx->argc+1; - fctx->argv = zrealloc(fctx->argv, fctx->argv_len*sizeof(ValkeyModuleString *)); + fctx->argv = zrealloc(fctx->argv, fctx->argv_len*sizeof(RedisModuleString *)); } for (i = fctx->argc; i > pos; i--) { fctx->argv[i] = fctx->argv[i-1]; @@ -10731,31 +10839,31 @@ int VM_CommandFilterArgInsert(ValkeyModuleCommandFilterCtx *fctx, int pos, Valke fctx->argv[pos] = arg; fctx->argc++; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Modify the filtered command by replacing an existing argument with a new one. - * The specified ValkeyModuleString argument may be used by Redis after the + * The specified RedisModuleString argument may be used by Redis after the * filter context is destroyed, so it must not be auto-memory allocated, freed * or used elsewhere. */ -int VM_CommandFilterArgReplace(ValkeyModuleCommandFilterCtx *fctx, int pos, ValkeyModuleString *arg) +int RM_CommandFilterArgReplace(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) { - if (pos < 0 || pos >= fctx->argc) return VALKEYMODULE_ERR; + if (pos < 0 || pos >= fctx->argc) return REDISMODULE_ERR; decrRefCount(fctx->argv[pos]); fctx->argv[pos] = arg; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Modify the filtered command by deleting an argument at the specified * position. */ -int VM_CommandFilterArgDelete(ValkeyModuleCommandFilterCtx *fctx, int pos) +int RM_CommandFilterArgDelete(RedisModuleCommandFilterCtx *fctx, int pos) { int i; - if (pos < 0 || pos >= fctx->argc) return VALKEYMODULE_ERR; + if (pos < 0 || pos >= fctx->argc) return REDISMODULE_ERR; decrRefCount(fctx->argv[pos]); for (i = pos; i < fctx->argc-1; i++) { @@ -10763,46 +10871,46 @@ int VM_CommandFilterArgDelete(ValkeyModuleCommandFilterCtx *fctx, int pos) } fctx->argc--; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Get Client ID for client that issued the command we are filtering */ -unsigned long long VM_CommandFilterGetClientId(ValkeyModuleCommandFilterCtx *fctx) { +unsigned long long RM_CommandFilterGetClientId(RedisModuleCommandFilterCtx *fctx) { return fctx->c->id; } -/* For a given pointer allocated via ValkeyModule_Alloc() or - * ValkeyModule_Realloc(), return the amount of memory allocated for it. +/* For a given pointer allocated via RedisModule_Alloc() or + * RedisModule_Realloc(), return the amount of memory allocated for it. * Note that this may be different (larger) than the memory we allocated * with the allocation calls, since sometimes the underlying allocator * will allocate more memory. */ -size_t VM_MallocSize(void* ptr) { +size_t RM_MallocSize(void* ptr) { return zmalloc_size(ptr); } -/* Similar to VM_MallocSize, the difference is that VM_MallocUsableSize +/* Similar to RM_MallocSize, the difference is that RM_MallocUsableSize * returns the usable size of memory by the module. */ -size_t VM_MallocUsableSize(void *ptr) { +size_t RM_MallocUsableSize(void *ptr) { /* It is safe to use 'zmalloc_usable_size()' to manipulate additional * memory space, as we guarantee that the compiler can recognize this - * after 'VM_Alloc', 'VM_TryAlloc', 'VM_Realloc', or 'VM_Calloc'. */ + * after 'RM_Alloc', 'RM_TryAlloc', 'RM_Realloc', or 'RM_Calloc'. */ return zmalloc_usable_size(ptr); } -/* Same as VM_MallocSize, except it works on ValkeyModuleString pointers. +/* Same as RM_MallocSize, except it works on RedisModuleString pointers. */ -size_t VM_MallocSizeString(ValkeyModuleString* str) { +size_t RM_MallocSizeString(RedisModuleString* str) { serverAssert(str->type == OBJ_STRING); return sizeof(*str) + getStringObjectSdsUsedMemory(str); } -/* Same as VM_MallocSize, except it works on ValkeyModuleDict pointers. +/* Same as RM_MallocSize, except it works on RedisModuleDict pointers. * Note that the returned value is only the overhead of the underlying structures, * it does not include the allocation size of the keys and values. */ -size_t VM_MallocSizeDict(ValkeyModuleDict* dict) { - size_t size = sizeof(ValkeyModuleDict) + sizeof(rax); +size_t RM_MallocSizeDict(RedisModuleDict* dict) { + size_t size = sizeof(RedisModuleDict) + sizeof(rax); size += dict->rax->numnodes * sizeof(raxNode); /* For more info about this weird line, see streamRadixTreeMemoryUsage */ size += dict->rax->numnodes * sizeof(long)*30; @@ -10817,7 +10925,7 @@ size_t VM_MallocSizeDict(ValkeyModuleDict* dict) { * * Exactly 1 - Memory limit reached. * * Greater 1 - More memory used than the configured limit. */ -float VM_GetUsedMemoryRatio(void){ +float RM_GetUsedMemoryRatio(void){ float level; getMaxmemoryState(NULL, NULL, NULL, &level); return level; @@ -10827,27 +10935,27 @@ float VM_GetUsedMemoryRatio(void){ * ## Scanning keyspace and hashes * -------------------------------------------------------------------------- */ -typedef void (*ValkeyModuleScanCB)(ValkeyModuleCtx *ctx, ValkeyModuleString *keyname, ValkeyModuleKey *key, void *privdata); +typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keyname, RedisModuleKey *key, void *privdata); typedef struct { - ValkeyModuleCtx *ctx; + RedisModuleCtx *ctx; void* user_data; - ValkeyModuleScanCB fn; + RedisModuleScanCB fn; } ScanCBData; -typedef struct ValkeyModuleScanCursor{ - unsigned long cursor; +typedef struct RedisModuleScanCursor{ + unsigned long long cursor; int done; -}ValkeyModuleScanCursor; +}RedisModuleScanCursor; static void moduleScanCallback(void *privdata, const dictEntry *de) { ScanCBData *data = privdata; sds key = dictGetKey(de); robj* val = dictGetVal(de); - ValkeyModuleString *keyname = createObject(OBJ_STRING,sdsdup(key)); + RedisModuleString *keyname = createObject(OBJ_STRING,sdsdup(key)); /* Setup the key handle. */ - ValkeyModuleKey kp = {0}; - moduleInitKey(&kp, data->ctx, keyname, val, VALKEYMODULE_READ); + RedisModuleKey kp = {0}; + moduleInitKey(&kp, data->ctx, keyname, val, REDISMODULE_READ); data->fn(data->ctx, keyname, &kp, data->user_data); @@ -10855,22 +10963,22 @@ static void moduleScanCallback(void *privdata, const dictEntry *de) { decrRefCount(keyname); } -/* Create a new cursor to be used with ValkeyModule_Scan */ -ValkeyModuleScanCursor *VM_ScanCursorCreate(void) { - ValkeyModuleScanCursor* cursor = zmalloc(sizeof(*cursor)); +/* Create a new cursor to be used with RedisModule_Scan */ +RedisModuleScanCursor *RM_ScanCursorCreate(void) { + RedisModuleScanCursor* cursor = zmalloc(sizeof(*cursor)); cursor->cursor = 0; cursor->done = 0; return cursor; } /* Restart an existing cursor. The keys will be rescanned. */ -void VM_ScanCursorRestart(ValkeyModuleScanCursor *cursor) { +void RM_ScanCursorRestart(RedisModuleScanCursor *cursor) { cursor->cursor = 0; cursor->done = 0; } /* Destroy the cursor struct. */ -void VM_ScanCursorDestroy(ValkeyModuleScanCursor *cursor) { +void RM_ScanCursorDestroy(RedisModuleScanCursor *cursor) { zfree(cursor); } @@ -10879,41 +10987,41 @@ void VM_ScanCursorDestroy(ValkeyModuleScanCursor *cursor) { * * Callback for scan implementation. * - * void scan_callback(ValkeyModuleCtx *ctx, ValkeyModuleString *keyname, - * ValkeyModuleKey *key, void *privdata); + * void scan_callback(RedisModuleCtx *ctx, RedisModuleString *keyname, + * RedisModuleKey *key, void *privdata); * * - `ctx`: the redis module context provided to for the scan. * - `keyname`: owned by the caller and need to be retained if used after this * function. * - `key`: holds info on the key and value, it is provided as best effort, in * some cases it might be NULL, in which case the user should (can) use - * ValkeyModule_OpenKey() (and CloseKey too). + * RedisModule_OpenKey() (and CloseKey too). * when it is provided, it is owned by the caller and will be free when the * callback returns. - * - `privdata`: the user data provided to ValkeyModule_Scan(). + * - `privdata`: the user data provided to RedisModule_Scan(). * * The way it should be used: * - * ValkeyModuleScanCursor *c = ValkeyModule_ScanCursorCreate(); - * while(ValkeyModule_Scan(ctx, c, callback, privateData)); - * ValkeyModule_ScanCursorDestroy(c); + * RedisModuleScanCursor *c = RedisModule_ScanCursorCreate(); + * while(RedisModule_Scan(ctx, c, callback, privateData)); + * RedisModule_ScanCursorDestroy(c); * * It is also possible to use this API from another thread while the lock - * is acquired during the actual call to VM_Scan: + * is acquired during the actual call to RM_Scan: * - * ValkeyModuleScanCursor *c = ValkeyModule_ScanCursorCreate(); - * ValkeyModule_ThreadSafeContextLock(ctx); - * while(ValkeyModule_Scan(ctx, c, callback, privateData)){ - * ValkeyModule_ThreadSafeContextUnlock(ctx); + * RedisModuleScanCursor *c = RedisModule_ScanCursorCreate(); + * RedisModule_ThreadSafeContextLock(ctx); + * while(RedisModule_Scan(ctx, c, callback, privateData)){ + * RedisModule_ThreadSafeContextUnlock(ctx); * // do some background job - * ValkeyModule_ThreadSafeContextLock(ctx); + * RedisModule_ThreadSafeContextLock(ctx); * } - * ValkeyModule_ScanCursorDestroy(c); + * RedisModule_ScanCursorDestroy(c); * * The function will return 1 if there are more elements to scan and * 0 otherwise, possibly setting errno if the call failed. * - * It is also possible to restart an existing cursor using VM_ScanCursorRestart. + * It is also possible to restart an existing cursor using RM_ScanCursorRestart. * * IMPORTANT: This API is very similar to the Redis SCAN command from the * point of view of the guarantees it provides. This means that the API @@ -10930,14 +11038,14 @@ void VM_ScanCursorDestroy(ValkeyModuleScanCursor *cursor) { * later when the iteration is complete. However this can cost a lot of * memory, so it may make sense to just operate on the current key when * possible during the iteration, given that this is safe. */ -int VM_Scan(ValkeyModuleCtx *ctx, ValkeyModuleScanCursor *cursor, ValkeyModuleScanCB fn, void *privdata) { +int RM_Scan(RedisModuleCtx *ctx, RedisModuleScanCursor *cursor, RedisModuleScanCB fn, void *privdata) { if (cursor->done) { errno = ENOENT; return 0; } int ret = 1; ScanCBData data = { ctx, privdata, fn }; - cursor->cursor = dictScan(ctx->client->db->dict, cursor->cursor, moduleScanCallback, &data); + cursor->cursor = dbScan(ctx->client->db, DB_MAIN, cursor->cursor, -1, moduleScanCallback, NULL, &data); if (cursor->cursor == 0) { cursor->done = 1; ret = 0; @@ -10946,11 +11054,11 @@ int VM_Scan(ValkeyModuleCtx *ctx, ValkeyModuleScanCursor *cursor, ValkeyModuleSc return ret; } -typedef void (*ValkeyModuleScanKeyCB)(ValkeyModuleKey *key, ValkeyModuleString *field, ValkeyModuleString *value, void *privdata); +typedef void (*RedisModuleScanKeyCB)(RedisModuleKey *key, RedisModuleString *field, RedisModuleString *value, void *privdata); typedef struct { - ValkeyModuleKey *key; + RedisModuleKey *key; void* user_data; - ValkeyModuleScanKeyCB fn; + RedisModuleScanKeyCB fn; } ScanKeyCBData; static void moduleScanKeyCallback(void *privdata, const dictEntry *de) { @@ -10978,42 +11086,42 @@ static void moduleScanKeyCallback(void *privdata, const dictEntry *de) { * * Callback for scan implementation. * - * void scan_callback(ValkeyModuleKey *key, ValkeyModuleString* field, ValkeyModuleString* value, void *privdata); + * void scan_callback(RedisModuleKey *key, RedisModuleString* field, RedisModuleString* value, void *privdata); * * - key - the redis key context provided to for the scan. * - field - field name, owned by the caller and need to be retained if used * after this function. * - value - value string or NULL for set type, owned by the caller and need to * be retained if used after this function. - * - privdata - the user data provided to ValkeyModule_ScanKey. + * - privdata - the user data provided to RedisModule_ScanKey. * * The way it should be used: * - * ValkeyModuleScanCursor *c = ValkeyModule_ScanCursorCreate(); - * ValkeyModuleKey *key = ValkeyModule_OpenKey(...) - * while(ValkeyModule_ScanKey(key, c, callback, privateData)); - * ValkeyModule_CloseKey(key); - * ValkeyModule_ScanCursorDestroy(c); + * RedisModuleScanCursor *c = RedisModule_ScanCursorCreate(); + * RedisModuleKey *key = RedisModule_OpenKey(...) + * while(RedisModule_ScanKey(key, c, callback, privateData)); + * RedisModule_CloseKey(key); + * RedisModule_ScanCursorDestroy(c); * * It is also possible to use this API from another thread while the lock is acquired during - * the actual call to VM_ScanKey, and re-opening the key each time: - * - * ValkeyModuleScanCursor *c = ValkeyModule_ScanCursorCreate(); - * ValkeyModule_ThreadSafeContextLock(ctx); - * ValkeyModuleKey *key = ValkeyModule_OpenKey(...) - * while(ValkeyModule_ScanKey(ctx, c, callback, privateData)){ - * ValkeyModule_CloseKey(key); - * ValkeyModule_ThreadSafeContextUnlock(ctx); + * the actual call to RM_ScanKey, and re-opening the key each time: + * + * RedisModuleScanCursor *c = RedisModule_ScanCursorCreate(); + * RedisModule_ThreadSafeContextLock(ctx); + * RedisModuleKey *key = RedisModule_OpenKey(...) + * while(RedisModule_ScanKey(ctx, c, callback, privateData)){ + * RedisModule_CloseKey(key); + * RedisModule_ThreadSafeContextUnlock(ctx); * // do some background job - * ValkeyModule_ThreadSafeContextLock(ctx); - * ValkeyModuleKey *key = ValkeyModule_OpenKey(...) + * RedisModule_ThreadSafeContextLock(ctx); + * RedisModuleKey *key = RedisModule_OpenKey(...) * } - * ValkeyModule_CloseKey(key); - * ValkeyModule_ScanCursorDestroy(c); + * RedisModule_CloseKey(key); + * RedisModule_ScanCursorDestroy(c); * * The function will return 1 if there are more elements to scan and 0 otherwise, * possibly setting errno if the call failed. - * It is also possible to restart an existing cursor using VM_ScanCursorRestart. + * It is also possible to restart an existing cursor using RM_ScanCursorRestart. * * NOTE: Certain operations are unsafe while iterating the object. For instance * while the API guarantees to return at least one time all the elements that @@ -11022,7 +11130,7 @@ static void moduleScanKeyCallback(void *privdata, const dictEntry *de) { * you play with the elements, the more duplicates you may get. In general * deleting the current element of the data structure is safe, while removing * the key you are iterating is not safe. */ -int VM_ScanKey(ValkeyModuleKey *key, ValkeyModuleScanCursor *cursor, ValkeyModuleScanKeyCB fn, void *privdata) { +int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleScanKeyCB fn, void *privdata) { if (key == NULL || key->value == NULL) { errno = EINVAL; return 0; @@ -11103,14 +11211,14 @@ int VM_ScanKey(ValkeyModuleKey *key, ValkeyModuleScanCursor *cursor, ValkeyModul * main process where you can do some processing in the background without * affecting / freezing the traffic and no need for threads and GIL locking. * Note that Redis allows for only one concurrent fork. - * When the child wants to exit, it should call ValkeyModule_ExitFromChild. - * If the parent wants to kill the child it should call ValkeyModule_KillForkChild + * When the child wants to exit, it should call RedisModule_ExitFromChild. + * If the parent wants to kill the child it should call RedisModule_KillForkChild * The done handler callback will be executed on the parent process when the * child existed (but not when killed) * Return: -1 on failure, on success the parent process will get a positive PID * of the child, and the child process will get 0. */ -int VM_Fork(ValkeyModuleForkDoneHandler cb, void *user_data) { +int RM_Fork(RedisModuleForkDoneHandler cb, void *user_data) { pid_t childpid; if ((childpid = redisFork(CHILD_TYPE_MODULE)) == 0) { @@ -11131,17 +11239,17 @@ int VM_Fork(ValkeyModuleForkDoneHandler cb, void *user_data) { * so that it can report progress and COW memory to the parent which will be * reported in INFO. * The `progress` argument should between 0 and 1, or -1 when not available. */ -void VM_SendChildHeartbeat(double progress) { +void RM_SendChildHeartbeat(double progress) { sendChildInfoGeneric(CHILD_INFO_TYPE_CURRENT_INFO, 0, progress, "Module fork"); } /* Call from the child process when you want to terminate it. * retcode will be provided to the done handler executed on the parent process. */ -int VM_ExitFromChild(int retcode) { +int RM_ExitFromChild(int retcode) { sendChildCowInfo(CHILD_INFO_TYPE_MODULE_COW_SIZE, "Module fork"); exitFromChild(retcode); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Kill the active module forked child, if there is one active and the @@ -11167,13 +11275,13 @@ int TerminateModuleForkChild(int child_pid, int wait) { } /* Can be used to kill the forked child process from the parent process. - * child_pid would be the return value of ValkeyModule_Fork. */ -int VM_KillForkChild(int child_pid) { + * child_pid would be the return value of RedisModule_Fork. */ +int RM_KillForkChild(int child_pid) { /* Kill module child, wait for child exit. */ if (TerminateModuleForkChild(child_pid,1) == C_OK) - return VALKEYMODULE_OK; + return REDISMODULE_OK; else - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } void ModuleForkDoneHandler(int exitcode, int bysignal) { @@ -11193,29 +11301,29 @@ void ModuleForkDoneHandler(int exitcode, int bysignal) { * ## Server hooks implementation * -------------------------------------------------------------------------- */ -/* This must be synced with VALKEYMODULE_EVENT_* +/* This must be synced with REDISMODULE_EVENT_* * We use -1 (MAX_UINT64) to denote that this event doesn't have * a data structure associated with it. We use MAX_UINT64 on purpose, - * in order to pass the check in ValkeyModule_SubscribeToServerEvent. */ + * in order to pass the check in RedisModule_SubscribeToServerEvent. */ static uint64_t moduleEventVersions[] = { - VALKEYMODULE_REPLICATIONINFO_VERSION, /* VALKEYMODULE_EVENT_REPLICATION_ROLE_CHANGED */ - -1, /* VALKEYMODULE_EVENT_PERSISTENCE */ - VALKEYMODULE_FLUSHINFO_VERSION, /* VALKEYMODULE_EVENT_FLUSHDB */ - -1, /* VALKEYMODULE_EVENT_LOADING */ - VALKEYMODULE_CLIENTINFO_VERSION, /* VALKEYMODULE_EVENT_CLIENT_CHANGE */ - -1, /* VALKEYMODULE_EVENT_SHUTDOWN */ - -1, /* VALKEYMODULE_EVENT_REPLICA_CHANGE */ - -1, /* VALKEYMODULE_EVENT_PRIMARY_LINK_CHANGE */ - VALKEYMODULE_CRON_LOOP_VERSION, /* VALKEYMODULE_EVENT_CRON_LOOP */ - VALKEYMODULE_MODULE_CHANGE_VERSION, /* VALKEYMODULE_EVENT_MODULE_CHANGE */ - VALKEYMODULE_LOADING_PROGRESS_VERSION, /* VALKEYMODULE_EVENT_LOADING_PROGRESS */ - VALKEYMODULE_SWAPDBINFO_VERSION, /* VALKEYMODULE_EVENT_SWAPDB */ - -1, /* VALKEYMODULE_EVENT_REPL_BACKUP */ - -1, /* VALKEYMODULE_EVENT_FORK_CHILD */ - -1, /* VALKEYMODULE_EVENT_REPL_ASYNC_LOAD */ - -1, /* VALKEYMODULE_EVENT_EVENTLOOP */ - -1, /* VALKEYMODULE_EVENT_CONFIG */ - VALKEYMODULE_KEYINFO_VERSION, /* VALKEYMODULE_EVENT_KEY */ + REDISMODULE_REPLICATIONINFO_VERSION, /* REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED */ + -1, /* REDISMODULE_EVENT_PERSISTENCE */ + REDISMODULE_FLUSHINFO_VERSION, /* REDISMODULE_EVENT_FLUSHDB */ + -1, /* REDISMODULE_EVENT_LOADING */ + REDISMODULE_CLIENTINFO_VERSION, /* REDISMODULE_EVENT_CLIENT_CHANGE */ + -1, /* REDISMODULE_EVENT_SHUTDOWN */ + -1, /* REDISMODULE_EVENT_REPLICA_CHANGE */ + -1, /* REDISMODULE_EVENT_MASTER_LINK_CHANGE */ + REDISMODULE_CRON_LOOP_VERSION, /* REDISMODULE_EVENT_CRON_LOOP */ + REDISMODULE_MODULE_CHANGE_VERSION, /* REDISMODULE_EVENT_MODULE_CHANGE */ + REDISMODULE_LOADING_PROGRESS_VERSION, /* REDISMODULE_EVENT_LOADING_PROGRESS */ + REDISMODULE_SWAPDBINFO_VERSION, /* REDISMODULE_EVENT_SWAPDB */ + -1, /* REDISMODULE_EVENT_REPL_BACKUP */ + -1, /* REDISMODULE_EVENT_FORK_CHILD */ + -1, /* REDISMODULE_EVENT_REPL_ASYNC_LOAD */ + -1, /* REDISMODULE_EVENT_EVENTLOOP */ + -1, /* REDISMODULE_EVENT_CONFIG */ + REDISMODULE_KEYINFO_VERSION, /* REDISMODULE_EVENT_KEY */ }; /* Register to be notified, via a callback, when the specified server event @@ -11230,8 +11338,8 @@ static uint64_t moduleEventVersions[] = { * * The callback must be of this type: * - * int (*ValkeyModuleEventCallback)(ValkeyModuleCtx *ctx, - * ValkeyModuleEvent eid, + * int (*RedisModuleEventCallback)(RedisModuleCtx *ctx, + * RedisModuleEvent eid, * uint64_t subevent, * void *data); * @@ -11247,7 +11355,7 @@ static uint64_t moduleEventVersions[] = { * * Here is a list of events you can use as 'eid' and related sub events: * - * * ValkeyModuleEvent_ReplicationRoleChanged: + * * RedisModuleEvent_ReplicationRoleChanged: * * This event is called when the instance switches from master * to replica or the other way around, however the event is @@ -11256,11 +11364,11 @@ static uint64_t moduleEventVersions[] = { * * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_REPLROLECHANGED_NOW_PRIMARY` - * * `VALKEYMODULE_SUBEVENT_REPLROLECHANGED_NOW_REPLICA` + * * `REDISMODULE_SUBEVENT_REPLROLECHANGED_NOW_MASTER` + * * `REDISMODULE_SUBEVENT_REPLROLECHANGED_NOW_REPLICA` * * The 'data' field can be casted by the callback to a - * `ValkeyModuleReplicationInfo` structure with the following fields: + * `RedisModuleReplicationInfo` structure with the following fields: * * int master; // true if master, false if replica * char *masterhost; // master instance hostname for NOW_REPLICA @@ -11270,17 +11378,17 @@ static uint64_t moduleEventVersions[] = { * uint64_t repl1_offset; // Main replication offset * uint64_t repl2_offset; // Offset of replid2 validity * - * * ValkeyModuleEvent_Persistence + * * RedisModuleEvent_Persistence * * This event is called when RDB saving or AOF rewriting starts * and ends. The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_PERSISTENCE_RDB_START` - * * `VALKEYMODULE_SUBEVENT_PERSISTENCE_AOF_START` - * * `VALKEYMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START` - * * `VALKEYMODULE_SUBEVENT_PERSISTENCE_SYNC_AOF_START` - * * `VALKEYMODULE_SUBEVENT_PERSISTENCE_ENDED` - * * `VALKEYMODULE_SUBEVENT_PERSISTENCE_FAILED` + * * `REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START` + * * `REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START` + * * `REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START` + * * `REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_AOF_START` + * * `REDISMODULE_SUBEVENT_PERSISTENCE_ENDED` + * * `REDISMODULE_SUBEVENT_PERSISTENCE_FAILED` * * The above events are triggered not just when the user calls the * relevant commands like BGSAVE, but also when a saving operation @@ -11293,16 +11401,16 @@ static uint64_t moduleEventVersions[] = { * clients and commands. Also note that the AOF_START sub event may end * up saving RDB content in case of an AOF with rdb-preamble. * - * * ValkeyModuleEvent_FlushDB + * * RedisModuleEvent_FlushDB * * The FLUSHALL, FLUSHDB or an internal flush (for instance * because of replication, after the replica synchronization) * happened. The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_FLUSHDB_START` - * * `VALKEYMODULE_SUBEVENT_FLUSHDB_END` + * * `REDISMODULE_SUBEVENT_FLUSHDB_START` + * * `REDISMODULE_SUBEVENT_FLUSHDB_END` * - * The data pointer can be casted to a ValkeyModuleFlushInfo + * The data pointer can be casted to a RedisModuleFlushInfo * structure with the following fields: * * int32_t async; // True if the flush is done in a thread. @@ -11317,51 +11425,51 @@ static uint64_t moduleEventVersions[] = { * allowing the callback to call DBSIZE or other operation on the * yet-to-free keyspace. * - * * ValkeyModuleEvent_Loading + * * RedisModuleEvent_Loading * * Called on loading operations: at startup when the server is * started, but also after a first synchronization when the * replica is loading the RDB file from the master. * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_LOADING_RDB_START` - * * `VALKEYMODULE_SUBEVENT_LOADING_AOF_START` - * * `VALKEYMODULE_SUBEVENT_LOADING_REPL_START` - * * `VALKEYMODULE_SUBEVENT_LOADING_ENDED` - * * `VALKEYMODULE_SUBEVENT_LOADING_FAILED` + * * `REDISMODULE_SUBEVENT_LOADING_RDB_START` + * * `REDISMODULE_SUBEVENT_LOADING_AOF_START` + * * `REDISMODULE_SUBEVENT_LOADING_REPL_START` + * * `REDISMODULE_SUBEVENT_LOADING_ENDED` + * * `REDISMODULE_SUBEVENT_LOADING_FAILED` * * Note that AOF loading may start with an RDB data in case of * rdb-preamble, in which case you'll only receive an AOF_START event. * - * * ValkeyModuleEvent_ClientChange + * * RedisModuleEvent_ClientChange * * Called when a client connects or disconnects. - * The data pointer can be casted to a ValkeyModuleClientInfo - * structure, documented in ValkeyModule_GetClientInfoById(). + * The data pointer can be casted to a RedisModuleClientInfo + * structure, documented in RedisModule_GetClientInfoById(). * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED` - * * `VALKEYMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED` + * * `REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED` + * * `REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED` * - * * ValkeyModuleEvent_Shutdown + * * RedisModuleEvent_Shutdown * * The server is shutting down. No subevents are available. * - * * ValkeyModuleEvent_ReplicaChange + * * RedisModuleEvent_ReplicaChange * * This event is called when the instance (that can be both a * master or a replica) get a new online replica, or lose a * replica since it gets disconnected. * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE` - * * `VALKEYMODULE_SUBEVENT_REPLICA_CHANGE_OFFLINE` + * * `REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE` + * * `REDISMODULE_SUBEVENT_REPLICA_CHANGE_OFFLINE` * * No additional information is available so far: future versions * of Redis will have an API in order to enumerate the replicas * connected and their state. * - * * ValkeyModuleEvent_CronLoop + * * RedisModuleEvent_CronLoop * * This event is called every time Redis calls the serverCron() * function in order to do certain bookkeeping. Modules that are @@ -11370,12 +11478,12 @@ static uint64_t moduleEventVersions[] = { * this changes depending on the "hz" configuration. * No sub events are available. * - * The data pointer can be casted to a ValkeyModuleCronLoop + * The data pointer can be casted to a RedisModuleCronLoop * structure with the following fields: * * int32_t hz; // Approximate number of events per second. * - * * ValkeyModuleEvent_PrimaryLinkChange + * * RedisModuleEvent_MasterLinkChange * * This is called for replicas in order to notify when the * replication link becomes functional (up) with our master, @@ -11384,55 +11492,55 @@ static uint64_t moduleEventVersions[] = { * replication is happening correctly. * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_PRIMARY_LINK_UP` - * * `VALKEYMODULE_SUBEVENT_PRIMARY_LINK_DOWN` + * * `REDISMODULE_SUBEVENT_MASTER_LINK_UP` + * * `REDISMODULE_SUBEVENT_MASTER_LINK_DOWN` * - * * ValkeyModuleEvent_ModuleChange + * * RedisModuleEvent_ModuleChange * * This event is called when a new module is loaded or one is unloaded. * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_MODULE_LOADED` - * * `VALKEYMODULE_SUBEVENT_MODULE_UNLOADED` + * * `REDISMODULE_SUBEVENT_MODULE_LOADED` + * * `REDISMODULE_SUBEVENT_MODULE_UNLOADED` * - * The data pointer can be casted to a ValkeyModuleModuleChange + * The data pointer can be casted to a RedisModuleModuleChange * structure with the following fields: * * const char* module_name; // Name of module loaded or unloaded. * int32_t module_version; // Module version. * - * * ValkeyModuleEvent_LoadingProgress + * * RedisModuleEvent_LoadingProgress * * This event is called repeatedly called while an RDB or AOF file * is being loaded. * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_LOADING_PROGRESS_RDB` - * * `VALKEYMODULE_SUBEVENT_LOADING_PROGRESS_AOF` + * * `REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB` + * * `REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF` * - * The data pointer can be casted to a ValkeyModuleLoadingProgress + * The data pointer can be casted to a RedisModuleLoadingProgress * structure with the following fields: * * int32_t hz; // Approximate number of events per second. * int32_t progress; // Approximate progress between 0 and 1024, * // or -1 if unknown. * - * * ValkeyModuleEvent_SwapDB + * * RedisModuleEvent_SwapDB * * This event is called when a SWAPDB command has been successfully * Executed. * For this event call currently there is no subevents available. * - * The data pointer can be casted to a ValkeyModuleSwapDbInfo + * The data pointer can be casted to a RedisModuleSwapDbInfo * structure with the following fields: * * int32_t dbnum_first; // Swap Db first dbnum * int32_t dbnum_second; // Swap Db second dbnum * - * * ValkeyModuleEvent_ReplBackup + * * RedisModuleEvent_ReplBackup * * WARNING: Replication Backup events are deprecated since Redis 7.0 and are never fired. - * See ValkeyModuleEvent_ReplAsyncLoad for understanding how Async Replication Loading events + * See RedisModuleEvent_ReplAsyncLoad for understanding how Async Replication Loading events * are now triggered when repl-diskless-load is set to swapdb. * * Called when repl-diskless-load config is set to swapdb, @@ -11442,85 +11550,85 @@ static uint64_t moduleEventVersions[] = { * notification to backup / restore / discard its globals. * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_REPL_BACKUP_CREATE` - * * `VALKEYMODULE_SUBEVENT_REPL_BACKUP_RESTORE` - * * `VALKEYMODULE_SUBEVENT_REPL_BACKUP_DISCARD` + * * `REDISMODULE_SUBEVENT_REPL_BACKUP_CREATE` + * * `REDISMODULE_SUBEVENT_REPL_BACKUP_RESTORE` + * * `REDISMODULE_SUBEVENT_REPL_BACKUP_DISCARD` * - * * ValkeyModuleEvent_ReplAsyncLoad + * * RedisModuleEvent_ReplAsyncLoad * * Called when repl-diskless-load config is set to swapdb and a replication with a master of same * data set history (matching replication ID) occurs. * In which case redis serves current data set while loading new database in memory from socket. * Modules must have declared they support this mechanism in order to activate it, through - * VALKEYMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD flag. + * REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD flag. * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_REPL_ASYNC_LOAD_STARTED` - * * `VALKEYMODULE_SUBEVENT_REPL_ASYNC_LOAD_ABORTED` - * * `VALKEYMODULE_SUBEVENT_REPL_ASYNC_LOAD_COMPLETED` + * * `REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_STARTED` + * * `REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_ABORTED` + * * `REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_COMPLETED` * - * * ValkeyModuleEvent_ForkChild + * * RedisModuleEvent_ForkChild * * Called when a fork child (AOFRW, RDBSAVE, module fork...) is born/dies * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_FORK_CHILD_BORN` - * * `VALKEYMODULE_SUBEVENT_FORK_CHILD_DIED` + * * `REDISMODULE_SUBEVENT_FORK_CHILD_BORN` + * * `REDISMODULE_SUBEVENT_FORK_CHILD_DIED` * - * * ValkeyModuleEvent_EventLoop + * * RedisModuleEvent_EventLoop * * Called on each event loop iteration, once just before the event loop goes * to sleep or just after it wakes up. * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_EVENTLOOP_BEFORE_SLEEP` - * * `VALKEYMODULE_SUBEVENT_EVENTLOOP_AFTER_SLEEP` + * * `REDISMODULE_SUBEVENT_EVENTLOOP_BEFORE_SLEEP` + * * `REDISMODULE_SUBEVENT_EVENTLOOP_AFTER_SLEEP` * - * * ValkeyModule_Event_Config + * * RedisModule_Event_Config * * Called when a configuration event happens * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_CONFIG_CHANGE` + * * `REDISMODULE_SUBEVENT_CONFIG_CHANGE` * - * The data pointer can be casted to a ValkeyModuleConfigChange + * The data pointer can be casted to a RedisModuleConfigChange * structure with the following fields: * * const char **config_names; // An array of C string pointers containing the * // name of each modified configuration item * uint32_t num_changes; // The number of elements in the config_names array * - * * ValkeyModule_Event_Key + * * RedisModule_Event_Key * * Called when a key is removed from the keyspace. We can't modify any key in * the event. * The following sub events are available: * - * * `VALKEYMODULE_SUBEVENT_KEY_DELETED` - * * `VALKEYMODULE_SUBEVENT_KEY_EXPIRED` - * * `VALKEYMODULE_SUBEVENT_KEY_EVICTED` - * * `VALKEYMODULE_SUBEVENT_KEY_OVERWRITTEN` + * * `REDISMODULE_SUBEVENT_KEY_DELETED` + * * `REDISMODULE_SUBEVENT_KEY_EXPIRED` + * * `REDISMODULE_SUBEVENT_KEY_EVICTED` + * * `REDISMODULE_SUBEVENT_KEY_OVERWRITTEN` * - * The data pointer can be casted to a ValkeyModuleKeyInfo + * The data pointer can be casted to a RedisModuleKeyInfo * structure with the following fields: * - * ValkeyModuleKey *key; // Key name + * RedisModuleKey *key; // Key name * - * The function returns VALKEYMODULE_OK if the module was successfully subscribed + * The function returns REDISMODULE_OK if the module was successfully subscribed * for the specified event. If the API is called from a wrong context or unsupported event - * is given then VALKEYMODULE_ERR is returned. */ -int VM_SubscribeToServerEvent(ValkeyModuleCtx *ctx, ValkeyModuleEvent event, ValkeyModuleEventCallback callback) { - ValkeyModuleEventListener *el; + * is given then REDISMODULE_ERR is returned. */ +int RM_SubscribeToServerEvent(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback) { + RedisModuleEventListener *el; /* Protect in case of calls from contexts without a module reference. */ - if (ctx->module == NULL) return VALKEYMODULE_ERR; - if (event.id >= _VALKEYMODULE_EVENT_NEXT) return VALKEYMODULE_ERR; - if (event.dataver > moduleEventVersions[event.id]) return VALKEYMODULE_ERR; /* Module compiled with a newer valkeymodule.h than we support */ + if (ctx->module == NULL) return REDISMODULE_ERR; + if (event.id >= _REDISMODULE_EVENT_NEXT) return REDISMODULE_ERR; + if (event.dataver > moduleEventVersions[event.id]) return REDISMODULE_ERR; /* Module compiled with a newer redismodule.h than we support */ /* Search an event matching this module and event ID. */ listIter li; listNode *ln; - listRewind(ValkeyModule_EventListeners,&li); + listRewind(RedisModule_EventListeners,&li); while((ln = listNext(&li))) { el = ln->value; if (el->module == ctx->module && el->event.id == event.id) @@ -11530,12 +11638,12 @@ int VM_SubscribeToServerEvent(ValkeyModuleCtx *ctx, ValkeyModuleEvent event, Val /* Modify or remove the event listener if we already had one. */ if (ln) { if (callback == NULL) { - listDelNode(ValkeyModule_EventListeners,ln); + listDelNode(RedisModule_EventListeners,ln); zfree(el); } else { el->callback = callback; /* Update the callback with the new one. */ } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* No event found, we need to add a new one. */ @@ -11543,50 +11651,50 @@ int VM_SubscribeToServerEvent(ValkeyModuleCtx *ctx, ValkeyModuleEvent event, Val el->module = ctx->module; el->event = event; el->callback = callback; - listAddNodeTail(ValkeyModule_EventListeners,el); - return VALKEYMODULE_OK; + listAddNodeTail(RedisModule_EventListeners,el); + return REDISMODULE_OK; } /** * For a given server event and subevent, return zero if the * subevent is not supported and non-zero otherwise. */ -int VM_IsSubEventSupported(ValkeyModuleEvent event, int64_t subevent) { +int RM_IsSubEventSupported(RedisModuleEvent event, int64_t subevent) { switch (event.id) { - case VALKEYMODULE_EVENT_REPLICATION_ROLE_CHANGED: - return subevent < _VALKEYMODULE_EVENT_REPLROLECHANGED_NEXT; - case VALKEYMODULE_EVENT_PERSISTENCE: - return subevent < _VALKEYMODULE_SUBEVENT_PERSISTENCE_NEXT; - case VALKEYMODULE_EVENT_FLUSHDB: - return subevent < _VALKEYMODULE_SUBEVENT_FLUSHDB_NEXT; - case VALKEYMODULE_EVENT_LOADING: - return subevent < _VALKEYMODULE_SUBEVENT_LOADING_NEXT; - case VALKEYMODULE_EVENT_CLIENT_CHANGE: - return subevent < _VALKEYMODULE_SUBEVENT_CLIENT_CHANGE_NEXT; - case VALKEYMODULE_EVENT_SHUTDOWN: - return subevent < _VALKEYMODULE_SUBEVENT_SHUTDOWN_NEXT; - case VALKEYMODULE_EVENT_REPLICA_CHANGE: - return subevent < _VALKEYMODULE_EVENT_REPLROLECHANGED_NEXT; - case VALKEYMODULE_EVENT_PRIMARY_LINK_CHANGE: - return subevent < _VALKEYMODULE_SUBEVENT_PRIMARY_NEXT; - case VALKEYMODULE_EVENT_CRON_LOOP: - return subevent < _VALKEYMODULE_SUBEVENT_CRON_LOOP_NEXT; - case VALKEYMODULE_EVENT_MODULE_CHANGE: - return subevent < _VALKEYMODULE_SUBEVENT_MODULE_NEXT; - case VALKEYMODULE_EVENT_LOADING_PROGRESS: - return subevent < _VALKEYMODULE_SUBEVENT_LOADING_PROGRESS_NEXT; - case VALKEYMODULE_EVENT_SWAPDB: - return subevent < _VALKEYMODULE_SUBEVENT_SWAPDB_NEXT; - case VALKEYMODULE_EVENT_REPL_ASYNC_LOAD: - return subevent < _VALKEYMODULE_SUBEVENT_REPL_ASYNC_LOAD_NEXT; - case VALKEYMODULE_EVENT_FORK_CHILD: - return subevent < _VALKEYMODULE_SUBEVENT_FORK_CHILD_NEXT; - case VALKEYMODULE_EVENT_EVENTLOOP: - return subevent < _VALKEYMODULE_SUBEVENT_EVENTLOOP_NEXT; - case VALKEYMODULE_EVENT_CONFIG: - return subevent < _VALKEYMODULE_SUBEVENT_CONFIG_NEXT; - case VALKEYMODULE_EVENT_KEY: - return subevent < _VALKEYMODULE_SUBEVENT_KEY_NEXT; + case REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED: + return subevent < _REDISMODULE_EVENT_REPLROLECHANGED_NEXT; + case REDISMODULE_EVENT_PERSISTENCE: + return subevent < _REDISMODULE_SUBEVENT_PERSISTENCE_NEXT; + case REDISMODULE_EVENT_FLUSHDB: + return subevent < _REDISMODULE_SUBEVENT_FLUSHDB_NEXT; + case REDISMODULE_EVENT_LOADING: + return subevent < _REDISMODULE_SUBEVENT_LOADING_NEXT; + case REDISMODULE_EVENT_CLIENT_CHANGE: + return subevent < _REDISMODULE_SUBEVENT_CLIENT_CHANGE_NEXT; + case REDISMODULE_EVENT_SHUTDOWN: + return subevent < _REDISMODULE_SUBEVENT_SHUTDOWN_NEXT; + case REDISMODULE_EVENT_REPLICA_CHANGE: + return subevent < _REDISMODULE_EVENT_REPLROLECHANGED_NEXT; + case REDISMODULE_EVENT_MASTER_LINK_CHANGE: + return subevent < _REDISMODULE_SUBEVENT_MASTER_NEXT; + case REDISMODULE_EVENT_CRON_LOOP: + return subevent < _REDISMODULE_SUBEVENT_CRON_LOOP_NEXT; + case REDISMODULE_EVENT_MODULE_CHANGE: + return subevent < _REDISMODULE_SUBEVENT_MODULE_NEXT; + case REDISMODULE_EVENT_LOADING_PROGRESS: + return subevent < _REDISMODULE_SUBEVENT_LOADING_PROGRESS_NEXT; + case REDISMODULE_EVENT_SWAPDB: + return subevent < _REDISMODULE_SUBEVENT_SWAPDB_NEXT; + case REDISMODULE_EVENT_REPL_ASYNC_LOAD: + return subevent < _REDISMODULE_SUBEVENT_REPL_ASYNC_LOAD_NEXT; + case REDISMODULE_EVENT_FORK_CHILD: + return subevent < _REDISMODULE_SUBEVENT_FORK_CHILD_NEXT; + case REDISMODULE_EVENT_EVENTLOOP: + return subevent < _REDISMODULE_SUBEVENT_EVENTLOOP_NEXT; + case REDISMODULE_EVENT_CONFIG: + return subevent < _REDISMODULE_SUBEVENT_CONFIG_NEXT; + case REDISMODULE_EVENT_KEY: + return subevent < _REDISMODULE_SUBEVENT_KEY_NEXT; default: break; } @@ -11595,7 +11703,7 @@ int VM_IsSubEventSupported(ValkeyModuleEvent event, int64_t subevent) { typedef struct KeyInfo { int32_t dbnum; - ValkeyModuleString *key; + RedisModuleString *key; robj *value; int mode; } KeyInfo; @@ -11611,65 +11719,65 @@ void moduleFireServerEvent(uint64_t eid, int subid, void *data) { /* Fast path to return ASAP if there is nothing to do, avoiding to * setup the iterator and so forth: we want this call to be extremely * cheap if there are no registered modules. */ - if (listLength(ValkeyModule_EventListeners) == 0) return; + if (listLength(RedisModule_EventListeners) == 0) return; listIter li; listNode *ln; - listRewind(ValkeyModule_EventListeners,&li); + listRewind(RedisModule_EventListeners,&li); while((ln = listNext(&li))) { - ValkeyModuleEventListener *el = ln->value; + RedisModuleEventListener *el = ln->value; if (el->event.id == eid) { - ValkeyModuleCtx ctx; - if (eid == VALKEYMODULE_EVENT_CLIENT_CHANGE) { + RedisModuleCtx ctx; + if (eid == REDISMODULE_EVENT_CLIENT_CHANGE) { /* In the case of client changes, we're pushing the real client * so the event handler can mutate it if needed. For example, * to change its authentication state in a way that does not * depend on specific commands executed later. */ - moduleCreateContext(&ctx,el->module,VALKEYMODULE_CTX_NONE); + moduleCreateContext(&ctx,el->module,REDISMODULE_CTX_NONE); ctx.client = (client *) data; } else { - moduleCreateContext(&ctx,el->module,VALKEYMODULE_CTX_TEMP_CLIENT); + moduleCreateContext(&ctx,el->module,REDISMODULE_CTX_TEMP_CLIENT); } void *moduledata = NULL; - ValkeyModuleClientInfoV1 civ1; - ValkeyModuleReplicationInfoV1 riv1; - ValkeyModuleModuleChangeV1 mcv1; - ValkeyModuleKey key; - ValkeyModuleKeyInfoV1 ki = {VALKEYMODULE_KEYINFO_VERSION, &key}; + RedisModuleClientInfoV1 civ1; + RedisModuleReplicationInfoV1 riv1; + RedisModuleModuleChangeV1 mcv1; + RedisModuleKey key; + RedisModuleKeyInfoV1 ki = {REDISMODULE_KEYINFO_VERSION, &key}; /* Event specific context and data pointer setup. */ - if (eid == VALKEYMODULE_EVENT_CLIENT_CHANGE) { - serverAssert(modulePopulateClientInfoStructure(&civ1,data, el->event.dataver) == VALKEYMODULE_OK); + if (eid == REDISMODULE_EVENT_CLIENT_CHANGE) { + serverAssert(modulePopulateClientInfoStructure(&civ1,data, el->event.dataver) == REDISMODULE_OK); moduledata = &civ1; - } else if (eid == VALKEYMODULE_EVENT_REPLICATION_ROLE_CHANGED) { - serverAssert(modulePopulateReplicationInfoStructure(&riv1,el->event.dataver) == VALKEYMODULE_OK); + } else if (eid == REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED) { + serverAssert(modulePopulateReplicationInfoStructure(&riv1,el->event.dataver) == REDISMODULE_OK); moduledata = &riv1; - } else if (eid == VALKEYMODULE_EVENT_FLUSHDB) { + } else if (eid == REDISMODULE_EVENT_FLUSHDB) { moduledata = data; - ValkeyModuleFlushInfoV1 *fi = data; + RedisModuleFlushInfoV1 *fi = data; if (fi->dbnum != -1) selectDb(ctx.client, fi->dbnum); - } else if (eid == VALKEYMODULE_EVENT_MODULE_CHANGE) { - ValkeyModule *m = data; + } else if (eid == REDISMODULE_EVENT_MODULE_CHANGE) { + RedisModule *m = data; if (m == el->module) { moduleFreeContext(&ctx); continue; } - mcv1.version = VALKEYMODULE_MODULE_CHANGE_VERSION; + mcv1.version = REDISMODULE_MODULE_CHANGE_VERSION; mcv1.module_name = m->name; mcv1.module_version = m->ver; moduledata = &mcv1; - } else if (eid == VALKEYMODULE_EVENT_LOADING_PROGRESS) { + } else if (eid == REDISMODULE_EVENT_LOADING_PROGRESS) { moduledata = data; - } else if (eid == VALKEYMODULE_EVENT_CRON_LOOP) { + } else if (eid == REDISMODULE_EVENT_CRON_LOOP) { moduledata = data; - } else if (eid == VALKEYMODULE_EVENT_SWAPDB) { + } else if (eid == REDISMODULE_EVENT_SWAPDB) { moduledata = data; - } else if (eid == VALKEYMODULE_EVENT_CONFIG) { + } else if (eid == REDISMODULE_EVENT_CONFIG) { moduledata = data; - } else if (eid == VALKEYMODULE_EVENT_KEY) { + } else if (eid == REDISMODULE_EVENT_KEY) { KeyInfo *info = data; selectDb(ctx.client, info->dbnum); moduleInitKey(&key, &ctx, info->key, info->value, info->mode); @@ -11680,7 +11788,7 @@ void moduleFireServerEvent(uint64_t eid, int subid, void *data) { el->callback(&ctx,el->event,subid,moduledata); el->module->in_hook--; - if (eid == VALKEYMODULE_EVENT_KEY) { + if (eid == REDISMODULE_EVENT_KEY) { moduleCloseKey(&key); } @@ -11691,16 +11799,16 @@ void moduleFireServerEvent(uint64_t eid, int subid, void *data) { /* Remove all the listeners for this module: this is used before unloading * a module. */ -void moduleUnsubscribeAllServerEvents(ValkeyModule *module) { - ValkeyModuleEventListener *el; +void moduleUnsubscribeAllServerEvents(RedisModule *module) { + RedisModuleEventListener *el; listIter li; listNode *ln; - listRewind(ValkeyModule_EventListeners,&li); + listRewind(RedisModule_EventListeners,&li); while((ln = listNext(&li))) { el = ln->value; if (el->module == module) { - listDelNode(ValkeyModule_EventListeners,ln); + listDelNode(RedisModule_EventListeners,ln); zfree(el); } } @@ -11714,13 +11822,13 @@ void processModuleLoadingProgressEvent(int is_aof) { int progress = -1; if (server.loading_total_bytes) progress = (server.loading_loaded_bytes<<10) / server.loading_total_bytes; - ValkeyModuleLoadingProgressV1 fi = {VALKEYMODULE_LOADING_PROGRESS_VERSION, + RedisModuleLoadingProgressV1 fi = {REDISMODULE_LOADING_PROGRESS_VERSION, server.hz, progress}; - moduleFireServerEvent(VALKEYMODULE_EVENT_LOADING_PROGRESS, + moduleFireServerEvent(REDISMODULE_EVENT_LOADING_PROGRESS, is_aof? - VALKEYMODULE_SUBEVENT_LOADING_PROGRESS_AOF: - VALKEYMODULE_SUBEVENT_LOADING_PROGRESS_RDB, + REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF: + REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB, &fi); /* decide when the next event should fire. */ next_event = now + 1000000 / server.hz; @@ -11731,23 +11839,23 @@ void processModuleLoadingProgressEvent(int is_aof) { * will be called to tell the module which key is about to be released. */ void moduleNotifyKeyUnlink(robj *key, robj *val, int dbid, int flags) { server.lazy_expire_disabled++; - int subevent = VALKEYMODULE_SUBEVENT_KEY_DELETED; + int subevent = REDISMODULE_SUBEVENT_KEY_DELETED; if (flags & DB_FLAG_KEY_EXPIRED) { - subevent = VALKEYMODULE_SUBEVENT_KEY_EXPIRED; + subevent = REDISMODULE_SUBEVENT_KEY_EXPIRED; } else if (flags & DB_FLAG_KEY_EVICTED) { - subevent = VALKEYMODULE_SUBEVENT_KEY_EVICTED; + subevent = REDISMODULE_SUBEVENT_KEY_EVICTED; } else if (flags & DB_FLAG_KEY_OVERWRITE) { - subevent = VALKEYMODULE_SUBEVENT_KEY_OVERWRITTEN; + subevent = REDISMODULE_SUBEVENT_KEY_OVERWRITTEN; } - KeyInfo info = {dbid, key, val, VALKEYMODULE_READ}; - moduleFireServerEvent(VALKEYMODULE_EVENT_KEY, subevent, &info); + KeyInfo info = {dbid, key, val, REDISMODULE_READ}; + moduleFireServerEvent(REDISMODULE_EVENT_KEY, subevent, &info); if (val->type == OBJ_MODULE) { moduleValue *mv = val->ptr; moduleType *mt = mv->type; /* We prefer to use the enhanced version. */ if (mt->unlink2 != NULL) { - ValkeyModuleKeyOptCtx ctx = {key, NULL, dbid, -1}; + RedisModuleKeyOptCtx ctx = {key, NULL, dbid, -1}; mt->unlink2(&ctx,mv->value); } else if (mt->unlink != NULL) { mt->unlink(key,mv->value); @@ -11765,7 +11873,7 @@ size_t moduleGetFreeEffort(robj *key, robj *val, int dbid) { size_t effort = 1; /* We prefer to use the enhanced version. */ if (mt->free_effort2 != NULL) { - ValkeyModuleKeyOptCtx ctx = {key, NULL, dbid, -1}; + RedisModuleKeyOptCtx ctx = {key, NULL, dbid, -1}; effort = mt->free_effort2(&ctx,mv->value); } else if (mt->free_effort != NULL) { effort = mt->free_effort(key,mv->value); @@ -11782,7 +11890,7 @@ size_t moduleGetMemUsage(robj *key, robj *val, size_t sample_size, int dbid) { size_t size = 0; /* We prefer to use the enhanced version. */ if (mt->mem_usage2 != NULL) { - ValkeyModuleKeyOptCtx ctx = {key, NULL, dbid, -1}; + RedisModuleKeyOptCtx ctx = {key, NULL, dbid, -1}; size = mt->mem_usage2(&ctx, mv->value, sample_size); } else if (mt->mem_usage != NULL) { size = mt->mem_usage(mv->value); @@ -11821,11 +11929,8 @@ int moduleRegisterApi(const char *funcname, void *funcptr) { return dictAdd(server.moduleapi, (char*)funcname, funcptr); } -/* Register Module APIs under both ValkeyModule_ and ValkeyModule_ namespaces - * so that legacy Redis module binaries can continue to function */ #define REGISTER_API(name) \ - moduleRegisterApi("ValkeyModule_" #name, (void *)(unsigned long)VM_ ## name);\ - moduleRegisterApi("ValkeyModule_" #name, (void *)(unsigned long)VM_ ## name);\ + moduleRegisterApi("RedisModule_" #name, (void *)(unsigned long)RM_ ## name) /* Global initialization at Redis startup. */ void moduleRegisterCoreAPI(void); @@ -11852,6 +11957,7 @@ void moduleInitModulesSystem(void) { moduleUnblockedClients = listCreate(); server.loadmodule_queue = listCreate(); server.module_configs_queue = dictCreate(&sdsKeyValueHashDictType); + server.module_gil_acquring = 0; modules = dictCreate(&modulesDictType); moduleAuthCallbacks = listCreate(); @@ -11880,10 +11986,10 @@ void moduleInitModulesSystem(void) { Timers = raxNew(); /* Setup the event listeners data structures. */ - ValkeyModule_EventListeners = listCreate(); + RedisModule_EventListeners = listCreate(); /* Making sure moduleEventVersions is synced with the number of events. */ - serverAssert(sizeof(moduleEventVersions)/sizeof(moduleEventVersions[0]) == _VALKEYMODULE_EVENT_NEXT); + serverAssert(sizeof(moduleEventVersions)/sizeof(moduleEventVersions[0]) == _REDISMODULE_EVENT_NEXT); /* Our thread-safe contexts GIL must start with already locked: * it is just unlocked when it's safe. */ @@ -11928,7 +12034,7 @@ void moduleLoadQueueEntryFree(struct moduleLoadQueueEntry *loadmod) { } /* Remove Module Configs from standardConfig array in config.c */ -void moduleRemoveConfigs(ValkeyModule *module) { +void moduleRemoveConfigs(RedisModule *module) { listIter li; listNode *ln; listRewind(module->module_configs, &li); @@ -11941,6 +12047,13 @@ void moduleRemoveConfigs(ValkeyModule *module) { } } +/* Remove ACL categories added by the module when it fails to load. */ +void moduleRemoveCateogires(RedisModule *module) { + if (module->num_acl_categories_added) { + ACLCleanupCategoriesOnFailure(module->num_acl_categories_added); + } +} + /* Load all the modules in the server.loadmodule_queue list, which is * populated by `loadmodule` directives in the configuration file. * We can't load modules directly when processing the configuration file @@ -11974,7 +12087,7 @@ void moduleLoadFromQueue(void) { } } -void moduleFreeModuleStructure(struct ValkeyModule *module) { +void moduleFreeModuleStructure(struct RedisModule *module) { listRelease(module->types); listRelease(module->filters); listRelease(module->usedby); @@ -12007,11 +12120,11 @@ void moduleFreeArgs(struct redisCommandArg *args, int num_args) { * Note that caller needs to handle the deletion of the command table dict, * and after that needs to free the command->fullname and the command itself. */ -int moduleFreeCommand(struct ValkeyModule *module, struct redisCommand *cmd) { - if (cmd->proc != ValkeyModuleCommandDispatcher) +int moduleFreeCommand(struct RedisModule *module, struct redisCommand *cmd) { + if (cmd->proc != RedisModuleCommandDispatcher) return C_ERR; - ValkeyModuleCommand *cp = cmd->module_cmd; + RedisModuleCommand *cp = cmd->module_cmd; if (cp->module != module) return C_ERR; @@ -12061,7 +12174,7 @@ int moduleFreeCommand(struct ValkeyModule *module, struct redisCommand *cmd) { return C_OK; } -void moduleUnregisterCommands(struct ValkeyModule *module) { +void moduleUnregisterCommands(struct RedisModule *module) { /* Unregister all the commands registered by this module. */ dictIterator *di = dictGetSafeIterator(server.commands); dictEntry *de; @@ -12081,16 +12194,16 @@ void moduleUnregisterCommands(struct ValkeyModule *module) { /* We parse argv to add sds "NAME VALUE" pairs to the server.module_configs_queue list of configs. * We also increment the module_argv pointer to just after ARGS if there are args, otherwise * we set it to NULL */ -int parseLoadexArguments(ValkeyModuleString ***module_argv, int *module_argc) { +int parseLoadexArguments(RedisModuleString ***module_argv, int *module_argc) { int args_specified = 0; - ValkeyModuleString **argv = *module_argv; + RedisModuleString **argv = *module_argv; int argc = *module_argc; for (int i = 0; i < argc; i++) { char *arg_val = argv[i]->ptr; if (!strcasecmp(arg_val, "CONFIG")) { if (i + 2 >= argc) { serverLog(LL_NOTICE, "CONFIG specified without name value pair"); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } sds name = sdsdup(argv[i + 1]->ptr); sds value = sdsdup(argv[i + 2]->ptr); @@ -12109,18 +12222,18 @@ int parseLoadexArguments(ValkeyModuleString ***module_argv, int *module_argc) { break; } else { serverLog(LL_NOTICE, "Syntax Error from arguments to loadex around %s.", arg_val); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } } if (!args_specified) { *module_argv = NULL; *module_argc = 0; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Unregister module-related things, called when moduleLoad fails or moduleUnload. */ -void moduleUnregisterCleanup(ValkeyModule *module) { +void moduleUnregisterCleanup(RedisModule *module) { moduleFreeAuthenticatedClients(module); moduleUnregisterCommands(module); moduleUnsubscribeNotifications(module); @@ -12152,32 +12265,22 @@ int moduleLoad(const char *path, void **module_argv, int module_argc, int is_loa serverLog(LL_WARNING, "Module %s failed to load: %s", path, dlerror()); return C_ERR; } - - const char *onLoadNames[] = {"ValkeyModule_OnLoad", "RedisModule_OnLoad"}; - for (size_t i = 0; i < sizeof(onLoadNames) / sizeof(onLoadNames[0]); i++) { - onload = (int (*)(void *, void **, int))(unsigned long) dlsym(handle, onLoadNames[i]); - if (onload != NULL) { - if (i != 0) { - serverLog(LL_NOTICE, "Legacy Redis Module %s found", path); - } - break; - } - } - + onload = (int (*)(void *, void **, int))(unsigned long) dlsym(handle,"RedisModule_OnLoad"); if (onload == NULL) { dlclose(handle); serverLog(LL_WARNING, - "Module %s does not export ValkeyModule_OnLoad() or RedisModule_OnLoad() " + "Module %s does not export RedisModule_OnLoad() " "symbol. Module not loaded.",path); return C_ERR; } - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, NULL, VALKEYMODULE_CTX_TEMP_CLIENT); /* We pass NULL since we don't have a module yet. */ - if (onload((void*)&ctx,module_argv,module_argc) == VALKEYMODULE_ERR) { + RedisModuleCtx ctx; + moduleCreateContext(&ctx, NULL, REDISMODULE_CTX_TEMP_CLIENT); /* We pass NULL since we don't have a module yet. */ + if (onload((void*)&ctx,module_argv,module_argc) == REDISMODULE_ERR) { serverLog(LL_WARNING, "Module %s initialization failed. Module not loaded",path); if (ctx.module) { moduleUnregisterCleanup(ctx.module); + moduleRemoveCateogires(ctx.module); moduleFreeModuleStructure(ctx.module); } moduleFreeContext(&ctx); @@ -12224,8 +12327,8 @@ int moduleLoad(const char *path, void **module_argv, int module_argc, int is_loa } /* Fire the loaded modules event. */ - moduleFireServerEvent(VALKEYMODULE_EVENT_MODULE_CHANGE, - VALKEYMODULE_SUBEVENT_MODULE_LOADED, + moduleFireServerEvent(REDISMODULE_EVENT_MODULE_CHANGE, + REDISMODULE_SUBEVENT_MODULE_LOADED, ctx.module); moduleFreeContext(&ctx); @@ -12236,7 +12339,7 @@ int moduleLoad(const char *path, void **module_argv, int module_argc, int is_loa * C_OK is returned, otherwise C_ERR is returned and errmsg is set * with an appropriate message. */ int moduleUnload(sds name, const char **errmsg) { - struct ValkeyModule *module = dictFetchValue(modules,name); + struct RedisModule *module = dictFetchValue(modules,name); if (module == NULL) { *errmsg = "no such module with that name"; @@ -12260,26 +12363,16 @@ int moduleUnload(sds name, const char **errmsg) { } /* Give module a chance to clean up. */ - const char *onUnloadNames[] = {"ValkeyModule_OnUnload", "RedisModule_OnUnload"}; - int (*onunload)(void *) = NULL; - for (size_t i = 0; i < sizeof(onUnloadNames) / sizeof(onUnloadNames[0]); i++) { - onunload = (int (*)(void *))(unsigned long)dlsym(module->handle, onUnloadNames[i]); - if (onunload) { - if (i != 0) { - serverLog(LL_NOTICE, "Legacy Redis Module %s found", name); - } - break; - } - } - + int (*onunload)(void *); + onunload = (int (*)(void *))(unsigned long) dlsym(module->handle, "RedisModule_OnUnload"); if (onunload) { - ValkeyModuleCtx ctx; - moduleCreateContext(&ctx, module, VALKEYMODULE_CTX_TEMP_CLIENT); + RedisModuleCtx ctx; + moduleCreateContext(&ctx, module, REDISMODULE_CTX_TEMP_CLIENT); int unload_status = onunload((void*)&ctx); moduleFreeContext(&ctx); - if (unload_status == VALKEYMODULE_ERR) { - serverLog(LL_WARNING, "Module %s OnUnload failed. Unload canceled.", name); + if (unload_status == REDISMODULE_ERR) { + serverLog(LL_WARNING, "Module %s OnUnload failed. Unload canceled.", name); errno = ECANCELED; return C_ERR; } @@ -12296,8 +12389,8 @@ int moduleUnload(sds name, const char **errmsg) { } /* Fire the unloaded modules event. */ - moduleFireServerEvent(VALKEYMODULE_EVENT_MODULE_CHANGE, - VALKEYMODULE_SUBEVENT_MODULE_UNLOADED, + moduleFireServerEvent(REDISMODULE_EVENT_MODULE_CHANGE, + REDISMODULE_SUBEVENT_MODULE_UNLOADED, module); /* Remove from list of modules. */ @@ -12333,7 +12426,7 @@ void addReplyLoadedModules(client *c) { addReplyArrayLen(c,dictSize(modules)); while ((de = dictNext(di)) != NULL) { sds name = dictGetKey(de); - struct ValkeyModule *module = dictGetVal(de); + struct RedisModule *module = dictGetVal(de); sds path = module->loadmod->path; addReplyMapLen(c,4); addReplyBulkCString(c,"name"); @@ -12359,7 +12452,7 @@ sds genModulesInfoStringRenderModulesList(list *l) { listRewind(l,&li); sds output = sdsnew("["); while((ln = listNext(&li))) { - ValkeyModule *module = ln->value; + RedisModule *module = ln->value; output = sdscat(output,module->name); if (ln != listLast(l)) output = sdscat(output,"|"); @@ -12369,13 +12462,13 @@ sds genModulesInfoStringRenderModulesList(list *l) { } /* Helper for genModulesInfoString(): render module options as an SDS string. */ -sds genModulesInfoStringRenderModuleOptions(struct ValkeyModule *module) { +sds genModulesInfoStringRenderModuleOptions(struct RedisModule *module) { sds output = sdsnew("["); - if (module->options & VALKEYMODULE_OPTIONS_HANDLE_IO_ERRORS) + if (module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS) output = sdscat(output,"handle-io-errors|"); - if (module->options & VALKEYMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD) + if (module->options & REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD) output = sdscat(output,"handle-repl-async-load|"); - if (module->options & VALKEYMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED) + if (module->options & REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED) output = sdscat(output,"no-implicit-signal-modified|"); output = sdstrim(output,"|"); output = sdscat(output,"]"); @@ -12387,7 +12480,6 @@ sds genModulesInfoStringRenderModuleOptions(struct ValkeyModule *module) { * output. * * After the call, the passed sds info string is no longer valid and all the - * * references must be substituted with the new pointer returned by the call. */ sds genModulesInfoString(sds info) { dictIterator *di = dictGetIterator(modules); @@ -12395,7 +12487,7 @@ sds genModulesInfoString(sds info) { while ((de = dictNext(di)) != NULL) { sds name = dictGetKey(de); - struct ValkeyModule *module = dictGetVal(de); + struct RedisModule *module = dictGetVal(de); sds usedby = genModulesInfoStringRenderModulesList(module->usedby); sds using = genModulesInfoStringRenderModulesList(module->using); @@ -12418,41 +12510,43 @@ sds genModulesInfoString(sds info) { * -------------------------------------------------------------------------- */ /* Check if the configuration name is already registered */ -int isModuleConfigNameRegistered(ValkeyModule *module, sds name) { +int isModuleConfigNameRegistered(RedisModule *module, const char *name) { listNode *match = listSearchKey(module->module_configs, (void *) name); return match != NULL; } -/* Assert that the flags passed into the VM_RegisterConfig Suite are valid */ +/* Assert that the flags passed into the RM_RegisterConfig Suite are valid */ int moduleVerifyConfigFlags(unsigned int flags, configType type) { - if ((flags & ~(VALKEYMODULE_CONFIG_DEFAULT - | VALKEYMODULE_CONFIG_IMMUTABLE - | VALKEYMODULE_CONFIG_SENSITIVE - | VALKEYMODULE_CONFIG_HIDDEN - | VALKEYMODULE_CONFIG_PROTECTED - | VALKEYMODULE_CONFIG_DENY_LOADING - | VALKEYMODULE_CONFIG_BITFLAGS - | VALKEYMODULE_CONFIG_MEMORY))) { + if ((flags & ~(REDISMODULE_CONFIG_DEFAULT + | REDISMODULE_CONFIG_IMMUTABLE + | REDISMODULE_CONFIG_SENSITIVE + | REDISMODULE_CONFIG_HIDDEN + | REDISMODULE_CONFIG_PROTECTED + | REDISMODULE_CONFIG_DENY_LOADING + | REDISMODULE_CONFIG_BITFLAGS + | REDISMODULE_CONFIG_MEMORY))) { serverLogRaw(LL_WARNING, "Invalid flag(s) for configuration"); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - if (type != NUMERIC_CONFIG && flags & VALKEYMODULE_CONFIG_MEMORY) { + if (type != NUMERIC_CONFIG && flags & REDISMODULE_CONFIG_MEMORY) { serverLogRaw(LL_WARNING, "Numeric flag provided for non-numeric configuration."); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - if (type != ENUM_CONFIG && flags & VALKEYMODULE_CONFIG_BITFLAGS) { + if (type != ENUM_CONFIG && flags & REDISMODULE_CONFIG_BITFLAGS) { serverLogRaw(LL_WARNING, "Enum flag provided for non-enum configuration."); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -int moduleVerifyConfigName(sds name) { - if (sdslen(name) == 0) { - serverLogRaw(LL_WARNING, "Module config names cannot be an empty string."); - return VALKEYMODULE_ERR; +/* Verify a module resource or name has only alphanumeric characters, underscores + * or dashes. */ +int moduleVerifyResourceName(const char *name) { + if (name[0] == '\0') { + return REDISMODULE_ERR; } - for (size_t i = 0 ; i < sdslen(name) ; ++i) { + + for (size_t i = 0; name[i] != '\0'; i++) { char curr_char = name[i]; if ((curr_char >= 'a' && curr_char <= 'z') || (curr_char >= 'A' && curr_char <= 'Z') || @@ -12461,17 +12555,17 @@ int moduleVerifyConfigName(sds name) { { continue; } - serverLog(LL_WARNING, "Invalid character %c in Module Config name %s.", curr_char, name); - return VALKEYMODULE_ERR; + serverLog(LL_WARNING, "Invalid character %c in Module resource name %s.", curr_char, name); + return REDISMODULE_ERR; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* 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]; -static void propagateErrorString(ValkeyModuleString *err_in, const char **err) { +static void propagateErrorString(RedisModuleString *err_in, const char **err) { if (err_in) { redis_strlcpy(configerr, err_in->ptr, CONFIG_ERR_SIZE); decrRefCount(err_in); @@ -12480,33 +12574,33 @@ static void propagateErrorString(ValkeyModuleString *err_in, const char **err) { } int setModuleBoolConfig(ModuleConfig *config, int val, const char **err) { - ValkeyModuleString *error = NULL; + RedisModuleString *error = NULL; int return_code = config->set_fn.set_bool(config->name, val, config->privdata, &error); propagateErrorString(error, err); - return return_code == VALKEYMODULE_OK ? 1 : 0; + return return_code == REDISMODULE_OK ? 1 : 0; } int setModuleStringConfig(ModuleConfig *config, sds strval, const char **err) { - ValkeyModuleString *error = NULL; - ValkeyModuleString *new = createStringObject(strval, sdslen(strval)); + RedisModuleString *error = NULL; + RedisModuleString *new = createStringObject(strval, sdslen(strval)); int return_code = config->set_fn.set_string(config->name, new, config->privdata, &error); propagateErrorString(error, err); decrRefCount(new); - return return_code == VALKEYMODULE_OK ? 1 : 0; + return return_code == REDISMODULE_OK ? 1 : 0; } int setModuleEnumConfig(ModuleConfig *config, int val, const char **err) { - ValkeyModuleString *error = NULL; + RedisModuleString *error = NULL; int return_code = config->set_fn.set_enum(config->name, val, config->privdata, &error); propagateErrorString(error, err); - return return_code == VALKEYMODULE_OK ? 1 : 0; + return return_code == REDISMODULE_OK ? 1 : 0; } int setModuleNumericConfig(ModuleConfig *config, long long val, const char **err) { - ValkeyModuleString *error = NULL; + RedisModuleString *error = NULL; int return_code = config->set_fn.set_numeric(config->name, val, config->privdata, &error); propagateErrorString(error, err); - return return_code == VALKEYMODULE_OK ? 1 : 0; + return return_code == REDISMODULE_OK ? 1 : 0; } /* This is a series of get functions for each type that act as dispatchers for @@ -12516,7 +12610,7 @@ int getModuleBoolConfig(ModuleConfig *module_config) { } sds getModuleStringConfig(ModuleConfig *module_config) { - ValkeyModuleString *val = module_config->get_fn.get_string(module_config->name, module_config->privdata); + RedisModuleString *val = module_config->get_fn.get_string(module_config->name, module_config->privdata); return val ? sdsdup(val->ptr) : NULL; } @@ -12530,7 +12624,7 @@ long long getModuleNumericConfig(ModuleConfig *module_config) { /* This function takes a module and a list of configs stored as sds NAME VALUE pairs. * It attempts to call set on each of these configs. */ -int loadModuleConfigs(ValkeyModule *module) { +int loadModuleConfigs(RedisModule *module) { listIter li; listNode *ln; const char *err = NULL; @@ -12544,21 +12638,21 @@ int loadModuleConfigs(ValkeyModule *module) { serverLog(LL_WARNING, "Issue during loading of configuration %s : %s", (sds) dictGetKey(config_argument), err); sdsfree(config_name); dictEmpty(server.module_configs_queue, NULL); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } } else { if (!performModuleConfigSetDefaultFromName(config_name, &err)) { serverLog(LL_WARNING, "Issue attempting to set default value of configuration %s : %s", module_config->name, err); sdsfree(config_name); dictEmpty(server.module_configs_queue, NULL); - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } } dictDelete(server.module_configs_queue, config_name); sdsfree(config_name); } module->configs_initialized = 1; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Add module_config to the list if the apply and privdata do not match one already in it. */ @@ -12583,13 +12677,13 @@ int moduleConfigApplyConfig(list *module_configs, const char **err, const char * listIter li; listNode *ln; ModuleConfig *module_config; - ValkeyModuleString *error = NULL; - ValkeyModuleCtx ctx; + RedisModuleString *error = NULL; + RedisModuleCtx ctx; listRewind(module_configs, &li); while ((ln = listNext(&li))) { module_config = listNodeValue(ln); - moduleCreateContext(&ctx, module_config->module, VALKEYMODULE_CTX_NONE); + moduleCreateContext(&ctx, module_config->module, REDISMODULE_CTX_NONE); if (module_config->apply_fn(&ctx, module_config->privdata, &error)) { if (err_arg_name) *err_arg_name = module_config->name; propagateErrorString(error, err); @@ -12606,51 +12700,51 @@ int moduleConfigApplyConfig(list *module_configs, const char **err, const char * * -------------------------------------------------------------------------- */ /* Create a module config object. */ -ModuleConfig *createModuleConfig(sds name, ValkeyModuleConfigApplyFunc apply_fn, void *privdata, ValkeyModule *module) { +ModuleConfig *createModuleConfig(const char *name, RedisModuleConfigApplyFunc apply_fn, void *privdata, RedisModule *module) { ModuleConfig *new_config = zmalloc(sizeof(ModuleConfig)); - new_config->name = sdsdup(name); + new_config->name = sdsnew(name); new_config->apply_fn = apply_fn; new_config->privdata = privdata; new_config->module = module; return new_config; } -int moduleConfigValidityCheck(ValkeyModule *module, sds name, unsigned int flags, configType type) { +int moduleConfigValidityCheck(RedisModule *module, const char *name, unsigned int flags, configType type) { if (!module->onload) { errno = EBUSY; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - if (moduleVerifyConfigFlags(flags, type) || moduleVerifyConfigName(name)) { + if (moduleVerifyConfigFlags(flags, type) || moduleVerifyResourceName(name)) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } if (isModuleConfigNameRegistered(module, name)) { serverLog(LL_WARNING, "Configuration by the name: %s already registered", name); errno = EALREADY; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - return VALKEYMODULE_OK; + return REDISMODULE_OK; } unsigned int maskModuleConfigFlags(unsigned int flags) { unsigned int new_flags = 0; - if (flags & VALKEYMODULE_CONFIG_DEFAULT) new_flags |= MODIFIABLE_CONFIG; - if (flags & VALKEYMODULE_CONFIG_IMMUTABLE) new_flags |= IMMUTABLE_CONFIG; - if (flags & VALKEYMODULE_CONFIG_HIDDEN) new_flags |= HIDDEN_CONFIG; - if (flags & VALKEYMODULE_CONFIG_PROTECTED) new_flags |= PROTECTED_CONFIG; - if (flags & VALKEYMODULE_CONFIG_DENY_LOADING) new_flags |= DENY_LOADING_CONFIG; + if (flags & REDISMODULE_CONFIG_DEFAULT) new_flags |= MODIFIABLE_CONFIG; + if (flags & REDISMODULE_CONFIG_IMMUTABLE) new_flags |= IMMUTABLE_CONFIG; + if (flags & REDISMODULE_CONFIG_HIDDEN) new_flags |= HIDDEN_CONFIG; + if (flags & REDISMODULE_CONFIG_PROTECTED) new_flags |= PROTECTED_CONFIG; + if (flags & REDISMODULE_CONFIG_DENY_LOADING) new_flags |= DENY_LOADING_CONFIG; return new_flags; } unsigned int maskModuleNumericConfigFlags(unsigned int flags) { unsigned int new_flags = 0; - if (flags & VALKEYMODULE_CONFIG_MEMORY) new_flags |= MEMORY_CONFIG; + if (flags & REDISMODULE_CONFIG_MEMORY) new_flags |= MEMORY_CONFIG; return new_flags; } unsigned int maskModuleEnumConfigFlags(unsigned int flags) { unsigned int new_flags = 0; - if (flags & VALKEYMODULE_CONFIG_BITFLAGS) new_flags |= MULTI_ARG_CONFIG; + if (flags & REDISMODULE_CONFIG_BITFLAGS) new_flags |= MULTI_ARG_CONFIG; return new_flags; } @@ -12677,23 +12771,23 @@ unsigned int maskModuleEnumConfigFlags(unsigned int flags) { * * Numeric: 64 bit signed integer, which also supports min and max values. * * Bool: Yes or no value. * - * The `setfn` callback is expected to return VALKEYMODULE_OK when the value is successfully - * applied. It can also return VALKEYMODULE_ERR if the value can't be applied, and the - * *err pointer can be set with a ValkeyModuleString error message to provide to the client. - * This ValkeyModuleString will be freed by redis after returning from the set callback. + * The `setfn` callback is expected to return REDISMODULE_OK when the value is successfully + * applied. It can also return REDISMODULE_ERR if the value can't be applied, and the + * *err pointer can be set with a RedisModuleString error message to provide to the client. + * This RedisModuleString will be freed by redis after returning from the set callback. * * All configs are registered with a name, a type, a default value, private data that is made * available in the callbacks, as well as several flags that modify the behavior of the config. * The name must only contain alphanumeric characters or dashes. The supported flags are: * - * * VALKEYMODULE_CONFIG_DEFAULT: The default flags for a config. This creates a config that can be modified after startup. - * * VALKEYMODULE_CONFIG_IMMUTABLE: This config can only be provided loading time. - * * VALKEYMODULE_CONFIG_SENSITIVE: The value stored in this config is redacted from all logging. - * * VALKEYMODULE_CONFIG_HIDDEN: The name is hidden from `CONFIG GET` with pattern matching. - * * VALKEYMODULE_CONFIG_PROTECTED: This config will be only be modifiable based off the value of enable-protected-configs. - * * VALKEYMODULE_CONFIG_DENY_LOADING: This config is not modifiable while the server is loading data. - * * VALKEYMODULE_CONFIG_MEMORY: For numeric configs, this config will convert data unit notations into their byte equivalent. - * * VALKEYMODULE_CONFIG_BITFLAGS: For enum configs, this config will allow multiple entries to be combined as bit flags. + * * REDISMODULE_CONFIG_DEFAULT: The default flags for a config. This creates a config that can be modified after startup. + * * REDISMODULE_CONFIG_IMMUTABLE: This config can only be provided loading time. + * * REDISMODULE_CONFIG_SENSITIVE: The value stored in this config is redacted from all logging. + * * REDISMODULE_CONFIG_HIDDEN: The name is hidden from `CONFIG GET` with pattern matching. + * * REDISMODULE_CONFIG_PROTECTED: This config will be only be modifiable based off the value of enable-protected-configs. + * * REDISMODULE_CONFIG_DENY_LOADING: This config is not modifiable while the server is loading data. + * * REDISMODULE_CONFIG_MEMORY: For numeric configs, this config will convert data unit notations into their byte equivalent. + * * REDISMODULE_CONFIG_BITFLAGS: For enum configs, this config will allow multiple entries to be combined as bit flags. * * Default values are used on startup to set the value if it is not provided via the config file * or command line. Default values are also used to compare to on a config rewrite. @@ -12705,65 +12799,59 @@ unsigned int maskModuleEnumConfigFlags(unsigned int flags) { * * Example implementation: * - * ValkeyModuleString *strval; + * RedisModuleString *strval; * int adjustable = 1; - * ValkeyModuleString *getStringConfigCommand(const char *name, void *privdata) { + * RedisModuleString *getStringConfigCommand(const char *name, void *privdata) { * return strval; * } * - * int setStringConfigCommand(const char *name, ValkeyModuleString *new, void *privdata, ValkeyModuleString **err) { + * int setStringConfigCommand(const char *name, RedisModuleString *new, void *privdata, RedisModuleString **err) { * if (adjustable) { - * ValkeyModule_Free(strval); - * ValkeyModule_RetainString(NULL, new); + * RedisModule_Free(strval); + * RedisModule_RetainString(NULL, new); * strval = new; - * return VALKEYMODULE_OK; + * return REDISMODULE_OK; * } - * *err = ValkeyModule_CreateString(NULL, "Not adjustable.", 15); - * return VALKEYMODULE_ERR; + * *err = RedisModule_CreateString(NULL, "Not adjustable.", 15); + * return REDISMODULE_ERR; * } * ... - * ValkeyModule_RegisterStringConfig(ctx, "string", NULL, VALKEYMODULE_CONFIG_DEFAULT, getStringConfigCommand, setStringConfigCommand, NULL, NULL); + * RedisModule_RegisterStringConfig(ctx, "string", NULL, REDISMODULE_CONFIG_DEFAULT, getStringConfigCommand, setStringConfigCommand, NULL, NULL); * - * If the registration fails, VALKEYMODULE_ERR is returned and one of the following + * If the registration fails, REDISMODULE_ERR is returned and one of the following * errno is set: - * * EBUSY: Registering the Config outside of ValkeyModule_OnLoad. + * * EBUSY: Registering the Config outside of RedisModule_OnLoad. * * EINVAL: The provided flags are invalid for the registration or the name of the config contains invalid characters. * * EALREADY: The provided configuration name is already used. */ -int VM_RegisterStringConfig(ValkeyModuleCtx *ctx, const char *name, const char *default_val, unsigned int flags, ValkeyModuleConfigGetStringFunc getfn, ValkeyModuleConfigSetStringFunc setfn, ValkeyModuleConfigApplyFunc applyfn, void *privdata) { - ValkeyModule *module = ctx->module; - sds config_name = sdsnew(name); - if (moduleConfigValidityCheck(module, config_name, flags, NUMERIC_CONFIG)) { - sdsfree(config_name); - return VALKEYMODULE_ERR; +int RM_RegisterStringConfig(RedisModuleCtx *ctx, const char *name, const char *default_val, unsigned int flags, RedisModuleConfigGetStringFunc getfn, RedisModuleConfigSetStringFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) { + RedisModule *module = ctx->module; + if (moduleConfigValidityCheck(module, name, flags, NUMERIC_CONFIG)) { + return REDISMODULE_ERR; } - ModuleConfig *new_config = createModuleConfig(config_name, applyfn, privdata, module); - sdsfree(config_name); + ModuleConfig *new_config = createModuleConfig(name, applyfn, privdata, module); new_config->get_fn.get_string = getfn; new_config->set_fn.set_string = setfn; listAddNodeTail(module->module_configs, new_config); flags = maskModuleConfigFlags(flags); addModuleStringConfig(module->name, name, flags, new_config, default_val ? sdsnew(default_val) : NULL); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Create a bool config that server clients can interact with via the * `CONFIG SET`, `CONFIG GET`, and `CONFIG REWRITE` commands. See - * ValkeyModule_RegisterStringConfig for detailed information about configs. */ -int VM_RegisterBoolConfig(ValkeyModuleCtx *ctx, const char *name, int default_val, unsigned int flags, ValkeyModuleConfigGetBoolFunc getfn, ValkeyModuleConfigSetBoolFunc setfn, ValkeyModuleConfigApplyFunc applyfn, void *privdata) { - ValkeyModule *module = ctx->module; - sds config_name = sdsnew(name); - if (moduleConfigValidityCheck(module, config_name, flags, BOOL_CONFIG)) { - sdsfree(config_name); - return VALKEYMODULE_ERR; + * 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; + if (moduleConfigValidityCheck(module, name, flags, BOOL_CONFIG)) { + return REDISMODULE_ERR; } - ModuleConfig *new_config = createModuleConfig(config_name, applyfn, privdata, module); - sdsfree(config_name); + ModuleConfig *new_config = createModuleConfig(name, applyfn, privdata, module); new_config->get_fn.get_bool = getfn; new_config->set_fn.set_bool = setfn; listAddNodeTail(module->module_configs, new_config); flags = maskModuleConfigFlags(flags); addModuleBoolConfig(module->name, name, flags, new_config, default_val); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* @@ -12785,25 +12873,22 @@ int VM_RegisterBoolConfig(ValkeyModuleCtx *ctx, const char *name, int default_va * * int setEnumConfigCommand(const char *name, int val, void *privdata, const char **err) { * enum_val = val; - * return VALKEYMODULE_OK; + * return REDISMODULE_OK; * } * ... - * ValkeyModule_RegisterEnumConfig(ctx, "enum", 0, VALKEYMODULE_CONFIG_DEFAULT, enum_vals, int_vals, 3, getEnumConfigCommand, setEnumConfigCommand, NULL, NULL); + * RedisModule_RegisterEnumConfig(ctx, "enum", 0, REDISMODULE_CONFIG_DEFAULT, enum_vals, int_vals, 3, getEnumConfigCommand, setEnumConfigCommand, NULL, NULL); * - * Note that you can use VALKEYMODULE_CONFIG_BITFLAGS so that multiple enum string + * Note that you can use REDISMODULE_CONFIG_BITFLAGS so that multiple enum string * can be combined into one integer as bit flags, in which case you may want to * sort your enums so that the preferred combinations are present first. * - * See ValkeyModule_RegisterStringConfig for detailed general information about configs. */ -int VM_RegisterEnumConfig(ValkeyModuleCtx *ctx, const char *name, int default_val, unsigned int flags, const char **enum_values, const int *int_values, int num_enum_vals, ValkeyModuleConfigGetEnumFunc getfn, ValkeyModuleConfigSetEnumFunc setfn, ValkeyModuleConfigApplyFunc applyfn, void *privdata) { - ValkeyModule *module = ctx->module; - sds config_name = sdsnew(name); - if (moduleConfigValidityCheck(module, config_name, flags, ENUM_CONFIG)) { - sdsfree(config_name); - return VALKEYMODULE_ERR; + * See RedisModule_RegisterStringConfig for detailed general information about configs. */ +int RM_RegisterEnumConfig(RedisModuleCtx *ctx, const char *name, int default_val, unsigned int flags, const char **enum_values, const int *int_values, int num_enum_vals, RedisModuleConfigGetEnumFunc getfn, RedisModuleConfigSetEnumFunc setfn, RedisModuleConfigApplyFunc applyfn, void *privdata) { + RedisModule *module = ctx->module; + if (moduleConfigValidityCheck(module, name, flags, ENUM_CONFIG)) { + return REDISMODULE_ERR; } - ModuleConfig *new_config = createModuleConfig(config_name, applyfn, privdata, module); - sdsfree(config_name); + ModuleConfig *new_config = createModuleConfig(name, applyfn, privdata, module); new_config->get_fn.get_enum = getfn; new_config->set_fn.set_enum = setfn; configEnum *enum_vals = zmalloc((num_enum_vals + 1) * sizeof(configEnum)); @@ -12816,76 +12901,73 @@ int VM_RegisterEnumConfig(ValkeyModuleCtx *ctx, const char *name, int default_va listAddNodeTail(module->module_configs, new_config); flags = maskModuleConfigFlags(flags) | maskModuleEnumConfigFlags(flags); addModuleEnumConfig(module->name, name, flags, new_config, default_val, enum_vals); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* * Create an integer config that server clients can interact with via the * `CONFIG SET`, `CONFIG GET`, and `CONFIG REWRITE` commands. See - * ValkeyModule_RegisterStringConfig for detailed information about configs. */ -int VM_RegisterNumericConfig(ValkeyModuleCtx *ctx, const char *name, long long default_val, unsigned int flags, long long min, long long max, ValkeyModuleConfigGetNumericFunc getfn, ValkeyModuleConfigSetNumericFunc setfn, ValkeyModuleConfigApplyFunc applyfn, void *privdata) { - ValkeyModule *module = ctx->module; - sds config_name = sdsnew(name); - if (moduleConfigValidityCheck(module, config_name, flags, NUMERIC_CONFIG)) { - sdsfree(config_name); - return VALKEYMODULE_ERR; + * 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; + if (moduleConfigValidityCheck(module, name, flags, NUMERIC_CONFIG)) { + return REDISMODULE_ERR; } - ModuleConfig *new_config = createModuleConfig(config_name, applyfn, privdata, module); - sdsfree(config_name); + ModuleConfig *new_config = createModuleConfig(name, applyfn, privdata, module); new_config->get_fn.get_numeric = getfn; new_config->set_fn.set_numeric = setfn; listAddNodeTail(module->module_configs, new_config); unsigned int numeric_flags = maskModuleNumericConfigFlags(flags); flags = maskModuleConfigFlags(flags); addModuleNumericConfig(module->name, name, flags, new_config, default_val, numeric_flags, min, max); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Applies all pending configurations on the module load. This should be called - * after all of the configurations have been registered for the module inside of ValkeyModule_OnLoad. - * This will return VALKEYMODULE_ERR if it is called outside ValkeyModule_OnLoad. + * after all of the configurations have been registered for the module inside of RedisModule_OnLoad. + * This will return REDISMODULE_ERR if it is called outside RedisModule_OnLoad. * This API needs to be called when configurations are provided in either `MODULE LOADEX` * or provided as startup arguments. */ -int VM_LoadConfigs(ValkeyModuleCtx *ctx) { +int RM_LoadConfigs(RedisModuleCtx *ctx) { if (!ctx || !ctx->module || !ctx->module->onload) { - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - ValkeyModule *module = ctx->module; + RedisModule *module = ctx->module; /* Load configs from conf file or arguments from loadex */ - if (loadModuleConfigs(module)) return VALKEYMODULE_ERR; - return VALKEYMODULE_OK; + if (loadModuleConfigs(module)) return REDISMODULE_ERR; + return REDISMODULE_OK; } /* -------------------------------------------------------------------------- * ## RDB load/save API * -------------------------------------------------------------------------- */ -#define VALKEYMODULE_RDB_STREAM_FILE 1 +#define REDISMODULE_RDB_STREAM_FILE 1 -typedef struct ValkeyModuleRdbStream { +typedef struct RedisModuleRdbStream { int type; union { char *filename; } data; -} ValkeyModuleRdbStream; +} RedisModuleRdbStream; /* Create a stream object to save/load RDB to/from a file. * - * This function returns a pointer to ValkeyModuleRdbStream which is owned - * by the caller. It requires a call to VM_RdbStreamFree() to free + * This function returns a pointer to RedisModuleRdbStream which is owned + * by the caller. It requires a call to RM_RdbStreamFree() to free * the object. */ -ValkeyModuleRdbStream *VM_RdbStreamCreateFromFile(const char *filename) { - ValkeyModuleRdbStream *stream = zmalloc(sizeof(*stream)); - stream->type = VALKEYMODULE_RDB_STREAM_FILE; +RedisModuleRdbStream *RM_RdbStreamCreateFromFile(const char *filename) { + RedisModuleRdbStream *stream = zmalloc(sizeof(*stream)); + stream->type = REDISMODULE_RDB_STREAM_FILE; stream->data.filename = zstrdup(filename); return stream; } /* Release an RDB stream object. */ -void VM_RdbStreamFree(ValkeyModuleRdbStream *stream) { +void RM_RdbStreamFree(RedisModuleRdbStream *stream) { switch (stream->type) { - case VALKEYMODULE_RDB_STREAM_FILE: + case REDISMODULE_RDB_STREAM_FILE: zfree(stream->data.filename); break; default: @@ -12900,27 +12982,27 @@ void VM_RdbStreamFree(ValkeyModuleRdbStream *stream) { * * `flags` must be zero. This parameter is for future use. * - * On success VALKEYMODULE_OK is returned, otherwise VALKEYMODULE_ERR is returned + * On success REDISMODULE_OK is returned, otherwise REDISMODULE_ERR is returned * and errno is set accordingly. * * Example: * - * ValkeyModuleRdbStream *s = ValkeyModule_RdbStreamCreateFromFile("exp.rdb"); - * ValkeyModule_RdbLoad(ctx, s, 0); - * ValkeyModule_RdbStreamFree(s); + * RedisModuleRdbStream *s = RedisModule_RdbStreamCreateFromFile("exp.rdb"); + * RedisModule_RdbLoad(ctx, s, 0); + * RedisModule_RdbStreamFree(s); */ -int VM_RdbLoad(ValkeyModuleCtx *ctx, ValkeyModuleRdbStream *stream, int flags) { +int RM_RdbLoad(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags) { UNUSED(ctx); if (!stream || flags != 0) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } /* Not allowed on replicas. */ if (server.masterhost != NULL) { errno = ENOTSUP; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } /* Drop replicas if exist. */ @@ -12936,12 +13018,12 @@ int VM_RdbLoad(ValkeyModuleCtx *ctx, ValkeyModuleRdbStream *stream, int flags) { emptyData(-1,EMPTYDB_NO_FLAGS,NULL); /* rdbLoad() can go back to the networking and process network events. If - * VM_RdbLoad() is called inside a command callback, we don't want to + * RM_RdbLoad() is called inside a command callback, we don't want to * process the current client. Otherwise, we may free the client or try to * process next message while we are already in the command callback. */ if (server.current_client) protectClient(server.current_client); - serverAssert(stream->type == VALKEYMODULE_RDB_STREAM_FILE); + serverAssert(stream->type == REDISMODULE_RDB_STREAM_FILE); int ret = rdbLoad(stream->data.filename,NULL,RDBFLAGS_NONE); if (server.current_client) unprotectClient(server.current_client); @@ -12949,42 +13031,42 @@ int VM_RdbLoad(ValkeyModuleCtx *ctx, ValkeyModuleRdbStream *stream, int flags) { if (ret != RDB_OK) { errno = (ret == RDB_NOT_EXIST) ? ENOENT : EIO; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } errno = 0; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Save dataset to the RDB stream. * * `flags` must be zero. This parameter is for future use. * - * On success VALKEYMODULE_OK is returned, otherwise VALKEYMODULE_ERR is returned + * On success REDISMODULE_OK is returned, otherwise REDISMODULE_ERR is returned * and errno is set accordingly. * * Example: * - * ValkeyModuleRdbStream *s = ValkeyModule_RdbStreamCreateFromFile("exp.rdb"); - * ValkeyModule_RdbSave(ctx, s, 0); - * ValkeyModule_RdbStreamFree(s); + * RedisModuleRdbStream *s = RedisModule_RdbStreamCreateFromFile("exp.rdb"); + * RedisModule_RdbSave(ctx, s, 0); + * RedisModule_RdbStreamFree(s); */ -int VM_RdbSave(ValkeyModuleCtx *ctx, ValkeyModuleRdbStream *stream, int flags) { +int RM_RdbSave(RedisModuleCtx *ctx, RedisModuleRdbStream *stream, int flags) { UNUSED(ctx); if (!stream || flags != 0) { errno = EINVAL; - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } - serverAssert(stream->type == VALKEYMODULE_RDB_STREAM_FILE); + serverAssert(stream->type == REDISMODULE_RDB_STREAM_FILE); if (rdbSaveToFile(stream->data.filename) != C_OK) { - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; } errno = 0; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Redis MODULE command. @@ -13034,7 +13116,7 @@ NULL } /* 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((ValkeyModuleString ***) &argv, &argc) == VALKEYMODULE_OK && + if (parseLoadexArguments((RedisModuleString ***) &argv, &argc) == REDISMODULE_OK && moduleLoad(c->argv[2]->ptr, (void **)argv, argc, 1) == C_OK) addReply(c,shared.ok); else { @@ -13071,52 +13153,52 @@ size_t moduleCount(void) { /* Set the key last access time for LRU based eviction. not relevant if the * servers's maxmemory policy is LFU based. Value is idle time in milliseconds. - * returns VALKEYMODULE_OK if the LRU was updated, VALKEYMODULE_ERR otherwise. */ -int VM_SetLRU(ValkeyModuleKey *key, mstime_t lru_idle) { + * returns REDISMODULE_OK if the LRU was updated, REDISMODULE_ERR otherwise. */ +int RM_SetLRU(RedisModuleKey *key, mstime_t lru_idle) { if (!key->value) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; if (objectSetLRUOrLFU(key->value, -1, lru_idle, lru_idle>=0 ? LRU_CLOCK() : 0, 1)) - return VALKEYMODULE_OK; - return VALKEYMODULE_ERR; + return REDISMODULE_OK; + return REDISMODULE_ERR; } /* Gets the key last access time. * Value is idletime in milliseconds or -1 if the server's eviction policy is * LFU based. - * returns VALKEYMODULE_OK if when key is valid. */ -int VM_GetLRU(ValkeyModuleKey *key, mstime_t *lru_idle) { + * returns REDISMODULE_OK if when key is valid. */ +int RM_GetLRU(RedisModuleKey *key, mstime_t *lru_idle) { *lru_idle = -1; if (!key->value) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) - return VALKEYMODULE_OK; + return REDISMODULE_OK; *lru_idle = estimateObjectIdleTime(key->value); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* Set the key access frequency. only relevant if the server's maxmemory policy * is LFU based. * The frequency is a logarithmic counter that provides an indication of * the access frequencyonly (must be <= 255). - * returns VALKEYMODULE_OK if the LFU was updated, VALKEYMODULE_ERR otherwise. */ -int VM_SetLFU(ValkeyModuleKey *key, long long lfu_freq) { + * returns REDISMODULE_OK if the LFU was updated, REDISMODULE_ERR otherwise. */ +int RM_SetLFU(RedisModuleKey *key, long long lfu_freq) { if (!key->value) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; if (objectSetLRUOrLFU(key->value, lfu_freq, -1, 0, 1)) - return VALKEYMODULE_OK; - return VALKEYMODULE_ERR; + return REDISMODULE_OK; + return REDISMODULE_ERR; } /* Gets the key access frequency or -1 if the server's eviction policy is not * LFU based. - * returns VALKEYMODULE_OK if when key is valid. */ -int VM_GetLFU(ValkeyModuleKey *key, long long *lfu_freq) { + * returns REDISMODULE_OK if when key is valid. */ +int RM_GetLFU(RedisModuleKey *key, long long *lfu_freq) { *lfu_freq = -1; if (!key->value) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) *lfu_freq = LFUDecrAndReturn(key->value); - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* -------------------------------------------------------------------------- @@ -13129,15 +13211,15 @@ int VM_GetLFU(ValkeyModuleKey *key, long long *lfu_freq) { * by the redis server version in use. * Example: * - * int supportedFlags = VM_GetModuleOptionsAll(); - * if (supportedFlags & VALKEYMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS) { - * // VALKEYMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS is supported + * int supportedFlags = RM_GetModuleOptionsAll(); + * if (supportedFlags & REDISMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS) { + * // REDISMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS is supported * } else{ - * // VALKEYMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS is not supported + * // REDISMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS is not supported * } */ -int VM_GetModuleOptionsAll(void) { - return _VALKEYMODULE_OPTIONS_FLAGS_NEXT - 1; +int RM_GetModuleOptionsAll(void) { + return _REDISMODULE_OPTIONS_FLAGS_NEXT - 1; } /** @@ -13146,15 +13228,15 @@ int VM_GetModuleOptionsAll(void) { * by the redis server version in use. * Example: * - * int supportedFlags = VM_GetContextFlagsAll(); - * if (supportedFlags & VALKEYMODULE_CTX_FLAGS_MULTI) { - * // VALKEYMODULE_CTX_FLAGS_MULTI is supported + * int supportedFlags = RM_GetContextFlagsAll(); + * if (supportedFlags & REDISMODULE_CTX_FLAGS_MULTI) { + * // REDISMODULE_CTX_FLAGS_MULTI is supported * } else{ - * // VALKEYMODULE_CTX_FLAGS_MULTI is not supported + * // REDISMODULE_CTX_FLAGS_MULTI is not supported * } */ -int VM_GetContextFlagsAll(void) { - return _VALKEYMODULE_CTX_FLAGS_NEXT - 1; +int RM_GetContextFlagsAll(void) { + return _REDISMODULE_CTX_FLAGS_NEXT - 1; } /** @@ -13163,32 +13245,32 @@ int VM_GetContextFlagsAll(void) { * by the redis server version in use. * Example: * - * int supportedFlags = VM_GetKeyspaceNotificationFlagsAll(); - * if (supportedFlags & VALKEYMODULE_NOTIFY_LOADED) { - * // VALKEYMODULE_NOTIFY_LOADED is supported + * int supportedFlags = RM_GetKeyspaceNotificationFlagsAll(); + * if (supportedFlags & REDISMODULE_NOTIFY_LOADED) { + * // REDISMODULE_NOTIFY_LOADED is supported * } else{ - * // VALKEYMODULE_NOTIFY_LOADED is not supported + * // REDISMODULE_NOTIFY_LOADED is not supported * } */ -int VM_GetKeyspaceNotificationFlagsAll(void) { - return _VALKEYMODULE_NOTIFY_NEXT - 1; +int RM_GetKeyspaceNotificationFlagsAll(void) { + return _REDISMODULE_NOTIFY_NEXT - 1; } /** * Return the redis version in format of 0x00MMmmpp. * Example for 6.0.7 the return value will be 0x00060007. */ -int VM_GetServerVersion(void) { - return VALKEY_VERSION_NUM; +int RM_GetServerVersion(void) { + return REDIS_VERSION_NUM; } /** - * Return the current redis-server runtime value of VALKEYMODULE_TYPE_METHOD_VERSION. - * You can use that when calling VM_CreateDataType to know which fields of - * ValkeyModuleTypeMethods are gonna be supported and which will be ignored. + * Return the current redis-server runtime value of REDISMODULE_TYPE_METHOD_VERSION. + * You can use that when calling RM_CreateDataType to know which fields of + * RedisModuleTypeMethods are gonna be supported and which will be ignored. */ -int VM_GetTypeMethodVersion(void) { - return VALKEYMODULE_TYPE_METHOD_VERSION; +int RM_GetTypeMethodVersion(void) { + return REDISMODULE_TYPE_METHOD_VERSION; } /* Replace the value assigned to a module type. @@ -13196,10 +13278,10 @@ int VM_GetTypeMethodVersion(void) { * The key must be open for writing, have an existing value, and have a moduleType * that matches the one specified by the caller. * - * Unlike VM_ModuleTypeSetValue() which will free the old value, this function + * Unlike RM_ModuleTypeSetValue() which will free the old value, this function * simply swaps the old value with the new value. * - * The function returns VALKEYMODULE_OK on success, VALKEYMODULE_ERR on errors + * The function returns REDISMODULE_OK on success, REDISMODULE_ERR on errors * such as: * * 1. Key is not opened for writing. @@ -13208,21 +13290,21 @@ int VM_GetTypeMethodVersion(void) { * * If old_value is non-NULL, the old value is returned by reference. */ -int VM_ModuleTypeReplaceValue(ValkeyModuleKey *key, moduleType *mt, void *new_value, void **old_value) { - if (!(key->mode & VALKEYMODULE_WRITE) || key->iter) - return VALKEYMODULE_ERR; +int RM_ModuleTypeReplaceValue(RedisModuleKey *key, moduleType *mt, void *new_value, void **old_value) { + if (!(key->mode & REDISMODULE_WRITE) || key->iter) + return REDISMODULE_ERR; if (!key->value || key->value->type != OBJ_MODULE) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; moduleValue *mv = key->value->ptr; if (mv->type != mt) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; if (old_value) *old_value = mv->value; mv->value = new_value; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* For a specified command, parse its arguments and return an array that @@ -13230,7 +13312,7 @@ int VM_ModuleTypeReplaceValue(ValkeyModuleKey *key, moduleType *mt, void *new_va * essentially a more efficient way to do `COMMAND GETKEYS`. * * The out_flags argument is optional, and can be set to NULL. - * When provided it is filled with VALKEYMODULE_CMD_KEY_ flags in matching + * When provided it is filled with REDISMODULE_CMD_KEY_ flags in matching * indexes with the key indexes of the returned array. * * A NULL return value indicates the specified command has no keys, or @@ -13242,10 +13324,10 @@ int VM_ModuleTypeReplaceValue(ValkeyModuleKey *key, moduleType *mt, void *new_va * * NOTE: The returned array is not a Redis Module object so it does not * get automatically freed even when auto-memory is used. The caller - * must explicitly call VM_Free() to free it, same as the out_flags pointer if + * must explicitly call RM_Free() to free it, same as the out_flags pointer if * used. */ -int *VM_GetCommandKeysWithFlags(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc, int *num_keys, int **out_flags) { +int *RM_GetCommandKeysWithFlags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys, int **out_flags) { UNUSED(ctx); struct redisCommand *cmd; int *res = NULL; @@ -13291,13 +13373,13 @@ int *VM_GetCommandKeysWithFlags(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, return res; } -/* Identical to VM_GetCommandKeysWithFlags when flags are not needed. */ -int *VM_GetCommandKeys(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc, int *num_keys) { - return VM_GetCommandKeysWithFlags(ctx, argv, argc, num_keys, NULL); +/* Identical to RM_GetCommandKeysWithFlags when flags are not needed. */ +int *RM_GetCommandKeys(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys) { + return RM_GetCommandKeysWithFlags(ctx, argv, argc, num_keys, NULL); } /* Return the name of the command currently running */ -const char *VM_GetCurrentCommandName(ValkeyModuleCtx *ctx) { +const char *RM_GetCurrentCommandName(RedisModuleCtx *ctx) { if (!ctx || !ctx->client || !ctx->client->cmd) return NULL; @@ -13311,7 +13393,7 @@ const char *VM_GetCurrentCommandName(ValkeyModuleCtx *ctx) { /* The defrag context, used to manage state during calls to the data type * defrag callback. */ -struct ValkeyModuleDefragCtx { +struct RedisModuleDefragCtx { long long int endtime; unsigned long *cursor; struct redisObject *key; /* Optional name of key processed, NULL when unknown. */ @@ -13321,9 +13403,9 @@ struct ValkeyModuleDefragCtx { /* Register a defrag callback for global data, i.e. anything that the module * may allocate that is not tied to a specific data type. */ -int VM_RegisterDefragFunc(ValkeyModuleCtx *ctx, ValkeyModuleDefragFunc cb) { +int RM_RegisterDefragFunc(RedisModuleCtx *ctx, RedisModuleDefragFunc cb) { ctx->module->defrag_cb = cb; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } /* When the data type defrag callback iterates complex structures, this @@ -13331,8 +13413,8 @@ int VM_RegisterDefragFunc(ValkeyModuleCtx *ctx, ValkeyModuleDefragFunc cb) { * indicates the callback may continue its work. A non-zero value (true) * indicates it should stop. * - * When stopped, the callback may use VM_DefragCursorSet() to store its - * position so it can later use VM_DefragCursorGet() to resume defragging. + * When stopped, the callback may use RM_DefragCursorSet() to store its + * position so it can later use RM_DefragCursorGet() to resume defragging. * * When stopped and more work is left to be done, the callback should * return 1. Otherwise, it should return 0. @@ -13340,13 +13422,13 @@ int VM_RegisterDefragFunc(ValkeyModuleCtx *ctx, ValkeyModuleDefragFunc cb) { * NOTE: Modules should consider the frequency in which this function is called, * so it generally makes sense to do small batches of work in between calls. */ -int VM_DefragShouldStop(ValkeyModuleDefragCtx *ctx) { +int RM_DefragShouldStop(RedisModuleDefragCtx *ctx) { return (ctx->endtime != 0 && ctx->endtime < ustime()); } /* Store an arbitrary cursor value for future re-use. * - * This should only be called if VM_DefragShouldStop() has returned a non-zero + * This should only be called if RM_DefragShouldStop() has returned a non-zero * value and the defrag callback is about to exit without fully iterating its * data type. * @@ -13357,7 +13439,7 @@ int VM_DefragShouldStop(ValkeyModuleDefragCtx *ctx) { * * Smaller keys, keys that do not implement `free_effort` or the global * defrag callback are not called in late-defrag mode. In those cases, a - * call to this function will return VALKEYMODULE_ERR. + * call to this function will return REDISMODULE_ERR. * * The cursor may be used by the module to represent some progress into the * module's data type. Modules may also store additional cursor-related @@ -13366,29 +13448,29 @@ int VM_DefragShouldStop(ValkeyModuleDefragCtx *ctx) { * a guarantee that concurrent defragmentation of multiple keys will * not be performed. */ -int VM_DefragCursorSet(ValkeyModuleDefragCtx *ctx, unsigned long cursor) { +int RM_DefragCursorSet(RedisModuleDefragCtx *ctx, unsigned long cursor) { if (!ctx->cursor) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; *ctx->cursor = cursor; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* Fetch a cursor value that has been previously stored using VM_DefragCursorSet(). +/* Fetch a cursor value that has been previously stored using RM_DefragCursorSet(). * - * If not called for a late defrag operation, VALKEYMODULE_ERR will be returned and - * the cursor should be ignored. See VM_DefragCursorSet() for more details on + * If not called for a late defrag operation, REDISMODULE_ERR will be returned and + * the cursor should be ignored. See RM_DefragCursorSet() for more details on * defrag cursors. */ -int VM_DefragCursorGet(ValkeyModuleDefragCtx *ctx, unsigned long *cursor) { +int RM_DefragCursorGet(RedisModuleDefragCtx *ctx, unsigned long *cursor) { if (!ctx->cursor) - return VALKEYMODULE_ERR; + return REDISMODULE_ERR; *cursor = *ctx->cursor; - return VALKEYMODULE_OK; + return REDISMODULE_OK; } -/* Defrag a memory allocation previously allocated by VM_Alloc, VM_Calloc, etc. +/* Defrag a memory allocation previously allocated by RM_Alloc, RM_Calloc, etc. * The defragmentation process involves allocating a new memory block and copying * the contents to it, like realloc(). * @@ -13399,22 +13481,22 @@ int VM_DefragCursorGet(ValkeyModuleDefragCtx *ctx, unsigned long *cursor) { * of the old one and update any reference to the old pointer, which must not * be used again. */ -void *VM_DefragAlloc(ValkeyModuleDefragCtx *ctx, void *ptr) { +void *RM_DefragAlloc(RedisModuleDefragCtx *ctx, void *ptr) { UNUSED(ctx); return activeDefragAlloc(ptr); } -/* Defrag a ValkeyModuleString previously allocated by VM_Alloc, VM_Calloc, etc. - * See VM_DefragAlloc() for more information on how the defragmentation process +/* Defrag a RedisModuleString previously allocated by RM_Alloc, RM_Calloc, etc. + * See RM_DefragAlloc() for more information on how the defragmentation process * works. * * NOTE: It is only possible to defrag strings that have a single reference. - * Typically this means strings retained with VM_RetainString or VM_HoldString + * Typically this means strings retained with RM_RetainString or RM_HoldString * may not be defragmentable. One exception is command argvs which, if retained * by the module, will end up with a single reference (because the reference * on the Redis side is dropped as soon as the command callback returns). */ -ValkeyModuleString *VM_DefragValkeyModuleString(ValkeyModuleDefragCtx *ctx, ValkeyModuleString *str) { +RedisModuleString *RM_DefragRedisModuleString(RedisModuleDefragCtx *ctx, RedisModuleString *str) { UNUSED(ctx); return activeDefragStringOb(str); } @@ -13429,7 +13511,7 @@ int moduleLateDefrag(robj *key, robj *value, unsigned long *cursor, long long en moduleValue *mv = value->ptr; moduleType *mt = mv->type; - ValkeyModuleDefragCtx defrag_ctx = { endtime, cursor, key, dbid}; + RedisModuleDefragCtx defrag_ctx = { endtime, cursor, key, dbid}; /* Invoke callback. Note that the callback may be missing if the key has been * replaced with a different type since our last visit. @@ -13478,7 +13560,7 @@ int moduleDefragValue(robj *key, robj *value, int dbid) { return 0; /* Defrag later */ } - ValkeyModuleDefragCtx defrag_ctx = { 0, NULL, key, dbid }; + RedisModuleDefragCtx defrag_ctx = { 0, NULL, key, dbid }; mt->defrag(&defrag_ctx, key, &mv->value); return 1; } @@ -13489,10 +13571,10 @@ void moduleDefragGlobals(void) { dictEntry *de; while ((de = dictNext(di)) != NULL) { - struct ValkeyModule *module = dictGetVal(de); + struct RedisModule *module = dictGetVal(de); if (!module->defrag_cb) continue; - ValkeyModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1}; + RedisModuleDefragCtx defrag_ctx = { 0, NULL, NULL, -1}; module->defrag_cb(&defrag_ctx); } dictReleaseIterator(di); @@ -13501,14 +13583,14 @@ void moduleDefragGlobals(void) { /* Returns the name of the key currently being processed. * There is no guarantee that the key name is always available, so this may return NULL. */ -const ValkeyModuleString *VM_GetKeyNameFromDefragCtx(ValkeyModuleDefragCtx *ctx) { +const RedisModuleString *RM_GetKeyNameFromDefragCtx(RedisModuleDefragCtx *ctx) { return ctx->key; } /* Returns the database id of the key currently being processed. * There is no guarantee that this info is always available, so this may return -1. */ -int VM_GetDbIdFromDefragCtx(ValkeyModuleDefragCtx *ctx) { +int RM_GetDbIdFromDefragCtx(RedisModuleDefragCtx *ctx) { return ctx->dbid; } @@ -13520,7 +13602,9 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(Alloc); REGISTER_API(TryAlloc); REGISTER_API(Calloc); + REGISTER_API(TryCalloc); REGISTER_API(Realloc); + REGISTER_API(TryRealloc); REGISTER_API(Free); REGISTER_API(Strdup); REGISTER_API(CreateCommand); @@ -13528,6 +13612,7 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(CreateSubcommand); REGISTER_API(SetCommandInfo); REGISTER_API(SetCommandACLCategories); + REGISTER_API(AddACLCategory); REGISTER_API(SetModuleAttribs); REGISTER_API(IsModuleNameBusy); REGISTER_API(WrongArity); @@ -13851,7 +13936,7 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(GetTypeMethodVersion); REGISTER_API(RegisterDefragFunc); REGISTER_API(DefragAlloc); - REGISTER_API(DefragValkeyModuleString); + REGISTER_API(DefragRedisModuleString); REGISTER_API(DefragShouldStop); REGISTER_API(DefragCursorSet); REGISTER_API(DefragCursorGet); diff --git a/src/server.c b/src/server.c index 53be33a090..3b02d609d7 100644 --- a/src/server.c +++ b/src/server.c @@ -3512,12 +3512,20 @@ void call(client *c, int flags) { * re-processing and unblock the client.*/ c->flags |= CLIENT_EXECUTING_COMMAND; + /* Setting the CLIENT_REPROCESSING_COMMAND flag so that during the actual + * processing of the command proc, the client is aware that it is being + * re-processed. */ + if (reprocessing_command) c->flags |= CLIENT_REPROCESSING_COMMAND; + monotime monotonic_start = 0; if (monotonicGetType() == MONOTONIC_CLOCK_HW) monotonic_start = getMonotonicUs(); c->cmd->proc(c); + /* Clear the CLIENT_REPROCESSING_COMMAND flag after the proc is executed. */ + if (reprocessing_command) c->flags &= ~CLIENT_REPROCESSING_COMMAND; + exitExecutionUnit(); /* In case client is blocked after trying to execute the command, @@ -3575,7 +3583,7 @@ void call(client *c, int flags) { /* Send the command to clients in MONITOR mode if applicable, * since some administrative commands are considered too dangerous to be shown. - * Other exceptions is a client which is unblocked and retring to process the command + * Other exceptions is a client which is unblocked and retrying to process the command * or we are currently in the process of loading AOF. */ if (update_command_stats && !reprocessing_command && !(c->cmd->flags & (CMD_SKIP_MONITOR|CMD_ADMIN))) { diff --git a/src/server.h b/src/server.h index 5eb0b54707..f4c890401e 100644 --- a/src/server.h +++ b/src/server.h @@ -400,6 +400,7 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT]; auth had been authenticated from the Module. */ #define CLIENT_MODULE_PREVENT_AOF_PROP (1ULL<<48) /* Module client do not want to propagate to AOF */ #define CLIENT_MODULE_PREVENT_REPL_PROP (1ULL<<49) /* Module client do not want to propagate to replica */ +#define CLIENT_REPROCESSING_COMMAND (1ULL<<50) /* The client is re-processing the command. */ /* Client block type (btype field in client structure) * if CLIENT_BLOCKED flag is set. */ diff --git a/tests/unit/type/list.tcl b/tests/unit/type/list.tcl index e47e3bdafb..75c8c8259c 100644 --- a/tests/unit/type/list.tcl +++ b/tests/unit/type/list.tcl @@ -1199,6 +1199,34 @@ foreach {pop} {BLPOP BLMPOP_LEFT} { r select 9 } {OK} {singledb:skip needs:debug} + test {BLPOP unblock but the key is expired and then block again - reprocessing command} { + r flushall + r debug set-active-expire 0 + set rd [redis_deferring_client] + + set start [clock milliseconds] + $rd blpop mylist 1 + wait_for_blocked_clients_count 1 + + # The exec will try to awake the blocked client, but the key is expired, + # so the client will be blocked again during the command reprocessing. + r multi + r rpush mylist a + r pexpire mylist 100 + r debug sleep 0.2 + r exec + + assert_equal {} [$rd read] + set end [clock milliseconds] + + # In the past, this time would have been 1000+200, in order to avoid + # timing issues, we increase the range a bit. + assert_range [expr $end-$start] 1000 1100 + + r debug set-active-expire 1 + $rd close + } {0} {needs:debug} + foreach {pop} {BLPOP BLMPOP_LEFT} { test "$pop when new key is moved into place" { set rd [redis_deferring_client] diff --git a/tests/unit/type/stream-cgroups.tcl b/tests/unit/type/stream-cgroups.tcl index a6cc5da7df..242059ef73 100644 --- a/tests/unit/type/stream-cgroups.tcl +++ b/tests/unit/type/stream-cgroups.tcl @@ -475,7 +475,7 @@ start_server { $rd close } - test {Blocking XREADGROUP for stream key that has clients blocked on list - avoid endless loop} { + test {Blocking XREADGROUP for stream key that has clients blocked on stream - avoid endless loop} { r DEL mystream r XGROUP CREATE mystream mygroup $ MKSTREAM @@ -498,6 +498,34 @@ start_server { assert_equal [r ping] {PONG} } + test {Blocking XREADGROUP for stream key that has clients blocked on stream - reprocessing command} { + r DEL mystream + r XGROUP CREATE mystream mygroup $ MKSTREAM + + set rd1 [redis_deferring_client] + set rd2 [redis_deferring_client] + + $rd1 xreadgroup GROUP mygroup myuser BLOCK 0 STREAMS mystream > + wait_for_blocked_clients_count 1 + + set start [clock milliseconds] + $rd2 xreadgroup GROUP mygroup myuser BLOCK 1000 STREAMS mystream > + wait_for_blocked_clients_count 2 + + # After a while call xadd and let rd2 re-process the command. + after 200 + r xadd mystream * field value + assert_equal {} [$rd2 read] + set end [clock milliseconds] + + # In the past, this time would have been 1000+200, in order to avoid + # timing issues, we increase the range a bit. + assert_range [expr $end-$start] 1000 1100 + + $rd1 close + $rd2 close + } + test {XGROUP DESTROY should unblock XREADGROUP with -NOGROUP} { r config resetstat r del mystream diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl index 33427d89c2..7836577a27 100644 --- a/tests/unit/type/zset.tcl +++ b/tests/unit/type/zset.tcl @@ -1942,6 +1942,34 @@ start_server {tags {"zset"}} { } } + test {BZPOPMIN unblock but the key is expired and then block again - reprocessing command} { + r flushall + r debug set-active-expire 0 + set rd [redis_deferring_client] + + set start [clock milliseconds] + $rd bzpopmin zset{t} 1 + wait_for_blocked_clients_count 1 + + # The exec will try to awake the blocked client, but the key is expired, + # so the client will be blocked again during the command reprocessing. + r multi + r zadd zset{t} 1 one + r pexpire zset{t} 100 + r debug sleep 0.2 + r exec + + assert_equal {} [$rd read] + set end [clock milliseconds] + + # In the past, this time would have been 1000+200, in order to avoid + # timing issues, we increase the range a bit. + assert_range [expr $end-$start] 1000 1100 + + r debug set-active-expire 1 + $rd close + } {0} {needs:debug} + test "BZPOPMIN with same key multiple times should work" { set rd [redis_deferring_client] r del z1{t} z2{t} From 913e3fb90361e5f129d988d0fbc41d6b19b93b85 Mon Sep 17 00:00:00 2001 From: Matthew Douglass <5410142+mdouglass@users.noreply.github.com> Date: Sat, 9 Mar 2024 22:46:49 -0800 Subject: [PATCH 4/7] Fix conversion of numbers in lua args to redis args (#13115) Since lua_Number is not explicitly an integer or a double, we need to make an effort to convert it as an integer when that's possible, since the string could later be used in a context that doesn't support scientific notation (e.g. 1e9 instead of 100000000). Since fpconv_dtoa converts numbers with the equivalent of `%f` or `%e`, which ever is shorter, this would break if we try to pass a long integer number to a command that takes integer. we'll get an implicit conversion to string in Lua, and then the parsing in getLongLongFromObjectOrReply will fail. ``` > eval "redis.call('hincrby', 'key', 'field', '1000000000')" 0 (nil) > eval "redis.call('hincrby', 'key', 'field', tonumber('1000000000'))" 0 (error) ERR value is not an integer or out of range script: ac99c32e4daf7e300d593085b611de261954a946, on @user_script:1. ``` Switch to using ll2string if the number can be safely represented as a long long. The problem was introduced in #10587 (Redis 7.2). closes #13113. --------- Co-authored-by: Binbin Co-authored-by: debing.sun Co-authored-by: Oran Agra Signed-off-by: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com> --- src/script_lua.c | 13 +++++++++++-- tests/unit/scripting.tcl | 8 ++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/script_lua.c b/src/script_lua.c index 432d6156ba..2f54485b9f 100644 --- a/src/script_lua.c +++ b/src/script_lua.c @@ -819,8 +819,17 @@ static robj **luaArgsToRedisArgv(lua_State *lua, int *argc, int *argv_len) { /* We can't use lua_tolstring() for number -> string conversion * since Lua uses a format specifier that loses precision. */ lua_Number num = lua_tonumber(lua,j+1); - obj_len = fpconv_dtoa((double)num, dbuf); - dbuf[obj_len] = '\0'; + /* Integer printing function is much faster, check if we can safely use it. + * Since lua_Number is not explicitly an integer or a double, we need to make an effort + * to convert it as an integer when that's possible, since the string could later be used + * in a context that doesn't support scientific notation (e.g. 1e9 instead of 100000000). */ + long long lvalue; + if (double2ll((double)num, &lvalue)) + obj_len = ll2string(dbuf, sizeof(dbuf), lvalue); + else { + obj_len = fpconv_dtoa((double)num, dbuf); + dbuf[obj_len] = '\0'; + } obj_s = dbuf; } else { obj_s = (char*)lua_tolstring(lua,j+1,&obj_len); diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl index c8294964a8..a64572b6d4 100644 --- a/tests/unit/scripting.tcl +++ b/tests/unit/scripting.tcl @@ -174,6 +174,14 @@ start_server {tags {"scripting"}} { } 1 x } {number 1} + test {EVAL - Lua number -> Redis integer conversion} { + r del hash + run_script { + local foo = redis.pcall('hincrby','hash','field',200000000) + return {type(foo),foo} + } 0 + } {number 200000000} + test {EVAL - Redis bulk -> Lua type conversion} { r set mykey myval run_script { From 87be9b4c656458b3296698bd0af1c4d99918673f Mon Sep 17 00:00:00 2001 From: LiiNen <38001238+LiiNen@users.noreply.github.com> Date: Wed, 28 Feb 2024 16:44:30 +0900 Subject: [PATCH 5/7] Fix redis-cli --count (for --scan, --bigkeys, etc) was ignored unless --pattern was also used (#13092) The --count option for redis-cli has been released in redis 7.2. https://github.com/redis/redis/pull/12042 But I have found in code, that some logic was missing for using this 'count' option. ``` static redisReply *sendScan(unsigned long long *it) { redisReply *reply; if (config.pattern) reply = redisCommand(context, "SCAN %llu MATCH %b COUNT %d", *it, config.pattern, sdslen(config.pattern), config.count); else reply = redisCommand(context,"SCAN %llu",*it); ``` The intention was being able to using scan count. But in this case, the --count will be only applied when 'pattern' is declared. So, I had fix it simply, to be worked properly - even if --pattern option is not being used. I tested it simply with time() command several times, and I could see it works as intended with this commit. The examples of test results are below: ``` # unstable build time(./redis-cli -a $AUTH -p $PORT -h $HOST --scan >/dev/null 2>/dev/null) real 0m1.287s user 0m0.011s sys 0m0.022s # count is not applied time(./redis-cli -a $AUTH -p $PORT -h $HOST --scan --count 1000 >/dev/null 2>/dev/null) real 0m1.117s user 0m0.011s sys 0m0.020s # count is applied with --pattern time(./redis-cli -a $AUTH -p $PORT -h $HOST --scan --count 1000 --pattern "hash:*" >/dev/null 2>/dev/null) real 0m0.045s user 0m0.002s sys 0m0.002s ``` ``` # fix-redis-cli-scan-count build time(./redis-cli -a $AUTH -p $PORT -h $HOST --scan >/dev/null 2>/dev/null) real 0m1.084s user 0m0.008s sys 0m0.024s # count is applied even if --pattern is not declared time(./redis-cli -a $AUTH -p $PORT -h $HOST --scan --count 1000 >/dev/null 2>/dev/null) real 0m0.043s user 0m0.000s sys 0m0.004s # of course this also applied time(./redis-cli -a $AUTH -p $PORT -h $HOST --scan --count 1000 --pattern "hash:*" >/dev/null 2>/dev/null) real 0m0.031s user 0m0.002s sys 0m0.002s ``` Thanks a lot. Signed-off-by: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com> --- src/valkey-cli.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/valkey-cli.c b/src/valkey-cli.c index 5270d66ff3..81020cd931 100644 --- a/src/valkey-cli.c +++ b/src/valkey-cli.c @@ -8840,7 +8840,8 @@ static redisReply *sendScan(unsigned long long *it) { reply = redisCommand(context, "SCAN %llu MATCH %b COUNT %d", *it, config.pattern, sdslen(config.pattern), config.count); else - reply = redisCommand(context,"SCAN %llu",*it); + reply = redisCommand(context, "SCAN %llu COUNT %d", + *it, config.count); /* Handle any error conditions */ if(reply == NULL) { From a462fe1bc380aee5333d2b0df1382af0df684b9a Mon Sep 17 00:00:00 2001 From: Binbin Date: Tue, 12 Mar 2024 14:47:43 +0800 Subject: [PATCH 6/7] Fix redis-check-aof incorrectly considering data in manifest format as MP-AOF (#12958) The check in fileIsManifest misjudged the manifest file. For example, if resp aof contains "file", it will be considered a manifest file and the check will fail: ``` *3 $3 set $4 file $4 file ``` In #12951, if the preamble aof also contains it, it will also fail. Fixes #12951. the bug was happening if the the word "file" is mentioned in the first 1024 lines of the AOF. and now as soon as it finds a non-comment line it'll break (if it contains "file" or doesn't) Signed-off-by: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com> --- src/aof.c | 4 +- src/valkey-check-aof.c | 10 ++- tests/integration/aof.tcl | 181 +++++++++++++++++--------------------- 3 files changed, 91 insertions(+), 104 deletions(-) diff --git a/src/aof.c b/src/aof.c index 615e149369..309f1edbee 100644 --- a/src/aof.c +++ b/src/aof.c @@ -117,7 +117,9 @@ aofInfo *aofInfoDup(aofInfo *orig) { return ai; } -/* Format aofInfo as a string and it will be a line in the manifest. */ +/* Format aofInfo as a string and it will be a line in the manifest. + * + * When update this format, make sure to update redis-check-aof as well. */ sds aofInfoFormat(sds buf, aofInfo *ai) { sds filename_repr = NULL; diff --git a/src/valkey-check-aof.c b/src/valkey-check-aof.c index 55e318a067..9fa21f9c9c 100644 --- a/src/valkey-check-aof.c +++ b/src/valkey-check-aof.c @@ -233,6 +233,7 @@ int checkSingleAof(char *aof_filename, char *aof_filepath, int last_file, int fi struct redis_stat sb; if (redis_fstat(fileno(fp),&sb) == -1) { printf("Cannot stat file: %s, aborting...\n", aof_filename); + fclose(fp); exit(1); } @@ -343,6 +344,7 @@ int fileIsRDB(char *filepath) { struct redis_stat sb; if (redis_fstat(fileno(fp), &sb) == -1) { printf("Cannot stat file: %s\n", filepath); + fclose(fp); exit(1); } @@ -379,6 +381,7 @@ int fileIsManifest(char *filepath) { struct redis_stat sb; if (redis_fstat(fileno(fp), &sb) == -1) { printf("Cannot stat file: %s\n", filepath); + fclose(fp); exit(1); } @@ -395,15 +398,20 @@ int fileIsManifest(char *filepath) { break; } else { printf("Cannot read file: %s\n", filepath); + fclose(fp); exit(1); } } - /* Skip comments lines */ + /* We will skip comments lines. + * At present, the manifest format is fixed, see aofInfoFormat. + * We will break directly as long as it encounters other items. */ if (buf[0] == '#') { continue; } else if (!memcmp(buf, "file", strlen("file"))) { is_manifest = 1; + } else { + break; } } diff --git a/tests/integration/aof.tcl b/tests/integration/aof.tcl index 4aeae0f5be..137b2d8633 100644 --- a/tests/integration/aof.tcl +++ b/tests/integration/aof.tcl @@ -23,7 +23,7 @@ tags {"aof external:skip"} { start_server_aof [list dir $server_path aof-load-truncated yes] { test "Unfinished MULTI: Server should start if load-truncated is yes" { - assert_equal 1 [is_alive $srv] + assert_equal 1 [is_alive [srv pid]] } } @@ -39,11 +39,11 @@ tags {"aof external:skip"} { start_server_aof [list dir $server_path aof-load-truncated yes] { test "Short read: Server should start if load-truncated is yes" { - assert_equal 1 [is_alive $srv] + assert_equal 1 [is_alive [srv pid]] } test "Truncated AOF loaded: we expect foo to be equal to 5" { - set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] + set client [redis [srv host] [srv port] 0 $::tls] wait_done_loading $client assert {[$client get foo] eq "5"} } @@ -56,11 +56,11 @@ tags {"aof external:skip"} { # Now the AOF file is expected to be correct start_server_aof [list dir $server_path aof-load-truncated yes] { test "Short read + command: Server should start" { - assert_equal 1 [is_alive $srv] + assert_equal 1 [is_alive [srv pid]] } test "Truncated AOF loaded: we expect foo to be equal to 6 now" { - set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] + set client [redis [srv host] [srv port] 0 $::tls] wait_done_loading $client assert {[$client get foo] eq "6"} } @@ -73,21 +73,9 @@ tags {"aof external:skip"} { append_to_aof [formatCommand set foo hello] } - start_server_aof [list dir $server_path aof-load-truncated yes] { + start_server_aof_ex [list dir $server_path aof-load-truncated yes] [list wait_ready false] { test "Bad format: Server should have logged an error" { - set pattern "*Bad file format reading the append only file*" - set retry 10 - while {$retry} { - set result [exec tail -1 < [dict get $srv stdout]] - if {[string match $pattern $result]} { - break - } - incr retry -1 - after 1000 - } - if {$retry == 0} { - error "assertion:expected error not found on config file" - } + wait_for_log_messages 0 {"*Bad file format reading the append only file*"} 0 10 1000 } } @@ -98,21 +86,9 @@ tags {"aof external:skip"} { append_to_aof [formatCommand set bar world] } - start_server_aof [list dir $server_path aof-load-truncated no] { + start_server_aof_ex [list dir $server_path aof-load-truncated no] [list wait_ready false] { test "Unfinished MULTI: Server should have logged an error" { - set pattern "*Unexpected end of file reading the append only file*" - set retry 10 - while {$retry} { - set result [exec tail -1 < [dict get $srv stdout]] - if {[string match $pattern $result]} { - break - } - incr retry -1 - after 1000 - } - if {$retry == 0} { - error "assertion:expected error not found on config file" - } + wait_for_log_messages 0 {"*Unexpected end of file reading the append only file*"} 0 10 1000 } } @@ -122,28 +98,16 @@ tags {"aof external:skip"} { append_to_aof [string range [formatCommand set bar world] 0 end-1] } - start_server_aof [list dir $server_path aof-load-truncated no] { + start_server_aof_ex [list dir $server_path aof-load-truncated no] [list wait_ready false] { test "Short read: Server should have logged an error" { - set pattern "*Unexpected end of file reading the append only file*" - set retry 10 - while {$retry} { - set result [exec tail -1 < [dict get $srv stdout]] - if {[string match $pattern $result]} { - break - } - incr retry -1 - after 1000 - } - if {$retry == 0} { - error "assertion:expected error not found on config file" - } + wait_for_log_messages 0 {"*Unexpected end of file reading the append only file*"} 0 10 1000 } } - ## Test that valkey-check-aof indeed sees this AOF is not valid + ## Test that redis-check-aof indeed sees this AOF is not valid test "Short read: Utility should confirm the AOF is not valid" { catch { - exec src/valkey-check-aof $aof_manifest_file + exec src/redis-check-aof $aof_manifest_file } result assert_match "*not valid*" $result } @@ -155,24 +119,24 @@ tags {"aof external:skip"} { } catch { - exec src/valkey-check-aof $aof_manifest_file + exec src/redis-check-aof $aof_manifest_file } result assert_match "*ok_up_to_line=8*" $result } test "Short read: Utility should be able to fix the AOF" { - set result [exec src/valkey-check-aof --fix $aof_manifest_file << "y\n"] + set result [exec src/redis-check-aof --fix $aof_manifest_file << "y\n"] assert_match "*Successfully truncated AOF*" $result } ## Test that the server can be started using the truncated AOF start_server_aof [list dir $server_path aof-load-truncated no] { test "Fixed AOF: Server should have been started" { - assert_equal 1 [is_alive $srv] + assert_equal 1 [is_alive [srv pid]] } test "Fixed AOF: Keyspace should contain values that were parseable" { - set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] + set client [redis [srv host] [srv port] 0 $::tls] wait_done_loading $client assert_equal "hello" [$client get foo] assert_equal "" [$client get bar] @@ -188,11 +152,11 @@ tags {"aof external:skip"} { start_server_aof [list dir $server_path aof-load-truncated no] { test "AOF+SPOP: Server should have been started" { - assert_equal 1 [is_alive $srv] + assert_equal 1 [is_alive [srv pid]] } test "AOF+SPOP: Set should have 1 member" { - set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] + set client [redis [srv host] [srv port] 0 $::tls] wait_done_loading $client assert_equal 1 [$client scard set] } @@ -208,11 +172,11 @@ tags {"aof external:skip"} { start_server_aof [list dir $server_path] { test "AOF+SPOP: Server should have been started" { - assert_equal 1 [is_alive $srv] + assert_equal 1 [is_alive [srv pid]] } test "AOF+SPOP: Set should have 1 member" { - set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] + set client [redis [srv host] [srv port] 0 $::tls] wait_done_loading $client assert_equal 1 [$client scard set] } @@ -227,11 +191,11 @@ tags {"aof external:skip"} { start_server_aof [list dir $server_path aof-load-truncated no] { test "AOF+EXPIRE: Server should have been started" { - assert_equal 1 [is_alive $srv] + assert_equal 1 [is_alive [srv pid]] } test "AOF+EXPIRE: List should be empty" { - set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] + set client [redis [srv host] [srv port] 0 $::tls] wait_done_loading $client assert_equal 0 [$client llen list] } @@ -293,21 +257,9 @@ tags {"aof external:skip"} { append_to_aof [formatCommand set foo hello] } - start_server_aof [list dir $server_path aof-load-truncated yes] { + start_server_aof_ex [list dir $server_path aof-load-truncated yes] [list wait_ready false] { test "Unknown command: Server should have logged an error" { - set pattern "*Unknown command 'bla' reading the append only file*" - set retry 10 - while {$retry} { - set result [exec tail -1 < [dict get $srv stdout]] - if {[string match $pattern $result]} { - break - } - incr retry -1 - after 1000 - } - if {$retry == 0} { - error "assertion:expected error not found on config file" - } + wait_for_log_messages 0 {"*Unknown command 'bla' reading the append only file*"} 0 10 1000 } } @@ -320,8 +272,8 @@ tags {"aof external:skip"} { start_server_aof [list dir $server_path aof-load-truncated no] { test "AOF+LMPOP/BLMPOP: pop elements from the list" { - set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] - set client2 [redis [dict get $srv host] [dict get $srv port] 1 $::tls] + set client [redis [srv host] [srv port] 0 $::tls] + set client2 [redis [srv host] [srv port] 1 $::tls] wait_done_loading $client # Pop all elements from mylist, should be blmpop delete mylist. @@ -347,7 +299,7 @@ tags {"aof external:skip"} { start_server_aof [list dir $server_path aof-load-truncated no] { test "AOF+LMPOP/BLMPOP: after pop elements from the list" { - set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] + set client [redis [srv host] [srv port] 0 $::tls] wait_done_loading $client # mylist and mylist2 no longer exist. @@ -367,8 +319,8 @@ tags {"aof external:skip"} { start_server_aof [list dir $server_path aof-load-truncated no] { test "AOF+ZMPOP/BZMPOP: pop elements from the zset" { - set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] - set client2 [redis [dict get $srv host] [dict get $srv port] 1 $::tls] + set client [redis [srv host] [srv port] 0 $::tls] + set client2 [redis [srv host] [srv port] 1 $::tls] wait_done_loading $client # Pop all elements from myzset, should be bzmpop delete myzset. @@ -394,7 +346,7 @@ tags {"aof external:skip"} { start_server_aof [list dir $server_path aof-load-truncated no] { test "AOF+ZMPOP/BZMPOP: after pop elements from the zset" { - set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls] + set client [redis [srv host] [srv port] 0 $::tls] wait_done_loading $client # myzset and myzset2 no longer exist. @@ -435,7 +387,7 @@ tags {"aof external:skip"} { } start_server_aof [list dir $server_path] { test {Successfully load AOF which has timestamp annotations inside} { - set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls] + set c [redis [srv host] [srv port] 0 $::tls] wait_done_loading $c assert_equal "bar1" [$c get foo1] assert_equal "bar2" [$c get foo2] @@ -445,9 +397,9 @@ tags {"aof external:skip"} { test {Truncate AOF to specific timestamp} { # truncate to timestamp 1628217473 - exec src/valkey-check-aof --truncate-to-timestamp 1628217473 $aof_manifest_file + exec src/redis-check-aof --truncate-to-timestamp 1628217473 $aof_manifest_file start_server_aof [list dir $server_path] { - set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls] + set c [redis [srv host] [srv port] 0 $::tls] wait_done_loading $c assert_equal "bar1" [$c get foo1] assert_equal "bar2" [$c get foo2] @@ -455,9 +407,9 @@ tags {"aof external:skip"} { } # truncate to timestamp 1628217471 - exec src/valkey-check-aof --truncate-to-timestamp 1628217471 $aof_manifest_file + exec src/redis-check-aof --truncate-to-timestamp 1628217471 $aof_manifest_file start_server_aof [list dir $server_path] { - set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls] + set c [redis [srv host] [srv port] 0 $::tls] wait_done_loading $c assert_equal "bar1" [$c get foo1] assert_equal "bar2" [$c get foo2] @@ -465,21 +417,21 @@ tags {"aof external:skip"} { } # truncate to timestamp 1628217470 - exec src/valkey-check-aof --truncate-to-timestamp 1628217470 $aof_manifest_file + exec src/redis-check-aof --truncate-to-timestamp 1628217470 $aof_manifest_file start_server_aof [list dir $server_path] { - set c [redis [dict get $srv host] [dict get $srv port] 0 $::tls] + set c [redis [srv host] [srv port] 0 $::tls] wait_done_loading $c assert_equal "bar1" [$c get foo1] assert_equal "" [$c get foo2] } # truncate to timestamp 1628217469 - catch {exec src/valkey-check-aof --truncate-to-timestamp 1628217469 $aof_manifest_file} e + catch {exec src/redis-check-aof --truncate-to-timestamp 1628217469 $aof_manifest_file} e assert_match {*aborting*} $e } test {EVAL timeout with slow verbatim Lua script from AOF} { - start_server [list overrides [list dir $server_path appendonly yes lua-time-limit 1 aof-use-rdb-preamble no]] { + start_server [list overrides [list dir $server_path appendonly yes lua-time-limit 1 aof-use-rdb-preamble no]] { # generate a long running script that is propagated to the AOF as script # make sure that the script times out during loading create_aof $aof_dirpath $aof_file { @@ -517,26 +469,38 @@ tags {"aof external:skip"} { } } - test {Test valkey-check-aof for old style resp AOF} { + test {Test redis-check-aof for old style resp AOF} { create_aof $aof_dirpath $aof_file { append_to_aof [formatCommand set foo hello] append_to_aof [formatCommand set bar world] } catch { - exec src/valkey-check-aof $aof_file + exec src/redis-check-aof $aof_file + } result + assert_match "*Start checking Old-Style AOF*is valid*" $result + } + + test {Test redis-check-aof for old style resp AOF - has data in the same format as manifest} { + create_aof $aof_dirpath $aof_file { + append_to_aof [formatCommand set file file] + append_to_aof [formatCommand set "file appendonly.aof.2.base.rdb seq 2 type b" "file appendonly.aof.2.base.rdb seq 2 type b"] + } + + catch { + exec src/redis-check-aof $aof_file } result assert_match "*Start checking Old-Style AOF*is valid*" $result } - test {Test valkey-check-aof for old style rdb-preamble AOF} { + test {Test redis-check-aof for old style rdb-preamble AOF} { catch { - exec src/valkey-check-aof tests/assets/rdb-preamble.aof + exec src/redis-check-aof tests/assets/rdb-preamble.aof } result assert_match "*Start checking Old-Style AOF*RDB preamble is OK, proceeding with AOF tail*is valid*" $result } - test {Test valkey-check-aof for Multi Part AOF with resp AOF base} { + test {Test redis-check-aof for Multi Part AOF with resp AOF base} { create_aof $aof_dirpath $aof_base_file { append_to_aof [formatCommand set foo hello] append_to_aof [formatCommand set bar world] @@ -553,12 +517,12 @@ tags {"aof external:skip"} { } catch { - exec src/valkey-check-aof $aof_manifest_file - } result + exec src/redis-check-aof $aof_manifest_file + } result assert_match "*Start checking Multi Part AOF*Start to check BASE AOF (RESP format)*BASE AOF*is valid*Start to check INCR files*INCR AOF*is valid*All AOF files and manifest are valid*" $result } - test {Test valkey-check-aof for Multi Part AOF with rdb-preamble AOF base} { + test {Test redis-check-aof for Multi Part AOF with rdb-preamble AOF base} { exec cp tests/assets/rdb-preamble.aof $aof_base_file create_aof $aof_dirpath $aof_file { @@ -572,12 +536,25 @@ tags {"aof external:skip"} { } catch { - exec src/valkey-check-aof $aof_manifest_file + exec src/redis-check-aof $aof_manifest_file } result assert_match "*Start checking Multi Part AOF*Start to check BASE AOF (RDB format)*DB preamble is OK, proceeding with AOF tail*BASE AOF*is valid*Start to check INCR files*INCR AOF*is valid*All AOF files and manifest are valid*" $result } - test {Test valkey-check-aof only truncates the last file for Multi Part AOF in fix mode} { + test {Test redis-check-aof for Multi Part AOF contains a format error} { + create_aof_manifest $aof_dirpath $aof_manifest_file { + append_to_manifest "file appendonly.aof.1.base.aof seq 1 type b\n" + append_to_manifest "file appendonly.aof.1.incr.aof seq 1 type i\n" + append_to_manifest "!!!\n" + } + + catch { + exec src/redis-check-aof $aof_manifest_file + } result + assert_match "*Invalid AOF manifest file format*" $result + } + + test {Test redis-check-aof only truncates the last file for Multi Part AOF in fix mode} { create_aof $aof_dirpath $aof_base_file { append_to_aof [formatCommand set foo hello] append_to_aof [formatCommand multi] @@ -595,17 +572,17 @@ tags {"aof external:skip"} { } catch { - exec src/valkey-check-aof $aof_manifest_file + exec src/redis-check-aof $aof_manifest_file } result assert_match "*not valid*" $result catch { - exec src/valkey-check-aof --fix $aof_manifest_file + exec src/redis-check-aof --fix $aof_manifest_file } result assert_match "*Failed to truncate AOF*because it is not the last file*" $result } - test {Test valkey-check-aof only truncates the last file for Multi Part AOF in truncate-to-timestamp mode} { + test {Test redis-check-aof only truncates the last file for Multi Part AOF in truncate-to-timestamp mode} { create_aof $aof_dirpath $aof_base_file { append_to_aof "#TS:1628217470\r\n" append_to_aof [formatCommand set foo1 bar1] @@ -628,7 +605,7 @@ tags {"aof external:skip"} { } catch { - exec src/valkey-check-aof --truncate-to-timestamp 1628217473 $aof_manifest_file + exec src/redis-check-aof --truncate-to-timestamp 1628217473 $aof_manifest_file } result assert_match "*Failed to truncate AOF*to timestamp*because it is not the last file*" $result } From f7d0c13619e786bc135624b674dc5bdafd89b7ad Mon Sep 17 00:00:00 2001 From: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com> Date: Wed, 29 May 2024 10:04:01 +0530 Subject: [PATCH 7/7] fix: redis check aof -> valkey check aof Signed-off-by: Anuragkillswitch <70265851+Anuragkillswitch@users.noreply.github.com> --- src/server.c | 2 +- tests/integration/aof.tcl | 50 +++++++++++++++++++-------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/server.c b/src/server.c index 3b02d609d7..573574a26a 100644 --- a/src/server.c +++ b/src/server.c @@ -7152,7 +7152,7 @@ int main(int argc, char **argv) { #ifdef USE_REDIS_SYMLINKS if (strstr(exec_name,"redis-check-rdb") != NULL) redis_check_rdb_main(argc, argv, NULL); - else if (strstr(exec_name,"redis-check-aof") != NULL) + else if (strstr(exec_name,"valkey-check-aof") != NULL) redis_check_aof_main(argc,argv); #endif diff --git a/tests/integration/aof.tcl b/tests/integration/aof.tcl index 137b2d8633..6006a00ff9 100644 --- a/tests/integration/aof.tcl +++ b/tests/integration/aof.tcl @@ -104,10 +104,10 @@ tags {"aof external:skip"} { } } - ## Test that redis-check-aof indeed sees this AOF is not valid + ## Test that valkey-check-aof indeed sees this AOF is not valid test "Short read: Utility should confirm the AOF is not valid" { catch { - exec src/redis-check-aof $aof_manifest_file + exec src/valkey-check-aof $aof_manifest_file } result assert_match "*not valid*" $result } @@ -119,13 +119,13 @@ tags {"aof external:skip"} { } catch { - exec src/redis-check-aof $aof_manifest_file + exec src/valkey-check-aof $aof_manifest_file } result assert_match "*ok_up_to_line=8*" $result } test "Short read: Utility should be able to fix the AOF" { - set result [exec src/redis-check-aof --fix $aof_manifest_file << "y\n"] + set result [exec src/valkey-check-aof --fix $aof_manifest_file << "y\n"] assert_match "*Successfully truncated AOF*" $result } @@ -397,7 +397,7 @@ tags {"aof external:skip"} { test {Truncate AOF to specific timestamp} { # truncate to timestamp 1628217473 - exec src/redis-check-aof --truncate-to-timestamp 1628217473 $aof_manifest_file + exec src/valkey-check-aof --truncate-to-timestamp 1628217473 $aof_manifest_file start_server_aof [list dir $server_path] { set c [redis [srv host] [srv port] 0 $::tls] wait_done_loading $c @@ -407,7 +407,7 @@ tags {"aof external:skip"} { } # truncate to timestamp 1628217471 - exec src/redis-check-aof --truncate-to-timestamp 1628217471 $aof_manifest_file + exec src/valkey-check-aof --truncate-to-timestamp 1628217471 $aof_manifest_file start_server_aof [list dir $server_path] { set c [redis [srv host] [srv port] 0 $::tls] wait_done_loading $c @@ -417,7 +417,7 @@ tags {"aof external:skip"} { } # truncate to timestamp 1628217470 - exec src/redis-check-aof --truncate-to-timestamp 1628217470 $aof_manifest_file + exec src/valkey-check-aof --truncate-to-timestamp 1628217470 $aof_manifest_file start_server_aof [list dir $server_path] { set c [redis [srv host] [srv port] 0 $::tls] wait_done_loading $c @@ -426,7 +426,7 @@ tags {"aof external:skip"} { } # truncate to timestamp 1628217469 - catch {exec src/redis-check-aof --truncate-to-timestamp 1628217469 $aof_manifest_file} e + catch {exec src/valkey-check-aof --truncate-to-timestamp 1628217469 $aof_manifest_file} e assert_match {*aborting*} $e } @@ -469,38 +469,38 @@ tags {"aof external:skip"} { } } - test {Test redis-check-aof for old style resp AOF} { + test {Test valkey-check-aof for old style resp AOF} { create_aof $aof_dirpath $aof_file { append_to_aof [formatCommand set foo hello] append_to_aof [formatCommand set bar world] } catch { - exec src/redis-check-aof $aof_file + exec src/valkey-check-aof $aof_file } result assert_match "*Start checking Old-Style AOF*is valid*" $result } - test {Test redis-check-aof for old style resp AOF - has data in the same format as manifest} { + test {Test valkey-check-aof for old style resp AOF - has data in the same format as manifest} { create_aof $aof_dirpath $aof_file { append_to_aof [formatCommand set file file] append_to_aof [formatCommand set "file appendonly.aof.2.base.rdb seq 2 type b" "file appendonly.aof.2.base.rdb seq 2 type b"] } catch { - exec src/redis-check-aof $aof_file + exec src/valkey-check-aof $aof_file } result assert_match "*Start checking Old-Style AOF*is valid*" $result } - test {Test redis-check-aof for old style rdb-preamble AOF} { + test {Test valkey-check-aof for old style rdb-preamble AOF} { catch { - exec src/redis-check-aof tests/assets/rdb-preamble.aof + exec src/valkey-check-aof tests/assets/rdb-preamble.aof } result assert_match "*Start checking Old-Style AOF*RDB preamble is OK, proceeding with AOF tail*is valid*" $result } - test {Test redis-check-aof for Multi Part AOF with resp AOF base} { + test {Test valkey-check-aof for Multi Part AOF with resp AOF base} { create_aof $aof_dirpath $aof_base_file { append_to_aof [formatCommand set foo hello] append_to_aof [formatCommand set bar world] @@ -517,12 +517,12 @@ tags {"aof external:skip"} { } catch { - exec src/redis-check-aof $aof_manifest_file + exec src/valkey-check-aof $aof_manifest_file } result assert_match "*Start checking Multi Part AOF*Start to check BASE AOF (RESP format)*BASE AOF*is valid*Start to check INCR files*INCR AOF*is valid*All AOF files and manifest are valid*" $result } - test {Test redis-check-aof for Multi Part AOF with rdb-preamble AOF base} { + test {Test valkey-check-aof for Multi Part AOF with rdb-preamble AOF base} { exec cp tests/assets/rdb-preamble.aof $aof_base_file create_aof $aof_dirpath $aof_file { @@ -536,12 +536,12 @@ tags {"aof external:skip"} { } catch { - exec src/redis-check-aof $aof_manifest_file + exec src/valkey-check-aof $aof_manifest_file } result assert_match "*Start checking Multi Part AOF*Start to check BASE AOF (RDB format)*DB preamble is OK, proceeding with AOF tail*BASE AOF*is valid*Start to check INCR files*INCR AOF*is valid*All AOF files and manifest are valid*" $result } - test {Test redis-check-aof for Multi Part AOF contains a format error} { + test {Test valkey-check-aof for Multi Part AOF contains a format error} { create_aof_manifest $aof_dirpath $aof_manifest_file { append_to_manifest "file appendonly.aof.1.base.aof seq 1 type b\n" append_to_manifest "file appendonly.aof.1.incr.aof seq 1 type i\n" @@ -549,12 +549,12 @@ tags {"aof external:skip"} { } catch { - exec src/redis-check-aof $aof_manifest_file + exec src/valkey-check-aof $aof_manifest_file } result assert_match "*Invalid AOF manifest file format*" $result } - test {Test redis-check-aof only truncates the last file for Multi Part AOF in fix mode} { + test {Test valkey-check-aof only truncates the last file for Multi Part AOF in fix mode} { create_aof $aof_dirpath $aof_base_file { append_to_aof [formatCommand set foo hello] append_to_aof [formatCommand multi] @@ -572,17 +572,17 @@ tags {"aof external:skip"} { } catch { - exec src/redis-check-aof $aof_manifest_file + exec src/valkey-check-aof $aof_manifest_file } result assert_match "*not valid*" $result catch { - exec src/redis-check-aof --fix $aof_manifest_file + exec src/valkey-check-aof --fix $aof_manifest_file } result assert_match "*Failed to truncate AOF*because it is not the last file*" $result } - test {Test redis-check-aof only truncates the last file for Multi Part AOF in truncate-to-timestamp mode} { + test {Test valkey-check-aof only truncates the last file for Multi Part AOF in truncate-to-timestamp mode} { create_aof $aof_dirpath $aof_base_file { append_to_aof "#TS:1628217470\r\n" append_to_aof [formatCommand set foo1 bar1] @@ -605,7 +605,7 @@ tags {"aof external:skip"} { } catch { - exec src/redis-check-aof --truncate-to-timestamp 1628217473 $aof_manifest_file + exec src/valkey-check-aof --truncate-to-timestamp 1628217473 $aof_manifest_file } result assert_match "*Failed to truncate AOF*to timestamp*because it is not the last file*" $result }