Commit d0854927 authored by Oran Agra's avatar Oran Agra
Browse files

Merge branch 'unstable' into 6.2

parents ec2d1807 b57d0eb4
...@@ -58,3 +58,14 @@ jobs: ...@@ -58,3 +58,14 @@ jobs:
run: | run: |
yum -y install gcc make yum -y install gcc make
make REDIS_CFLAGS='-Werror' make REDIS_CFLAGS='-Werror'
build-freebsd:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: make
uses: vmactions/freebsd-vm@v0.1.0
with:
usesh: true
prepare: pkg install -y gmake
run: gmake
...@@ -99,6 +99,23 @@ jobs: ...@@ -99,6 +99,23 @@ jobs:
./runtest-cluster --tls ./runtest-cluster --tls
./runtest-cluster ./runtest-cluster
test-ubuntu-io-threads:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: |
make
- name: test
run: |
sudo apt-get install tcl8.5 tcl-tls
./runtest --config io-threads 4 --config io-threads-do-reads yes --accurate --verbose --tags network
- name: cluster tests
run: |
./runtest-cluster --config io-threads 4 --config io-threads-do-reads yes
test-valgrind: test-valgrind:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'redis/redis' if: github.repository == 'redis/redis'
...@@ -186,3 +203,20 @@ jobs: ...@@ -186,3 +203,20 @@ jobs:
- name: cluster tests - name: cluster tests
run: ./runtest-cluster run: ./runtest-cluster
test-freebsd:
runs-on: macos-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: test
uses: vmactions/freebsd-vm@v0.1.0
with:
usesh: true
prepare: pkg install -y gmake lang/tcl85
run: |
gmake
./runtest --accurate --verbose --no-latency
MAKE=gmake ./runtest-moduleapi --verbose
./runtest-sentinel
./runtest-cluster
...@@ -325,31 +325,52 @@ databases 16 ...@@ -325,31 +325,52 @@ databases 16
# ASCII art logo in startup logs by setting the following option to yes. # ASCII art logo in startup logs by setting the following option to yes.
always-show-logo no always-show-logo no
# By default, Redis modifies the process title (as seen in 'top' and 'ps') to
# provide some runtime information. It is possible to disable this and leave
# the process name as executed by setting the following to no.
set-proc-title yes
# When changing the process title, Redis uses the following template to construct
# the modified title.
#
# Template variables are specified in curly brackets. The following variables are
# supported:
#
# {title} Name of process as executed if parent, or type of child process.
# {listen-addr} Bind address or '*' followed by TCP or TLS port listening on, or
# Unix socket if only that's available.
# {server-mode} Special mode, i.e. "[sentinel]" or "[cluster]".
# {port} TCP port listening on, or 0.
# {tls-port} TLS port listening on, or 0.
# {unixsocket} Unix domain socket listening on, or "".
# {config-file} Name of configuration file used.
#
proc-title-template "{title} {listen-addr} {server-mode}"
################################ SNAPSHOTTING ################################ ################################ SNAPSHOTTING ################################
# Save the DB to disk.
# #
# Save the DB on disk: # save <seconds> <changes>
# #
# save <seconds> <changes> # Redis will save the DB if both the given number of seconds and the given
# number of write operations against the DB occurred.
# #
# Will save the DB if both the given number of seconds and the given # Snapshotting can be completely disabled with a single empty string argument
# number of write operations against the DB occurred. # as in following example:
# #
# In the example below the behavior will be to save: # save ""
# after 900 sec (15 min) if at least 1 key changed
# after 300 sec (5 min) if at least 10 keys changed
# after 60 sec if at least 10000 keys changed
# #
# Note: you can disable saving completely by commenting out all "save" lines. # Unless specified otherwise, by default Redis will save the DB:
# * After 3600 seconds (an hour) if at least 1 key changed
# * After 300 seconds (5 minutes) if at least 100 keys changed
# * After 60 seconds if at least 10000 keys changed
# #
# It is also possible to remove all the previously configured save # You can set these explicitly by uncommenting the three following lines.
# points by adding a save directive with a single empty string argument
# like in the following example:
# #
# save "" # save 3600 1
# save 300 100
save 900 1 # save 60 10000
save 300 10
save 60 10000
# By default Redis will stop accepting writes if RDB snapshots are enabled # By default Redis will stop accepting writes if RDB snapshots are enabled
# (at least one save point) and the latest background save failed. # (at least one save point) and the latest background save failed.
...@@ -1089,6 +1110,13 @@ replica-lazy-flush no ...@@ -1089,6 +1110,13 @@ replica-lazy-flush no
lazyfree-lazy-user-del no lazyfree-lazy-user-del no
# FLUSHDB, FLUSHALL, and SCRIPT FLUSH support both asynchronous and synchronous
# deletion, which can be controlled by passing the [SYNC|ASYNC] flags into the
# commands. When neither flag is passed, this directive will be used to determine
# if the data should be deleted asynchronously.
lazyfree-lazy-user-flush no
################################ THREADED I/O ################################# ################################ THREADED I/O #################################
# Redis is mostly single threaded, however there are certain threaded # Redis is mostly single threaded, however there are certain threaded
......
...@@ -23,6 +23,7 @@ $TCLSH tests/test_helper.tcl \ ...@@ -23,6 +23,7 @@ $TCLSH tests/test_helper.tcl \
--single unit/moduleapi/hooks \ --single unit/moduleapi/hooks \
--single unit/moduleapi/misc \ --single unit/moduleapi/misc \
--single unit/moduleapi/blockonkeys \ --single unit/moduleapi/blockonkeys \
--single unit/moduleapi/blockonbackground \
--single unit/moduleapi/scan \ --single unit/moduleapi/scan \
--single unit/moduleapi/datatype \ --single unit/moduleapi/datatype \
--single unit/moduleapi/auth \ --single unit/moduleapi/auth \
...@@ -31,4 +32,5 @@ $TCLSH tests/test_helper.tcl \ ...@@ -31,4 +32,5 @@ $TCLSH tests/test_helper.tcl \
--single unit/moduleapi/getkeys \ --single unit/moduleapi/getkeys \
--single unit/moduleapi/test_lazyfree \ --single unit/moduleapi/test_lazyfree \
--single unit/moduleapi/defrag \ --single unit/moduleapi/defrag \
--single unit/moduleapi/stream \
"${@}" "${@}"
...@@ -321,3 +321,21 @@ sentinel deny-scripts-reconfig yes ...@@ -321,3 +321,21 @@ sentinel deny-scripts-reconfig yes
# is possible to just rename a command to itself: # is possible to just rename a command to itself:
# #
# SENTINEL rename-command mymaster CONFIG CONFIG # SENTINEL rename-command mymaster CONFIG CONFIG
# HOSTNAMES SUPPORT
#
# Normally Sentinel uses only IP addresses and requires SENTINEL MONITOR
# to specify an IP address. Also, it requires the Redis replica-announce-ip
# keyword to specify only IP addresses.
#
# You may enable hostnames support by enabling resolve-hostnames. Note
# that you must make sure your DNS is configured properly and that DNS
# resolution does not introduce very long delays.
#
SENTINEL resolve-hostnames no
# When resolve-hostnames is enabled, Sentinel still uses IP addresses
# when exposing instances to users, configuration files, etc. If you want
# to retain the hostnames when announced, enable announce-hostnames below.
#
SENTINEL announce-hostnames no
...@@ -1024,8 +1024,8 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) { ...@@ -1024,8 +1024,8 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
/* Return a description of the error that occurred in ACLSetUser() according to /* Return a description of the error that occurred in ACLSetUser() according to
* the errno value set by the function on error. */ * the errno value set by the function on error. */
char *ACLSetUserStringError(void) { const char *ACLSetUserStringError(void) {
char *errmsg = "Wrong format"; const char *errmsg = "Wrong format";
if (errno == ENOENT) if (errno == ENOENT)
errmsg = "Unknown command or category name in ACL"; errmsg = "Unknown command or category name in ACL";
else if (errno == EINVAL) else if (errno == EINVAL)
...@@ -1454,7 +1454,7 @@ int ACLLoadConfiguredUsers(void) { ...@@ -1454,7 +1454,7 @@ int ACLLoadConfiguredUsers(void) {
/* Load every rule defined for this user. */ /* Load every rule defined for this user. */
for (int j = 1; aclrules[j]; j++) { for (int j = 1; aclrules[j]; j++) {
if (ACLSetUser(u,aclrules[j],sdslen(aclrules[j])) != C_OK) { if (ACLSetUser(u,aclrules[j],sdslen(aclrules[j])) != C_OK) {
char *errmsg = ACLSetUserStringError(); const char *errmsg = ACLSetUserStringError();
serverLog(LL_WARNING,"Error loading ACL rule '%s' for " serverLog(LL_WARNING,"Error loading ACL rule '%s' for "
"the user named '%s': %s", "the user named '%s': %s",
aclrules[j],aclrules[0],errmsg); aclrules[j],aclrules[0],errmsg);
...@@ -1587,7 +1587,7 @@ sds ACLLoadFromFile(const char *filename) { ...@@ -1587,7 +1587,7 @@ sds ACLLoadFromFile(const char *filename) {
for (j = 2; j < argc; j++) { for (j = 2; j < argc; j++) {
argv[j] = sdstrim(argv[j],"\t\r\n"); argv[j] = sdstrim(argv[j],"\t\r\n");
if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) != C_OK) { if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) != C_OK) {
char *errmsg = ACLSetUserStringError(); const char *errmsg = ACLSetUserStringError();
errors = sdscatprintf(errors, errors = sdscatprintf(errors,
"%s:%d: %s. ", "%s:%d: %s. ",
server.acl_filename, linenum, errmsg); server.acl_filename, linenum, errmsg);
...@@ -1908,7 +1908,7 @@ void aclCommand(client *c) { ...@@ -1908,7 +1908,7 @@ void aclCommand(client *c) {
for (int j = 3; j < c->argc; j++) { for (int j = 3; j < c->argc; j++) {
if (ACLSetUser(tempu,c->argv[j]->ptr,sdslen(c->argv[j]->ptr)) != C_OK) { if (ACLSetUser(tempu,c->argv[j]->ptr,sdslen(c->argv[j]->ptr)) != C_OK) {
char *errmsg = ACLSetUserStringError(); const char *errmsg = ACLSetUserStringError();
addReplyErrorFormat(c, addReplyErrorFormat(c,
"Error in ACL SETUSER modifier '%s': %s", "Error in ACL SETUSER modifier '%s': %s",
(char*)c->argv[j]->ptr, errmsg); (char*)c->argv[j]->ptr, errmsg);
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
*/ */
#include "ae.h" #include "ae.h"
#include "anet.h"
#include <stdio.h> #include <stdio.h>
#include <sys/time.h> #include <sys/time.h>
......
...@@ -51,6 +51,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) { ...@@ -51,6 +51,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
zfree(state); zfree(state);
return -1; return -1;
} }
anetCloexec(state->epfd);
eventLoop->apidata = state; eventLoop->apidata = state;
return 0; return 0;
} }
......
...@@ -82,6 +82,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) { ...@@ -82,6 +82,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
zfree(state); zfree(state);
return -1; return -1;
} }
anetCloexec(state->portfd);
state->npending = 0; state->npending = 0;
......
...@@ -53,6 +53,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) { ...@@ -53,6 +53,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
zfree(state); zfree(state);
return -1; return -1;
} }
anetCloexec(state->kqfd);
eventLoop->apidata = state; eventLoop->apidata = state;
return 0; return 0;
} }
......
...@@ -69,6 +69,11 @@ int anetSetBlock(char *err, int fd, int non_block) { ...@@ -69,6 +69,11 @@ int anetSetBlock(char *err, int fd, int non_block) {
return ANET_ERR; return ANET_ERR;
} }
/* Check if this flag has been set or unset, if so,
* then there is no need to call fcntl to set/unset it again. */
if (!!(flags & O_NONBLOCK) == !!non_block)
return ANET_OK;
if (non_block) if (non_block)
flags |= O_NONBLOCK; flags |= O_NONBLOCK;
else else
...@@ -89,6 +94,29 @@ int anetBlock(char *err, int fd) { ...@@ -89,6 +94,29 @@ int anetBlock(char *err, int fd) {
return anetSetBlock(err,fd,0); return anetSetBlock(err,fd,0);
} }
/* Enable the FD_CLOEXEC on the given fd to avoid fd leaks.
* This function should be invoked for fd's on specific places
* where fork + execve system calls are called. */
int anetCloexec(int fd) {
int r;
int flags;
do {
r = fcntl(fd, F_GETFD);
} while (r == -1 && errno == EINTR);
if (r == -1 || (r & FD_CLOEXEC))
return r;
flags = r | FD_CLOEXEC;
do {
r = fcntl(fd, F_SETFD, flags);
} while (r == -1 && errno == EINTR);
return r;
}
/* Set TCP keep alive option to detect dead peers. The interval option /* Set TCP keep alive option to detect dead peers. The interval option
* is only used for Linux as we are using Linux-specific APIs to set * is only used for Linux as we are using Linux-specific APIs to set
* the probe send time, interval, and count. */ * the probe send time, interval, and count. */
...@@ -207,14 +235,13 @@ int anetRecvTimeout(char *err, int fd, long long ms) { ...@@ -207,14 +235,13 @@ int anetRecvTimeout(char *err, int fd, long long ms) {
return ANET_OK; return ANET_OK;
} }
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to /* Resolve the hostname "host" and set the string representation of the
* do the actual work. It resolves the hostname "host" and set the string * IP address into the buffer pointed by "ipbuf".
* representation of the IP address into the buffer pointed by "ipbuf".
* *
* If flags is set to ANET_IP_ONLY the function only resolves hostnames * If flags is set to ANET_IP_ONLY the function only resolves hostnames
* that are actually already IPv4 or IPv6 addresses. This turns the function * that are actually already IPv4 or IPv6 addresses. This turns the function
* into a validating / normalizing function. */ * into a validating / normalizing function. */
int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
int flags) int flags)
{ {
struct addrinfo hints, *info; struct addrinfo hints, *info;
...@@ -241,14 +268,6 @@ int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, ...@@ -241,14 +268,6 @@ int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
return ANET_OK; return ANET_OK;
} }
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_NONE);
}
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_IP_ONLY);
}
static int anetSetReuseAddr(char *err, int fd) { static int anetSetReuseAddr(char *err, int fd) {
int yes = 1; int yes = 1;
/* Make sure connection-intensive things like the redis benchmark /* Make sure connection-intensive things like the redis benchmark
......
...@@ -60,8 +60,7 @@ int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, ...@@ -60,8 +60,7 @@ int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
int anetUnixConnect(char *err, const char *path); int anetUnixConnect(char *err, const char *path);
int anetUnixNonBlockConnect(char *err, const char *path); int anetUnixNonBlockConnect(char *err, const char *path);
int anetRead(int fd, char *buf, int count); int anetRead(int fd, char *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len); int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags);
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
int anetTcpServer(char *err, int port, char *bindaddr, int backlog); int anetTcpServer(char *err, int port, char *bindaddr, int backlog);
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog); int anetTcp6Server(char *err, int port, char *bindaddr, int backlog);
int anetUnixServer(char *err, char *path, mode_t perm, int backlog); int anetUnixServer(char *err, char *path, mode_t perm, int backlog);
...@@ -70,6 +69,7 @@ int anetUnixAccept(char *err, int serversock); ...@@ -70,6 +69,7 @@ int anetUnixAccept(char *err, int serversock);
int anetWrite(int fd, char *buf, int count); int anetWrite(int fd, char *buf, int count);
int anetNonBlock(char *err, int fd); int anetNonBlock(char *err, int fd);
int anetBlock(char *err, int fd); int anetBlock(char *err, int fd);
int anetCloexec(int fd);
int anetEnableTcpNoDelay(char *err, int fd); int anetEnableTcpNoDelay(char *err, int fd);
int anetDisableTcpNoDelay(char *err, int fd); int anetDisableTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd); int anetTcpKeepAlive(char *err, int fd);
......
...@@ -235,6 +235,8 @@ void stopAppendOnly(void) { ...@@ -235,6 +235,8 @@ void stopAppendOnly(void) {
serverAssert(server.aof_state != AOF_OFF); serverAssert(server.aof_state != AOF_OFF);
flushAppendOnlyFile(1); flushAppendOnlyFile(1);
redis_fsync(server.aof_fd); redis_fsync(server.aof_fd);
server.aof_fsync_offset = server.aof_current_size;
server.aof_last_fsync = server.unixtime;
close(server.aof_fd); close(server.aof_fd);
server.aof_fd = -1; server.aof_fd = -1;
...@@ -242,6 +244,8 @@ void stopAppendOnly(void) { ...@@ -242,6 +244,8 @@ void stopAppendOnly(void) {
server.aof_state = AOF_OFF; server.aof_state = AOF_OFF;
server.aof_rewrite_scheduled = 0; server.aof_rewrite_scheduled = 0;
killAppendOnlyChild(); killAppendOnlyChild();
sdsfree(server.aof_buf);
server.aof_buf = sdsempty();
} }
/* Called when the user switches from "appendonly no" to "appendonly yes" /* Called when the user switches from "appendonly no" to "appendonly yes"
...@@ -285,6 +289,12 @@ int startAppendOnly(void) { ...@@ -285,6 +289,12 @@ int startAppendOnly(void) {
server.aof_state = AOF_WAIT_REWRITE; server.aof_state = AOF_WAIT_REWRITE;
server.aof_last_fsync = server.unixtime; server.aof_last_fsync = server.unixtime;
server.aof_fd = newfd; server.aof_fd = newfd;
/* If AOF was in error state, we just ignore it and log the event. */
if (server.aof_last_write_status == C_ERR) {
serverLog(LL_WARNING,"AOF reopen, just ignore the last error.");
server.aof_last_write_status = C_OK;
}
return C_OK; return C_OK;
} }
...@@ -451,10 +461,11 @@ void flushAppendOnlyFile(int force) { ...@@ -451,10 +461,11 @@ void flushAppendOnlyFile(int force) {
/* Handle the AOF write error. */ /* Handle the AOF write error. */
if (server.aof_fsync == AOF_FSYNC_ALWAYS) { if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
/* We can't recover when the fsync policy is ALWAYS since the /* We can't recover when the fsync policy is ALWAYS since the reply
* reply for the client is already in the output buffers, and we * for the client is already in the output buffers (both writes and
* have the contract with the user that on acknowledged write data * reads), and the changes to the db can't be rolled back. Since we
* is synced on disk. */ * have a contract with the user that on acknowledged or observed
* writes are is synced on disk, we must exit. */
serverLog(LL_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting..."); serverLog(LL_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
exit(1); exit(1);
} else { } else {
...@@ -502,7 +513,14 @@ try_fsync: ...@@ -502,7 +513,14 @@ try_fsync:
/* redis_fsync is defined as fdatasync() for Linux in order to avoid /* redis_fsync is defined as fdatasync() for Linux in order to avoid
* flushing metadata. */ * flushing metadata. */
latencyStartMonitor(latency); latencyStartMonitor(latency);
redis_fsync(server.aof_fd); /* Let's try to get this data on the disk */ /* Let's try to get this data on the disk. To guarantee data safe when
* the AOF fsync policy is 'always', we should exit if failed to fsync
* AOF (see comment next to the exit(1) after write error above). */
if (redis_fsync(server.aof_fd) == -1) {
serverLog(LL_WARNING,"Can't persist AOF for fsync error when the "
"AOF fsync policy is 'always': %s. Exiting...", strerror(errno));
exit(1);
}
latencyEndMonitor(latency); latencyEndMonitor(latency);
latencyAddSampleIfNeeded("aof-fsync-always",latency); latencyAddSampleIfNeeded("aof-fsync-always",latency);
server.aof_fsync_offset = server.aof_current_size; server.aof_fsync_offset = server.aof_current_size;
...@@ -581,8 +599,6 @@ sds catAppendOnlyExpireAtCommand(sds buf, struct redisCommand *cmd, robj *key, r ...@@ -581,8 +599,6 @@ sds catAppendOnlyExpireAtCommand(sds buf, struct redisCommand *cmd, robj *key, r
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) { void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
sds buf = sdsempty(); sds buf = sdsempty();
robj *tmpargv[3];
/* The DB this command was targeting is not the same as the last command /* The DB this command was targeting is not the same as the last command
* we appended. To issue a SELECT command is needed. */ * we appended. To issue a SELECT command is needed. */
if (dictid != server.aof_selected_db) { if (dictid != server.aof_selected_db) {
...@@ -598,32 +614,31 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a ...@@ -598,32 +614,31 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
cmd->proc == expireatCommand) { cmd->proc == expireatCommand) {
/* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */ /* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]); buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
} else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
/* Translate SETEX/PSETEX to SET and PEXPIREAT */
tmpargv[0] = createStringObject("SET",3);
tmpargv[1] = argv[1];
tmpargv[2] = argv[3];
buf = catAppendOnlyGenericCommand(buf,3,tmpargv);
decrRefCount(tmpargv[0]);
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
} else if (cmd->proc == setCommand && argc > 3) { } else if (cmd->proc == setCommand && argc > 3) {
int i; robj *pxarg = NULL;
robj *exarg = NULL, *pxarg = NULL; /* When SET is used with EX/PX argument setGenericCommand propagates them with PX millisecond argument.
for (i = 3; i < argc; i ++) { * So since the command arguments are re-written there, we can rely here on the index of PX being 3. */
if (!strcasecmp(argv[i]->ptr, "ex")) exarg = argv[i+1]; if (!strcasecmp(argv[3]->ptr, "px")) {
if (!strcasecmp(argv[i]->ptr, "px")) pxarg = argv[i+1]; pxarg = argv[4];
} }
serverAssert(!(exarg && pxarg)); /* For AOF we convert SET key value relative time in milliseconds to SET key value absolute time in
* millisecond. Whenever the condition is true it implies that original SET has been transformed
if (exarg || pxarg) { * to SET PX with millisecond time argument so we do not need to worry about unit here.*/
/* Translate SET [EX seconds][PX milliseconds] to SET and PEXPIREAT */ if (pxarg) {
buf = catAppendOnlyGenericCommand(buf,3,argv); robj *millisecond = getDecodedObject(pxarg);
if (exarg) long long when = strtoll(millisecond->ptr,NULL,10);
buf = catAppendOnlyExpireAtCommand(buf,server.expireCommand,argv[1], when += mstime();
exarg);
if (pxarg) decrRefCount(millisecond);
buf = catAppendOnlyExpireAtCommand(buf,server.pexpireCommand,argv[1],
pxarg); robj *newargs[5];
newargs[0] = argv[0];
newargs[1] = argv[1];
newargs[2] = argv[2];
newargs[3] = shared.pxat;
newargs[4] = createStringObjectFromLongLong(when);
buf = catAppendOnlyGenericCommand(buf,5,newargs);
decrRefCount(newargs[4]);
} else { } else {
buf = catAppendOnlyGenericCommand(buf,argc,argv); buf = catAppendOnlyGenericCommand(buf,argc,argv);
} }
...@@ -1852,6 +1867,20 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) { ...@@ -1852,6 +1867,20 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
} }
latencyEndMonitor(latency); latencyEndMonitor(latency);
latencyAddSampleIfNeeded("aof-rewrite-diff-write",latency); latencyAddSampleIfNeeded("aof-rewrite-diff-write",latency);
if (server.aof_fsync == AOF_FSYNC_EVERYSEC) {
aof_background_fsync(newfd);
} else if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
latencyStartMonitor(latency);
if (redis_fsync(newfd) == -1) {
serverLog(LL_WARNING,
"Error trying to fsync the parent diff to the rewritten AOF: %s", strerror(errno));
close(newfd);
goto cleanup;
}
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("aof-rewrite-done-fsync",latency);
}
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Residual parent diff successfully flushed to the rewritten AOF (%.2f MB)", (double) aofRewriteBufferSize() / (1024*1024)); "Residual parent diff successfully flushed to the rewritten AOF (%.2f MB)", (double) aofRewriteBufferSize() / (1024*1024));
...@@ -1919,14 +1948,11 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) { ...@@ -1919,14 +1948,11 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
/* AOF enabled, replace the old fd with the new one. */ /* AOF enabled, replace the old fd with the new one. */
oldfd = server.aof_fd; oldfd = server.aof_fd;
server.aof_fd = newfd; server.aof_fd = newfd;
if (server.aof_fsync == AOF_FSYNC_ALWAYS)
redis_fsync(newfd);
else if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
aof_background_fsync(newfd);
server.aof_selected_db = -1; /* Make sure SELECT is re-issued */ server.aof_selected_db = -1; /* Make sure SELECT is re-issued */
aofUpdateCurrentSize(); aofUpdateCurrentSize();
server.aof_rewrite_base_size = server.aof_current_size; server.aof_rewrite_base_size = server.aof_current_size;
server.aof_fsync_offset = server.aof_current_size; server.aof_fsync_offset = server.aof_current_size;
server.aof_last_fsync = server.unixtime;
/* Clear regular AOF buffer since its contents was just written to /* Clear regular AOF buffer since its contents was just written to
* the new AOF from the background rewrite buffer. */ * the new AOF from the background rewrite buffer. */
......
...@@ -61,6 +61,9 @@ ...@@ -61,6 +61,9 @@
*/ */
#include "server.h" #include "server.h"
#include "slowlog.h"
#include "latency.h"
#include "monotonic.h"
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int wherefrom, int whereto); int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int wherefrom, int whereto);
int getListPositionFromObjectOrReply(client *c, robj *arg, int *position); int getListPositionFromObjectOrReply(client *c, robj *arg, int *position);
...@@ -97,6 +100,20 @@ void blockClient(client *c, int btype) { ...@@ -97,6 +100,20 @@ void blockClient(client *c, int btype) {
} }
} }
/* This function is called after a client has finished a blocking operation
* in order to update the total command duration, log the command into
* the Slow log if needed, and log the reply duration event if needed. */
void updateStatsOnUnblock(client *c, long blocked_us, long reply_us){
const ustime_t total_cmd_duration = c->duration + blocked_us + reply_us;
c->lastcmd->microseconds += total_cmd_duration;
/* Log the command into the Slow log if needed. */
if (!(c->lastcmd->flags & CMD_SKIP_SLOWLOG)) {
slowlogPushEntryIfNeeded(c,c->argv,c->argc,total_cmd_duration);
/* Log the reply duration event. */
latencyAddSampleIfNeeded("command-unblocking",reply_us/1000);
}
}
/* This function is called in the beforeSleep() function of the event loop /* This function is called in the beforeSleep() function of the event loop
* in order to process the pending input buffer of clients that were * in order to process the pending input buffer of clients that were
* unblocked after a blocking operation. */ * unblocked after a blocking operation. */
...@@ -264,6 +281,8 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) { ...@@ -264,6 +281,8 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
if (dstkey) incrRefCount(dstkey); if (dstkey) incrRefCount(dstkey);
unblockClient(receiver); unblockClient(receiver);
monotime replyTimer;
elapsedStart(&replyTimer);
if (serveClientBlockedOnList(receiver, if (serveClientBlockedOnList(receiver,
rl->key,dstkey,rl->db,value, rl->key,dstkey,rl->db,value,
wherefrom, whereto) == C_ERR) wherefrom, whereto) == C_ERR)
...@@ -272,6 +291,7 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) { ...@@ -272,6 +291,7 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
* to also undo the POP operation. */ * to also undo the POP operation. */
listTypePush(o,value,wherefrom); listTypePush(o,value,wherefrom);
} }
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
if (dstkey) decrRefCount(dstkey); if (dstkey) decrRefCount(dstkey);
decrRefCount(value); decrRefCount(value);
...@@ -316,7 +336,10 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) { ...@@ -316,7 +336,10 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
receiver->lastcmd->proc == bzpopminCommand) receiver->lastcmd->proc == bzpopminCommand)
? ZSET_MIN : ZSET_MAX; ? ZSET_MIN : ZSET_MAX;
unblockClient(receiver); unblockClient(receiver);
monotime replyTimer;
elapsedStart(&replyTimer);
genericZpopCommand(receiver,&rl->key,1,where,1,NULL); genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
zcard--; zcard--;
/* Replicate the command. */ /* Replicate the command. */
...@@ -406,6 +429,8 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) { ...@@ -406,6 +429,8 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
} }
} }
monotime replyTimer;
elapsedStart(&replyTimer);
/* Emit the two elements sub-array consisting of /* Emit the two elements sub-array consisting of
* the name of the stream and the data we * the name of the stream and the data we
* extracted from it. Wrapped in a single-item * extracted from it. Wrapped in a single-item
...@@ -425,6 +450,7 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) { ...@@ -425,6 +450,7 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
streamReplyWithRange(receiver,s,&start,NULL, streamReplyWithRange(receiver,s,&start,NULL,
receiver->bpop.xread_count, receiver->bpop.xread_count,
0, group, consumer, noack, &pi); 0, group, consumer, noack, &pi);
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
/* Note that after we unblock the client, 'gt' /* Note that after we unblock the client, 'gt'
* and other receiver->bpop stuff are no longer * and other receiver->bpop stuff are no longer
...@@ -471,7 +497,10 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) { ...@@ -471,7 +497,10 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
* different modules with different triggers to consider if a key * different modules with different triggers to consider if a key
* is ready or not. This means we can't exit the loop but need * is ready or not. This means we can't exit the loop but need
* to continue after the first failure. */ * to continue after the first failure. */
monotime replyTimer;
elapsedStart(&replyTimer);
if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue; if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue;
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
moduleUnblockClient(receiver); moduleUnblockClient(receiver);
} }
...@@ -684,10 +713,20 @@ static int getBlockedTypeByType(int type) { ...@@ -684,10 +713,20 @@ static int getBlockedTypeByType(int type) {
void signalKeyAsReady(redisDb *db, robj *key, int type) { void signalKeyAsReady(redisDb *db, robj *key, int type) {
readyList *rl; readyList *rl;
/* If no clients are blocked on this type, just return */ /* Quick returns. */
int btype = getBlockedTypeByType(type); int btype = getBlockedTypeByType(type);
if (btype == BLOCKED_NONE || !server.blocked_clients_by_type[btype]) if (btype == BLOCKED_NONE) {
/* The type can never block. */
return; return;
}
if (!server.blocked_clients_by_type[btype] &&
!server.blocked_clients_by_type[BLOCKED_MODULE]) {
/* No clients block on this type. Note: Blocked modules are represented
* by BLOCKED_MODULE, even if the intention is to wake up by normal
* types (list, zset, stream), so we need to check that there are no
* blocked modules before we do a quick return here. */
return;
}
/* No clients blocking for this key? No need to queue it. */ /* No clients blocking for this key? No need to queue it. */
if (dictFind(db->blocking_keys,key) == NULL) return; if (dictFind(db->blocking_keys,key) == NULL) return;
......
...@@ -398,7 +398,7 @@ int clusterLockConfig(char *filename) { ...@@ -398,7 +398,7 @@ int clusterLockConfig(char *filename) {
/* To lock it, we need to open the file in a way it is created if /* To lock it, we need to open the file in a way it is created if
* it does not exist, otherwise there is a race condition with other * it does not exist, otherwise there is a race condition with other
* processes. */ * processes. */
int fd = open(filename,O_WRONLY|O_CREAT,0644); int fd = open(filename,O_WRONLY|O_CREAT|O_CLOEXEC,0644);
if (fd == -1) { if (fd == -1) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Can't open %s in order to acquire a lock: %s", "Can't open %s in order to acquire a lock: %s",
...@@ -509,8 +509,7 @@ void clusterInit(void) { ...@@ -509,8 +509,7 @@ void clusterInit(void) {
serverLog(LL_WARNING, "Redis port number too high. " serverLog(LL_WARNING, "Redis port number too high. "
"Cluster communication port is 10,000 port " "Cluster communication port is 10,000 port "
"numbers higher than your Redis port. " "numbers higher than your Redis port. "
"Your Redis port number must be " "Your Redis port number must be 55535 or less.");
"lower than 55535.");
exit(1); exit(1);
} }
if (listenToPort(port+CLUSTER_PORT_INCR, if (listenToPort(port+CLUSTER_PORT_INCR,
...@@ -779,6 +778,7 @@ clusterNode *createClusterNode(char *nodename, int flags) { ...@@ -779,6 +778,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
node->configEpoch = 0; node->configEpoch = 0;
node->flags = flags; node->flags = flags;
memset(node->slots,0,sizeof(node->slots)); memset(node->slots,0,sizeof(node->slots));
node->slots_info = NULL;
node->numslots = 0; node->numslots = 0;
node->numslaves = 0; node->numslaves = 0;
node->slaves = NULL; node->slaves = NULL;
...@@ -4144,8 +4144,8 @@ sds clusterGenNodeDescription(clusterNode *node) { ...@@ -4144,8 +4144,8 @@ sds clusterGenNodeDescription(clusterNode *node) {
sds ci; sds ci;
/* Node coordinates */ /* Node coordinates */
ci = sdscatprintf(sdsempty(),"%.40s %s:%d@%d ", ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN);
node->name, ci = sdscatfmt(ci," %s:%i@%i ",
node->ip, node->ip,
node->port, node->port,
node->cport); node->cport);
...@@ -4154,40 +4154,46 @@ sds clusterGenNodeDescription(clusterNode *node) { ...@@ -4154,40 +4154,46 @@ sds clusterGenNodeDescription(clusterNode *node) {
ci = representClusterNodeFlags(ci, node->flags); ci = representClusterNodeFlags(ci, node->flags);
/* Slave of... or just "-" */ /* Slave of... or just "-" */
ci = sdscatlen(ci," ",1);
if (node->slaveof) if (node->slaveof)
ci = sdscatprintf(ci," %.40s ",node->slaveof->name); ci = sdscatlen(ci,node->slaveof->name,CLUSTER_NAMELEN);
else else
ci = sdscatlen(ci," - ",3); ci = sdscatlen(ci,"-",1);
unsigned long long nodeEpoch = node->configEpoch; unsigned long long nodeEpoch = node->configEpoch;
if (nodeIsSlave(node) && node->slaveof) { if (nodeIsSlave(node) && node->slaveof) {
nodeEpoch = node->slaveof->configEpoch; nodeEpoch = node->slaveof->configEpoch;
} }
/* Latency from the POV of this node, config epoch, link status */ /* Latency from the POV of this node, config epoch, link status */
ci = sdscatprintf(ci,"%lld %lld %llu %s", ci = sdscatfmt(ci," %I %I %U %s",
(long long) node->ping_sent, (long long) node->ping_sent,
(long long) node->pong_received, (long long) node->pong_received,
nodeEpoch, nodeEpoch,
(node->link || node->flags & CLUSTER_NODE_MYSELF) ? (node->link || node->flags & CLUSTER_NODE_MYSELF) ?
"connected" : "disconnected"); "connected" : "disconnected");
/* Slots served by this instance */ /* Slots served by this instance. If we already have slots info,
start = -1; * append it diretly, otherwise, generate slots only if it has. */
for (j = 0; j < CLUSTER_SLOTS; j++) { if (node->slots_info) {
int bit; ci = sdscatsds(ci, node->slots_info);
} else if (node->numslots > 0) {
start = -1;
for (j = 0; j < CLUSTER_SLOTS; j++) {
int bit;
if ((bit = clusterNodeGetSlotBit(node,j)) != 0) { if ((bit = clusterNodeGetSlotBit(node,j)) != 0) {
if (start == -1) start = j; if (start == -1) start = j;
} }
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) { if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
if (bit && j == CLUSTER_SLOTS-1) j++; if (bit && j == CLUSTER_SLOTS-1) j++;
if (start == j-1) { if (start == j-1) {
ci = sdscatprintf(ci," %d",start); ci = sdscatfmt(ci," %i",start);
} else { } else {
ci = sdscatprintf(ci," %d-%d",start,j-1); ci = sdscatfmt(ci," %i-%i",start,j-1);
}
start = -1;
} }
start = -1;
} }
} }
...@@ -4208,6 +4214,41 @@ sds clusterGenNodeDescription(clusterNode *node) { ...@@ -4208,6 +4214,41 @@ sds clusterGenNodeDescription(clusterNode *node) {
return ci; return ci;
} }
/* Generate the slot topology for all nodes and store the string representation
* in the slots_info struct on the node. This is used to improve the efficiency
* of clusterGenNodesDescription() because it removes looping of the slot space
* for generating the slot info for each node individually. */
void clusterGenNodesSlotsInfo(int filter) {
clusterNode *n = NULL;
int start = -1;
for (int i = 0; i <= CLUSTER_SLOTS; i++) {
/* Find start node and slot id. */
if (n == NULL) {
if (i == CLUSTER_SLOTS) break;
n = server.cluster->slots[i];
start = i;
continue;
}
/* Generate slots info when occur different node with start
* or end of slot. */
if (i == CLUSTER_SLOTS || n != server.cluster->slots[i]) {
if (!(n->flags & filter)) {
if (n->slots_info == NULL) n->slots_info = sdsempty();
if (start == i-1) {
n->slots_info = sdscatfmt(n->slots_info," %i",start);
} else {
n->slots_info = sdscatfmt(n->slots_info," %i-%i",start,i-1);
}
}
if (i == CLUSTER_SLOTS) break;
n = server.cluster->slots[i];
start = i;
}
}
}
/* Generate a csv-alike representation of the nodes we are aware of, /* Generate a csv-alike representation of the nodes we are aware of,
* including the "myself" node, and return an SDS string containing the * including the "myself" node, and return an SDS string containing the
* representation (it is up to the caller to free it). * representation (it is up to the caller to free it).
...@@ -4225,6 +4266,9 @@ sds clusterGenNodesDescription(int filter) { ...@@ -4225,6 +4266,9 @@ sds clusterGenNodesDescription(int filter) {
dictIterator *di; dictIterator *di;
dictEntry *de; dictEntry *de;
/* Generate all nodes slots info firstly. */
clusterGenNodesSlotsInfo(filter);
di = dictGetSafeIterator(server.cluster->nodes); di = dictGetSafeIterator(server.cluster->nodes);
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
clusterNode *node = dictGetVal(de); clusterNode *node = dictGetVal(de);
...@@ -4234,6 +4278,12 @@ sds clusterGenNodesDescription(int filter) { ...@@ -4234,6 +4278,12 @@ sds clusterGenNodesDescription(int filter) {
ci = sdscatsds(ci,ni); ci = sdscatsds(ci,ni);
sdsfree(ni); sdsfree(ni);
ci = sdscatlen(ci,"\n",1); ci = sdscatlen(ci,"\n",1);
/* Release slots info. */
if (node->slots_info) {
sdsfree(node->slots_info);
node->slots_info = NULL;
}
} }
dictReleaseIterator(di); dictReleaseIterator(di);
return ci; return ci;
......
...@@ -118,6 +118,7 @@ typedef struct clusterNode { ...@@ -118,6 +118,7 @@ typedef struct clusterNode {
int flags; /* CLUSTER_NODE_... */ int flags; /* CLUSTER_NODE_... */
uint64_t configEpoch; /* Last configEpoch observed for this node */ uint64_t configEpoch; /* Last configEpoch observed for this node */
unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */ unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
sds slots_info; /* Slots info represented by string. */
int numslots; /* Number of slots handled by this node */ int numslots; /* Number of slots handled by this node */
int numslaves; /* Number of slave nodes, if this is a master */ int numslaves; /* Number of slave nodes, if this is a master */
struct clusterNode **slaves; /* pointers to slave nodes */ struct clusterNode **slaves; /* pointers to slave nodes */
......
...@@ -153,15 +153,15 @@ int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT] = { 0, 200, 800 }; ...@@ -153,15 +153,15 @@ int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT] = { 0, 200, 800 };
typedef struct boolConfigData { typedef struct boolConfigData {
int *config; /* The pointer to the server config this value is stored in */ int *config; /* The pointer to the server config this value is stored in */
const int default_value; /* The default value of the config on rewrite */ const int default_value; /* The default value of the config on rewrite */
int (*is_valid_fn)(int val, char **err); /* Optional function to check validity of new value (generic doc above) */ int (*is_valid_fn)(int val, const char **err); /* Optional function to check validity of new value (generic doc above) */
int (*update_fn)(int val, int prev, char **err); /* Optional function to apply new value at runtime (generic doc above) */ int (*update_fn)(int val, int prev, const char **err); /* Optional function to apply new value at runtime (generic doc above) */
} boolConfigData; } boolConfigData;
typedef struct stringConfigData { typedef struct stringConfigData {
char **config; /* Pointer to the server config this value is stored in. */ char **config; /* Pointer to the server config this value is stored in. */
const char *default_value; /* Default value of the config on rewrite. */ const char *default_value; /* Default value of the config on rewrite. */
int (*is_valid_fn)(char* val, char **err); /* Optional function to check validity of new value (generic doc above) */ int (*is_valid_fn)(char* val, const char **err); /* Optional function to check validity of new value (generic doc above) */
int (*update_fn)(char* val, char* prev, char **err); /* Optional function to apply new value at runtime (generic doc above) */ int (*update_fn)(char* val, char* prev, const char **err); /* Optional function to apply new value at runtime (generic doc above) */
int convert_empty_to_null; /* Boolean indicating if empty strings should int convert_empty_to_null; /* Boolean indicating if empty strings should
be stored as a NULL value. */ be stored as a NULL value. */
} stringConfigData; } stringConfigData;
...@@ -169,8 +169,8 @@ typedef struct stringConfigData { ...@@ -169,8 +169,8 @@ typedef struct stringConfigData {
typedef struct sdsConfigData { typedef struct sdsConfigData {
sds *config; /* Pointer to the server config this value is stored in. */ sds *config; /* Pointer to the server config this value is stored in. */
const char *default_value; /* Default value of the config on rewrite. */ const char *default_value; /* Default value of the config on rewrite. */
int (*is_valid_fn)(sds val, char **err); /* Optional function to check validity of new value (generic doc above) */ int (*is_valid_fn)(sds val, const char **err); /* Optional function to check validity of new value (generic doc above) */
int (*update_fn)(sds val, sds prev, char **err); /* Optional function to apply new value at runtime (generic doc above) */ int (*update_fn)(sds val, sds prev, const char **err); /* Optional function to apply new value at runtime (generic doc above) */
int convert_empty_to_null; /* Boolean indicating if empty SDS strings should int convert_empty_to_null; /* Boolean indicating if empty SDS strings should
be stored as a NULL value. */ be stored as a NULL value. */
} sdsConfigData; } sdsConfigData;
...@@ -179,8 +179,8 @@ typedef struct enumConfigData { ...@@ -179,8 +179,8 @@ typedef struct enumConfigData {
int *config; /* The pointer to the server config this value is stored in */ int *config; /* The pointer to the server config this value is stored in */
configEnum *enum_value; /* The underlying enum type this data represents */ configEnum *enum_value; /* The underlying enum type this data represents */
const int default_value; /* The default value of the config on rewrite */ const int default_value; /* The default value of the config on rewrite */
int (*is_valid_fn)(int val, char **err); /* Optional function to check validity of new value (generic doc above) */ int (*is_valid_fn)(int val, const char **err); /* Optional function to check validity of new value (generic doc above) */
int (*update_fn)(int val, int prev, char **err); /* Optional function to apply new value at runtime (generic doc above) */ int (*update_fn)(int val, int prev, const char **err); /* Optional function to apply new value at runtime (generic doc above) */
} enumConfigData; } enumConfigData;
typedef enum numericType { typedef enum numericType {
...@@ -214,8 +214,8 @@ typedef struct numericConfigData { ...@@ -214,8 +214,8 @@ typedef struct numericConfigData {
long long lower_bound; /* The lower bound of this numeric value */ long long lower_bound; /* The lower bound of this numeric value */
long long upper_bound; /* The upper bound of this numeric value */ long long upper_bound; /* The upper bound of this numeric value */
const long long default_value; /* The default value of the config on rewrite */ const long long default_value; /* The default value of the config on rewrite */
int (*is_valid_fn)(long long val, char **err); /* Optional function to check validity of new value (generic doc above) */ int (*is_valid_fn)(long long val, const char **err); /* Optional function to check validity of new value (generic doc above) */
int (*update_fn)(long long val, long long prev, char **err); /* Optional function to apply new value at runtime (generic doc above) */ int (*update_fn)(long long val, long long prev, const char **err); /* Optional function to apply new value at runtime (generic doc above) */
} numericConfigData; } numericConfigData;
typedef union typeData { typedef union typeData {
...@@ -230,10 +230,10 @@ typedef struct typeInterface { ...@@ -230,10 +230,10 @@ typedef struct typeInterface {
/* Called on server start, to init the server with default value */ /* Called on server start, to init the server with default value */
void (*init)(typeData data); void (*init)(typeData data);
/* Called on server start, should return 1 on success, 0 on error and should set err */ /* Called on server start, should return 1 on success, 0 on error and should set err */
int (*load)(typeData data, sds *argc, int argv, char **err); int (*load)(typeData data, sds *argc, int argv, const char **err);
/* Called on server startup and CONFIG SET, returns 1 on success, 0 on error /* Called on server startup and CONFIG SET, returns 1 on success, 0 on error
* and can set a verbose err string, update is true when called from CONFIG SET */ * and can set a verbose err string, update is true when called from CONFIG SET */
int (*set)(typeData data, sds value, int update, char **err); int (*set)(typeData data, sds value, int update, const char **err);
/* Called on CONFIG GET, required to add output to the client */ /* Called on CONFIG GET, required to add output to the client */
void (*get)(client *c, typeData data); void (*get)(client *c, typeData data);
/* Called on CONFIG REWRITE, required to rewrite the config state */ /* Called on CONFIG REWRITE, required to rewrite the config state */
...@@ -325,7 +325,7 @@ void queueLoadModule(sds path, sds *argv, int argc) { ...@@ -325,7 +325,7 @@ void queueLoadModule(sds path, sds *argv, int argc) {
* server.oom_score_adj_values if valid. * server.oom_score_adj_values if valid.
*/ */
static int updateOOMScoreAdjValues(sds *args, char **err, int apply) { static int updateOOMScoreAdjValues(sds *args, const char **err, int apply) {
int i; int i;
int values[CONFIG_OOM_COUNT]; int values[CONFIG_OOM_COUNT];
...@@ -385,7 +385,7 @@ void initConfigValues() { ...@@ -385,7 +385,7 @@ void initConfigValues() {
} }
void loadServerConfigFromString(char *config) { void loadServerConfigFromString(char *config) {
char *err = NULL; const char *err = NULL;
int linenum = 0, totlines, i; int linenum = 0, totlines, i;
int slaveof_linenum = 0; int slaveof_linenum = 0;
sds *lines; sds *lines;
...@@ -608,7 +608,7 @@ void loadServerConfigFromString(char *config) { ...@@ -608,7 +608,7 @@ void loadServerConfigFromString(char *config) {
int argc_err; int argc_err;
if (ACLAppendUserForLoading(argv,argc,&argc_err) == C_ERR) { if (ACLAppendUserForLoading(argv,argc,&argc_err) == C_ERR) {
char buf[1024]; char buf[1024];
char *errmsg = ACLSetUserStringError(); const char *errmsg = ACLSetUserStringError();
snprintf(buf,sizeof(buf),"Error in user declaration '%s': %s", snprintf(buf,sizeof(buf),"Error in user declaration '%s': %s",
argv[argc_err],errmsg); argv[argc_err],errmsg);
err = buf; err = buf;
...@@ -624,8 +624,7 @@ void loadServerConfigFromString(char *config) { ...@@ -624,8 +624,7 @@ void loadServerConfigFromString(char *config) {
err = "sentinel directive while not in sentinel mode"; err = "sentinel directive while not in sentinel mode";
goto loaderr; goto loaderr;
} }
err = sentinelHandleConfiguration(argv+1,argc-1); queueSentinelConfig(argv+1,argc-1,linenum,lines[i]);
if (err) goto loaderr;
} }
} else { } else {
err = "Bad directive or wrong number of arguments"; goto loaderr; err = "Bad directive or wrong number of arguments"; goto loaderr;
...@@ -730,7 +729,7 @@ void configSetCommand(client *c) { ...@@ -730,7 +729,7 @@ void configSetCommand(client *c) {
robj *o; robj *o;
long long ll; long long ll;
int err; int err;
char *errstr = NULL; const char *errstr = NULL;
serverAssertWithInfo(c,c->argv[2],sdsEncodedObject(c->argv[2])); serverAssertWithInfo(c,c->argv[2],sdsEncodedObject(c->argv[2]));
serverAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3])); serverAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3]));
o = c->argv[3]; o = c->argv[3];
...@@ -1221,7 +1220,16 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) { ...@@ -1221,7 +1220,16 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
sdsfree(argv[0]); sdsfree(argv[0]);
argv[0] = alt; argv[0] = alt;
} }
rewriteConfigAddLineNumberToOption(state,argv[0],linenum); /* If this is sentinel config, we use sentinel "sentinel <config>" as option
to avoid messing up the sequence. */
if (server.sentinel_mode && argc > 1 && !strcasecmp(argv[0],"sentinel")) {
sds sentinelOption = sdsempty();
sentinelOption = sdscatfmt(sentinelOption,"%S %S",argv[0],argv[1]);
rewriteConfigAddLineNumberToOption(state,sentinelOption,linenum);
sdsfree(sentinelOption);
} else {
rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
}
sdsfreesplitres(argv,argc); sdsfreesplitres(argv,argc);
} }
fclose(fp); fclose(fp);
...@@ -1683,7 +1691,7 @@ int rewriteConfigOverwriteFile(char *configfile, sds content) { ...@@ -1683,7 +1691,7 @@ int rewriteConfigOverwriteFile(char *configfile, sds content) {
if (fsync(fd)) if (fsync(fd))
serverLog(LL_WARNING, "Could not sync tmp config file to disk (%s)", strerror(errno)); serverLog(LL_WARNING, "Could not sync tmp config file to disk (%s)", strerror(errno));
else if (fchmod(fd, 0644) == -1) else if (fchmod(fd, 0644 & ~server.umask) == -1)
serverLog(LL_WARNING, "Could not chmod config file (%s)", strerror(errno)); serverLog(LL_WARNING, "Could not chmod config file (%s)", strerror(errno));
else if (rename(tmp_conffile, configfile) == -1) else if (rename(tmp_conffile, configfile) == -1)
serverLog(LL_WARNING, "Could not rename tmp config file (%s)", strerror(errno)); serverLog(LL_WARNING, "Could not rename tmp config file (%s)", strerror(errno));
...@@ -1795,7 +1803,7 @@ static void boolConfigInit(typeData data) { ...@@ -1795,7 +1803,7 @@ static void boolConfigInit(typeData data) {
*data.yesno.config = data.yesno.default_value; *data.yesno.config = data.yesno.default_value;
} }
static int boolConfigSet(typeData data, sds value, int update, char **err) { static int boolConfigSet(typeData data, sds value, int update, const char **err) {
int yn = yesnotoi(value); int yn = yesnotoi(value);
if (yn == -1) { if (yn == -1) {
*err = "argument must be 'yes' or 'no'"; *err = "argument must be 'yes' or 'no'";
...@@ -1836,7 +1844,7 @@ static void stringConfigInit(typeData data) { ...@@ -1836,7 +1844,7 @@ static void stringConfigInit(typeData data) {
*data.string.config = (data.string.convert_empty_to_null && !data.string.default_value) ? NULL : zstrdup(data.string.default_value); *data.string.config = (data.string.convert_empty_to_null && !data.string.default_value) ? NULL : zstrdup(data.string.default_value);
} }
static int stringConfigSet(typeData data, sds value, int update, char **err) { static int stringConfigSet(typeData data, sds value, int update, const char **err) {
if (data.string.is_valid_fn && !data.string.is_valid_fn(value, err)) if (data.string.is_valid_fn && !data.string.is_valid_fn(value, err))
return 0; return 0;
char *prev = *data.string.config; char *prev = *data.string.config;
...@@ -1863,7 +1871,7 @@ static void sdsConfigInit(typeData data) { ...@@ -1863,7 +1871,7 @@ static void sdsConfigInit(typeData data) {
*data.sds.config = (data.sds.convert_empty_to_null && !data.sds.default_value) ? NULL: sdsnew(data.sds.default_value); *data.sds.config = (data.sds.convert_empty_to_null && !data.sds.default_value) ? NULL: sdsnew(data.sds.default_value);
} }
static int sdsConfigSet(typeData data, sds value, int update, char **err) { static int sdsConfigSet(typeData data, sds value, int update, const char **err) {
if (data.sds.is_valid_fn && !data.sds.is_valid_fn(value, err)) if (data.sds.is_valid_fn && !data.sds.is_valid_fn(value, err))
return 0; return 0;
sds prev = *data.sds.config; sds prev = *data.sds.config;
...@@ -1922,7 +1930,7 @@ static void enumConfigInit(typeData data) { ...@@ -1922,7 +1930,7 @@ static void enumConfigInit(typeData data) {
*data.enumd.config = data.enumd.default_value; *data.enumd.config = data.enumd.default_value;
} }
static int enumConfigSet(typeData data, sds value, int update, char **err) { static int enumConfigSet(typeData data, sds value, int update, const char **err) {
int enumval = configEnumGetValue(data.enumd.enum_value, value); int enumval = configEnumGetValue(data.enumd.enum_value, value);
if (enumval == INT_MIN) { if (enumval == INT_MIN) {
sds enumerr = sdsnew("argument must be one of the following: "); sds enumerr = sdsnew("argument must be one of the following: ");
...@@ -2028,7 +2036,7 @@ static void numericConfigInit(typeData data) { ...@@ -2028,7 +2036,7 @@ static void numericConfigInit(typeData data) {
SET_NUMERIC_TYPE(data.numeric.default_value) SET_NUMERIC_TYPE(data.numeric.default_value)
} }
static int numericBoundaryCheck(typeData data, long long ll, char **err) { static int numericBoundaryCheck(typeData data, long long ll, const char **err) {
if (data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG || if (data.numeric.numeric_type == NUMERIC_TYPE_ULONG_LONG ||
data.numeric.numeric_type == NUMERIC_TYPE_UINT || data.numeric.numeric_type == NUMERIC_TYPE_UINT ||
data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) { data.numeric.numeric_type == NUMERIC_TYPE_SIZE_T) {
...@@ -2058,7 +2066,7 @@ static int numericBoundaryCheck(typeData data, long long ll, char **err) { ...@@ -2058,7 +2066,7 @@ static int numericBoundaryCheck(typeData data, long long ll, char **err) {
return 1; return 1;
} }
static int numericConfigSet(typeData data, sds value, int update, char **err) { static int numericConfigSet(typeData data, sds value, int update, const char **err) {
long long ll, prev = 0; long long ll, prev = 0;
if (data.numeric.is_memory) { if (data.numeric.is_memory) {
int memerr; int memerr;
...@@ -2196,7 +2204,7 @@ static void numericConfigRewrite(typeData data, const char *name, struct rewrite ...@@ -2196,7 +2204,7 @@ static void numericConfigRewrite(typeData data, const char *name, struct rewrite
} \ } \
} }
static int isValidActiveDefrag(int val, char **err) { static int isValidActiveDefrag(int val, const char **err) {
#ifndef HAVE_DEFRAG #ifndef HAVE_DEFRAG
if (val) { if (val) {
*err = "Active defragmentation cannot be enabled: it " *err = "Active defragmentation cannot be enabled: it "
...@@ -2212,7 +2220,7 @@ static int isValidActiveDefrag(int val, char **err) { ...@@ -2212,7 +2220,7 @@ static int isValidActiveDefrag(int val, char **err) {
return 1; return 1;
} }
static int isValidDBfilename(char *val, char **err) { static int isValidDBfilename(char *val, const char **err) {
if (!pathIsBaseName(val)) { if (!pathIsBaseName(val)) {
*err = "dbfilename can't be a path, just a filename"; *err = "dbfilename can't be a path, just a filename";
return 0; return 0;
...@@ -2220,7 +2228,7 @@ static int isValidDBfilename(char *val, char **err) { ...@@ -2220,7 +2228,7 @@ static int isValidDBfilename(char *val, char **err) {
return 1; return 1;
} }
static int isValidAOFfilename(char *val, char **err) { static int isValidAOFfilename(char *val, const char **err) {
if (!pathIsBaseName(val)) { if (!pathIsBaseName(val)) {
*err = "appendfilename can't be a path, just a filename"; *err = "appendfilename can't be a path, just a filename";
return 0; return 0;
...@@ -2228,7 +2236,26 @@ static int isValidAOFfilename(char *val, char **err) { ...@@ -2228,7 +2236,26 @@ static int isValidAOFfilename(char *val, char **err) {
return 1; return 1;
} }
static int updateHZ(long long val, long long prev, char **err) { /* Validate specified string is a valid proc-title-template */
static int isValidProcTitleTemplate(char *val, const char **err) {
if (!validateProcTitleTemplate(val)) {
*err = "template format is invalid or contains unknown variables";
return 0;
}
return 1;
}
static int updateProcTitleTemplate(char *val, char *prev, const char **err) {
UNUSED(val);
UNUSED(prev);
if (redisSetProcTitle(NULL) == C_ERR) {
*err = "failed to set process title";
return 0;
}
return 1;
}
static int updateHZ(long long val, long long prev, const char **err) {
UNUSED(prev); UNUSED(prev);
UNUSED(err); UNUSED(err);
/* Hz is more a hint from the user, so we accept values out of range /* Hz is more a hint from the user, so we accept values out of range
...@@ -2240,14 +2267,14 @@ static int updateHZ(long long val, long long prev, char **err) { ...@@ -2240,14 +2267,14 @@ static int updateHZ(long long val, long long prev, char **err) {
return 1; return 1;
} }
static int updateJemallocBgThread(int val, int prev, char **err) { static int updateJemallocBgThread(int val, int prev, const char **err) {
UNUSED(prev); UNUSED(prev);
UNUSED(err); UNUSED(err);
set_jemalloc_bg_thread(val); set_jemalloc_bg_thread(val);
return 1; return 1;
} }
static int updateReplBacklogSize(long long val, long long prev, char **err) { static int updateReplBacklogSize(long long val, long long prev, const char **err) {
/* resizeReplicationBacklog sets server.repl_backlog_size, and relies on /* resizeReplicationBacklog sets server.repl_backlog_size, and relies on
* being able to tell when the size changes, so restore prev before calling it. */ * being able to tell when the size changes, so restore prev before calling it. */
UNUSED(err); UNUSED(err);
...@@ -2256,7 +2283,7 @@ static int updateReplBacklogSize(long long val, long long prev, char **err) { ...@@ -2256,7 +2283,7 @@ static int updateReplBacklogSize(long long val, long long prev, char **err) {
return 1; return 1;
} }
static int updateMaxmemory(long long val, long long prev, char **err) { static int updateMaxmemory(long long val, long long prev, const char **err) {
UNUSED(prev); UNUSED(prev);
UNUSED(err); UNUSED(err);
if (val) { if (val) {
...@@ -2269,7 +2296,7 @@ static int updateMaxmemory(long long val, long long prev, char **err) { ...@@ -2269,7 +2296,7 @@ static int updateMaxmemory(long long val, long long prev, char **err) {
return 1; return 1;
} }
static int updateGoodSlaves(long long val, long long prev, char **err) { static int updateGoodSlaves(long long val, long long prev, const char **err) {
UNUSED(val); UNUSED(val);
UNUSED(prev); UNUSED(prev);
UNUSED(err); UNUSED(err);
...@@ -2277,7 +2304,7 @@ static int updateGoodSlaves(long long val, long long prev, char **err) { ...@@ -2277,7 +2304,7 @@ static int updateGoodSlaves(long long val, long long prev, char **err) {
return 1; return 1;
} }
static int updateAppendonly(int val, int prev, char **err) { static int updateAppendonly(int val, int prev, const char **err) {
UNUSED(prev); UNUSED(prev);
if (val == 0 && server.aof_state != AOF_OFF) { if (val == 0 && server.aof_state != AOF_OFF) {
stopAppendOnly(); stopAppendOnly();
...@@ -2290,7 +2317,7 @@ static int updateAppendonly(int val, int prev, char **err) { ...@@ -2290,7 +2317,7 @@ static int updateAppendonly(int val, int prev, char **err) {
return 1; return 1;
} }
static int updateSighandlerEnabled(int val, int prev, char **err) { static int updateSighandlerEnabled(int val, int prev, const char **err) {
UNUSED(err); UNUSED(err);
UNUSED(prev); UNUSED(prev);
if (val) if (val)
...@@ -2300,7 +2327,7 @@ static int updateSighandlerEnabled(int val, int prev, char **err) { ...@@ -2300,7 +2327,7 @@ static int updateSighandlerEnabled(int val, int prev, char **err) {
return 1; return 1;
} }
static int updateMaxclients(long long val, long long prev, char **err) { static int updateMaxclients(long long val, long long prev, const char **err) {
/* Try to check if the OS is capable of supporting so many FDs. */ /* Try to check if the OS is capable of supporting so many FDs. */
if (val > prev) { if (val > prev) {
adjustOpenFilesLimit(); adjustOpenFilesLimit();
...@@ -2328,7 +2355,7 @@ static int updateMaxclients(long long val, long long prev, char **err) { ...@@ -2328,7 +2355,7 @@ static int updateMaxclients(long long val, long long prev, char **err) {
return 1; return 1;
} }
static int updateOOMScoreAdj(int val, int prev, char **err) { static int updateOOMScoreAdj(int val, int prev, const char **err) {
UNUSED(prev); UNUSED(prev);
if (val) { if (val) {
...@@ -2342,7 +2369,7 @@ static int updateOOMScoreAdj(int val, int prev, char **err) { ...@@ -2342,7 +2369,7 @@ static int updateOOMScoreAdj(int val, int prev, char **err) {
} }
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
static int updateTlsCfg(char *val, char *prev, char **err) { static int updateTlsCfg(char *val, char *prev, const char **err) {
UNUSED(val); UNUSED(val);
UNUSED(prev); UNUSED(prev);
UNUSED(err); UNUSED(err);
...@@ -2355,13 +2382,13 @@ static int updateTlsCfg(char *val, char *prev, char **err) { ...@@ -2355,13 +2382,13 @@ static int updateTlsCfg(char *val, char *prev, char **err) {
} }
return 1; return 1;
} }
static int updateTlsCfgBool(int val, int prev, char **err) { static int updateTlsCfgBool(int val, int prev, const char **err) {
UNUSED(val); UNUSED(val);
UNUSED(prev); UNUSED(prev);
return updateTlsCfg(NULL, NULL, err); return updateTlsCfg(NULL, NULL, err);
} }
static int updateTlsCfgInt(long long val, long long prev, char **err) { static int updateTlsCfgInt(long long val, long long prev, const char **err) {
UNUSED(val); UNUSED(val);
UNUSED(prev); UNUSED(prev);
return updateTlsCfg(NULL, NULL, err); return updateTlsCfg(NULL, NULL, err);
...@@ -2380,11 +2407,13 @@ standardConfig configs[] = { ...@@ -2380,11 +2407,13 @@ standardConfig configs[] = {
createBoolConfig("rdb-del-sync-files", NULL, MODIFIABLE_CONFIG, server.rdb_del_sync_files, 0, 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("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("stop-writes-on-bgsave-error", NULL, MODIFIABLE_CONFIG, server.stop_writes_on_bgsave_err, 1, NULL, NULL),
createBoolConfig("set-proc-title", NULL, IMMUTABLE_CONFIG, server.set_proc_title, 1, NULL, NULL), /* Should setproctitle be used? */
createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, server.dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/ createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, server.dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/
createBoolConfig("lazyfree-lazy-eviction", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_eviction, 0, NULL, NULL), createBoolConfig("lazyfree-lazy-eviction", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_eviction, 0, NULL, NULL),
createBoolConfig("lazyfree-lazy-expire", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_expire, 0, NULL, NULL), createBoolConfig("lazyfree-lazy-expire", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_expire, 0, NULL, NULL),
createBoolConfig("lazyfree-lazy-server-del", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_server_del, 0, NULL, NULL), createBoolConfig("lazyfree-lazy-server-del", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_server_del, 0, NULL, NULL),
createBoolConfig("lazyfree-lazy-user-del", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_user_del , 0, NULL, NULL), createBoolConfig("lazyfree-lazy-user-del", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_user_del , 0, NULL, NULL),
createBoolConfig("lazyfree-lazy-user-flush", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_user_flush , 0, NULL, NULL),
createBoolConfig("repl-disable-tcp-nodelay", NULL, MODIFIABLE_CONFIG, server.repl_disable_tcp_nodelay, 0, NULL, NULL), createBoolConfig("repl-disable-tcp-nodelay", NULL, MODIFIABLE_CONFIG, server.repl_disable_tcp_nodelay, 0, NULL, NULL),
createBoolConfig("repl-diskless-sync", NULL, MODIFIABLE_CONFIG, server.repl_diskless_sync, 0, NULL, NULL), createBoolConfig("repl-diskless-sync", NULL, MODIFIABLE_CONFIG, server.repl_diskless_sync, 0, NULL, NULL),
createBoolConfig("gopher-enabled", NULL, MODIFIABLE_CONFIG, server.gopher_enabled, 0, NULL, NULL), createBoolConfig("gopher-enabled", NULL, MODIFIABLE_CONFIG, server.gopher_enabled, 0, NULL, NULL),
...@@ -2425,6 +2454,7 @@ standardConfig configs[] = { ...@@ -2425,6 +2454,7 @@ standardConfig configs[] = {
createStringConfig("aof_rewrite_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.aof_rewrite_cpulist, NULL, NULL, NULL), createStringConfig("aof_rewrite_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.aof_rewrite_cpulist, NULL, NULL, NULL),
createStringConfig("bgsave_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bgsave_cpulist, NULL, NULL, NULL), createStringConfig("bgsave_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bgsave_cpulist, NULL, NULL, NULL),
createStringConfig("ignore-warnings", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.ignore_warnings, "", NULL, NULL), createStringConfig("ignore-warnings", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.ignore_warnings, "", NULL, NULL),
createStringConfig("proc-title-template", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.proc_title_template, CONFIG_DEFAULT_PROC_TITLE_TEMPLATE, isValidProcTitleTemplate, updateProcTitleTemplate),
/* SDS Configs */ /* SDS Configs */
createSDSConfig("masterauth", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.masterauth, NULL, NULL, NULL), createSDSConfig("masterauth", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.masterauth, NULL, NULL, NULL),
......
...@@ -226,7 +226,7 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) { ...@@ -226,7 +226,7 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
/* Although the key is not really deleted from the database, we regard /* Although the key is not really deleted from the database, we regard
overwrite as two steps of unlink+add, so we still need to call the unlink overwrite as two steps of unlink+add, so we still need to call the unlink
callback of the module. */ callback of the module. */
moduleNotifyKeyUnlink(key,val); moduleNotifyKeyUnlink(key,old);
dictSetVal(db->dict, de, val); dictSetVal(db->dict, de, val);
if (server.lazyfree_lazy_server_del) { if (server.lazyfree_lazy_server_del) {
...@@ -595,21 +595,23 @@ void signalFlushedDb(int dbid, int async) { ...@@ -595,21 +595,23 @@ void signalFlushedDb(int dbid, int async) {
/* Return the set of flags to use for the emptyDb() call for FLUSHALL /* Return the set of flags to use for the emptyDb() call for FLUSHALL
* and FLUSHDB commands. * and FLUSHDB commands.
* *
* Currently the command just attempts to parse the "ASYNC" option. It * sync: flushes the database in an sync manner.
* also checks if the command arity is wrong. * async: flushes the database in an async manner.
* no option: determine sync or async according to the value of lazyfree-lazy-user-flush.
* *
* On success C_OK is returned and the flags are stored in *flags, otherwise * On success C_OK is returned and the flags are stored in *flags, otherwise
* C_ERR is returned and the function sends an error to the client. */ * C_ERR is returned and the function sends an error to the client. */
int getFlushCommandFlags(client *c, int *flags) { int getFlushCommandFlags(client *c, int *flags) {
/* Parse the optional ASYNC option. */ /* Parse the optional ASYNC option. */
if (c->argc > 1) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"sync")) {
if (c->argc > 2 || strcasecmp(c->argv[1]->ptr,"async")) { *flags = EMPTYDB_NO_FLAGS;
addReplyErrorObject(c,shared.syntaxerr); } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"async")) {
return C_ERR;
}
*flags = EMPTYDB_ASYNC; *flags = EMPTYDB_ASYNC;
} else if (c->argc == 1) {
*flags = server.lazyfree_lazy_user_flush ? EMPTYDB_ASYNC : EMPTYDB_NO_FLAGS;
} else { } else {
*flags = EMPTYDB_NO_FLAGS; addReplyErrorObject(c,shared.syntaxerr);
return C_ERR;
} }
return C_OK; return C_OK;
} }
...@@ -951,7 +953,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) { ...@@ -951,7 +953,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
int filter = 0; int filter = 0;
/* Filter element if it does not match the pattern. */ /* Filter element if it does not match the pattern. */
if (!filter && use_pattern) { if (use_pattern) {
if (sdsEncodedObject(kobj)) { if (sdsEncodedObject(kobj)) {
if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0)) if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))
filter = 1; filter = 1;
......
...@@ -367,7 +367,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) { ...@@ -367,7 +367,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
} else if (dict_val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) { } else if (dict_val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) {
void *newptr, *ptr = dictGetVal(de); void *newptr, *ptr = dictGetVal(de);
if ((newptr = activeDefragAlloc(ptr))) if ((newptr = activeDefragAlloc(ptr)))
ln->value = newptr, defragged++; de->v.val = newptr, defragged++;
} }
defragged += dictIterDefragEntry(di); defragged += dictIterDefragEntry(di);
} }
......
...@@ -53,15 +53,19 @@ ...@@ -53,15 +53,19 @@
* to the function to avoid too many gettimeofday() syscalls. */ * to the function to avoid too many gettimeofday() syscalls. */
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) { int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
long long t = dictGetSignedIntegerVal(de); long long t = dictGetSignedIntegerVal(de);
mstime_t expire_latency;
if (now > t) { if (now > t) {
sds key = dictGetKey(de); sds key = dictGetKey(de);
robj *keyobj = createStringObject(key,sdslen(key)); robj *keyobj = createStringObject(key,sdslen(key));
propagateExpire(db,keyobj,server.lazyfree_lazy_expire); propagateExpire(db,keyobj,server.lazyfree_lazy_expire);
latencyStartMonitor(expire_latency);
if (server.lazyfree_lazy_expire) if (server.lazyfree_lazy_expire)
dbAsyncDelete(db,keyobj); dbAsyncDelete(db,keyobj);
else else
dbSyncDelete(db,keyobj); dbSyncDelete(db,keyobj);
latencyEndMonitor(expire_latency);
latencyAddSampleIfNeeded("expire-del",expire_latency);
notifyKeyspaceEvent(NOTIFY_EXPIRED, notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",keyobj,db->id); "expired",keyobj,db->id);
signalModifiedKey(NULL, db, keyobj); signalModifiedKey(NULL, db, keyobj);
...@@ -224,7 +228,7 @@ void activeExpireCycle(int type) { ...@@ -224,7 +228,7 @@ void activeExpireCycle(int type) {
/* When there are less than 1% filled slots, sampling the key /* When there are less than 1% filled slots, sampling the key
* space is expensive, so stop here waiting for better times... * space is expensive, so stop here waiting for better times...
* The dictionary will be resized asap. */ * The dictionary will be resized asap. */
if (num && slots > DICT_HT_INITIAL_SIZE && if (slots > DICT_HT_INITIAL_SIZE &&
(num*100/slots < 1)) break; (num*100/slots < 1)) break;
/* The main collection cycle. Sample random keys among keys /* The main collection cycle. Sample random keys among keys
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment