Merge 'origin/unstable' into 7.0 for 7.0.3
This commit is contained in:
@@ -93,4 +93,4 @@ Community Impact Guidelines were inspired by Mozilla’s code of conduct
|
||||
enforcement ladder.
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -223,11 +223,11 @@ public discussion groups, you agree to release your code under the terms
|
||||
of the BSD license that you can find in the [COPYING][1] file included in the Redis
|
||||
source distribution.
|
||||
|
||||
Please see the [CONTRIBUTING][2] file in this source distribution for more
|
||||
Please see the [CONTRIBUTING.md][2] file in this source distribution for more
|
||||
information. For security bugs and vulnerabilities, please see [SECURITY.md][3].
|
||||
|
||||
[1]: https://github.com/redis/redis/blob/unstable/COPYING
|
||||
[2]: https://github.com/redis/redis/blob/unstable/CONTRIBUTING
|
||||
[2]: https://github.com/redis/redis/blob/unstable/CONTRIBUTING.md
|
||||
[3]: https://github.com/redis/redis/blob/unstable/SECURITY.md
|
||||
|
||||
Redis internals
|
||||
|
||||
Vendored
+1
-1
@@ -57,7 +57,7 @@ typedef enum {
|
||||
REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */
|
||||
REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */
|
||||
REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED, /* Failed to load private key */
|
||||
REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certifcate store */
|
||||
REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certificate store */
|
||||
REDIS_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */
|
||||
} redisSSLContextError;
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -184,7 +184,7 @@ const char *redisSSLContextGetError(redisSSLContextError error)
|
||||
case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:
|
||||
return "Failed to load private key";
|
||||
case REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED:
|
||||
return "Failed to open system certifcate store";
|
||||
return "Failed to open system certificate store";
|
||||
case REDIS_SSL_CTX_OS_CERT_ADD_FAILED:
|
||||
return "Failed to add CA certificates obtained from system to the SSL context";
|
||||
default:
|
||||
|
||||
+1
-1
@@ -1450,7 +1450,7 @@ appendfsync everysec
|
||||
# BGSAVE or BGREWRITEAOF is in progress.
|
||||
#
|
||||
# This means that while another child is saving, the durability of Redis is
|
||||
# the same as "appendfsync none". In practical terms, this means that it is
|
||||
# the same as "appendfsync no". In practical terms, this means that it is
|
||||
# possible to lose up to 30 seconds of log in the worst scenario (with the
|
||||
# default Linux settings).
|
||||
#
|
||||
|
||||
+3
-1
@@ -286,7 +286,9 @@ sentinel failover-timeout mymaster 180000
|
||||
|
||||
sentinel deny-scripts-reconfig yes
|
||||
|
||||
# REDIS COMMANDS RENAMING
|
||||
# REDIS COMMANDS RENAMING (DEPRECATED)
|
||||
#
|
||||
# WARNING: avoid using this option if possible, instead use ACLs.
|
||||
#
|
||||
# Sometimes the Redis server has certain commands, that are needed for Sentinel
|
||||
# to work correctly, renamed to unguessable strings. This is often the case
|
||||
|
||||
@@ -2169,15 +2169,25 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
server.acl_filename, linenum);
|
||||
}
|
||||
|
||||
int j;
|
||||
for (j = 0; j < merged_argc; j++) {
|
||||
int syntax_error = 0;
|
||||
for (int j = 0; j < merged_argc; j++) {
|
||||
acl_args[j] = sdstrim(acl_args[j],"\t\r\n");
|
||||
if (ACLSetUser(u,acl_args[j],sdslen(acl_args[j])) != C_OK) {
|
||||
const char *errmsg = ACLSetUserStringError();
|
||||
errors = sdscatprintf(errors,
|
||||
"%s:%d: %s. ",
|
||||
server.acl_filename, linenum, errmsg);
|
||||
continue;
|
||||
if (errno == ENOENT) {
|
||||
/* For missing commands, we print out more information since
|
||||
* it shouldn't contain any sensitive information. */
|
||||
errors = sdscatprintf(errors,
|
||||
"%s:%d: Error in applying operation '%s': %s. ",
|
||||
server.acl_filename, linenum, acl_args[j], errmsg);
|
||||
} else if (syntax_error == 0) {
|
||||
/* For all other errors, only print out the first error encountered
|
||||
* since it might affect future operations. */
|
||||
errors = sdscatprintf(errors,
|
||||
"%s:%d: %s. ",
|
||||
server.acl_filename, linenum, errmsg);
|
||||
syntax_error = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -572,6 +572,16 @@ int writeAofManifestFile(sds buf) {
|
||||
tmp_am_name, am_name, strerror(errno));
|
||||
|
||||
ret = C_ERR;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Also sync the AOF directory as new AOF files may be added in the directory */
|
||||
if (fsyncFileDir(am_filepath) == -1) {
|
||||
serverLog(LL_WARNING, "Fail to fsync AOF directory %s: %s.",
|
||||
am_filepath, strerror(errno));
|
||||
|
||||
ret = C_ERR;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
@@ -1122,8 +1132,8 @@ void flushAppendOnlyFile(int force) {
|
||||
if (can_log) {
|
||||
serverLog(LL_WARNING,"Error writing to the AOF file: %s",
|
||||
strerror(errno));
|
||||
server.aof_last_write_errno = errno;
|
||||
}
|
||||
server.aof_last_write_errno = errno;
|
||||
} else {
|
||||
if (can_log) {
|
||||
serverLog(LL_WARNING,"Short write while writing to "
|
||||
@@ -2398,6 +2408,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
if (dirCreateIfMissing(server.aof_dirname) == -1) {
|
||||
serverLog(LL_WARNING, "Can't open or create append-only dir %s: %s",
|
||||
server.aof_dirname, strerror(errno));
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
@@ -2405,7 +2416,10 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
* feedAppendOnlyFile() to issue a SELECT command. */
|
||||
server.aof_selected_db = -1;
|
||||
flushAppendOnlyFile(1);
|
||||
if (openNewIncrAofForAppend() != C_OK) return C_ERR;
|
||||
if (openNewIncrAofForAppend() != C_OK) {
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
return C_ERR;
|
||||
}
|
||||
server.stat_aof_rewrites++;
|
||||
if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
|
||||
char tmpfile[256];
|
||||
@@ -2563,6 +2577,8 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
strerror(errno));
|
||||
aofManifestFree(temp_am);
|
||||
sdsfree(new_base_filepath);
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
server.stat_aofrw_consecutive_failures++;
|
||||
goto cleanup;
|
||||
}
|
||||
latencyEndMonitor(latency);
|
||||
@@ -2591,6 +2607,8 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
sdsfree(temp_incr_filepath);
|
||||
sdsfree(new_incr_filepath);
|
||||
sdsfree(temp_incr_aof_name);
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
server.stat_aofrw_consecutive_failures++;
|
||||
goto cleanup;
|
||||
}
|
||||
latencyEndMonitor(latency);
|
||||
@@ -2614,6 +2632,8 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
bg_unlink(new_incr_filepath);
|
||||
sdsfree(new_incr_filepath);
|
||||
}
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
server.stat_aofrw_consecutive_failures++;
|
||||
goto cleanup;
|
||||
}
|
||||
sdsfree(new_base_filepath);
|
||||
|
||||
+36
-32
@@ -308,8 +308,10 @@ int clusterLoadConfig(char *filename) {
|
||||
if (atoi(argv[4])) n->ping_sent = mstime();
|
||||
if (atoi(argv[5])) n->pong_received = mstime();
|
||||
|
||||
/* Set configEpoch for this node. */
|
||||
n->configEpoch = strtoull(argv[6],NULL,10);
|
||||
/* 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++) {
|
||||
@@ -458,8 +460,8 @@ void clusterSaveConfigOrDie(int do_fsync) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Lock the cluster config using flock(), and leaks the file descriptor used to
|
||||
* acquire the lock so that the file will be locked forever.
|
||||
/* 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
|
||||
@@ -498,8 +500,8 @@ int clusterLockConfig(char *filename) {
|
||||
close(fd);
|
||||
return C_ERR;
|
||||
}
|
||||
/* Lock acquired: leak the 'fd' by not closing it, so that we'll retain the
|
||||
* lock to the file as long as the process exists.
|
||||
/* 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(),
|
||||
@@ -964,6 +966,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
|
||||
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;
|
||||
@@ -1217,10 +1220,14 @@ clusterNode *clusterLookupNode(const char *name, int length) {
|
||||
return dictGetVal(de);
|
||||
}
|
||||
|
||||
/* Get all the nodes serving the same slots as myself. */
|
||||
/* Get all the nodes serving the same slots as the given node. */
|
||||
list *clusterGetNodesServingMySlots(clusterNode *node) {
|
||||
list *nodes_for_slot = listCreate();
|
||||
clusterNode *my_primary = nodeIsMaster(node) ? node : node->slaveof;
|
||||
|
||||
/* This function is only valid for fully connected nodes, so
|
||||
* they should have a known primary. */
|
||||
serverAssert(my_primary);
|
||||
listAddNodeTail(nodes_for_slot, my_primary);
|
||||
for (int i=0; i < my_primary->numslaves; i++) {
|
||||
listAddNodeTail(nodes_for_slot, my_primary->slaves[i]);
|
||||
@@ -1999,7 +2006,7 @@ int writeHostnamePingExt(clusterMsgPingExt **cursor) {
|
||||
uint32_t extension_size = getHostnamePingExtSize();
|
||||
|
||||
/* Move the write cursor */
|
||||
(*cursor)->type = CLUSTERMSG_EXT_TYPE_HOSTNAME;
|
||||
(*cursor)->type = htons(CLUSTERMSG_EXT_TYPE_HOSTNAME);
|
||||
(*cursor)->length = htonl(extension_size);
|
||||
/* Make sure the string is NULL terminated by adding 1 */
|
||||
*cursor = (clusterMsgPingExt *) (ext->hostname + EIGHT_BYTE_ALIGN(sdslen(myself->hostname) + 1));
|
||||
@@ -2847,18 +2854,6 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
|
||||
* totlen field is up to the caller. */
|
||||
}
|
||||
|
||||
/* Return non zero if the node is already present in the gossip section of the
|
||||
* message pointed by 'hdr' and having 'count' gossip entries. Otherwise
|
||||
* zero is returned. Helper for clusterSendPing(). */
|
||||
int clusterNodeIsInGossipSection(clusterMsg *hdr, int count, clusterNode *n) {
|
||||
int j;
|
||||
for (j = 0; j < count; j++) {
|
||||
if (memcmp(hdr->data.ping.gossip[j].nodename,n->name,
|
||||
CLUSTER_NAMELEN) == 0) break;
|
||||
}
|
||||
return j != count;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
@@ -2878,6 +2873,8 @@ void clusterSetGossipEntry(clusterMsg *hdr, int i, clusterNode *n) {
|
||||
/* 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++;
|
||||
unsigned char *buf;
|
||||
clusterMsg *hdr;
|
||||
int gossipcount = 0; /* Number of gossip sections added so far. */
|
||||
@@ -2928,7 +2925,7 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
* to put inside the packet. */
|
||||
estlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
|
||||
estlen += (sizeof(clusterMsgDataGossip)*(wanted + pfail_wanted));
|
||||
estlen += sizeof(clusterMsgPingExt) + getHostnamePingExtSize();
|
||||
estlen += getHostnamePingExtSize();
|
||||
|
||||
/* Note: clusterBuildMessageHdr() expects the buffer to be always at least
|
||||
* sizeof(clusterMsg) or more. */
|
||||
@@ -2967,10 +2964,11 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
}
|
||||
|
||||
/* Do not add a node we already have. */
|
||||
if (clusterNodeIsInGossipSection(hdr,gossipcount,this)) continue;
|
||||
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++;
|
||||
}
|
||||
@@ -2987,7 +2985,6 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
if (node->flags & CLUSTER_NODE_NOADDR) continue;
|
||||
if (!(node->flags & CLUSTER_NODE_PFAIL)) continue;
|
||||
clusterSetGossipEntry(hdr,gossipcount,node);
|
||||
freshnodes--;
|
||||
gossipcount++;
|
||||
/* We take the count of the slots we allocated, since the
|
||||
* PFAIL stats may not match perfectly with the current number
|
||||
@@ -5110,7 +5107,7 @@ void clusterReplyShards(client *c) {
|
||||
* information and an empty slots array. */
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *n = dictGetVal(de);
|
||||
if (nodeIsSlave(n)) {
|
||||
if (!nodeIsMaster(n)) {
|
||||
/* You can force a replica to own slots, even though it'll get reverted,
|
||||
* so freeing the slot pair here just in case. */
|
||||
clusterFreeNodesSlotsInfo(n);
|
||||
@@ -6104,7 +6101,7 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
|
||||
|
||||
/* Create the socket */
|
||||
conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
|
||||
if (connBlockingConnect(conn, c->argv[1]->ptr, atoi(c->argv[2]->ptr), timeout)
|
||||
if (connBlockingConnect(conn, host->ptr, atoi(port->ptr), timeout)
|
||||
!= C_OK) {
|
||||
addReplyError(c,"-IOERR error or timeout connecting to the client");
|
||||
connClose(conn);
|
||||
@@ -6583,7 +6580,8 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
int multiple_keys = 0;
|
||||
multiState *ms, _ms;
|
||||
multiCmd mc;
|
||||
int i, slot = 0, migrating_slot = 0, importing_slot = 0, missing_keys = 0;
|
||||
int i, slot = 0, migrating_slot = 0, importing_slot = 0, missing_keys = 0,
|
||||
existing_keys = 0;
|
||||
|
||||
/* Allow any key to be set if a module disabled cluster redirections. */
|
||||
if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
|
||||
@@ -6695,10 +6693,10 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
* node until the migration completes with CLUSTER SETSLOT <slot>
|
||||
* NODE <node-id>. */
|
||||
int flags = LOOKUP_NOTOUCH | LOOKUP_NOSTATS | LOOKUP_NONOTIFY;
|
||||
if ((migrating_slot || importing_slot) && !is_pubsubshard &&
|
||||
lookupKeyReadWithFlags(&server.db[0], thiskey, flags) == NULL)
|
||||
if ((migrating_slot || importing_slot) && !is_pubsubshard)
|
||||
{
|
||||
missing_keys++;
|
||||
if (lookupKeyReadWithFlags(&server.db[0], thiskey, flags) == NULL) missing_keys++;
|
||||
else existing_keys++;
|
||||
}
|
||||
}
|
||||
getKeysFreeResult(&result);
|
||||
@@ -6742,10 +6740,16 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
return myself;
|
||||
|
||||
/* If we don't have all the keys and we are migrating the slot, send
|
||||
* an ASK redirection. */
|
||||
* an ASK redirection or TRYAGAIN. */
|
||||
if (migrating_slot && missing_keys) {
|
||||
if (error_code) *error_code = CLUSTER_REDIR_ASK;
|
||||
return server.cluster->migrating_slots_to[slot];
|
||||
/* If we have keys but we don't have all keys, we return TRYAGAIN */
|
||||
if (existing_keys) {
|
||||
if (error_code) *error_code = CLUSTER_REDIR_UNSTABLE;
|
||||
return NULL;
|
||||
} else {
|
||||
if (error_code) *error_code = CLUSTER_REDIR_ASK;
|
||||
return server.cluster->migrating_slots_to[slot];
|
||||
}
|
||||
}
|
||||
|
||||
/* If we are receiving the slot, and the client correctly flagged the
|
||||
|
||||
@@ -127,6 +127,7 @@ typedef struct clusterNode {
|
||||
may be NULL even if the node is a slave
|
||||
if we don't have the master node in our
|
||||
tables. */
|
||||
unsigned long long last_in_ping_gossip; /* The number of the last carried in the ping gossip section */
|
||||
mstime_t ping_sent; /* Unix time we sent latest ping */
|
||||
mstime_t pong_received; /* Unix time we received the pong */
|
||||
mstime_t data_received; /* Unix time we received any data */
|
||||
|
||||
+32
-28
@@ -470,7 +470,10 @@ NULL
|
||||
/********** CLUSTER MEET ********************/
|
||||
|
||||
/* CLUSTER MEET history */
|
||||
#define CLUSTER_MEET_History NULL
|
||||
commandHistory CLUSTER_MEET_History[] = {
|
||||
{"4.0.0","Added the optional `cluster_bus_port` argument."},
|
||||
{0}
|
||||
};
|
||||
|
||||
/* CLUSTER MEET tips */
|
||||
const char *CLUSTER_MEET_tips[] = {
|
||||
@@ -482,6 +485,7 @@ NULL
|
||||
struct redisCommandArg CLUSTER_MEET_Args[] = {
|
||||
{"ip",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
|
||||
{"port",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE},
|
||||
{"cluster_bus_port",ARG_TYPE_INTEGER,-1,NULL,NULL,"4.0.0",CMD_ARG_OPTIONAL},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -685,7 +689,7 @@ struct redisCommand CLUSTER_Subcommands[] = {
|
||||
{"nodes","Get Cluster config for the node","O(N) where N is the total number of Cluster nodes","3.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CLUSTER,CLUSTER_NODES_History,CLUSTER_NODES_tips,clusterCommand,2,CMD_STALE,0},
|
||||
{"replicas","List replica nodes of the specified master node","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CLUSTER,CLUSTER_REPLICAS_History,CLUSTER_REPLICAS_tips,clusterCommand,3,CMD_ADMIN|CMD_STALE,0,.args=CLUSTER_REPLICAS_Args},
|
||||
{"replicate","Reconfigure a node as a replica of the specified master node","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CLUSTER,CLUSTER_REPLICATE_History,CLUSTER_REPLICATE_tips,clusterCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,.args=CLUSTER_REPLICATE_Args},
|
||||
{"reset","Reset a Redis Cluster node","O(N) where N is the number of known nodes. The command may execute a FLUSHALL as a side effect.","3.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CLUSTER,CLUSTER_RESET_History,CLUSTER_RESET_tips,clusterCommand,3,CMD_ADMIN|CMD_STALE|CMD_NOSCRIPT,0,.args=CLUSTER_RESET_Args},
|
||||
{"reset","Reset a Redis Cluster node","O(N) where N is the number of known nodes. The command may execute a FLUSHALL as a side effect.","3.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CLUSTER,CLUSTER_RESET_History,CLUSTER_RESET_tips,clusterCommand,-2,CMD_ADMIN|CMD_STALE|CMD_NOSCRIPT,0,.args=CLUSTER_RESET_Args},
|
||||
{"saveconfig","Forces the node to save cluster state on disk","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CLUSTER,CLUSTER_SAVECONFIG_History,CLUSTER_SAVECONFIG_tips,clusterCommand,2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0},
|
||||
{"set-config-epoch","Set the configuration epoch in a new node","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CLUSTER,CLUSTER_SET_CONFIG_EPOCH_History,CLUSTER_SET_CONFIG_EPOCH_tips,clusterCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,.args=CLUSTER_SET_CONFIG_EPOCH_Args},
|
||||
{"setslot","Bind a hash slot to a specific node","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CLUSTER,CLUSTER_SETSLOT_History,CLUSTER_SETSLOT_tips,clusterCommand,-4,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,.args=CLUSTER_SETSLOT_Args},
|
||||
@@ -1025,22 +1029,22 @@ struct redisCommandArg CLIENT_UNBLOCK_Args[] = {
|
||||
|
||||
/* CLIENT command table */
|
||||
struct redisCommand CLIENT_Subcommands[] = {
|
||||
{"caching","Instruct the server about tracking or not keys in the next request","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_CACHING_History,CLIENT_CACHING_tips,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=CLIENT_CACHING_Args},
|
||||
{"getname","Get the current connection name","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_GETNAME_History,CLIENT_GETNAME_tips,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
|
||||
{"getredir","Get tracking notifications redirection client ID if any","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_GETREDIR_History,CLIENT_GETREDIR_tips,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
|
||||
{"help","Show helpful text about the different subcommands","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_HELP_History,CLIENT_HELP_tips,clientCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
|
||||
{"id","Returns the client ID for the current connection","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_ID_History,CLIENT_ID_tips,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
|
||||
{"info","Returns information about the current client connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_INFO_History,CLIENT_INFO_tips,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
|
||||
{"kill","Kill the connection of a client","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,CLIENT_KILL_tips,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=CLIENT_KILL_Args},
|
||||
{"list","Get the list of client connections","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,CLIENT_LIST_tips,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=CLIENT_LIST_Args},
|
||||
{"no-evict","Set client eviction mode for the current connection","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_NO_EVICT_History,CLIENT_NO_EVICT_tips,clientCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=CLIENT_NO_EVICT_Args},
|
||||
{"pause","Stop processing commands from clients for some time","O(1)","2.9.50",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_PAUSE_History,CLIENT_PAUSE_tips,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=CLIENT_PAUSE_Args},
|
||||
{"reply","Instruct the server whether to reply to commands","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_REPLY_History,CLIENT_REPLY_tips,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=CLIENT_REPLY_Args},
|
||||
{"setname","Set the current connection name","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_SETNAME_History,CLIENT_SETNAME_tips,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=CLIENT_SETNAME_Args},
|
||||
{"tracking","Enable or disable server assisted client side caching support","O(1). Some options may introduce additional complexity.","6.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_TRACKING_History,CLIENT_TRACKING_tips,clientCommand,-3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=CLIENT_TRACKING_Args},
|
||||
{"trackinginfo","Return information about server assisted client side caching for the current connection","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_TRACKINGINFO_History,CLIENT_TRACKINGINFO_tips,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
|
||||
{"unblock","Unblock a client blocked in a blocking command from a different connection","O(log N) where N is the number of client connections","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_UNBLOCK_History,CLIENT_UNBLOCK_tips,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=CLIENT_UNBLOCK_Args},
|
||||
{"unpause","Resume processing of clients that were paused","O(N) Where N is the number of paused clients","6.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_UNPAUSE_History,CLIENT_UNPAUSE_tips,clientCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
|
||||
{"caching","Instruct the server about tracking or not keys in the next request","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_CACHING_History,CLIENT_CACHING_tips,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_CACHING_Args},
|
||||
{"getname","Get the current connection name","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_GETNAME_History,CLIENT_GETNAME_tips,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION},
|
||||
{"getredir","Get tracking notifications redirection client ID if any","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_GETREDIR_History,CLIENT_GETREDIR_tips,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION},
|
||||
{"help","Show helpful text about the different subcommands","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_HELP_History,CLIENT_HELP_tips,clientCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION},
|
||||
{"id","Returns the client ID for the current connection","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_ID_History,CLIENT_ID_tips,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION},
|
||||
{"info","Returns information about the current client connection.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_INFO_History,CLIENT_INFO_tips,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION},
|
||||
{"kill","Kill the connection of a client","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_KILL_History,CLIENT_KILL_tips,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_KILL_Args},
|
||||
{"list","Get the list of client connections","O(N) where N is the number of client connections","2.4.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_LIST_History,CLIENT_LIST_tips,clientCommand,-2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_LIST_Args},
|
||||
{"no-evict","Set client eviction mode for the current connection","O(1)","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_NO_EVICT_History,CLIENT_NO_EVICT_tips,clientCommand,3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_NO_EVICT_Args},
|
||||
{"pause","Stop processing commands from clients for some time","O(1)","2.9.50",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_PAUSE_History,CLIENT_PAUSE_tips,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_PAUSE_Args},
|
||||
{"reply","Instruct the server whether to reply to commands","O(1)","3.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_REPLY_History,CLIENT_REPLY_tips,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_REPLY_Args},
|
||||
{"setname","Set the current connection name","O(1)","2.6.9",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_SETNAME_History,CLIENT_SETNAME_tips,clientCommand,3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_SETNAME_Args},
|
||||
{"tracking","Enable or disable server assisted client side caching support","O(1). Some options may introduce additional complexity.","6.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_TRACKING_History,CLIENT_TRACKING_tips,clientCommand,-3,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_TRACKING_Args},
|
||||
{"trackinginfo","Return information about server assisted client side caching for the current connection","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_TRACKINGINFO_History,CLIENT_TRACKINGINFO_tips,clientCommand,2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION},
|
||||
{"unblock","Unblock a client blocked in a blocking command from a different connection","O(log N) where N is the number of client connections","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_UNBLOCK_History,CLIENT_UNBLOCK_tips,clientCommand,-3,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=CLIENT_UNBLOCK_Args},
|
||||
{"unpause","Resume processing of clients that were paused","O(N) Where N is the number of paused clients","6.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_UNPAUSE_History,CLIENT_UNPAUSE_tips,clientCommand,2,CMD_ADMIN|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -4317,13 +4321,13 @@ struct redisCommandArg COMMAND_LIST_Args[] = {
|
||||
|
||||
/* COMMAND command table */
|
||||
struct redisCommand COMMAND_Subcommands[] = {
|
||||
{"count","Get total number of Redis commands","O(1)","2.8.13",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_COUNT_History,COMMAND_COUNT_tips,commandCountCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
|
||||
{"docs","Get array of specific Redis command documentation","O(N) where N is the number of commands to look up","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_DOCS_History,COMMAND_DOCS_tips,commandDocsCommand,-2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=COMMAND_DOCS_Args},
|
||||
{"getkeys","Extract keys given a full Redis command","O(N) where N is the number of arguments to the command","2.8.13",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_GETKEYS_History,COMMAND_GETKEYS_tips,commandGetKeysCommand,-4,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
|
||||
{"getkeysandflags","Extract keys and access flags given a full Redis command","O(N) where N is the number of arguments to the command","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_GETKEYSANDFLAGS_History,COMMAND_GETKEYSANDFLAGS_tips,commandGetKeysAndFlagsCommand,-4,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
|
||||
{"help","Show helpful text about the different subcommands","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_HELP_History,COMMAND_HELP_tips,commandHelpCommand,2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION},
|
||||
{"info","Get array of specific Redis command details, or all when no argument is given.","O(N) where N is the number of commands to look up","2.8.13",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_INFO_History,COMMAND_INFO_tips,commandInfoCommand,-2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=COMMAND_INFO_Args},
|
||||
{"list","Get an array of Redis command names","O(N) where N is the total number of Redis commands","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_LIST_History,COMMAND_LIST_tips,commandListCommand,-2,CMD_LOADING|CMD_STALE,ACL_CATEGORY_CONNECTION,.args=COMMAND_LIST_Args},
|
||||
{"count","Get total number of Redis commands","O(1)","2.8.13",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_COUNT_History,COMMAND_COUNT_tips,commandCountCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION},
|
||||
{"docs","Get array of specific Redis command documentation","O(N) where N is the number of commands to look up","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_DOCS_History,COMMAND_DOCS_tips,commandDocsCommand,-2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=COMMAND_DOCS_Args},
|
||||
{"getkeys","Extract keys given a full Redis command","O(N) where N is the number of arguments to the command","2.8.13",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_GETKEYS_History,COMMAND_GETKEYS_tips,commandGetKeysCommand,-4,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION},
|
||||
{"getkeysandflags","Extract keys and access flags given a full Redis command","O(N) where N is the number of arguments to the command","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_GETKEYSANDFLAGS_History,COMMAND_GETKEYSANDFLAGS_tips,commandGetKeysAndFlagsCommand,-4,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION},
|
||||
{"help","Show helpful text about the different subcommands","O(1)","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_HELP_History,COMMAND_HELP_tips,commandHelpCommand,2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION},
|
||||
{"info","Get array of specific Redis command details, or all when no argument is given.","O(N) where N is the number of commands to look up","2.8.13",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_INFO_History,COMMAND_INFO_tips,commandInfoCommand,-2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=COMMAND_INFO_Args},
|
||||
{"list","Get an array of Redis command names","O(N) where N is the total number of Redis commands","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SERVER,COMMAND_LIST_History,COMMAND_LIST_tips,commandListCommand,-2,CMD_LOADING|CMD_STALE|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=COMMAND_LIST_Args},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -5808,8 +5812,8 @@ struct redisCommandArg ZRANGE_offset_count_Subargs[] = {
|
||||
/* ZRANGE argument table */
|
||||
struct redisCommandArg ZRANGE_Args[] = {
|
||||
{"key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE},
|
||||
{"min",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
|
||||
{"max",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
|
||||
{"start",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
|
||||
{"stop",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
|
||||
{"sortby",ARG_TYPE_ONEOF,-1,NULL,NULL,"6.2.0",CMD_ARG_OPTIONAL,.subargs=ZRANGE_sortby_Subargs},
|
||||
{"rev",ARG_TYPE_PURE_TOKEN,-1,"REV",NULL,"6.2.0",CMD_ARG_OPTIONAL},
|
||||
{"offset_count",ARG_TYPE_BLOCK,-1,"LIMIT",NULL,"6.2.0",CMD_ARG_OPTIONAL,.subargs=ZRANGE_offset_count_Subargs},
|
||||
@@ -7175,7 +7179,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
/* connection */
|
||||
{"auth","Authenticate to the server","O(N) where N is the number of passwords defined for the user","1.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,AUTH_History,AUTH_tips,authCommand,-2,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_NO_AUTH|CMD_SENTINEL|CMD_ALLOW_BUSY,ACL_CATEGORY_CONNECTION,.args=AUTH_Args},
|
||||
{"client","A container for client connection commands","Depends on subcommand.","2.4.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,CLIENT_History,CLIENT_tips,NULL,-2,CMD_SENTINEL,0,.subcommands=CLIENT_Subcommands},
|
||||
{"echo","Echo the given string","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,ECHO_History,ECHO_tips,echoCommand,2,CMD_FAST,ACL_CATEGORY_CONNECTION,.args=ECHO_Args},
|
||||
{"echo","Echo the given string","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,ECHO_History,ECHO_tips,echoCommand,2,CMD_LOADING|CMD_STALE|CMD_FAST,ACL_CATEGORY_CONNECTION,.args=ECHO_Args},
|
||||
{"hello","Handshake with Redis","O(1)","6.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,HELLO_History,HELLO_tips,helloCommand,-1,CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_NO_AUTH|CMD_SENTINEL|CMD_ALLOW_BUSY,ACL_CATEGORY_CONNECTION,.args=HELLO_Args},
|
||||
{"ping","Ping the server","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,PING_History,PING_tips,pingCommand,-1,CMD_FAST|CMD_SENTINEL,ACL_CATEGORY_CONNECTION,.args=PING_Args},
|
||||
{"quit","Close the connection","O(1)","1.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_CONNECTION,QUIT_History,QUIT_tips,quitCommand,-1,CMD_ALLOW_BUSY|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_NO_AUTH,ACL_CATEGORY_CONNECTION},
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"command_flags": [
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"command_flags": [
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"command_flags": [
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"function": "clientCommand",
|
||||
"command_flags": [
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"command_flags": [
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"command_flags": [
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
"ADMIN",
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
"ADMIN",
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
"ADMIN",
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
"ADMIN",
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"command_flags": [
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"command_flags": [
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"command_flags": [
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"command_flags": [
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
"ADMIN",
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
"ADMIN",
|
||||
"NOSCRIPT",
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
"arity": -4,
|
||||
"container": "CLUSTER",
|
||||
"function": "clusterCommand",
|
||||
"history": [
|
||||
[
|
||||
"4.0.0",
|
||||
"Added the optional `cluster_bus_port` argument."
|
||||
]
|
||||
],
|
||||
"command_flags": [
|
||||
"NO_ASYNC_LOADING",
|
||||
"ADMIN",
|
||||
@@ -23,6 +29,12 @@
|
||||
{
|
||||
"name": "port",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "cluster_bus_port",
|
||||
"type": "integer",
|
||||
"optional": true,
|
||||
"since": "4.0.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"complexity": "O(N) where N is the number of known nodes. The command may execute a FLUSHALL as a side effect.",
|
||||
"group": "cluster",
|
||||
"since": "3.0.0",
|
||||
"arity": 3,
|
||||
"arity": -2,
|
||||
"container": "CLUSTER",
|
||||
"function": "clusterCommand",
|
||||
"command_flags": [
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"function": "commandCountCommand",
|
||||
"command_flags": [
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"function": "commandDocsCommand",
|
||||
"command_flags": [
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"function": "commandGetKeysCommand",
|
||||
"command_flags": [
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"function": "commandGetKeysAndFlagsCommand",
|
||||
"command_flags": [
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"function": "commandHelpCommand",
|
||||
"command_flags": [
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
],
|
||||
"command_flags": [
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"function": "commandListCommand",
|
||||
"command_flags": [
|
||||
"LOADING",
|
||||
"STALE"
|
||||
"STALE",
|
||||
"SENTINEL"
|
||||
],
|
||||
"acl_categories": [
|
||||
"CONNECTION"
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"arity": 2,
|
||||
"function": "echoCommand",
|
||||
"command_flags": [
|
||||
"LOADING",
|
||||
"STALE",
|
||||
"FAST"
|
||||
],
|
||||
"acl_categories": [
|
||||
|
||||
@@ -45,11 +45,11 @@
|
||||
"key_spec_index": 0
|
||||
},
|
||||
{
|
||||
"name": "min",
|
||||
"name": "start",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"name": "stop",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1665,6 +1665,7 @@ int rewriteConfigOverwriteFile(char *configfile, sds content) {
|
||||
const char *tmp_suffix = ".XXXXXX";
|
||||
size_t offset = 0;
|
||||
ssize_t written_bytes = 0;
|
||||
int old_errno;
|
||||
|
||||
int tmp_path_len = snprintf(tmp_conffile, sizeof(tmp_conffile), "%s%s", configfile, tmp_suffix);
|
||||
if (tmp_path_len <= 0 || (unsigned int)tmp_path_len >= sizeof(tmp_conffile)) {
|
||||
@@ -1701,14 +1702,18 @@ int rewriteConfigOverwriteFile(char *configfile, sds content) {
|
||||
serverLog(LL_WARNING, "Could not chmod config file (%s)", strerror(errno));
|
||||
else if (rename(tmp_conffile, configfile) == -1)
|
||||
serverLog(LL_WARNING, "Could not rename tmp config file (%s)", strerror(errno));
|
||||
else if (fsyncFileDir(configfile) == -1)
|
||||
serverLog(LL_WARNING, "Could not sync config file dir (%s)", strerror(errno));
|
||||
else {
|
||||
retval = 0;
|
||||
serverLog(LL_DEBUG, "Rewritten config file (%s) successfully", configfile);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
old_errno = errno;
|
||||
close(fd);
|
||||
if (retval) unlink(tmp_conffile);
|
||||
errno = old_errno;
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -282,7 +282,7 @@ int dictRehashMilliseconds(dict *d, int ms) {
|
||||
/* This function performs just a step of rehashing, and only if hashing has
|
||||
* not been paused for our hash table. When we have iterators in the
|
||||
* middle of a rehashing we can't mess with the two hash tables otherwise
|
||||
* some element can be missed or duplicated.
|
||||
* some elements can be missed or duplicated.
|
||||
*
|
||||
* This function is called by common lookup or update operations in the
|
||||
* dictionary so that the hash table automatically migrates from H1 to H2
|
||||
|
||||
+5
-5
@@ -266,14 +266,14 @@ void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evic
|
||||
* has a low value.
|
||||
*
|
||||
* New keys don't start at zero, in order to have the ability to collect
|
||||
* some accesses before being trashed away, so they start at COUNTER_INIT_VAL.
|
||||
* The logarithmic increment performed on LOG_C takes care of COUNTER_INIT_VAL
|
||||
* when incrementing the key, so that keys starting at COUNTER_INIT_VAL
|
||||
* some accesses before being trashed away, so they start at LFU_INIT_VAL.
|
||||
* The logarithmic increment performed on LOG_C takes care of LFU_INIT_VAL
|
||||
* when incrementing the key, so that keys starting at LFU_INIT_VAL
|
||||
* (or having a smaller value) have a very high chance of being incremented
|
||||
* on access.
|
||||
*
|
||||
* During decrement, the value of the logarithmic counter is halved if
|
||||
* its current value is greater than two times the COUNTER_INIT_VAL, otherwise
|
||||
* its current value is greater than two times the LFU_INIT_VAL, otherwise
|
||||
* it is just decremented by one.
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
@@ -295,7 +295,7 @@ unsigned long LFUTimeElapsed(unsigned long ldt) {
|
||||
}
|
||||
|
||||
/* Logarithmically increment a counter. The greater is the current counter value
|
||||
* the less likely is that it gets really implemented. Saturate it at 255. */
|
||||
* the less likely is that it gets really incremented. Saturate it at 255. */
|
||||
uint8_t LFULogIncr(uint8_t counter) {
|
||||
if (counter == 255) return 255;
|
||||
double r = (double)rand()/RAND_MAX;
|
||||
|
||||
+1
-1
@@ -674,7 +674,7 @@ void fcallroCommand(client *c) {
|
||||
* * Engine name
|
||||
* * Library description
|
||||
* * Library code
|
||||
* RDB_OPCODE_FUNCTION is saved before each library to present
|
||||
* RDB_OPCODE_FUNCTION2 is saved before each library to present
|
||||
* that the payload is a library.
|
||||
* RDB version and crc64 is saved at the end of the payload.
|
||||
* The RDB version is saved for backward compatibility.
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
/* Automatically generated by ./utils/generate-command-help.rb, do not edit. */
|
||||
/* Automatically generated by utils/generate-command-help.rb, do not edit. */
|
||||
|
||||
#ifndef __REDIS_HELP_H
|
||||
#define __REDIS_HELP_H
|
||||
@@ -138,7 +138,7 @@ struct commandHelp {
|
||||
"key GET encoding offset [GET encoding offset ...]",
|
||||
"Perform arbitrary bitfield integer operations on strings. Read-only variant of BITFIELD",
|
||||
15,
|
||||
"6.2.0" },
|
||||
"6.0.0" },
|
||||
{ "BITOP",
|
||||
"operation destkey key [key ...]",
|
||||
"Perform bitwise operations between strings",
|
||||
@@ -355,7 +355,7 @@ struct commandHelp {
|
||||
12,
|
||||
"7.0.0" },
|
||||
{ "CLUSTER MEET",
|
||||
"ip port",
|
||||
"ip port [cluster_bus_port]",
|
||||
"Force a node cluster to handshake with another node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
@@ -1775,7 +1775,7 @@ struct commandHelp {
|
||||
4,
|
||||
"6.2.0" },
|
||||
{ "ZRANGE",
|
||||
"key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]",
|
||||
"key start stop [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]",
|
||||
"Return a range of members in a sorted set",
|
||||
4,
|
||||
"1.2.0" },
|
||||
|
||||
+1
-4
@@ -49,7 +49,6 @@
|
||||
#define LP_HDR_NUMELE_UNKNOWN UINT16_MAX
|
||||
#define LP_MAX_INT_ENCODING_LEN 9
|
||||
#define LP_MAX_BACKLEN_SIZE 5
|
||||
#define LP_MAX_ENTRY_BACKLEN 34359738367ULL
|
||||
#define LP_ENCODING_INT 0
|
||||
#define LP_ENCODING_STRING 1
|
||||
|
||||
@@ -569,9 +568,7 @@ unsigned long lpLength(unsigned char *lp) {
|
||||
* this lib.
|
||||
*
|
||||
* Similarly, there is no error returned since the listpack normally can be
|
||||
* assumed to be valid, so that would be a very high API cost. However a function
|
||||
* in order to check the integrity of the listpack at load time is provided,
|
||||
* check lpIsValid(). */
|
||||
* assumed to be valid, so that would be a very high API cost. */
|
||||
static inline unsigned char *lpGetWithSize(unsigned char *p, int64_t *count, unsigned char *intbuf, uint64_t *entry_size) {
|
||||
int64_t val;
|
||||
uint64_t uval, negstart, negmax;
|
||||
|
||||
+77
-8
@@ -2297,7 +2297,7 @@ RedisModuleString *RM_CreateStringPrintf(RedisModuleCtx *ctx, const char *fmt, .
|
||||
}
|
||||
|
||||
|
||||
/* Like RedisModule_CreatString(), 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 RedisModule_FreeString() or by
|
||||
@@ -2311,7 +2311,21 @@ RedisModuleString *RM_CreateStringFromLongLong(RedisModuleCtx *ctx, long long ll
|
||||
return RM_CreateString(ctx,buf,len);
|
||||
}
|
||||
|
||||
/* Like RedisModule_CreatString(), but creates a string starting from a double
|
||||
/* 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 RedisModule_FreeString() or by
|
||||
* enabling automatic memory management.
|
||||
*
|
||||
* The passed context 'ctx' may be NULL if necessary, see the
|
||||
* 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 RM_CreateString(ctx,buf,len);
|
||||
}
|
||||
|
||||
/* 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 RedisModule_FreeString() or by
|
||||
@@ -2322,7 +2336,7 @@ RedisModuleString *RM_CreateStringFromDouble(RedisModuleCtx *ctx, double d) {
|
||||
return RM_CreateString(ctx,buf,len);
|
||||
}
|
||||
|
||||
/* Like RedisModule_CreatString(), 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 RedisModule_FreeString() or by
|
||||
@@ -2337,7 +2351,7 @@ RedisModuleString *RM_CreateStringFromLongDouble(RedisModuleCtx *ctx, long doubl
|
||||
return RM_CreateString(ctx,buf,len);
|
||||
}
|
||||
|
||||
/* Like RedisModule_CreatString(), but creates a string starting from another
|
||||
/* Like RedisModule_CreateString(), but creates a string starting from another
|
||||
* RedisModuleString.
|
||||
*
|
||||
* The returned string must be released with RedisModule_FreeString() or by
|
||||
@@ -2519,6 +2533,14 @@ int RM_StringToLongLong(const RedisModuleString *str, long long *ll) {
|
||||
REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Convert the string into a `unsigned long long` integer, storing it at `*ull`.
|
||||
* 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 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 REDISMODULE_OK on success or REDISMODULE_ERR if the string is
|
||||
* not a valid string representation of a double value. */
|
||||
@@ -3327,8 +3349,8 @@ int modulePopulateReplicationInfoStructure(void *ri, int structver) {
|
||||
* is returned.
|
||||
*
|
||||
* When the client exist and the `ci` pointer is not NULL, but points to
|
||||
* a structure of type RedisModuleClientInfo, previously initialized with
|
||||
* the correct REDISMODULE_CLIENTINFO_INITIALIZER, 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; // REDISMODULE_CLIENTINFO_FLAG_*
|
||||
@@ -3373,6 +3395,40 @@ int RM_GetClientInfoById(void *ci, uint64_t id) {
|
||||
return modulePopulateClientInfoStructure(ci,client,structver);
|
||||
}
|
||||
|
||||
/* Returns the name of the client connection with the given ID.
|
||||
*
|
||||
* If the client ID does not exist or if the client has no name associated with
|
||||
* it, NULL is returned. */
|
||||
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, 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 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 RM_SetClientNameById(uint64_t id, RedisModuleString *name) {
|
||||
client *client = lookupClientByID(id);
|
||||
if (client == NULL) {
|
||||
errno = ENOENT;
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
if (clientSetName(client, name) == C_ERR) {
|
||||
errno = EINVAL;
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Publish a message to subscribers (see PUBLISH command). */
|
||||
int RM_PublishMessage(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) {
|
||||
UNUSED(ctx);
|
||||
@@ -5783,7 +5839,16 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
|
||||
if (flags & REDISMODULE_ARGV_RESPECT_DENY_OOM) {
|
||||
if (cmd->flags & CMD_DENYOOM) {
|
||||
if (server.pre_command_oom_state) {
|
||||
int oom_state;
|
||||
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. */
|
||||
oom_state = (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_ERR);
|
||||
} else {
|
||||
oom_state = server.pre_command_oom_state;
|
||||
}
|
||||
if (oom_state) {
|
||||
errno = ENOSPC;
|
||||
if (error_as_call_replies) {
|
||||
sds msg = sdsdup(shared.oomerr->ptr);
|
||||
@@ -5823,7 +5888,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
}
|
||||
|
||||
int deny_write_type = writeCommandsDeniedByDiskError();
|
||||
int obey_client = mustObeyClient(server.current_client);
|
||||
int obey_client = (server.current_client && mustObeyClient(server.current_client));
|
||||
|
||||
if (deny_write_type != DISK_ERROR_TYPE_NONE && !obey_client) {
|
||||
errno = ESPIPE;
|
||||
@@ -12378,6 +12443,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(ListInsert);
|
||||
REGISTER_API(ListDelete);
|
||||
REGISTER_API(StringToLongLong);
|
||||
REGISTER_API(StringToULongLong);
|
||||
REGISTER_API(StringToDouble);
|
||||
REGISTER_API(StringToLongDouble);
|
||||
REGISTER_API(StringToStreamID);
|
||||
@@ -12400,6 +12466,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(CreateStringFromCallReply);
|
||||
REGISTER_API(CreateString);
|
||||
REGISTER_API(CreateStringFromLongLong);
|
||||
REGISTER_API(CreateStringFromULongLong);
|
||||
REGISTER_API(CreateStringFromDouble);
|
||||
REGISTER_API(CreateStringFromLongDouble);
|
||||
REGISTER_API(CreateStringFromString);
|
||||
@@ -12592,6 +12659,8 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(ServerInfoGetFieldUnsigned);
|
||||
REGISTER_API(ServerInfoGetFieldDouble);
|
||||
REGISTER_API(GetClientInfoById);
|
||||
REGISTER_API(GetClientNameById);
|
||||
REGISTER_API(SetClientNameById);
|
||||
REGISTER_API(PublishMessage);
|
||||
REGISTER_API(PublishMessageShard);
|
||||
REGISTER_API(SubscribeToServerEvent);
|
||||
|
||||
+14
-3
@@ -38,6 +38,7 @@ void initClientMultiState(client *c) {
|
||||
c->mstate.cmd_flags = 0;
|
||||
c->mstate.cmd_inv_flags = 0;
|
||||
c->mstate.argv_len_sums = 0;
|
||||
c->mstate.alloc_count = 0;
|
||||
}
|
||||
|
||||
/* Release all the resources associated with MULTI/EXEC state */
|
||||
@@ -65,9 +66,16 @@ void queueMultiCommand(client *c, uint64_t cmd_flags) {
|
||||
* aborted. */
|
||||
if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC))
|
||||
return;
|
||||
|
||||
c->mstate.commands = zrealloc(c->mstate.commands,
|
||||
sizeof(multiCmd)*(c->mstate.count+1));
|
||||
if (c->mstate.count == 0) {
|
||||
/* If a client is using multi/exec, assuming it is used to execute at least
|
||||
* two commands. Hence, creating by default size of 2. */
|
||||
c->mstate.commands = zmalloc(sizeof(multiCmd)*2);
|
||||
c->mstate.alloc_count = 2;
|
||||
}
|
||||
if (c->mstate.count == c->mstate.alloc_count) {
|
||||
c->mstate.alloc_count = c->mstate.alloc_count < INT_MAX/2 ? c->mstate.alloc_count*2 : INT_MAX;
|
||||
c->mstate.commands = zrealloc(c->mstate.commands, sizeof(multiCmd)*(c->mstate.alloc_count));
|
||||
}
|
||||
mc = c->mstate.commands+c->mstate.count;
|
||||
mc->cmd = c->cmd;
|
||||
mc->argc = c->argc;
|
||||
@@ -230,6 +238,7 @@ void execCommand(client *c) {
|
||||
/* Commands may alter argc/argv, restore mstate. */
|
||||
c->mstate.commands[j].argc = c->argc;
|
||||
c->mstate.commands[j].argv = c->argv;
|
||||
c->mstate.commands[j].argv_len = c->argv_len;
|
||||
c->mstate.commands[j].cmd = c->cmd;
|
||||
}
|
||||
|
||||
@@ -465,5 +474,7 @@ size_t multiStateMemOverhead(client *c) {
|
||||
size_t mem = c->mstate.argv_len_sums;
|
||||
/* Add watched keys overhead, Note: this doesn't take into account the watched keys themselves, because they aren't managed per-client. */
|
||||
mem += listLength(c->watched_keys) * (sizeof(listNode) + sizeof(watchedKey));
|
||||
/* Reserved memory for queued multi commands. */
|
||||
mem += c->mstate.alloc_count * sizeof(multiCmd);
|
||||
return mem;
|
||||
}
|
||||
|
||||
+34
-22
@@ -2785,7 +2785,7 @@ sds catClientInfoString(sds s, client *client) {
|
||||
}
|
||||
|
||||
sds ret = sdscatfmt(s,
|
||||
"id=%U addr=%s laddr=%s %s name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U argv-mem=%U multi-mem=%U rbs=%U rbp=%U obl=%U oll=%U omem=%U tot-mem=%U events=%s cmd=%s user=%s redir=%I resp=%i",
|
||||
"id=%U addr=%s laddr=%s %s name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i ssub=%i multi=%i qbuf=%U qbuf-free=%U argv-mem=%U multi-mem=%U rbs=%U rbp=%U obl=%U oll=%U omem=%U tot-mem=%U events=%s cmd=%s user=%s redir=%I resp=%i",
|
||||
(unsigned long long) client->id,
|
||||
getClientPeerId(client),
|
||||
getClientSockname(client),
|
||||
@@ -2797,6 +2797,7 @@ sds catClientInfoString(sds s, client *client) {
|
||||
client->db->id,
|
||||
(int) dictSize(client->pubsub_channels),
|
||||
(int) listLength(client->pubsub_patterns),
|
||||
(int) dictSize(client->pubsubshard_channels),
|
||||
(client->flags & CLIENT_MULTI) ? client->mstate.count : -1,
|
||||
(unsigned long long) sdslen(client->querybuf),
|
||||
(unsigned long long) sdsavail(client->querybuf),
|
||||
@@ -2832,18 +2833,9 @@ sds getAllClientsInfoString(int type) {
|
||||
return o;
|
||||
}
|
||||
|
||||
/* This function implements CLIENT SETNAME, including replying to the
|
||||
* user with an error if the charset is wrong (in that case C_ERR is
|
||||
* returned). If the function succeeded C_OK is returned, and it's up
|
||||
* to the caller to send a reply if needed.
|
||||
*
|
||||
* Setting an empty string as name has the effect of unsetting the
|
||||
* currently set name: the client will remain unnamed.
|
||||
*
|
||||
* This function is also used to implement the HELLO SETNAME option. */
|
||||
int clientSetNameOrReply(client *c, robj *name) {
|
||||
int len = sdslen(name->ptr);
|
||||
char *p = name->ptr;
|
||||
/* Returns C_OK if the name has been set or C_ERR if the name is invalid. */
|
||||
int clientSetName(client *c, robj *name) {
|
||||
int len = (name != NULL) ? sdslen(name->ptr) : 0;
|
||||
|
||||
/* Setting the client name to an empty string actually removes
|
||||
* the current name. */
|
||||
@@ -2856,11 +2848,9 @@ int clientSetNameOrReply(client *c, robj *name) {
|
||||
/* Otherwise check if the charset is ok. We need to do this otherwise
|
||||
* CLIENT LIST format will break. You should always be able to
|
||||
* split by space to get the different fields. */
|
||||
char *p = name->ptr;
|
||||
for (int j = 0; j < len; j++) {
|
||||
if (p[j] < '!' || p[j] > '~') { /* ASCII is assumed. */
|
||||
addReplyError(c,
|
||||
"Client names cannot contain spaces, "
|
||||
"newlines or special characters.");
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
@@ -2870,6 +2860,25 @@ int clientSetNameOrReply(client *c, robj *name) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* This function implements CLIENT SETNAME, including replying to the
|
||||
* user with an error if the charset is wrong (in that case C_ERR is
|
||||
* returned). If the function succeeded C_OK is returned, and it's up
|
||||
* to the caller to send a reply if needed.
|
||||
*
|
||||
* Setting an empty string as name has the effect of unsetting the
|
||||
* currently set name: the client will remain unnamed.
|
||||
*
|
||||
* This function is also used to implement the HELLO SETNAME option. */
|
||||
int clientSetNameOrReply(client *c, robj *name) {
|
||||
int result = clientSetName(c, name);
|
||||
if (result == C_ERR) {
|
||||
addReplyError(c,
|
||||
"Client names cannot contain spaces, "
|
||||
"newlines or special characters.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Reset the client state to resemble a newly connected client.
|
||||
*/
|
||||
void resetCommand(client *c) {
|
||||
@@ -3660,9 +3669,7 @@ size_t getClientMemoryUsage(client *c, size_t *output_buffer_mem_usage) {
|
||||
|
||||
/* Add memory overhead of pubsub channels and patterns. Note: this is just the overhead of the robj pointers
|
||||
* to the strings themselves because they aren't stored per client. */
|
||||
mem += listLength(c->pubsub_patterns) * sizeof(listNode);
|
||||
mem += dictSize(c->pubsub_channels) * sizeof(dictEntry) +
|
||||
dictSlots(c->pubsub_channels) * sizeof(dictEntry*);
|
||||
mem += pubsubMemOverhead(c);
|
||||
|
||||
/* Add memory overhead of the tracking prefixes, this is an underestimation so we don't need to traverse the entire rax */
|
||||
if (c->client_tracking_prefixes)
|
||||
@@ -3997,10 +4004,15 @@ void processEventsWhileBlocked(void) {
|
||||
* ========================================================================== */
|
||||
|
||||
#define IO_THREADS_MAX_NUM 128
|
||||
#define CACHE_LINE_SIZE 64
|
||||
|
||||
typedef struct __attribute__((aligned(CACHE_LINE_SIZE))) threads_pending {
|
||||
redisAtomic unsigned long value;
|
||||
} threads_pending;
|
||||
|
||||
pthread_t io_threads[IO_THREADS_MAX_NUM];
|
||||
pthread_mutex_t io_threads_mutex[IO_THREADS_MAX_NUM];
|
||||
redisAtomic unsigned long io_threads_pending[IO_THREADS_MAX_NUM];
|
||||
threads_pending io_threads_pending[IO_THREADS_MAX_NUM];
|
||||
int io_threads_op; /* IO_THREADS_OP_IDLE, IO_THREADS_OP_READ or IO_THREADS_OP_WRITE. */ // TODO: should access to this be atomic??!
|
||||
|
||||
/* This is the list of clients each thread will serve when threaded I/O is
|
||||
@@ -4010,12 +4022,12 @@ list *io_threads_list[IO_THREADS_MAX_NUM];
|
||||
|
||||
static inline unsigned long getIOPendingCount(int i) {
|
||||
unsigned long count = 0;
|
||||
atomicGetWithSync(io_threads_pending[i], count);
|
||||
atomicGetWithSync(io_threads_pending[i].value, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
static inline void setIOPendingCount(int i, unsigned long count) {
|
||||
atomicSetWithSync(io_threads_pending[i], count);
|
||||
atomicSetWithSync(io_threads_pending[i].value, count);
|
||||
}
|
||||
|
||||
void *IOThreadMain(void *myid) {
|
||||
|
||||
@@ -722,3 +722,15 @@ void sunsubscribeCommand(client *c) {
|
||||
}
|
||||
if (clientTotalPubSubSubscriptionCount(c) == 0) c->flags &= ~CLIENT_PUBSUB;
|
||||
}
|
||||
|
||||
size_t pubsubMemOverhead(client *c) {
|
||||
/* PubSub patterns */
|
||||
size_t mem = listLength(c->pubsub_patterns) * sizeof(listNode);
|
||||
/* Global PubSub channels */
|
||||
mem += dictSize(c->pubsub_channels) * sizeof(dictEntry) +
|
||||
dictSlots(c->pubsub_channels) * sizeof(dictEntry*);
|
||||
/* Sharded PubSub channels */
|
||||
mem += dictSize(c->pubsubshard_channels) * sizeof(dictEntry) +
|
||||
dictSlots(c->pubsubshard_channels) * sizeof(dictEntry*);
|
||||
return mem;
|
||||
}
|
||||
|
||||
@@ -1160,7 +1160,6 @@ ssize_t rdbSaveAuxFieldStrInt(rio *rdb, char *key, long long val) {
|
||||
|
||||
/* Save a few default AUX fields with information about the RDB generated. */
|
||||
int rdbSaveInfoAuxFields(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
UNUSED(rdbflags);
|
||||
int redis_bits = (sizeof(void*) == 8) ? 64 : 32;
|
||||
int aof_base = (rdbflags & RDBFLAGS_AOF_PREAMBLE) != 0;
|
||||
|
||||
@@ -1397,6 +1396,7 @@ int rdbSave(int req, char *filename, rdbSaveInfo *rsi) {
|
||||
FILE *fp = NULL;
|
||||
rio rdb;
|
||||
int error = 0;
|
||||
char *err_op; /* For a detailed log */
|
||||
|
||||
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
|
||||
fp = fopen(tmpfile,"w");
|
||||
@@ -1420,13 +1420,14 @@ int rdbSave(int req, char *filename, rdbSaveInfo *rsi) {
|
||||
|
||||
if (rdbSaveRio(req,&rdb,&error,RDBFLAGS_NONE,rsi) == C_ERR) {
|
||||
errno = error;
|
||||
err_op = "rdbSaveRio";
|
||||
goto werr;
|
||||
}
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
if (fflush(fp)) goto werr;
|
||||
if (fsync(fileno(fp))) goto werr;
|
||||
if (fclose(fp)) { fp = NULL; goto werr; }
|
||||
if (fflush(fp)) { err_op = "fflush"; goto werr; }
|
||||
if (fsync(fileno(fp))) { err_op = "fsync"; goto werr; }
|
||||
if (fclose(fp)) { fp = NULL; err_op = "fclose"; goto werr; }
|
||||
fp = NULL;
|
||||
|
||||
/* Use RENAME to make sure the DB file is changed atomically only
|
||||
@@ -1445,6 +1446,7 @@ int rdbSave(int req, char *filename, rdbSaveInfo *rsi) {
|
||||
stopSaving(0);
|
||||
return C_ERR;
|
||||
}
|
||||
if (fsyncFileDir(filename) == -1) { err_op = "fsyncFileDir"; goto werr; }
|
||||
|
||||
serverLog(LL_NOTICE,"DB saved on disk");
|
||||
server.dirty = 0;
|
||||
@@ -1454,7 +1456,7 @@ int rdbSave(int req, char *filename, rdbSaveInfo *rsi) {
|
||||
return C_OK;
|
||||
|
||||
werr:
|
||||
serverLog(LL_WARNING,"Write error saving DB on disk: %s", strerror(errno));
|
||||
serverLog(LL_WARNING,"Write error saving DB on disk(%s): %s", err_op, strerror(errno));
|
||||
if (fp) fclose(fp);
|
||||
unlink(tmpfile);
|
||||
stopSaving(0);
|
||||
@@ -3244,7 +3246,7 @@ eoferr:
|
||||
* to do the actual loading. Moreover the ETA displayed in the INFO
|
||||
* output is initialized and finalized.
|
||||
*
|
||||
* If you pass an 'rsi' structure initialized with RDB_SAVE_OPTION_INIT, the
|
||||
* If you pass an 'rsi' structure initialized with RDB_SAVE_INFO_INIT, the
|
||||
* loading code will fill the information fields in the structure. */
|
||||
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
|
||||
FILE *fp;
|
||||
|
||||
@@ -846,6 +846,10 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
|
||||
}
|
||||
if (config.idlemode == 0)
|
||||
aeCreateFileEvent(el,c->context->fd,AE_WRITABLE,writeHandler,c);
|
||||
else
|
||||
/* In idle mode, clients still need to register readHandler for catching errors */
|
||||
aeCreateFileEvent(el,c->context->fd,AE_READABLE,readHandler,c);
|
||||
|
||||
listAddNodeTail(config.clients,c);
|
||||
atomicIncr(config.liveclients, 1);
|
||||
atomicGet(config.slots_last_update, c->slots_last_update);
|
||||
|
||||
@@ -283,7 +283,10 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
robj *auxkey, *auxval;
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_AUX;
|
||||
if ((auxkey = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
|
||||
if ((auxval = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
|
||||
if ((auxval = rdbLoadStringObject(&rdb)) == NULL) {
|
||||
decrRefCount(auxkey);
|
||||
goto eoferr;
|
||||
}
|
||||
|
||||
rdbCheckInfo("AUX FIELD %s = '%s'",
|
||||
(char*)auxkey->ptr, (char*)auxval->ptr);
|
||||
@@ -297,6 +300,10 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
if ((moduleid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
if ((when_opcode = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
if ((when = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
if (when_opcode != RDB_MODULE_OPCODE_UINT) {
|
||||
rdbCheckError("bad when_opcode");
|
||||
goto err;
|
||||
}
|
||||
|
||||
char name[10];
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
|
||||
+66
-47
@@ -81,6 +81,7 @@
|
||||
#define REDIS_CLI_CLUSTER_YES_ENV "REDISCLI_CLUSTER_YES"
|
||||
|
||||
#define CLUSTER_MANAGER_SLOTS 16384
|
||||
#define CLUSTER_MANAGER_PORT_INCR 10000 /* same as CLUSTER_PORT_INCR */
|
||||
#define CLUSTER_MANAGER_MIGRATE_TIMEOUT 60000
|
||||
#define CLUSTER_MANAGER_MIGRATE_PIPELINE 10
|
||||
#define CLUSTER_MANAGER_REBALANCE_THRESHOLD 2
|
||||
@@ -2867,6 +2868,7 @@ typedef struct clusterManagerNode {
|
||||
sds name;
|
||||
char *ip;
|
||||
int port;
|
||||
int bus_port; /* cluster-port */
|
||||
uint64_t current_epoch;
|
||||
time_t ping_sent;
|
||||
time_t ping_recv;
|
||||
@@ -2937,7 +2939,7 @@ typedef int (*clusterManagerOnReplyError)(redisReply *reply,
|
||||
|
||||
/* Cluster Manager helper functions */
|
||||
|
||||
static clusterManagerNode *clusterManagerNewNode(char *ip, int port);
|
||||
static clusterManagerNode *clusterManagerNewNode(char *ip, int port, int bus_port);
|
||||
static clusterManagerNode *clusterManagerNodeByName(const char *name);
|
||||
static clusterManagerNode *clusterManagerNodeByAbbreviatedName(const char *n);
|
||||
static void clusterManagerNodeResetSlots(clusterManagerNode *node);
|
||||
@@ -2997,15 +2999,15 @@ typedef struct clusterManagerCommandDef {
|
||||
clusterManagerCommandDef clusterManagerCommands[] = {
|
||||
{"create", clusterManagerCommandCreate, -2, "host1:port1 ... hostN:portN",
|
||||
"replicas <arg>"},
|
||||
{"check", clusterManagerCommandCheck, -1, "host:port",
|
||||
{"check", clusterManagerCommandCheck, -1, "<host:port> or <host> <port> - separated by either colon or space",
|
||||
"search-multiple-owners"},
|
||||
{"info", clusterManagerCommandInfo, -1, "host:port", NULL},
|
||||
{"fix", clusterManagerCommandFix, -1, "host:port",
|
||||
{"info", clusterManagerCommandInfo, -1, "<host:port> or <host> <port> - separated by either colon or space", NULL},
|
||||
{"fix", clusterManagerCommandFix, -1, "<host:port> or <host> <port> - separated by either colon or space",
|
||||
"search-multiple-owners,fix-with-unreachable-masters"},
|
||||
{"reshard", clusterManagerCommandReshard, -1, "host:port",
|
||||
{"reshard", clusterManagerCommandReshard, -1, "<host:port> or <host> <port> - separated by either colon or space",
|
||||
"from <arg>,to <arg>,slots <arg>,yes,timeout <arg>,pipeline <arg>,"
|
||||
"replace"},
|
||||
{"rebalance", clusterManagerCommandRebalance, -1, "host:port",
|
||||
{"rebalance", clusterManagerCommandRebalance, -1, "<host:port> or <host> <port> - separated by either colon or space",
|
||||
"weight <node1=w1...nodeN=wN>,use-empty-masters,"
|
||||
"timeout <arg>,simulate,pipeline <arg>,threshold <arg>,replace"},
|
||||
{"add-node", clusterManagerCommandAddNode, 2,
|
||||
@@ -3094,6 +3096,7 @@ static clusterManagerCommandProc *validateClusterManagerCommand(void) {
|
||||
static int parseClusterNodeAddress(char *addr, char **ip_ptr, int *port_ptr,
|
||||
int *bus_port_ptr)
|
||||
{
|
||||
/* ip:port[@bus_port] */
|
||||
char *c = strrchr(addr, '@');
|
||||
if (c != NULL) {
|
||||
*c = '\0';
|
||||
@@ -3203,12 +3206,15 @@ static void freeClusterManager(void) {
|
||||
dictRelease(clusterManagerUncoveredSlots);
|
||||
}
|
||||
|
||||
static clusterManagerNode *clusterManagerNewNode(char *ip, int port) {
|
||||
static clusterManagerNode *clusterManagerNewNode(char *ip, int port, int bus_port) {
|
||||
clusterManagerNode *node = zmalloc(sizeof(*node));
|
||||
node->context = NULL;
|
||||
node->name = NULL;
|
||||
node->ip = ip;
|
||||
node->port = port;
|
||||
/* We don't need to know the bus_port, at this point this value may be wrong.
|
||||
* If it is used, it will be corrected in clusterManagerLoadInfoFromNode. */
|
||||
node->bus_port = bus_port ? bus_port : port + CLUSTER_MANAGER_PORT_INCR;
|
||||
node->current_epoch = 0;
|
||||
node->ping_sent = 0;
|
||||
node->ping_recv = 0;
|
||||
@@ -4611,9 +4617,20 @@ static int clusterManagerNodeLoadInfo(clusterManagerNode *node, int opts,
|
||||
success = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char *ip = NULL;
|
||||
int port = 0, bus_port = 0;
|
||||
if (addr == NULL || !parseClusterNodeAddress(addr, &ip, &port, &bus_port)) {
|
||||
fprintf(stderr, "Error: invalid CLUSTER NODES reply\n");
|
||||
success = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
int myself = (strstr(flags, "myself") != NULL);
|
||||
clusterManagerNode *currentNode = NULL;
|
||||
if (myself) {
|
||||
/* bus-port could be wrong, correct it here, see clusterManagerNewNode. */
|
||||
node->bus_port = bus_port;
|
||||
node->flags |= CLUSTER_MANAGER_FLAG_MYSELF;
|
||||
currentNode = node;
|
||||
clusterManagerNodeResetSlots(node);
|
||||
@@ -4681,22 +4698,7 @@ static int clusterManagerNodeLoadInfo(clusterManagerNode *node, int opts,
|
||||
if (!(node->flags & CLUSTER_MANAGER_FLAG_MYSELF)) continue;
|
||||
else break;
|
||||
} else {
|
||||
if (addr == NULL) {
|
||||
fprintf(stderr, "Error: invalid CLUSTER NODES reply\n");
|
||||
success = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
char *c = strrchr(addr, '@');
|
||||
if (c != NULL) *c = '\0';
|
||||
c = strrchr(addr, ':');
|
||||
if (c == NULL) {
|
||||
fprintf(stderr, "Error: invalid CLUSTER NODES reply\n");
|
||||
success = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
*c = '\0';
|
||||
int port = atoi(++c);
|
||||
currentNode = clusterManagerNewNode(sdsnew(addr), port);
|
||||
currentNode = clusterManagerNewNode(sdsnew(ip), port, bus_port);
|
||||
currentNode->flags |= CLUSTER_MANAGER_FLAG_FRIEND;
|
||||
if (node->friends == NULL) node->friends = listCreate();
|
||||
listAddNodeTail(node->friends, currentNode);
|
||||
@@ -6110,17 +6112,14 @@ static int clusterManagerCommandCreate(int argc, char **argv) {
|
||||
cluster_manager.nodes = listCreate();
|
||||
for (i = 0; i < argc; i++) {
|
||||
char *addr = argv[i];
|
||||
char *c = strrchr(addr, '@');
|
||||
if (c != NULL) *c = '\0';
|
||||
c = strrchr(addr, ':');
|
||||
if (c == NULL) {
|
||||
char *ip = NULL;
|
||||
int port = 0;
|
||||
if (!parseClusterNodeAddress(addr, &ip, &port, NULL)) {
|
||||
fprintf(stderr, "Invalid address format: %s\n", addr);
|
||||
return 0;
|
||||
}
|
||||
*c = '\0';
|
||||
char *ip = addr;
|
||||
int port = atoi(++c);
|
||||
clusterManagerNode *node = clusterManagerNewNode(ip, port);
|
||||
|
||||
clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
|
||||
if (!clusterManagerNodeConnect(node)) {
|
||||
freeClusterManagerNode(node);
|
||||
return 0;
|
||||
@@ -6327,8 +6326,16 @@ assign_replicas:
|
||||
continue;
|
||||
}
|
||||
redisReply *reply = NULL;
|
||||
reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d",
|
||||
first_ip, first->port);
|
||||
if (first->bus_port == 0 || (first->bus_port == first->port + CLUSTER_MANAGER_PORT_INCR)) {
|
||||
/* CLUSTER MEET bus-port parameter was added in 4.0.
|
||||
* So if (bus_port == 0) or (bus_port == port + CLUSTER_MANAGER_PORT_INCR),
|
||||
* we just call CLUSTER MEET with 2 arguments, using the old form. */
|
||||
reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d",
|
||||
first->ip, first->port);
|
||||
} else {
|
||||
reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d %d",
|
||||
first->ip, first->port, first->bus_port);
|
||||
}
|
||||
int is_err = 0;
|
||||
if (reply != NULL) {
|
||||
if ((is_err = reply->type == REDIS_REPLY_ERROR))
|
||||
@@ -6362,6 +6369,8 @@ assign_replicas:
|
||||
}
|
||||
success = 0;
|
||||
goto cleanup;
|
||||
} else if (err != NULL) {
|
||||
zfree(err);
|
||||
}
|
||||
}
|
||||
// Reset Nodes
|
||||
@@ -6405,7 +6414,7 @@ static int clusterManagerCommandAddNode(int argc, char **argv) {
|
||||
clusterManagerLogInfo(">>> Adding node %s:%d to cluster %s:%d\n", ip, port,
|
||||
ref_ip, ref_port);
|
||||
// Check the existing cluster
|
||||
clusterManagerNode *refnode = clusterManagerNewNode(ref_ip, ref_port);
|
||||
clusterManagerNode *refnode = clusterManagerNewNode(ref_ip, ref_port, 0);
|
||||
if (!clusterManagerLoadInfoFromNode(refnode)) return 0;
|
||||
if (!clusterManagerCheckCluster(0)) return 0;
|
||||
|
||||
@@ -6429,7 +6438,7 @@ static int clusterManagerCommandAddNode(int argc, char **argv) {
|
||||
}
|
||||
|
||||
// Add the new node
|
||||
clusterManagerNode *new_node = clusterManagerNewNode(ip, port);
|
||||
clusterManagerNode *new_node = clusterManagerNewNode(ip, port, 0);
|
||||
int added = 0;
|
||||
if (!clusterManagerNodeConnect(new_node)) {
|
||||
clusterManagerLogErr("[ERR] Sorry, can't connect to node %s:%d\n",
|
||||
@@ -6507,8 +6516,18 @@ static int clusterManagerCommandAddNode(int argc, char **argv) {
|
||||
success = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d",
|
||||
first_ip, first->port);
|
||||
|
||||
if (first->bus_port == 0 || (first->bus_port == first->port + CLUSTER_MANAGER_PORT_INCR)) {
|
||||
/* CLUSTER MEET bus-port parameter was added in 4.0.
|
||||
* So if (bus_port == 0) or (bus_port == port + CLUSTER_MANAGER_PORT_INCR),
|
||||
* we just call CLUSTER MEET with 2 arguments, using the old form. */
|
||||
reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d",
|
||||
first_ip, first->port);
|
||||
} else {
|
||||
reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d %d",
|
||||
first->ip, first->port, first->bus_port);
|
||||
}
|
||||
|
||||
if (!(success = clusterManagerCheckRedisReply(new_node, reply, NULL)))
|
||||
goto cleanup;
|
||||
|
||||
@@ -6545,7 +6564,7 @@ static int clusterManagerCommandDeleteNode(int argc, char **argv) {
|
||||
char *node_id = argv[1];
|
||||
clusterManagerLogInfo(">>> Removing node %s from cluster %s:%d\n",
|
||||
node_id, ip, port);
|
||||
clusterManagerNode *ref_node = clusterManagerNewNode(ip, port);
|
||||
clusterManagerNode *ref_node = clusterManagerNewNode(ip, port, 0);
|
||||
clusterManagerNode *node = NULL;
|
||||
|
||||
// Load cluster information
|
||||
@@ -6607,7 +6626,7 @@ static int clusterManagerCommandInfo(int argc, char **argv) {
|
||||
int port = 0;
|
||||
char *ip = NULL;
|
||||
if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
|
||||
clusterManagerNode *node = clusterManagerNewNode(ip, port);
|
||||
clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
|
||||
if (!clusterManagerLoadInfoFromNode(node)) return 0;
|
||||
clusterManagerShowClusterInfo();
|
||||
return 1;
|
||||
@@ -6620,7 +6639,7 @@ static int clusterManagerCommandCheck(int argc, char **argv) {
|
||||
int port = 0;
|
||||
char *ip = NULL;
|
||||
if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
|
||||
clusterManagerNode *node = clusterManagerNewNode(ip, port);
|
||||
clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
|
||||
if (!clusterManagerLoadInfoFromNode(node)) return 0;
|
||||
clusterManagerShowClusterInfo();
|
||||
return clusterManagerCheckCluster(0);
|
||||
@@ -6638,7 +6657,7 @@ static int clusterManagerCommandReshard(int argc, char **argv) {
|
||||
int port = 0;
|
||||
char *ip = NULL;
|
||||
if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
|
||||
clusterManagerNode *node = clusterManagerNewNode(ip, port);
|
||||
clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
|
||||
if (!clusterManagerLoadInfoFromNode(node)) return 0;
|
||||
clusterManagerCheckCluster(0);
|
||||
if (cluster_manager.errors && listLength(cluster_manager.errors) > 0) {
|
||||
@@ -6827,7 +6846,7 @@ static int clusterManagerCommandRebalance(int argc, char **argv) {
|
||||
clusterManagerNode **weightedNodes = NULL;
|
||||
list *involved = NULL;
|
||||
if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
|
||||
clusterManagerNode *node = clusterManagerNewNode(ip, port);
|
||||
clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
|
||||
if (!clusterManagerLoadInfoFromNode(node)) return 0;
|
||||
int result = 1, i;
|
||||
if (config.cluster_manager_command.weight != NULL) {
|
||||
@@ -7028,7 +7047,7 @@ static int clusterManagerCommandSetTimeout(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
// Load cluster information
|
||||
clusterManagerNode *node = clusterManagerNewNode(ip, port);
|
||||
clusterManagerNode *node = clusterManagerNewNode(ip, port, 0);
|
||||
if (!clusterManagerLoadInfoFromNode(node)) return 0;
|
||||
int ok_count = 0, err_count = 0;
|
||||
|
||||
@@ -7098,7 +7117,7 @@ static int clusterManagerCommandImport(int argc, char **argv) {
|
||||
clusterManagerLogInfo(">>> Importing data from %s:%d to cluster %s:%d\n",
|
||||
src_ip, src_port, ip, port);
|
||||
|
||||
clusterManagerNode *refnode = clusterManagerNewNode(ip, port);
|
||||
clusterManagerNode *refnode = clusterManagerNewNode(ip, port, 0);
|
||||
if (!clusterManagerLoadInfoFromNode(refnode)) return 0;
|
||||
if (!clusterManagerCheckCluster(0)) return 0;
|
||||
char *reply_err = NULL;
|
||||
@@ -7233,7 +7252,7 @@ static int clusterManagerCommandCall(int argc, char **argv) {
|
||||
int port = 0, i;
|
||||
char *ip = NULL;
|
||||
if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
|
||||
clusterManagerNode *refnode = clusterManagerNewNode(ip, port);
|
||||
clusterManagerNode *refnode = clusterManagerNewNode(ip, port, 0);
|
||||
if (!clusterManagerLoadInfoFromNode(refnode)) return 0;
|
||||
argc--;
|
||||
argv++;
|
||||
@@ -7278,7 +7297,7 @@ static int clusterManagerCommandBackup(int argc, char **argv) {
|
||||
int success = 1, port = 0;
|
||||
char *ip = NULL;
|
||||
if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
|
||||
clusterManagerNode *refnode = clusterManagerNewNode(ip, port);
|
||||
clusterManagerNode *refnode = clusterManagerNewNode(ip, port, 0);
|
||||
if (!clusterManagerLoadInfoFromNode(refnode)) return 0;
|
||||
int no_issues = clusterManagerCheckCluster(0);
|
||||
int cluster_errors_count = (no_issues ? 0 :
|
||||
@@ -7489,7 +7508,7 @@ struct distsamples {
|
||||
* of the collected samples targeting an xterm 256 terminal.
|
||||
*
|
||||
* Takes an array of distsamples structures, ordered from smaller to bigger
|
||||
* 'max' value. Last sample max must be 0, to mean that it olds all the
|
||||
* 'max' value. Last sample max must be 0, to mean that it holds all the
|
||||
* samples greater than the previous one, and is also the stop sentinel.
|
||||
*
|
||||
* "tot' is the total number of samples in the different buckets, so it
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@
|
||||
#define assert(_e) (likely((_e))?(void)0 : (_serverAssert(#_e,__FILE__,__LINE__),redis_unreachable()))
|
||||
#define panic(...) _serverPanic(__FILE__,__LINE__,__VA_ARGS__),redis_unreachable()
|
||||
|
||||
void _serverAssert(char *estr, char *file, int line);
|
||||
void _serverAssert(const char *estr, const char *file, int line);
|
||||
void _serverPanic(const char *file, int line, const char *msg, ...);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -656,6 +656,8 @@ typedef struct RedisModuleClientInfo {
|
||||
|
||||
#define RedisModuleClientInfo RedisModuleClientInfoV1
|
||||
|
||||
#define REDISMODULE_CLIENTINFO_INITIALIZER_V1 { .version = 1 }
|
||||
|
||||
#define REDISMODULE_REPLICATIONINFO_VERSION 1
|
||||
typedef struct RedisModuleReplicationInfo {
|
||||
uint64_t version; /* Not used since this structure is never passed
|
||||
@@ -915,6 +917,7 @@ REDISMODULE_API size_t (*RedisModule_CallReplyLength)(RedisModuleCallReply *repl
|
||||
REDISMODULE_API RedisModuleCallReply * (*RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromULongLong)(RedisModuleCtx *ctx, unsigned long long ull) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str) REDISMODULE_ATTR;
|
||||
@@ -948,6 +951,7 @@ REDISMODULE_API int (*RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithBigNumber)(RedisModuleCtx *ctx, const char *bignum, size_t len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StringToULongLong)(const RedisModuleString *str, unsigned long long *ull) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StringToDouble)(const RedisModuleString *str, double *d) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StringToLongDouble)(const RedisModuleString *str, long double *d) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StringToStreamID)(const RedisModuleString *str, RedisModuleStreamID *id) REDISMODULE_ATTR;
|
||||
@@ -1000,6 +1004,8 @@ REDISMODULE_API void (*RedisModule_ChannelAtPosWithFlags)(RedisModuleCtx *ctx, i
|
||||
REDISMODULE_API unsigned long long (*RedisModule_GetClientId)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_GetClientUserNameById)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetClientInfoById)(void *ci, uint64_t id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_GetClientNameById)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SetClientNameById)(uint64_t id, RedisModuleString *name) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_PublishMessageShard)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetContextFlags)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
@@ -1258,6 +1264,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(ListInsert);
|
||||
REDISMODULE_GET_API(ListDelete);
|
||||
REDISMODULE_GET_API(StringToLongLong);
|
||||
REDISMODULE_GET_API(StringToULongLong);
|
||||
REDISMODULE_GET_API(StringToDouble);
|
||||
REDISMODULE_GET_API(StringToLongDouble);
|
||||
REDISMODULE_GET_API(StringToStreamID);
|
||||
@@ -1280,6 +1287,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(CreateStringFromCallReply);
|
||||
REDISMODULE_GET_API(CreateString);
|
||||
REDISMODULE_GET_API(CreateStringFromLongLong);
|
||||
REDISMODULE_GET_API(CreateStringFromULongLong);
|
||||
REDISMODULE_GET_API(CreateStringFromDouble);
|
||||
REDISMODULE_GET_API(CreateStringFromLongDouble);
|
||||
REDISMODULE_GET_API(CreateStringFromString);
|
||||
@@ -1426,6 +1434,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(ServerInfoGetFieldUnsigned);
|
||||
REDISMODULE_GET_API(ServerInfoGetFieldDouble);
|
||||
REDISMODULE_GET_API(GetClientInfoById);
|
||||
REDISMODULE_GET_API(GetClientNameById);
|
||||
REDISMODULE_GET_API(SetClientNameById);
|
||||
REDISMODULE_GET_API(PublishMessage);
|
||||
REDISMODULE_GET_API(PublishMessageShard);
|
||||
REDISMODULE_GET_API(SubscribeToServerEvent);
|
||||
|
||||
+11
-1
@@ -2151,6 +2151,16 @@ void readSyncBulkPayload(connection *conn) {
|
||||
/* Close old rdb asynchronously. */
|
||||
if (old_rdb_fd != -1) bioCreateCloseJob(old_rdb_fd);
|
||||
|
||||
/* Sync the directory to ensure rename is persisted */
|
||||
if (fsyncFileDir(server.rdb_filename) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Failed trying to sync DB directory %s in "
|
||||
"MASTER <-> REPLICA synchronization: %s",
|
||||
server.rdb_filename, strerror(errno));
|
||||
cancelReplicationHandshake(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION) != C_OK) {
|
||||
serverLog(LL_WARNING,
|
||||
"Failed trying to load the MASTER synchronization "
|
||||
@@ -3310,7 +3320,7 @@ void replicationDiscardCachedMaster(void) {
|
||||
* passed as argument as the socket for the new master.
|
||||
*
|
||||
* This function is called when successfully setup a partial resynchronization
|
||||
* so the stream of data that we'll receive will start from were this
|
||||
* so the stream of data that we'll receive will start from where this
|
||||
* master left. */
|
||||
void replicationResurrectCachedMaster(connection *conn) {
|
||||
server.master = server.cached_master;
|
||||
|
||||
@@ -363,6 +363,11 @@ static int scriptVerifyWriteCommandAllow(scriptRunCtx *run_ctx, char **err) {
|
||||
if (!(run_ctx->c->cmd->flags & CMD_WRITE))
|
||||
return C_OK;
|
||||
|
||||
/* If the script already made a modification to the dataset, we can't
|
||||
* fail it on unpredictable error state. */
|
||||
if ((run_ctx->flags & SCRIPT_WRITE_DIRTY))
|
||||
return C_OK;
|
||||
|
||||
/* Write commands are forbidden against read-only slaves, or if a
|
||||
* command marked as non-deterministic was already called in the context
|
||||
* of this script. */
|
||||
|
||||
+75
-22
@@ -48,6 +48,7 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/uio.h>
|
||||
@@ -634,7 +635,7 @@ void resetChildState() {
|
||||
NULL);
|
||||
}
|
||||
|
||||
/* Return if child type is mutual exclusive with other fork children */
|
||||
/* Return if child type is mutually exclusive with other fork children */
|
||||
int isMutuallyExclusiveChildType(int type) {
|
||||
return type == CHILD_TYPE_RDB || type == CHILD_TYPE_AOF || type == CHILD_TYPE_MODULE;
|
||||
}
|
||||
@@ -2793,8 +2794,23 @@ int populateArgsStructure(struct redisCommandArg *args) {
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Recursively populate the command structure. */
|
||||
void populateCommandStructure(struct redisCommand *c) {
|
||||
/* Recursively populate the command structure.
|
||||
*
|
||||
* On success, the function return C_OK. Otherwise C_ERR is returned and we won't
|
||||
* add this command in the commands dict. */
|
||||
int populateCommandStructure(struct redisCommand *c) {
|
||||
/* If the command marks with CMD_SENTINEL, it exists in sentinel. */
|
||||
if (!(c->flags & CMD_SENTINEL) && server.sentinel_mode)
|
||||
return C_ERR;
|
||||
|
||||
/* If the command marks with CMD_ONLY_SENTINEL, it only exists in sentinel. */
|
||||
if (c->flags & CMD_ONLY_SENTINEL && !server.sentinel_mode)
|
||||
return C_ERR;
|
||||
|
||||
/* Translate the command string flags description into an actual
|
||||
* set of flags. */
|
||||
setImplicitACLCategories(c);
|
||||
|
||||
/* Redis commands don't need more args than STATIC_KEY_SPECS_NUM (Number of keys
|
||||
* specs can be greater than STATIC_KEY_SPECS_NUM only for module commands) */
|
||||
c->key_specs = c->key_specs_static;
|
||||
@@ -2828,14 +2844,15 @@ void populateCommandStructure(struct redisCommand *c) {
|
||||
for (int j = 0; c->subcommands[j].declared_name; j++) {
|
||||
struct redisCommand *sub = c->subcommands+j;
|
||||
|
||||
/* Translate the command string flags description into an actual
|
||||
* set of flags. */
|
||||
setImplicitACLCategories(sub);
|
||||
sub->fullname = catSubCommandFullname(c->declared_name, sub->declared_name);
|
||||
populateCommandStructure(sub);
|
||||
if (populateCommandStructure(sub) == C_ERR)
|
||||
continue;
|
||||
|
||||
commandAddSubcommand(c, sub, sub->declared_name);
|
||||
}
|
||||
}
|
||||
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
extern struct redisCommand redisCommandTable[];
|
||||
@@ -2853,16 +2870,9 @@ void populateCommandTable(void) {
|
||||
|
||||
int retval1, retval2;
|
||||
|
||||
setImplicitACLCategories(c);
|
||||
|
||||
if (!(c->flags & CMD_SENTINEL) && server.sentinel_mode)
|
||||
continue;
|
||||
|
||||
if (c->flags & CMD_ONLY_SENTINEL && !server.sentinel_mode)
|
||||
continue;
|
||||
|
||||
c->fullname = sdsnew(c->declared_name);
|
||||
populateCommandStructure(c);
|
||||
if (populateCommandStructure(c) == C_ERR)
|
||||
continue;
|
||||
|
||||
retval1 = dictAdd(server.commands, sdsdup(c->fullname), c);
|
||||
/* Populate an additional dictionary that will be unaffected
|
||||
@@ -3584,8 +3594,8 @@ int commandCheckArity(client *c, sds *err) {
|
||||
* if C_ERR is returned the client was destroyed (i.e. after QUIT). */
|
||||
int processCommand(client *c) {
|
||||
if (!scriptIsTimedout()) {
|
||||
/* Both EXEC and EVAL call call() directly so there should be
|
||||
* no way in_exec or in_eval is 1.
|
||||
/* Both EXEC and scripts call call() directly so there should be
|
||||
* no way in_exec or scriptIsRunning() is 1.
|
||||
* That is unless lua_timedout, in which case client may run
|
||||
* some commands. */
|
||||
serverAssert(!server.in_exec);
|
||||
@@ -4207,6 +4217,12 @@ int finishShutdown(void) {
|
||||
|
||||
/* Close the listening sockets. Apparently this allows faster restarts. */
|
||||
closeListeningSockets(1);
|
||||
|
||||
/* Unlock the cluster config file before shutdown */
|
||||
if (server.cluster_enabled && server.cluster_config_file_lock_fd != -1) {
|
||||
flock(server.cluster_config_file_lock_fd, LOCK_UN|LOCK_NB);
|
||||
}
|
||||
|
||||
serverLog(LL_WARNING,"%s is now ready to exit, bye bye...",
|
||||
server.sentinel_mode ? "Sentinel" : "Redis");
|
||||
return C_OK;
|
||||
@@ -5645,6 +5661,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
|
||||
"keyspace_misses:%lld\r\n"
|
||||
"pubsub_channels:%ld\r\n"
|
||||
"pubsub_patterns:%lu\r\n"
|
||||
"pubsubshard_channels:%lu\r\n"
|
||||
"latest_fork_usec:%lld\r\n"
|
||||
"total_forks:%lld\r\n"
|
||||
"migrate_cached_sockets:%ld\r\n"
|
||||
@@ -5694,6 +5711,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
|
||||
server.stat_keyspace_misses,
|
||||
dictSize(server.pubsub_channels),
|
||||
dictSize(server.pubsub_patterns),
|
||||
dictSize(server.pubsubshard_channels),
|
||||
server.stat_fork_time,
|
||||
server.stat_total_forks,
|
||||
dictSize(server.migrate_cached_sockets),
|
||||
@@ -6094,9 +6112,12 @@ void usage(void) {
|
||||
fprintf(stderr," ./redis-server - (read config from stdin)\n");
|
||||
fprintf(stderr," ./redis-server -v or --version\n");
|
||||
fprintf(stderr," ./redis-server -h or --help\n");
|
||||
fprintf(stderr," ./redis-server --test-memory <megabytes>\n\n");
|
||||
fprintf(stderr," ./redis-server --test-memory <megabytes>\n");
|
||||
fprintf(stderr," ./redis-server --check-system\n");
|
||||
fprintf(stderr,"\n");
|
||||
fprintf(stderr,"Examples:\n");
|
||||
fprintf(stderr," ./redis-server (run the server with default conf)\n");
|
||||
fprintf(stderr," echo 'maxmemory 128mb' | ./redis-server -\n");
|
||||
fprintf(stderr," ./redis-server /etc/redis/6379.conf\n");
|
||||
fprintf(stderr," ./redis-server --port 7777\n");
|
||||
fprintf(stderr," ./redis-server --port 7777 --replicaof 127.0.0.1 8888\n");
|
||||
@@ -6347,7 +6368,7 @@ int redisFork(int purpose) {
|
||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
||||
|
||||
/* The child_pid and child_type are only for mutual exclusive children.
|
||||
/* The child_pid and child_type are only for mutually exclusive children.
|
||||
* other child types should handle and store their pid's in dedicated variables.
|
||||
*
|
||||
* Today, we allows CHILD_TYPE_LDB to run in parallel with the other fork types:
|
||||
@@ -6433,7 +6454,7 @@ void dismissClientMemory(client *c) {
|
||||
|
||||
/* In the child process, we don't need some buffers anymore, and these are
|
||||
* likely to change in the parent when there's heavy write traffic.
|
||||
* We dismis them right away, to avoid CoW.
|
||||
* We dismiss them right away, to avoid CoW.
|
||||
* see dismissMemeory(). */
|
||||
void dismissMemoryInChild(void) {
|
||||
/* madvise(MADV_DONTNEED) may not work if Transparent Huge Pages is enabled. */
|
||||
@@ -6880,6 +6901,8 @@ int main(int argc, char **argv) {
|
||||
server.exec_argv[1] = zstrdup(server.configfile);
|
||||
j = 2; // Skip this arg when parsing options
|
||||
}
|
||||
sds *argv_tmp;
|
||||
int argc_tmp;
|
||||
int handled_last_config_arg = 1;
|
||||
while(j < argc) {
|
||||
/* Either first or last argument - Should we read config from stdin? */
|
||||
@@ -6897,7 +6920,37 @@ int main(int argc, char **argv) {
|
||||
/* argv[j]+2 for removing the preceding `--` */
|
||||
options = sdscat(options,argv[j]+2);
|
||||
options = sdscat(options," ");
|
||||
handled_last_config_arg = 0;
|
||||
|
||||
argv_tmp = sdssplitargs(argv[j], &argc_tmp);
|
||||
if (argc_tmp == 1) {
|
||||
/* Means that we only have one option name, like --port or "--port " */
|
||||
handled_last_config_arg = 0;
|
||||
|
||||
if ((j != argc-1) && argv[j+1][0] == '-' && argv[j+1][1] == '-' &&
|
||||
!strcasecmp(argv[j], "--save"))
|
||||
{
|
||||
/* Special case: handle some things like `--save --config value`.
|
||||
* In this case, if next argument starts with `--`, we will reset
|
||||
* handled_last_config_arg flag and append an empty "" config value
|
||||
* to the options, so it will become `--save "" --config value`.
|
||||
* We are doing it to be compatible with pre 7.0 behavior (which we
|
||||
* break it in #10660, 7.0.1), since there might be users who generate
|
||||
* a command line from an array and when it's empty that's what they produce. */
|
||||
options = sdscat(options, "\"\"");
|
||||
handled_last_config_arg = 1;
|
||||
}
|
||||
else if ((j == argc-1) && !strcasecmp(argv[j], "--save")) {
|
||||
/* Special case: when empty save is the last argument.
|
||||
* In this case, we append an empty "" config value to the options,
|
||||
* so it will become `--save ""` and will follow the same reset thing. */
|
||||
options = sdscat(options, "\"\"");
|
||||
}
|
||||
} else {
|
||||
/* Means that we are passing both config name and it's value in the same arg,
|
||||
* like "--port 6380", so we need to reset handled_last_config_arg flag. */
|
||||
handled_last_config_arg = 1;
|
||||
}
|
||||
sdsfreesplitres(argv_tmp, argc_tmp);
|
||||
} else {
|
||||
/* Option argument */
|
||||
options = sdscatrepr(options,argv[j],strlen(argv[j]));
|
||||
|
||||
+9
-7
@@ -959,6 +959,7 @@ typedef struct multiState {
|
||||
is possible to know if all the commands have a
|
||||
certain flag. */
|
||||
size_t argv_len_sums; /* mem used by all commands arguments */
|
||||
int alloc_count; /* total number of multiCmd struct memory reserved. */
|
||||
} multiState;
|
||||
|
||||
/* This structure holds the blocking operation state for a client.
|
||||
@@ -1857,7 +1858,7 @@ struct redisServer {
|
||||
dict *pubsub_patterns; /* A dict of pubsub_patterns */
|
||||
int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
|
||||
xor of NOTIFY_... flags. */
|
||||
dict *pubsubshard_channels; /* Map channels to list of subscribed clients */
|
||||
dict *pubsubshard_channels; /* Map shard channels to list of subscribed clients */
|
||||
/* Cluster */
|
||||
int cluster_enabled; /* Is cluster enabled? */
|
||||
int cluster_port; /* Set the cluster port for a node. */
|
||||
@@ -2186,13 +2187,14 @@ typedef int redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, ge
|
||||
* or may just execute read commands. A command can not be marked
|
||||
* both CMD_WRITE and CMD_MAY_REPLICATE
|
||||
*
|
||||
* CMD_SENTINEL: This command is present in sentinel mode too.
|
||||
* CMD_SENTINEL: This command is present in sentinel mode.
|
||||
*
|
||||
* CMD_SENTINEL_ONLY: This command is present only when in sentinel mode.
|
||||
* CMD_ONLY_SENTINEL: This command is present only when in sentinel mode.
|
||||
* And should be removed from redis.
|
||||
*
|
||||
* CMD_NO_MANDATORY_KEYS: This key arguments for this command are optional.
|
||||
*
|
||||
* CMD_NO_MULTI: The command is nt allowed inside a transaction
|
||||
* CMD_NO_MULTI: The command is not allowed inside a transaction
|
||||
*
|
||||
* The following additional flags are only used in order to put commands
|
||||
* in a specific ACL category. Commands can have multiple ACL categories.
|
||||
@@ -2242,7 +2244,6 @@ struct redisCommand {
|
||||
struct redisCommandArg *args;
|
||||
|
||||
/* Runtime populated data */
|
||||
/* What keys should be loaded in background when calling this command? */
|
||||
long long microseconds, calls, rejected_calls, failed_calls;
|
||||
int id; /* Command ID. This is a progressive ID starting from 0 that
|
||||
is assigned at runtime, and is used in order to check
|
||||
@@ -2498,6 +2499,7 @@ char *getClientPeerId(client *client);
|
||||
char *getClientSockName(client *client);
|
||||
sds catClientInfoString(sds s, client *client);
|
||||
sds getAllClientsInfoString(int type);
|
||||
int clientSetName(client *c, robj *name);
|
||||
void rewriteClientCommandVector(client *c, int argc, ...);
|
||||
void rewriteClientCommandArgument(client *c, int i, robj *newval);
|
||||
void replaceClientCommandVector(client *c, int argc, robj **argv);
|
||||
@@ -2607,7 +2609,6 @@ void decrRefCount(robj *o);
|
||||
void decrRefCountVoid(void *o);
|
||||
void incrRefCount(robj *o);
|
||||
robj *makeObjectShared(robj *o);
|
||||
robj *resetRefCount(robj *obj);
|
||||
void freeStringObject(robj *o);
|
||||
void freeListObject(robj *o);
|
||||
void freeSetObject(robj *o);
|
||||
@@ -2752,7 +2753,7 @@ void sendChildInfo(childInfoType info_type, size_t keys, char *pname);
|
||||
void receiveChildInfo(void);
|
||||
|
||||
/* Fork helpers */
|
||||
int redisFork(int type);
|
||||
int redisFork(int purpose);
|
||||
int hasActiveChildProcess();
|
||||
void resetChildState();
|
||||
int isMutuallyExclusiveChildType(int type);
|
||||
@@ -2998,6 +2999,7 @@ int pubsubPublishMessageAndPropagateToCluster(robj *channel, robj *message, int
|
||||
void addReplyPubsubMessage(client *c, robj *channel, robj *msg, robj *message_bulk);
|
||||
int serverPubsubSubscriptionCount();
|
||||
int serverPubsubShardSubscriptionCount();
|
||||
size_t pubsubMemOverhead(client *c);
|
||||
|
||||
/* Keyspace events notification */
|
||||
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid);
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@ int checkOvercommit(sds *error_msg) {
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
if (atoi(buf)) {
|
||||
if (strtol(buf, NULL, 10) == 0) {
|
||||
*error_msg = sdsnew(
|
||||
"overcommit_memory is set to 0! Background save may fail under low memory condition. "
|
||||
"To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the "
|
||||
|
||||
+4
-2
@@ -2952,8 +2952,10 @@ static void zrangeResultFinalizeClient(zrange_result_handler *handler,
|
||||
/* Result handler methods for storing the ZRANGESTORE to a zset. */
|
||||
static void zrangeResultBeginStore(zrange_result_handler *handler, long length)
|
||||
{
|
||||
UNUSED(length);
|
||||
handler->dstobj = createZsetListpackObject();
|
||||
if (length > (long)server.zset_max_listpack_entries)
|
||||
handler->dstobj = createZsetObject();
|
||||
else
|
||||
handler->dstobj = createZsetListpackObject();
|
||||
}
|
||||
|
||||
static void zrangeResultEmitCBufferForStore(zrange_result_handler *handler,
|
||||
|
||||
@@ -722,6 +722,8 @@ static void connTLSClose(connection *conn_) {
|
||||
tls_connection *conn = (tls_connection *) conn_;
|
||||
|
||||
if (conn->ssl) {
|
||||
if (conn->c.state == CONN_STATE_CONNECTED)
|
||||
SSL_shutdown(conn->ssl);
|
||||
SSL_free(conn->ssl);
|
||||
conn->ssl = NULL;
|
||||
}
|
||||
@@ -834,12 +836,12 @@ static int connTLSWritev(connection *conn_, const struct iovec *iov, int iovcnt)
|
||||
* which is not worth doing so much memory copying to reduce system calls,
|
||||
* therefore, invoke connTLSWrite() multiple times to avoid memory copies. */
|
||||
if (iov_bytes_len > NET_MAX_WRITES_PER_EVENT) {
|
||||
size_t tot_sent = 0;
|
||||
ssize_t tot_sent = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
size_t sent = connTLSWrite(conn_, iov[i].iov_base, iov[i].iov_len);
|
||||
ssize_t sent = connTLSWrite(conn_, iov[i].iov_base, iov[i].iov_len);
|
||||
if (sent <= 0) return tot_sent > 0 ? tot_sent : sent;
|
||||
tot_sent += sent;
|
||||
if (sent != iov[i].iov_len) break;
|
||||
if ((size_t) sent != iov[i].iov_len) break;
|
||||
}
|
||||
return tot_sent;
|
||||
}
|
||||
|
||||
+49
@@ -43,6 +43,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "sha256.h"
|
||||
@@ -923,6 +924,54 @@ sds makePath(char *path, char *filename) {
|
||||
return sdscatfmt(sdsempty(), "%s/%s", path, filename);
|
||||
}
|
||||
|
||||
/* Given the filename, sync the corresponding directory.
|
||||
*
|
||||
* Usually a portable and safe pattern to overwrite existing files would be like:
|
||||
* 1. create a new temp file (on the same file system!)
|
||||
* 2. write data to the temp file
|
||||
* 3. fsync() the temp file
|
||||
* 4. rename the temp file to the appropriate name
|
||||
* 5. fsync() the containing directory */
|
||||
int fsyncFileDir(const char *filename) {
|
||||
#ifdef _AIX
|
||||
/* AIX is unable to fsync a directory */
|
||||
return 0;
|
||||
#endif
|
||||
char temp_filename[PATH_MAX + 1];
|
||||
char *dname;
|
||||
int dir_fd;
|
||||
|
||||
if (strlen(filename) > PATH_MAX) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* In the glibc implementation dirname may modify their argument. */
|
||||
memcpy(temp_filename, filename, strlen(filename) + 1);
|
||||
dname = dirname(temp_filename);
|
||||
|
||||
dir_fd = open(dname, O_RDONLY);
|
||||
if (dir_fd == -1) {
|
||||
/* Some OSs don't allow us to open directories at all, just
|
||||
* ignore the error in that case */
|
||||
if (errno == EISDIR) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/* Some OSs don't allow us to fsync directories at all, so we can ignore
|
||||
* those errors. */
|
||||
if (redis_fsync(dir_fd) == -1 && !(errno == EBADF || errno == EINVAL)) {
|
||||
int save_errno = errno;
|
||||
close(dir_fd);
|
||||
errno = save_errno;
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(dir_fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ int dirExists(char *dname);
|
||||
int dirRemove(char *dname);
|
||||
int fileExist(char *filename);
|
||||
sds makePath(char *path, char *filename);
|
||||
int fsyncFileDir(const char *filename);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int utilTest(int argc, char **argv, int flags);
|
||||
|
||||
@@ -80,3 +80,16 @@ test "Script no-cluster flag" {
|
||||
|
||||
assert_match {*Can not run script on cluster, 'no-cluster' flag is set*} $e
|
||||
}
|
||||
|
||||
test "CLUSTER RESET SOFT test" {
|
||||
set last_epoch_node0 [get_info_field [R 0 cluster info] cluster_current_epoch]
|
||||
R 0 FLUSHALL
|
||||
R 0 CLUSTER RESET
|
||||
assert {[get_info_field [R 0 cluster info] cluster_current_epoch] eq $last_epoch_node0}
|
||||
|
||||
set last_epoch_node1 [get_info_field [R 1 cluster info] cluster_current_epoch]
|
||||
R 1 FLUSHALL
|
||||
R 1 CLUSTER RESET SOFT
|
||||
assert {[get_info_field [R 1 cluster info] cluster_current_epoch] eq $last_epoch_node1}
|
||||
}
|
||||
|
||||
|
||||
@@ -182,4 +182,21 @@ test "Test the replica reports a loading state while it's loading" {
|
||||
|
||||
# Final sanity, the replica agrees it is online.
|
||||
assert_equal "online" [dict get [get_node_info_from_shard $replica_cluster_id $replica_id "node"] health]
|
||||
}
|
||||
}
|
||||
|
||||
test "Regression test for a crash when calling SHARDS during handshake" {
|
||||
# Reset forget a node, so we can use it to establish handshaking connections
|
||||
set id [R 19 CLUSTER MYID]
|
||||
R 19 CLUSTER RESET HARD
|
||||
for {set i 0} {$i < 19} {incr i} {
|
||||
R $i CLUSTER FORGET $id
|
||||
}
|
||||
R 19 cluster meet 127.0.0.1 [get_instance_attrib redis 0 port]
|
||||
# This should line would previously crash, since all the outbound
|
||||
# connections were in handshake state.
|
||||
R 19 CLUSTER SHARDS
|
||||
}
|
||||
|
||||
test "Cluster is up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Tests for the response of slot migrations.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
source "../tests/includes/utils.tcl"
|
||||
|
||||
test "Create a 2 nodes cluster" {
|
||||
create_cluster 2 0
|
||||
config_set_all_nodes cluster-allow-replica-migration no
|
||||
}
|
||||
|
||||
test "Cluster is up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
set cluster [redis_cluster 127.0.0.1:[get_instance_attrib redis 0 port]]
|
||||
catch {unset nodefrom}
|
||||
catch {unset nodeto}
|
||||
|
||||
$cluster refresh_nodes_map
|
||||
|
||||
test "Set many keys in the cluster" {
|
||||
for {set i 0} {$i < 5000} {incr i} {
|
||||
$cluster set $i $i
|
||||
assert { [$cluster get $i] eq $i }
|
||||
}
|
||||
}
|
||||
|
||||
test "Test cluster responses during migration of slot x" {
|
||||
|
||||
set slot 10
|
||||
array set nodefrom [$cluster masternode_for_slot $slot]
|
||||
array set nodeto [$cluster masternode_notfor_slot $slot]
|
||||
|
||||
$nodeto(link) cluster setslot $slot importing $nodefrom(id)
|
||||
$nodefrom(link) cluster setslot $slot migrating $nodeto(id)
|
||||
|
||||
# Get a key from that slot
|
||||
set key [$nodefrom(link) cluster GETKEYSINSLOT $slot "1"]
|
||||
|
||||
# MOVED REPLY
|
||||
assert_error "*MOVED*" {$nodeto(link) set $key "newVal"}
|
||||
|
||||
# ASK REPLY
|
||||
assert_error "*ASK*" {$nodefrom(link) set "abc{$key}" "newVal"}
|
||||
|
||||
# UNSTABLE REPLY
|
||||
assert_error "*TRYAGAIN*" {$nodefrom(link) mset "a{$key}" "newVal" $key "newVal2"}
|
||||
}
|
||||
|
||||
config_set_all_nodes cluster-allow-replica-migration yes
|
||||
@@ -116,6 +116,15 @@ start_server {tags {"benchmark network external:skip"}} {
|
||||
# ensure the keyspace has the desired size
|
||||
assert_match {50} [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d]
|
||||
}
|
||||
|
||||
test {benchmark: clients idle mode should return error when reached maxclients limit} {
|
||||
set cmd [redisbenchmark $master_host $master_port "-c 10 -I"]
|
||||
set original_maxclients [lindex [r config get maxclients] 1]
|
||||
r config set maxclients 5
|
||||
catch { exec {*}$cmd } error
|
||||
assert_match "*Error*" $error
|
||||
r config set maxclients $original_maxclients
|
||||
}
|
||||
|
||||
# tls specific tests
|
||||
if {$::tls} {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include <strings.h>
|
||||
|
||||
#define UNUSED(V) ((void) V)
|
||||
|
||||
@@ -119,8 +120,20 @@ void *bg_call_worker(void *arg) {
|
||||
}
|
||||
|
||||
// Call the command
|
||||
const char* cmd = RedisModule_StringPtrLen(bg->argv[1], NULL);
|
||||
RedisModuleCallReply* rep = RedisModule_Call(ctx, cmd, "v", bg->argv + 2, bg->argc - 2);
|
||||
const char *module_cmd = RedisModule_StringPtrLen(bg->argv[0], NULL);
|
||||
int cmd_pos = 1;
|
||||
RedisModuleString *format_redis_str = RedisModule_CreateString(NULL, "v", 1);
|
||||
if (!strcasecmp(module_cmd, "do_bg_rm_call_format")) {
|
||||
cmd_pos = 2;
|
||||
size_t format_len;
|
||||
const char *format = RedisModule_StringPtrLen(bg->argv[1], &format_len);
|
||||
RedisModule_StringAppendBuffer(NULL, format_redis_str, format, format_len);
|
||||
RedisModule_StringAppendBuffer(NULL, format_redis_str, "E", 1);
|
||||
}
|
||||
const char *format = RedisModule_StringPtrLen(format_redis_str, NULL);
|
||||
const char *cmd = RedisModule_StringPtrLen(bg->argv[cmd_pos], NULL);
|
||||
RedisModuleCallReply *rep = RedisModule_Call(ctx, cmd, format, bg->argv + cmd_pos + 1, bg->argc - cmd_pos - 1);
|
||||
RedisModule_FreeString(NULL, format_redis_str);
|
||||
|
||||
// Release GIL
|
||||
RedisModule_ThreadSafeContextUnlock(ctx);
|
||||
@@ -306,6 +319,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
if (RedisModule_CreateCommand(ctx, "do_bg_rm_call", do_bg_rm_call, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "do_bg_rm_call_format", do_bg_rm_call, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "do_fake_bg_true", do_fake_bg_true, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
|
||||
+100
-2
@@ -4,6 +4,7 @@
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
||||
@@ -239,9 +240,17 @@ int test_clientinfo(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
(void) argv;
|
||||
(void) argc;
|
||||
|
||||
RedisModuleClientInfo ci = { .version = REDISMODULE_CLIENTINFO_VERSION };
|
||||
RedisModuleClientInfoV1 ci = REDISMODULE_CLIENTINFO_INITIALIZER_V1;
|
||||
uint64_t client_id = RedisModule_GetClientId(ctx);
|
||||
|
||||
if (RedisModule_GetClientInfoById(&ci, RedisModule_GetClientId(ctx)) == REDISMODULE_ERR) {
|
||||
/* Check expected result from the V1 initializer. */
|
||||
assert(ci.version == 1);
|
||||
/* Trying to populate a future version of the struct should fail. */
|
||||
ci.version = REDISMODULE_CLIENTINFO_VERSION + 1;
|
||||
assert(RedisModule_GetClientInfoById(&ci, client_id) == REDISMODULE_ERR);
|
||||
|
||||
ci.version = 1;
|
||||
if (RedisModule_GetClientInfoById(&ci, client_id) == REDISMODULE_ERR) {
|
||||
RedisModule_ReplyWithError(ctx, "failed to get client info");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -270,6 +279,27 @@ int test_clientinfo(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int test_getname(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
(void)argv;
|
||||
if (argc != 1) return RedisModule_WrongArity(ctx);
|
||||
unsigned long long id = RedisModule_GetClientId(ctx);
|
||||
RedisModuleString *name = RedisModule_GetClientNameById(ctx, id);
|
||||
if (name == NULL)
|
||||
return RedisModule_ReplyWithError(ctx, "-ERR No name");
|
||||
RedisModule_ReplyWithString(ctx, name);
|
||||
RedisModule_FreeString(ctx, name);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int test_setname(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
unsigned long long id = RedisModule_GetClientId(ctx);
|
||||
if (RedisModule_SetClientNameById(id, argv[1]) == REDISMODULE_OK)
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
else
|
||||
return RedisModule_ReplyWithError(ctx, strerror(errno));
|
||||
}
|
||||
|
||||
int test_log_tsctx(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
RedisModuleCtx *tsctx = RedisModule_GetDetachedThreadSafeContext(ctx);
|
||||
@@ -354,6 +384,68 @@ int test_rm_call_flags(RedisModuleCtx *ctx, RedisModuleString **argv, int argc){
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int test_ull_conv(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
UNUSED(argv);
|
||||
UNUSED(argc);
|
||||
unsigned long long ull = 18446744073709551615ULL;
|
||||
const char *ullstr = "18446744073709551615";
|
||||
|
||||
RedisModuleString *s1 = RedisModule_CreateStringFromULongLong(ctx, ull);
|
||||
RedisModuleString *s2 =
|
||||
RedisModule_CreateString(ctx, ullstr, strlen(ullstr));
|
||||
if (RedisModule_StringCompare(s1, s2) != 0) {
|
||||
char err[4096];
|
||||
snprintf(err, 4096,
|
||||
"Failed to convert unsigned long long to string ('%s' != '%s')",
|
||||
RedisModule_StringPtrLen(s1, NULL),
|
||||
RedisModule_StringPtrLen(s2, NULL));
|
||||
RedisModule_ReplyWithError(ctx, err);
|
||||
goto final;
|
||||
}
|
||||
unsigned long long ull2 = 0;
|
||||
if (RedisModule_StringToULongLong(s2, &ull2) == REDISMODULE_ERR) {
|
||||
RedisModule_ReplyWithError(ctx,
|
||||
"Failed to convert string to unsigned long long");
|
||||
goto final;
|
||||
}
|
||||
if (ull2 != ull) {
|
||||
char err[4096];
|
||||
snprintf(err, 4096,
|
||||
"Failed to convert string to unsigned long long (%llu != %llu)",
|
||||
ull2,
|
||||
ull);
|
||||
RedisModule_ReplyWithError(ctx, err);
|
||||
goto final;
|
||||
}
|
||||
|
||||
/* Make sure we can't convert a string more than ULLONG_MAX or less than 0 */
|
||||
ullstr = "18446744073709551616";
|
||||
RedisModuleString *s3 = RedisModule_CreateString(ctx, ullstr, strlen(ullstr));
|
||||
unsigned long long ull3;
|
||||
if (RedisModule_StringToULongLong(s3, &ull3) == REDISMODULE_OK) {
|
||||
RedisModule_ReplyWithError(ctx, "Invalid string successfully converted to unsigned long long");
|
||||
RedisModule_FreeString(ctx, s3);
|
||||
goto final;
|
||||
}
|
||||
RedisModule_FreeString(ctx, s3);
|
||||
ullstr = "-1";
|
||||
RedisModuleString *s4 = RedisModule_CreateString(ctx, ullstr, strlen(ullstr));
|
||||
unsigned long long ull4;
|
||||
if (RedisModule_StringToULongLong(s4, &ull4) == REDISMODULE_OK) {
|
||||
RedisModule_ReplyWithError(ctx, "Invalid string successfully converted to unsigned long long");
|
||||
RedisModule_FreeString(ctx, s4);
|
||||
goto final;
|
||||
}
|
||||
RedisModule_FreeString(ctx, s4);
|
||||
|
||||
RedisModule_ReplyWithSimpleString(ctx, "ok");
|
||||
|
||||
final:
|
||||
RedisModule_FreeString(ctx, s1);
|
||||
RedisModule_FreeString(ctx, s2);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
@@ -366,6 +458,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.ld_conversion", test_ld_conv, "",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.ull_conversion", test_ull_conv, "",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.flushall", test_flushall,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.dbsize", test_dbsize,"",0,0,0) == REDISMODULE_ERR)
|
||||
@@ -384,6 +478,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.clientinfo", test_clientinfo,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.getname", test_getname,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.setname", test_setname,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.redisversion", test_redisversion,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.getclientcert", test_getclientcert,"",0,0,0) == REDISMODULE_ERR)
|
||||
|
||||
@@ -12,6 +12,13 @@ if {$::simulate_error} {
|
||||
}
|
||||
}
|
||||
|
||||
test "Sentinel commands sanity check" {
|
||||
foreach_sentinel_id id {
|
||||
assert_equal {72} [llength [S $id command list]]
|
||||
assert_equal {15} [S $id command count]
|
||||
}
|
||||
}
|
||||
|
||||
test "Basic failover works if the master is down" {
|
||||
set old_port [RPort $master_id]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
|
||||
@@ -126,7 +126,7 @@ test "Sentinels (re)connection following master ACL change" {
|
||||
wait_for_condition 100 50 {
|
||||
[string match "*disconnected*" [dict get [S $sent2re SENTINEL MASTER mymaster] flags]] != 0
|
||||
} else {
|
||||
fail "Expected: Sentinel to be disconnected from master due to wrong password"
|
||||
fail "Expected: Restarted sentinel to be disconnected from master due to obsolete password"
|
||||
}
|
||||
|
||||
# Verify sentinel with updated password managed to connect (wait for sentinelTimer to reconnect)
|
||||
@@ -137,8 +137,10 @@ test "Sentinels (re)connection following master ACL change" {
|
||||
}
|
||||
|
||||
# Verify sentinel untouched gets failed to connect master
|
||||
if {![string match "*disconnected*" [dict get [S $sent2un SENTINEL MASTER mymaster] flags]]} {
|
||||
fail "Expected: Sentinel to be disconnected from master due to wrong password"
|
||||
wait_for_condition 100 50 {
|
||||
[string match "*disconnected*" [dict get [S $sent2un SENTINEL MASTER mymaster] flags]] != 0
|
||||
} else {
|
||||
fail "Expected: Sentinel to be disconnected from master due to obsolete password"
|
||||
}
|
||||
|
||||
# Now update all sentinels with new password
|
||||
@@ -167,14 +169,14 @@ test "Set parameters in normal case" {
|
||||
set origin_down_after_milliseconds [dict get $info down-after-milliseconds]
|
||||
set update_quorum [expr $origin_quorum+1]
|
||||
set update_down_after_milliseconds [expr $origin_down_after_milliseconds+1000]
|
||||
|
||||
|
||||
assert_equal [S 0 SENTINEL SET mymaster quorum $update_quorum] "OK"
|
||||
assert_equal [S 0 SENTINEL SET mymaster down-after-milliseconds $update_down_after_milliseconds] "OK"
|
||||
|
||||
set update_info [S 0 SENTINEL master mymaster]
|
||||
assert {[dict get $update_info quorum] != $origin_quorum}
|
||||
assert {[dict get $update_info down-after-milliseconds] != $origin_down_after_milliseconds}
|
||||
|
||||
|
||||
#restore to origin config parameters
|
||||
assert_equal [S 0 SENTINEL SET mymaster quorum $origin_quorum] "OK"
|
||||
assert_equal [S 0 SENTINEL SET mymaster down-after-milliseconds $origin_down_after_milliseconds] "OK"
|
||||
|
||||
+34
-7
@@ -67,6 +67,33 @@ proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0} {tlsoptions {}} {re
|
||||
interp alias {} ::redis::redisHandle$id {} ::redis::__dispatch__ $id
|
||||
}
|
||||
|
||||
# On recent versions of tcl-tls/OpenSSL, reading from a dropped connection
|
||||
# results with an error we need to catch and mimic the old behavior.
|
||||
proc ::redis::redis_safe_read {fd len} {
|
||||
if {$len == -1} {
|
||||
set err [catch {set val [read $fd]} msg]
|
||||
} else {
|
||||
set err [catch {set val [read $fd $len]} msg]
|
||||
}
|
||||
if {!$err} {
|
||||
return $val
|
||||
}
|
||||
if {[string match "*connection abort*" $msg]} {
|
||||
return {}
|
||||
}
|
||||
error $msg
|
||||
}
|
||||
|
||||
proc ::redis::redis_safe_gets {fd} {
|
||||
if {[catch {set val [gets $fd]} msg]} {
|
||||
if {[string match "*connection abort*" $msg]} {
|
||||
return {}
|
||||
}
|
||||
error $msg
|
||||
}
|
||||
return $val
|
||||
}
|
||||
|
||||
# This is a wrapper to the actual dispatching procedure that handles
|
||||
# reconnection if needed.
|
||||
proc ::redis::__dispatch__ {id method args} {
|
||||
@@ -148,8 +175,8 @@ proc ::redis::__method__read {id fd} {
|
||||
::redis::redis_read_reply $id $fd
|
||||
}
|
||||
|
||||
proc ::redis::__method__rawread {id fd len} {
|
||||
return [read $fd $len]
|
||||
proc ::redis::__method__rawread {id fd {len -1}} {
|
||||
return [redis_safe_read $fd $len]
|
||||
}
|
||||
|
||||
proc ::redis::__method__write {id fd buf} {
|
||||
@@ -207,8 +234,8 @@ proc ::redis::redis_writenl {fd buf} {
|
||||
}
|
||||
|
||||
proc ::redis::redis_readnl {fd len} {
|
||||
set buf [read $fd $len]
|
||||
read $fd 2 ; # discard CR LF
|
||||
set buf [redis_safe_read $fd $len]
|
||||
redis_safe_read $fd 2 ; # discard CR LF
|
||||
return $buf
|
||||
}
|
||||
|
||||
@@ -254,11 +281,11 @@ proc ::redis::redis_read_map {id fd} {
|
||||
}
|
||||
|
||||
proc ::redis::redis_read_line fd {
|
||||
string trim [gets $fd]
|
||||
string trim [redis_safe_gets $fd]
|
||||
}
|
||||
|
||||
proc ::redis::redis_read_null fd {
|
||||
gets $fd
|
||||
redis_safe_gets $fd
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -281,7 +308,7 @@ proc ::redis::redis_read_reply {id fd} {
|
||||
}
|
||||
|
||||
while {1} {
|
||||
set type [read $fd 1]
|
||||
set type [redis_safe_read $fd 1]
|
||||
switch -exact -- $type {
|
||||
_ {return [redis_read_null $fd]}
|
||||
: -
|
||||
|
||||
@@ -72,8 +72,8 @@ proc sanitizer_errors_from_file {filename} {
|
||||
}
|
||||
|
||||
proc getInfoProperty {infostr property} {
|
||||
if {[regexp "\r\n$property:(.*?)\r\n" $infostr _ value]} {
|
||||
set _ $value
|
||||
if {[regexp -lineanchor "^$property:(.*?)\r\n" $infostr _ value]} {
|
||||
return $value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,13 +156,13 @@ start_server {} {
|
||||
test "client evicted due to pubsub subscriptions" {
|
||||
r flushdb
|
||||
|
||||
# Since pubsub subscriptions cause a small overheatd this test uses a minimal maxmemory-clients config
|
||||
# Since pubsub subscriptions cause a small overhead this test uses a minimal maxmemory-clients config
|
||||
set temp_maxmemory_clients 200000
|
||||
r config set maxmemory-clients $temp_maxmemory_clients
|
||||
|
||||
# Test eviction due to pubsub patterns
|
||||
set rr [redis_client]
|
||||
# Add patterns until list maxes out maxmemroy clients and causes client eviction
|
||||
# Add patterns until list maxes out maxmemory clients and causes client eviction
|
||||
catch {
|
||||
for {set j 0} {$j < $temp_maxmemory_clients} {incr j} {
|
||||
$rr psubscribe $j
|
||||
@@ -173,7 +173,7 @@ start_server {} {
|
||||
|
||||
# Test eviction due to pubsub channels
|
||||
set rr [redis_client]
|
||||
# Add patterns until list maxes out maxmemroy clients and causes client eviction
|
||||
# Subscribe to global channels until list maxes out maxmemory clients and causes client eviction
|
||||
catch {
|
||||
for {set j 0} {$j < $temp_maxmemory_clients} {incr j} {
|
||||
$rr subscribe $j
|
||||
@@ -181,6 +181,17 @@ start_server {} {
|
||||
} e
|
||||
assert_match {I/O error reading reply} $e
|
||||
$rr close
|
||||
|
||||
# Test eviction due to sharded pubsub channels
|
||||
set rr [redis_client]
|
||||
# Subscribe to sharded pubsub channels until list maxes out maxmemory clients and causes client eviction
|
||||
catch {
|
||||
for {set j 0} {$j < $temp_maxmemory_clients} {incr j} {
|
||||
$rr ssubscribe $j
|
||||
}
|
||||
} e
|
||||
assert_match {I/O error reading reply} $e
|
||||
$rr close
|
||||
|
||||
# Restore config for next tests
|
||||
r config set maxmemory-clients $maxmemory_clients
|
||||
|
||||
+86
-8
@@ -3,9 +3,7 @@
|
||||
source tests/support/cli.tcl
|
||||
|
||||
proc cluster_info {r field} {
|
||||
if {[regexp "^$field:(.*?)\r\n" [$r cluster info] _ value]} {
|
||||
set _ $value
|
||||
}
|
||||
set _ [getInfoProperty [$r cluster info] $field]
|
||||
}
|
||||
|
||||
# Provide easy access to CLUSTER INFO properties. Same semantic as "proc s".
|
||||
@@ -110,7 +108,7 @@ start_multiple_servers 3 [list overrides $base_conf] {
|
||||
}
|
||||
|
||||
$node3_rd close
|
||||
|
||||
|
||||
test "Run blocking command again on cluster node1" {
|
||||
$node1 del key9184688
|
||||
# key9184688 is mapped to slot 10923 which has been moved to node1
|
||||
@@ -123,9 +121,9 @@ start_multiple_servers 3 [list overrides $base_conf] {
|
||||
fail "Client not blocked"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test "Kill a cluster node and wait for fail state" {
|
||||
# kill node3 in cluster
|
||||
# kill node3 in cluster
|
||||
exec kill -SIGSTOP $node3_pid
|
||||
|
||||
wait_for_condition 1000 50 {
|
||||
@@ -135,7 +133,7 @@ start_multiple_servers 3 [list overrides $base_conf] {
|
||||
fail "Cluster doesn't fail"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test "Verify command got unblocked after cluster failure" {
|
||||
assert_error {*CLUSTERDOWN*} {$node1_rd read}
|
||||
|
||||
@@ -208,7 +206,7 @@ start_multiple_servers 5 [list overrides $base_conf] {
|
||||
127.0.0.1:[srv -4 port] \
|
||||
127.0.0.1:[srv 0 port]
|
||||
} e
|
||||
assert_match {*node already contains functions*} $e
|
||||
assert_match {*node already contains functions*} $e
|
||||
}
|
||||
} ;# stop servers
|
||||
|
||||
@@ -315,6 +313,86 @@ test {Migrate the last slot away from a node using redis-cli} {
|
||||
}
|
||||
}
|
||||
|
||||
# Test redis-cli --cluster create, add-node with cluster-port.
|
||||
# Create five nodes, three with custom cluster_port and two with default values.
|
||||
start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] {
|
||||
start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1]] {
|
||||
start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] {
|
||||
start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1]] {
|
||||
start_server [list overrides [list cluster-enabled yes cluster-node-timeout 1 cluster-port [find_available_port $::baseport $::portcount]]] {
|
||||
|
||||
# The first three are used to test --cluster create.
|
||||
# The last two are used to test --cluster add-node
|
||||
set node1_rd [redis_client 0]
|
||||
set node2_rd [redis_client -1]
|
||||
set node3_rd [redis_client -2]
|
||||
set node4_rd [redis_client -3]
|
||||
set node5_rd [redis_client -4]
|
||||
|
||||
test {redis-cli --cluster create with cluster-port} {
|
||||
exec src/redis-cli --cluster-yes --cluster create \
|
||||
127.0.0.1:[srv 0 port] \
|
||||
127.0.0.1:[srv -1 port] \
|
||||
127.0.0.1:[srv -2 port]
|
||||
|
||||
wait_for_condition 1000 50 {
|
||||
[csi 0 cluster_state] eq {ok} &&
|
||||
[csi -1 cluster_state] eq {ok} &&
|
||||
[csi -2 cluster_state] eq {ok}
|
||||
} else {
|
||||
fail "Cluster doesn't stabilize"
|
||||
}
|
||||
|
||||
# Make sure each node can meet other nodes
|
||||
assert_equal 3 [csi 0 cluster_known_nodes]
|
||||
assert_equal 3 [csi -1 cluster_known_nodes]
|
||||
assert_equal 3 [csi -2 cluster_known_nodes]
|
||||
}
|
||||
|
||||
test {redis-cli --cluster add-node with cluster-port} {
|
||||
# Adding node to the cluster (without cluster-port)
|
||||
exec src/redis-cli --cluster-yes --cluster add-node \
|
||||
127.0.0.1:[srv -3 port] \
|
||||
127.0.0.1:[srv 0 port]
|
||||
|
||||
wait_for_condition 1000 50 {
|
||||
[csi 0 cluster_state] eq {ok} &&
|
||||
[csi -1 cluster_state] eq {ok} &&
|
||||
[csi -2 cluster_state] eq {ok} &&
|
||||
[csi -3 cluster_state] eq {ok}
|
||||
} else {
|
||||
fail "Cluster doesn't stabilize"
|
||||
}
|
||||
|
||||
# Adding node to the cluster (with cluster-port)
|
||||
exec src/redis-cli --cluster-yes --cluster add-node \
|
||||
127.0.0.1:[srv -4 port] \
|
||||
127.0.0.1:[srv 0 port]
|
||||
|
||||
wait_for_condition 1000 50 {
|
||||
[csi 0 cluster_state] eq {ok} &&
|
||||
[csi -1 cluster_state] eq {ok} &&
|
||||
[csi -2 cluster_state] eq {ok} &&
|
||||
[csi -3 cluster_state] eq {ok} &&
|
||||
[csi -4 cluster_state] eq {ok}
|
||||
} else {
|
||||
fail "Cluster doesn't stabilize"
|
||||
}
|
||||
|
||||
# Make sure each node can meet other nodes
|
||||
assert_equal 5 [csi 0 cluster_known_nodes]
|
||||
assert_equal 5 [csi -1 cluster_known_nodes]
|
||||
assert_equal 5 [csi -2 cluster_known_nodes]
|
||||
assert_equal 5 [csi -3 cluster_known_nodes]
|
||||
assert_equal 5 [csi -4 cluster_known_nodes]
|
||||
}
|
||||
# stop 5 servers
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} ;# tags
|
||||
|
||||
set ::singledb $old_singledb
|
||||
|
||||
@@ -7,7 +7,7 @@ start_server {tags {"introspection"}} {
|
||||
|
||||
test {CLIENT LIST} {
|
||||
r client list
|
||||
} {id=* addr=*:* laddr=*:* fd=* name=* age=* idle=* flags=N db=* sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=* argv-mem=* multi-mem=0 rbs=* rbp=* obl=0 oll=0 omem=0 tot-mem=* events=r cmd=client|list user=* redir=-1 resp=2*}
|
||||
} {id=* addr=*:* laddr=*:* fd=* name=* age=* idle=* flags=N db=* sub=0 psub=0 ssub=0 multi=-1 qbuf=26 qbuf-free=* argv-mem=* multi-mem=0 rbs=* rbp=* obl=0 oll=0 omem=0 tot-mem=* events=r cmd=client|list user=* redir=-1 resp=2*}
|
||||
|
||||
test {CLIENT LIST with IDs} {
|
||||
set myid [r client id]
|
||||
@@ -17,7 +17,7 @@ start_server {tags {"introspection"}} {
|
||||
|
||||
test {CLIENT INFO} {
|
||||
r client info
|
||||
} {id=* addr=*:* laddr=*:* fd=* name=* age=* idle=* flags=N db=* sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=* argv-mem=* multi-mem=0 rbs=* rbp=* obl=0 oll=0 omem=0 tot-mem=* events=r cmd=client|info user=* redir=-1 resp=2*}
|
||||
} {id=* addr=*:* laddr=*:* fd=* name=* age=* idle=* flags=N db=* sub=0 psub=0 ssub=0 multi=-1 qbuf=26 qbuf-free=* argv-mem=* multi-mem=0 rbs=* rbp=* obl=0 oll=0 omem=0 tot-mem=* events=r cmd=client|info user=* redir=-1 resp=2*}
|
||||
|
||||
test {CLIENT KILL with illegal arguments} {
|
||||
assert_error "ERR wrong number of arguments for 'client|kill' command" {r client kill}
|
||||
@@ -503,6 +503,21 @@ start_server {tags {"introspection"}} {
|
||||
assert_match {*'replicaof "--127.0.0.1"'*wrong number of arguments*} $err
|
||||
} {} {external:skip}
|
||||
|
||||
test {redis-server command line arguments - allow passing option name and option value in the same arg} {
|
||||
start_server {config "default.conf" args {"--maxmemory 700mb" "--maxmemory-policy volatile-lru"}} {
|
||||
assert_match [r config get maxmemory] {maxmemory 734003200}
|
||||
assert_match [r config get maxmemory-policy] {maxmemory-policy volatile-lru}
|
||||
}
|
||||
} {} {external:skip}
|
||||
|
||||
test {redis-server command line arguments - wrong usage that we support anyway} {
|
||||
start_server {config "default.conf" args {loglevel verbose "--maxmemory '700mb'" "--maxmemory-policy 'volatile-lru'"}} {
|
||||
assert_match [r config get loglevel] {loglevel verbose}
|
||||
assert_match [r config get maxmemory] {maxmemory 734003200}
|
||||
assert_match [r config get maxmemory-policy] {maxmemory-policy volatile-lru}
|
||||
}
|
||||
} {} {external:skip}
|
||||
|
||||
test {redis-server command line arguments - allow option value to use the `--` prefix} {
|
||||
start_server {config "default.conf" args {--proc-title-template --my--title--template --loglevel verbose}} {
|
||||
assert_match [r config get proc-title-template] {proc-title-template --my--title--template}
|
||||
@@ -510,15 +525,40 @@ start_server {tags {"introspection"}} {
|
||||
}
|
||||
} {} {external:skip}
|
||||
|
||||
test {redis-server command line arguments - option name and option value in the same arg and `--` prefix} {
|
||||
start_server {config "default.conf" args {"--proc-title-template --my--title--template" "--loglevel verbose"}} {
|
||||
assert_match [r config get proc-title-template] {proc-title-template --my--title--template}
|
||||
assert_match [r config get loglevel] {loglevel verbose}
|
||||
}
|
||||
} {} {external:skip}
|
||||
|
||||
test {redis-server command line arguments - save with empty input} {
|
||||
# Take `--loglevel` as the save option value.
|
||||
catch {exec src/redis-server --save --loglevel verbose} err
|
||||
assert_match {*'save "--loglevel" "verbose"'*Invalid save parameters*} $err
|
||||
start_server {config "default.conf" args {--save --loglevel verbose}} {
|
||||
assert_match [r config get save] {save {}}
|
||||
assert_match [r config get loglevel] {loglevel verbose}
|
||||
}
|
||||
|
||||
start_server {config "default.conf" args {--loglevel verbose --save}} {
|
||||
assert_match [r config get save] {save {}}
|
||||
assert_match [r config get loglevel] {loglevel verbose}
|
||||
}
|
||||
|
||||
start_server {config "default.conf" args {--save {} --loglevel verbose}} {
|
||||
assert_match [r config get save] {save {}}
|
||||
assert_match [r config get loglevel] {loglevel verbose}
|
||||
}
|
||||
|
||||
start_server {config "default.conf" args {--loglevel verbose --save {}}} {
|
||||
assert_match [r config get save] {save {}}
|
||||
assert_match [r config get loglevel] {loglevel verbose}
|
||||
}
|
||||
|
||||
start_server {config "default.conf" args {--proc-title-template --save --save {} --loglevel verbose}} {
|
||||
assert_match [r config get proc-title-template] {proc-title-template --save}
|
||||
assert_match [r config get save] {save {}}
|
||||
assert_match [r config get loglevel] {loglevel verbose}
|
||||
}
|
||||
|
||||
} {} {external:skip}
|
||||
|
||||
test {redis-server command line arguments - take one bulk string with spaces for MULTI_ARG configs parsing} {
|
||||
|
||||
@@ -76,6 +76,19 @@ start_server {tags {"modules"}} {
|
||||
r do_bg_rm_call hgetall hash
|
||||
} {foo bar}
|
||||
|
||||
test {RM_Call from blocked client with script mode} {
|
||||
r do_bg_rm_call_format S hset k foo bar
|
||||
} {1}
|
||||
|
||||
test {RM_Call from blocked client with oom mode} {
|
||||
r config set maxmemory 1
|
||||
# will set server.pre_command_oom_state to 1
|
||||
assert_error {OOM command not allowed*} {r hset hash foo bar}
|
||||
r config set maxmemory 0
|
||||
# now its should be OK to call OOM commands
|
||||
r do_bg_rm_call_format M hset k1 foo bar
|
||||
} {1} {needs:config-maxmemory}
|
||||
|
||||
test {RESP version carries through to blocked client} {
|
||||
for {set client_proto 2} {$client_proto <= 3} {incr client_proto} {
|
||||
r hello $client_proto
|
||||
|
||||
@@ -29,6 +29,11 @@ start_server {tags {"modules"}} {
|
||||
set ld [r test.ld_conversion]
|
||||
assert {[string match $ld "0.00000000000000001"]}
|
||||
}
|
||||
|
||||
test {test unsigned long long conversions} {
|
||||
set ret [r test.ull_conversion]
|
||||
assert {[string match $ret "ok"]}
|
||||
}
|
||||
|
||||
test {test module db commands} {
|
||||
r set x foo
|
||||
@@ -103,6 +108,18 @@ start_server {tags {"modules"}} {
|
||||
assert { [dict get $info flags] == "${ssl_flag}::tracking::" }
|
||||
}
|
||||
|
||||
test {test module get/set client name by id api} {
|
||||
catch { r test.getname } e
|
||||
assert_equal "-ERR No name" $e
|
||||
r client setname nobody
|
||||
catch { r test.setname "name with spaces" } e
|
||||
assert_match "*Invalid argument*" $e
|
||||
assert_equal nobody [r client getname]
|
||||
assert_equal nobody [r test.getname]
|
||||
r test.setname somebody
|
||||
assert_equal somebody [r client getname]
|
||||
}
|
||||
|
||||
test {test module getclientcert api} {
|
||||
set cert [r test.getclientcert]
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ start_server {tags {"obuf-limits external:skip"}} {
|
||||
|
||||
# Read nothing
|
||||
set fd [$rd channel]
|
||||
assert_equal {} [read $fd]
|
||||
assert_equal {} [$rd rawread]
|
||||
}
|
||||
|
||||
# Note: This test assumes that what's written with one write, will be read by redis in one read.
|
||||
@@ -180,8 +180,7 @@ start_server {tags {"obuf-limits external:skip"}} {
|
||||
assert_equal "PONG" [r ping]
|
||||
set clients [r client list]
|
||||
assert_no_match "*name=multicommands*" $clients
|
||||
set fd [$rd2 channel]
|
||||
assert_equal {} [read $fd]
|
||||
assert_equal {} [$rd2 rawread]
|
||||
}
|
||||
|
||||
test {Execute transactions completely even if client output buffer limit is enforced} {
|
||||
|
||||
@@ -1570,6 +1570,52 @@ start_server {tags {"scripting"}} {
|
||||
r config set min-replicas-to-write 0
|
||||
}
|
||||
|
||||
test "not enough good replicas state change during long script" {
|
||||
r set x "pre-script value"
|
||||
r config set min-replicas-to-write 1
|
||||
r config set lua-time-limit 10
|
||||
start_server {tags {"external:skip"}} {
|
||||
# add a replica and wait for the master to recognize it's online
|
||||
r slaveof [srv -1 host] [srv -1 port]
|
||||
wait_replica_online [srv -1 client]
|
||||
|
||||
# run a slow script that does one write, then waits for INFO to indicate
|
||||
# that the replica dropped, and then runs another write
|
||||
set rd [redis_deferring_client -1]
|
||||
$rd eval {
|
||||
redis.call('set','x',"script value")
|
||||
while true do
|
||||
local info = redis.call('info','replication')
|
||||
if (string.match(info, "connected_slaves:0")) then
|
||||
redis.call('set','x',info)
|
||||
break
|
||||
end
|
||||
end
|
||||
return 1
|
||||
} 1 x
|
||||
|
||||
# wait for the script to time out and yield
|
||||
wait_for_condition 100 100 {
|
||||
[catch {r -1 ping} e] == 1
|
||||
} else {
|
||||
fail "Can't wait for script to start running"
|
||||
}
|
||||
catch {r -1 ping} e
|
||||
assert_match {BUSY*} $e
|
||||
|
||||
# cause the replica to disconnect (triggering the busy script to exit)
|
||||
r slaveof no one
|
||||
|
||||
# make sure the script was able to write after the replica dropped
|
||||
assert_equal [$rd read] 1
|
||||
assert_match {*connected_slaves:0*} [r -1 get x]
|
||||
|
||||
$rd close
|
||||
}
|
||||
r config set min-replicas-to-write 0
|
||||
r config set lua-time-limit 5000
|
||||
} {OK} {external:skip needs:repl}
|
||||
|
||||
test "allow-stale shebang flag" {
|
||||
r config set replica-serve-stale-data no
|
||||
r replicaof 127.0.0.1 1
|
||||
@@ -1599,19 +1645,19 @@ start_server {tags {"scripting"}} {
|
||||
} 1 x
|
||||
}
|
||||
|
||||
assert_match {*redis_version*} [
|
||||
assert_match {foobar} [
|
||||
r eval {#!lua flags=allow-stale,no-writes
|
||||
return redis.call('info','server')
|
||||
return redis.call('echo','foobar')
|
||||
} 0
|
||||
]
|
||||
|
||||
# Test again with EVALSHA
|
||||
set sha [
|
||||
r script load {#!lua flags=allow-stale,no-writes
|
||||
return redis.call('info','server')
|
||||
return redis.call('echo','foobar')
|
||||
}
|
||||
]
|
||||
assert_match {*redis_version*} [r evalsha $sha 0]
|
||||
assert_match {foobar} [r evalsha $sha 0]
|
||||
|
||||
r replicaof no one
|
||||
r config set replica-serve-stale-data yes
|
||||
|
||||
@@ -2208,7 +2208,7 @@ start_server {tags {"zset"}} {
|
||||
assert_match "*syntax*" $err
|
||||
}
|
||||
|
||||
test {ZRANGESTORE with zset-max-listpack-entries 0 dst key should use skiplist encoding} {
|
||||
test {ZRANGESTORE with zset-max-listpack-entries 0 #10767 case} {
|
||||
set original_max [lindex [r config get zset-max-listpack-entries] 1]
|
||||
r config set zset-max-listpack-entries 0
|
||||
r del z1{t} z2{t}
|
||||
@@ -2217,6 +2217,18 @@ start_server {tags {"zset"}} {
|
||||
r config set zset-max-listpack-entries $original_max
|
||||
}
|
||||
|
||||
test {ZRANGESTORE with zset-max-listpack-entries 1 dst key should use skiplist encoding} {
|
||||
set original_max [lindex [r config get zset-max-listpack-entries] 1]
|
||||
r config set zset-max-listpack-entries 1
|
||||
r del z1{t} z2{t} z3{t}
|
||||
r zadd z1{t} 1 a 2 b
|
||||
assert_equal 1 [r zrangestore z2{t} z1{t} 0 0]
|
||||
assert_encoding listpack z2{t}
|
||||
assert_equal 2 [r zrangestore z3{t} z1{t} 0 1]
|
||||
assert_encoding skiplist z3{t}
|
||||
r config set zset-max-listpack-entries $original_max
|
||||
}
|
||||
|
||||
test {ZRANGE invalid syntax} {
|
||||
catch {r zrange z1{t} 0 -1 limit 1 2} err
|
||||
assert_match "*syntax*" $err
|
||||
|
||||
@@ -11,12 +11,13 @@ def markdown(s)
|
||||
s.chop! while s[-1] == "\n" || s[-1] == " "
|
||||
lines = s.split("\n")
|
||||
newlines = []
|
||||
# Fix some markdown, except in code blocks indented by 4 spaces.
|
||||
# Fix some markdown
|
||||
lines.each{|l|
|
||||
# Rewrite RM_Xyz() to RedisModule_Xyz().
|
||||
l = l.gsub(/(?<![A-Z_])RM_(?=[A-Z])/, 'RedisModule_')
|
||||
# Fix more markdown, except in code blocks indented by 4 spaces, which we
|
||||
# don't want to mess with.
|
||||
if not l.start_with?(' ')
|
||||
# Rewrite RM_Xyz() to `RedisModule_Xyz()`. The () suffix is
|
||||
# optional. Even RM_Xyz*() with * as wildcard is handled.
|
||||
l = l.gsub(/(?<!`)RM_([A-z]+(?:\*?\(\))?)/, '`RedisModule_\1`')
|
||||
# Add backquotes around RedisModule functions and type where missing.
|
||||
l = l.gsub(/(?<!`)RedisModule[A-z]+(?:\*?\(\))?/){|x| "`#{x}`"}
|
||||
# Add backquotes around c functions like malloc() where missing.
|
||||
|
||||
@@ -19,7 +19,7 @@ struct entry {
|
||||
};
|
||||
|
||||
#define to_16bit_minutes(x) ((x/60) & 65535)
|
||||
#define COUNTER_INIT_VAL 5
|
||||
#define LFU_INIT_VAL 5
|
||||
|
||||
/* Compute the difference in minutes between two 16 bit minutes times
|
||||
* obtained with to_16bit_minutes(). Since they can wrap around if
|
||||
@@ -36,7 +36,7 @@ uint16_t minutes_diff(uint16_t now, uint16_t prev) {
|
||||
uint8_t log_incr(uint8_t counter) {
|
||||
if (counter == 255) return counter;
|
||||
double r = (double)rand()/RAND_MAX;
|
||||
double baseval = counter-COUNTER_INIT_VAL;
|
||||
double baseval = counter-LFU_INIT_VAL;
|
||||
if (baseval < 0) baseval = 0;
|
||||
double limit = 1.0/(baseval*10+1);
|
||||
if (r < limit) counter++;
|
||||
@@ -56,7 +56,7 @@ uint8_t scan_entry(struct entry *e) {
|
||||
>= decr_every)
|
||||
{
|
||||
if (e->counter) {
|
||||
if (e->counter > COUNTER_INIT_VAL*2) {
|
||||
if (e->counter > LFU_INIT_VAL*2) {
|
||||
e->counter /= 2;
|
||||
} else {
|
||||
e->counter--;
|
||||
@@ -89,7 +89,7 @@ int main(void) {
|
||||
|
||||
/* Initialize. */
|
||||
for (j = 0; j < keyspace_size; j++) {
|
||||
entries[j].counter = COUNTER_INIT_VAL;
|
||||
entries[j].counter = LFU_INIT_VAL;
|
||||
entries[j].decrtime = to_16bit_minutes(start);
|
||||
entries[j].hits = 0;
|
||||
entries[j].ctime = time(NULL);
|
||||
@@ -131,7 +131,7 @@ int main(void) {
|
||||
* 10 and 19, a random one every 10 seconds. */
|
||||
if (new_entry_time <= now) {
|
||||
idx = 10+(rand()%10);
|
||||
entries[idx].counter = COUNTER_INIT_VAL;
|
||||
entries[idx].counter = LFU_INIT_VAL;
|
||||
entries[idx].decrtime = to_16bit_minutes(time(NULL));
|
||||
entries[idx].hits = 0;
|
||||
entries[idx].ctime = time(NULL);
|
||||
|
||||
Reference in New Issue
Block a user