summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--deps/linenoise/README.markdown25
-rw-r--r--deps/linenoise/example.c5
-rw-r--r--deps/linenoise/linenoise.c32
-rw-r--r--deps/linenoise/linenoise.h2
-rw-r--r--redis.conf21
-rw-r--r--sentinel.conf20
-rw-r--r--src/Makefile4
-rw-r--r--src/acl.c11
-rw-r--r--src/ae.c8
-rw-r--r--src/asciilogo.h2
-rw-r--r--src/cluster.c14
-rw-r--r--src/config.c17
-rw-r--r--src/connection.c12
-rw-r--r--src/connection.h15
-rw-r--r--src/connhelpers.h53
-rw-r--r--src/db.c2
-rw-r--r--src/latency.c2
-rw-r--r--src/module.c37
-rw-r--r--src/networking.c39
-rw-r--r--src/rdb.c2
-rw-r--r--src/redis-cli.c27
-rw-r--r--src/replication.c112
-rw-r--r--src/sentinel.c47
-rw-r--r--src/server.c21
-rw-r--r--src/server.h8
-rw-r--r--src/tracking.c4
-rw-r--r--tests/cluster/tests/14-consistency-check.tcl87
-rw-r--r--tests/integration/psync2.tcl21
-rw-r--r--tests/support/server.tcl7
-rw-r--r--tests/test_helper.tcl3
-rw-r--r--tests/unit/acl.tcl7
32 files changed, 558 insertions, 110 deletions
diff --git a/.gitignore b/.gitignore
index de626d61b..e445fd201 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,4 @@ deps/lua/src/liblua.a
*.dSYM
Makefile.dep
.vscode/*
+.idea/*
diff --git a/deps/linenoise/README.markdown b/deps/linenoise/README.markdown
index e01642cf8..1afea2ae6 100644
--- a/deps/linenoise/README.markdown
+++ b/deps/linenoise/README.markdown
@@ -21,7 +21,7 @@ So what usually happens is either:
The result is a pollution of binaries without line editing support.
-So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporing line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not.
+So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporting line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not.
## Terminals, in 2010.
@@ -126,6 +126,24 @@ Linenoise has direct support for persisting the history into an history
file. The functions `linenoiseHistorySave` and `linenoiseHistoryLoad` do
just that. Both functions return -1 on error and 0 on success.
+## Mask mode
+
+Sometimes it is useful to allow the user to type passwords or other
+secrets that should not be displayed. For such situations linenoise supports
+a "mask mode" that will just replace the characters the user is typing
+with `*` characters, like in the following example:
+
+ $ ./linenoise_example
+ hello> get mykey
+ echo: 'get mykey'
+ hello> /mask
+ hello> *********
+
+You can enable and disable mask mode using the following two functions:
+
+ void linenoiseMaskModeEnable(void);
+ void linenoiseMaskModeDisable(void);
+
## Completion
Linenoise supports completion, which is the ability to complete the user
@@ -222,3 +240,8 @@ Sometimes you may want to clear the screen as a result of something the
user typed. You can do this by calling the following function:
void linenoiseClearScreen(void);
+
+## Related projects
+
+* [Linenoise NG](https://github.com/arangodb/linenoise-ng) is a fork of Linenoise that aims to add more advanced features like UTF-8 support, Windows support and other features. Uses C++ instead of C as development language.
+* [Linenoise-swift](https://github.com/andybest/linenoise-swift) is a reimplementation of Linenoise written in Swift.
diff --git a/deps/linenoise/example.c b/deps/linenoise/example.c
index 3a544d3c6..74358c323 100644
--- a/deps/linenoise/example.c
+++ b/deps/linenoise/example.c
@@ -55,6 +55,7 @@ int main(int argc, char **argv) {
*
* The typed string is returned as a malloc() allocated string by
* linenoise, so the user needs to free() it. */
+
while((line = linenoise("hello> ")) != NULL) {
/* Do something with the string. */
if (line[0] != '\0' && line[0] != '/') {
@@ -65,6 +66,10 @@ int main(int argc, char **argv) {
/* The "/historylen" command will change the history len. */
int len = atoi(line+11);
linenoiseHistorySetMaxLen(len);
+ } else if (!strncmp(line, "/mask", 5)) {
+ linenoiseMaskModeEnable();
+ } else if (!strncmp(line, "/unmask", 7)) {
+ linenoiseMaskModeDisable();
} else if (line[0] == '/') {
printf("Unreconized command: %s\n", line);
}
diff --git a/deps/linenoise/linenoise.c b/deps/linenoise/linenoise.c
index fce14a7c5..cfe51e768 100644
--- a/deps/linenoise/linenoise.c
+++ b/deps/linenoise/linenoise.c
@@ -125,6 +125,7 @@ static linenoiseHintsCallback *hintsCallback = NULL;
static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
static struct termios orig_termios; /* In order to restore at exit.*/
+static int maskmode = 0; /* Show "***" instead of input. For passwords. */
static int rawmode = 0; /* For atexit() function to check if restore is needed*/
static int mlmode = 0; /* Multi line mode. Default is single line. */
static int atexit_registered = 0; /* Register atexit just 1 time. */
@@ -197,6 +198,19 @@ FILE *lndebug_fp = NULL;
/* ======================= Low level terminal handling ====================== */
+/* Enable "mask mode". When it is enabled, instead of the input that
+ * the user is typing, the terminal will just display a corresponding
+ * number of asterisks, like "****". This is useful for passwords and other
+ * secrets that should not be displayed. */
+void linenoiseMaskModeEnable(void) {
+ maskmode = 1;
+}
+
+/* Disable mask mode. */
+void linenoiseMaskModeDisable(void) {
+ maskmode = 0;
+}
+
/* Set if to use or not the multi line mode. */
void linenoiseSetMultiLine(int ml) {
mlmode = ml;
@@ -485,6 +499,8 @@ void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
if (bold == 1 && color == -1) color = 37;
if (color != -1 || bold != 0)
snprintf(seq,64,"\033[%d;%d;49m",bold,color);
+ else
+ seq[0] = '\0';
abAppend(ab,seq,strlen(seq));
abAppend(ab,hint,hintlen);
if (color != -1 || bold != 0)
@@ -523,7 +539,11 @@ static void refreshSingleLine(struct linenoiseState *l) {
abAppend(&ab,seq,strlen(seq));
/* Write the prompt and the current buffer content */
abAppend(&ab,l->prompt,strlen(l->prompt));
- abAppend(&ab,buf,len);
+ if (maskmode == 1) {
+ while (len--) abAppend(&ab,"*",1);
+ } else {
+ abAppend(&ab,buf,len);
+ }
/* Show hits if any. */
refreshShowHints(&ab,l,plen);
/* Erase to right */
@@ -577,7 +597,12 @@ static void refreshMultiLine(struct linenoiseState *l) {
/* Write the prompt and the current buffer content */
abAppend(&ab,l->prompt,strlen(l->prompt));
- abAppend(&ab,l->buf,l->len);
+ if (maskmode == 1) {
+ unsigned int i;
+ for (i = 0; i < l->len; i++) abAppend(&ab,"*",1);
+ } else {
+ abAppend(&ab,l->buf,l->len);
+ }
/* Show hits if any. */
refreshShowHints(&ab,l,plen);
@@ -645,7 +670,8 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) {
if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) {
/* Avoid a full update of the line in the
* trivial case. */
- if (write(l->ofd,&c,1) == -1) return -1;
+ char d = (maskmode==1) ? '*' : c;
+ if (write(l->ofd,&d,1) == -1) return -1;
} else {
refreshLine(l);
}
diff --git a/deps/linenoise/linenoise.h b/deps/linenoise/linenoise.h
index ed20232c5..6dfee73bc 100644
--- a/deps/linenoise/linenoise.h
+++ b/deps/linenoise/linenoise.h
@@ -65,6 +65,8 @@ int linenoiseHistoryLoad(const char *filename);
void linenoiseClearScreen(void);
void linenoiseSetMultiLine(int ml);
void linenoisePrintKeyCodes(void);
+void linenoiseMaskModeEnable(void);
+void linenoiseMaskModeDisable(void);
#ifdef __cplusplus
}
diff --git a/redis.conf b/redis.conf
index c04880f32..7c55a3ab0 100644
--- a/redis.conf
+++ b/redis.conf
@@ -142,7 +142,8 @@ tcp-keepalive 300
# server to connected clients, masters or cluster peers. These files should be
# PEM formatted.
#
-# tls-cert-file redis.crt tls-key-file redis.key
+# tls-cert-file redis.crt
+# tls-key-file redis.key
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
#
@@ -175,8 +176,7 @@ tcp-keepalive 300
# tls-cluster yes
# Explicitly specify TLS versions to support. Allowed values are case insensitive
-# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or
-# "default" which is currently >= TLSv1.1.
+# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1)
#
# tls-protocols TLSv1.2
@@ -321,6 +321,19 @@ rdbchecksum yes
# The filename where to dump the DB
dbfilename dump.rdb
+# Remove RDB files used by replication in instances without persistence
+# enabled. By default this option is disabled, however there are environments
+# where for regulations or other security concerns, RDB files persisted on
+# disk by masters in order to feed replicas, or stored on disk by replicas
+# in order to load them for the initial synchronization, should be deleted
+# ASAP. Note that this option ONLY WORKS in instances that have both AOF
+# and RDB persistence disabled, otherwise is completely ignored.
+#
+# An alternative (and sometimes better) way to obtain the same effect is
+# to use diskless replication on both master and replicas instances. However
+# in the case of replicas, diskless is not always an option.
+rdb-del-sync-files no
+
# The working directory.
#
# The DB will be written inside this directory, with the filename specified
@@ -1615,7 +1628,7 @@ hz 10
# offers, and enables by default, the ability to use an adaptive HZ value
# which will temporary raise when there are many connected clients.
#
-# When dynamic HZ is enabled, the actual configured HZ will be used as
+# When dynamic HZ is enabled, the actual configured HZ will be used
# as a baseline, but multiples of the configured HZ value will be actually
# used as needed once more clients are connected. In this way an idle
# instance will use very little CPU time while a busy instance will be
diff --git a/sentinel.conf b/sentinel.conf
index bc9a705ac..4ca5e5f8f 100644
--- a/sentinel.conf
+++ b/sentinel.conf
@@ -102,6 +102,18 @@ sentinel monitor mymaster 127.0.0.1 6379 2
#
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
+# sentinel auth-user <master-name> <username>
+#
+# This is useful in order to authenticate to instances having ACL capabilities,
+# that is, running Redis 6.0 or greater. When just auth-pass is provided the
+# Sentinel instance will authenticate to Redis using the old "AUTH <pass>"
+# method. When also an username is provided, it will use "AUTH <user> <pass>".
+# In the Redis servers side, the ACL to provide just minimal access to
+# Sentinel instances, should be configured along the following lines:
+#
+# user sentinel-user >somepassword +client +subscribe +publish \
+# +ping +info +multi +slaveof +config +client +exec on
+
# sentinel down-after-milliseconds <master-name> <milliseconds>
#
# Number of milliseconds the master (or any attached replica or sentinel) should
@@ -112,6 +124,14 @@ sentinel monitor mymaster 127.0.0.1 6379 2
# Default is 30 seconds.
sentinel down-after-milliseconds mymaster 30000
+# requirepass <password>
+#
+# You can configure Sentinel itself to require a password, however when doing
+# so Sentinel will try to authenticate with the same password to all the
+# other Sentinels. So you need to configure all your Sentinels in a given
+# group with the same "requirepass" password. Check the following documentation
+# for more info: https://redis.io/topics/sentinel
+
# sentinel parallel-syncs <master-name> <numreplicas>
#
# How many replicas we can reconfigure to point to the new replica simultaneously
diff --git a/src/Makefile b/src/Makefile
index 00b623a4b..bbfb06440 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -208,9 +208,9 @@ REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.o
REDIS_CLI_NAME=redis-cli
-REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
+REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o crc64.o siphash.o crc16.o
REDIS_BENCHMARK_NAME=redis-benchmark
-REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o
+REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o
REDIS_CHECK_RDB_NAME=redis-check-rdb
REDIS_CHECK_AOF_NAME=redis-check-aof
diff --git a/src/acl.c b/src/acl.c
index efe6b96ad..27f4bdb84 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -899,16 +899,6 @@ char *ACLSetUserStringError(void) {
return errmsg;
}
-/* Return the first password of the default user or NULL.
- * This function is needed for backward compatibility with the old
- * directive "requirepass" when Redis supported a single global
- * password. */
-sds ACLDefaultUserFirstPassword(void) {
- if (listLength(DefaultUser->passwords) == 0) return NULL;
- listNode *first = listFirst(DefaultUser->passwords);
- return listNodeValue(first);
-}
-
/* Initialize the default user, that will always exist for all the process
* lifetime. */
void ACLInitDefaultUser(void) {
@@ -925,6 +915,7 @@ void ACLInit(void) {
UsersToLoad = listCreate();
ACLLog = listCreate();
ACLInitDefaultUser();
+ server.requirepass = NULL; /* Only used for backward compatibility. */
}
/* Check the username and password pair and return C_OK if they are valid,
diff --git a/src/ae.c b/src/ae.c
index d2248fe5c..1bf6cbfbf 100644
--- a/src/ae.c
+++ b/src/ae.c
@@ -464,6 +464,7 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
if (!invert && fe->mask & mask & AE_READABLE) {
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
+ fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
}
/* Fire the writable event. */
@@ -476,8 +477,11 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
/* If we have to invert the call, fire the readable event now
* after the writable one. */
- if (invert && fe->mask & mask & AE_READABLE) {
- if (!fired || fe->wfileProc != fe->rfileProc) {
+ if (invert) {
+ fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
+ if ((fe->mask & mask & AE_READABLE) &&
+ (!fired || fe->wfileProc != fe->rfileProc))
+ {
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
diff --git a/src/asciilogo.h b/src/asciilogo.h
index 83c538b54..044ca0c55 100644
--- a/src/asciilogo.h
+++ b/src/asciilogo.h
@@ -27,7 +27,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
-char *ascii_logo =
+const char *ascii_logo =
" _._ \n"
" _.-``__ ''-._ \n"
" _.-`` `. `_. ''-._ Redis %s (%s/%d) %s bit\n"
diff --git a/src/cluster.c b/src/cluster.c
index c05e46f76..a2e9ff5b6 100644
--- a/src/cluster.c
+++ b/src/cluster.c
@@ -681,9 +681,10 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
* or schedule it for later depending on connection implementation.
*/
if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) {
- serverLog(LL_VERBOSE,
- "Error accepting cluster node connection: %s",
- connGetLastError(conn));
+ if (connGetState(conn) == CONN_STATE_ERROR)
+ serverLog(LL_VERBOSE,
+ "Error accepting cluster node connection: %s",
+ connGetLastError(conn));
connClose(conn);
return;
}
@@ -4261,7 +4262,7 @@ void clusterCommand(client *c) {
"FORGET <node-id> -- Remove a node from the cluster.",
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
"FLUSHSLOTS -- Delete current node own slots information.",
-"INFO - Return onformation about the cluster.",
+"INFO - Return information about the cluster.",
"KEYSLOT <key> -- Return the hash slot for <key>.",
"MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
"MYID -- Return the node id.",
@@ -4272,6 +4273,7 @@ void clusterCommand(client *c) {
"SET-config-epoch <epoch> - Set config epoch of current node.",
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
"REPLICAS <node-id> -- Return <node-id> replicas.",
+"SAVECONFIG - Force saving cluster configuration on disk.",
"SLOTS -- Return information about slots range mappings. Each range is made of:",
" start, end, master and replicas IP addresses, ports and ids",
NULL
@@ -4981,6 +4983,7 @@ void restoreCommand(client *c) {
}
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000);
signalModifiedKey(c->db,c->argv[1]);
+ notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",c->argv[1],c->db->id);
addReply(c,shared.ok);
server.dirty++;
}
@@ -5327,6 +5330,7 @@ try_again:
/* No COPY option: remove the local key, signal the change. */
dbDelete(c->db,kv[j]);
signalModifiedKey(c->db,kv[j]);
+ notifyKeyspaceEvent(NOTIFY_GENERIC,"del",kv[j],c->db->id);
server.dirty++;
/* Populate the argument vector to replace the old one. */
@@ -5489,7 +5493,7 @@ void readwriteCommand(client *c) {
* already "down" but it is fragile to rely on the update of the global state,
* so we also handle it here.
*
- * CLUSTER_REDIR_DOWN_STATE and CLUSTER_REDIR_DOWN_RO_STATE if the cluster is
+ * CLUSTER_REDIR_DOWN_STATE and CLUSTER_REDIR_DOWN_RO_STATE if the cluster is
* down but the user attempts to execute a command that addresses one or more keys. */
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *error_code) {
clusterNode *n = NULL;
diff --git a/src/config.c b/src/config.c
index fd04b7c87..7c87ebe6e 100644
--- a/src/config.c
+++ b/src/config.c
@@ -411,11 +411,15 @@ void loadServerConfigFromString(char *config) {
goto loaderr;
}
/* The old "requirepass" directive just translates to setting
- * a password to the default user. */
+ * a password to the default user. The only thing we do
+ * additionally is to remember the cleartext password in this
+ * case, for backward compatibility with Redis <= 5. */
ACLSetUser(DefaultUser,"resetpass",-1);
sds aclop = sdscatprintf(sdsempty(),">%s",argv[1]);
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop);
+ sdsfree(server.requirepass);
+ server.requirepass = sdsnew(argv[1]);
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
/* DEAD OPTION */
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
@@ -623,11 +627,15 @@ void configSetCommand(client *c) {
config_set_special_field("requirepass") {
if (sdslen(o->ptr) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt;
/* The old "requirepass" directive just translates to setting
- * a password to the default user. */
+ * a password to the default user. The only thing we do
+ * additionally is to remember the cleartext password in this
+ * case, for backward compatibility with Redis <= 5. */
ACLSetUser(DefaultUser,"resetpass",-1);
sds aclop = sdscatprintf(sdsempty(),">%s",(char*)o->ptr);
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop);
+ sdsfree(server.requirepass);
+ server.requirepass = sdsnew(o->ptr);
} config_set_special_field("save") {
int vlen, j;
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
@@ -899,7 +907,7 @@ void configGetCommand(client *c) {
}
if (stringmatch(pattern,"requirepass",1)) {
addReplyBulkCString(c,"requirepass");
- sds password = ACLDefaultUserFirstPassword();
+ sds password = server.requirepass;
if (password) {
addReplyBulkCBuffer(c,password,sdslen(password));
} else {
@@ -1341,7 +1349,7 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
void rewriteConfigRequirepassOption(struct rewriteConfigState *state, char *option) {
int force = 1;
sds line;
- sds password = ACLDefaultUserFirstPassword();
+ sds password = server.requirepass;
/* If there is no password set, we don't want the requirepass option
* to be present in the configuration at all. */
@@ -2084,6 +2092,7 @@ standardConfig configs[] = {
createBoolConfig("always-show-logo", NULL, IMMUTABLE_CONFIG, server.always_show_logo, 0, NULL, NULL),
createBoolConfig("protected-mode", NULL, MODIFIABLE_CONFIG, server.protected_mode, 1, NULL, NULL),
createBoolConfig("rdbcompression", NULL, MODIFIABLE_CONFIG, server.rdb_compression, 1, NULL, NULL),
+ createBoolConfig("rdb-del-sync-files", NULL, MODIFIABLE_CONFIG, server.rdb_del_sync_files, 0, NULL, NULL),
createBoolConfig("activerehashing", NULL, MODIFIABLE_CONFIG, server.activerehashing, 1, NULL, NULL),
createBoolConfig("stop-writes-on-bgsave-error", NULL, MODIFIABLE_CONFIG, server.stop_writes_on_bgsave_err, 1, NULL, NULL),
createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, server.dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/
diff --git a/src/connection.c b/src/connection.c
index 58d86c31b..2015c9195 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -152,7 +152,7 @@ static void connSocketClose(connection *conn) {
/* If called from within a handler, schedule the close but
* keep the connection until the handler returns.
*/
- if (conn->flags & CONN_FLAG_IN_HANDLER) {
+ if (connHasRefs(conn)) {
conn->flags |= CONN_FLAG_CLOSE_SCHEDULED;
return;
}
@@ -183,10 +183,16 @@ static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
}
static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
+ int ret = C_OK;
+
if (conn->state != CONN_STATE_ACCEPTING) return C_ERR;
conn->state = CONN_STATE_CONNECTED;
- if (!callHandler(conn, accept_handler)) return C_ERR;
- return C_OK;
+
+ connIncrRefs(conn);
+ if (!callHandler(conn, accept_handler)) ret = C_ERR;
+ connDecrRefs(conn);
+
+ return ret;
}
/* Register a write handler, to be called when the connection is writable.
diff --git a/src/connection.h b/src/connection.h
index 97622f8d6..db09dfd83 100644
--- a/src/connection.h
+++ b/src/connection.h
@@ -45,9 +45,8 @@ typedef enum {
CONN_STATE_ERROR
} ConnectionState;
-#define CONN_FLAG_IN_HANDLER (1<<0) /* A handler execution is in progress */
-#define CONN_FLAG_CLOSE_SCHEDULED (1<<1) /* Closed scheduled by a handler */
-#define CONN_FLAG_WRITE_BARRIER (1<<2) /* Write barrier requested */
+#define CONN_FLAG_CLOSE_SCHEDULED (1<<0) /* Closed scheduled by a handler */
+#define CONN_FLAG_WRITE_BARRIER (1<<1) /* Write barrier requested */
typedef void (*ConnectionCallbackFunc)(struct connection *conn);
@@ -70,7 +69,8 @@ typedef struct ConnectionType {
struct connection {
ConnectionType *type;
ConnectionState state;
- int flags;
+ short int flags;
+ short int refs;
int last_errno;
void *private_data;
ConnectionCallbackFunc conn_handler;
@@ -88,6 +88,13 @@ struct connection {
* connAccept() may directly call accept_handler(), or return and call it
* at a later time. This behavior is a bit awkward but aims to reduce the need
* to wait for the next event loop, if no additional handshake is required.
+ *
+ * IMPORTANT: accept_handler may decide to close the connection, calling connClose().
+ * To make this safe, the connection is only marked with CONN_FLAG_CLOSE_SCHEDULED
+ * in this case, and connAccept() returns with an error.
+ *
+ * connAccept() callers must always check the return value and on error (C_ERR)
+ * a connClose() must be called.
*/
static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
diff --git a/src/connhelpers.h b/src/connhelpers.h
index f237c9b1d..86250d09e 100644
--- a/src/connhelpers.h
+++ b/src/connhelpers.h
@@ -37,46 +37,49 @@
* implementations (currently sockets in connection.c and TLS in tls.c).
*
* Currently helpers implement the mechanisms for invoking connection
- * handlers, tracking in-handler states and dealing with deferred
- * destruction (if invoked by a handler).
+ * handlers and tracking connection references, to allow safe destruction
+ * of connections from within a handler.
*/
-/* Called whenever a handler is invoked on a connection and sets the
- * CONN_FLAG_IN_HANDLER flag to indicate we're in a handler context.
+/* Incremenet connection references.
*
- * An attempt to close a connection while CONN_FLAG_IN_HANDLER is
- * set will result with deferred close, i.e. setting the CONN_FLAG_CLOSE_SCHEDULED
- * instead of destructing it.
+ * Inside a connection handler, we guarantee refs >= 1 so it is always
+ * safe to connClose().
+ *
+ * In other cases where we don't want to prematurely lose the connection,
+ * it can go beyond 1 as well; currently it is only done by connAccept().
*/
-static inline void enterHandler(connection *conn) {
- conn->flags |= CONN_FLAG_IN_HANDLER;
+static inline void connIncrRefs(connection *conn) {
+ conn->refs++;
}
-/* Called whenever a handler returns. This unsets the CONN_FLAG_IN_HANDLER
- * flag and performs actual close/destruction if a deferred close was
- * scheduled by the handler.
+/* Decrement connection references.
+ *
+ * Note that this is not intended to provide any automatic free logic!
+ * callHandler() takes care of that for the common flows, and anywhere an
+ * explicit connIncrRefs() is used, the caller is expected to take care of
+ * that.
*/
-static inline int exitHandler(connection *conn) {
- conn->flags &= ~CONN_FLAG_IN_HANDLER;
- if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
- connClose(conn);
- return 0;
- }
- return 1;
+
+static inline void connDecrRefs(connection *conn) {
+ conn->refs--;
+}
+
+static inline int connHasRefs(connection *conn) {
+ return conn->refs;
}
/* Helper for connection implementations to call handlers:
- * 1. Mark the handler in use.
+ * 1. Increment refs to protect the connection.
* 2. Execute the handler (if set).
- * 3. Mark the handler as NOT in use and perform deferred close if was
- * requested by the handler at any time.
+ * 3. Decrement refs and perform deferred close, if refs==0.
*/
static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) {
- conn->flags |= CONN_FLAG_IN_HANDLER;
+ connIncrRefs(conn);
if (handler) handler(conn);
- conn->flags &= ~CONN_FLAG_IN_HANDLER;
+ connDecrRefs(conn);
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
- connClose(conn);
+ if (!connHasRefs(conn)) connClose(conn);
return 0;
}
return 1;
diff --git a/src/db.c b/src/db.c
index 6c5d4b4d4..04e26c33b 100644
--- a/src/db.c
+++ b/src/db.c
@@ -1305,7 +1305,7 @@ int expireIfNeeded(redisDb *db, robj *key) {
/* -----------------------------------------------------------------------------
* API to get key arguments from commands
* ---------------------------------------------------------------------------*/
-#define MAX_KEYS_BUFFER 65536
+#define MAX_KEYS_BUFFER 256
static int getKeysTempBuffer[MAX_KEYS_BUFFER];
/* The base case is to use the keys position as given in the command table
diff --git a/src/latency.c b/src/latency.c
index 74ced72a5..9a291ac9b 100644
--- a/src/latency.c
+++ b/src/latency.c
@@ -85,7 +85,7 @@ int THPGetAnonHugePagesSize(void) {
/* ---------------------------- Latency API --------------------------------- */
/* Latency monitor initialization. We just need to create the dictionary
- * of time series, each time serie is craeted on demand in order to avoid
+ * of time series, each time serie is created on demand in order to avoid
* having a fixed list to maintain. */
void latencyMonitorInit(void) {
server.latency_events = dictCreate(&latencyTimeSeriesDictType,NULL);
diff --git a/src/module.c b/src/module.c
index bbd54082c..6f61a5ca8 100644
--- a/src/module.c
+++ b/src/module.c
@@ -1795,7 +1795,12 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
* current request context (whether the client is a Lua script or in a MULTI),
* and about the Redis instance in general, i.e replication and persistence.
*
- * The available flags are:
+ * It is possible to call this function even with a NULL context, however
+ * in this case the following flags will not be reported:
+ *
+ * * LUA, MULTI, REPLICATED, DIRTY (see below for more info).
+ *
+ * Available flags and their meaning:
*
* * REDISMODULE_CTX_FLAGS_LUA: The command is running in a Lua script
*
@@ -1848,20 +1853,22 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
int flags = 0;
/* Client specific flags */
- if (ctx->client) {
- if (ctx->client->flags & CLIENT_LUA)
- flags |= REDISMODULE_CTX_FLAGS_LUA;
- if (ctx->client->flags & CLIENT_MULTI)
- flags |= REDISMODULE_CTX_FLAGS_MULTI;
- /* Module command recieved from MASTER, is replicated. */
- if (ctx->client->flags & CLIENT_MASTER)
- flags |= REDISMODULE_CTX_FLAGS_REPLICATED;
- }
+ if (ctx) {
+ if (ctx->client) {
+ if (ctx->client->flags & CLIENT_LUA)
+ flags |= REDISMODULE_CTX_FLAGS_LUA;
+ if (ctx->client->flags & CLIENT_MULTI)
+ flags |= REDISMODULE_CTX_FLAGS_MULTI;
+ /* Module command recieved from MASTER, is replicated. */
+ if (ctx->client->flags & CLIENT_MASTER)
+ flags |= REDISMODULE_CTX_FLAGS_REPLICATED;
+ }
- /* For DIRTY flags, we need the blocked client if used */
- client *c = ctx->blocked_client ? ctx->blocked_client->client : ctx->client;
- if (c && (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC))) {
- flags |= REDISMODULE_CTX_FLAGS_MULTI_DIRTY;
+ /* For DIRTY flags, we need the blocked client if used */
+ client *c = ctx->blocked_client ? ctx->blocked_client->client : ctx->client;
+ if (c && (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC))) {
+ flags |= REDISMODULE_CTX_FLAGS_MULTI_DIRTY;
+ }
}
if (server.cluster_enabled)
@@ -6234,7 +6241,7 @@ int moduleUnregisterUsedAPI(RedisModule *module) {
RedisModule *used = ln->value;
listNode *ln = listSearchKey(used->usedby,module);
if (ln) {
- listDelNode(module->using,ln);
+ listDelNode(used->usedby,ln);
count++;
}
}
diff --git a/src/networking.c b/src/networking.c
index 4c394af70..a550e4040 100644
--- a/src/networking.c
+++ b/src/networking.c
@@ -36,6 +36,7 @@
static void setProtocolError(const char *errstr, client *c);
int postponeClientRead(client *c);
+int ProcessingEventsWhileBlocked = 0; /* See processEventsWhileBlocked(). */
/* Return the size consumed from the allocator, for the specified SDS string,
* including internal fragmentation. This function is used in order to compute
@@ -123,7 +124,8 @@ client *createClient(connection *conn) {
c->ctime = c->lastinteraction = server.unixtime;
/* If the default user does not require authentication, the user is
* directly authenticated. */
- c->authenticated = (c->user->flags & USER_FLAG_NOPASS) != 0;
+ c->authenticated = (c->user->flags & USER_FLAG_NOPASS) &&
+ !(c->user->flags & USER_FLAG_DISABLED);
c->replstate = REPL_STATE_NONE;
c->repl_put_online_on_ack = 0;
c->reploff = 0;
@@ -784,7 +786,7 @@ void clientAcceptHandler(connection *conn) {
serverLog(LL_WARNING,
"Error accepting a client connection: %s",
connGetLastError(conn));
- freeClient(c);
+ freeClientAsync(c);
return;
}
@@ -826,7 +828,7 @@ void clientAcceptHandler(connection *conn) {
/* Nothing to do, Just to avoid the warning... */
}
server.stat_rejected_conn++;
- freeClient(c);
+ freeClientAsync(c);
return;
}
}
@@ -885,9 +887,10 @@ static void acceptCommonHandler(connection *conn, int flags, char *ip) {
*/
if (connAccept(conn, clientAcceptHandler) == C_ERR) {
char conninfo[100];
- serverLog(LL_WARNING,
- "Error accepting a client connection: %s (conn: %s)",
- connGetLastError(conn), connGetInfo(conn, conninfo, sizeof(conninfo)));
+ if (connGetState(conn) == CONN_STATE_ERROR)
+ serverLog(LL_WARNING,
+ "Error accepting a client connection: %s (conn: %s)",
+ connGetLastError(conn), connGetInfo(conn, conninfo, sizeof(conninfo)));
freeClient(connGetPrivateData(conn));
return;
}
@@ -2738,6 +2741,12 @@ int clientsArePaused(void) {
int processEventsWhileBlocked(void) {
int iterations = 4; /* See the function top-comment. */
int count = 0;
+
+ /* Note: when we are processing events while blocked (for instance during
+ * busy Lua scripts), we set a global flag. When such flag is set, we
+ * avoid handling the read part of clients using threaded I/O.
+ * See https://github.com/antirez/redis/issues/6988 for more info. */
+ ProcessingEventsWhileBlocked = 1;
while (iterations--) {
int events = 0;
events += aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
@@ -2745,6 +2754,7 @@ int processEventsWhileBlocked(void) {
if (!events) break;
count += events;
}
+ ProcessingEventsWhileBlocked = 0;
return count;
}
@@ -2970,6 +2980,7 @@ int handleClientsWithPendingWritesUsingThreads(void) {
int postponeClientRead(client *c) {
if (io_threads_active &&
server.io_threads_do_reads &&
+ !ProcessingEventsWhileBlocked &&
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ)))
{
c->flags |= CLIENT_PENDING_READ;
@@ -3031,16 +3042,22 @@ int handleClientsWithPendingReadsUsingThreads(void) {
if (tio_debug) printf("I/O READ All threads finshed\n");
/* Run the list of clients again to process the new buffers. */
- listRewind(server.clients_pending_read,&li);
- while((ln = listNext(&li))) {
+ while(listLength(server.clients_pending_read)) {
+ ln = listFirst(server.clients_pending_read);
client *c = listNodeValue(ln);
c->flags &= ~CLIENT_PENDING_READ;
+ listDelNode(server.clients_pending_read,ln);
+
if (c->flags & CLIENT_PENDING_COMMAND) {
- c->flags &= ~ CLIENT_PENDING_COMMAND;
- processCommandAndResetClient(c);
+ c->flags &= ~CLIENT_PENDING_COMMAND;
+ if (processCommandAndResetClient(c) == C_ERR) {
+ /* If the client is no longer valid, we avoid
+ * processing the client later. So we just go
+ * to the next. */
+ continue;
+ }
}
processInputBufferAndReplicate(c);
}
- listEmpty(server.clients_pending_read);
return processed;
}
diff --git a/src/rdb.c b/src/rdb.c
index cbcea96c6..5d34f5a32 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -2231,7 +2231,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
* received from the master. In the latter case, the master is
* responsible for key expiry. If we would expire keys here, the
* snapshot taken by the master may not be reflected on the slave. */
- if (server.masterhost == NULL && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now) {
+ if (iAmMaster() && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now) {
decrRefCount(key);
decrRefCount(val);
} else {
diff --git a/src/redis-cli.c b/src/redis-cli.c
index 54898f42e..72480d08c 100644
--- a/src/redis-cli.c
+++ b/src/redis-cli.c
@@ -229,6 +229,7 @@ static struct config {
int hotkeys;
int stdinarg; /* get last arg from stdin. (-x option) */
char *auth;
+ int askpass;
char *user;
int output; /* output mode, see OUTPUT_* defines */
sds mb_delim;
@@ -1291,7 +1292,11 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
(argc == 3 && !strcasecmp(command,"latency") &&
!strcasecmp(argv[1],"graph")) ||
(argc == 2 && !strcasecmp(command,"latency") &&
- !strcasecmp(argv[1],"doctor")))
+ !strcasecmp(argv[1],"doctor")) ||
+ /* Format PROXY INFO command for Redis Cluster Proxy:
+ * https://github.com/artix75/redis-cluster-proxy */
+ (argc >= 2 && !strcasecmp(command,"proxy") &&
+ !strcasecmp(argv[1],"info")))
{
output_raw = 1;
}
@@ -1450,6 +1455,8 @@ static int parseOptions(int argc, char **argv) {
config.dbnum = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--no-auth-warning")) {
config.no_auth_warning = 1;
+ } else if (!strcmp(argv[i], "--askpass")) {
+ config.askpass = 1;
} else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass"))
&& !lastarg)
{
@@ -1690,6 +1697,9 @@ static void usage(void) {
" (if both are used, this argument takes predecence).\n"
" --user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
" --pass <password> Alias of -a for consistency with the new --user option.\n"
+" --askpass Force user to input password with mask from STDIN.\n"
+" If this argument is used, '-a' and " REDIS_CLI_AUTH_ENV "\n"
+" environment variable will be ignored.\n"
" -u <uri> Server URI.\n"
" -r <repeat> Execute specified command N times.\n"
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
@@ -1983,6 +1993,8 @@ static void repl(void) {
if (config.eval) {
config.eval_ldb = 1;
config.output = OUTPUT_RAW;
+ sdsfreesplitres(argv,argc);
+ linenoiseFree(line);
return; /* Return to evalMode to restart the session. */
} else {
printf("Use 'restart' only in Lua debugging mode.");
@@ -7858,6 +7870,13 @@ static void intrinsicLatencyMode(void) {
}
}
+static sds askPassword() {
+ linenoiseMaskModeEnable();
+ sds auth = linenoise("Please input password: ");
+ linenoiseMaskModeDisable();
+ return auth;
+}
+
/*------------------------------------------------------------------------------
* Program main()
*--------------------------------------------------------------------------- */
@@ -7894,6 +7913,7 @@ int main(int argc, char **argv) {
config.hotkeys = 0;
config.stdinarg = 0;
config.auth = NULL;
+ config.askpass = 0;
config.user = NULL;
config.eval = NULL;
config.eval_ldb = 0;
@@ -7935,6 +7955,10 @@ int main(int argc, char **argv) {
parseEnv();
+ if (config.askpass) {
+ config.auth = askPassword();
+ }
+
#ifdef USE_OPENSSL
if (config.tls) {
ERR_load_crypto_strings();
@@ -8045,3 +8069,4 @@ int main(int argc, char **argv) {
return noninteractive(argc,convertToSds(argc,argv));
}
}
+
diff --git a/src/replication.c b/src/replication.c
index c497051c8..31e14d7fe 100644
--- a/src/replication.c
+++ b/src/replication.c
@@ -45,6 +45,11 @@ void replicationSendAck(void);
void putSlaveOnline(client *slave);
int cancelReplicationHandshake(void);
+/* We take a global flag to remember if this instance generated an RDB
+ * because of replication, so that we can remove the RDB file in case
+ * the instance is configured to have no persistence. */
+int RDBGeneratedByReplication = 0;
+
/* --------------------------- Utility functions ---------------------------- */
/* Return the pointer to a string representing the slave ip:listening_port
@@ -74,6 +79,34 @@ char *replicationGetSlaveName(client *c) {
return buf;
}
+/* Plain unlink() can block for quite some time in order to actually apply
+ * the file deletion to the filesystem. This call removes the file in a
+ * background thread instead. We actually just do close() in the thread,
+ * by using the fact that if there is another instance of the same file open,
+ * the foreground unlink() will not really do anything, and deleting the
+ * file will only happen once the last reference is lost. */
+int bg_unlink(const char *filename) {
+ int fd = open(filename,O_RDONLY|O_NONBLOCK);
+ if (fd == -1) {
+ /* Can't open the file? Fall back to unlinking in the main thread. */
+ return unlink(filename);
+ } else {
+ /* The following unlink() will not do anything since file
+ * is still open. */
+ int retval = unlink(filename);
+ if (retval == -1) {
+ /* If we got an unlink error, we just return it, closing the
+ * new reference we have to the file. */
+ int old_errno = errno;
+ close(fd); /* This would overwrite our errno. So we saved it. */
+ errno = old_errno;
+ return -1;
+ }
+ bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)fd,NULL,NULL);
+ return 0; /* Success. */
+ }
+}
+
/* ---------------------------------- MASTER -------------------------------- */
void createReplicationBacklog(void) {
@@ -591,6 +624,14 @@ int startBgsaveForReplication(int mincapa) {
retval = C_ERR;
}
+ /* If we succeeded to start a BGSAVE with disk target, let's remember
+ * this fact, so that we can later delete the file if needed. Note
+ * that we don't set the flag to 1 if the feature is disabled, otherwise
+ * it would never be cleared: the file is not deleted. This way if
+ * the user enables it later with CONFIG SET, we are fine. */
+ if (retval == C_OK && !socket_target && server.rdb_del_sync_files)
+ RDBGeneratedByReplication = 1;
+
/* If we failed to BGSAVE, remove the slaves waiting for a full
* resynchronization from the list of slaves, inform them with
* an error about what happened, close the connection ASAP. */
@@ -883,6 +924,53 @@ void putSlaveOnline(client *slave) {
replicationGetSlaveName(slave));
}
+/* We call this function periodically to remove an RDB file that was
+ * generated because of replication, in an instance that is otherwise
+ * without any persistence. We don't want instances without persistence
+ * to take RDB files around, this violates certain policies in certain
+ * environments. */
+void removeRDBUsedToSyncReplicas(void) {
+ /* If the feature is disabled, return ASAP but also clear the
+ * RDBGeneratedByReplication flag in case it was set. Otherwise if the
+ * feature was enabled, but gets disabled later with CONFIG SET, the
+ * flag may remain set to one: then next time the feature is re-enabled
+ * via CONFIG SET we have have it set even if no RDB was generated
+ * because of replication recently. */
+ if (!server.rdb_del_sync_files) {
+ RDBGeneratedByReplication = 0;
+ return;
+ }
+
+ if (allPersistenceDisabled() && RDBGeneratedByReplication) {
+ client *slave;
+ listNode *ln;
+ listIter li;
+
+ int delrdb = 1;
+ listRewind(server.slaves,&li);
+ while((ln = listNext(&li))) {
+ slave = ln->value;
+ if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START ||
+ slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END ||
+ slave->replstate == SLAVE_STATE_SEND_BULK)
+ {
+ delrdb = 0;
+ break; /* No need to check the other replicas. */
+ }
+ }
+ if (delrdb) {
+ struct stat sb;
+ if (lstat(server.rdb_filename,&sb) != -1) {
+ RDBGeneratedByReplication = 0;
+ serverLog(LL_NOTICE,
+ "Removing the RDB file used to feed replicas "
+ "in a persistence-less instance");
+ bg_unlink(server.rdb_filename);
+ }
+ }
+ }
+}
+
void sendBulkToSlave(connection *conn) {
client *slave = connGetPrivateData(conn);
char buf[PROTO_IOBUF_LEN];
@@ -894,7 +982,8 @@ void sendBulkToSlave(connection *conn) {
if (slave->replpreamble) {
nwritten = connWrite(conn,slave->replpreamble,sdslen(slave->replpreamble));
if (nwritten == -1) {
- serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s",
+ serverLog(LL_VERBOSE,
+ "Write error sending RDB preamble to replica: %s",
connGetLastError(conn));
freeClient(slave);
return;
@@ -1639,12 +1728,25 @@ void readSyncBulkPayload(connection *conn) {
"Failed trying to load the MASTER synchronization "
"DB from disk");
cancelReplicationHandshake();
+ if (server.rdb_del_sync_files && allPersistenceDisabled()) {
+ serverLog(LL_NOTICE,"Removing the RDB file obtained from "
+ "the master. This replica has persistence "
+ "disabled");
+ bg_unlink(server.rdb_filename);
+ }
/* Note that there's no point in restarting the AOF on sync failure,
it'll be restarted when sync succeeds or replica promoted. */
return;
}
/* Cleanup. */
+ if (server.rdb_del_sync_files && allPersistenceDisabled()) {
+ serverLog(LL_NOTICE,"Removing the RDB file obtained from "
+ "the master. This replica has persistence "
+ "disabled");
+ bg_unlink(server.rdb_filename);
+ }
+
zfree(server.repl_transfer_tmpfile);
close(server.repl_transfer_fd);
server.repl_transfer_fd = -1;
@@ -2182,6 +2284,10 @@ void syncWithMaster(connection *conn) {
if (psync_result == PSYNC_CONTINUE) {
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.");
+ if (server.supervised_mode == SUPERVISED_SYSTEMD) {
+ redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Partial Resynchronization accepted. Ready to accept connections.\n");
+ redisCommunicateSystemd("READY=1\n");
+ }
return;
}
@@ -3149,6 +3255,10 @@ void replicationCron(void) {
}
}
+ /* Remove the RDB file used for replication if Redis is not running
+ * with any persistence. */
+ removeRDBUsedToSyncReplicas();
+
/* Refresh the number of slaves with lag <= min-slaves-max-lag. */
refreshGoodSlavesCount();
replication_cron_loops++; /* Incremented with frequency 1 HZ. */
diff --git a/src/sentinel.c b/src/sentinel.c
index 10c003d03..d091bf230 100644
--- a/src/sentinel.c
+++ b/src/sentinel.c
@@ -205,7 +205,8 @@ typedef struct sentinelRedisInstance {
dict *slaves; /* Slaves for this master instance. */
unsigned int quorum;/* Number of sentinels that need to agree on failure. */
int parallel_syncs; /* How many slaves to reconfigure at same time. */
- char *auth_pass; /* Password to use for AUTH against master & slaves. */
+ char *auth_pass; /* Password to use for AUTH against master & replica. */
+ char *auth_user; /* Username for ACLs AUTH against master & replica. */
/* Slave specific. */
mstime_t master_link_down_time; /* Slave replication link down time. */
@@ -1231,6 +1232,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
SENTINEL_DEFAULT_DOWN_AFTER;
ri->master_link_down_time = 0;
ri->auth_pass = NULL;
+ ri->auth_user = NULL;
ri->slave_priority = SENTINEL_DEFAULT_SLAVE_PRIORITY;
ri->slave_reconf_sent_time = 0;
ri->slave_master_host = NULL;
@@ -1289,6 +1291,7 @@ void releaseSentinelRedisInstance(sentinelRedisInstance *ri) {
sdsfree(ri->slave_master_host);
sdsfree(ri->leader);
sdsfree(ri->auth_pass);
+ sdsfree(ri->auth_user);
sdsfree(ri->info);
releaseSentinelAddr(ri->addr);
dictRelease(ri->renamed_commands);
@@ -1654,19 +1657,19 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
ri->failover_timeout = atoi(argv[2]);
if (ri->failover_timeout <= 0)
return "negative or zero time parameter.";
- } else if (!strcasecmp(argv[0],"parallel-syncs") && argc == 3) {
+ } else if (!strcasecmp(argv[0],"parallel-syncs") && argc == 3) {
/* parallel-syncs <name> <milliseconds> */
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
ri->parallel_syncs = atoi(argv[2]);
- } else if (!strcasecmp(argv[0],"notification-script") && argc == 3) {
+ } else if (!strcasecmp(argv[0],"notification-script") && argc == 3) {
/* notification-script <name> <path> */
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
if (access(argv[2],X_OK) == -1)
return "Notification script seems non existing or non executable.";
ri->notification_script = sdsnew(argv[2]);
- } else if (!strcasecmp(argv[0],"client-reconfig-script") && argc == 3) {
+ } else if (!strcasecmp(argv[0],"client-reconfig-script") && argc == 3) {
/* client-reconfig-script <name> <path> */
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
@@ -1674,11 +1677,16 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
return "Client reconfiguration script seems non existing or "
"non executable.";
ri->client_reconfig_script = sdsnew(argv[2]);
- } else if (!strcasecmp(argv[0],"auth-pass") && argc == 3) {
+ } else if (!strcasecmp(argv[0],"auth-pass") && argc == 3) {
/* auth-pass <name> <password> */
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
ri->auth_pass = sdsnew(argv[2]);
+ } else if (!strcasecmp(argv[0],"auth-user") && argc == 3) {
+ /* auth-user <name> <username> */
+ ri = sentinelGetMasterByName(argv[1]);
+ if (!ri) return "No such master with specified name.";
+ ri->auth_user = sdsnew(argv[2]);
} else if (!strcasecmp(argv[0],"current-epoch") && argc == 2) {
/* current-epoch <epoch> */
unsigned long long current_epoch = strtoull(argv[1],NULL,10);
@@ -1836,7 +1844,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
rewriteConfigRewriteLine(state,"sentinel",line,1);
}
- /* sentinel auth-pass */
+ /* sentinel auth-pass & auth-user */
if (master->auth_pass) {
line = sdscatprintf(sdsempty(),
"sentinel auth-pass %s %s",
@@ -1844,6 +1852,13 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
rewriteConfigRewriteLine(state,"sentinel",line,1);
}
+ if (master->auth_user) {
+ line = sdscatprintf(sdsempty(),
+ "sentinel auth-user %s %s",
+ master->name, master->auth_user);
+ rewriteConfigRewriteLine(state,"sentinel",line,1);
+ }
+
/* sentinel config-epoch */
line = sdscatprintf(sdsempty(),
"sentinel config-epoch %s %llu",
@@ -1968,19 +1983,29 @@ werr:
* will disconnect and reconnect the link and so forth. */
void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
char *auth_pass = NULL;
+ char *auth_user = NULL;
if (ri->flags & SRI_MASTER) {
auth_pass = ri->auth_pass;
+ auth_user = ri->auth_user;
} else if (ri->flags & SRI_SLAVE) {
auth_pass = ri->master->auth_pass;
+ auth_user = ri->master->auth_user;
} else if (ri->flags & SRI_SENTINEL) {
- auth_pass = ACLDefaultUserFirstPassword();
+ auth_pass = server.requirepass;
+ auth_user = NULL;
}
- if (auth_pass) {
+ if (auth_pass && auth_user == NULL) {
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s",
sentinelInstanceMapCommand(ri,"AUTH"),
auth_pass) == C_OK) ri->link->pending_commands++;
+ } else if (auth_pass && auth_user) {
+ /* If we also have an username, use the ACL-style AUTH command
+ * with two arguments, username and password. */
+ if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s %s",
+ sentinelInstanceMapCommand(ri,"AUTH"),
+ auth_user, auth_pass) == C_OK) ri->link->pending_commands++;
}
}
@@ -3522,6 +3547,12 @@ void sentinelSetCommand(client *c) {
sdsfree(ri->auth_pass);
ri->auth_pass = strlen(value) ? sdsnew(value) : NULL;
changes++;
+ } else if (!strcasecmp(option,"auth-user") && moreargs > 0) {
+ /* auth-user <username> */
+ char *value = c->argv[++j]->ptr;
+ sdsfree(ri->auth_user);
+ ri->auth_user = strlen(value) ? sdsnew(value) : NULL;
+ changes++;
} else if (!strcasecmp(option,"quorum") && moreargs > 0) {
/* quorum <count> */
robj *o = c->argv[++j];
diff --git a/src/server.c b/src/server.c
index ab2afd47f..f5fb339f9 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1459,12 +1459,20 @@ void updateDictResizePolicy(void) {
dictDisableResize();
}
+/* Return true if there are no active children processes doing RDB saving,
+ * AOF rewriting, or some side process spawned by a loaded module. */
int hasActiveChildProcess() {
return server.rdb_child_pid != -1 ||
server.aof_child_pid != -1 ||
server.module_child_pid != -1;
}
+/* Return true if this instance has persistence completely turned off:
+ * both RDB and AOF are disabled. */
+int allPersistenceDisabled(void) {
+ return server.saveparamslen == 0 && server.aof_state == AOF_OFF;
+}
+
/* ======================= Cron: called every 100 ms ======================== */
/* Add a sample to the operations per second array of samples. */
@@ -1687,7 +1695,7 @@ void databasesCron(void) {
/* Expire keys by random sampling. Not required for slaves
* as master will synthesize DELs for us. */
if (server.active_expire_enabled) {
- if (server.masterhost == NULL) {
+ if (iAmMaster()) {
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
} else {
expireSlaveKeys();
@@ -2084,6 +2092,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
void beforeSleep(struct aeEventLoop *eventLoop) {
UNUSED(eventLoop);
+ /* We should handle pending reads clients ASAP after event loop. */
+ handleClientsWithPendingReadsUsingThreads();
+
/* Handle TLS pending data. (must be done before flushAppendOnlyFile) */
tlsProcessPendingData();
/* If tls still has pending unread data don't sleep at all. */
@@ -2153,7 +2164,6 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
void afterSleep(struct aeEventLoop *eventLoop) {
UNUSED(eventLoop);
if (moduleCount()) moduleAcquireGIL();
- handleClientsWithPendingReadsUsingThreads();
}
/* =========================== Server initialization ======================== */
@@ -3374,7 +3384,7 @@ int processCommand(client *c) {
/* Check if the user is authenticated. This check is skipped in case
* the default user is flagged as "nopass" and is active. */
int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
- DefaultUser->flags & USER_FLAG_DISABLED) &&
+ (DefaultUser->flags & USER_FLAG_DISABLED)) &&
!c->authenticated;
if (auth_required) {
/* AUTH and HELLO and no auth modules are valid even in
@@ -4857,6 +4867,11 @@ int redisIsSupervised(int mode) {
return 0;
}
+int iAmMaster(void) {
+ return ((!server.cluster_enabled && server.masterhost == NULL) ||
+ (server.cluster_enabled && nodeIsMaster(server.cluster->myself)));
+}
+
int main(int argc, char **argv) {
struct timeval tv;
diff --git a/src/server.h b/src/server.h
index e5043855c..ed4707d66 100644
--- a/src/server.h
+++ b/src/server.h
@@ -1202,6 +1202,8 @@ struct redisServer {
char *rdb_filename; /* Name of RDB file */
int rdb_compression; /* Use compression in RDB? */
int rdb_checksum; /* Use RDB checksum? */
+ int rdb_del_sync_files; /* Remove RDB files used only for SYNC if
+ the instance does not use persistence. */
time_t lastsave; /* Unix time of last successful save */
time_t lastbgsave_try; /* Unix time of last attempted bgsave */
time_t rdb_save_time_last; /* Time used by last RDB save run. */
@@ -1393,6 +1395,9 @@ struct redisServer {
/* ACLs */
char *acl_filename; /* ACL Users file. NULL if not configured. */
unsigned long acllog_max_len; /* Maximum length of the ACL LOG list. */
+ sds requirepass; /* Remember the cleartext password set with the
+ old "requirepass" directive for backward
+ compatibility with Redis <= 5. */
/* Assert & bug reporting */
const char *assert_failed;
const char *assert_file;
@@ -1786,6 +1791,7 @@ void loadingProgress(off_t pos);
void stopLoading(int success);
void startSaving(int rdbflags);
void stopSaving(int success);
+int allPersistenceDisabled(void);
#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
@@ -2388,4 +2394,6 @@ int tlsConfigure(redisTLSContextConfig *ctx_config);
#define redisDebugMark() \
printf("-- MARK %s:%d --\n", __FILE__, __LINE__)
+int iAmMaster(void);
+
#endif
diff --git a/src/tracking.c b/src/tracking.c
index 45f83103a..6f7929430 100644
--- a/src/tracking.c
+++ b/src/tracking.c
@@ -94,7 +94,7 @@ void disableTracking(client *c) {
server.tracking_clients--;
c->flags &= ~(CLIENT_TRACKING|CLIENT_TRACKING_BROKEN_REDIR|
CLIENT_TRACKING_BCAST|CLIENT_TRACKING_OPTIN|
- CLIENT_TRACKING_OPTOUT);
+ CLIENT_TRACKING_OPTOUT|CLIENT_TRACKING_CACHING);
}
}
@@ -271,7 +271,7 @@ void trackingInvalidateKey(robj *keyobj) {
trackingRememberKeyToBroadcast(sdskey,sdslen(sdskey));
rax *ids = raxFind(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey));
- if (ids == raxNotFound) return;;
+ if (ids == raxNotFound) return;
raxIterator ri;
raxStart(&ri,ids);
diff --git a/tests/cluster/tests/14-consistency-check.tcl b/tests/cluster/tests/14-consistency-check.tcl
new file mode 100644
index 000000000..a43725ebc
--- /dev/null
+++ b/tests/cluster/tests/14-consistency-check.tcl
@@ -0,0 +1,87 @@
+source "../tests/includes/init-tests.tcl"
+
+test "Create a 5 nodes cluster" {
+ create_cluster 5 5
+}
+
+test "Cluster should start ok" {
+ assert_cluster_state ok
+}
+
+test "Cluster is writable" {
+ cluster_write_test 0
+}
+
+proc find_non_empty_master {} {
+ set master_id_no {}
+ foreach_redis_id id {
+ if {[RI $id role] eq {master} && [R $id dbsize] > 0} {
+ set master_id_no $id
+ }
+ }
+ return $master_id_no
+}
+
+proc get_one_of_my_replica {id} {
+ set replica_port [lindex [lindex [lindex [R $id role] 2] 0] 1]
+ set replica_id_num [get_instance_id_by_port redis $replica_port]
+ return $replica_id_num
+}
+
+proc cluster_write_keys_with_expire {id ttl} {
+ set prefix [randstring 20 20 alpha]
+ set port [get_instance_attrib redis $id port]
+ set cluster [redis_cluster 127.0.0.1:$port]
+ for {set j 100} {$j < 200} {incr j} {
+ $cluster setex key_expire.$j $ttl $prefix.$j
+ }
+ $cluster close
+}
+
+proc test_slave_load_expired_keys {aof} {
+ test "Slave expired keys is loaded when restarted: appendonly=$aof" {
+ set master_id [find_non_empty_master]
+ set replica_id [get_one_of_my_replica $master_id]
+
+ set master_dbsize [R $master_id dbsize]
+ set slave_dbsize [R $replica_id dbsize]
+ assert_equal $master_dbsize $slave_dbsize
+
+ set data_ttl 5
+ cluster_write_keys_with_expire $master_id $data_ttl
+ after 100
+ set replica_dbsize_1 [R $replica_id dbsize]
+ assert {$replica_dbsize_1 > $slave_dbsize}
+
+ R $replica_id config set appendonly $aof
+ R $replica_id config rewrite
+
+ set start_time [clock seconds]
+ set end_time [expr $start_time+$data_ttl+2]
+ R $replica_id save
+ set replica_dbsize_2 [R $replica_id dbsize]
+ assert {$replica_dbsize_2 > $slave_dbsize}
+ kill_instance redis $replica_id
+
+ set master_port [get_instance_attrib redis $master_id port]
+ exec ../../../src/redis-cli -h 127.0.0.1 -p $master_port debug sleep [expr $data_ttl+3] > /dev/null &
+
+ while {[clock seconds] <= $end_time} {
+ #wait for $data_ttl seconds
+ }
+ restart_instance redis $replica_id
+
+ wait_for_condition 200 50 {
+ [R $replica_id ping] eq {PONG}
+ } else {
+ fail "replica #$replica_id not started"
+ }
+
+ set replica_dbsize_3 [R $replica_id dbsize]
+ assert {$replica_dbsize_3 > $slave_dbsize}
+ }
+}
+
+test_slave_load_expired_keys no
+after 5000
+test_slave_load_expired_keys yes
diff --git a/tests/integration/psync2.tcl b/tests/integration/psync2.tcl
index d1212b640..333736ffa 100644
--- a/tests/integration/psync2.tcl
+++ b/tests/integration/psync2.tcl
@@ -114,6 +114,27 @@ start_server {} {
}
}
+ # wait for all the slaves to be in sync with the master
+ set master_ofs [status $R($master_id) master_repl_offset]
+ wait_for_condition 500 100 {
+ $master_ofs == [status $R(0) master_repl_offset] &&
+ $master_ofs == [status $R(1) master_repl_offset] &&
+ $master_ofs == [status $R(2) master_repl_offset] &&
+ $master_ofs == [status $R(3) master_repl_offset] &&
+ $master_ofs == [status $R(4) master_repl_offset]
+ } else {
+ if {$debug_msg} {
+ for {set j 0} {$j < 5} {incr j} {
+ puts "$j: sync_full: [status $R($j) sync_full]"
+ puts "$j: id1 : [status $R($j) master_replid]:[status $R($j) master_repl_offset]"
+ puts "$j: id2 : [status $R($j) master_replid2]:[status $R($j) second_repl_offset]"
+ puts "$j: backlog : firstbyte=[status $R($j) repl_backlog_first_byte_offset] len=[status $R($j) repl_backlog_histlen]"
+ puts "---"
+ }
+ }
+ fail "Slaves are not in sync with the master after too long time."
+ }
+
# Put down the old master so that it cannot generate more
# replication stream, this way in the next master switch, the time at
# which we move slaves away is not important, each will have full
diff --git a/tests/support/server.tcl b/tests/support/server.tcl
index d086366dc..400017c5f 100644
--- a/tests/support/server.tcl
+++ b/tests/support/server.tcl
@@ -159,9 +159,12 @@ proc start_server {options {code undefined}} {
if {$::external} {
if {[llength $::servers] == 0} {
set srv {}
+ # In test_server_main(tests/test_helper.tcl:215~218), increase the value of start_port
+ # and assign it to ::port through the `--port` option, so we need to reduce it.
+ set baseport [expr {$::port-100}]
dict set srv "host" $::host
- dict set srv "port" $::port
- set client [redis $::host $::port 0 $::tls]
+ dict set srv "port" $baseport
+ set client [redis $::host $baseport 0 $::tls]
dict set srv "client" $client
$client select 9
diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl
index fa5579669..4dbead193 100644
--- a/tests/test_helper.tcl
+++ b/tests/test_helper.tcl
@@ -505,6 +505,9 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
} elseif {$opt eq {--host}} {
set ::external 1
set ::host $arg
+ # If we use an external server, we can only set numclients to 1,
+ # otherwise the port will be miscalculated.
+ set ::numclients 1
incr j
} elseif {$opt eq {--port}} {
set ::port $arg
diff --git a/tests/unit/acl.tcl b/tests/unit/acl.tcl
index fc1664a75..85c9b81a9 100644
--- a/tests/unit/acl.tcl
+++ b/tests/unit/acl.tcl
@@ -248,4 +248,11 @@ start_server {tags {"acl"}} {
r AUTH default ""
assert {[llength [r ACL LOG]] == 5}
}
+
+ test {When default user is off, new connections are not authenticated} {
+ r ACL setuser default off
+ catch {set rd1 [redis_deferring_client]} e
+ r ACL setuser default on
+ set e
+ } {*NOAUTH*}
}