diff options
author | Oran Agra <oran@redislabs.com> | 2022-07-11 15:22:06 +0300 |
---|---|---|
committer | Oran Agra <oran@redislabs.com> | 2022-07-11 15:22:06 +0300 |
commit | 7b9fc6fca08a9c5f3fb26742e6c9a1fb738ffd65 (patch) | |
tree | 5249d0edba4b88efc1802f59676c7ebabbada25d | |
parent | 05833959e3875ea10f9b2934dc68daca549c9531 (diff) | |
parent | 693acc0114af6298ab6601ab6d6c668c4e795049 (diff) | |
download | redis-7b9fc6fca08a9c5f3fb26742e6c9a1fb738ffd65.tar.gz |
Merge 'origin/unstable' into 7.0 for 7.0.3
84 files changed, 1098 insertions, 292 deletions
diff --git a/CONDUCT b/CODE_OF_CONDUCT.md index e0e15e268..d66769b98 100644 --- a/CONDUCT +++ b/CODE_OF_CONDUCT.md @@ -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.
\ No newline at end of file +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING b/CONTRIBUTING.md index 56b71834d..56b71834d 100644 --- a/CONTRIBUTING +++ b/CONTRIBUTING.md @@ -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 diff --git a/deps/hiredis/hiredis_ssl.h b/deps/hiredis/hiredis_ssl.h index e3d3e1cf5..50fb77cd8 100644 --- a/deps/hiredis/hiredis_ssl.h +++ b/deps/hiredis/hiredis_ssl.h @@ -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; diff --git a/deps/hiredis/ssl.c b/deps/hiredis/ssl.c index a709ea7cc..c10af7c54 100644 --- a/deps/hiredis/ssl.c +++ b/deps/hiredis/ssl.c @@ -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: diff --git a/redis.conf b/redis.conf index 7635c14b4..0431c3742 100644 --- a/redis.conf +++ b/redis.conf @@ -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). # diff --git a/sentinel.conf b/sentinel.conf index 4d211c06b..bba9b92c2 100644 --- a/sentinel.conf +++ b/sentinel.conf @@ -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); diff --git a/src/cluster.c b/src/cluster.c index d328ede2f..ae353e1dd 100644 --- a/src/cluster.c +++ b/src/cluster.c @@ -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 diff --git a/src/cluster.h b/src/cluster.h index 1349a7a92..c50cfff80 100644 --- a/src/cluster.h +++ b/src/cluster.h @@ -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 */ diff --git a/src/commands.c b/src/commands.c index 7ea796470..63dfd92e3 100644 --- a/src/commands.c +++ b/src/commands.c @@ -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}, diff --git a/src/commands/client-caching.json b/src/commands/client-caching.json index de49cfc39..b8beaa8b6 100644 --- a/src/commands/client-caching.json +++ b/src/commands/client-caching.json @@ -10,7 +10,8 @@ "command_flags": [ "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-getname.json b/src/commands/client-getname.json index 89a62e872..515e0ed67 100644 --- a/src/commands/client-getname.json +++ b/src/commands/client-getname.json @@ -10,7 +10,8 @@ "command_flags": [ "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-getredir.json b/src/commands/client-getredir.json index 2313d0713..8d5b23997 100644 --- a/src/commands/client-getredir.json +++ b/src/commands/client-getredir.json @@ -10,7 +10,8 @@ "command_flags": [ "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-help.json b/src/commands/client-help.json index 6f725bae6..fee49f9b8 100644 --- a/src/commands/client-help.json +++ b/src/commands/client-help.json @@ -9,7 +9,8 @@ "function": "clientCommand", "command_flags": [ "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-id.json b/src/commands/client-id.json index e05c8c62a..792da7dbb 100644 --- a/src/commands/client-id.json +++ b/src/commands/client-id.json @@ -10,7 +10,8 @@ "command_flags": [ "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-info.json b/src/commands/client-info.json index 2668eaf14..06fa094bb 100644 --- a/src/commands/client-info.json +++ b/src/commands/client-info.json @@ -10,7 +10,8 @@ "command_flags": [ "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-kill.json b/src/commands/client-kill.json index 398738bb2..452724fe2 100644 --- a/src/commands/client-kill.json +++ b/src/commands/client-kill.json @@ -33,7 +33,8 @@ "ADMIN", "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-list.json b/src/commands/client-list.json index bcd1dcefb..75605cfb1 100644 --- a/src/commands/client-list.json +++ b/src/commands/client-list.json @@ -25,7 +25,8 @@ "ADMIN", "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-no-evict.json b/src/commands/client-no-evict.json index af99348d2..fc0ad71c9 100644 --- a/src/commands/client-no-evict.json +++ b/src/commands/client-no-evict.json @@ -11,7 +11,8 @@ "ADMIN", "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-pause.json b/src/commands/client-pause.json index 3a1d9be83..90b7e3bc9 100644 --- a/src/commands/client-pause.json +++ b/src/commands/client-pause.json @@ -17,7 +17,8 @@ "ADMIN", "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-reply.json b/src/commands/client-reply.json index 59128ca3a..25d2cef72 100644 --- a/src/commands/client-reply.json +++ b/src/commands/client-reply.json @@ -10,7 +10,8 @@ "command_flags": [ "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-setname.json b/src/commands/client-setname.json index 58e5f446d..cc9199fea 100644 --- a/src/commands/client-setname.json +++ b/src/commands/client-setname.json @@ -10,7 +10,8 @@ "command_flags": [ "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-tracking.json b/src/commands/client-tracking.json index 06f1df944..40811f155 100644 --- a/src/commands/client-tracking.json +++ b/src/commands/client-tracking.json @@ -10,7 +10,8 @@ "command_flags": [ "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-trackinginfo.json b/src/commands/client-trackinginfo.json index 0669f9a27..124c44281 100644 --- a/src/commands/client-trackinginfo.json +++ b/src/commands/client-trackinginfo.json @@ -10,7 +10,8 @@ "command_flags": [ "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-unblock.json b/src/commands/client-unblock.json index c6690730b..75bdddc29 100644 --- a/src/commands/client-unblock.json +++ b/src/commands/client-unblock.json @@ -11,7 +11,8 @@ "ADMIN", "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/client-unpause.json b/src/commands/client-unpause.json index c25fd0466..661baa0fd 100644 --- a/src/commands/client-unpause.json +++ b/src/commands/client-unpause.json @@ -11,7 +11,8 @@ "ADMIN", "NOSCRIPT", "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/cluster-meet.json b/src/commands/cluster-meet.json index c36b7007e..9a85be76d 100644 --- a/src/commands/cluster-meet.json +++ b/src/commands/cluster-meet.json @@ -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" } ] } diff --git a/src/commands/cluster-reset.json b/src/commands/cluster-reset.json index b7d675cd8..630f458e7 100644 --- a/src/commands/cluster-reset.json +++ b/src/commands/cluster-reset.json @@ -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": [ diff --git a/src/commands/command-count.json b/src/commands/command-count.json index 88f7873bd..f2081ca46 100644 --- a/src/commands/command-count.json +++ b/src/commands/command-count.json @@ -9,7 +9,8 @@ "function": "commandCountCommand", "command_flags": [ "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/command-docs.json b/src/commands/command-docs.json index 68a32f8a1..7dc81d61c 100644 --- a/src/commands/command-docs.json +++ b/src/commands/command-docs.json @@ -9,7 +9,8 @@ "function": "commandDocsCommand", "command_flags": [ "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/command-getkeys.json b/src/commands/command-getkeys.json index ca655ac32..744726144 100644 --- a/src/commands/command-getkeys.json +++ b/src/commands/command-getkeys.json @@ -9,7 +9,8 @@ "function": "commandGetKeysCommand", "command_flags": [ "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/command-getkeysandflags.json b/src/commands/command-getkeysandflags.json index 44b3ddcb1..6410835ee 100644 --- a/src/commands/command-getkeysandflags.json +++ b/src/commands/command-getkeysandflags.json @@ -9,7 +9,8 @@ "function": "commandGetKeysAndFlagsCommand", "command_flags": [ "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/command-help.json b/src/commands/command-help.json index ed759083f..d5ad719f0 100644 --- a/src/commands/command-help.json +++ b/src/commands/command-help.json @@ -9,7 +9,8 @@ "function": "commandHelpCommand", "command_flags": [ "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/command-info.json b/src/commands/command-info.json index 40d60a3ec..52ab40084 100644 --- a/src/commands/command-info.json +++ b/src/commands/command-info.json @@ -15,7 +15,8 @@ ], "command_flags": [ "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/command-list.json b/src/commands/command-list.json index 49e9558ca..9ef624f07 100644 --- a/src/commands/command-list.json +++ b/src/commands/command-list.json @@ -9,7 +9,8 @@ "function": "commandListCommand", "command_flags": [ "LOADING", - "STALE" + "STALE", + "SENTINEL" ], "acl_categories": [ "CONNECTION" diff --git a/src/commands/echo.json b/src/commands/echo.json index a641bd35d..f38d10bc5 100644 --- a/src/commands/echo.json +++ b/src/commands/echo.json @@ -7,6 +7,8 @@ "arity": 2, "function": "echoCommand", "command_flags": [ + "LOADING", + "STALE", "FAST" ], "acl_categories": [ diff --git a/src/commands/zrange.json b/src/commands/zrange.json index 1c7232a95..63fb01a09 100644 --- a/src/commands/zrange.json +++ b/src/commands/zrange.json @@ -45,11 +45,11 @@ "key_spec_index": 0 }, { - "name": "min", + "name": "start", "type": "string" }, { - "name": "max", + "name": "stop", "type": "string" }, { diff --git a/src/config.c b/src/config.c index c714b1994..115446c67 100644 --- a/src/config.c +++ b/src/config.c @@ -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; } diff --git a/src/dict.c b/src/dict.c index 9d96c6c27..cb6850027 100644 --- a/src/dict.c +++ b/src/dict.c @@ -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 diff --git a/src/evict.c b/src/evict.c index 316edf606..6ee85ede5 100644 --- a/src/evict.c +++ b/src/evict.c @@ -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; diff --git a/src/functions.c b/src/functions.c index 463a07311..688d9f5c6 100644 --- a/src/functions.c +++ b/src/functions.c @@ -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. diff --git a/src/help.h b/src/help.h index 7714fc783..a95da3695 100644 --- a/src/help.h +++ b/src/help.h @@ -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" }, diff --git a/src/listpack.c b/src/listpack.c index 75189f55f..2ef77131c 100644 --- a/src/listpack.c +++ b/src/listpack.c @@ -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; diff --git a/src/module.c b/src/module.c index a1f6a66f5..3ea8433f8 100644 --- a/src/module.c +++ b/src/module.c @@ -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); diff --git a/src/multi.c b/src/multi.c index 78f08526a..5249c58bf 100644 --- a/src/multi.c +++ b/src/multi.c @@ -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; } diff --git a/src/networking.c b/src/networking.c index bf39c6582..e8a93dde3 100644 --- a/src/networking.c +++ b/src/networking.c @@ -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) { diff --git a/src/pubsub.c b/src/pubsub.c index 2b063455d..0b909bddd 100644 --- a/src/pubsub.c +++ b/src/pubsub.c @@ -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; diff --git a/src/redis-benchmark.c b/src/redis-benchmark.c index 9b183559d..2e0362b07 100644 --- a/src/redis-benchmark.c +++ b/src/redis-benchmark.c @@ -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); diff --git a/src/redis-check-rdb.c b/src/redis-check-rdb.c index accdc35b0..959768991 100644 --- a/src/redis-check-rdb.c +++ b/src/redis-check-rdb.c @@ -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); diff --git a/src/redis-cli.c b/src/redis-cli.c index 485076792..0de78c67e 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -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 diff --git a/src/redisassert.h b/src/redisassert.h index dd7920554..a3f95da09 100644 --- a/src/redisassert.h +++ b/src/redisassert.h @@ -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 diff --git a/src/redismodule.h b/src/redismodule.h index 899bb519d..f1019fc2c 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -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); diff --git a/src/replication.c b/src/replication.c index 385efcf6c..b929b0460 100644 --- a/src/replication.c +++ b/src/replication.c @@ -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; diff --git a/src/script.c b/src/script.c index f12f3c8c6..a6d2e1a0c 100644 --- a/src/script.c +++ b/src/script.c @@ -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. */ diff --git a/src/server.c b/src/server.c index 96ceb0122..de32ff847 100644 --- a/src/server.c +++ b/src/server.c @@ -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])); diff --git a/src/server.h b/src/server.h index abaa5f046..f33eaa5ce 100644 --- a/src/server.h +++ b/src/server.h @@ -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); diff --git a/src/syscheck.c b/src/syscheck.c index 58dc78f1b..5f0b799e4 100644 --- a/src/syscheck.c +++ b/src/syscheck.c @@ -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 " diff --git a/src/t_zset.c b/src/t_zset.c index 771d953b5..34f8fb74b 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -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; } diff --git a/src/util.c b/src/util.c index 1e083fc54..4b534e9a6 100644 --- a/src/util.c +++ b/src/util.c @@ -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> diff --git a/src/util.h b/src/util.h index 0515f1a83..3cf6c3e98 100644 --- a/src/util.h +++ b/src/util.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); diff --git a/tests/cluster/tests/00-base.tcl b/tests/cluster/tests/00-base.tcl index 12d8244a8..e9e99baaa 100644 --- a/tests/cluster/tests/00-base.tcl +++ b/tests/cluster/tests/00-base.tcl @@ -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} +} + diff --git a/tests/cluster/tests/28-cluster-shards.tcl b/tests/cluster/tests/28-cluster-shards.tcl index fe794f2b7..8d218cb6f 100644 --- a/tests/cluster/tests/28-cluster-shards.tcl +++ b/tests/cluster/tests/28-cluster-shards.tcl @@ -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] -}
\ No newline at end of file +} + +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 +} diff --git a/tests/cluster/tests/29-slot-migration-response.tcl b/tests/cluster/tests/29-slot-migration-response.tcl new file mode 100644 index 000000000..060cc8d4e --- /dev/null +++ b/tests/cluster/tests/29-slot-migration-response.tcl @@ -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 diff --git a/tests/integration/redis-benchmark.tcl b/tests/integration/redis-benchmark.tcl index 6aa4c7882..5e8555b1b 100644 --- a/tests/integration/redis-benchmark.tcl +++ b/tests/integration/redis-benchmark.tcl @@ -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} { diff --git a/tests/modules/blockedclient.c b/tests/modules/blockedclient.c index 9c2598a34..f4234b0d3 100644 --- a/tests/modules/blockedclient.c +++ b/tests/modules/blockedclient.c @@ -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; diff --git a/tests/modules/misc.c b/tests/modules/misc.c index da6ee9f9e..64b456904 100644 --- a/tests/modules/misc.c +++ b/tests/modules/misc.c @@ -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) diff --git a/tests/sentinel/tests/00-base.tcl b/tests/sentinel/tests/00-base.tcl index 1a9879a20..761ee82d1 100644 --- a/tests/sentinel/tests/00-base.tcl +++ b/tests/sentinel/tests/00-base.tcl @@ -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] diff --git a/tests/sentinel/tests/03-runtime-reconf.tcl b/tests/sentinel/tests/03-runtime-reconf.tcl index b58744258..bd6eecc97 100644 --- a/tests/sentinel/tests/03-runtime-reconf.tcl +++ b/tests/sentinel/tests/03-runtime-reconf.tcl @@ -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" diff --git a/tests/support/redis.tcl b/tests/support/redis.tcl index edcc1fb48..861e8bc27 100644 --- a/tests/support/redis.tcl +++ b/tests/support/redis.tcl @@ -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]} : - diff --git a/tests/support/util.tcl b/tests/support/util.tcl index 6741b719a..fd72dcf75 100644 --- a/tests/support/util.tcl +++ b/tests/support/util.tcl @@ -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 } } diff --git a/tests/unit/client-eviction.tcl b/tests/unit/client-eviction.tcl index 469828473..cd6ee1a6e 100644 --- a/tests/unit/client-eviction.tcl +++ b/tests/unit/client-eviction.tcl @@ -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 diff --git a/tests/unit/cluster.tcl b/tests/unit/cluster.tcl index 9d49a2dee..180a19257 100644 --- a/tests/unit/cluster.tcl +++ b/tests/unit/cluster.tcl @@ -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 diff --git a/tests/unit/introspection.tcl b/tests/unit/introspection.tcl index 9b5575d75..73f1649a7 100644 --- a/tests/unit/introspection.tcl +++ b/tests/unit/introspection.tcl @@ -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} { diff --git a/tests/unit/moduleapi/blockedclient.tcl b/tests/unit/moduleapi/blockedclient.tcl index dd6085c81..2cb44788e 100644 --- a/tests/unit/moduleapi/blockedclient.tcl +++ b/tests/unit/moduleapi/blockedclient.tcl @@ -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 diff --git a/tests/unit/moduleapi/misc.tcl b/tests/unit/moduleapi/misc.tcl index c8a5107f9..e9efdc2b3 100644 --- a/tests/unit/moduleapi/misc.tcl +++ b/tests/unit/moduleapi/misc.tcl @@ -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] diff --git a/tests/unit/obuf-limits.tcl b/tests/unit/obuf-limits.tcl index 4453d4889..38048935a 100644 --- a/tests/unit/obuf-limits.tcl +++ b/tests/unit/obuf-limits.tcl @@ -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} { diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl index 6e43ca264..f1c05a4d4 100644 --- a/tests/unit/scripting.tcl +++ b/tests/unit/scripting.tcl @@ -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 diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl index 4394117af..0e2457516 100644 --- a/tests/unit/type/zset.tcl +++ b/tests/unit/type/zset.tcl @@ -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 diff --git a/utils/generate-module-api-doc.rb b/utils/generate-module-api-doc.rb index d4282cbfa..8829434bc 100755 --- a/utils/generate-module-api-doc.rb +++ b/utils/generate-module-api-doc.rb @@ -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. diff --git a/utils/lru/lfu-simulation.c b/utils/lru/lfu-simulation.c index 51d639d66..60105e55b 100644 --- a/utils/lru/lfu-simulation.c +++ b/utils/lru/lfu-simulation.c @@ -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); |