summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOran Agra <oran@redislabs.com>2022-07-11 17:44:20 +0300
committerGitHub <noreply@github.com>2022-07-11 17:44:20 +0300
commit76b9c13d1dbd692a1fdee7aefde8e2dc5ccd0e3a (patch)
treedd9c1c34afb75f0ff8f774eef6ef87125ad06dd2
parent05833959e3875ea10f9b2934dc68daca549c9531 (diff)
parent85abb7cf2ad3d091310a293ff372689f710b2ef7 (diff)
downloadredis-76b9c13d1dbd692a1fdee7aefde8e2dc5ccd0e3a.tar.gz
Merge pull request #10962 from oranagra/release-7.0.37.0.3
Release 7.0.3
-rw-r--r--00-RELEASENOTES61
-rw-r--r--CODE_OF_CONDUCT.md (renamed from CONDUCT)2
-rw-r--r--CONTRIBUTING.md (renamed from CONTRIBUTING)0
-rw-r--r--README.md4
-rw-r--r--deps/hiredis/hiredis_ssl.h2
-rw-r--r--deps/hiredis/ssl.c2
-rw-r--r--redis.conf2
-rw-r--r--sentinel.conf4
-rw-r--r--src/acl.c22
-rw-r--r--src/aof.c24
-rw-r--r--src/cluster.c68
-rw-r--r--src/cluster.h1
-rw-r--r--src/commands.c60
-rw-r--r--src/commands/client-caching.json3
-rw-r--r--src/commands/client-getname.json3
-rw-r--r--src/commands/client-getredir.json3
-rw-r--r--src/commands/client-help.json3
-rw-r--r--src/commands/client-id.json3
-rw-r--r--src/commands/client-info.json3
-rw-r--r--src/commands/client-kill.json3
-rw-r--r--src/commands/client-list.json3
-rw-r--r--src/commands/client-no-evict.json3
-rw-r--r--src/commands/client-pause.json3
-rw-r--r--src/commands/client-reply.json3
-rw-r--r--src/commands/client-setname.json3
-rw-r--r--src/commands/client-tracking.json3
-rw-r--r--src/commands/client-trackinginfo.json3
-rw-r--r--src/commands/client-unblock.json3
-rw-r--r--src/commands/client-unpause.json3
-rw-r--r--src/commands/cluster-meet.json12
-rw-r--r--src/commands/cluster-reset.json2
-rw-r--r--src/commands/command-count.json3
-rw-r--r--src/commands/command-docs.json3
-rw-r--r--src/commands/command-getkeys.json3
-rw-r--r--src/commands/command-getkeysandflags.json3
-rw-r--r--src/commands/command-help.json3
-rw-r--r--src/commands/command-info.json3
-rw-r--r--src/commands/command-list.json3
-rw-r--r--src/commands/echo.json2
-rw-r--r--src/commands/zrange.json4
-rw-r--r--src/config.c5
-rw-r--r--src/dict.c2
-rw-r--r--src/evict.c10
-rw-r--r--src/functions.c2
-rw-r--r--src/help.h8
-rw-r--r--src/listpack.c5
-rw-r--r--src/module.c85
-rw-r--r--src/multi.c17
-rw-r--r--src/networking.c56
-rw-r--r--src/pubsub.c12
-rw-r--r--src/rdb.c14
-rw-r--r--src/redis-benchmark.c4
-rw-r--r--src/redis-check-rdb.c9
-rw-r--r--src/redis-cli.c113
-rw-r--r--src/redisassert.h2
-rw-r--r--src/redismodule.h10
-rw-r--r--src/replication.c12
-rw-r--r--src/script.c5
-rw-r--r--src/server.c97
-rw-r--r--src/server.h16
-rw-r--r--src/syscheck.c2
-rw-r--r--src/t_zset.c6
-rw-r--r--src/tls.c8
-rw-r--r--src/util.c49
-rw-r--r--src/util.h1
-rw-r--r--src/version.h4
-rw-r--r--tests/cluster/tests/00-base.tcl13
-rw-r--r--tests/cluster/tests/28-cluster-shards.tcl19
-rw-r--r--tests/cluster/tests/29-slot-migration-response.tcl50
-rw-r--r--tests/integration/redis-benchmark.tcl9
-rw-r--r--tests/modules/blockedclient.c20
-rw-r--r--tests/modules/misc.c102
-rw-r--r--tests/sentinel/tests/00-base.tcl7
-rw-r--r--tests/sentinel/tests/03-runtime-reconf.tcl12
-rw-r--r--tests/support/redis.tcl41
-rw-r--r--tests/support/util.tcl4
-rw-r--r--tests/unit/client-eviction.tcl17
-rw-r--r--tests/unit/cluster.tcl94
-rw-r--r--tests/unit/introspection.tcl50
-rw-r--r--tests/unit/moduleapi/blockedclient.tcl13
-rw-r--r--tests/unit/moduleapi/misc.tcl17
-rw-r--r--tests/unit/obuf-limits.tcl5
-rw-r--r--tests/unit/scripting.tcl54
-rw-r--r--tests/unit/type/zset.tcl14
-rwxr-xr-xutils/generate-module-api-doc.rb9
-rw-r--r--utils/lru/lfu-simulation.c10
86 files changed, 1160 insertions, 295 deletions
diff --git a/00-RELEASENOTES b/00-RELEASENOTES
index b6ebeb5ed..171b8f64f 100644
--- a/00-RELEASENOTES
+++ b/00-RELEASENOTES
@@ -13,6 +13,65 @@ SECURITY: There are security fixes in the release.
================================================================================
+Redis 7.0.3 Released Monday Jul 11 12:00:00 IST 2022
+================================================================================
+
+Upgrade urgency: MODERATE, specifically if you're using a previous release of
+Redis 7.0, contains fixes for bugs in previous 7.0 releases.
+
+
+Performance and resource utilization improvements
+=================================================
+
+* Optimize zset conversion on large ZRANGESTORE (#10789)
+* Optimize the performance of sending PING on large clusters (#10624)
+* Allow for faster restart of Redis in cluster mode (#10912)
+
+INFO fields and introspection changes
+=====================================
+
+* Add missing sharded pubsub keychannel count to CLIENT LIST (#10895)
+* Add missing pubsubshard_channels field in INFO STATS (#10929)
+
+Module API changes
+==================
+
+* Add RM_StringToULongLong and RM_CreateStringFromULongLong (#10889)
+* Add RM_SetClientNameById and RM_GetClientNameById (#10839)
+
+Changes in CLI tools
+====================
+
+* Add missing cluster-port support to redis-cli --cluster (#10344)
+
+Other General Improvements
+==========================
+
+* Account sharded pubsub channels memory consumption (#10925)
+* Allow ECHO in loading and stale modes (#10853)
+* Cluster: Throw -TRYAGAIN instead of -ASK on migrating nodes for multi-key
+ commands when the node only has some of the keys (#9526)
+
+Bug Fixes
+=========
+
+* TLS: Notify clients on connection shutdown (#10931)
+* Fsync directory while persisting AOF manifest, RDB file, and config file (#10737)
+* Script that made modification will not break with unexpected NOREPLICAS error (#10855)
+* Cluster: Fix a bug where nodes may not acknowledge a CLUSTER FAILOVER TAKEOVER
+ after a replica reboots (#10798)
+* Cluster: Fix crash during handshake and cluster shards call (#10942)
+
+Fixes for issues in previous releases of Redis 7.0
+--------------------------------------------------
+
+* TLS: Fix issues with large replies (#10909)
+* Correctly report the startup warning for vm.overcommit_memory (#10841)
+* redis-server command line allow passing config name and value in the same argument (#10866)
+* Support --save command line argument with no value for backwards compatibility (#10866)
+* Fix CLUSTER RESET command regression requiring an argument (#10898)
+
+================================================================================
Redis 7.0.2 Released Sunday Jun 12 12:00:00 IST 2022
================================================================================
@@ -598,7 +657,7 @@ New configuration options
* repl-diskless-sync-max-replicas, allows faster replication in some cases (#10092)
* latency-tracking, enabled by default, and latency-tracking-info-percentiles (#9462)
* cluster-announce-hostnameand cluster-preferred-endpoint-type (#9530)
-* cluster-allow-pubsublocal-when-down (#8621)
+* cluster-allow-pubsubshard-when-down (#8621)
* cluster-link-sendbuf-limit (#9774)
* list-max-listpack-*, hash-max-listpack-*, zset-max-listpack-* as aliases for
the old ziplist configs (#8887, #9366, #9740)
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
diff --git a/README.md b/README.md
index 0277ddc62..bab7fad63 100644
--- a/README.md
+++ b/README.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
diff --git a/src/acl.c b/src/acl.c
index 3443ee691..b22761411 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -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;
+ }
}
}
diff --git a/src/aof.c b/src/aof.c
index 823759f24..150404bc3 100644
--- a/src/aof.c
+++ b/src/aof.c
@@ -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;
+}
diff --git a/src/rdb.c b/src/rdb.c
index 141677c30..b53fbdb21 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -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,
diff --git a/src/tls.c b/src/tls.c
index 66c485ac2..e8d8a4f64 100644
--- a/src/tls.c
+++ b/src/tls.c
@@ -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/src/version.h b/src/version.h
index eff0a6015..f42c936d5 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,2 +1,2 @@
-#define REDIS_VERSION "7.0.2"
-#define REDIS_VERSION_NUM 0x00070002
+#define REDIS_VERSION "7.0.3"
+#define REDIS_VERSION_NUM 0x00070003
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);