Merge unstable into 6.2
This commit is contained in:
commit
b34fc03e5e
2
deps/hiredis/async_private.h
vendored
2
deps/hiredis/async_private.h
vendored
@ -51,7 +51,7 @@
|
|||||||
#define _EL_CLEANUP(ctx) do { \
|
#define _EL_CLEANUP(ctx) do { \
|
||||||
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
||||||
ctx->ev.cleanup = NULL; \
|
ctx->ev.cleanup = NULL; \
|
||||||
} while(0);
|
} while(0)
|
||||||
|
|
||||||
static inline void refreshTimeout(redisAsyncContext *ctx) {
|
static inline void refreshTimeout(redisAsyncContext *ctx) {
|
||||||
#define REDIS_TIMER_ISSET(tvp) \
|
#define REDIS_TIMER_ISSET(tvp) \
|
||||||
|
18
redis.conf
18
redis.conf
@ -196,9 +196,12 @@ tcp-keepalive 300
|
|||||||
#
|
#
|
||||||
# tls-cluster yes
|
# tls-cluster yes
|
||||||
|
|
||||||
# Explicitly specify TLS versions to support. Allowed values are case insensitive
|
# By default, only TLSv1.2 and TLSv1.3 are enabled and it is highly recommended
|
||||||
# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or
|
# that older formally deprecated versions are kept disabled to reduce the attack surface.
|
||||||
# any combination. To enable only TLSv1.2 and TLSv1.3, use:
|
# You can 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 any combination.
|
||||||
|
# To enable only TLSv1.2 and TLSv1.3, use:
|
||||||
#
|
#
|
||||||
# tls-protocols "TLSv1.2 TLSv1.3"
|
# tls-protocols "TLSv1.2 TLSv1.3"
|
||||||
|
|
||||||
@ -688,7 +691,7 @@ replica-priority 100
|
|||||||
|
|
||||||
# Redis implements server assisted support for client side caching of values.
|
# Redis implements server assisted support for client side caching of values.
|
||||||
# This is implemented using an invalidation table that remembers, using
|
# This is implemented using an invalidation table that remembers, using
|
||||||
# 16 millions of slots, what clients may have certain subsets of keys. In turn
|
# a radix key indexed by key name, what clients have which keys. In turn
|
||||||
# this is used in order to send invalidation messages to clients. Please
|
# this is used in order to send invalidation messages to clients. Please
|
||||||
# check this page to understand more about the feature:
|
# check this page to understand more about the feature:
|
||||||
#
|
#
|
||||||
@ -1973,3 +1976,10 @@ jemalloc-bg-thread yes
|
|||||||
#
|
#
|
||||||
# Set bgsave child process to cpu affinity 1,10,11
|
# Set bgsave child process to cpu affinity 1,10,11
|
||||||
# bgsave_cpulist 1,10-11
|
# bgsave_cpulist 1,10-11
|
||||||
|
|
||||||
|
# In some cases redis will emit warnings and even refuse to start if it detects
|
||||||
|
# that the system is in bad state, it is possible to suppress these warnings
|
||||||
|
# by setting the following config which takes a space delimited list of warnings
|
||||||
|
# to suppress
|
||||||
|
#
|
||||||
|
# ignore-warnings ARM64-COW-BUG
|
||||||
|
22
src/Makefile
22
src/Makefile
@ -115,8 +115,18 @@ else
|
|||||||
ifeq ($(uname_S),Darwin)
|
ifeq ($(uname_S),Darwin)
|
||||||
# Darwin
|
# Darwin
|
||||||
FINAL_LIBS+= -ldl
|
FINAL_LIBS+= -ldl
|
||||||
|
# Homebrew's OpenSSL is not linked to /usr/local to avoid
|
||||||
|
# conflicts with the system's LibreSSL installation so it
|
||||||
|
# must be referenced explicitly during build.
|
||||||
|
ifeq ($(uname_M),arm64)
|
||||||
|
# Homebrew arm64 uses /opt/homebrew as HOMEBREW_PREFIX
|
||||||
|
OPENSSL_CFLAGS=-I/opt/homebrew/opt/openssl/include
|
||||||
|
OPENSSL_LDFLAGS=-L/opt/homebrew/opt/openssl/lib
|
||||||
|
else
|
||||||
|
# Homebrew x86/ppc uses /usr/local as HOMEBREW_PREFIX
|
||||||
OPENSSL_CFLAGS=-I/usr/local/opt/openssl/include
|
OPENSSL_CFLAGS=-I/usr/local/opt/openssl/include
|
||||||
OPENSSL_LDFLAGS=-L/usr/local/opt/openssl/lib
|
OPENSSL_LDFLAGS=-L/usr/local/opt/openssl/lib
|
||||||
|
endif
|
||||||
else
|
else
|
||||||
ifeq ($(uname_S),AIX)
|
ifeq ($(uname_S),AIX)
|
||||||
# AIX
|
# AIX
|
||||||
@ -260,11 +270,11 @@ endif
|
|||||||
|
|
||||||
REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)
|
REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)
|
||||||
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
|
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
|
||||||
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 crcspeed.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 timeout.o setcpuaffinity.o monotonic.o
|
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 crcspeed.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 timeout.o setcpuaffinity.o monotonic.o mt19937-64.o
|
||||||
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
|
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
|
||||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o
|
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o
|
||||||
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
|
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
|
||||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o release.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o
|
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o release.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o
|
||||||
REDIS_CHECK_RDB_NAME=redis-check-rdb$(PROG_SUFFIX)
|
REDIS_CHECK_RDB_NAME=redis-check-rdb$(PROG_SUFFIX)
|
||||||
REDIS_CHECK_AOF_NAME=redis-check-aof$(PROG_SUFFIX)
|
REDIS_CHECK_AOF_NAME=redis-check-aof$(PROG_SUFFIX)
|
||||||
|
|
||||||
@ -336,7 +346,7 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
|
|||||||
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
||||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/hdr_histogram/hdr_histogram.o $(FINAL_LIBS)
|
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/hdr_histogram/hdr_histogram.o $(FINAL_LIBS)
|
||||||
|
|
||||||
dict-benchmark: dict.c zmalloc.c sds.c siphash.c
|
dict-benchmark: dict.c zmalloc.c sds.c siphash.c mt19937-64.c
|
||||||
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
|
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
|
||||||
|
|
||||||
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
|
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
|
||||||
@ -405,8 +415,8 @@ install: all
|
|||||||
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
|
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
|
||||||
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
|
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
|
||||||
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
|
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
|
||||||
$(REDIS_INSTALL) $(REDIS_CHECK_RDB_NAME) $(INSTALL_BIN)
|
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_RDB_NAME)
|
||||||
$(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN)
|
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_AOF_NAME)
|
||||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
|
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
|
38
src/acl.c
38
src/acl.c
@ -2184,18 +2184,30 @@ void aclCommand(client *c) {
|
|||||||
}
|
}
|
||||||
} else if (c->argc == 2 && !strcasecmp(sub,"help")) {
|
} else if (c->argc == 2 && !strcasecmp(sub,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"LOAD -- Reload users from the ACL file.",
|
"CAT [<category>]",
|
||||||
"SAVE -- Save the current config to the ACL file.",
|
" List all commands that belong to <category>, or all command categories",
|
||||||
"LIST -- Show user details in config file format.",
|
" when no category is specified.",
|
||||||
"USERS -- List all the registered usernames.",
|
"DELUSER <username> [<username> ...]",
|
||||||
"SETUSER <username> [attribs ...] -- Create or modify a user.",
|
" Delete a list of users.",
|
||||||
"GETUSER <username> -- Get the user details.",
|
"GETUSER <username>",
|
||||||
"DELUSER <username> [...] -- Delete a list of users.",
|
" Get the user's details.",
|
||||||
"CAT -- List available categories.",
|
"GENPASS [<bits>]",
|
||||||
"CAT <category> -- List commands inside category.",
|
" Generate a secure 256-bit user password. The optional `bits` argument can",
|
||||||
"GENPASS [<bits>] -- Generate a secure user password.",
|
" be used to specify a different size.",
|
||||||
"WHOAMI -- Return the current connection username.",
|
"LIST",
|
||||||
"LOG [<count> | RESET] -- Show the ACL log entries.",
|
" Show users details in config file format.",
|
||||||
|
"LOAD",
|
||||||
|
" Reload users from the ACL file.",
|
||||||
|
"LOG [<count> | RESET]",
|
||||||
|
" Show the ACL log entries.",
|
||||||
|
"SAVE",
|
||||||
|
" Save the current config to the ACL file.",
|
||||||
|
"SETUSER <username> <attribute> [<attribute> ...]",
|
||||||
|
" Create or modify a user with the specified attributes.",
|
||||||
|
"USERS",
|
||||||
|
" List all the registered usernames.",
|
||||||
|
"WHOAMI",
|
||||||
|
" Return the current connection username.",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c,help);
|
addReplyHelp(c,help);
|
||||||
@ -2224,7 +2236,7 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd) {
|
|||||||
void authCommand(client *c) {
|
void authCommand(client *c) {
|
||||||
/* Only two or three argument forms are allowed. */
|
/* Only two or three argument forms are allowed. */
|
||||||
if (c->argc > 3) {
|
if (c->argc > 3) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
80
src/aof.c
80
src/aof.c
@ -206,29 +206,27 @@ int aofFsyncInProgress(void) {
|
|||||||
/* Starts a background task that performs fsync() against the specified
|
/* Starts a background task that performs fsync() against the specified
|
||||||
* file descriptor (the one of the AOF file) in another thread. */
|
* file descriptor (the one of the AOF file) in another thread. */
|
||||||
void aof_background_fsync(int fd) {
|
void aof_background_fsync(int fd) {
|
||||||
bioCreateBackgroundJob(BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL);
|
bioCreateFsyncJob(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Kills an AOFRW child process if exists */
|
/* Kills an AOFRW child process if exists */
|
||||||
void killAppendOnlyChild(void) {
|
void killAppendOnlyChild(void) {
|
||||||
int statloc;
|
int statloc;
|
||||||
/* No AOFRW child? return. */
|
/* No AOFRW child? return. */
|
||||||
if (server.aof_child_pid == -1) return;
|
if (server.child_type != CHILD_TYPE_AOF) return;
|
||||||
/* Kill AOFRW child, wait for child exit. */
|
/* Kill AOFRW child, wait for child exit. */
|
||||||
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
|
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
|
||||||
(long) server.aof_child_pid);
|
(long) server.child_pid);
|
||||||
if (kill(server.aof_child_pid,SIGUSR1) != -1) {
|
if (kill(server.child_pid,SIGUSR1) != -1) {
|
||||||
while(wait3(&statloc,0,NULL) != server.aof_child_pid);
|
while(wait3(&statloc,0,NULL) != server.child_pid);
|
||||||
}
|
}
|
||||||
/* Reset the buffer accumulating changes while the child saves. */
|
/* Reset the buffer accumulating changes while the child saves. */
|
||||||
aofRewriteBufferReset();
|
aofRewriteBufferReset();
|
||||||
aofRemoveTempFile(server.aof_child_pid);
|
aofRemoveTempFile(server.child_pid);
|
||||||
server.aof_child_pid = -1;
|
resetChildState();
|
||||||
server.aof_rewrite_time_start = -1;
|
server.aof_rewrite_time_start = -1;
|
||||||
/* Close pipes used for IPC between the two processes. */
|
/* Close pipes used for IPC between the two processes. */
|
||||||
aofClosePipes();
|
aofClosePipes();
|
||||||
closeChildInfoPipe();
|
|
||||||
updateDictResizePolicy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called when the user switches from "appendonly yes" to "appendonly no"
|
/* Called when the user switches from "appendonly yes" to "appendonly no"
|
||||||
@ -265,14 +263,14 @@ int startAppendOnly(void) {
|
|||||||
strerror(errno));
|
strerror(errno));
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
if (hasActiveChildProcess() && server.aof_child_pid == -1) {
|
if (hasActiveChildProcess() && server.child_type != CHILD_TYPE_AOF) {
|
||||||
server.aof_rewrite_scheduled = 1;
|
server.aof_rewrite_scheduled = 1;
|
||||||
serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
|
serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
|
||||||
} else {
|
} else {
|
||||||
/* If there is a pending AOF rewrite, we need to switch it off and
|
/* If there is a pending AOF rewrite, we need to switch it off and
|
||||||
* start a new one: the old one cannot be reused because it is not
|
* start a new one: the old one cannot be reused because it is not
|
||||||
* accumulating the AOF buffer. */
|
* accumulating the AOF buffer. */
|
||||||
if (server.aof_child_pid != -1) {
|
if (server.child_type == CHILD_TYPE_AOF) {
|
||||||
serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
|
serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
|
||||||
killAppendOnlyChild();
|
killAppendOnlyChild();
|
||||||
}
|
}
|
||||||
@ -646,7 +644,7 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
|||||||
* accumulate the differences between the child DB and the current one
|
* accumulate the differences between the child DB and the current one
|
||||||
* in a buffer, so that when the child process will do its work we
|
* in a buffer, so that when the child process will do its work we
|
||||||
* can append the differences to the new append only file. */
|
* can append the differences to the new append only file. */
|
||||||
if (server.aof_child_pid != -1)
|
if (server.child_type == CHILD_TYPE_AOF)
|
||||||
aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
|
aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
|
||||||
|
|
||||||
sdsfree(buf);
|
sdsfree(buf);
|
||||||
@ -1427,6 +1425,8 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
|||||||
dictEntry *de;
|
dictEntry *de;
|
||||||
size_t processed = 0;
|
size_t processed = 0;
|
||||||
int j;
|
int j;
|
||||||
|
long key_count = 0;
|
||||||
|
long long cow_updated_time = 0;
|
||||||
|
|
||||||
for (j = 0; j < server.dbnum; j++) {
|
for (j = 0; j < server.dbnum; j++) {
|
||||||
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
|
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
|
||||||
@ -1486,6 +1486,19 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
|||||||
processed = aof->processed_bytes;
|
processed = aof->processed_bytes;
|
||||||
aofReadDiffFromParent();
|
aofReadDiffFromParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Update COW info every 1 second (approximately).
|
||||||
|
* in order to avoid calling mstime() on each iteration, we will
|
||||||
|
* check the diff every 1024 keys */
|
||||||
|
if ((key_count & 1023) == 0) {
|
||||||
|
key_count = 0;
|
||||||
|
long long now = mstime();
|
||||||
|
if (now - cow_updated_time >= 1000) {
|
||||||
|
sendChildCOWInfo(CHILD_TYPE_AOF, 0, "AOF rewrite");
|
||||||
|
cow_updated_time = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key_count++;
|
||||||
}
|
}
|
||||||
dictReleaseIterator(di);
|
dictReleaseIterator(di);
|
||||||
di = NULL;
|
di = NULL;
|
||||||
@ -1579,8 +1592,31 @@ int rewriteAppendOnlyFile(char *filename) {
|
|||||||
serverLog(LL_NOTICE,
|
serverLog(LL_NOTICE,
|
||||||
"Concatenating %.2f MB of AOF diff received from parent.",
|
"Concatenating %.2f MB of AOF diff received from parent.",
|
||||||
(double) sdslen(server.aof_child_diff) / (1024*1024));
|
(double) sdslen(server.aof_child_diff) / (1024*1024));
|
||||||
if (rioWrite(&aof,server.aof_child_diff,sdslen(server.aof_child_diff)) == 0)
|
|
||||||
goto werr;
|
/* Now we write the entire AOF buffer we received from the parent
|
||||||
|
* via the pipe during the life of this fork child.
|
||||||
|
* once a second, we'll take a break and send updated COW info to the parent */
|
||||||
|
size_t bytes_to_write = sdslen(server.aof_child_diff);
|
||||||
|
const char *buf = server.aof_child_diff;
|
||||||
|
long long cow_updated_time = mstime();
|
||||||
|
|
||||||
|
while (bytes_to_write) {
|
||||||
|
/* We write the AOF buffer in chunk of 8MB so that we can check the time in between them */
|
||||||
|
size_t chunk_size = bytes_to_write < (8<<20) ? bytes_to_write : (8<<20);
|
||||||
|
|
||||||
|
if (rioWrite(&aof,buf,chunk_size) == 0)
|
||||||
|
goto werr;
|
||||||
|
|
||||||
|
bytes_to_write -= chunk_size;
|
||||||
|
buf += chunk_size;
|
||||||
|
|
||||||
|
/* Update COW info */
|
||||||
|
long long now = mstime();
|
||||||
|
if (now - cow_updated_time >= 1000) {
|
||||||
|
sendChildCOWInfo(CHILD_TYPE_AOF, 0, "AOF rewrite");
|
||||||
|
cow_updated_time = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Make sure data will not remain on the OS's output buffers */
|
/* Make sure data will not remain on the OS's output buffers */
|
||||||
if (fflush(fp)) goto werr;
|
if (fflush(fp)) goto werr;
|
||||||
@ -1703,7 +1739,6 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
|
|
||||||
if (hasActiveChildProcess()) return C_ERR;
|
if (hasActiveChildProcess()) return C_ERR;
|
||||||
if (aofCreatePipes() != C_OK) return C_ERR;
|
if (aofCreatePipes() != C_OK) return C_ERR;
|
||||||
openChildInfoPipe();
|
|
||||||
if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
|
if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
|
||||||
char tmpfile[256];
|
char tmpfile[256];
|
||||||
|
|
||||||
@ -1712,7 +1747,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
redisSetCpuAffinity(server.aof_rewrite_cpulist);
|
redisSetCpuAffinity(server.aof_rewrite_cpulist);
|
||||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||||
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
||||||
sendChildCOWInfo(CHILD_TYPE_AOF, "AOF rewrite");
|
sendChildCOWInfo(CHILD_TYPE_AOF, 1, "AOF rewrite");
|
||||||
exitFromChild(0);
|
exitFromChild(0);
|
||||||
} else {
|
} else {
|
||||||
exitFromChild(1);
|
exitFromChild(1);
|
||||||
@ -1720,7 +1755,6 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
} else {
|
} else {
|
||||||
/* Parent */
|
/* Parent */
|
||||||
if (childpid == -1) {
|
if (childpid == -1) {
|
||||||
closeChildInfoPipe();
|
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Can't rewrite append only file in background: fork: %s",
|
"Can't rewrite append only file in background: fork: %s",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
@ -1731,8 +1765,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
"Background append only file rewriting started by pid %ld",(long) childpid);
|
"Background append only file rewriting started by pid %ld",(long) childpid);
|
||||||
server.aof_rewrite_scheduled = 0;
|
server.aof_rewrite_scheduled = 0;
|
||||||
server.aof_rewrite_time_start = time(NULL);
|
server.aof_rewrite_time_start = time(NULL);
|
||||||
server.aof_child_pid = childpid;
|
|
||||||
updateDictResizePolicy();
|
|
||||||
/* We set appendseldb to -1 in order to force the next call to the
|
/* We set appendseldb to -1 in order to force the next call to the
|
||||||
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
||||||
* accumulated by the parent into server.aof_rewrite_buf will start
|
* accumulated by the parent into server.aof_rewrite_buf will start
|
||||||
@ -1745,7 +1778,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void bgrewriteaofCommand(client *c) {
|
void bgrewriteaofCommand(client *c) {
|
||||||
if (server.aof_child_pid != -1) {
|
if (server.child_type == CHILD_TYPE_AOF) {
|
||||||
addReplyError(c,"Background append only file rewriting already in progress");
|
addReplyError(c,"Background append only file rewriting already in progress");
|
||||||
} else if (hasActiveChildProcess()) {
|
} else if (hasActiveChildProcess()) {
|
||||||
server.aof_rewrite_scheduled = 1;
|
server.aof_rewrite_scheduled = 1;
|
||||||
@ -1803,7 +1836,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
|||||||
* rewritten AOF. */
|
* rewritten AOF. */
|
||||||
latencyStartMonitor(latency);
|
latencyStartMonitor(latency);
|
||||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof",
|
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof",
|
||||||
(int)server.aof_child_pid);
|
(int)server.child_pid);
|
||||||
newfd = open(tmpfile,O_WRONLY|O_APPEND);
|
newfd = open(tmpfile,O_WRONLY|O_APPEND);
|
||||||
if (newfd == -1) {
|
if (newfd == -1) {
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
@ -1909,7 +1942,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
|||||||
server.aof_state = AOF_ON;
|
server.aof_state = AOF_ON;
|
||||||
|
|
||||||
/* Asynchronously close the overwritten AOF. */
|
/* Asynchronously close the overwritten AOF. */
|
||||||
if (oldfd != -1) bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)oldfd,NULL,NULL);
|
if (oldfd != -1) bioCreateCloseJob(oldfd);
|
||||||
|
|
||||||
serverLog(LL_VERBOSE,
|
serverLog(LL_VERBOSE,
|
||||||
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
||||||
@ -1931,8 +1964,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
|||||||
cleanup:
|
cleanup:
|
||||||
aofClosePipes();
|
aofClosePipes();
|
||||||
aofRewriteBufferReset();
|
aofRewriteBufferReset();
|
||||||
aofRemoveTempFile(server.aof_child_pid);
|
aofRemoveTempFile(server.child_pid);
|
||||||
server.aof_child_pid = -1;
|
|
||||||
server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start;
|
server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start;
|
||||||
server.aof_rewrite_time_start = -1;
|
server.aof_rewrite_time_start = -1;
|
||||||
/* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */
|
/* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */
|
||||||
|
61
src/bio.c
61
src/bio.c
@ -78,15 +78,13 @@ static unsigned long long bio_pending[BIO_NUM_OPS];
|
|||||||
* file as the API does not expose the internals at all. */
|
* file as the API does not expose the internals at all. */
|
||||||
struct bio_job {
|
struct bio_job {
|
||||||
time_t time; /* Time at which the job was created. */
|
time_t time; /* Time at which the job was created. */
|
||||||
/* Job specific arguments pointers. If we need to pass more than three
|
/* Job specific arguments.*/
|
||||||
* arguments we can just pass a pointer to a structure or alike. */
|
int fd; /* Fd for file based background jobs */
|
||||||
void *arg1, *arg2, *arg3;
|
lazy_free_fn *free_fn; /* Function that will free the provided arguments */
|
||||||
|
void *free_args[]; /* List of arguments to be passed to the free function */
|
||||||
};
|
};
|
||||||
|
|
||||||
void *bioProcessBackgroundJobs(void *arg);
|
void *bioProcessBackgroundJobs(void *arg);
|
||||||
void lazyfreeFreeObjectFromBioThread(robj *o);
|
|
||||||
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2);
|
|
||||||
void lazyfreeFreeSlotsMapFromBioThread(rax *rt);
|
|
||||||
|
|
||||||
/* Make sure we have enough stack to perform all the things we do in the
|
/* Make sure we have enough stack to perform all the things we do in the
|
||||||
* main thread. */
|
* main thread. */
|
||||||
@ -128,13 +126,8 @@ void bioInit(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) {
|
void bioSubmitJob(int type, struct bio_job *job) {
|
||||||
struct bio_job *job = zmalloc(sizeof(*job));
|
|
||||||
|
|
||||||
job->time = time(NULL);
|
job->time = time(NULL);
|
||||||
job->arg1 = arg1;
|
|
||||||
job->arg2 = arg2;
|
|
||||||
job->arg3 = arg3;
|
|
||||||
pthread_mutex_lock(&bio_mutex[type]);
|
pthread_mutex_lock(&bio_mutex[type]);
|
||||||
listAddNodeTail(bio_jobs[type],job);
|
listAddNodeTail(bio_jobs[type],job);
|
||||||
bio_pending[type]++;
|
bio_pending[type]++;
|
||||||
@ -142,6 +135,35 @@ void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) {
|
|||||||
pthread_mutex_unlock(&bio_mutex[type]);
|
pthread_mutex_unlock(&bio_mutex[type]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...) {
|
||||||
|
va_list valist;
|
||||||
|
/* Allocate memory for the job structure and all required
|
||||||
|
* arguments */
|
||||||
|
struct bio_job *job = zmalloc(sizeof(*job) + sizeof(void *) * (arg_count));
|
||||||
|
job->free_fn = free_fn;
|
||||||
|
|
||||||
|
va_start(valist, arg_count);
|
||||||
|
for (int i = 0; i < arg_count; i++) {
|
||||||
|
job->free_args[i] = va_arg(valist, void *);
|
||||||
|
}
|
||||||
|
va_end(valist);
|
||||||
|
bioSubmitJob(BIO_LAZY_FREE, job);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bioCreateCloseJob(int fd) {
|
||||||
|
struct bio_job *job = zmalloc(sizeof(*job));
|
||||||
|
job->fd = fd;
|
||||||
|
|
||||||
|
bioSubmitJob(BIO_CLOSE_FILE, job);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bioCreateFsyncJob(int fd) {
|
||||||
|
struct bio_job *job = zmalloc(sizeof(*job));
|
||||||
|
job->fd = fd;
|
||||||
|
|
||||||
|
bioSubmitJob(BIO_AOF_FSYNC, job);
|
||||||
|
}
|
||||||
|
|
||||||
void *bioProcessBackgroundJobs(void *arg) {
|
void *bioProcessBackgroundJobs(void *arg) {
|
||||||
struct bio_job *job;
|
struct bio_job *job;
|
||||||
unsigned long type = (unsigned long) arg;
|
unsigned long type = (unsigned long) arg;
|
||||||
@ -196,20 +218,11 @@ void *bioProcessBackgroundJobs(void *arg) {
|
|||||||
|
|
||||||
/* Process the job accordingly to its type. */
|
/* Process the job accordingly to its type. */
|
||||||
if (type == BIO_CLOSE_FILE) {
|
if (type == BIO_CLOSE_FILE) {
|
||||||
close((long)job->arg1);
|
close(job->fd);
|
||||||
} else if (type == BIO_AOF_FSYNC) {
|
} else if (type == BIO_AOF_FSYNC) {
|
||||||
redis_fsync((long)job->arg1);
|
redis_fsync(job->fd);
|
||||||
} else if (type == BIO_LAZY_FREE) {
|
} else if (type == BIO_LAZY_FREE) {
|
||||||
/* What we free changes depending on what arguments are set:
|
job->free_fn(job->free_args);
|
||||||
* arg1 -> free the object at pointer.
|
|
||||||
* arg2 & arg3 -> free two dictionaries (a Redis DB).
|
|
||||||
* only arg3 -> free the radix tree. */
|
|
||||||
if (job->arg1)
|
|
||||||
lazyfreeFreeObjectFromBioThread(job->arg1);
|
|
||||||
else if (job->arg2 && job->arg3)
|
|
||||||
lazyfreeFreeDatabaseFromBioThread(job->arg2,job->arg3);
|
|
||||||
else if (job->arg3)
|
|
||||||
lazyfreeFreeSlotsMapFromBioThread(job->arg3);
|
|
||||||
} else {
|
} else {
|
||||||
serverPanic("Wrong job type in bioProcessBackgroundJobs().");
|
serverPanic("Wrong job type in bioProcessBackgroundJobs().");
|
||||||
}
|
}
|
||||||
|
@ -30,13 +30,17 @@
|
|||||||
#ifndef __BIO_H
|
#ifndef __BIO_H
|
||||||
#define __BIO_H
|
#define __BIO_H
|
||||||
|
|
||||||
|
typedef void lazy_free_fn(void *args[]);
|
||||||
|
|
||||||
/* Exported API */
|
/* Exported API */
|
||||||
void bioInit(void);
|
void bioInit(void);
|
||||||
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3);
|
|
||||||
unsigned long long bioPendingJobsOfType(int type);
|
unsigned long long bioPendingJobsOfType(int type);
|
||||||
unsigned long long bioWaitStepOfType(int type);
|
unsigned long long bioWaitStepOfType(int type);
|
||||||
time_t bioOlderJobOfType(int type);
|
time_t bioOlderJobOfType(int type);
|
||||||
void bioKillThreads(void);
|
void bioKillThreads(void);
|
||||||
|
void bioCreateCloseJob(int fd);
|
||||||
|
void bioCreateFsyncJob(int fd);
|
||||||
|
void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...);
|
||||||
|
|
||||||
/* Background job opcodes */
|
/* Background job opcodes */
|
||||||
#define BIO_CLOSE_FILE 0 /* Deferred close(2) syscall. */
|
#define BIO_CLOSE_FILE 0 /* Deferred close(2) syscall. */
|
||||||
|
@ -611,7 +611,7 @@ void bitopCommand(client *c) {
|
|||||||
else if((opname[0] == 'n' || opname[0] == 'N') && !strcasecmp(opname,"not"))
|
else if((opname[0] == 'n' || opname[0] == 'N') && !strcasecmp(opname,"not"))
|
||||||
op = BITOP_NOT;
|
op = BITOP_NOT;
|
||||||
else {
|
else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -813,7 +813,7 @@ void bitcountCommand(client *c) {
|
|||||||
end = strlen-1;
|
end = strlen-1;
|
||||||
} else {
|
} else {
|
||||||
/* Syntax error. */
|
/* Syntax error. */
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -878,7 +878,7 @@ void bitposCommand(client *c) {
|
|||||||
end = strlen-1;
|
end = strlen-1;
|
||||||
} else {
|
} else {
|
||||||
/* Syntax error. */
|
/* Syntax error. */
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -970,7 +970,7 @@ void bitfieldGeneric(client *c, int flags) {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
zfree(ops);
|
zfree(ops);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,12 @@ void blockClient(client *c, int btype) {
|
|||||||
server.blocked_clients++;
|
server.blocked_clients++;
|
||||||
server.blocked_clients_by_type[btype]++;
|
server.blocked_clients_by_type[btype]++;
|
||||||
addClientToTimeoutTable(c);
|
addClientToTimeoutTable(c);
|
||||||
|
if (btype == BLOCKED_PAUSE) {
|
||||||
|
listAddNodeTail(server.paused_clients, c);
|
||||||
|
c->paused_list_node = listLast(server.paused_clients);
|
||||||
|
/* Mark this client to execute its command */
|
||||||
|
c->flags |= CLIENT_PENDING_COMMAND;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function is called in the beforeSleep() function of the event loop
|
/* This function is called in the beforeSleep() function of the event loop
|
||||||
@ -110,6 +116,11 @@ void processUnblockedClients(void) {
|
|||||||
* client is not blocked before to proceed, but things may change and
|
* client is not blocked before to proceed, but things may change and
|
||||||
* the code is conceptually more correct this way. */
|
* the code is conceptually more correct this way. */
|
||||||
if (!(c->flags & CLIENT_BLOCKED)) {
|
if (!(c->flags & CLIENT_BLOCKED)) {
|
||||||
|
/* If we have a queued command, execute it now. */
|
||||||
|
if (processPendingCommandsAndResetClient(c) == C_ERR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* Then process client if it has more data in it's buffer. */
|
||||||
if (c->querybuf && sdslen(c->querybuf) > 0) {
|
if (c->querybuf && sdslen(c->querybuf) > 0) {
|
||||||
processInputBuffer(c);
|
processInputBuffer(c);
|
||||||
}
|
}
|
||||||
@ -154,6 +165,9 @@ void unblockClient(client *c) {
|
|||||||
} else if (c->btype == BLOCKED_MODULE) {
|
} else if (c->btype == BLOCKED_MODULE) {
|
||||||
if (moduleClientIsBlockedOnKeys(c)) unblockClientWaitingData(c);
|
if (moduleClientIsBlockedOnKeys(c)) unblockClientWaitingData(c);
|
||||||
unblockClientFromModule(c);
|
unblockClientFromModule(c);
|
||||||
|
} else if (c->btype == BLOCKED_PAUSE) {
|
||||||
|
listDelNode(server.paused_clients,c->paused_list_node);
|
||||||
|
c->paused_list_node = NULL;
|
||||||
} else {
|
} else {
|
||||||
serverPanic("Unknown btype in unblockClient().");
|
serverPanic("Unknown btype in unblockClient().");
|
||||||
}
|
}
|
||||||
@ -200,9 +214,16 @@ void disconnectAllBlockedClients(void) {
|
|||||||
client *c = listNodeValue(ln);
|
client *c = listNodeValue(ln);
|
||||||
|
|
||||||
if (c->flags & CLIENT_BLOCKED) {
|
if (c->flags & CLIENT_BLOCKED) {
|
||||||
addReplySds(c,sdsnew(
|
/* PAUSED clients are an exception, when they'll be unblocked, the
|
||||||
|
* command processing will start from scratch, and the command will
|
||||||
|
* be either executed or rejected. (unlike LIST blocked clients for
|
||||||
|
* which the command is already in progress in a way. */
|
||||||
|
if (c->btype == BLOCKED_PAUSE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
addReplyError(c,
|
||||||
"-UNBLOCKED force unblock from blocking operation, "
|
"-UNBLOCKED force unblock from blocking operation, "
|
||||||
"instance state changed (master -> replica?)\r\n"));
|
"instance state changed (master -> replica?)");
|
||||||
unblockClient(c);
|
unblockClient(c);
|
||||||
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,12 @@
|
|||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int process_type; /* AOF or RDB child? */
|
||||||
|
int on_exit; /* COW size of active or exited child */
|
||||||
|
size_t cow_size; /* Copy on write size. */
|
||||||
|
} child_info_data;
|
||||||
|
|
||||||
/* Open a child-parent channel used in order to move information about the
|
/* Open a child-parent channel used in order to move information about the
|
||||||
* RDB / AOF saving process from the child to the parent (for instance
|
* RDB / AOF saving process from the child to the parent (for instance
|
||||||
* the amount of copy on write memory used) */
|
* the amount of copy on write memory used) */
|
||||||
@ -41,7 +47,7 @@ void openChildInfoPipe(void) {
|
|||||||
} else if (anetNonBlock(NULL,server.child_info_pipe[0]) != ANET_OK) {
|
} else if (anetNonBlock(NULL,server.child_info_pipe[0]) != ANET_OK) {
|
||||||
closeChildInfoPipe();
|
closeChildInfoPipe();
|
||||||
} else {
|
} else {
|
||||||
memset(&server.child_info_data,0,sizeof(server.child_info_data));
|
server.child_info_nread = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,34 +60,76 @@ void closeChildInfoPipe(void) {
|
|||||||
close(server.child_info_pipe[1]);
|
close(server.child_info_pipe[1]);
|
||||||
server.child_info_pipe[0] = -1;
|
server.child_info_pipe[0] = -1;
|
||||||
server.child_info_pipe[1] = -1;
|
server.child_info_pipe[1] = -1;
|
||||||
|
server.child_info_nread = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send COW data to parent. The child should call this function after populating
|
/* Send COW data to parent. */
|
||||||
* the corresponding fields it want to sent (according to the process type). */
|
void sendChildInfo(int process_type, int on_exit, size_t cow_size) {
|
||||||
void sendChildInfo(int ptype) {
|
|
||||||
if (server.child_info_pipe[1] == -1) return;
|
if (server.child_info_pipe[1] == -1) return;
|
||||||
server.child_info_data.magic = CHILD_INFO_MAGIC;
|
|
||||||
server.child_info_data.process_type = ptype;
|
child_info_data buffer = {.process_type = process_type, .on_exit = on_exit, .cow_size = cow_size};
|
||||||
ssize_t wlen = sizeof(server.child_info_data);
|
ssize_t wlen = sizeof(buffer);
|
||||||
if (write(server.child_info_pipe[1],&server.child_info_data,wlen) != wlen) {
|
|
||||||
|
if (write(server.child_info_pipe[1],&buffer,wlen) != wlen) {
|
||||||
/* Nothing to do on error, this will be detected by the other side. */
|
/* Nothing to do on error, this will be detected by the other side. */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Receive COW data from parent. */
|
/* Update COW data. */
|
||||||
void receiveChildInfo(void) {
|
void updateChildInfo(int process_type, int on_exit, size_t cow_size) {
|
||||||
if (server.child_info_pipe[0] == -1) return;
|
if (!on_exit) {
|
||||||
ssize_t wlen = sizeof(server.child_info_data);
|
server.stat_current_cow_bytes = cow_size;
|
||||||
if (read(server.child_info_pipe[0],&server.child_info_data,wlen) == wlen &&
|
return;
|
||||||
server.child_info_data.magic == CHILD_INFO_MAGIC)
|
}
|
||||||
{
|
|
||||||
if (server.child_info_data.process_type == CHILD_TYPE_RDB) {
|
if (process_type == CHILD_TYPE_RDB) {
|
||||||
server.stat_rdb_cow_bytes = server.child_info_data.cow_size;
|
server.stat_rdb_cow_bytes = cow_size;
|
||||||
} else if (server.child_info_data.process_type == CHILD_TYPE_AOF) {
|
} else if (process_type == CHILD_TYPE_AOF) {
|
||||||
server.stat_aof_cow_bytes = server.child_info_data.cow_size;
|
server.stat_aof_cow_bytes = cow_size;
|
||||||
} else if (server.child_info_data.process_type == CHILD_TYPE_MODULE) {
|
} else if (process_type == CHILD_TYPE_MODULE) {
|
||||||
server.stat_module_cow_bytes = server.child_info_data.cow_size;
|
server.stat_module_cow_bytes = cow_size;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read COW info data from the pipe.
|
||||||
|
* if complete data read into the buffer, process type, copy-on-write type and copy-on-write size
|
||||||
|
* are stored into *process_type, *on_exit and *cow_size respectively and returns 1.
|
||||||
|
* otherwise, the partial data is left in the buffer, waiting for the next read, and returns 0. */
|
||||||
|
int readChildInfo(int *process_type, int *on_exit, size_t *cow_size) {
|
||||||
|
/* We are using here a static buffer in combination with the server.child_info_nread to handle short reads */
|
||||||
|
static child_info_data buffer;
|
||||||
|
ssize_t wlen = sizeof(buffer);
|
||||||
|
|
||||||
|
/* Do not overlap */
|
||||||
|
if (server.child_info_nread == wlen) server.child_info_nread = 0;
|
||||||
|
|
||||||
|
int nread = read(server.child_info_pipe[0], (char *)&buffer + server.child_info_nread, wlen - server.child_info_nread);
|
||||||
|
if (nread > 0) {
|
||||||
|
server.child_info_nread += nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We have complete child info */
|
||||||
|
if (server.child_info_nread == wlen) {
|
||||||
|
*process_type = buffer.process_type;
|
||||||
|
*on_exit = buffer.on_exit;
|
||||||
|
*cow_size = buffer.cow_size;
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Receive COW data from child. */
|
||||||
|
void receiveChildInfo(void) {
|
||||||
|
if (server.child_info_pipe[0] == -1) return;
|
||||||
|
|
||||||
|
int process_type;
|
||||||
|
int on_exit;
|
||||||
|
size_t cow_size;
|
||||||
|
|
||||||
|
/* Drain the pipe and update child info so that we get the final message. */
|
||||||
|
while (readChildInfo(&process_type, &on_exit, &cow_size)) {
|
||||||
|
updateChildInfo(process_type, on_exit, cow_size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
120
src/cluster.c
120
src/cluster.c
@ -47,7 +47,7 @@
|
|||||||
clusterNode *myself = NULL;
|
clusterNode *myself = NULL;
|
||||||
|
|
||||||
clusterNode *createClusterNode(char *nodename, int flags);
|
clusterNode *createClusterNode(char *nodename, int flags);
|
||||||
int clusterAddNode(clusterNode *node);
|
void clusterAddNode(clusterNode *node);
|
||||||
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||||
void clusterReadHandler(connection *conn);
|
void clusterReadHandler(connection *conn);
|
||||||
void clusterSendPing(clusterLink *link, int type);
|
void clusterSendPing(clusterLink *link, int type);
|
||||||
@ -961,12 +961,12 @@ void freeClusterNode(clusterNode *n) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Add a node to the nodes hash table */
|
/* Add a node to the nodes hash table */
|
||||||
int clusterAddNode(clusterNode *node) {
|
void clusterAddNode(clusterNode *node) {
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
retval = dictAdd(server.cluster->nodes,
|
retval = dictAdd(server.cluster->nodes,
|
||||||
sdsnewlen(node->name,CLUSTER_NAMELEN), node);
|
sdsnewlen(node->name,CLUSTER_NAMELEN), node);
|
||||||
return (retval == DICT_OK) ? C_OK : C_ERR;
|
serverAssert(retval == DICT_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove a node from the cluster. The function performs the high level
|
/* Remove a node from the cluster. The function performs the high level
|
||||||
@ -2164,7 +2164,7 @@ int clusterProcessPacket(clusterLink *link) {
|
|||||||
resetManualFailover();
|
resetManualFailover();
|
||||||
server.cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
|
server.cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
|
||||||
server.cluster->mf_slave = sender;
|
server.cluster->mf_slave = sender;
|
||||||
pauseClients(now+(CLUSTER_MF_TIMEOUT*CLUSTER_MF_PAUSE_MULT));
|
pauseClients(now+(CLUSTER_MF_TIMEOUT*CLUSTER_MF_PAUSE_MULT),CLIENT_PAUSE_WRITE);
|
||||||
serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
|
serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
|
||||||
sender->name);
|
sender->name);
|
||||||
/* We need to send a ping message to the replica, as it would carry
|
/* We need to send a ping message to the replica, as it would carry
|
||||||
@ -3421,9 +3421,8 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
|||||||
* The function can be used both to initialize the manual failover state at
|
* The function can be used both to initialize the manual failover state at
|
||||||
* startup or to abort a manual failover in progress. */
|
* startup or to abort a manual failover in progress. */
|
||||||
void resetManualFailover(void) {
|
void resetManualFailover(void) {
|
||||||
if (server.cluster->mf_end && clientsArePaused()) {
|
if (server.cluster->mf_end) {
|
||||||
server.clients_pause_end_time = 0;
|
checkClientPauseTimeoutAndReturnIfPaused();
|
||||||
clientsArePaused(); /* Just use the side effect of the function. */
|
|
||||||
}
|
}
|
||||||
server.cluster->mf_end = 0; /* No manual failover in progress. */
|
server.cluster->mf_end = 0; /* No manual failover in progress. */
|
||||||
server.cluster->mf_can_start = 0;
|
server.cluster->mf_can_start = 0;
|
||||||
@ -4357,28 +4356,49 @@ void clusterCommand(client *c) {
|
|||||||
|
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"ADDSLOTS <slot> [slot ...] -- Assign slots to current node.",
|
"ADDSLOTS <slot> [<slot> ...]",
|
||||||
"BUMPEPOCH -- Advance the cluster config epoch.",
|
" Assign slots to current node.",
|
||||||
"COUNT-failure-reports <node-id> -- Return number of failure reports for <node-id>.",
|
"BUMPEPOCH",
|
||||||
"COUNTKEYSINSLOT <slot> - Return the number of keys in <slot>.",
|
" Advance the cluster config epoch.",
|
||||||
"DELSLOTS <slot> [slot ...] -- Delete slots information from current node.",
|
"COUNT-FAILURE-REPORTS <node-id>",
|
||||||
"FAILOVER [force|takeover] -- Promote current replica node to being a master.",
|
" Return number of failure reports for <node-id>.",
|
||||||
"FORGET <node-id> -- Remove a node from the cluster.",
|
"COUNTKEYSINSLOT <slot>",
|
||||||
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
|
" Return the number of keys in <slot>.",
|
||||||
"FLUSHSLOTS -- Delete current node own slots information.",
|
"DELSLOTS <slot> [<slot> ...]",
|
||||||
"INFO - Return information about the cluster.",
|
" Delete slots information from current node.",
|
||||||
"KEYSLOT <key> -- Return the hash slot for <key>.",
|
"FAILOVER [FORCE|TAKEOVER]",
|
||||||
"MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
|
" Promote current replica node to being a master.",
|
||||||
"MYID -- Return the node id.",
|
"FORGET <node-id>",
|
||||||
"NODES -- Return cluster configuration seen by node. Output format:",
|
" Remove a node from the cluster.",
|
||||||
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ... <slot>",
|
"GETKEYSINSLOT <slot> <count>",
|
||||||
"REPLICATE <node-id> -- Configure current node as replica to <node-id>.",
|
" Return key names stored by current node in a slot.",
|
||||||
"RESET [hard|soft] -- Reset current node (default: soft).",
|
"FLUSHSLOTS",
|
||||||
"SET-config-epoch <epoch> - Set config epoch of current node.",
|
" Delete current node own slots information.",
|
||||||
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
|
"INFO",
|
||||||
"REPLICAS <node-id> -- Return <node-id> replicas.",
|
" Return information about the cluster.",
|
||||||
"SAVECONFIG - Force saving cluster configuration on disk.",
|
"KEYSLOT <key>",
|
||||||
"SLOTS -- Return information about slots range mappings. Each range is made of:",
|
" Return the hash slot for <key>.",
|
||||||
|
"MEET <ip> <port> [<bus-port>]",
|
||||||
|
" Connect nodes into a working cluster.",
|
||||||
|
"MYID",
|
||||||
|
" Return the node id.",
|
||||||
|
"NODES",
|
||||||
|
" Return cluster configuration seen by node. Output format:",
|
||||||
|
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ...",
|
||||||
|
"REPLICATE <node-id>",
|
||||||
|
" Configure current node as replica to <node-id>.",
|
||||||
|
"RESET [HARD|SOFT]",
|
||||||
|
" Reset current node (default: soft).",
|
||||||
|
"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",
|
" start, end, master and replicas IP addresses, ports and ids",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
@ -4820,7 +4840,7 @@ NULL
|
|||||||
takeover = 1;
|
takeover = 1;
|
||||||
force = 1; /* Takeover also implies force. */
|
force = 1; /* Takeover also implies force. */
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4911,7 +4931,7 @@ NULL
|
|||||||
} else if (!strcasecmp(c->argv[2]->ptr,"soft")) {
|
} else if (!strcasecmp(c->argv[2]->ptr,"soft")) {
|
||||||
hard = 0;
|
hard = 0;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5049,7 +5069,7 @@ void restoreCommand(client *c) {
|
|||||||
}
|
}
|
||||||
j++; /* Consume additional arg. */
|
j++; /* Consume additional arg. */
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5057,7 +5077,7 @@ void restoreCommand(client *c) {
|
|||||||
/* Make sure this key does not already exist here... */
|
/* Make sure this key does not already exist here... */
|
||||||
robj *key = c->argv[1];
|
robj *key = c->argv[1];
|
||||||
if (!replace && lookupKeyWrite(c->db,key) != NULL) {
|
if (!replace && lookupKeyWrite(c->db,key) != NULL) {
|
||||||
addReply(c,shared.busykeyerr);
|
addReplyErrorObject(c,shared.busykeyerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5170,8 +5190,7 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
|
|||||||
conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
|
conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
|
||||||
if (connBlockingConnect(conn, c->argv[1]->ptr, atoi(c->argv[2]->ptr), timeout)
|
if (connBlockingConnect(conn, c->argv[1]->ptr, atoi(c->argv[2]->ptr), timeout)
|
||||||
!= C_OK) {
|
!= C_OK) {
|
||||||
addReplySds(c,
|
addReplyError(c,"-IOERR error or timeout connecting to the client");
|
||||||
sdsnew("-IOERR error or timeout connecting to the client\r\n"));
|
|
||||||
connClose(conn);
|
connClose(conn);
|
||||||
sdsfree(name);
|
sdsfree(name);
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -5259,14 +5278,14 @@ void migrateCommand(client *c) {
|
|||||||
replace = 1;
|
replace = 1;
|
||||||
} else if (!strcasecmp(c->argv[j]->ptr,"auth")) {
|
} else if (!strcasecmp(c->argv[j]->ptr,"auth")) {
|
||||||
if (!moreargs) {
|
if (!moreargs) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
j++;
|
j++;
|
||||||
password = c->argv[j]->ptr;
|
password = c->argv[j]->ptr;
|
||||||
} else if (!strcasecmp(c->argv[j]->ptr,"auth2")) {
|
} else if (!strcasecmp(c->argv[j]->ptr,"auth2")) {
|
||||||
if (moreargs < 2) {
|
if (moreargs < 2) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
username = c->argv[++j]->ptr;
|
username = c->argv[++j]->ptr;
|
||||||
@ -5282,7 +5301,7 @@ void migrateCommand(client *c) {
|
|||||||
num_keys = c->argc - j - 1;
|
num_keys = c->argc - j - 1;
|
||||||
break; /* All the remaining args are keys. */
|
break; /* All the remaining args are keys. */
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5763,7 +5782,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
|||||||
* cluster is down. */
|
* cluster is down. */
|
||||||
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
|
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
|
||||||
return NULL;
|
return NULL;
|
||||||
} else if (!(cmd->flags & CMD_READONLY) && !(cmd->proc == evalCommand)
|
} else if ((cmd->flags & CMD_WRITE) && !(cmd->proc == evalCommand)
|
||||||
&& !(cmd->proc == evalShaCommand))
|
&& !(cmd->proc == evalShaCommand))
|
||||||
{
|
{
|
||||||
/* The cluster is configured to allow read only commands
|
/* The cluster is configured to allow read only commands
|
||||||
@ -5812,11 +5831,10 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
|||||||
/* Handle the read-only client case reading from a slave: if this
|
/* Handle the read-only client case reading from a slave: if this
|
||||||
* node is a slave and the request is about a hash slot our master
|
* node is a slave and the request is about a hash slot our master
|
||||||
* is serving, we can reply without redirection. */
|
* is serving, we can reply without redirection. */
|
||||||
int is_readonly_command = (c->cmd->flags & CMD_READONLY) ||
|
int is_write_command = (c->cmd->flags & CMD_WRITE) ||
|
||||||
(c->cmd->proc == execCommand && !(c->mstate.cmd_inv_flags & CMD_READONLY));
|
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE));
|
||||||
if (c->flags & CLIENT_READONLY &&
|
if (c->flags & CLIENT_READONLY &&
|
||||||
(is_readonly_command || cmd->proc == evalCommand ||
|
(!is_write_command || cmd->proc == evalCommand || cmd->proc == evalShaCommand) &&
|
||||||
cmd->proc == evalShaCommand) &&
|
|
||||||
nodeIsSlave(myself) &&
|
nodeIsSlave(myself) &&
|
||||||
myself->slaveof == n)
|
myself->slaveof == n)
|
||||||
{
|
{
|
||||||
@ -5838,23 +5856,23 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
|||||||
* be set to the hash slot that caused the redirection. */
|
* be set to the hash slot that caused the redirection. */
|
||||||
void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code) {
|
void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code) {
|
||||||
if (error_code == CLUSTER_REDIR_CROSS_SLOT) {
|
if (error_code == CLUSTER_REDIR_CROSS_SLOT) {
|
||||||
addReplySds(c,sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n"));
|
addReplyError(c,"-CROSSSLOT Keys in request don't hash to the same slot");
|
||||||
} else if (error_code == CLUSTER_REDIR_UNSTABLE) {
|
} else if (error_code == CLUSTER_REDIR_UNSTABLE) {
|
||||||
/* The request spawns multiple keys in the same slot,
|
/* The request spawns multiple keys in the same slot,
|
||||||
* but the slot is not "stable" currently as there is
|
* but the slot is not "stable" currently as there is
|
||||||
* a migration or import in progress. */
|
* a migration or import in progress. */
|
||||||
addReplySds(c,sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n"));
|
addReplyError(c,"-TRYAGAIN Multiple keys request during rehashing of slot");
|
||||||
} else if (error_code == CLUSTER_REDIR_DOWN_STATE) {
|
} else if (error_code == CLUSTER_REDIR_DOWN_STATE) {
|
||||||
addReplySds(c,sdsnew("-CLUSTERDOWN The cluster is down\r\n"));
|
addReplyError(c,"-CLUSTERDOWN The cluster is down");
|
||||||
} else if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) {
|
} else if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) {
|
||||||
addReplySds(c,sdsnew("-CLUSTERDOWN The cluster is down and only accepts read commands\r\n"));
|
addReplyError(c,"-CLUSTERDOWN The cluster is down and only accepts read commands");
|
||||||
} else if (error_code == CLUSTER_REDIR_DOWN_UNBOUND) {
|
} else if (error_code == CLUSTER_REDIR_DOWN_UNBOUND) {
|
||||||
addReplySds(c,sdsnew("-CLUSTERDOWN Hash slot not served\r\n"));
|
addReplyError(c,"-CLUSTERDOWN Hash slot not served");
|
||||||
} else if (error_code == CLUSTER_REDIR_MOVED ||
|
} else if (error_code == CLUSTER_REDIR_MOVED ||
|
||||||
error_code == CLUSTER_REDIR_ASK)
|
error_code == CLUSTER_REDIR_ASK)
|
||||||
{
|
{
|
||||||
addReplySds(c,sdscatprintf(sdsempty(),
|
addReplyErrorSds(c,sdscatprintf(sdsempty(),
|
||||||
"-%s %d %s:%d\r\n",
|
"-%s %d %s:%d",
|
||||||
(error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
|
(error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
|
||||||
hashslot,n->ip,n->port));
|
hashslot,n->ip,n->port));
|
||||||
} else {
|
} else {
|
||||||
@ -5901,7 +5919,7 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
|||||||
/* if the client is read-only and attempting to access key that our
|
/* if the client is read-only and attempting to access key that our
|
||||||
* replica can handle, allow it. */
|
* replica can handle, allow it. */
|
||||||
if ((c->flags & CLIENT_READONLY) &&
|
if ((c->flags & CLIENT_READONLY) &&
|
||||||
(c->lastcmd->flags & CMD_READONLY) &&
|
!(c->lastcmd->flags & CMD_WRITE) &&
|
||||||
nodeIsSlave(myself) && myself->slaveof == node)
|
nodeIsSlave(myself) && myself->slaveof == node)
|
||||||
{
|
{
|
||||||
node = myself;
|
node = myself;
|
||||||
|
@ -40,7 +40,7 @@ typedef struct clusterLink {
|
|||||||
sds sndbuf; /* Packet send buffer */
|
sds sndbuf; /* Packet send buffer */
|
||||||
char *rcvbuf; /* Packet reception buffer */
|
char *rcvbuf; /* Packet reception buffer */
|
||||||
size_t rcvbuf_len; /* Used size of rcvbuf */
|
size_t rcvbuf_len; /* Used size of rcvbuf */
|
||||||
size_t rcvbuf_alloc; /* Used size of rcvbuf */
|
size_t rcvbuf_alloc; /* Allocated size of rcvbuf */
|
||||||
struct clusterNode *node; /* Node related to this link if any, or NULL */
|
struct clusterNode *node; /* Node related to this link if any, or NULL */
|
||||||
} clusterLink;
|
} clusterLink;
|
||||||
|
|
||||||
|
124
src/config.c
124
src/config.c
@ -166,6 +166,15 @@ typedef struct stringConfigData {
|
|||||||
be stored as a NULL value. */
|
be stored as a NULL value. */
|
||||||
} stringConfigData;
|
} stringConfigData;
|
||||||
|
|
||||||
|
typedef struct sdsConfigData {
|
||||||
|
sds *config; /* Pointer to the server config this value is stored in. */
|
||||||
|
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 (*update_fn)(sds val, sds prev, 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
|
||||||
|
be stored as a NULL value. */
|
||||||
|
} sdsConfigData;
|
||||||
|
|
||||||
typedef struct enumConfigData {
|
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 */
|
||||||
@ -212,6 +221,7 @@ typedef struct numericConfigData {
|
|||||||
typedef union typeData {
|
typedef union typeData {
|
||||||
boolConfigData yesno;
|
boolConfigData yesno;
|
||||||
stringConfigData string;
|
stringConfigData string;
|
||||||
|
sdsConfigData sds;
|
||||||
enumConfigData enumd;
|
enumConfigData enumd;
|
||||||
numericConfigData numeric;
|
numericConfigData numeric;
|
||||||
} typeData;
|
} typeData;
|
||||||
@ -512,7 +522,7 @@ void loadServerConfigFromString(char *config) {
|
|||||||
}
|
}
|
||||||
server.repl_state = REPL_STATE_CONNECT;
|
server.repl_state = REPL_STATE_CONNECT;
|
||||||
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
|
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
|
||||||
if (strlen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
|
if (sdslen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
|
||||||
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
|
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
|
||||||
goto loaderr;
|
goto loaderr;
|
||||||
}
|
}
|
||||||
@ -524,10 +534,10 @@ void loadServerConfigFromString(char *config) {
|
|||||||
sdsfree(server.requirepass);
|
sdsfree(server.requirepass);
|
||||||
server.requirepass = NULL;
|
server.requirepass = NULL;
|
||||||
if (sdslen(argv[1])) {
|
if (sdslen(argv[1])) {
|
||||||
sds aclop = sdscatprintf(sdsempty(),">%s",argv[1]);
|
sds aclop = sdscatlen(sdsnew(">"), argv[1], sdslen(argv[1]));
|
||||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||||
sdsfree(aclop);
|
sdsfree(aclop);
|
||||||
server.requirepass = sdsnew(argv[1]);
|
server.requirepass = sdsdup(argv[1]);
|
||||||
} else {
|
} else {
|
||||||
ACLSetUser(DefaultUser,"nopass",-1);
|
ACLSetUser(DefaultUser,"nopass",-1);
|
||||||
}
|
}
|
||||||
@ -751,10 +761,10 @@ void configSetCommand(client *c) {
|
|||||||
sdsfree(server.requirepass);
|
sdsfree(server.requirepass);
|
||||||
server.requirepass = NULL;
|
server.requirepass = NULL;
|
||||||
if (sdslen(o->ptr)) {
|
if (sdslen(o->ptr)) {
|
||||||
sds aclop = sdscatprintf(sdsempty(),">%s",(char*)o->ptr);
|
sds aclop = sdscatlen(sdsnew(">"), o->ptr, sdslen(o->ptr));
|
||||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||||
sdsfree(aclop);
|
sdsfree(aclop);
|
||||||
server.requirepass = sdsnew(o->ptr);
|
server.requirepass = sdsdup(o->ptr);
|
||||||
} else {
|
} else {
|
||||||
ACLSetUser(DefaultUser,"nopass",-1);
|
ACLSetUser(DefaultUser,"nopass",-1);
|
||||||
}
|
}
|
||||||
@ -905,7 +915,7 @@ badfmt: /* Bad format errors */
|
|||||||
addReplyBulkCString(c,_var ? _var : ""); \
|
addReplyBulkCString(c,_var ? _var : ""); \
|
||||||
matches++; \
|
matches++; \
|
||||||
} \
|
} \
|
||||||
} while(0);
|
} while(0)
|
||||||
|
|
||||||
#define config_get_bool_field(_name,_var) do { \
|
#define config_get_bool_field(_name,_var) do { \
|
||||||
if (stringmatch(pattern,_name,1)) { \
|
if (stringmatch(pattern,_name,1)) { \
|
||||||
@ -913,7 +923,7 @@ badfmt: /* Bad format errors */
|
|||||||
addReplyBulkCString(c,_var ? "yes" : "no"); \
|
addReplyBulkCString(c,_var ? "yes" : "no"); \
|
||||||
matches++; \
|
matches++; \
|
||||||
} \
|
} \
|
||||||
} while(0);
|
} while(0)
|
||||||
|
|
||||||
#define config_get_numerical_field(_name,_var) do { \
|
#define config_get_numerical_field(_name,_var) do { \
|
||||||
if (stringmatch(pattern,_name,1)) { \
|
if (stringmatch(pattern,_name,1)) { \
|
||||||
@ -922,8 +932,7 @@ badfmt: /* Bad format errors */
|
|||||||
addReplyBulkCString(c,buf); \
|
addReplyBulkCString(c,buf); \
|
||||||
matches++; \
|
matches++; \
|
||||||
} \
|
} \
|
||||||
} while(0);
|
} while(0)
|
||||||
|
|
||||||
|
|
||||||
void configGetCommand(client *c) {
|
void configGetCommand(client *c) {
|
||||||
robj *o = c->argv[2];
|
robj *o = c->argv[2];
|
||||||
@ -1330,6 +1339,28 @@ void rewriteConfigStringOption(struct rewriteConfigState *state, const char *opt
|
|||||||
rewriteConfigRewriteLine(state,option,line,force);
|
rewriteConfigRewriteLine(state,option,line,force);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Rewrite a SDS string option. */
|
||||||
|
void rewriteConfigSdsOption(struct rewriteConfigState *state, const char *option, sds value, const sds defvalue) {
|
||||||
|
int force = 1;
|
||||||
|
sds line;
|
||||||
|
|
||||||
|
/* If there is no value set, we don't want the SDS option
|
||||||
|
* to be present in the configuration at all. */
|
||||||
|
if (value == NULL) {
|
||||||
|
rewriteConfigMarkAsProcessed(state, option);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set force to zero if the value is set to its default. */
|
||||||
|
if (defvalue && sdscmp(value, defvalue) == 0) force = 0;
|
||||||
|
|
||||||
|
line = sdsnew(option);
|
||||||
|
line = sdscatlen(line, " ", 1);
|
||||||
|
line = sdscatrepr(line, value, sdslen(value));
|
||||||
|
|
||||||
|
rewriteConfigRewriteLine(state, option, line, force);
|
||||||
|
}
|
||||||
|
|
||||||
/* Rewrite a numerical (long long range) option. */
|
/* Rewrite a numerical (long long range) option. */
|
||||||
void rewriteConfigNumericalOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) {
|
void rewriteConfigNumericalOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) {
|
||||||
int force = value != defvalue;
|
int force = value != defvalue;
|
||||||
@ -1802,22 +1833,14 @@ static void boolConfigRewrite(typeData data, const char *name, struct rewriteCon
|
|||||||
|
|
||||||
/* String Configs */
|
/* String Configs */
|
||||||
static void stringConfigInit(typeData data) {
|
static void stringConfigInit(typeData data) {
|
||||||
if (data.string.convert_empty_to_null) {
|
*data.string.config = (data.string.convert_empty_to_null && !data.string.default_value) ? NULL : zstrdup(data.string.default_value);
|
||||||
*data.string.config = data.string.default_value ? zstrdup(data.string.default_value) : NULL;
|
|
||||||
} else {
|
|
||||||
*data.string.config = 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, 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;
|
||||||
if (data.string.convert_empty_to_null) {
|
*data.string.config = (data.string.convert_empty_to_null && !value[0]) ? NULL : zstrdup(value);
|
||||||
*data.string.config = value[0] ? zstrdup(value) : NULL;
|
|
||||||
} else {
|
|
||||||
*data.string.config = zstrdup(value);
|
|
||||||
}
|
|
||||||
if (update && data.string.update_fn && !data.string.update_fn(*data.string.config, prev, err)) {
|
if (update && data.string.update_fn && !data.string.update_fn(*data.string.config, prev, err)) {
|
||||||
zfree(*data.string.config);
|
zfree(*data.string.config);
|
||||||
*data.string.config = prev;
|
*data.string.config = prev;
|
||||||
@ -1835,6 +1858,38 @@ static void stringConfigRewrite(typeData data, const char *name, struct rewriteC
|
|||||||
rewriteConfigStringOption(state, name,*(data.string.config), data.string.default_value);
|
rewriteConfigStringOption(state, name,*(data.string.config), data.string.default_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* SDS Configs */
|
||||||
|
static void sdsConfigInit(typeData data) {
|
||||||
|
*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) {
|
||||||
|
if (data.sds.is_valid_fn && !data.sds.is_valid_fn(value, err))
|
||||||
|
return 0;
|
||||||
|
sds prev = *data.sds.config;
|
||||||
|
*data.sds.config = (data.sds.convert_empty_to_null && (sdslen(value) == 0)) ? NULL : sdsdup(value);
|
||||||
|
if (update && data.sds.update_fn && !data.sds.update_fn(*data.sds.config, prev, err)) {
|
||||||
|
sdsfree(*data.sds.config);
|
||||||
|
*data.sds.config = prev;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
sdsfree(prev);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sdsConfigGet(client *c, typeData data) {
|
||||||
|
if (*data.sds.config) {
|
||||||
|
addReplyBulkSds(c, sdsdup(*data.sds.config));
|
||||||
|
} else {
|
||||||
|
addReplyBulkCString(c, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) {
|
||||||
|
rewriteConfigSdsOption(state, name, *(data.sds.config), data.sds.default_value ? sdsnew(data.sds.default_value) : NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#define ALLOW_EMPTY_STRING 0
|
#define ALLOW_EMPTY_STRING 0
|
||||||
#define EMPTY_STRING_IS_NULL 1
|
#define EMPTY_STRING_IS_NULL 1
|
||||||
|
|
||||||
@ -1850,6 +1905,18 @@ static void stringConfigRewrite(typeData data, const char *name, struct rewriteC
|
|||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define createSDSConfig(name, alias, modifiable, empty_to_null, config_addr, default, is_valid, update) { \
|
||||||
|
embedCommonConfig(name, alias, modifiable) \
|
||||||
|
embedConfigInterface(sdsConfigInit, sdsConfigSet, sdsConfigGet, sdsConfigRewrite) \
|
||||||
|
.data.sds = { \
|
||||||
|
.config = &(config_addr), \
|
||||||
|
.default_value = (default), \
|
||||||
|
.is_valid_fn = (is_valid), \
|
||||||
|
.update_fn = (update), \
|
||||||
|
.convert_empty_to_null = (empty_to_null), \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
/* Enum configs */
|
/* Enum configs */
|
||||||
static void enumConfigInit(typeData data) {
|
static void enumConfigInit(typeData data) {
|
||||||
*data.enumd.config = data.enumd.default_value;
|
*data.enumd.config = data.enumd.default_value;
|
||||||
@ -2349,7 +2416,6 @@ standardConfig configs[] = {
|
|||||||
createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.pidfile, NULL, NULL, NULL),
|
createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.pidfile, NULL, NULL, NULL),
|
||||||
createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.slave_announce_ip, NULL, NULL, NULL),
|
createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.slave_announce_ip, NULL, NULL, NULL),
|
||||||
createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.masteruser, NULL, NULL, NULL),
|
createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.masteruser, NULL, NULL, NULL),
|
||||||
createStringConfig("masterauth", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.masterauth, NULL, NULL, NULL),
|
|
||||||
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, NULL, NULL),
|
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, NULL, NULL),
|
||||||
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.syslog_ident, "redis", NULL, NULL),
|
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.syslog_ident, "redis", NULL, NULL),
|
||||||
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb", isValidDBfilename, NULL),
|
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb", isValidDBfilename, NULL),
|
||||||
@ -2358,6 +2424,10 @@ standardConfig configs[] = {
|
|||||||
createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bio_cpulist, NULL, NULL, NULL),
|
createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bio_cpulist, NULL, NULL, NULL),
|
||||||
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),
|
||||||
|
|
||||||
|
/* SDS Configs */
|
||||||
|
createSDSConfig("masterauth", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.masterauth, NULL, NULL, NULL),
|
||||||
|
|
||||||
/* Enum Configs */
|
/* Enum Configs */
|
||||||
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, server.supervised_mode, SUPERVISED_NONE, NULL, NULL),
|
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, server.supervised_mode, SUPERVISED_NONE, NULL, NULL),
|
||||||
@ -2477,12 +2547,17 @@ void configCommand(client *c) {
|
|||||||
|
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"GET <pattern> -- Return parameters matching the glob-like <pattern> and their values.",
|
"GET <pattern>",
|
||||||
"SET <parameter> <value> -- Set parameter to value.",
|
" Return parameters matching the glob-like <pattern> and their values.",
|
||||||
"RESETSTAT -- Reset statistics reported by INFO.",
|
"SET <directive> <value>",
|
||||||
"REWRITE -- Rewrite the configuration file.",
|
" Set the configuration <directive> to <value>.",
|
||||||
|
"RESETSTAT",
|
||||||
|
" Reset statistics reported by the INFO command.",
|
||||||
|
"REWRITE",
|
||||||
|
" Rewrite the configuration file.",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"set") && c->argc == 4) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"set") && c->argc == 4) {
|
||||||
configSetCommand(c);
|
configSetCommand(c);
|
||||||
@ -2491,6 +2566,7 @@ NULL
|
|||||||
} else if (!strcasecmp(c->argv[1]->ptr,"resetstat") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"resetstat") && c->argc == 2) {
|
||||||
resetServerStats();
|
resetServerStats();
|
||||||
resetCommandTableStats();
|
resetCommandTableStats();
|
||||||
|
resetErrorTableStats();
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"rewrite") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"rewrite") && c->argc == 2) {
|
||||||
if (server.configfile == NULL) {
|
if (server.configfile == NULL) {
|
||||||
|
@ -134,7 +134,7 @@ int crc64Test(int argc, char *argv[]) {
|
|||||||
printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
|
printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
|
||||||
(uint64_t)_crc64(0, "123456789", 9));
|
(uint64_t)_crc64(0, "123456789", 9));
|
||||||
printf("[64speed]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
|
printf("[64speed]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
|
||||||
(uint64_t)crc64(0, "123456789", 9));
|
(uint64_t)crc64(0, (unsigned char*)"123456789", 9));
|
||||||
char li[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed "
|
char li[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed "
|
||||||
"do eiusmod tempor incididunt ut labore et dolore magna "
|
"do eiusmod tempor incididunt ut labore et dolore magna "
|
||||||
"aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
|
"aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
|
||||||
@ -146,7 +146,7 @@ int crc64Test(int argc, char *argv[]) {
|
|||||||
printf("[calcula]: c7794709e69683b3 == %016" PRIx64 "\n",
|
printf("[calcula]: c7794709e69683b3 == %016" PRIx64 "\n",
|
||||||
(uint64_t)_crc64(0, li, sizeof(li)));
|
(uint64_t)_crc64(0, li, sizeof(li)));
|
||||||
printf("[64speed]: c7794709e69683b3 == %016" PRIx64 "\n",
|
printf("[64speed]: c7794709e69683b3 == %016" PRIx64 "\n",
|
||||||
(uint64_t)crc64(0, li, sizeof(li)));
|
(uint64_t)crc64(0, (unsigned char*)li, sizeof(li)));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,8 @@ void crcspeed64little_init(crcfn64 crcfn, uint64_t table[8][256]) {
|
|||||||
|
|
||||||
/* generate CRCs for all single byte sequences */
|
/* generate CRCs for all single byte sequences */
|
||||||
for (int n = 0; n < 256; n++) {
|
for (int n = 0; n < 256; n++) {
|
||||||
table[0][n] = crcfn(0, &n, 1);
|
unsigned char v = n;
|
||||||
|
table[0][n] = crcfn(0, &v, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* generate nested CRC table for future slice-by-8 lookup */
|
/* generate nested CRC table for future slice-by-8 lookup */
|
||||||
|
53
src/db.c
53
src/db.c
@ -433,7 +433,7 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
|||||||
/* Make sure the WATCHed keys are affected by the FLUSH* commands.
|
/* Make sure the WATCHed keys are affected by the FLUSH* commands.
|
||||||
* Note that we need to call the function while the keys are still
|
* Note that we need to call the function while the keys are still
|
||||||
* there. */
|
* there. */
|
||||||
signalFlushedDb(dbnum);
|
signalFlushedDb(dbnum, async);
|
||||||
|
|
||||||
/* Empty redis database structure. */
|
/* Empty redis database structure. */
|
||||||
removed = emptyDbStructure(server.db, dbnum, async, callback);
|
removed = emptyDbStructure(server.db, dbnum, async, callback);
|
||||||
@ -572,9 +572,20 @@ void signalModifiedKey(client *c, redisDb *db, robj *key) {
|
|||||||
trackingInvalidateKey(c,key);
|
trackingInvalidateKey(c,key);
|
||||||
}
|
}
|
||||||
|
|
||||||
void signalFlushedDb(int dbid) {
|
void signalFlushedDb(int dbid, int async) {
|
||||||
touchWatchedKeysOnFlush(dbid);
|
int startdb, enddb;
|
||||||
trackingInvalidateKeysOnFlush(dbid);
|
if (dbid == -1) {
|
||||||
|
startdb = 0;
|
||||||
|
enddb = server.dbnum-1;
|
||||||
|
} else {
|
||||||
|
startdb = enddb = dbid;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = startdb; j <= enddb; j++) {
|
||||||
|
touchAllWatchedKeysInDb(&server.db[j], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackingInvalidateKeysOnFlush(async);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------------
|
/*-----------------------------------------------------------------------------
|
||||||
@ -593,7 +604,7 @@ int getFlushCommandFlags(client *c, int *flags) {
|
|||||||
/* Parse the optional ASYNC option. */
|
/* Parse the optional ASYNC option. */
|
||||||
if (c->argc > 1) {
|
if (c->argc > 1) {
|
||||||
if (c->argc > 2 || strcasecmp(c->argv[1]->ptr,"async")) {
|
if (c->argc > 2 || strcasecmp(c->argv[1]->ptr,"async")) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
*flags = EMPTYDB_ASYNC;
|
*flags = EMPTYDB_ASYNC;
|
||||||
@ -606,7 +617,7 @@ int getFlushCommandFlags(client *c, int *flags) {
|
|||||||
/* Flushes the whole server data set. */
|
/* Flushes the whole server data set. */
|
||||||
void flushAllDataAndResetRDB(int flags) {
|
void flushAllDataAndResetRDB(int flags) {
|
||||||
server.dirty += emptyDb(-1,flags,NULL);
|
server.dirty += emptyDb(-1,flags,NULL);
|
||||||
if (server.rdb_child_pid != -1) killRDBChild();
|
if (server.child_type == CHILD_TYPE_RDB) killRDBChild();
|
||||||
if (server.saveparamslen > 0) {
|
if (server.saveparamslen > 0) {
|
||||||
/* Normally rdbSave() will reset dirty, but we don't want this here
|
/* Normally rdbSave() will reset dirty, but we don't want this here
|
||||||
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
|
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
|
||||||
@ -616,6 +627,9 @@ void flushAllDataAndResetRDB(int flags) {
|
|||||||
rdbSave(server.rdb_filename,rsiptr);
|
rdbSave(server.rdb_filename,rsiptr);
|
||||||
server.dirty = saved_dirty;
|
server.dirty = saved_dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Without that extra dirty++, when db was already empty, FLUSHALL will
|
||||||
|
* not be replicated nor put into the AOF. */
|
||||||
server.dirty++;
|
server.dirty++;
|
||||||
#if defined(USE_JEMALLOC)
|
#if defined(USE_JEMALLOC)
|
||||||
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
||||||
@ -839,7 +853,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (count < 1) {
|
if (count < 1) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -858,7 +872,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
|||||||
typename = c->argv[i+1]->ptr;
|
typename = c->argv[i+1]->ptr;
|
||||||
i+= 2;
|
i+= 2;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1047,7 +1061,7 @@ void shutdownCommand(client *c) {
|
|||||||
int flags = 0;
|
int flags = 0;
|
||||||
|
|
||||||
if (c->argc > 2) {
|
if (c->argc > 2) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
} else if (c->argc == 2) {
|
} else if (c->argc == 2) {
|
||||||
if (!strcasecmp(c->argv[1]->ptr,"nosave")) {
|
if (!strcasecmp(c->argv[1]->ptr,"nosave")) {
|
||||||
@ -1055,7 +1069,7 @@ void shutdownCommand(client *c) {
|
|||||||
} else if (!strcasecmp(c->argv[1]->ptr,"save")) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"save")) {
|
||||||
flags |= SHUTDOWN_SAVE;
|
flags |= SHUTDOWN_SAVE;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1141,7 +1155,7 @@ void moveCommand(client *c) {
|
|||||||
/* If the user is moving using as target the same
|
/* If the user is moving using as target the same
|
||||||
* DB as the source DB it is probably an error. */
|
* DB as the source DB it is probably an error. */
|
||||||
if (src == dst) {
|
if (src == dst) {
|
||||||
addReply(c,shared.sameobjecterr);
|
addReplyErrorObject(c,shared.sameobjecterr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1205,7 +1219,7 @@ void copyCommand(client *c) {
|
|||||||
selectDb(c,srcid); /* Back to the source DB */
|
selectDb(c,srcid); /* Back to the source DB */
|
||||||
j++; /* Consume additional arg. */
|
j++; /* Consume additional arg. */
|
||||||
} else {
|
} else {
|
||||||
addReply(c, shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1221,7 +1235,7 @@ void copyCommand(client *c) {
|
|||||||
robj *key = c->argv[1];
|
robj *key = c->argv[1];
|
||||||
robj *newkey = c->argv[2];
|
robj *newkey = c->argv[2];
|
||||||
if (src == dst && (sdscmp(key->ptr, newkey->ptr) == 0)) {
|
if (src == dst && (sdscmp(key->ptr, newkey->ptr) == 0)) {
|
||||||
addReply(c,shared.sameobjecterr);
|
addReplyErrorObject(c,shared.sameobjecterr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1328,9 +1342,14 @@ int dbSwapDatabases(long id1, long id2) {
|
|||||||
* However normally we only do this check for efficiency reasons
|
* However normally we only do this check for efficiency reasons
|
||||||
* in dbAdd() when a list is created. So here we need to rescan
|
* in dbAdd() when a list is created. So here we need to rescan
|
||||||
* the list of clients blocked on lists and signal lists as ready
|
* the list of clients blocked on lists and signal lists as ready
|
||||||
* if needed. */
|
* if needed.
|
||||||
|
*
|
||||||
|
* Also the swapdb should make transaction fail if there is any
|
||||||
|
* client watching keys */
|
||||||
scanDatabaseForReadyLists(db1);
|
scanDatabaseForReadyLists(db1);
|
||||||
|
touchAllWatchedKeysInDb(db1, db2);
|
||||||
scanDatabaseForReadyLists(db2);
|
scanDatabaseForReadyLists(db2);
|
||||||
|
touchAllWatchedKeysInDb(db2, db1);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1501,6 +1520,12 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
|||||||
* we think the key is expired at this time. */
|
* we think the key is expired at this time. */
|
||||||
if (server.masterhost != NULL) return 1;
|
if (server.masterhost != NULL) return 1;
|
||||||
|
|
||||||
|
/* If clients are paused, we keep the current dataset constant,
|
||||||
|
* but return to the client what we believe is the right state. Typically,
|
||||||
|
* at the end of the pause we will properly expire the key OR we will
|
||||||
|
* have failed over and the new primary will send us the expire. */
|
||||||
|
if (checkClientPauseTimeoutAndReturnIfPaused()) return 1;
|
||||||
|
|
||||||
/* Delete the key */
|
/* Delete the key */
|
||||||
server.stat_expiredkeys++;
|
server.stat_expiredkeys++;
|
||||||
propagateExpire(db,key,server.lazyfree_lazy_expire);
|
propagateExpire(db,key,server.lazyfree_lazy_expire);
|
||||||
|
123
src/debug.c
123
src/debug.c
@ -381,39 +381,88 @@ void mallctl_string(client *c, robj **argv, int argc) {
|
|||||||
void debugCommand(client *c) {
|
void debugCommand(client *c) {
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"ASSERT -- Crash by assertion failed.",
|
"AOF-FLUSH-SLEEP <microsec>",
|
||||||
"CHANGE-REPL-ID -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.",
|
" Server will sleep before flushing the AOF, this is used for testing.",
|
||||||
"CRASH-AND-RECOVER <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
|
"ASSERT",
|
||||||
"DIGEST -- Output a hex signature representing the current DB content.",
|
" Crash by assertion failed.",
|
||||||
"DIGEST-VALUE <key-1> ... <key-N>-- Output a hex signature of the values of all the specified keys.",
|
"CHANGE-REPL-ID"
|
||||||
"DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false]",
|
" Change the replication IDs of the instance.",
|
||||||
"ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
|
" Dangerous: should be used only for testing the replication subsystem.",
|
||||||
"LOG <message> -- write message to the server log.",
|
"CONFIG-REWRITE-FORCE-ALL",
|
||||||
"LEAK <string> -- Create a memory leak of the input string.",
|
" Like CONFIG REWRITE but writes all configuration options, including",
|
||||||
"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.",
|
" keywords not listed in original configuration file or default values.",
|
||||||
"HTSTATS-KEY <key> -- Like htstats but for the hash table stored as key's value.",
|
"CRASH-AND-RECOVER <milliseconds>",
|
||||||
"LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.",
|
" Hard crash and restart after a <milliseconds> delay.",
|
||||||
"LUA-ALWAYS-REPLICATE-COMMANDS <0|1> -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.",
|
"DIGEST",
|
||||||
"OBJECT <key> -- Show low level info about key and associated value.",
|
" Output a hex signature representing the current DB content.",
|
||||||
"OOM -- Crash the server simulating an out-of-memory error.",
|
"DIGEST-VALUE <key> [<key> ...]",
|
||||||
"PANIC -- Crash the server simulating a panic.",
|
" Output a hex signature of the values of all the specified keys.",
|
||||||
"POPULATE <count> [prefix] [size] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.",
|
"ERROR <string>",
|
||||||
"RELOAD [MERGE] [NOFLUSH] [NOSAVE] -- Save the RDB on disk and reload it back in memory. By default it will save the RDB file and load it back. With the NOFLUSH option the current database is not removed before loading the new one, but conflicts in keys will kill the server with an exception. When MERGE is used, conflicting keys will be loaded (the key in the loaded RDB file will win). When NOSAVE is used, the server will not save the current dataset in the RDB file before loading. Use DEBUG RELOAD NOSAVE when you want just to load the RDB file you placed in the Redis working directory in order to replace the current dataset in memory. Use DEBUG RELOAD NOSAVE NOFLUSH MERGE when you want to add what is in the current RDB file placed in the Redis current directory, with the current memory content. Use DEBUG RELOAD when you want to verify Redis is able to persist the current dataset in the RDB file, flush the memory content, and load it back.",
|
" Return a Redis protocol error with <string> as message. Useful for clients",
|
||||||
"RESTART -- Graceful restart: save config, db, restart.",
|
" unit tests to simulate Redis errors.",
|
||||||
"SDSLEN <key> -- Show low level SDS string info representing key and value.",
|
"LOG <message>",
|
||||||
"SEGFAULT -- Crash the server with sigsegv.",
|
" Write <message> to the server log.",
|
||||||
"SET-ACTIVE-EXPIRE <0|1> -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.",
|
"HTSTATS <dbid>",
|
||||||
"SET-SKIP-CHECKSUM-VALIDATION <0|1> -- Enables or disables checksum checks for rdb or RESTORE payload.",
|
" Return hash table statistics of the specified Redis database.",
|
||||||
"AOF-FLUSH-SLEEP <microsec> -- Server will sleep before flushing the AOF, this is used for testing",
|
"HTSTATS-KEY <key>",
|
||||||
"SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.",
|
" Like HTSTATS but for the hash table stored at <key>'s value.",
|
||||||
"STRUCTSIZE -- Return the size of different Redis core C structures.",
|
"LOADAOF",
|
||||||
"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
|
" Flush the AOF buffers on disk and reload the AOF in memory.",
|
||||||
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
|
"LUA-ALWAYS-REPLICATE-COMMANDS <0|1>",
|
||||||
"CONFIG-REWRITE-FORCE-ALL -- Like CONFIG REWRITE but writes all configuration options, including keywords not listed in original configuration file or default values.",
|
" Setting it to 1 makes Lua replication defaulting to replicating single",
|
||||||
|
" commands, without the script having to enable effects replication.",
|
||||||
#ifdef USE_JEMALLOC
|
#ifdef USE_JEMALLOC
|
||||||
"MALLCTL <key> [<val>] -- Get or set a malloc tunning integer.",
|
"MALLCTL <key> [<val>]",
|
||||||
"MALLCTL-STR <key> [<val>] -- Get or set a malloc tunning string.",
|
" Get or set a malloc tuning integer.",
|
||||||
|
"MALLCTL-STR <key> [<val>]",
|
||||||
|
" Get or set a malloc tuning string.",
|
||||||
#endif
|
#endif
|
||||||
|
"OBJECT <key>",
|
||||||
|
" Show low level info about `key` and associated value.",
|
||||||
|
"OOM",
|
||||||
|
" Crash the server simulating an out-of-memory error.",
|
||||||
|
"PANIC",
|
||||||
|
" Crash the server simulating a panic.",
|
||||||
|
"POPULATE <count> [<prefix>] [<size>]",
|
||||||
|
" Create <count> string keys named key:<num>. If <prefix> is specified then",
|
||||||
|
" it is used instead of the 'key' prefix.",
|
||||||
|
"DEBUG PROTOCOL <type>",
|
||||||
|
" Reply with a test value of the specified type. <type> can be: string,",
|
||||||
|
" integer, double, bignum, null, array, set, map, attrib, push, verbatim,",
|
||||||
|
" true, false.",
|
||||||
|
"RELOAD [option ...]",
|
||||||
|
" Save the RDB on disk and reload it back to memory. Valid <option> values:",
|
||||||
|
" * MERGE: conflicting keys will be loaded from RDB.",
|
||||||
|
" * NOFLUSH: the existing database will not be removed before load, but",
|
||||||
|
" conflicting keys will generate an exception and kill the server."
|
||||||
|
" * NOSAVE: the database will be loaded from an existing RDB file.",
|
||||||
|
" Examples:",
|
||||||
|
" * DEBUG RELOAD: verify that the server is able to persist, flsuh and reload",
|
||||||
|
" the database.",
|
||||||
|
" * DEBUG RELOAD NOSAVE: replace the current database with the contents of an",
|
||||||
|
" existing RDB file.",
|
||||||
|
" * DEBUG RELOAD NOSAVE NOFLUSH MERGE: add the contents of an existing RDB",
|
||||||
|
" file to the database.",
|
||||||
|
"RESTART",
|
||||||
|
" Graceful restart: save config, db, restart.",
|
||||||
|
"SDSLEN <key>",
|
||||||
|
" Show low level SDS string info representing `key` and value.",
|
||||||
|
"SEGFAULT",
|
||||||
|
" Crash the server with sigsegv.",
|
||||||
|
"SET-ACTIVE-EXPIRE <0|1>",
|
||||||
|
" Setting it to 0 disables expiring keys in background when they are not",
|
||||||
|
" accessed (otherwise the Redis behavior). Setting it to 1 reenables back the",
|
||||||
|
" default.",
|
||||||
|
"SET-SKIP-CHECKSUM-VALIDATION <0|1>",
|
||||||
|
" Enables or disables checksum checks for RDB files and RESTORE's payload.",
|
||||||
|
"SLEEP <seconds>",
|
||||||
|
" Stop the server for <seconds>. Decimals allowed.",
|
||||||
|
"STRINGMATCH-TEST",
|
||||||
|
" Run a fuzz tester against the stringmatchlen() function.",
|
||||||
|
"STRUCTSIZE",
|
||||||
|
" Return the size of different Redis core C structures.",
|
||||||
|
"ZIPLIST <key>",
|
||||||
|
" Show low level info about the ziplist encoding of <key>.",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
@ -474,7 +523,7 @@ NULL
|
|||||||
rdbSaveInfo rsi, *rsiptr;
|
rdbSaveInfo rsi, *rsiptr;
|
||||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||||
if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
|
if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
|
||||||
addReply(c,shared.err);
|
addReplyErrorObject(c,shared.err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -500,7 +549,7 @@ NULL
|
|||||||
int ret = loadAppendOnlyFile(server.aof_filename);
|
int ret = loadAppendOnlyFile(server.aof_filename);
|
||||||
unprotectClient(c);
|
unprotectClient(c);
|
||||||
if (ret != C_OK) {
|
if (ret != C_OK) {
|
||||||
addReply(c,shared.err);
|
addReplyErrorObject(c,shared.err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
server.dirty = 0; /* Prevent AOF / replication */
|
server.dirty = 0; /* Prevent AOF / replication */
|
||||||
@ -512,7 +561,7 @@ NULL
|
|||||||
char *strenc;
|
char *strenc;
|
||||||
|
|
||||||
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
||||||
addReply(c,shared.nokeyerr);
|
addReplyErrorObject(c,shared.nokeyerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
val = dictGetVal(de);
|
val = dictGetVal(de);
|
||||||
@ -564,7 +613,7 @@ NULL
|
|||||||
sds key;
|
sds key;
|
||||||
|
|
||||||
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
||||||
addReply(c,shared.nokeyerr);
|
addReplyErrorObject(c,shared.nokeyerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
val = dictGetVal(de);
|
val = dictGetVal(de);
|
||||||
@ -1569,7 +1618,7 @@ void logCurrentClient(void) {
|
|||||||
}
|
}
|
||||||
/* Check if the first argument, usually a key, is found inside the
|
/* Check if the first argument, usually a key, is found inside the
|
||||||
* selected DB, and if so print info about the associated object. */
|
* selected DB, and if so print info about the associated object. */
|
||||||
if (cc->argc >= 1) {
|
if (cc->argc > 1) {
|
||||||
robj *val, *key;
|
robj *val, *key;
|
||||||
dictEntry *de;
|
dictEntry *de;
|
||||||
|
|
||||||
@ -1808,7 +1857,7 @@ void bugReportEnd(int killViaSignal, int sig) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
/* free(messages); Don't call free() with possibly corrupted memory. */
|
/* free(messages); Don't call free() with possibly corrupted memory. */
|
||||||
if (server.daemonize && server.supervised == 0) unlink(server.pidfile);
|
if (server.daemonize && server.supervised == 0 && server.pidfile) unlink(server.pidfile);
|
||||||
|
|
||||||
if (!killViaSignal) {
|
if (!killViaSignal) {
|
||||||
if (server.use_exit_on_panic)
|
if (server.use_exit_on_panic)
|
||||||
|
@ -38,4 +38,4 @@
|
|||||||
fprintf(fp,__VA_ARGS__); \
|
fprintf(fp,__VA_ARGS__); \
|
||||||
fprintf(fp,"\n"); \
|
fprintf(fp,"\n"); \
|
||||||
fclose(fp); \
|
fclose(fp); \
|
||||||
} while (0);
|
} while (0)
|
||||||
|
@ -58,7 +58,6 @@ void* activeDefragAlloc(void *ptr) {
|
|||||||
void *newptr;
|
void *newptr;
|
||||||
if(!je_get_defrag_hint(ptr)) {
|
if(!je_get_defrag_hint(ptr)) {
|
||||||
server.stat_active_defrag_misses++;
|
server.stat_active_defrag_misses++;
|
||||||
size = zmalloc_size(ptr);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
/* move this allocation to a new allocation.
|
/* move this allocation to a new allocation.
|
||||||
|
25
src/dict.c
25
src/dict.c
@ -646,13 +646,13 @@ dictEntry *dictGetRandomKey(dict *d)
|
|||||||
do {
|
do {
|
||||||
/* We are sure there are no elements in indexes from 0
|
/* We are sure there are no elements in indexes from 0
|
||||||
* to rehashidx-1 */
|
* to rehashidx-1 */
|
||||||
h = d->rehashidx + (random() % (dictSlots(d) - d->rehashidx));
|
h = d->rehashidx + (randomULong() % (dictSlots(d) - d->rehashidx));
|
||||||
he = (h >= d->ht[0].size) ? d->ht[1].table[h - d->ht[0].size] :
|
he = (h >= d->ht[0].size) ? d->ht[1].table[h - d->ht[0].size] :
|
||||||
d->ht[0].table[h];
|
d->ht[0].table[h];
|
||||||
} while(he == NULL);
|
} while(he == NULL);
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
h = random() & d->ht[0].sizemask;
|
h = randomULong() & d->ht[0].sizemask;
|
||||||
he = d->ht[0].table[h];
|
he = d->ht[0].table[h];
|
||||||
} while(he == NULL);
|
} while(he == NULL);
|
||||||
}
|
}
|
||||||
@ -718,7 +718,7 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
|||||||
maxsizemask = d->ht[1].sizemask;
|
maxsizemask = d->ht[1].sizemask;
|
||||||
|
|
||||||
/* Pick a random point inside the larger table. */
|
/* Pick a random point inside the larger table. */
|
||||||
unsigned long i = random() & maxsizemask;
|
unsigned long i = randomULong() & maxsizemask;
|
||||||
unsigned long emptylen = 0; /* Continuous empty entries so far. */
|
unsigned long emptylen = 0; /* Continuous empty entries so far. */
|
||||||
while(stored < count && maxsteps--) {
|
while(stored < count && maxsteps--) {
|
||||||
for (j = 0; j < tables; j++) {
|
for (j = 0; j < tables; j++) {
|
||||||
@ -743,7 +743,7 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
|||||||
if (he == NULL) {
|
if (he == NULL) {
|
||||||
emptylen++;
|
emptylen++;
|
||||||
if (emptylen >= 5 && emptylen > count) {
|
if (emptylen >= 5 && emptylen > count) {
|
||||||
i = random() & maxsizemask;
|
i = randomULong() & maxsizemask;
|
||||||
emptylen = 0;
|
emptylen = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1135,10 +1135,10 @@ size_t _dictGetStatsHt(char *buf, size_t bufsize, dictht *ht, int tableid) {
|
|||||||
/* Generate human readable stats. */
|
/* Generate human readable stats. */
|
||||||
l += snprintf(buf+l,bufsize-l,
|
l += snprintf(buf+l,bufsize-l,
|
||||||
"Hash table %d stats (%s):\n"
|
"Hash table %d stats (%s):\n"
|
||||||
" table size: %ld\n"
|
" table size: %lu\n"
|
||||||
" number of elements: %ld\n"
|
" number of elements: %lu\n"
|
||||||
" different slots: %ld\n"
|
" different slots: %lu\n"
|
||||||
" max chain length: %ld\n"
|
" max chain length: %lu\n"
|
||||||
" avg chain length (counted): %.02f\n"
|
" avg chain length (counted): %.02f\n"
|
||||||
" avg chain length (computed): %.02f\n"
|
" avg chain length (computed): %.02f\n"
|
||||||
" Chain length distribution:\n",
|
" Chain length distribution:\n",
|
||||||
@ -1215,7 +1215,7 @@ dictType BenchmarkDictType = {
|
|||||||
#define end_benchmark(msg) do { \
|
#define end_benchmark(msg) do { \
|
||||||
elapsed = timeInMilliseconds()-start; \
|
elapsed = timeInMilliseconds()-start; \
|
||||||
printf(msg ": %ld items in %lld ms\n", count, elapsed); \
|
printf(msg ": %ld items in %lld ms\n", count, elapsed); \
|
||||||
} while(0);
|
} while(0)
|
||||||
|
|
||||||
/* dict-benchmark [count] */
|
/* dict-benchmark [count] */
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
@ -1270,6 +1270,13 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
end_benchmark("Random access of existing elements");
|
end_benchmark("Random access of existing elements");
|
||||||
|
|
||||||
|
start_benchmark();
|
||||||
|
for (j = 0; j < count; j++) {
|
||||||
|
dictEntry *de = dictGetRandomKey(dict);
|
||||||
|
assert(de != NULL);
|
||||||
|
}
|
||||||
|
end_benchmark("Accessing random keys");
|
||||||
|
|
||||||
start_benchmark();
|
start_benchmark();
|
||||||
for (j = 0; j < count; j++) {
|
for (j = 0; j < count; j++) {
|
||||||
sds key = sdsfromlonglong(rand() % count);
|
sds key = sdsfromlonglong(rand() % count);
|
||||||
|
14
src/dict.h
14
src/dict.h
@ -33,11 +33,14 @@
|
|||||||
* POSSIBILITY OF SUCH DAMAGE.
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#ifndef __DICT_H
|
#ifndef __DICT_H
|
||||||
#define __DICT_H
|
#define __DICT_H
|
||||||
|
|
||||||
|
#include "mt19937-64.h"
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
#define DICT_OK 0
|
#define DICT_OK 0
|
||||||
#define DICT_ERR 1
|
#define DICT_ERR 1
|
||||||
|
|
||||||
@ -148,6 +151,13 @@ typedef void (dictScanBucketFunction)(void *privdata, dictEntry **bucketref);
|
|||||||
#define dictSize(d) ((d)->ht[0].used+(d)->ht[1].used)
|
#define dictSize(d) ((d)->ht[0].used+(d)->ht[1].used)
|
||||||
#define dictIsRehashing(d) ((d)->rehashidx != -1)
|
#define dictIsRehashing(d) ((d)->rehashidx != -1)
|
||||||
|
|
||||||
|
/* If our unsigned long type can store a 64 bit number, use a 64 bit PRNG. */
|
||||||
|
#if ULONG_MAX >= 0xffffffffffffffff
|
||||||
|
#define randomULong() ((unsigned long) genrand64_int64())
|
||||||
|
#else
|
||||||
|
#define randomULong() random()
|
||||||
|
#endif
|
||||||
|
|
||||||
/* API */
|
/* API */
|
||||||
dict *dictCreate(dictType *type, void *privDataPtr);
|
dict *dictCreate(dictType *type, void *privDataPtr);
|
||||||
int dictExpand(dict *d, unsigned long size);
|
int dictExpand(dict *d, unsigned long size);
|
||||||
|
@ -462,7 +462,7 @@ static int isSafeToPerformEvictions(void) {
|
|||||||
/* When clients are paused the dataset should be static not just from the
|
/* When clients are paused the dataset should be static not just from the
|
||||||
* POV of clients not being able to write, but also from the POV of
|
* POV of clients not being able to write, but also from the POV of
|
||||||
* expires and evictions of keys not being performed. */
|
* expires and evictions of keys not being performed. */
|
||||||
if (clientsArePaused()) return 0;
|
if (checkClientPauseTimeoutAndReturnIfPaused()) return 0;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ void activeExpireCycle(int type) {
|
|||||||
/* When clients are paused the dataset should be static not just from the
|
/* When clients are paused the dataset should be static not just from the
|
||||||
* POV of clients not being able to write, but also from the POV of
|
* POV of clients not being able to write, but also from the POV of
|
||||||
* expires and evictions of keys not being performed. */
|
* expires and evictions of keys not being performed. */
|
||||||
if (clientsArePaused()) return;
|
if (checkClientPauseTimeoutAndReturnIfPaused()) return;
|
||||||
|
|
||||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
|
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
|
||||||
/* Don't start a fast cycle if the previous cycle did not exit
|
/* Don't start a fast cycle if the previous cycle did not exit
|
||||||
|
95
src/geo.c
95
src/geo.c
@ -100,8 +100,8 @@ int extractLongLatOrReply(client *c, robj **argv, double *xy) {
|
|||||||
}
|
}
|
||||||
if (xy[0] < GEO_LONG_MIN || xy[0] > GEO_LONG_MAX ||
|
if (xy[0] < GEO_LONG_MIN || xy[0] > GEO_LONG_MAX ||
|
||||||
xy[1] < GEO_LAT_MIN || xy[1] > GEO_LAT_MAX) {
|
xy[1] < GEO_LAT_MIN || xy[1] > GEO_LAT_MAX) {
|
||||||
addReplySds(c, sdscatprintf(sdsempty(),
|
addReplyErrorFormat(c,
|
||||||
"-ERR invalid longitude,latitude pair %f,%f\r\n",xy[0],xy[1]));
|
"-ERR invalid longitude,latitude pair %f,%f\r\n",xy[0],xy[1]);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
return C_OK;
|
return C_OK;
|
||||||
@ -249,7 +249,7 @@ int geoAppendIfWithinShape(geoArray *ga, GeoShape *shape, double score, sds memb
|
|||||||
* using multiple queries to the sorted set, that we later need to sort
|
* using multiple queries to the sorted set, that we later need to sort
|
||||||
* via qsort. Similarly we need to be able to reject points outside the search
|
* via qsort. Similarly we need to be able to reject points outside the search
|
||||||
* radius area ASAP in order to allocate and process more points than needed. */
|
* radius area ASAP in order to allocate and process more points than needed. */
|
||||||
int geoGetPointsInRange(robj *zobj, double min, double max, GeoShape *shape, geoArray *ga) {
|
int geoGetPointsInRange(robj *zobj, double min, double max, GeoShape *shape, geoArray *ga, unsigned long limit) {
|
||||||
/* minex 0 = include min in range; maxex 1 = exclude max in range */
|
/* minex 0 = include min in range; maxex 1 = exclude max in range */
|
||||||
/* That's: min <= val < max */
|
/* That's: min <= val < max */
|
||||||
zrangespec range = { .min = min, .max = max, .minex = 0, .maxex = 1 };
|
zrangespec range = { .min = min, .max = max, .minex = 0, .maxex = 1 };
|
||||||
@ -283,6 +283,7 @@ int geoGetPointsInRange(robj *zobj, double min, double max, GeoShape *shape, geo
|
|||||||
sdsnewlen(vstr,vlen);
|
sdsnewlen(vstr,vlen);
|
||||||
if (geoAppendIfWithinShape(ga,shape,score,member)
|
if (geoAppendIfWithinShape(ga,shape,score,member)
|
||||||
== C_ERR) sdsfree(member);
|
== C_ERR) sdsfree(member);
|
||||||
|
if (ga->used && limit && ga->used >= limit) break;
|
||||||
zzlNext(zl, &eptr, &sptr);
|
zzlNext(zl, &eptr, &sptr);
|
||||||
}
|
}
|
||||||
} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
|
} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||||
@ -304,6 +305,7 @@ int geoGetPointsInRange(robj *zobj, double min, double max, GeoShape *shape, geo
|
|||||||
ele = sdsdup(ele);
|
ele = sdsdup(ele);
|
||||||
if (geoAppendIfWithinShape(ga,shape,ln->score,ele)
|
if (geoAppendIfWithinShape(ga,shape,ln->score,ele)
|
||||||
== C_ERR) sdsfree(ele);
|
== C_ERR) sdsfree(ele);
|
||||||
|
if (ga->used && limit && ga->used >= limit) break;
|
||||||
ln = ln->level[0].forward;
|
ln = ln->level[0].forward;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -342,15 +344,15 @@ void scoresOfGeoHashBox(GeoHashBits hash, GeoHashFix52Bits *min, GeoHashFix52Bit
|
|||||||
/* Obtain all members between the min/max of this geohash bounding box.
|
/* Obtain all members between the min/max of this geohash bounding box.
|
||||||
* Populate a geoArray of GeoPoints by calling geoGetPointsInRange().
|
* Populate a geoArray of GeoPoints by calling geoGetPointsInRange().
|
||||||
* Return the number of points added to the array. */
|
* Return the number of points added to the array. */
|
||||||
int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, GeoShape *shape) {
|
int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, GeoShape *shape, unsigned long limit) {
|
||||||
GeoHashFix52Bits min, max;
|
GeoHashFix52Bits min, max;
|
||||||
|
|
||||||
scoresOfGeoHashBox(hash,&min,&max);
|
scoresOfGeoHashBox(hash,&min,&max);
|
||||||
return geoGetPointsInRange(zobj, min, max, shape, ga);
|
return geoGetPointsInRange(zobj, min, max, shape, ga, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Search all eight neighbors + self geohash box */
|
/* Search all eight neighbors + self geohash box */
|
||||||
int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, GeoShape *shape, geoArray *ga) {
|
int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, GeoShape *shape, geoArray *ga, unsigned long limit) {
|
||||||
GeoHashBits neighbors[9];
|
GeoHashBits neighbors[9];
|
||||||
unsigned int i, count = 0, last_processed = 0;
|
unsigned int i, count = 0, last_processed = 0;
|
||||||
int debugmsg = 0;
|
int debugmsg = 0;
|
||||||
@ -401,7 +403,8 @@ int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, GeoShape *shape, geoArray
|
|||||||
D("Skipping processing of %d, same as previous\n",i);
|
D("Skipping processing of %d, same as previous\n",i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
count += membersOfGeoHashBox(zobj, neighbors[i], ga, shape);
|
if (ga->used && limit && ga->used >= limit) break;
|
||||||
|
count += membersOfGeoHashBox(zobj, neighbors[i], ga, shape, limit);
|
||||||
last_processed = i;
|
last_processed = i;
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
@ -428,31 +431,45 @@ static int sort_gp_desc(const void *a, const void *b) {
|
|||||||
* Commands
|
* Commands
|
||||||
* ==================================================================== */
|
* ==================================================================== */
|
||||||
|
|
||||||
/* GEOADD key long lat name [long2 lat2 name2 ... longN latN nameN] */
|
/* GEOADD key [CH] [NX|XX] long lat name [long2 lat2 name2 ... longN latN nameN] */
|
||||||
void geoaddCommand(client *c) {
|
void geoaddCommand(client *c) {
|
||||||
/* Check arguments number for sanity. */
|
int xx = 0, nx = 0, longidx = 2;
|
||||||
if ((c->argc - 2) % 3 != 0) {
|
int i;
|
||||||
|
|
||||||
|
/* Parse options. At the end 'longidx' is set to the argument position
|
||||||
|
* of the longitude of the first element. */
|
||||||
|
while (longidx < c->argc) {
|
||||||
|
char *opt = c->argv[longidx]->ptr;
|
||||||
|
if (!strcasecmp(opt,"nx")) nx = 1;
|
||||||
|
else if (!strcasecmp(opt,"xx")) xx = 1;
|
||||||
|
else if (!strcasecmp(opt,"ch")) {}
|
||||||
|
else break;
|
||||||
|
longidx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((c->argc - longidx) % 3 || (xx && nx)) {
|
||||||
/* Need an odd number of arguments if we got this far... */
|
/* Need an odd number of arguments if we got this far... */
|
||||||
addReplyError(c, "syntax error. Try GEOADD key [x1] [y1] [name1] "
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
"[x2] [y2] [name2] ... ");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int elements = (c->argc - 2) / 3;
|
/* Set up the vector for calling ZADD. */
|
||||||
int argc = 2+elements*2; /* ZADD key score ele ... */
|
int elements = (c->argc - longidx) / 3;
|
||||||
|
int argc = longidx+elements*2; /* ZADD key [CH] [NX|XX] score ele ... */
|
||||||
robj **argv = zcalloc(argc*sizeof(robj*));
|
robj **argv = zcalloc(argc*sizeof(robj*));
|
||||||
argv[0] = createRawStringObject("zadd",4);
|
argv[0] = createRawStringObject("zadd",4);
|
||||||
argv[1] = c->argv[1]; /* key */
|
for (i = 1; i < longidx; i++) {
|
||||||
incrRefCount(argv[1]);
|
argv[i] = c->argv[i];
|
||||||
|
incrRefCount(argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
/* Create the argument vector to call ZADD in order to add all
|
/* Create the argument vector to call ZADD in order to add all
|
||||||
* the score,value pairs to the requested zset, where score is actually
|
* the score,value pairs to the requested zset, where score is actually
|
||||||
* an encoded version of lat,long. */
|
* an encoded version of lat,long. */
|
||||||
int i;
|
|
||||||
for (i = 0; i < elements; i++) {
|
for (i = 0; i < elements; i++) {
|
||||||
double xy[2];
|
double xy[2];
|
||||||
|
|
||||||
if (extractLongLatOrReply(c, (c->argv+2)+(i*3),xy) == C_ERR) {
|
if (extractLongLatOrReply(c, (c->argv+longidx)+(i*3),xy) == C_ERR) {
|
||||||
for (i = 0; i < argc; i++)
|
for (i = 0; i < argc; i++)
|
||||||
if (argv[i]) decrRefCount(argv[i]);
|
if (argv[i]) decrRefCount(argv[i]);
|
||||||
zfree(argv);
|
zfree(argv);
|
||||||
@ -464,9 +481,9 @@ void geoaddCommand(client *c) {
|
|||||||
geohashEncodeWGS84(xy[0], xy[1], GEO_STEP_MAX, &hash);
|
geohashEncodeWGS84(xy[0], xy[1], GEO_STEP_MAX, &hash);
|
||||||
GeoHashFix52Bits bits = geohashAlign52Bits(hash);
|
GeoHashFix52Bits bits = geohashAlign52Bits(hash);
|
||||||
robj *score = createObject(OBJ_STRING, sdsfromlonglong(bits));
|
robj *score = createObject(OBJ_STRING, sdsfromlonglong(bits));
|
||||||
robj *val = c->argv[2 + i * 3 + 2];
|
robj *val = c->argv[longidx + i * 3 + 2];
|
||||||
argv[2+i*2] = score;
|
argv[longidx+i*2] = score;
|
||||||
argv[3+i*2] = val;
|
argv[longidx+1+i*2] = val;
|
||||||
incrRefCount(val);
|
incrRefCount(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,12 +503,12 @@ void geoaddCommand(client *c) {
|
|||||||
#define GEOSEARCHSTORE (1<<4) /* GEOSEARCHSTORE just accept STOREDIST option */
|
#define GEOSEARCHSTORE (1<<4) /* GEOSEARCHSTORE just accept STOREDIST option */
|
||||||
|
|
||||||
/* GEORADIUS key x y radius unit [WITHDIST] [WITHHASH] [WITHCOORD] [ASC|DESC]
|
/* GEORADIUS key x y radius unit [WITHDIST] [WITHHASH] [WITHCOORD] [ASC|DESC]
|
||||||
* [COUNT count] [STORE key] [STOREDIST key]
|
* [COUNT count [ANY]] [STORE key] [STOREDIST key]
|
||||||
* GEORADIUSBYMEMBER key member radius unit ... options ...
|
* GEORADIUSBYMEMBER key member radius unit ... options ...
|
||||||
* GEOSEARCH key [FROMMEMBER member] [FORMLOG long lat] [BYRADIUS radius unit]
|
* GEOSEARCH key [FROMMEMBER member] [FROMLONLAT long lat] [BYRADIUS radius unit]
|
||||||
* [BYBOX width height unit] [WITHCORD] [WITHDIST] [WITHASH] [COUNT count] [ASC|DESC]
|
* [BYBOX width height unit] [WITHCORD] [WITHDIST] [WITHASH] [COUNT count [ANY]] [ASC|DESC]
|
||||||
* GEOSEARCHSTORE dest_key src_key [FROMMEMBER member] [FORMLOG long lat] [BYRADIUS radius unit]
|
* GEOSEARCHSTORE dest_key src_key [FROMMEMBER member] [FROMLONLAT long lat] [BYRADIUS radius unit]
|
||||||
* [BYBOX width height unit] [WITHCORD] [WITHDIST] [WITHASH] [COUNT count] [ASC|DESC] [STOREDIST]
|
* [BYBOX width height unit] [WITHCORD] [WITHDIST] [WITHASH] [COUNT count [ANY]] [ASC|DESC] [STOREDIST]
|
||||||
* */
|
* */
|
||||||
void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
||||||
robj *storekey = NULL;
|
robj *storekey = NULL;
|
||||||
@ -536,7 +553,8 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
|||||||
int withdist = 0, withhash = 0, withcoords = 0;
|
int withdist = 0, withhash = 0, withcoords = 0;
|
||||||
int frommember = 0, fromloc = 0, byradius = 0, bybox = 0;
|
int frommember = 0, fromloc = 0, byradius = 0, bybox = 0;
|
||||||
int sort = SORT_NONE;
|
int sort = SORT_NONE;
|
||||||
long long count = 0;
|
int any = 0; /* any=1 means a limited search, stop as soon as enough results were found. */
|
||||||
|
long long count = 0; /* Max number of results to return. 0 means unlimited. */
|
||||||
if (c->argc > base_args) {
|
if (c->argc > base_args) {
|
||||||
int remaining = c->argc - base_args;
|
int remaining = c->argc - base_args;
|
||||||
for (int i = 0; i < remaining; i++) {
|
for (int i = 0; i < remaining; i++) {
|
||||||
@ -547,13 +565,15 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
|||||||
withhash = 1;
|
withhash = 1;
|
||||||
} else if (!strcasecmp(arg, "withcoord")) {
|
} else if (!strcasecmp(arg, "withcoord")) {
|
||||||
withcoords = 1;
|
withcoords = 1;
|
||||||
|
} else if (!strcasecmp(arg, "any")) {
|
||||||
|
any = 1;
|
||||||
} else if (!strcasecmp(arg, "asc")) {
|
} else if (!strcasecmp(arg, "asc")) {
|
||||||
sort = SORT_ASC;
|
sort = SORT_ASC;
|
||||||
} else if (!strcasecmp(arg, "desc")) {
|
} else if (!strcasecmp(arg, "desc")) {
|
||||||
sort = SORT_DESC;
|
sort = SORT_DESC;
|
||||||
} else if (!strcasecmp(arg, "count") && (i+1) < remaining) {
|
} else if (!strcasecmp(arg, "count") && (i+1) < remaining) {
|
||||||
if (getLongLongFromObjectOrReply(c, c->argv[base_args+i+1],
|
if (getLongLongFromObjectOrReply(c, c->argv[base_args+i+1],
|
||||||
&count, NULL) != C_OK) return;
|
&count, NULL) != C_OK) return;
|
||||||
if (count <= 0) {
|
if (count <= 0) {
|
||||||
addReplyError(c,"COUNT must be > 0");
|
addReplyError(c,"COUNT must be > 0");
|
||||||
return;
|
return;
|
||||||
@ -620,7 +640,7 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
|||||||
bybox = 1;
|
bybox = 1;
|
||||||
i += 3;
|
i += 3;
|
||||||
} else {
|
} else {
|
||||||
addReply(c, shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -648,16 +668,23 @@ void georadiusGeneric(client *c, int srcKeyIndex, int flags) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* COUNT without ordering does not make much sense, force ASC
|
if (any && !count) {
|
||||||
* ordering if COUNT was specified but no sorting was requested. */
|
addReplyErrorFormat(c, "the ANY argument requires COUNT argument");
|
||||||
if (count != 0 && sort == SORT_NONE) sort = SORT_ASC;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* COUNT without ordering does not make much sense (we need to
|
||||||
|
* sort in order to return the closest N entries),
|
||||||
|
* force ASC ordering if COUNT was specified but no sorting was
|
||||||
|
* requested. Note that this is not needed for ANY option. */
|
||||||
|
if (count != 0 && sort == SORT_NONE && !any) sort = SORT_ASC;
|
||||||
|
|
||||||
/* Get all neighbor geohash boxes for our radius search */
|
/* Get all neighbor geohash boxes for our radius search */
|
||||||
GeoHashRadius georadius = geohashCalculateAreasByShapeWGS84(&shape);
|
GeoHashRadius georadius = geohashCalculateAreasByShapeWGS84(&shape);
|
||||||
|
|
||||||
/* Search the zset for all matching points */
|
/* Search the zset for all matching points */
|
||||||
geoArray *ga = geoArrayCreate();
|
geoArray *ga = geoArrayCreate();
|
||||||
membersOfAllNeighbors(zobj, georadius, &shape, ga);
|
membersOfAllNeighbors(zobj, georadius, &shape, ga, any ? count : 0);
|
||||||
|
|
||||||
/* If no matching results, the user gets an empty reply. */
|
/* If no matching results, the user gets an empty reply. */
|
||||||
if (ga->used == 0 && storekey == NULL) {
|
if (ga->used == 0 && storekey == NULL) {
|
||||||
@ -902,7 +929,7 @@ void geodistCommand(client *c) {
|
|||||||
to_meter = extractUnitOrReply(c,c->argv[4]);
|
to_meter = extractUnitOrReply(c,c->argv[4]);
|
||||||
if (to_meter < 0) return;
|
if (to_meter < 0) return;
|
||||||
} else if (c->argc > 5) {
|
} else if (c->argc > 5) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ struct hllhdr {
|
|||||||
#define HLL_RAW 255 /* Only used internally, never exposed. */
|
#define HLL_RAW 255 /* Only used internally, never exposed. */
|
||||||
#define HLL_MAX_ENCODING 1
|
#define HLL_MAX_ENCODING 1
|
||||||
|
|
||||||
static char *invalid_hll_err = "-INVALIDOBJ Corrupted HLL object detected\r\n";
|
static char *invalid_hll_err = "-INVALIDOBJ Corrupted HLL object detected";
|
||||||
|
|
||||||
/* =========================== Low level bit macros ========================= */
|
/* =========================== Low level bit macros ========================= */
|
||||||
|
|
||||||
@ -1171,9 +1171,8 @@ int isHLLObjectOrReply(client *c, robj *o) {
|
|||||||
return C_OK;
|
return C_OK;
|
||||||
|
|
||||||
invalid:
|
invalid:
|
||||||
addReplySds(c,
|
addReplyError(c,"-WRONGTYPE Key is not a valid "
|
||||||
sdsnew("-WRONGTYPE Key is not a valid "
|
"HyperLogLog string value.");
|
||||||
"HyperLogLog string value.\r\n"));
|
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1203,7 +1202,7 @@ void pfaddCommand(client *c) {
|
|||||||
updated++;
|
updated++;
|
||||||
break;
|
break;
|
||||||
case -1:
|
case -1:
|
||||||
addReplySds(c,sdsnew(invalid_hll_err));
|
addReplyError(c,invalid_hll_err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1211,7 +1210,7 @@ void pfaddCommand(client *c) {
|
|||||||
if (updated) {
|
if (updated) {
|
||||||
signalModifiedKey(c,c->db,c->argv[1]);
|
signalModifiedKey(c,c->db,c->argv[1]);
|
||||||
notifyKeyspaceEvent(NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
|
notifyKeyspaceEvent(NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
|
||||||
server.dirty++;
|
server.dirty += updated;
|
||||||
HLL_INVALIDATE_CACHE(hdr);
|
HLL_INVALIDATE_CACHE(hdr);
|
||||||
}
|
}
|
||||||
addReply(c, updated ? shared.cone : shared.czero);
|
addReply(c, updated ? shared.cone : shared.czero);
|
||||||
@ -1245,7 +1244,7 @@ void pfcountCommand(client *c) {
|
|||||||
/* Merge with this HLL with our 'max' HLL by setting max[i]
|
/* Merge with this HLL with our 'max' HLL by setting max[i]
|
||||||
* to MAX(max[i],hll[i]). */
|
* to MAX(max[i],hll[i]). */
|
||||||
if (hllMerge(registers,o) == C_ERR) {
|
if (hllMerge(registers,o) == C_ERR) {
|
||||||
addReplySds(c,sdsnew(invalid_hll_err));
|
addReplyError(c,invalid_hll_err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1285,7 +1284,7 @@ void pfcountCommand(client *c) {
|
|||||||
/* Recompute it and update the cached value. */
|
/* Recompute it and update the cached value. */
|
||||||
card = hllCount(hdr,&invalid);
|
card = hllCount(hdr,&invalid);
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
addReplySds(c,sdsnew(invalid_hll_err));
|
addReplyError(c,invalid_hll_err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hdr->card[0] = card & 0xff;
|
hdr->card[0] = card & 0xff;
|
||||||
@ -1332,7 +1331,7 @@ void pfmergeCommand(client *c) {
|
|||||||
/* Merge with this HLL with our 'max' HLL by setting max[i]
|
/* Merge with this HLL with our 'max' HLL by setting max[i]
|
||||||
* to MAX(max[i],hll[i]). */
|
* to MAX(max[i],hll[i]). */
|
||||||
if (hllMerge(max,o) == C_ERR) {
|
if (hllMerge(max,o) == C_ERR) {
|
||||||
addReplySds(c,sdsnew(invalid_hll_err));
|
addReplyError(c,invalid_hll_err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1355,7 +1354,7 @@ void pfmergeCommand(client *c) {
|
|||||||
/* Convert the destination object to dense representation if at least
|
/* Convert the destination object to dense representation if at least
|
||||||
* one of the inputs was dense. */
|
* one of the inputs was dense. */
|
||||||
if (use_dense && hllSparseToDense(o) == C_ERR) {
|
if (use_dense && hllSparseToDense(o) == C_ERR) {
|
||||||
addReplySds(c,sdsnew(invalid_hll_err));
|
addReplyError(c,invalid_hll_err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1512,7 +1511,7 @@ void pfdebugCommand(client *c) {
|
|||||||
|
|
||||||
if (hdr->encoding == HLL_SPARSE) {
|
if (hdr->encoding == HLL_SPARSE) {
|
||||||
if (hllSparseToDense(o) == C_ERR) {
|
if (hllSparseToDense(o) == C_ERR) {
|
||||||
addReplySds(c,sdsnew(invalid_hll_err));
|
addReplyError(c,invalid_hll_err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
server.dirty++; /* Force propagation on encoding change. */
|
server.dirty++; /* Force propagation on encoding change. */
|
||||||
@ -1577,7 +1576,7 @@ void pfdebugCommand(client *c) {
|
|||||||
|
|
||||||
if (hdr->encoding == HLL_SPARSE) {
|
if (hdr->encoding == HLL_SPARSE) {
|
||||||
if (hllSparseToDense(o) == C_ERR) {
|
if (hllSparseToDense(o) == C_ERR) {
|
||||||
addReplySds(c,sdsnew(invalid_hll_err));
|
addReplyError(c,invalid_hll_err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
conv = 1;
|
conv = 1;
|
||||||
|
@ -358,12 +358,6 @@ static long long usec(void) {
|
|||||||
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
|
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define assert(_e) ((_e)?(void)0:(_assert(#_e,__FILE__,__LINE__),exit(1)))
|
|
||||||
static void _assert(char *estr, char *file, int line) {
|
|
||||||
printf("\n\n=== ASSERTION FAILED ===\n");
|
|
||||||
printf("==> %s:%d '%s' is not true\n",file,line,estr);
|
|
||||||
}
|
|
||||||
|
|
||||||
static intset *createSet(int bits, int size) {
|
static intset *createSet(int bits, int size) {
|
||||||
uint64_t mask = (1<<bits)-1;
|
uint64_t mask = (1<<bits)-1;
|
||||||
uint64_t value;
|
uint64_t value;
|
||||||
|
@ -584,16 +584,6 @@ sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
|
|||||||
* LATENCY RESET: reset data of a specified event or all the data if no event provided.
|
* LATENCY RESET: reset data of a specified event or all the data if no event provided.
|
||||||
*/
|
*/
|
||||||
void latencyCommand(client *c) {
|
void latencyCommand(client *c) {
|
||||||
const char *help[] = {
|
|
||||||
"DOCTOR -- Returns a human readable latency analysis report.",
|
|
||||||
"GRAPH <event> -- Returns an ASCII latency graph for the event class.",
|
|
||||||
"HISTORY <event> -- Returns time-latency samples for the event class.",
|
|
||||||
"LATEST -- Returns the latest latency samples for all events.",
|
|
||||||
"RESET [event ...] -- Resets latency data of one or more event classes.",
|
|
||||||
" (default: reset all data for all event classes)",
|
|
||||||
"HELP -- Prints this help.",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
struct latencyTimeSeries *ts;
|
struct latencyTimeSeries *ts;
|
||||||
|
|
||||||
if (!strcasecmp(c->argv[1]->ptr,"history") && c->argc == 3) {
|
if (!strcasecmp(c->argv[1]->ptr,"history") && c->argc == 3) {
|
||||||
@ -639,6 +629,20 @@ NULL
|
|||||||
addReplyLongLong(c,resets);
|
addReplyLongLong(c,resets);
|
||||||
}
|
}
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
|
||||||
|
const char *help[] = {
|
||||||
|
"DOCTOR",
|
||||||
|
" Return a human readable latency analysis report.",
|
||||||
|
"GRAPH <event>",
|
||||||
|
" Return an ASCII latency graph for the <event> class.",
|
||||||
|
"HISTORY <event>",
|
||||||
|
" Return time-latency samples for the <event> class.",
|
||||||
|
"LATEST",
|
||||||
|
" Return the latest latency samples for all events.",
|
||||||
|
"RESET [<event> ...]",
|
||||||
|
" Reset latency data of one or more <event> classes.",
|
||||||
|
" (default: reset all data for all event classes)",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
} else {
|
} else {
|
||||||
addReplySubcommandSyntaxError(c);
|
addReplySubcommandSyntaxError(c);
|
||||||
|
@ -6,6 +6,49 @@
|
|||||||
static redisAtomic size_t lazyfree_objects = 0;
|
static redisAtomic size_t lazyfree_objects = 0;
|
||||||
static redisAtomic size_t lazyfreed_objects = 0;
|
static redisAtomic size_t lazyfreed_objects = 0;
|
||||||
|
|
||||||
|
/* Release objects from the lazyfree thread. It's just decrRefCount()
|
||||||
|
* updating the count of objects to release. */
|
||||||
|
void lazyfreeFreeObject(void *args[]) {
|
||||||
|
robj *o = (robj *) args[0];
|
||||||
|
decrRefCount(o);
|
||||||
|
atomicDecr(lazyfree_objects,1);
|
||||||
|
atomicIncr(lazyfreed_objects,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release a database from the lazyfree thread. The 'db' pointer is the
|
||||||
|
* database which was substituted with a fresh one in the main thread
|
||||||
|
* when the database was logically deleted. */
|
||||||
|
void lazyfreeFreeDatabase(void *args[]) {
|
||||||
|
dict *ht1 = (dict *) args[0];
|
||||||
|
dict *ht2 = (dict *) args[1];
|
||||||
|
|
||||||
|
size_t numkeys = dictSize(ht1);
|
||||||
|
dictRelease(ht1);
|
||||||
|
dictRelease(ht2);
|
||||||
|
atomicDecr(lazyfree_objects,numkeys);
|
||||||
|
atomicIncr(lazyfreed_objects,numkeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release the skiplist mapping Redis Cluster keys to slots in the
|
||||||
|
* lazyfree thread. */
|
||||||
|
void lazyfreeFreeSlotsMap(void *args[]) {
|
||||||
|
rax *rt = args[0];
|
||||||
|
size_t len = rt->numele;
|
||||||
|
raxFree(rt);
|
||||||
|
atomicDecr(lazyfree_objects,len);
|
||||||
|
atomicIncr(lazyfreed_objects,len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release the rax mapping Redis Cluster keys to slots in the
|
||||||
|
* lazyfree thread. */
|
||||||
|
void lazyFreeTrackingTable(void *args[]) {
|
||||||
|
rax *rt = args[0];
|
||||||
|
size_t len = rt->numele;
|
||||||
|
raxFree(rt);
|
||||||
|
atomicDecr(lazyfree_objects,len);
|
||||||
|
atomicIncr(lazyfreed_objects,len);
|
||||||
|
}
|
||||||
|
|
||||||
/* Return the number of currently pending objects to free. */
|
/* Return the number of currently pending objects to free. */
|
||||||
size_t lazyfreeGetPendingObjectsCount(void) {
|
size_t lazyfreeGetPendingObjectsCount(void) {
|
||||||
size_t aux;
|
size_t aux;
|
||||||
@ -120,7 +163,7 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
|||||||
* equivalent to just calling decrRefCount(). */
|
* equivalent to just calling decrRefCount(). */
|
||||||
if (free_effort > LAZYFREE_THRESHOLD && val->refcount == 1) {
|
if (free_effort > LAZYFREE_THRESHOLD && val->refcount == 1) {
|
||||||
atomicIncr(lazyfree_objects,1);
|
atomicIncr(lazyfree_objects,1);
|
||||||
bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
|
bioCreateLazyFreeJob(lazyfreeFreeObject,1, val);
|
||||||
dictSetVal(db->dict,de,NULL);
|
dictSetVal(db->dict,de,NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,7 +184,7 @@ void freeObjAsync(robj *key, robj *obj) {
|
|||||||
size_t free_effort = lazyfreeGetFreeEffort(key,obj);
|
size_t free_effort = lazyfreeGetFreeEffort(key,obj);
|
||||||
if (free_effort > LAZYFREE_THRESHOLD && obj->refcount == 1) {
|
if (free_effort > LAZYFREE_THRESHOLD && obj->refcount == 1) {
|
||||||
atomicIncr(lazyfree_objects,1);
|
atomicIncr(lazyfree_objects,1);
|
||||||
bioCreateBackgroundJob(BIO_LAZY_FREE,obj,NULL,NULL);
|
bioCreateLazyFreeJob(lazyfreeFreeObject,1,obj);
|
||||||
} else {
|
} else {
|
||||||
decrRefCount(obj);
|
decrRefCount(obj);
|
||||||
}
|
}
|
||||||
@ -155,39 +198,17 @@ void emptyDbAsync(redisDb *db) {
|
|||||||
db->dict = dictCreate(&dbDictType,NULL);
|
db->dict = dictCreate(&dbDictType,NULL);
|
||||||
db->expires = dictCreate(&dbExpiresDictType,NULL);
|
db->expires = dictCreate(&dbExpiresDictType,NULL);
|
||||||
atomicIncr(lazyfree_objects,dictSize(oldht1));
|
atomicIncr(lazyfree_objects,dictSize(oldht1));
|
||||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,oldht2);
|
bioCreateLazyFreeJob(lazyfreeFreeDatabase,2,oldht1,oldht2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Release the radix tree mapping Redis Cluster keys to slots asynchronously. */
|
/* Release the radix tree mapping Redis Cluster keys to slots asynchronously. */
|
||||||
void freeSlotsToKeysMapAsync(rax *rt) {
|
void freeSlotsToKeysMapAsync(rax *rt) {
|
||||||
atomicIncr(lazyfree_objects,rt->numele);
|
atomicIncr(lazyfree_objects,rt->numele);
|
||||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,NULL,rt);
|
bioCreateLazyFreeJob(lazyfreeFreeSlotsMap,1,rt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Release objects from the lazyfree thread. It's just decrRefCount()
|
/* Free an object, if the object is huge enough, free it in async way. */
|
||||||
* updating the count of objects to release. */
|
void freeTrackingRadixTreeAsync(rax *tracking) {
|
||||||
void lazyfreeFreeObjectFromBioThread(robj *o) {
|
atomicIncr(lazyfree_objects,tracking->numele);
|
||||||
decrRefCount(o);
|
bioCreateLazyFreeJob(lazyFreeTrackingTable,1,tracking);
|
||||||
atomicDecr(lazyfree_objects,1);
|
|
||||||
atomicIncr(lazyfreed_objects,1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Release a database from the lazyfree thread. The 'db' pointer is the
|
|
||||||
* database which was substituted with a fresh one in the main thread
|
|
||||||
* when the database was logically deleted. */
|
|
||||||
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2) {
|
|
||||||
size_t numkeys = dictSize(ht1);
|
|
||||||
dictRelease(ht1);
|
|
||||||
dictRelease(ht2);
|
|
||||||
atomicDecr(lazyfree_objects,numkeys);
|
|
||||||
atomicIncr(lazyfreed_objects,numkeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Release the radix tree mapping Redis Cluster keys to slots in the
|
|
||||||
* lazyfree thread. */
|
|
||||||
void lazyfreeFreeSlotsMapFromBioThread(rax *rt) {
|
|
||||||
size_t len = rt->numele;
|
|
||||||
raxFree(rt);
|
|
||||||
atomicDecr(lazyfree_objects,len);
|
|
||||||
atomicIncr(lazyfreed_objects,len);
|
|
||||||
}
|
}
|
||||||
|
84
src/module.c
84
src/module.c
@ -747,6 +747,7 @@ int64_t commandFlagsFromString(char *s) {
|
|||||||
else if (!strcasecmp(t,"no-slowlog")) flags |= CMD_SKIP_SLOWLOG;
|
else if (!strcasecmp(t,"no-slowlog")) flags |= CMD_SKIP_SLOWLOG;
|
||||||
else if (!strcasecmp(t,"fast")) flags |= CMD_FAST;
|
else if (!strcasecmp(t,"fast")) flags |= CMD_FAST;
|
||||||
else if (!strcasecmp(t,"no-auth")) flags |= CMD_NO_AUTH;
|
else if (!strcasecmp(t,"no-auth")) flags |= CMD_NO_AUTH;
|
||||||
|
else if (!strcasecmp(t,"may-replicate")) flags |= CMD_MAY_REPLICATE;
|
||||||
else if (!strcasecmp(t,"getkeys-api")) flags |= CMD_MODULE_GETKEYS;
|
else if (!strcasecmp(t,"getkeys-api")) flags |= CMD_MODULE_GETKEYS;
|
||||||
else if (!strcasecmp(t,"no-cluster")) flags |= CMD_MODULE_NO_CLUSTER;
|
else if (!strcasecmp(t,"no-cluster")) flags |= CMD_MODULE_NO_CLUSTER;
|
||||||
else break;
|
else break;
|
||||||
@ -813,6 +814,8 @@ int64_t commandFlagsFromString(char *s) {
|
|||||||
* * **"no-auth"**: This command can be run by an un-authenticated client.
|
* * **"no-auth"**: This command can be run by an un-authenticated client.
|
||||||
* Normally this is used by a command that is used
|
* Normally this is used by a command that is used
|
||||||
* to authenticate a client.
|
* to authenticate a client.
|
||||||
|
* * **"may-replicate"**: This command may generate replication traffic, even
|
||||||
|
* though it's not a write command.
|
||||||
*/
|
*/
|
||||||
int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) {
|
int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) {
|
||||||
int64_t flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
|
int64_t flags = strflags ? commandFlagsFromString((char*)strflags) : 0;
|
||||||
@ -851,6 +854,8 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
|
|||||||
cp->rediscmd->keystep = keystep;
|
cp->rediscmd->keystep = keystep;
|
||||||
cp->rediscmd->microseconds = 0;
|
cp->rediscmd->microseconds = 0;
|
||||||
cp->rediscmd->calls = 0;
|
cp->rediscmd->calls = 0;
|
||||||
|
cp->rediscmd->rejected_calls = 0;
|
||||||
|
cp->rediscmd->failed_calls = 0;
|
||||||
dictAdd(server.commands,sdsdup(cmdname),cp->rediscmd);
|
dictAdd(server.commands,sdsdup(cmdname),cp->rediscmd);
|
||||||
dictAdd(server.orig_commands,sdsdup(cmdname),cp->rediscmd);
|
dictAdd(server.orig_commands,sdsdup(cmdname),cp->rediscmd);
|
||||||
cp->rediscmd->id = ACLGetCommandID(cmdname); /* ID used for ACL. */
|
cp->rediscmd->id = ACLGetCommandID(cmdname); /* ID used for ACL. */
|
||||||
@ -1368,18 +1373,6 @@ int RM_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll) {
|
|||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reply with an error or simple string (status message). Used to implement
|
|
||||||
* ReplyWithSimpleString() and ReplyWithError().
|
|
||||||
* The function always returns REDISMODULE_OK. */
|
|
||||||
int replyWithStatus(RedisModuleCtx *ctx, const char *msg, char *prefix) {
|
|
||||||
client *c = moduleGetReplyClient(ctx);
|
|
||||||
if (c == NULL) return REDISMODULE_OK;
|
|
||||||
addReplyProto(c,prefix,strlen(prefix));
|
|
||||||
addReplyProto(c,msg,strlen(msg));
|
|
||||||
addReplyProto(c,"\r\n",2);
|
|
||||||
return REDISMODULE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reply with the error 'err'.
|
/* Reply with the error 'err'.
|
||||||
*
|
*
|
||||||
* Note that 'err' must contain all the error, including
|
* Note that 'err' must contain all the error, including
|
||||||
@ -1395,7 +1388,10 @@ int replyWithStatus(RedisModuleCtx *ctx, const char *msg, char *prefix) {
|
|||||||
* The function always returns REDISMODULE_OK.
|
* The function always returns REDISMODULE_OK.
|
||||||
*/
|
*/
|
||||||
int RM_ReplyWithError(RedisModuleCtx *ctx, const char *err) {
|
int RM_ReplyWithError(RedisModuleCtx *ctx, const char *err) {
|
||||||
return replyWithStatus(ctx,err,"-");
|
client *c = moduleGetReplyClient(ctx);
|
||||||
|
if (c == NULL) return REDISMODULE_OK;
|
||||||
|
addReplyErrorFormat(c,"-%s",err);
|
||||||
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reply with a simple string (+... \r\n in RESP protocol). This replies
|
/* Reply with a simple string (+... \r\n in RESP protocol). This replies
|
||||||
@ -1404,7 +1400,12 @@ int RM_ReplyWithError(RedisModuleCtx *ctx, const char *err) {
|
|||||||
*
|
*
|
||||||
* The function always returns REDISMODULE_OK. */
|
* The function always returns REDISMODULE_OK. */
|
||||||
int RM_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg) {
|
int RM_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg) {
|
||||||
return replyWithStatus(ctx,msg,"+");
|
client *c = moduleGetReplyClient(ctx);
|
||||||
|
if (c == NULL) return REDISMODULE_OK;
|
||||||
|
addReplyProto(c,"+",1);
|
||||||
|
addReplyProto(c,msg,strlen(msg));
|
||||||
|
addReplyProto(c,"\r\n",2);
|
||||||
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reply with an array type of 'len' elements. However 'len' other calls
|
/* Reply with an array type of 'len' elements. However 'len' other calls
|
||||||
@ -1629,7 +1630,7 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
|||||||
ctx->saved_oparray = server.also_propagate;
|
ctx->saved_oparray = server.also_propagate;
|
||||||
redisOpArrayInit(&server.also_propagate);
|
redisOpArrayInit(&server.also_propagate);
|
||||||
}
|
}
|
||||||
execCommandPropagateMulti(ctx->client);
|
execCommandPropagateMulti(ctx->client->db->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Replicate the specified command and arguments to slaves and AOF, as effect
|
/* Replicate the specified command and arguments to slaves and AOF, as effect
|
||||||
@ -2044,7 +2045,7 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
|
|||||||
* periodically in timer callbacks or other periodic callbacks.
|
* periodically in timer callbacks or other periodic callbacks.
|
||||||
*/
|
*/
|
||||||
int RM_AvoidReplicaTraffic() {
|
int RM_AvoidReplicaTraffic() {
|
||||||
return clientsArePaused();
|
return checkClientPauseTimeoutAndReturnIfPaused();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Change the currently selected DB. Returns an error if the id
|
/* Change the currently selected DB. Returns an error if the id
|
||||||
@ -7067,33 +7068,32 @@ int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleSc
|
|||||||
*/
|
*/
|
||||||
int RM_Fork(RedisModuleForkDoneHandler cb, void *user_data) {
|
int RM_Fork(RedisModuleForkDoneHandler cb, void *user_data) {
|
||||||
pid_t childpid;
|
pid_t childpid;
|
||||||
if (hasActiveChildProcess()) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
openChildInfoPipe();
|
|
||||||
if ((childpid = redisFork(CHILD_TYPE_MODULE)) == 0) {
|
if ((childpid = redisFork(CHILD_TYPE_MODULE)) == 0) {
|
||||||
/* Child */
|
/* Child */
|
||||||
redisSetProcTitle("redis-module-fork");
|
redisSetProcTitle("redis-module-fork");
|
||||||
} else if (childpid == -1) {
|
} else if (childpid == -1) {
|
||||||
closeChildInfoPipe();
|
|
||||||
serverLog(LL_WARNING,"Can't fork for module: %s", strerror(errno));
|
serverLog(LL_WARNING,"Can't fork for module: %s", strerror(errno));
|
||||||
} else {
|
} else {
|
||||||
/* Parent */
|
/* Parent */
|
||||||
server.module_child_pid = childpid;
|
|
||||||
moduleForkInfo.done_handler = cb;
|
moduleForkInfo.done_handler = cb;
|
||||||
moduleForkInfo.done_handler_user_data = user_data;
|
moduleForkInfo.done_handler_user_data = user_data;
|
||||||
updateDictResizePolicy();
|
|
||||||
serverLog(LL_VERBOSE, "Module fork started pid: %ld ", (long) childpid);
|
serverLog(LL_VERBOSE, "Module fork started pid: %ld ", (long) childpid);
|
||||||
}
|
}
|
||||||
return childpid;
|
return childpid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The module is advised to call this function from the fork child once in a while,
|
||||||
|
* so that it can report COW memory to the parent which will be reported in INFO */
|
||||||
|
void RM_SendChildCOWInfo(void) {
|
||||||
|
sendChildCOWInfo(CHILD_TYPE_MODULE, 0, "Module fork");
|
||||||
|
}
|
||||||
|
|
||||||
/* Call from the child process when you want to terminate it.
|
/* Call from the child process when you want to terminate it.
|
||||||
* retcode will be provided to the done handler executed on the parent process.
|
* retcode will be provided to the done handler executed on the parent process.
|
||||||
*/
|
*/
|
||||||
int RM_ExitFromChild(int retcode) {
|
int RM_ExitFromChild(int retcode) {
|
||||||
sendChildCOWInfo(CHILD_TYPE_MODULE, "Module fork");
|
sendChildCOWInfo(CHILD_TYPE_MODULE, 1, "Module fork");
|
||||||
exitFromChild(retcode);
|
exitFromChild(retcode);
|
||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
@ -7103,22 +7103,20 @@ int RM_ExitFromChild(int retcode) {
|
|||||||
* child or the pid does not match, return C_ERR without doing anything. */
|
* child or the pid does not match, return C_ERR without doing anything. */
|
||||||
int TerminateModuleForkChild(int child_pid, int wait) {
|
int TerminateModuleForkChild(int child_pid, int wait) {
|
||||||
/* Module child should be active and pid should match. */
|
/* Module child should be active and pid should match. */
|
||||||
if (server.module_child_pid == -1 ||
|
if (server.child_type != CHILD_TYPE_MODULE ||
|
||||||
server.module_child_pid != child_pid) return C_ERR;
|
server.child_pid != child_pid) return C_ERR;
|
||||||
|
|
||||||
int statloc;
|
int statloc;
|
||||||
serverLog(LL_VERBOSE,"Killing running module fork child: %ld",
|
serverLog(LL_VERBOSE,"Killing running module fork child: %ld",
|
||||||
(long) server.module_child_pid);
|
(long) server.child_pid);
|
||||||
if (kill(server.module_child_pid,SIGUSR1) != -1 && wait) {
|
if (kill(server.child_pid,SIGUSR1) != -1 && wait) {
|
||||||
while(wait4(server.module_child_pid,&statloc,0,NULL) !=
|
while(wait4(server.child_pid,&statloc,0,NULL) !=
|
||||||
server.module_child_pid);
|
server.child_pid);
|
||||||
}
|
}
|
||||||
/* Reset the buffer accumulating changes while the child saves. */
|
/* Reset the buffer accumulating changes while the child saves. */
|
||||||
server.module_child_pid = -1;
|
resetChildState();
|
||||||
moduleForkInfo.done_handler = NULL;
|
moduleForkInfo.done_handler = NULL;
|
||||||
moduleForkInfo.done_handler_user_data = NULL;
|
moduleForkInfo.done_handler_user_data = NULL;
|
||||||
closeChildInfoPipe();
|
|
||||||
updateDictResizePolicy();
|
|
||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7135,12 +7133,12 @@ int RM_KillForkChild(int child_pid) {
|
|||||||
void ModuleForkDoneHandler(int exitcode, int bysignal) {
|
void ModuleForkDoneHandler(int exitcode, int bysignal) {
|
||||||
serverLog(LL_NOTICE,
|
serverLog(LL_NOTICE,
|
||||||
"Module fork exited pid: %ld, retcode: %d, bysignal: %d",
|
"Module fork exited pid: %ld, retcode: %d, bysignal: %d",
|
||||||
(long) server.module_child_pid, exitcode, bysignal);
|
(long) server.child_pid, exitcode, bysignal);
|
||||||
if (moduleForkInfo.done_handler) {
|
if (moduleForkInfo.done_handler) {
|
||||||
moduleForkInfo.done_handler(exitcode, bysignal,
|
moduleForkInfo.done_handler(exitcode, bysignal,
|
||||||
moduleForkInfo.done_handler_user_data);
|
moduleForkInfo.done_handler_user_data);
|
||||||
}
|
}
|
||||||
server.module_child_pid = -1;
|
|
||||||
moduleForkInfo.done_handler = NULL;
|
moduleForkInfo.done_handler = NULL;
|
||||||
moduleForkInfo.done_handler_user_data = NULL;
|
moduleForkInfo.done_handler_user_data = NULL;
|
||||||
}
|
}
|
||||||
@ -7938,14 +7936,21 @@ sds genModulesInfoString(sds info) {
|
|||||||
|
|
||||||
/* Redis MODULE command.
|
/* Redis MODULE command.
|
||||||
*
|
*
|
||||||
* MODULE LOAD <path> [args...] */
|
* MODULE LIST
|
||||||
|
* MODULE LOAD <path> [args...]
|
||||||
|
* MODULE UNLOAD <name>
|
||||||
|
*/
|
||||||
void moduleCommand(client *c) {
|
void moduleCommand(client *c) {
|
||||||
char *subcmd = c->argv[1]->ptr;
|
char *subcmd = c->argv[1]->ptr;
|
||||||
|
|
||||||
if (c->argc == 2 && !strcasecmp(subcmd,"help")) {
|
if (c->argc == 2 && !strcasecmp(subcmd,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"LIST -- Return a list of loaded modules.",
|
"LIST",
|
||||||
"LOAD <path> [arg ...] -- Load a module library from <path>.",
|
" Return a list of loaded modules.",
|
||||||
"UNLOAD <name> -- Unload a module.",
|
"LOAD <path> [<arg> ...]",
|
||||||
|
" Load a module library from <path>, passing to it any optional arguments.",
|
||||||
|
"UNLOAD <name>",
|
||||||
|
" Unload a module.",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
@ -8594,6 +8599,7 @@ void moduleRegisterCoreAPI(void) {
|
|||||||
REGISTER_API(CommandFilterArgReplace);
|
REGISTER_API(CommandFilterArgReplace);
|
||||||
REGISTER_API(CommandFilterArgDelete);
|
REGISTER_API(CommandFilterArgDelete);
|
||||||
REGISTER_API(Fork);
|
REGISTER_API(Fork);
|
||||||
|
REGISTER_API(SendChildCOWInfo);
|
||||||
REGISTER_API(ExitFromChild);
|
REGISTER_API(ExitFromChild);
|
||||||
REGISTER_API(KillForkChild);
|
REGISTER_API(KillForkChild);
|
||||||
REGISTER_API(RegisterInfoFunc);
|
REGISTER_API(RegisterInfoFunc);
|
||||||
|
@ -364,7 +364,7 @@ int TestAssertIntegerReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply, lon
|
|||||||
do { \
|
do { \
|
||||||
RedisModule_Log(ctx,"warning","Testing %s", name); \
|
RedisModule_Log(ctx,"warning","Testing %s", name); \
|
||||||
reply = RedisModule_Call(ctx,name,__VA_ARGS__); \
|
reply = RedisModule_Call(ctx,name,__VA_ARGS__); \
|
||||||
} while (0);
|
} while (0)
|
||||||
|
|
||||||
/* TEST.IT -- Run all the tests. */
|
/* TEST.IT -- Run all the tests. */
|
||||||
int TestIt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
int TestIt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
187
src/mt19937-64.c
Normal file
187
src/mt19937-64.c
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
A C-program for MT19937-64 (2004/9/29 version).
|
||||||
|
Coded by Takuji Nishimura and Makoto Matsumoto.
|
||||||
|
|
||||||
|
This is a 64-bit version of Mersenne Twister pseudorandom number
|
||||||
|
generator.
|
||||||
|
|
||||||
|
Before using, initialize the state by using init_genrand64(seed)
|
||||||
|
or init_by_array64(init_key, key_length).
|
||||||
|
|
||||||
|
Copyright (C) 2004, Makoto Matsumoto and Takuji Nishimura,
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. The names of its contributors may not be used to endorse or promote
|
||||||
|
products derived from this software without specific prior written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
References:
|
||||||
|
T. Nishimura, ``Tables of 64-bit Mersenne Twisters''
|
||||||
|
ACM Transactions on Modeling and
|
||||||
|
Computer Simulation 10. (2000) 348--357.
|
||||||
|
M. Matsumoto and T. Nishimura,
|
||||||
|
``Mersenne Twister: a 623-dimensionally equidistributed
|
||||||
|
uniform pseudorandom number generator''
|
||||||
|
ACM Transactions on Modeling and
|
||||||
|
Computer Simulation 8. (Jan. 1998) 3--30.
|
||||||
|
|
||||||
|
Any feedback is very welcome.
|
||||||
|
http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html
|
||||||
|
email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "mt19937-64.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define NN 312
|
||||||
|
#define MM 156
|
||||||
|
#define MATRIX_A 0xB5026F5AA96619E9ULL
|
||||||
|
#define UM 0xFFFFFFFF80000000ULL /* Most significant 33 bits */
|
||||||
|
#define LM 0x7FFFFFFFULL /* Least significant 31 bits */
|
||||||
|
|
||||||
|
|
||||||
|
/* The array for the state vector */
|
||||||
|
static unsigned long long mt[NN];
|
||||||
|
/* mti==NN+1 means mt[NN] is not initialized */
|
||||||
|
static int mti=NN+1;
|
||||||
|
|
||||||
|
/* initializes mt[NN] with a seed */
|
||||||
|
void init_genrand64(unsigned long long seed)
|
||||||
|
{
|
||||||
|
mt[0] = seed;
|
||||||
|
for (mti=1; mti<NN; mti++)
|
||||||
|
mt[mti] = (6364136223846793005ULL * (mt[mti-1] ^ (mt[mti-1] >> 62)) + mti);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* initialize by an array with array-length */
|
||||||
|
/* init_key is the array for initializing keys */
|
||||||
|
/* key_length is its length */
|
||||||
|
void init_by_array64(unsigned long long init_key[],
|
||||||
|
unsigned long long key_length)
|
||||||
|
{
|
||||||
|
unsigned long long i, j, k;
|
||||||
|
init_genrand64(19650218ULL);
|
||||||
|
i=1; j=0;
|
||||||
|
k = (NN>key_length ? NN : key_length);
|
||||||
|
for (; k; k--) {
|
||||||
|
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 62)) * 3935559000370003845ULL))
|
||||||
|
+ init_key[j] + j; /* non linear */
|
||||||
|
i++; j++;
|
||||||
|
if (i>=NN) { mt[0] = mt[NN-1]; i=1; }
|
||||||
|
if (j>=key_length) j=0;
|
||||||
|
}
|
||||||
|
for (k=NN-1; k; k--) {
|
||||||
|
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 62)) * 2862933555777941757ULL))
|
||||||
|
- i; /* non linear */
|
||||||
|
i++;
|
||||||
|
if (i>=NN) { mt[0] = mt[NN-1]; i=1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
mt[0] = 1ULL << 63; /* MSB is 1; assuring non-zero initial array */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* generates a random number on [0, 2^64-1]-interval */
|
||||||
|
unsigned long long genrand64_int64(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
unsigned long long x;
|
||||||
|
static unsigned long long mag01[2]={0ULL, MATRIX_A};
|
||||||
|
|
||||||
|
if (mti >= NN) { /* generate NN words at one time */
|
||||||
|
|
||||||
|
/* if init_genrand64() has not been called, */
|
||||||
|
/* a default initial seed is used */
|
||||||
|
if (mti == NN+1)
|
||||||
|
init_genrand64(5489ULL);
|
||||||
|
|
||||||
|
for (i=0;i<NN-MM;i++) {
|
||||||
|
x = (mt[i]&UM)|(mt[i+1]&LM);
|
||||||
|
mt[i] = mt[i+MM] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
|
||||||
|
}
|
||||||
|
for (;i<NN-1;i++) {
|
||||||
|
x = (mt[i]&UM)|(mt[i+1]&LM);
|
||||||
|
mt[i] = mt[i+(MM-NN)] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
|
||||||
|
}
|
||||||
|
x = (mt[NN-1]&UM)|(mt[0]&LM);
|
||||||
|
mt[NN-1] = mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
|
||||||
|
|
||||||
|
mti = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = mt[mti++];
|
||||||
|
|
||||||
|
x ^= (x >> 29) & 0x5555555555555555ULL;
|
||||||
|
x ^= (x << 17) & 0x71D67FFFEDA60000ULL;
|
||||||
|
x ^= (x << 37) & 0xFFF7EEE000000000ULL;
|
||||||
|
x ^= (x >> 43);
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* generates a random number on [0, 2^63-1]-interval */
|
||||||
|
long long genrand64_int63(void)
|
||||||
|
{
|
||||||
|
return (long long)(genrand64_int64() >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* generates a random number on [0,1]-real-interval */
|
||||||
|
double genrand64_real1(void)
|
||||||
|
{
|
||||||
|
return (genrand64_int64() >> 11) * (1.0/9007199254740991.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* generates a random number on [0,1)-real-interval */
|
||||||
|
double genrand64_real2(void)
|
||||||
|
{
|
||||||
|
return (genrand64_int64() >> 11) * (1.0/9007199254740992.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* generates a random number on (0,1)-real-interval */
|
||||||
|
double genrand64_real3(void)
|
||||||
|
{
|
||||||
|
return ((genrand64_int64() >> 12) + 0.5) * (1.0/4503599627370496.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef MT19937_64_MAIN
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
unsigned long long init[4]={0x12345ULL, 0x23456ULL, 0x34567ULL, 0x45678ULL}, length=4;
|
||||||
|
init_by_array64(init, length);
|
||||||
|
printf("1000 outputs of genrand64_int64()\n");
|
||||||
|
for (i=0; i<1000; i++) {
|
||||||
|
printf("%20llu ", genrand64_int64());
|
||||||
|
if (i%5==4) printf("\n");
|
||||||
|
}
|
||||||
|
printf("\n1000 outputs of genrand64_real2()\n");
|
||||||
|
for (i=0; i<1000; i++) {
|
||||||
|
printf("%10.8f ", genrand64_real2());
|
||||||
|
if (i%5==4) printf("\n");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
87
src/mt19937-64.h
Normal file
87
src/mt19937-64.h
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
A C-program for MT19937-64 (2004/9/29 version).
|
||||||
|
Coded by Takuji Nishimura and Makoto Matsumoto.
|
||||||
|
|
||||||
|
This is a 64-bit version of Mersenne Twister pseudorandom number
|
||||||
|
generator.
|
||||||
|
|
||||||
|
Before using, initialize the state by using init_genrand64(seed)
|
||||||
|
or init_by_array64(init_key, key_length).
|
||||||
|
|
||||||
|
Copyright (C) 2004, Makoto Matsumoto and Takuji Nishimura,
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. The names of its contributors may not be used to endorse or promote
|
||||||
|
products derived from this software without specific prior written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
References:
|
||||||
|
T. Nishimura, ``Tables of 64-bit Mersenne Twisters''
|
||||||
|
ACM Transactions on Modeling and
|
||||||
|
Computer Simulation 10. (2000) 348--357.
|
||||||
|
M. Matsumoto and T. Nishimura,
|
||||||
|
``Mersenne Twister: a 623-dimensionally equidistributed
|
||||||
|
uniform pseudorandom number generator''
|
||||||
|
ACM Transactions on Modeling and
|
||||||
|
Computer Simulation 8. (Jan. 1998) 3--30.
|
||||||
|
|
||||||
|
Any feedback is very welcome.
|
||||||
|
http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html
|
||||||
|
email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __MT19937_64_H
|
||||||
|
#define __MT19937_64_H
|
||||||
|
|
||||||
|
/* initializes mt[NN] with a seed */
|
||||||
|
void init_genrand64(unsigned long long seed);
|
||||||
|
|
||||||
|
/* initialize by an array with array-length */
|
||||||
|
/* init_key is the array for initializing keys */
|
||||||
|
/* key_length is its length */
|
||||||
|
void init_by_array64(unsigned long long init_key[],
|
||||||
|
unsigned long long key_length);
|
||||||
|
|
||||||
|
/* generates a random number on [0, 2^64-1]-interval */
|
||||||
|
unsigned long long genrand64_int64(void);
|
||||||
|
|
||||||
|
|
||||||
|
/* generates a random number on [0, 2^63-1]-interval */
|
||||||
|
long long genrand64_int63(void);
|
||||||
|
|
||||||
|
/* generates a random number on [0,1]-real-interval */
|
||||||
|
double genrand64_real1(void);
|
||||||
|
|
||||||
|
/* generates a random number on [0,1)-real-interval */
|
||||||
|
double genrand64_real2(void);
|
||||||
|
|
||||||
|
/* generates a random number on (0,1)-real-interval */
|
||||||
|
double genrand64_real3(void);
|
||||||
|
|
||||||
|
/* generates a random number on (0,1]-real-interval */
|
||||||
|
double genrand64_real4(void);
|
||||||
|
|
||||||
|
#endif
|
67
src/multi.c
67
src/multi.c
@ -127,15 +127,15 @@ void beforePropagateMultiOrExec(int multi) {
|
|||||||
|
|
||||||
/* Send a MULTI command to all the slaves and AOF file. Check the execCommand
|
/* Send a MULTI command to all the slaves and AOF file. Check the execCommand
|
||||||
* implementation for more information. */
|
* implementation for more information. */
|
||||||
void execCommandPropagateMulti(client *c) {
|
void execCommandPropagateMulti(int dbid) {
|
||||||
beforePropagateMultiOrExec(1);
|
beforePropagateMultiOrExec(1);
|
||||||
propagate(server.multiCommand,c->db->id,&shared.multi,1,
|
propagate(server.multiCommand,dbid,&shared.multi,1,
|
||||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void execCommandPropagateExec(client *c) {
|
void execCommandPropagateExec(int dbid) {
|
||||||
beforePropagateMultiOrExec(0);
|
beforePropagateMultiOrExec(0);
|
||||||
propagate(server.execCommand,c->db->id,&shared.exec,1,
|
propagate(server.execCommand,dbid,&shared.exec,1,
|
||||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +162,6 @@ void execCommand(client *c) {
|
|||||||
robj **orig_argv;
|
robj **orig_argv;
|
||||||
int orig_argc;
|
int orig_argc;
|
||||||
struct redisCommand *orig_cmd;
|
struct redisCommand *orig_cmd;
|
||||||
int must_propagate = 0; /* Need to propagate MULTI/EXEC to AOF / slaves? */
|
|
||||||
int was_master = server.masterhost == NULL;
|
int was_master = server.masterhost == NULL;
|
||||||
|
|
||||||
if (!(c->flags & CLIENT_MULTI)) {
|
if (!(c->flags & CLIENT_MULTI)) {
|
||||||
@ -202,19 +201,6 @@ void execCommand(client *c) {
|
|||||||
c->argv = c->mstate.commands[j].argv;
|
c->argv = c->mstate.commands[j].argv;
|
||||||
c->cmd = c->mstate.commands[j].cmd;
|
c->cmd = c->mstate.commands[j].cmd;
|
||||||
|
|
||||||
/* Propagate a MULTI request once we encounter the first command which
|
|
||||||
* is not readonly nor an administrative one.
|
|
||||||
* This way we'll deliver the MULTI/..../EXEC block as a whole and
|
|
||||||
* both the AOF and the replication link will have the same consistency
|
|
||||||
* and atomicity guarantees. */
|
|
||||||
if (!must_propagate &&
|
|
||||||
!server.loading &&
|
|
||||||
!(c->cmd->flags & (CMD_READONLY|CMD_ADMIN)))
|
|
||||||
{
|
|
||||||
execCommandPropagateMulti(c);
|
|
||||||
must_propagate = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ACL permissions are also checked at the time of execution in case
|
/* ACL permissions are also checked at the time of execution in case
|
||||||
* they were changed after the commands were ququed. */
|
* they were changed after the commands were ququed. */
|
||||||
int acl_errpos;
|
int acl_errpos;
|
||||||
@ -265,7 +251,7 @@ void execCommand(client *c) {
|
|||||||
|
|
||||||
/* Make sure the EXEC command will be propagated as well if MULTI
|
/* Make sure the EXEC command will be propagated as well if MULTI
|
||||||
* was already propagated. */
|
* was already propagated. */
|
||||||
if (must_propagate) {
|
if (server.propagate_in_transaction) {
|
||||||
int is_master = server.masterhost == NULL;
|
int is_master = server.masterhost == NULL;
|
||||||
server.dirty++;
|
server.dirty++;
|
||||||
beforePropagateMultiOrExec(0);
|
beforePropagateMultiOrExec(0);
|
||||||
@ -388,31 +374,36 @@ void touchWatchedKey(redisDb *db, robj *key) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* On FLUSHDB or FLUSHALL all the watched keys that are present before the
|
/* Set CLIENT_DIRTY_CAS to all clients of DB when DB is dirty.
|
||||||
* flush but will be deleted as effect of the flushing operation should
|
* It may happen in the following situations:
|
||||||
* be touched. "dbid" is the DB that's getting the flush. -1 if it is
|
* FLUSHDB, FLUSHALL, SWAPDB
|
||||||
* a FLUSHALL operation (all the DBs flushed). */
|
*
|
||||||
void touchWatchedKeysOnFlush(int dbid) {
|
* replaced_with: for SWAPDB, the WATCH should be invalidated if
|
||||||
listIter li1, li2;
|
* the key exists in either of them, and skipped only if it
|
||||||
|
* doesn't exist in both. */
|
||||||
|
void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with) {
|
||||||
|
listIter li;
|
||||||
listNode *ln;
|
listNode *ln;
|
||||||
|
dictEntry *de;
|
||||||
|
|
||||||
/* For every client, check all the waited keys */
|
if (dictSize(emptied->watched_keys) == 0) return;
|
||||||
listRewind(server.clients,&li1);
|
|
||||||
while((ln = listNext(&li1))) {
|
|
||||||
client *c = listNodeValue(ln);
|
|
||||||
listRewind(c->watched_keys,&li2);
|
|
||||||
while((ln = listNext(&li2))) {
|
|
||||||
watchedKey *wk = listNodeValue(ln);
|
|
||||||
|
|
||||||
/* For every watched key matching the specified DB, if the
|
dictIterator *di = dictGetSafeIterator(emptied->watched_keys);
|
||||||
* key exists, mark the client as dirty, as the key will be
|
while((de = dictNext(di)) != NULL) {
|
||||||
* removed. */
|
robj *key = dictGetKey(de);
|
||||||
if (dbid == -1 || wk->db->id == dbid) {
|
list *clients = dictGetVal(de);
|
||||||
if (dictFind(wk->db->dict, wk->key->ptr) != NULL)
|
if (!clients) continue;
|
||||||
c->flags |= CLIENT_DIRTY_CAS;
|
listRewind(clients,&li);
|
||||||
|
while((ln = listNext(&li))) {
|
||||||
|
client *c = listNodeValue(ln);
|
||||||
|
if (dictFind(emptied->dict, key->ptr)) {
|
||||||
|
c->flags |= CLIENT_DIRTY_CAS;
|
||||||
|
} else if (replaced_with && dictFind(replaced_with->dict, key->ptr)) {
|
||||||
|
c->flags |= CLIENT_DIRTY_CAS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dictReleaseIterator(di);
|
||||||
}
|
}
|
||||||
|
|
||||||
void watchCommand(client *c) {
|
void watchCommand(client *c) {
|
||||||
|
412
src/networking.c
412
src/networking.c
@ -178,6 +178,7 @@ client *createClient(connection *conn) {
|
|||||||
c->peerid = NULL;
|
c->peerid = NULL;
|
||||||
c->sockname = NULL;
|
c->sockname = NULL;
|
||||||
c->client_list_node = NULL;
|
c->client_list_node = NULL;
|
||||||
|
c->paused_list_node = NULL;
|
||||||
c->client_tracking_redirection = 0;
|
c->client_tracking_redirection = 0;
|
||||||
c->client_tracking_prefixes = NULL;
|
c->client_tracking_prefixes = NULL;
|
||||||
c->client_cron_last_memory_usage = 0;
|
c->client_cron_last_memory_usage = 0;
|
||||||
@ -259,8 +260,14 @@ int prepareClientToWrite(client *c) {
|
|||||||
if (!c->conn) return C_ERR; /* Fake client for AOF loading. */
|
if (!c->conn) return C_ERR; /* Fake client for AOF loading. */
|
||||||
|
|
||||||
/* Schedule the client to write the output buffers to the socket, unless
|
/* Schedule the client to write the output buffers to the socket, unless
|
||||||
* it should already be setup to do so (it has already pending data). */
|
* it should already be setup to do so (it has already pending data).
|
||||||
if (!clientHasPendingReplies(c)) clientInstallWriteHandler(c);
|
*
|
||||||
|
* If CLIENT_PENDING_READ is set, we're in an IO thread and should
|
||||||
|
* not install a write handler. Instead, it will be done by
|
||||||
|
* handleClientsWithPendingReadsUsingThreads() upon return.
|
||||||
|
*/
|
||||||
|
if (!clientHasPendingReplies(c) && !(c->flags & CLIENT_PENDING_READ))
|
||||||
|
clientInstallWriteHandler(c);
|
||||||
|
|
||||||
/* Authorize the caller to queue in the output buffer of this client. */
|
/* Authorize the caller to queue in the output buffer of this client. */
|
||||||
return C_OK;
|
return C_OK;
|
||||||
@ -399,6 +406,24 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
|
|||||||
|
|
||||||
/* Do some actions after an error reply was sent (Log if needed, updates stats, etc.) */
|
/* Do some actions after an error reply was sent (Log if needed, updates stats, etc.) */
|
||||||
void afterErrorReply(client *c, const char *s, size_t len) {
|
void afterErrorReply(client *c, const char *s, size_t len) {
|
||||||
|
/* Increment the global error counter */
|
||||||
|
server.stat_total_error_replies++;
|
||||||
|
/* Increment the error stats
|
||||||
|
* If the string already starts with "-..." then the error prefix
|
||||||
|
* is provided by the caller ( we limit the search to 32 chars). Otherwise we use "-ERR". */
|
||||||
|
if (s[0] != '-') {
|
||||||
|
incrementErrorCount("ERR", 3);
|
||||||
|
} else {
|
||||||
|
char *spaceloc = memchr(s, ' ', len < 32 ? len : 32);
|
||||||
|
if (spaceloc) {
|
||||||
|
const size_t errEndPos = (size_t)(spaceloc - s);
|
||||||
|
incrementErrorCount(s+1, errEndPos-1);
|
||||||
|
} else {
|
||||||
|
/* Fallback to ERR if we can't retrieve the error prefix */
|
||||||
|
incrementErrorCount("ERR", 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Sometimes it could be normal that a slave replies to a master with
|
/* Sometimes it could be normal that a slave replies to a master with
|
||||||
* an error and this function gets called. Actually the error will never
|
* an error and this function gets called. Actually the error will never
|
||||||
* be sent because addReply*() against master clients has no effect...
|
* be sent because addReply*() against master clients has no effect...
|
||||||
@ -452,9 +477,11 @@ void addReplyError(client *c, const char *err) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* See addReplyErrorLength for expectations from the input string. */
|
/* See addReplyErrorLength for expectations from the input string. */
|
||||||
|
/* As a side effect the SDS string is freed. */
|
||||||
void addReplyErrorSds(client *c, sds err) {
|
void addReplyErrorSds(client *c, sds err) {
|
||||||
addReplyErrorLength(c,err,sdslen(err));
|
addReplyErrorLength(c,err,sdslen(err));
|
||||||
afterErrorReply(c,err,sdslen(err));
|
afterErrorReply(c,err,sdslen(err));
|
||||||
|
sdsfree(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* See addReplyErrorLength for expectations from the formatted string.
|
/* See addReplyErrorLength for expectations from the formatted string.
|
||||||
@ -533,13 +560,9 @@ void *addReplyDeferredLen(client *c) {
|
|||||||
return listLast(c->reply);
|
return listLast(c->reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Populate the length object and try gluing it to the next chunk. */
|
void setDeferredReply(client *c, void *node, const char *s, size_t length) {
|
||||||
void setDeferredAggregateLen(client *c, void *node, long length, char prefix) {
|
|
||||||
serverAssert(length >= 0);
|
|
||||||
listNode *ln = (listNode*)node;
|
listNode *ln = (listNode*)node;
|
||||||
clientReplyBlock *next;
|
clientReplyBlock *next;
|
||||||
char lenstr[128];
|
|
||||||
size_t lenstr_len = sprintf(lenstr, "%c%ld\r\n", prefix, length);
|
|
||||||
|
|
||||||
/* Abort when *node is NULL: when the client should not accept writes
|
/* Abort when *node is NULL: when the client should not accept writes
|
||||||
* we return NULL in addReplyDeferredLen() */
|
* we return NULL in addReplyDeferredLen() */
|
||||||
@ -557,25 +580,39 @@ void setDeferredAggregateLen(client *c, void *node, long length, char prefix) {
|
|||||||
* - It has enough room already allocated
|
* - It has enough room already allocated
|
||||||
* - And not too large (avoid large memmove) */
|
* - And not too large (avoid large memmove) */
|
||||||
if (ln->next != NULL && (next = listNodeValue(ln->next)) &&
|
if (ln->next != NULL && (next = listNodeValue(ln->next)) &&
|
||||||
next->size - next->used >= lenstr_len &&
|
next->size - next->used >= length &&
|
||||||
next->used < PROTO_REPLY_CHUNK_BYTES * 4) {
|
next->used < PROTO_REPLY_CHUNK_BYTES * 4)
|
||||||
memmove(next->buf + lenstr_len, next->buf, next->used);
|
{
|
||||||
memcpy(next->buf, lenstr, lenstr_len);
|
memmove(next->buf + length, next->buf, next->used);
|
||||||
next->used += lenstr_len;
|
memcpy(next->buf, s, length);
|
||||||
|
next->used += length;
|
||||||
listDelNode(c->reply,ln);
|
listDelNode(c->reply,ln);
|
||||||
} else {
|
} else {
|
||||||
/* Create a new node */
|
/* Create a new node */
|
||||||
clientReplyBlock *buf = zmalloc(lenstr_len + sizeof(clientReplyBlock));
|
clientReplyBlock *buf = zmalloc(length + sizeof(clientReplyBlock));
|
||||||
/* Take over the allocation's internal fragmentation */
|
/* Take over the allocation's internal fragmentation */
|
||||||
buf->size = zmalloc_usable_size(buf) - sizeof(clientReplyBlock);
|
buf->size = zmalloc_usable_size(buf) - sizeof(clientReplyBlock);
|
||||||
buf->used = lenstr_len;
|
buf->used = length;
|
||||||
memcpy(buf->buf, lenstr, lenstr_len);
|
memcpy(buf->buf, s, length);
|
||||||
listNodeValue(ln) = buf;
|
listNodeValue(ln) = buf;
|
||||||
c->reply_bytes += buf->size;
|
c->reply_bytes += buf->size;
|
||||||
}
|
}
|
||||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Populate the length object and try gluing it to the next chunk. */
|
||||||
|
void setDeferredAggregateLen(client *c, void *node, long length, char prefix) {
|
||||||
|
serverAssert(length >= 0);
|
||||||
|
|
||||||
|
/* Abort when *node is NULL: when the client should not accept writes
|
||||||
|
* we return NULL in addReplyDeferredLen() */
|
||||||
|
if (node == NULL) return;
|
||||||
|
|
||||||
|
char lenstr[128];
|
||||||
|
size_t lenstr_len = sprintf(lenstr, "%c%ld\r\n", prefix, length);
|
||||||
|
setDeferredReply(c, node, lenstr, lenstr_len);
|
||||||
|
}
|
||||||
|
|
||||||
void setDeferredArrayLen(client *c, void *node, long length) {
|
void setDeferredArrayLen(client *c, void *node, long length) {
|
||||||
setDeferredAggregateLen(c,node,length,'*');
|
setDeferredAggregateLen(c,node,length,'*');
|
||||||
}
|
}
|
||||||
@ -771,6 +808,14 @@ void addReplyBulkSds(client *c, sds s) {
|
|||||||
addReply(c,shared.crlf);
|
addReply(c,shared.crlf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set sds to a deferred reply (for symmetry with addReplyBulkSds it also frees the sds) */
|
||||||
|
void setDeferredReplyBulkSds(client *c, void *node, sds s) {
|
||||||
|
sds reply = sdscatprintf(sdsempty(), "$%d\r\n%s\r\n", (unsigned)sdslen(s), s);
|
||||||
|
setDeferredReply(c, node, reply, sdslen(reply));
|
||||||
|
sdsfree(reply);
|
||||||
|
sdsfree(s);
|
||||||
|
}
|
||||||
|
|
||||||
/* Add a C null term string as bulk reply */
|
/* Add a C null term string as bulk reply */
|
||||||
void addReplyBulkCString(client *c, const char *s) {
|
void addReplyBulkCString(client *c, const char *s) {
|
||||||
if (s == NULL) {
|
if (s == NULL) {
|
||||||
@ -829,12 +874,16 @@ void addReplyHelp(client *c, const char **help) {
|
|||||||
|
|
||||||
sdstoupper(cmd);
|
sdstoupper(cmd);
|
||||||
addReplyStatusFormat(c,
|
addReplyStatusFormat(c,
|
||||||
"%s <subcommand> arg arg ... arg. Subcommands are:",cmd);
|
"%s <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",cmd);
|
||||||
sdsfree(cmd);
|
sdsfree(cmd);
|
||||||
|
|
||||||
while (help[blen]) addReplyStatus(c,help[blen++]);
|
while (help[blen]) addReplyStatus(c,help[blen++]);
|
||||||
|
|
||||||
blen++; /* Account for the header line(s). */
|
addReplyStatus(c,"HELP");
|
||||||
|
addReplyStatus(c," Prints this help.");
|
||||||
|
|
||||||
|
blen += 1; /* Account for the header. */
|
||||||
|
blen += 2; /* Account for the footer. */
|
||||||
setDeferredArrayLen(c,blenp,blen);
|
setDeferredArrayLen(c,blenp,blen);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1310,7 +1359,7 @@ void freeClient(client *c) {
|
|||||||
* to keep data safe and we may delay configured 'save' for full sync. */
|
* to keep data safe and we may delay configured 'save' for full sync. */
|
||||||
if (server.saveparamslen == 0 &&
|
if (server.saveparamslen == 0 &&
|
||||||
c->replstate == SLAVE_STATE_WAIT_BGSAVE_END &&
|
c->replstate == SLAVE_STATE_WAIT_BGSAVE_END &&
|
||||||
server.rdb_child_pid != -1 &&
|
server.child_type == CHILD_TYPE_RDB &&
|
||||||
server.rdb_child_type == RDB_CHILD_TYPE_DISK &&
|
server.rdb_child_type == RDB_CHILD_TYPE_DISK &&
|
||||||
anyOtherSlaveWaitRdb(c) == 0)
|
anyOtherSlaveWaitRdb(c) == 0)
|
||||||
{
|
{
|
||||||
@ -1686,6 +1735,7 @@ int processInlineBuffer(client *c) {
|
|||||||
* However the is an exception: masters may send us just a newline
|
* However the is an exception: masters may send us just a newline
|
||||||
* to keep the connection active. */
|
* to keep the connection active. */
|
||||||
if (querylen != 0 && c->flags & CLIENT_MASTER) {
|
if (querylen != 0 && c->flags & CLIENT_MASTER) {
|
||||||
|
sdsfreesplitres(argv,argc);
|
||||||
serverLog(LL_WARNING,"WARNING: Receiving inline protocol from master, master stream corruption? Closing the master connection and discarding the cached master.");
|
serverLog(LL_WARNING,"WARNING: Receiving inline protocol from master, master stream corruption? Closing the master connection and discarding the cached master.");
|
||||||
setProtocolError("Master using the inline protocol. Desync?",c);
|
setProtocolError("Master using the inline protocol. Desync?",c);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
@ -1851,7 +1901,7 @@ int processMultibulkBuffer(client *c) {
|
|||||||
c->qb_pos = 0;
|
c->qb_pos = 0;
|
||||||
/* Hint the sds library about the amount of bytes this string is
|
/* Hint the sds library about the amount of bytes this string is
|
||||||
* going to contain. */
|
* going to contain. */
|
||||||
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2);
|
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2-sdslen(c->querybuf));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c->bulklen = ll;
|
c->bulklen = ll;
|
||||||
@ -1911,7 +1961,7 @@ void commandProcessed(client *c) {
|
|||||||
* still be able to access the client argv and argc field.
|
* still be able to access the client argv and argc field.
|
||||||
* The client will be reset in unblockClientFromModule(). */
|
* The client will be reset in unblockClientFromModule(). */
|
||||||
if (!(c->flags & CLIENT_BLOCKED) ||
|
if (!(c->flags & CLIENT_BLOCKED) ||
|
||||||
c->btype != BLOCKED_MODULE)
|
(c->btype != BLOCKED_MODULE && c->btype != BLOCKED_PAUSE))
|
||||||
{
|
{
|
||||||
resetClient(c);
|
resetClient(c);
|
||||||
}
|
}
|
||||||
@ -1954,6 +2004,20 @@ int processCommandAndResetClient(client *c) {
|
|||||||
return deadclient ? C_ERR : C_OK;
|
return deadclient ? C_ERR : C_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* This function will execute any fully parsed commands pending on
|
||||||
|
* the client. Returns C_ERR if the client is no longer valid after executing
|
||||||
|
* the command, and C_OK for all other cases. */
|
||||||
|
int processPendingCommandsAndResetClient(client *c) {
|
||||||
|
if (c->flags & CLIENT_PENDING_COMMAND) {
|
||||||
|
c->flags &= ~CLIENT_PENDING_COMMAND;
|
||||||
|
if (processCommandAndResetClient(c) == C_ERR) {
|
||||||
|
return C_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* This function is called every time, in the client structure 'c', there is
|
/* This function is called every time, in the client structure 'c', there is
|
||||||
* more query buffer to process, because we read more data from the socket
|
* more query buffer to process, because we read more data from the socket
|
||||||
* or because a client was blocked and later reactivated, so there could be
|
* or because a client was blocked and later reactivated, so there could be
|
||||||
@ -1961,9 +2025,6 @@ int processCommandAndResetClient(client *c) {
|
|||||||
void processInputBuffer(client *c) {
|
void processInputBuffer(client *c) {
|
||||||
/* Keep processing while there is something in the input buffer */
|
/* Keep processing while there is something in the input buffer */
|
||||||
while(c->qb_pos < sdslen(c->querybuf)) {
|
while(c->qb_pos < sdslen(c->querybuf)) {
|
||||||
/* Return if clients are paused. */
|
|
||||||
if (!(c->flags & CLIENT_SLAVE) && clientsArePaused()) break;
|
|
||||||
|
|
||||||
/* Immediately abort if the client is in the middle of something. */
|
/* Immediately abort if the client is in the middle of something. */
|
||||||
if (c->flags & CLIENT_BLOCKED) break;
|
if (c->flags & CLIENT_BLOCKED) break;
|
||||||
|
|
||||||
@ -2361,26 +2422,47 @@ void clientCommand(client *c) {
|
|||||||
|
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"ID -- Return the ID of the current connection.",
|
"CACHING (YES|NO)",
|
||||||
"INFO -- Return information about the current client connection.",
|
" Enable/disable tracking of the keys for next command in OPTIN/OPTOUT modes.",
|
||||||
"GETNAME -- Return the name of the current connection.",
|
"GETREDIR",
|
||||||
"KILL <ip:port> -- Kill connection made from <ip:port>.",
|
" Return the client ID we are redirecting to when tracking is enabled.",
|
||||||
"KILL <option> <value> [option value ...] -- Kill connections. Options are:",
|
"GETNAME",
|
||||||
" ADDR <ip:port> -- Kill connection made from <ip:port>",
|
" Return the name of the current connection.",
|
||||||
" LADDR <ip:port> -- Kill connection made to <ip:port>",
|
"ID",
|
||||||
" TYPE (normal|master|replica|pubsub) -- Kill connections by type.",
|
" Return the ID of the current connection.",
|
||||||
" USER <username> -- Kill connections authenticated with such user.",
|
"INFO",
|
||||||
" SKIPME (yes|no) -- Skip killing current connection (default: yes).",
|
" Return information about the current client connection.",
|
||||||
"LIST [options ...] -- Return information about client connections. Options:",
|
"KILL <ip:port>",
|
||||||
" TYPE (normal|master|replica|pubsub) -- Return clients of specified type.",
|
" Kill connection made from <ip:port>.",
|
||||||
" ID id [id ...] -- Return clients of specified IDs only.",
|
"KILL <option> <value> [<option> <value> [...]]",
|
||||||
"PAUSE <timeout> -- Suspend all Redis clients for <timout> milliseconds.",
|
" Kill connections. Options are:",
|
||||||
"REPLY (on|off|skip) -- Control the replies sent to the current connection.",
|
" * ADDR <ip:port>",
|
||||||
"SETNAME <name> -- Assign the name <name> to the current connection.",
|
" Kill connection made from <ip:port>",
|
||||||
"UNBLOCK <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.",
|
" * TYPE (normal|master|replica|pubsub)",
|
||||||
"TRACKING (on|off) [REDIRECT <id>] [BCAST] [PREFIX first] [PREFIX second] [OPTIN] [OPTOUT]... -- Enable client keys tracking for client side caching.",
|
" Kill connections by type.",
|
||||||
"CACHING (yes|no) -- Enable/Disable tracking of the keys for next command in OPTIN/OPTOUT mode.",
|
" * USER <username>",
|
||||||
"GETREDIR -- Return the client ID we are redirecting to when tracking is enabled.",
|
" Kill connections authenticated by <username>.",
|
||||||
|
" * SKIPME (YES|NO)",
|
||||||
|
" Skip killing current connection (default: yes).",
|
||||||
|
"LIST [options ...]",
|
||||||
|
" Return information about client connections. Options:",
|
||||||
|
" * TYPE (NORMAL|MASTER|REPLICA|PUBSUB)",
|
||||||
|
" Return clients of specified type.",
|
||||||
|
"UNPAUSE",
|
||||||
|
" Stop the current client pause, resuming traffic.",
|
||||||
|
"PAUSE <timeout> [WRITE|ALL]",
|
||||||
|
" Suspend all, or just write, clients for <timout> milliseconds.",
|
||||||
|
"REPLY (ON|OFF|SKIP)",
|
||||||
|
" Control the replies sent to the current connection.",
|
||||||
|
"SETNAME <name>",
|
||||||
|
" Assign the name <name> to the current connection.",
|
||||||
|
"UNBLOCK <clientid> [TIMEOUT|ERROR]",
|
||||||
|
" Unblock the specified blocked client.",
|
||||||
|
"TRACKING (ON|OFF) [REDIRECT <id>] [BCAST] [PREFIX <prefix> [...]]",
|
||||||
|
" [OPTIN] [OPTOUT]",
|
||||||
|
" Control server assisted client side caching.",
|
||||||
|
"TRACKINGINFO",
|
||||||
|
" Report tracking status for the current connection.",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
@ -2421,7 +2503,7 @@ NULL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (c->argc != 2) {
|
} else if (c->argc != 2) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2440,7 +2522,7 @@ NULL
|
|||||||
if (!(c->flags & CLIENT_REPLY_OFF))
|
if (!(c->flags & CLIENT_REPLY_OFF))
|
||||||
c->flags |= CLIENT_REPLY_SKIP_NEXT;
|
c->flags |= CLIENT_REPLY_SKIP_NEXT;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"kill")) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"kill")) {
|
||||||
@ -2496,17 +2578,17 @@ NULL
|
|||||||
} else if (!strcasecmp(c->argv[i+1]->ptr,"no")) {
|
} else if (!strcasecmp(c->argv[i+1]->ptr,"no")) {
|
||||||
skipme = 0;
|
skipme = 0;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
i += 2;
|
i += 2;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2585,13 +2667,31 @@ NULL
|
|||||||
addReplyBulk(c,c->name);
|
addReplyBulk(c,c->name);
|
||||||
else
|
else
|
||||||
addReplyNull(c);
|
addReplyNull(c);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"pause") && c->argc == 3) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"unpause") && c->argc == 2) {
|
||||||
/* CLIENT PAUSE */
|
/* CLIENT UNPAUSE */
|
||||||
|
unpauseClients();
|
||||||
|
addReply(c,shared.ok);
|
||||||
|
} else if (!strcasecmp(c->argv[1]->ptr,"pause") && (c->argc == 3 ||
|
||||||
|
c->argc == 4))
|
||||||
|
{
|
||||||
|
/* CLIENT PAUSE TIMEOUT [WRITE|ALL] */
|
||||||
long long duration;
|
long long duration;
|
||||||
|
int type = CLIENT_PAUSE_ALL;
|
||||||
|
if (c->argc == 4) {
|
||||||
|
if (!strcasecmp(c->argv[3]->ptr,"write")) {
|
||||||
|
type = CLIENT_PAUSE_WRITE;
|
||||||
|
} else if (!strcasecmp(c->argv[3]->ptr,"all")) {
|
||||||
|
type = CLIENT_PAUSE_ALL;
|
||||||
|
} else {
|
||||||
|
addReplyError(c,
|
||||||
|
"CLIENT PAUSE mode must be WRITE or ALL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (getTimeoutFromObjectOrReply(c,c->argv[2],&duration,
|
if (getTimeoutFromObjectOrReply(c,c->argv[2],&duration,
|
||||||
UNIT_MILLISECONDS) != C_OK) return;
|
UNIT_MILLISECONDS) != C_OK) return;
|
||||||
pauseClients(duration);
|
pauseClients(duration, type);
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"tracking") && c->argc >= 3) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"tracking") && c->argc >= 3) {
|
||||||
/* CLIENT TRACKING (on|off) [REDIRECT <id>] [BCAST] [PREFIX first]
|
/* CLIENT TRACKING (on|off) [REDIRECT <id>] [BCAST] [PREFIX first]
|
||||||
@ -2643,7 +2743,7 @@ NULL
|
|||||||
prefix[numprefix++] = c->argv[j];
|
prefix[numprefix++] = c->argv[j];
|
||||||
} else {
|
} else {
|
||||||
zfree(prefix);
|
zfree(prefix);
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2700,12 +2800,19 @@ NULL
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options & CLIENT_TRACKING_BCAST) {
|
||||||
|
if (!checkPrefixCollisionsOrReply(c,prefix,numprefix)) {
|
||||||
|
zfree(prefix);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enableTracking(c,redir,options,prefix,numprefix);
|
enableTracking(c,redir,options,prefix,numprefix);
|
||||||
} else if (!strcasecmp(c->argv[2]->ptr,"off")) {
|
} else if (!strcasecmp(c->argv[2]->ptr,"off")) {
|
||||||
disableTracking(c);
|
disableTracking(c);
|
||||||
} else {
|
} else {
|
||||||
zfree(prefix);
|
zfree(prefix);
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
zfree(prefix);
|
zfree(prefix);
|
||||||
@ -2734,7 +2841,7 @@ NULL
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2747,23 +2854,90 @@ NULL
|
|||||||
} else {
|
} else {
|
||||||
addReplyLongLong(c,-1);
|
addReplyLongLong(c,-1);
|
||||||
}
|
}
|
||||||
|
} else if (!strcasecmp(c->argv[1]->ptr,"trackinginfo") && c->argc == 2) {
|
||||||
|
addReplyMapLen(c,3);
|
||||||
|
|
||||||
|
/* Flags */
|
||||||
|
addReplyBulkCString(c,"flags");
|
||||||
|
void *arraylen_ptr = addReplyDeferredLen(c);
|
||||||
|
int numflags = 0;
|
||||||
|
addReplyBulkCString(c,c->flags & CLIENT_TRACKING ? "on" : "off");
|
||||||
|
numflags++;
|
||||||
|
if (c->flags & CLIENT_TRACKING_BCAST) {
|
||||||
|
addReplyBulkCString(c,"bcast");
|
||||||
|
numflags++;
|
||||||
|
}
|
||||||
|
if (c->flags & CLIENT_TRACKING_OPTIN) {
|
||||||
|
addReplyBulkCString(c,"optin");
|
||||||
|
numflags++;
|
||||||
|
if (c->flags & CLIENT_TRACKING_CACHING) {
|
||||||
|
addReplyBulkCString(c,"caching-yes");
|
||||||
|
numflags++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c->flags & CLIENT_TRACKING_OPTOUT) {
|
||||||
|
addReplyBulkCString(c,"optout");
|
||||||
|
numflags++;
|
||||||
|
if (c->flags & CLIENT_TRACKING_CACHING) {
|
||||||
|
addReplyBulkCString(c,"caching-no");
|
||||||
|
numflags++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c->flags & CLIENT_TRACKING_NOLOOP) {
|
||||||
|
addReplyBulkCString(c,"noloop");
|
||||||
|
numflags++;
|
||||||
|
}
|
||||||
|
if (c->flags & CLIENT_TRACKING_BROKEN_REDIR) {
|
||||||
|
addReplyBulkCString(c,"broken_redirect");
|
||||||
|
numflags++;
|
||||||
|
}
|
||||||
|
setDeferredSetLen(c,arraylen_ptr,numflags);
|
||||||
|
|
||||||
|
/* Redirect */
|
||||||
|
addReplyBulkCString(c,"redirect");
|
||||||
|
if (c->flags & CLIENT_TRACKING) {
|
||||||
|
addReplyLongLong(c,c->client_tracking_redirection);
|
||||||
|
} else {
|
||||||
|
addReplyLongLong(c,-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prefixes */
|
||||||
|
addReplyBulkCString(c,"prefixes");
|
||||||
|
if (c->client_tracking_prefixes) {
|
||||||
|
addReplyArrayLen(c,raxSize(c->client_tracking_prefixes));
|
||||||
|
raxIterator ri;
|
||||||
|
raxStart(&ri,c->client_tracking_prefixes);
|
||||||
|
raxSeek(&ri,"^",NULL,0);
|
||||||
|
while(raxNext(&ri)) {
|
||||||
|
addReplyBulkCBuffer(c,ri.key,ri.key_len);
|
||||||
|
}
|
||||||
|
raxStop(&ri);
|
||||||
|
} else {
|
||||||
|
addReplyArrayLen(c,0);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CLIENT HELP", (char*)c->argv[1]->ptr);
|
addReplySubcommandSyntaxError(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* HELLO <protocol-version> [AUTH <user> <password>] [SETNAME <name>] */
|
/* HELLO [<protocol-version> [AUTH <user> <password>] [SETNAME <name>] ] */
|
||||||
void helloCommand(client *c) {
|
void helloCommand(client *c) {
|
||||||
long long ver;
|
long long ver = 0;
|
||||||
|
int next_arg = 1;
|
||||||
|
|
||||||
if (getLongLongFromObject(c->argv[1],&ver) != C_OK ||
|
if (c->argc >= 2) {
|
||||||
ver < 2 || ver > 3)
|
if (getLongLongFromObjectOrReply(c, c->argv[next_arg++], &ver,
|
||||||
{
|
"Protocol version is not an integer or out of range") != C_OK) {
|
||||||
addReplyError(c,"-NOPROTO unsupported protocol version");
|
return;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
if (ver < 2 || ver > 3) {
|
||||||
|
addReplyError(c,"-NOPROTO unsupported protocol version");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int j = 2; j < c->argc; j++) {
|
for (int j = next_arg; j < c->argc; j++) {
|
||||||
int moreargs = (c->argc-1) - j;
|
int moreargs = (c->argc-1) - j;
|
||||||
const char *opt = c->argv[j]->ptr;
|
const char *opt = c->argv[j]->ptr;
|
||||||
if (!strcasecmp(opt,"AUTH") && moreargs >= 2) {
|
if (!strcasecmp(opt,"AUTH") && moreargs >= 2) {
|
||||||
@ -2791,7 +2965,7 @@ void helloCommand(client *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Let's switch to the specified RESP mode. */
|
/* Let's switch to the specified RESP mode. */
|
||||||
c->resp = ver;
|
if (ver) c->resp = ver;
|
||||||
addReplyMapLen(c,6 + !server.sentinel_mode);
|
addReplyMapLen(c,6 + !server.sentinel_mode);
|
||||||
|
|
||||||
addReplyBulkCString(c,"server");
|
addReplyBulkCString(c,"server");
|
||||||
@ -2801,7 +2975,7 @@ void helloCommand(client *c) {
|
|||||||
addReplyBulkCString(c,REDIS_VERSION);
|
addReplyBulkCString(c,REDIS_VERSION);
|
||||||
|
|
||||||
addReplyBulkCString(c,"proto");
|
addReplyBulkCString(c,"proto");
|
||||||
addReplyLongLong(c,ver);
|
addReplyLongLong(c,c->resp);
|
||||||
|
|
||||||
addReplyBulkCString(c,"id");
|
addReplyBulkCString(c,"id");
|
||||||
addReplyLongLong(c,c->id);
|
addReplyLongLong(c,c->id);
|
||||||
@ -3074,54 +3248,66 @@ void flushSlavesOutputBuffers(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pause clients up to the specified unixtime (in ms). While clients
|
/* Pause clients up to the specified unixtime (in ms) for a given type of
|
||||||
* are paused no command is processed from clients, so the data set can't
|
* commands.
|
||||||
* change during that time.
|
|
||||||
*
|
*
|
||||||
* However while this function pauses normal and Pub/Sub clients, slaves are
|
* A main use case of this function is to allow pausing replication traffic
|
||||||
* still served, so this function can be used on server upgrades where it is
|
* so that a failover without data loss to occur. Replicas will continue to receive
|
||||||
* required that slaves process the latest bytes from the replication stream
|
* traffic to faciliate this functionality.
|
||||||
* before being turned to masters.
|
|
||||||
*
|
*
|
||||||
* This function is also internally used by Redis Cluster for the manual
|
* This function is also internally used by Redis Cluster for the manual
|
||||||
* failover procedure implemented by CLUSTER FAILOVER.
|
* failover procedure implemented by CLUSTER FAILOVER.
|
||||||
*
|
*
|
||||||
* The function always succeed, even if there is already a pause in progress.
|
* The function always succeed, even if there is already a pause in progress.
|
||||||
* In such a case, the pause is extended if the duration is more than the
|
* In such a case, the duration is set to the maximum and new end time and the
|
||||||
* time left for the previous duration. However if the duration is smaller
|
* type is set to the more restrictive type of pause. */
|
||||||
* than the time left for the previous pause, no change is made to the
|
void pauseClients(mstime_t end, pause_type type) {
|
||||||
* left duration. */
|
if (type > server.client_pause_type) {
|
||||||
void pauseClients(mstime_t end) {
|
server.client_pause_type = type;
|
||||||
if (!server.clients_paused || end > server.clients_pause_end_time)
|
}
|
||||||
server.clients_pause_end_time = end;
|
|
||||||
server.clients_paused = 1;
|
if (end > server.client_pause_end_time) {
|
||||||
|
server.client_pause_end_time = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We allow write commands that were queued
|
||||||
|
* up before and after to execute. We need
|
||||||
|
* to track this state so that we don't assert
|
||||||
|
* in propagate(). */
|
||||||
|
if (server.in_exec) {
|
||||||
|
server.client_pause_in_transaction = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return non-zero if clients are currently paused. As a side effect the
|
/* Unpause clients and queue them for reprocessing. */
|
||||||
* function checks if the pause time was reached and clear it. */
|
void unpauseClients(void) {
|
||||||
int clientsArePaused(void) {
|
listNode *ln;
|
||||||
if (server.clients_paused &&
|
listIter li;
|
||||||
server.clients_pause_end_time < server.mstime)
|
client *c;
|
||||||
{
|
|
||||||
listNode *ln;
|
|
||||||
listIter li;
|
|
||||||
client *c;
|
|
||||||
|
|
||||||
server.clients_paused = 0;
|
server.client_pause_type = CLIENT_PAUSE_OFF;
|
||||||
|
|
||||||
/* Put all the clients in the unblocked clients queue in order to
|
/* Unblock all of the clients so they are reprocessed. */
|
||||||
* force the re-processing of the input buffer if any. */
|
listRewind(server.paused_clients,&li);
|
||||||
listRewind(server.clients,&li);
|
while ((ln = listNext(&li)) != NULL) {
|
||||||
while ((ln = listNext(&li)) != NULL) {
|
c = listNodeValue(ln);
|
||||||
c = listNodeValue(ln);
|
unblockClient(c);
|
||||||
|
|
||||||
/* Don't touch slaves and blocked clients.
|
|
||||||
* The latter pending requests will be processed when unblocked. */
|
|
||||||
if (c->flags & (CLIENT_SLAVE|CLIENT_BLOCKED)) continue;
|
|
||||||
queueClientForReprocessing(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return server.clients_paused;
|
}
|
||||||
|
|
||||||
|
/* Returns true if clients are paused and false otherwise. */
|
||||||
|
int areClientsPaused(void) {
|
||||||
|
return server.client_pause_type != CLIENT_PAUSE_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checks if the current client pause has elapsed and unpause clients
|
||||||
|
* if it has. Also returns true if clients are now paused and false
|
||||||
|
* otherwise. */
|
||||||
|
int checkClientPauseTimeoutAndReturnIfPaused(void) {
|
||||||
|
if (server.client_pause_end_time < server.mstime) {
|
||||||
|
unpauseClients();
|
||||||
|
}
|
||||||
|
return areClientsPaused();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function is called by Redis in order to process a few events from
|
/* This function is called by Redis in order to process a few events from
|
||||||
@ -3499,16 +3685,20 @@ int handleClientsWithPendingReadsUsingThreads(void) {
|
|||||||
c->flags &= ~CLIENT_PENDING_READ;
|
c->flags &= ~CLIENT_PENDING_READ;
|
||||||
listDelNode(server.clients_pending_read,ln);
|
listDelNode(server.clients_pending_read,ln);
|
||||||
|
|
||||||
if (c->flags & CLIENT_PENDING_COMMAND) {
|
if (processPendingCommandsAndResetClient(c) == C_ERR) {
|
||||||
c->flags &= ~CLIENT_PENDING_COMMAND;
|
/* If the client is no longer valid, we avoid
|
||||||
if (processCommandAndResetClient(c) == C_ERR) {
|
* processing the client later. So we just go
|
||||||
/* If the client is no longer valid, we avoid
|
* to the next. */
|
||||||
* processing the client later. So we just go
|
continue;
|
||||||
* to the next. */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processInputBuffer(c);
|
processInputBuffer(c);
|
||||||
|
|
||||||
|
/* We may have pending replies if a thread readQueryFromClient() produced
|
||||||
|
* replies and did not install a write handler (it can't).
|
||||||
|
*/
|
||||||
|
if (!(c->flags & CLIENT_PENDING_WRITE) && clientHasPendingReplies(c))
|
||||||
|
clientInstallWriteHandler(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update processed count on server */
|
/* Update processed count on server */
|
||||||
|
40
src/object.c
40
src/object.c
@ -404,7 +404,7 @@ robj *resetRefCount(robj *obj) {
|
|||||||
int checkType(client *c, robj *o, int type) {
|
int checkType(client *c, robj *o, int type) {
|
||||||
/* A NULL is considered an empty key */
|
/* A NULL is considered an empty key */
|
||||||
if (o && o->type != type) {
|
if (o && o->type != type) {
|
||||||
addReply(c,shared.wrongtypeerr);
|
addReplyErrorObject(c,shared.wrongtypeerr);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@ -1256,10 +1256,18 @@ void objectCommand(client *c) {
|
|||||||
|
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"ENCODING <key> -- Return the kind of internal representation used in order to store the value associated with a key.",
|
"ENCODING <key>",
|
||||||
"FREQ <key> -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.",
|
" Return the kind of internal representation used in order to store the value",
|
||||||
"IDLETIME <key> -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.",
|
" associated with a <key>.",
|
||||||
"REFCOUNT <key> -- Return the number of references of the value associated with the specified key.",
|
"FREQ <key>",
|
||||||
|
" Return the access frequency index of the <key>. The returned integer is",
|
||||||
|
" proportional to the logarithm of the recent access frequency of the key.",
|
||||||
|
"IDLETIME <key>",
|
||||||
|
" Return the idle time of the <key>, that is the approximated number of",
|
||||||
|
" seconds elapsed since the last access to the key.",
|
||||||
|
"REFCOUNT <key>",
|
||||||
|
" Return the number of references of the value associated with the specified",
|
||||||
|
" <key>.",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
@ -1303,11 +1311,17 @@ NULL
|
|||||||
void memoryCommand(client *c) {
|
void memoryCommand(client *c) {
|
||||||
if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
|
if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"DOCTOR - Return memory problems reports.",
|
"DOCTOR",
|
||||||
"MALLOC-STATS -- Return internal statistics report from the memory allocator.",
|
" Return memory problems reports.",
|
||||||
"PURGE -- Attempt to purge dirty pages for reclamation by the allocator.",
|
"MALLOC-STATS"
|
||||||
"STATS -- Return information about the memory usage of the server.",
|
" Return internal statistics report from the memory allocator.",
|
||||||
"USAGE <key> [SAMPLES <count>] -- Return memory in bytes used by <key> and its value. Nested values are sampled up to <count> times (default: 5).",
|
"PURGE",
|
||||||
|
" Attempt to purge dirty pages for reclamation by the allocator.",
|
||||||
|
"STATS",
|
||||||
|
" Return information about the memory usage of the server.",
|
||||||
|
"USAGE <key> [SAMPLES <count>]",
|
||||||
|
" Return memory in bytes used by <key> and its value. Nested values are",
|
||||||
|
" sampled up to <count> times (default: 5).",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
@ -1321,13 +1335,13 @@ NULL
|
|||||||
if (getLongLongFromObjectOrReply(c,c->argv[j+1],&samples,NULL)
|
if (getLongLongFromObjectOrReply(c,c->argv[j+1],&samples,NULL)
|
||||||
== C_ERR) return;
|
== C_ERR) return;
|
||||||
if (samples < 0) {
|
if (samples < 0) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (samples == 0) samples = LLONG_MAX;
|
if (samples == 0) samples = LLONG_MAX;
|
||||||
j++; /* skip option argument. */
|
j++; /* skip option argument. */
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1452,6 +1466,6 @@ NULL
|
|||||||
else
|
else
|
||||||
addReplyError(c, "Error purging dirty pages");
|
addReplyError(c, "Error purging dirty pages");
|
||||||
} else {
|
} else {
|
||||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try MEMORY HELP", (char*)c->argv[1]->ptr);
|
addReplySubcommandSyntaxError(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
src/pubsub.c
10
src/pubsub.c
@ -455,9 +455,13 @@ void publishCommand(client *c) {
|
|||||||
void pubsubCommand(client *c) {
|
void pubsubCommand(client *c) {
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"CHANNELS [<pattern>] -- Return the currently active channels matching a pattern (default: all).",
|
"CHANNELS [<pattern>]",
|
||||||
"NUMPAT -- Return number of subscriptions to patterns.",
|
" Return the currently active channels matching a <pattern> (default: '*').",
|
||||||
"NUMSUB [channel-1 .. channel-N] -- Returns the number of subscribers for the specified channels (excluding patterns, default: none).",
|
"NUMPAT",
|
||||||
|
" Return number of subscriptions to patterns.",
|
||||||
|
"NUMSUB [<channel> ...]",
|
||||||
|
" Return the number of subscribers for the specified channels, excluding",
|
||||||
|
" pattern subscriptions(default: no channels).",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
|
@ -66,10 +66,10 @@ static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};
|
|||||||
#else
|
#else
|
||||||
#define D(...) \
|
#define D(...) \
|
||||||
do { \
|
do { \
|
||||||
printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__); \
|
printf("%s:%s:%d:\t", __FILE__, __func__, __LINE__); \
|
||||||
printf(__VA_ARGS__); \
|
printf(__VA_ARGS__); \
|
||||||
printf("\n"); \
|
printf("\n"); \
|
||||||
} while (0);
|
} while (0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Bookmarks forward declarations */
|
/* Bookmarks forward declarations */
|
||||||
@ -1508,15 +1508,6 @@ void quicklistBookmarksClear(quicklist *ql) {
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
||||||
#define assert(_e) \
|
|
||||||
do { \
|
|
||||||
if (!(_e)) { \
|
|
||||||
printf("\n\n=== ASSERTION FAILED ===\n"); \
|
|
||||||
printf("==> %s:%d '%s' is not true\n", __FILE__, __LINE__, #_e); \
|
|
||||||
err++; \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define yell(str, ...) printf("ERROR! " str "\n\n", __VA_ARGS__)
|
#define yell(str, ...) printf("ERROR! " str "\n\n", __VA_ARGS__)
|
||||||
|
|
||||||
#define OK printf("\tOK\n")
|
#define OK printf("\tOK\n")
|
||||||
@ -1529,7 +1520,7 @@ void quicklistBookmarksClear(quicklist *ql) {
|
|||||||
|
|
||||||
#define ERR(x, ...) \
|
#define ERR(x, ...) \
|
||||||
do { \
|
do { \
|
||||||
printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__); \
|
printf("%s:%s:%d:\t", __FILE__, __func__, __LINE__); \
|
||||||
printf("ERROR! " x "\n", __VA_ARGS__); \
|
printf("ERROR! " x "\n", __VA_ARGS__); \
|
||||||
err++; \
|
err++; \
|
||||||
} while (0)
|
} while (0)
|
||||||
@ -1614,7 +1605,7 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
|
|||||||
|
|
||||||
ql_info(ql);
|
ql_info(ql);
|
||||||
if (len != ql->len) {
|
if (len != ql->len) {
|
||||||
yell("quicklist length wrong: expected %d, got %u", len, ql->len);
|
yell("quicklist length wrong: expected %d, got %lu", len, ql->len);
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1670,7 +1661,7 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
|
|||||||
if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) {
|
if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) {
|
||||||
yell("Incorrect compression: node %d is "
|
yell("Incorrect compression: node %d is "
|
||||||
"compressed at depth %d ((%u, %u); total "
|
"compressed at depth %d ((%u, %u); total "
|
||||||
"nodes: %u; size: %u; recompress: %d)",
|
"nodes: %lu; size: %u; recompress: %d)",
|
||||||
at, ql->compress, low_raw, high_raw, ql->len, node->sz,
|
at, ql->compress, low_raw, high_raw, ql->len, node->sz,
|
||||||
node->recompress);
|
node->recompress);
|
||||||
errors++;
|
errors++;
|
||||||
@ -1680,7 +1671,7 @@ static int _ql_verify(quicklist *ql, uint32_t len, uint32_t count,
|
|||||||
!node->attempted_compress) {
|
!node->attempted_compress) {
|
||||||
yell("Incorrect non-compression: node %d is NOT "
|
yell("Incorrect non-compression: node %d is NOT "
|
||||||
"compressed at depth %d ((%u, %u); total "
|
"compressed at depth %d ((%u, %u); total "
|
||||||
"nodes: %u; size: %u; recompress: %d; attempted: %d)",
|
"nodes: %lu; size: %u; recompress: %d; attempted: %d)",
|
||||||
at, ql->compress, low_raw, high_raw, ql->len, node->sz,
|
at, ql->compress, low_raw, high_raw, ql->len, node->sz,
|
||||||
node->recompress, node->attempted_compress);
|
node->recompress, node->attempted_compress);
|
||||||
errors++;
|
errors++;
|
||||||
@ -2706,7 +2697,7 @@ int quicklistTest(int argc, char *argv[]) {
|
|||||||
if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) {
|
if (node->encoding != QUICKLIST_NODE_ENCODING_RAW) {
|
||||||
ERR("Incorrect compression: node %d is "
|
ERR("Incorrect compression: node %d is "
|
||||||
"compressed at depth %d ((%u, %u); total "
|
"compressed at depth %d ((%u, %u); total "
|
||||||
"nodes: %u; size: %u)",
|
"nodes: %lu; size: %u)",
|
||||||
at, depth, low_raw, high_raw, ql->len,
|
at, depth, low_raw, high_raw, ql->len,
|
||||||
node->sz);
|
node->sz);
|
||||||
}
|
}
|
||||||
@ -2714,7 +2705,7 @@ int quicklistTest(int argc, char *argv[]) {
|
|||||||
if (node->encoding != QUICKLIST_NODE_ENCODING_LZF) {
|
if (node->encoding != QUICKLIST_NODE_ENCODING_LZF) {
|
||||||
ERR("Incorrect non-compression: node %d is NOT "
|
ERR("Incorrect non-compression: node %d is NOT "
|
||||||
"compressed at depth %d ((%u, %u); total "
|
"compressed at depth %d ((%u, %u); total "
|
||||||
"nodes: %u; size: %u; attempted: %d)",
|
"nodes: %lu; size: %u; attempted: %d)",
|
||||||
at, depth, low_raw, high_raw, ql->len,
|
at, depth, low_raw, high_raw, ql->len,
|
||||||
node->sz, node->attempted_compress);
|
node->sz, node->attempted_compress);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ void raxDebugShowNode(const char *msg, raxNode *n);
|
|||||||
#ifdef RAX_DEBUG_MSG
|
#ifdef RAX_DEBUG_MSG
|
||||||
#define debugf(...) \
|
#define debugf(...) \
|
||||||
if (raxDebugMsg) { \
|
if (raxDebugMsg) { \
|
||||||
printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__); \
|
printf("%s:%s:%d:\t", __FILE__, __func__, __LINE__); \
|
||||||
printf(__VA_ARGS__); \
|
printf(__VA_ARGS__); \
|
||||||
fflush(stdout); \
|
fflush(stdout); \
|
||||||
}
|
}
|
||||||
@ -1892,7 +1892,7 @@ void raxShow(rax *rax) {
|
|||||||
/* Used by debugnode() macro to show info about a given node. */
|
/* Used by debugnode() macro to show info about a given node. */
|
||||||
void raxDebugShowNode(const char *msg, raxNode *n) {
|
void raxDebugShowNode(const char *msg, raxNode *n) {
|
||||||
if (raxDebugMsg == 0) return;
|
if (raxDebugMsg == 0) return;
|
||||||
printf("%s: %p [%.*s] key:%d size:%d children:",
|
printf("%s: %p [%.*s] key:%u size:%u children:",
|
||||||
msg, (void*)n, (int)n->size, (char*)n->data, n->iskey, n->size);
|
msg, (void*)n, (int)n->size, (char*)n->data, n->iskey, n->size);
|
||||||
int numcld = n->iscompr ? 1 : n->size;
|
int numcld = n->iscompr ? 1 : n->size;
|
||||||
raxNode **cldptr = raxNodeLastChildPtr(n) - (numcld-1);
|
raxNode **cldptr = raxNodeLastChildPtr(n) - (numcld-1);
|
||||||
|
59
src/rdb.c
59
src/rdb.c
@ -94,7 +94,7 @@ void rdbReportError(int corruption_error, int linenum, char *reason, ...) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rdbWriteRaw(rio *rdb, void *p, size_t len) {
|
static ssize_t rdbWriteRaw(rio *rdb, void *p, size_t len) {
|
||||||
if (rdb && rioWrite(rdb,p,len) == 0)
|
if (rdb && rioWrite(rdb,p,len) == 0)
|
||||||
return -1;
|
return -1;
|
||||||
return len;
|
return len;
|
||||||
@ -1219,9 +1219,11 @@ int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {
|
|||||||
dictIterator *di = NULL;
|
dictIterator *di = NULL;
|
||||||
dictEntry *de;
|
dictEntry *de;
|
||||||
char magic[10];
|
char magic[10];
|
||||||
int j;
|
|
||||||
uint64_t cksum;
|
uint64_t cksum;
|
||||||
size_t processed = 0;
|
size_t processed = 0;
|
||||||
|
int j;
|
||||||
|
long key_count = 0;
|
||||||
|
long long cow_updated_time = 0;
|
||||||
|
|
||||||
if (server.rdb_checksum)
|
if (server.rdb_checksum)
|
||||||
rdb->update_cksum = rioGenericUpdateChecksum;
|
rdb->update_cksum = rioGenericUpdateChecksum;
|
||||||
@ -1267,6 +1269,23 @@ int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {
|
|||||||
processed = rdb->processed_bytes;
|
processed = rdb->processed_bytes;
|
||||||
aofReadDiffFromParent();
|
aofReadDiffFromParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Update COW info every 1 second (approximately).
|
||||||
|
* in order to avoid calling mstime() on each iteration, we will
|
||||||
|
* check the diff every 1024 keys */
|
||||||
|
if ((key_count & 1023) == 0) {
|
||||||
|
key_count = 0;
|
||||||
|
long long now = mstime();
|
||||||
|
if (now - cow_updated_time >= 1000) {
|
||||||
|
if (rdbflags & RDBFLAGS_AOF_PREAMBLE) {
|
||||||
|
sendChildCOWInfo(CHILD_TYPE_AOF, 0, "AOF rewrite");
|
||||||
|
} else {
|
||||||
|
sendChildCOWInfo(CHILD_TYPE_RDB, 0, "RDB");
|
||||||
|
}
|
||||||
|
cow_updated_time = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key_count++;
|
||||||
}
|
}
|
||||||
dictReleaseIterator(di);
|
dictReleaseIterator(di);
|
||||||
di = NULL; /* So that we don't release it again on error. */
|
di = NULL; /* So that we don't release it again on error. */
|
||||||
@ -1410,7 +1429,6 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
|||||||
|
|
||||||
server.dirty_before_bgsave = server.dirty;
|
server.dirty_before_bgsave = server.dirty;
|
||||||
server.lastbgsave_try = time(NULL);
|
server.lastbgsave_try = time(NULL);
|
||||||
openChildInfoPipe();
|
|
||||||
|
|
||||||
if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
|
if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
|
||||||
int retval;
|
int retval;
|
||||||
@ -1420,13 +1438,12 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
|||||||
redisSetCpuAffinity(server.bgsave_cpulist);
|
redisSetCpuAffinity(server.bgsave_cpulist);
|
||||||
retval = rdbSave(filename,rsi);
|
retval = rdbSave(filename,rsi);
|
||||||
if (retval == C_OK) {
|
if (retval == C_OK) {
|
||||||
sendChildCOWInfo(CHILD_TYPE_RDB, "RDB");
|
sendChildCOWInfo(CHILD_TYPE_RDB, 1, "RDB");
|
||||||
}
|
}
|
||||||
exitFromChild((retval == C_OK) ? 0 : 1);
|
exitFromChild((retval == C_OK) ? 0 : 1);
|
||||||
} else {
|
} else {
|
||||||
/* Parent */
|
/* Parent */
|
||||||
if (childpid == -1) {
|
if (childpid == -1) {
|
||||||
closeChildInfoPipe();
|
|
||||||
server.lastbgsave_status = C_ERR;
|
server.lastbgsave_status = C_ERR;
|
||||||
serverLog(LL_WARNING,"Can't save in background: fork: %s",
|
serverLog(LL_WARNING,"Can't save in background: fork: %s",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
@ -1434,9 +1451,7 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
|||||||
}
|
}
|
||||||
serverLog(LL_NOTICE,"Background saving started by pid %ld",(long) childpid);
|
serverLog(LL_NOTICE,"Background saving started by pid %ld",(long) childpid);
|
||||||
server.rdb_save_time_start = time(NULL);
|
server.rdb_save_time_start = time(NULL);
|
||||||
server.rdb_child_pid = childpid;
|
|
||||||
server.rdb_child_type = RDB_CHILD_TYPE_DISK;
|
server.rdb_child_type = RDB_CHILD_TYPE_DISK;
|
||||||
updateDictResizePolicy();
|
|
||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
return C_OK; /* unreached */
|
return C_OK; /* unreached */
|
||||||
@ -2654,7 +2669,7 @@ static void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
|
|||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Background saving terminated by signal %d", bysignal);
|
"Background saving terminated by signal %d", bysignal);
|
||||||
latencyStartMonitor(latency);
|
latencyStartMonitor(latency);
|
||||||
rdbRemoveTempFile(server.rdb_child_pid, 0);
|
rdbRemoveTempFile(server.child_pid, 0);
|
||||||
latencyEndMonitor(latency);
|
latencyEndMonitor(latency);
|
||||||
latencyAddSampleIfNeeded("rdb-unlink-temp-file",latency);
|
latencyAddSampleIfNeeded("rdb-unlink-temp-file",latency);
|
||||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||||
@ -2706,7 +2721,6 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
server.rdb_child_pid = -1;
|
|
||||||
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||||
server.rdb_save_time_last = time(NULL)-server.rdb_save_time_start;
|
server.rdb_save_time_last = time(NULL)-server.rdb_save_time_start;
|
||||||
server.rdb_save_time_start = -1;
|
server.rdb_save_time_start = -1;
|
||||||
@ -2719,10 +2733,13 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
|||||||
* the child did not exit for an error, but because we wanted), and performs
|
* the child did not exit for an error, but because we wanted), and performs
|
||||||
* the cleanup needed. */
|
* the cleanup needed. */
|
||||||
void killRDBChild(void) {
|
void killRDBChild(void) {
|
||||||
kill(server.rdb_child_pid,SIGUSR1);
|
kill(server.child_pid, SIGUSR1);
|
||||||
rdbRemoveTempFile(server.rdb_child_pid, 0);
|
/* Because we are not using here wait4 (like we have in killAppendOnlyChild
|
||||||
closeChildInfoPipe();
|
* and TerminateModuleForkChild), all the cleanup operations is done by
|
||||||
updateDictResizePolicy();
|
* checkChildrenDone, that later will find that the process killed.
|
||||||
|
* This includes:
|
||||||
|
* - resetChildState
|
||||||
|
* - rdbRemoveTempFile */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Spawn an RDB child that writes the RDB to the sockets of the slaves
|
/* Spawn an RDB child that writes the RDB to the sockets of the slaves
|
||||||
@ -2773,7 +2790,6 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Create the child process. */
|
/* Create the child process. */
|
||||||
openChildInfoPipe();
|
|
||||||
if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
|
if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
|
||||||
/* Child */
|
/* Child */
|
||||||
int retval, dummy;
|
int retval, dummy;
|
||||||
@ -2789,7 +2805,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
|||||||
retval = C_ERR;
|
retval = C_ERR;
|
||||||
|
|
||||||
if (retval == C_OK) {
|
if (retval == C_OK) {
|
||||||
sendChildCOWInfo(CHILD_TYPE_RDB, "RDB");
|
sendChildCOWInfo(CHILD_TYPE_RDB, 1, "RDB");
|
||||||
}
|
}
|
||||||
|
|
||||||
rioFreeFd(&rdb);
|
rioFreeFd(&rdb);
|
||||||
@ -2824,14 +2840,11 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
|||||||
server.rdb_pipe_conns = NULL;
|
server.rdb_pipe_conns = NULL;
|
||||||
server.rdb_pipe_numconns = 0;
|
server.rdb_pipe_numconns = 0;
|
||||||
server.rdb_pipe_numconns_writing = 0;
|
server.rdb_pipe_numconns_writing = 0;
|
||||||
closeChildInfoPipe();
|
|
||||||
} else {
|
} else {
|
||||||
serverLog(LL_NOTICE,"Background RDB transfer started by pid %ld",
|
serverLog(LL_NOTICE,"Background RDB transfer started by pid %ld",
|
||||||
(long) childpid);
|
(long) childpid);
|
||||||
server.rdb_save_time_start = time(NULL);
|
server.rdb_save_time_start = time(NULL);
|
||||||
server.rdb_child_pid = childpid;
|
|
||||||
server.rdb_child_type = RDB_CHILD_TYPE_SOCKET;
|
server.rdb_child_type = RDB_CHILD_TYPE_SOCKET;
|
||||||
updateDictResizePolicy();
|
|
||||||
close(rdb_pipe_write); /* close write in parent so that it can detect the close on the child. */
|
close(rdb_pipe_write); /* close write in parent so that it can detect the close on the child. */
|
||||||
if (aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) {
|
if (aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) {
|
||||||
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
|
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
|
||||||
@ -2843,7 +2856,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void saveCommand(client *c) {
|
void saveCommand(client *c) {
|
||||||
if (server.rdb_child_pid != -1) {
|
if (server.child_type == CHILD_TYPE_RDB) {
|
||||||
addReplyError(c,"Background save already in progress");
|
addReplyError(c,"Background save already in progress");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2852,7 +2865,7 @@ void saveCommand(client *c) {
|
|||||||
if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {
|
if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.err);
|
addReplyErrorObject(c,shared.err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2866,7 +2879,7 @@ void bgsaveCommand(client *c) {
|
|||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"schedule")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"schedule")) {
|
||||||
schedule = 1;
|
schedule = 1;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2874,7 +2887,7 @@ void bgsaveCommand(client *c) {
|
|||||||
rdbSaveInfo rsi, *rsiptr;
|
rdbSaveInfo rsi, *rsiptr;
|
||||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||||
|
|
||||||
if (server.rdb_child_pid != -1) {
|
if (server.child_type == CHILD_TYPE_RDB) {
|
||||||
addReplyError(c,"Background save already in progress");
|
addReplyError(c,"Background save already in progress");
|
||||||
} else if (hasActiveChildProcess()) {
|
} else if (hasActiveChildProcess()) {
|
||||||
if (schedule) {
|
if (schedule) {
|
||||||
@ -2889,7 +2902,7 @@ void bgsaveCommand(client *c) {
|
|||||||
} else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
|
} else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
|
||||||
addReplyStatus(c,"Background saving started");
|
addReplyStatus(c,"Background saving started");
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.err);
|
addReplyErrorObject(c,shared.err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@
|
|||||||
#include "crc16_slottable.h"
|
#include "crc16_slottable.h"
|
||||||
#include "hdr_histogram.h"
|
#include "hdr_histogram.h"
|
||||||
#include "cli_common.h"
|
#include "cli_common.h"
|
||||||
|
#include "mt19937-64.h"
|
||||||
|
|
||||||
#define UNUSED(V) ((void) V)
|
#define UNUSED(V) ((void) V)
|
||||||
#define RANDPTR_INITIAL_SIZE 8
|
#define RANDPTR_INITIAL_SIZE 8
|
||||||
@ -1182,8 +1183,8 @@ static int fetchClusterConfiguration() {
|
|||||||
}
|
}
|
||||||
if (myself) {
|
if (myself) {
|
||||||
node = firstNode;
|
node = firstNode;
|
||||||
if (node->ip == NULL && ip != NULL) {
|
if (ip != NULL && strcmp(node->ip, ip) != 0) {
|
||||||
node->ip = ip;
|
node->ip = sdsnew(ip);
|
||||||
node->port = port;
|
node->port = port;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1677,6 +1678,7 @@ int main(int argc, const char **argv) {
|
|||||||
client c;
|
client c;
|
||||||
|
|
||||||
srandom(time(NULL) ^ getpid());
|
srandom(time(NULL) ^ getpid());
|
||||||
|
init_genrand64(ustime() ^ getpid());
|
||||||
signal(SIGHUP, SIG_IGN);
|
signal(SIGHUP, SIG_IGN);
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
|
@ -27,10 +27,13 @@
|
|||||||
* POSSIBILITY OF SUCH DAMAGE.
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "mt19937-64.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "rdb.h"
|
#include "rdb.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
void createSharedObjects(void);
|
void createSharedObjects(void);
|
||||||
void rdbLoadProgressCallback(rio *r, const void *buf, size_t len);
|
void rdbLoadProgressCallback(rio *r, const void *buf, size_t len);
|
||||||
@ -362,10 +365,16 @@ err:
|
|||||||
* Otherwise if called with a non NULL fp, the function returns C_OK or
|
* Otherwise if called with a non NULL fp, the function returns C_OK or
|
||||||
* C_ERR depending on the success or failure. */
|
* C_ERR depending on the success or failure. */
|
||||||
int redis_check_rdb_main(int argc, char **argv, FILE *fp) {
|
int redis_check_rdb_main(int argc, char **argv, FILE *fp) {
|
||||||
|
struct timeval tv;
|
||||||
|
|
||||||
if (argc != 2 && fp == NULL) {
|
if (argc != 2 && fp == NULL) {
|
||||||
fprintf(stderr, "Usage: %s <rdb-file-name>\n", argv[0]);
|
fprintf(stderr, "Usage: %s <rdb-file-name>\n", argv[0]);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
init_genrand64(((long long) tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid());
|
||||||
|
|
||||||
/* In order to call the loading functions we need to create the shared
|
/* In order to call the loading functions we need to create the shared
|
||||||
* integer objects, however since this function may be called from
|
* integer objects, however since this function may be called from
|
||||||
* an already initialized Redis instance, check if we really need to. */
|
* an already initialized Redis instance, check if we really need to. */
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
#include "anet.h"
|
#include "anet.h"
|
||||||
#include "ae.h"
|
#include "ae.h"
|
||||||
#include "cli_common.h"
|
#include "cli_common.h"
|
||||||
|
#include "mt19937-64.h"
|
||||||
|
|
||||||
#define UNUSED(V) ((void) V)
|
#define UNUSED(V) ((void) V)
|
||||||
|
|
||||||
@ -5578,7 +5579,7 @@ static int clusterManagerCommandCreate(int argc, char **argv) {
|
|||||||
if (last > CLUSTER_MANAGER_SLOTS || i == (masters_count - 1))
|
if (last > CLUSTER_MANAGER_SLOTS || i == (masters_count - 1))
|
||||||
last = CLUSTER_MANAGER_SLOTS - 1;
|
last = CLUSTER_MANAGER_SLOTS - 1;
|
||||||
if (last < first) last = first;
|
if (last < first) last = first;
|
||||||
printf("Master[%d] -> Slots %lu - %lu\n", i, first, last);
|
printf("Master[%d] -> Slots %ld - %ld\n", i, first, last);
|
||||||
master->slots_count = 0;
|
master->slots_count = 0;
|
||||||
for (j = first; j <= last; j++) {
|
for (j = first; j <= last; j++) {
|
||||||
master->slots[j] = 1;
|
master->slots[j] = 1;
|
||||||
@ -7131,7 +7132,9 @@ static void getRDB(clusterManagerNode *node) {
|
|||||||
} else {
|
} else {
|
||||||
fprintf(stderr,"Transfer finished with success.\n");
|
fprintf(stderr,"Transfer finished with success.\n");
|
||||||
}
|
}
|
||||||
redisFree(s); /* Close the file descriptor ASAP as fsync() may take time. */
|
redisFree(s); /* Close the connection ASAP as fsync() may take time. */
|
||||||
|
if (node)
|
||||||
|
node->context = NULL;
|
||||||
fsync(fd);
|
fsync(fd);
|
||||||
close(fd);
|
close(fd);
|
||||||
fprintf(stderr,"Transfer finished with success.\n");
|
fprintf(stderr,"Transfer finished with success.\n");
|
||||||
@ -8123,6 +8126,7 @@ static sds askPassword(const char *msg) {
|
|||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
int firstarg;
|
int firstarg;
|
||||||
|
struct timeval tv;
|
||||||
|
|
||||||
config.hostip = sdsnew("127.0.0.1");
|
config.hostip = sdsnew("127.0.0.1");
|
||||||
config.hostport = 6379;
|
config.hostport = 6379;
|
||||||
@ -8219,6 +8223,9 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
init_genrand64(((long long) tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid());
|
||||||
|
|
||||||
/* Cluster Manager mode */
|
/* Cluster Manager mode */
|
||||||
if (CLUSTER_MANAGER_MODE()) {
|
if (CLUSTER_MANAGER_MODE()) {
|
||||||
clusterManagerCommandProc *proc = validateClusterManagerCommand();
|
clusterManagerCommandProc *proc = validateClusterManagerCommand();
|
||||||
|
@ -778,6 +778,7 @@ REDISMODULE_API int (*RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilt
|
|||||||
REDISMODULE_API int (*RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) REDISMODULE_ATTR;
|
REDISMODULE_API int (*RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) REDISMODULE_ATTR;
|
||||||
REDISMODULE_API int (*RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos) REDISMODULE_ATTR;
|
REDISMODULE_API int (*RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos) REDISMODULE_ATTR;
|
||||||
REDISMODULE_API int (*RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data) REDISMODULE_ATTR;
|
REDISMODULE_API int (*RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data) REDISMODULE_ATTR;
|
||||||
|
REDISMODULE_API void (*RedisModule_SendChildCOWInfo)(void) REDISMODULE_ATTR;
|
||||||
REDISMODULE_API int (*RedisModule_ExitFromChild)(int retcode) REDISMODULE_ATTR;
|
REDISMODULE_API int (*RedisModule_ExitFromChild)(int retcode) REDISMODULE_ATTR;
|
||||||
REDISMODULE_API int (*RedisModule_KillForkChild)(int child_pid) REDISMODULE_ATTR;
|
REDISMODULE_API int (*RedisModule_KillForkChild)(int child_pid) REDISMODULE_ATTR;
|
||||||
REDISMODULE_API float (*RedisModule_GetUsedMemoryRatio)() REDISMODULE_ATTR;
|
REDISMODULE_API float (*RedisModule_GetUsedMemoryRatio)() REDISMODULE_ATTR;
|
||||||
@ -1033,6 +1034,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(CommandFilterArgReplace);
|
REDISMODULE_GET_API(CommandFilterArgReplace);
|
||||||
REDISMODULE_GET_API(CommandFilterArgDelete);
|
REDISMODULE_GET_API(CommandFilterArgDelete);
|
||||||
REDISMODULE_GET_API(Fork);
|
REDISMODULE_GET_API(Fork);
|
||||||
|
REDISMODULE_GET_API(SendChildCOWInfo);
|
||||||
REDISMODULE_GET_API(ExitFromChild);
|
REDISMODULE_GET_API(ExitFromChild);
|
||||||
REDISMODULE_GET_API(KillForkChild);
|
REDISMODULE_GET_API(KillForkChild);
|
||||||
REDISMODULE_GET_API(GetUsedMemoryRatio);
|
REDISMODULE_GET_API(GetUsedMemoryRatio);
|
||||||
|
@ -102,7 +102,7 @@ int bg_unlink(const char *filename) {
|
|||||||
errno = old_errno;
|
errno = old_errno;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)fd,NULL,NULL);
|
bioCreateCloseJob(fd);
|
||||||
return 0; /* Success. */
|
return 0; /* Success. */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -715,7 +715,7 @@ void syncCommand(client *c) {
|
|||||||
/* Refuse SYNC requests if we are a slave but the link with our master
|
/* Refuse SYNC requests if we are a slave but the link with our master
|
||||||
* is not ok... */
|
* is not ok... */
|
||||||
if (server.masterhost && server.repl_state != REPL_STATE_CONNECTED) {
|
if (server.masterhost && server.repl_state != REPL_STATE_CONNECTED) {
|
||||||
addReplySds(c,sdsnew("-NOMASTERLINK Can't SYNC while not connected with my master\r\n"));
|
addReplyError(c,"-NOMASTERLINK Can't SYNC while not connected with my master");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -786,7 +786,7 @@ void syncCommand(client *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* CASE 1: BGSAVE is in progress, with disk target. */
|
/* CASE 1: BGSAVE is in progress, with disk target. */
|
||||||
if (server.rdb_child_pid != -1 &&
|
if (server.child_type == CHILD_TYPE_RDB &&
|
||||||
server.rdb_child_type == RDB_CHILD_TYPE_DISK)
|
server.rdb_child_type == RDB_CHILD_TYPE_DISK)
|
||||||
{
|
{
|
||||||
/* Ok a background save is in progress. Let's check if it is a good
|
/* Ok a background save is in progress. Let's check if it is a good
|
||||||
@ -816,7 +816,7 @@ void syncCommand(client *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* CASE 2: BGSAVE is in progress, with socket target. */
|
/* CASE 2: BGSAVE is in progress, with socket target. */
|
||||||
} else if (server.rdb_child_pid != -1 &&
|
} else if (server.child_type == CHILD_TYPE_RDB &&
|
||||||
server.rdb_child_type == RDB_CHILD_TYPE_SOCKET)
|
server.rdb_child_type == RDB_CHILD_TYPE_SOCKET)
|
||||||
{
|
{
|
||||||
/* There is an RDB child process but it is writing directly to
|
/* There is an RDB child process but it is writing directly to
|
||||||
@ -866,7 +866,7 @@ void replconfCommand(client *c) {
|
|||||||
if ((c->argc % 2) == 0) {
|
if ((c->argc % 2) == 0) {
|
||||||
/* Number of arguments must be odd to make sure that every
|
/* Number of arguments must be odd to make sure that every
|
||||||
* option has a corresponding value. */
|
* option has a corresponding value. */
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -914,7 +914,7 @@ void replconfCommand(client *c) {
|
|||||||
* There's a chance the ACK got to us before we detected that the
|
* There's a chance the ACK got to us before we detected that the
|
||||||
* bgsave is done (since that depends on cron ticks), so run a
|
* bgsave is done (since that depends on cron ticks), so run a
|
||||||
* quick check first (instead of waiting for the next ACK. */
|
* quick check first (instead of waiting for the next ACK. */
|
||||||
if (server.rdb_child_pid != -1 && c->replstate == SLAVE_STATE_WAIT_BGSAVE_END)
|
if (server.child_type == CHILD_TYPE_RDB && c->replstate == SLAVE_STATE_WAIT_BGSAVE_END)
|
||||||
checkChildrenDone();
|
checkChildrenDone();
|
||||||
if (c->repl_put_online_on_ack && c->replstate == SLAVE_STATE_ONLINE)
|
if (c->repl_put_online_on_ack && c->replstate == SLAVE_STATE_ONLINE)
|
||||||
putSlaveOnline(c);
|
putSlaveOnline(c);
|
||||||
@ -1334,8 +1334,8 @@ void shiftReplicationId(void) {
|
|||||||
/* Returns 1 if the given replication state is a handshake state,
|
/* Returns 1 if the given replication state is a handshake state,
|
||||||
* 0 otherwise. */
|
* 0 otherwise. */
|
||||||
int slaveIsInHandshakeState(void) {
|
int slaveIsInHandshakeState(void) {
|
||||||
return server.repl_state >= REPL_STATE_RECEIVE_PONG &&
|
return server.repl_state >= REPL_STATE_RECEIVE_PING_REPLY &&
|
||||||
server.repl_state <= REPL_STATE_RECEIVE_PSYNC;
|
server.repl_state <= REPL_STATE_RECEIVE_PSYNC_REPLY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Avoid the master to detect the slave is timing out while loading the
|
/* Avoid the master to detect the slave is timing out while loading the
|
||||||
@ -1690,7 +1690,6 @@ void readSyncBulkPayload(connection *conn) {
|
|||||||
* gets promoted. */
|
* gets promoted. */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopLoading(1);
|
|
||||||
|
|
||||||
/* RDB loading succeeded if we reach this point. */
|
/* RDB loading succeeded if we reach this point. */
|
||||||
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
|
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
|
||||||
@ -1705,6 +1704,7 @@ void readSyncBulkPayload(connection *conn) {
|
|||||||
if (!rioRead(&rdb,buf,CONFIG_RUN_ID_SIZE) ||
|
if (!rioRead(&rdb,buf,CONFIG_RUN_ID_SIZE) ||
|
||||||
memcmp(buf,eofmark,CONFIG_RUN_ID_SIZE) != 0)
|
memcmp(buf,eofmark,CONFIG_RUN_ID_SIZE) != 0)
|
||||||
{
|
{
|
||||||
|
stopLoading(0);
|
||||||
serverLog(LL_WARNING,"Replication stream EOF marker is broken");
|
serverLog(LL_WARNING,"Replication stream EOF marker is broken");
|
||||||
cancelReplicationHandshake(1);
|
cancelReplicationHandshake(1);
|
||||||
rioFreeConn(&rdb, NULL);
|
rioFreeConn(&rdb, NULL);
|
||||||
@ -1712,6 +1712,8 @@ void readSyncBulkPayload(connection *conn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopLoading(1);
|
||||||
|
|
||||||
/* Cleanup and restore the socket to the original state to continue
|
/* Cleanup and restore the socket to the original state to continue
|
||||||
* with the normal replication. */
|
* with the normal replication. */
|
||||||
rioFreeConn(&rdb, NULL);
|
rioFreeConn(&rdb, NULL);
|
||||||
@ -1719,13 +1721,13 @@ void readSyncBulkPayload(connection *conn) {
|
|||||||
connRecvTimeout(conn,0);
|
connRecvTimeout(conn,0);
|
||||||
} else {
|
} else {
|
||||||
/* Ensure background save doesn't overwrite synced data */
|
/* Ensure background save doesn't overwrite synced data */
|
||||||
if (server.rdb_child_pid != -1) {
|
if (server.child_type == CHILD_TYPE_RDB) {
|
||||||
serverLog(LL_NOTICE,
|
serverLog(LL_NOTICE,
|
||||||
"Replica is about to load the RDB file received from the "
|
"Replica is about to load the RDB file received from the "
|
||||||
"master, but there is a pending RDB child running. "
|
"master, but there is a pending RDB child running. "
|
||||||
"Killing process %ld and removing its temp file to avoid "
|
"Killing process %ld and removing its temp file to avoid "
|
||||||
"any race",
|
"any race",
|
||||||
(long) server.rdb_child_pid);
|
(long) server.child_pid);
|
||||||
killRDBChild();
|
killRDBChild();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1752,7 +1754,7 @@ void readSyncBulkPayload(connection *conn) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* Close old rdb asynchronously. */
|
/* Close old rdb asynchronously. */
|
||||||
if (old_rdb_fd != -1) bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)old_rdb_fd,NULL,NULL);
|
if (old_rdb_fd != -1) bioCreateCloseJob(old_rdb_fd);
|
||||||
|
|
||||||
if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION) != C_OK) {
|
if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION) != C_OK) {
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
@ -1827,66 +1829,94 @@ error:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send a synchronous command to the master. Used to send AUTH and
|
char *receiveSynchronousResponse(connection *conn) {
|
||||||
* REPLCONF commands before starting the replication with SYNC.
|
char buf[256];
|
||||||
|
/* Read the reply from the server. */
|
||||||
|
if (connSyncReadLine(conn,buf,sizeof(buf),server.repl_syncio_timeout*1000) == -1)
|
||||||
|
{
|
||||||
|
return sdscatprintf(sdsempty(),"-Reading from master: %s",
|
||||||
|
strerror(errno));
|
||||||
|
}
|
||||||
|
server.repl_transfer_lastio = server.unixtime;
|
||||||
|
return sdsnew(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send a pre-formatted multi-bulk command to the connection. */
|
||||||
|
char* sendCommandRaw(connection *conn, sds cmd) {
|
||||||
|
if (connSyncWrite(conn,cmd,sdslen(cmd),server.repl_syncio_timeout*1000) == -1) {
|
||||||
|
return sdscatprintf(sdsempty(),"-Writing to master: %s",
|
||||||
|
connGetLastError(conn));
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compose a multi-bulk command and send it to the connection.
|
||||||
|
* Used to send AUTH and REPLCONF commands to the master before starting the
|
||||||
|
* replication.
|
||||||
|
*
|
||||||
|
* Takes a list of char* arguments, terminated by a NULL argument.
|
||||||
*
|
*
|
||||||
* The command returns an sds string representing the result of the
|
* The command returns an sds string representing the result of the
|
||||||
* operation. On error the first byte is a "-".
|
* operation. On error the first byte is a "-".
|
||||||
*/
|
*/
|
||||||
#define SYNC_CMD_READ (1<<0)
|
char *sendCommand(connection *conn, ...) {
|
||||||
#define SYNC_CMD_WRITE (1<<1)
|
va_list ap;
|
||||||
#define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE)
|
sds cmd = sdsempty();
|
||||||
char *sendSynchronousCommand(int flags, connection *conn, ...) {
|
sds cmdargs = sdsempty();
|
||||||
|
size_t argslen = 0;
|
||||||
|
char *arg;
|
||||||
|
|
||||||
/* Create the command to send to the master, we use redis binary
|
/* Create the command to send to the master, we use redis binary
|
||||||
* protocol to make sure correct arguments are sent. This function
|
* protocol to make sure correct arguments are sent. This function
|
||||||
* is not safe for all binary data. */
|
* is not safe for all binary data. */
|
||||||
if (flags & SYNC_CMD_WRITE) {
|
va_start(ap,conn);
|
||||||
char *arg;
|
while(1) {
|
||||||
va_list ap;
|
arg = va_arg(ap, char*);
|
||||||
sds cmd = sdsempty();
|
if (arg == NULL) break;
|
||||||
sds cmdargs = sdsempty();
|
cmdargs = sdscatprintf(cmdargs,"$%zu\r\n%s\r\n",strlen(arg),arg);
|
||||||
size_t argslen = 0;
|
argslen++;
|
||||||
va_start(ap,conn);
|
|
||||||
|
|
||||||
while(1) {
|
|
||||||
arg = va_arg(ap, char*);
|
|
||||||
if (arg == NULL) break;
|
|
||||||
|
|
||||||
cmdargs = sdscatprintf(cmdargs,"$%zu\r\n%s\r\n",strlen(arg),arg);
|
|
||||||
argslen++;
|
|
||||||
}
|
|
||||||
|
|
||||||
va_end(ap);
|
|
||||||
|
|
||||||
cmd = sdscatprintf(cmd,"*%zu\r\n",argslen);
|
|
||||||
cmd = sdscatsds(cmd,cmdargs);
|
|
||||||
sdsfree(cmdargs);
|
|
||||||
|
|
||||||
/* Transfer command to the server. */
|
|
||||||
if (connSyncWrite(conn,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
|
|
||||||
== -1)
|
|
||||||
{
|
|
||||||
sdsfree(cmd);
|
|
||||||
return sdscatprintf(sdsempty(),"-Writing to master: %s",
|
|
||||||
connGetLastError(conn));
|
|
||||||
}
|
|
||||||
sdsfree(cmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read the reply from the server. */
|
cmd = sdscatprintf(cmd,"*%zu\r\n",argslen);
|
||||||
if (flags & SYNC_CMD_READ) {
|
cmd = sdscatsds(cmd,cmdargs);
|
||||||
char buf[256];
|
sdsfree(cmdargs);
|
||||||
|
|
||||||
if (connSyncReadLine(conn,buf,sizeof(buf),server.repl_syncio_timeout*1000)
|
va_end(ap);
|
||||||
== -1)
|
char* err = sendCommandRaw(conn, cmd);
|
||||||
{
|
sdsfree(cmd);
|
||||||
return sdscatprintf(sdsempty(),"-Reading from master: %s",
|
if(err)
|
||||||
strerror(errno));
|
return err;
|
||||||
}
|
return NULL;
|
||||||
server.repl_transfer_lastio = server.unixtime;
|
}
|
||||||
return sdsnew(buf);
|
|
||||||
|
/* Compose a multi-bulk command and send it to the connection.
|
||||||
|
* Used to send AUTH and REPLCONF commands to the master before starting the
|
||||||
|
* replication.
|
||||||
|
*
|
||||||
|
* argv_lens is optional, when NULL, strlen is used.
|
||||||
|
*
|
||||||
|
* The command returns an sds string representing the result of the
|
||||||
|
* operation. On error the first byte is a "-".
|
||||||
|
*/
|
||||||
|
char *sendCommandArgv(connection *conn, int argc, char **argv, size_t *argv_lens) {
|
||||||
|
sds cmd = sdsempty();
|
||||||
|
char *arg;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Create the command to send to the master. */
|
||||||
|
cmd = sdscatfmt(cmd,"*%i\r\n",argc);
|
||||||
|
for (i=0; i<argc; i++) {
|
||||||
|
int len;
|
||||||
|
arg = argv[i];
|
||||||
|
len = argv_lens ? argv_lens[i] : strlen(arg);
|
||||||
|
cmd = sdscatfmt(cmd,"$%i\r\n",len);
|
||||||
|
cmd = sdscatlen(cmd,arg,len);
|
||||||
|
cmd = sdscatlen(cmd,"\r\n",2);
|
||||||
}
|
}
|
||||||
|
char* err = sendCommandRaw(conn, cmd);
|
||||||
|
sdsfree(cmd);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1969,7 +1999,7 @@ int slaveTryPartialResynchronization(connection *conn, int read_reply) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Issue the PSYNC command */
|
/* Issue the PSYNC command */
|
||||||
reply = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PSYNC",psync_replid,psync_offset,NULL);
|
reply = sendCommand(conn,"PSYNC",psync_replid,psync_offset,NULL);
|
||||||
if (reply != NULL) {
|
if (reply != NULL) {
|
||||||
serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply);
|
serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply);
|
||||||
sdsfree(reply);
|
sdsfree(reply);
|
||||||
@ -1980,7 +2010,7 @@ int slaveTryPartialResynchronization(connection *conn, int read_reply) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Reading half */
|
/* Reading half */
|
||||||
reply = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
|
reply = receiveSynchronousResponse(conn);
|
||||||
if (sdslen(reply) == 0) {
|
if (sdslen(reply) == 0) {
|
||||||
/* The master may send empty newlines after it receives PSYNC
|
/* The master may send empty newlines after it receives PSYNC
|
||||||
* and before to reply, just to keep the connection alive. */
|
* and before to reply, just to keep the connection alive. */
|
||||||
@ -2131,17 +2161,17 @@ void syncWithMaster(connection *conn) {
|
|||||||
* registered and we can wait for the PONG reply. */
|
* registered and we can wait for the PONG reply. */
|
||||||
connSetReadHandler(conn, syncWithMaster);
|
connSetReadHandler(conn, syncWithMaster);
|
||||||
connSetWriteHandler(conn, NULL);
|
connSetWriteHandler(conn, NULL);
|
||||||
server.repl_state = REPL_STATE_RECEIVE_PONG;
|
server.repl_state = REPL_STATE_RECEIVE_PING_REPLY;
|
||||||
/* Send the PING, don't check for errors at all, we have the timeout
|
/* Send the PING, don't check for errors at all, we have the timeout
|
||||||
* that will take care about this. */
|
* that will take care about this. */
|
||||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PING",NULL);
|
err = sendCommand(conn,"PING",NULL);
|
||||||
if (err) goto write_error;
|
if (err) goto write_error;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Receive the PONG command. */
|
/* Receive the PONG command. */
|
||||||
if (server.repl_state == REPL_STATE_RECEIVE_PONG) {
|
if (server.repl_state == REPL_STATE_RECEIVE_PING_REPLY) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
|
err = receiveSynchronousResponse(conn);
|
||||||
|
|
||||||
/* We accept only two replies as valid, a positive +PONG reply
|
/* We accept only two replies as valid, a positive +PONG reply
|
||||||
* (we just check for "+") or an authentication error.
|
* (we just check for "+") or an authentication error.
|
||||||
@ -2161,59 +2191,88 @@ void syncWithMaster(connection *conn) {
|
|||||||
"Master replied to PING, replication can continue...");
|
"Master replied to PING, replication can continue...");
|
||||||
}
|
}
|
||||||
sdsfree(err);
|
sdsfree(err);
|
||||||
server.repl_state = REPL_STATE_SEND_AUTH;
|
err = NULL;
|
||||||
|
server.repl_state = REPL_STATE_SEND_HANDSHAKE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* AUTH with the master if required. */
|
if (server.repl_state == REPL_STATE_SEND_HANDSHAKE) {
|
||||||
if (server.repl_state == REPL_STATE_SEND_AUTH) {
|
/* AUTH with the master if required. */
|
||||||
if (server.masteruser && server.masterauth) {
|
if (server.masterauth) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"AUTH",
|
char *args[3] = {"AUTH",NULL,NULL};
|
||||||
server.masteruser,server.masterauth,NULL);
|
size_t lens[3] = {4,0,0};
|
||||||
|
int argc = 1;
|
||||||
|
if (server.masteruser) {
|
||||||
|
args[argc] = server.masteruser;
|
||||||
|
lens[argc] = strlen(server.masteruser);
|
||||||
|
argc++;
|
||||||
|
}
|
||||||
|
args[argc] = server.masterauth;
|
||||||
|
lens[argc] = sdslen(server.masterauth);
|
||||||
|
argc++;
|
||||||
|
err = sendCommandArgv(conn, argc, args, lens);
|
||||||
if (err) goto write_error;
|
if (err) goto write_error;
|
||||||
server.repl_state = REPL_STATE_RECEIVE_AUTH;
|
|
||||||
return;
|
|
||||||
} else if (server.masterauth) {
|
|
||||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"AUTH",server.masterauth,NULL);
|
|
||||||
if (err) goto write_error;
|
|
||||||
server.repl_state = REPL_STATE_RECEIVE_AUTH;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
server.repl_state = REPL_STATE_SEND_PORT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set the slave port, so that Master's INFO command can list the
|
||||||
|
* slave listening port correctly. */
|
||||||
|
{
|
||||||
|
int port;
|
||||||
|
if (server.slave_announce_port)
|
||||||
|
port = server.slave_announce_port;
|
||||||
|
else if (server.tls_replication && server.tls_port)
|
||||||
|
port = server.tls_port;
|
||||||
|
else
|
||||||
|
port = server.port;
|
||||||
|
sds portstr = sdsfromlonglong(port);
|
||||||
|
err = sendCommand(conn,"REPLCONF",
|
||||||
|
"listening-port",portstr, NULL);
|
||||||
|
sdsfree(portstr);
|
||||||
|
if (err) goto write_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the slave ip, so that Master's INFO command can list the
|
||||||
|
* slave IP address port correctly in case of port forwarding or NAT.
|
||||||
|
* Skip REPLCONF ip-address if there is no slave-announce-ip option set. */
|
||||||
|
if (server.slave_announce_ip) {
|
||||||
|
err = sendCommand(conn,"REPLCONF",
|
||||||
|
"ip-address",server.slave_announce_ip, NULL);
|
||||||
|
if (err) goto write_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inform the master of our (slave) capabilities.
|
||||||
|
*
|
||||||
|
* EOF: supports EOF-style RDB transfer for diskless replication.
|
||||||
|
* PSYNC2: supports PSYNC v2, so understands +CONTINUE <new repl ID>.
|
||||||
|
*
|
||||||
|
* The master will ignore capabilities it does not understand. */
|
||||||
|
err = sendCommand(conn,"REPLCONF",
|
||||||
|
"capa","eof","capa","psync2",NULL);
|
||||||
|
if (err) goto write_error;
|
||||||
|
|
||||||
|
server.repl_state = REPL_STATE_RECEIVE_AUTH_REPLY;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (server.repl_state == REPL_STATE_RECEIVE_AUTH_REPLY && !server.masterauth)
|
||||||
|
server.repl_state = REPL_STATE_RECEIVE_PORT_REPLY;
|
||||||
|
|
||||||
/* Receive AUTH reply. */
|
/* Receive AUTH reply. */
|
||||||
if (server.repl_state == REPL_STATE_RECEIVE_AUTH) {
|
if (server.repl_state == REPL_STATE_RECEIVE_AUTH_REPLY) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
|
err = receiveSynchronousResponse(conn);
|
||||||
if (err[0] == '-') {
|
if (err[0] == '-') {
|
||||||
serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err);
|
serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err);
|
||||||
sdsfree(err);
|
sdsfree(err);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
sdsfree(err);
|
sdsfree(err);
|
||||||
server.repl_state = REPL_STATE_SEND_PORT;
|
err = NULL;
|
||||||
}
|
server.repl_state = REPL_STATE_RECEIVE_PORT_REPLY;
|
||||||
|
|
||||||
/* Set the slave port, so that Master's INFO command can list the
|
|
||||||
* slave listening port correctly. */
|
|
||||||
if (server.repl_state == REPL_STATE_SEND_PORT) {
|
|
||||||
int port;
|
|
||||||
if (server.slave_announce_port) port = server.slave_announce_port;
|
|
||||||
else if (server.tls_replication && server.tls_port) port = server.tls_port;
|
|
||||||
else port = server.port;
|
|
||||||
sds portstr = sdsfromlonglong(port);
|
|
||||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
|
|
||||||
"listening-port",portstr, NULL);
|
|
||||||
sdsfree(portstr);
|
|
||||||
if (err) goto write_error;
|
|
||||||
sdsfree(err);
|
|
||||||
server.repl_state = REPL_STATE_RECEIVE_PORT;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Receive REPLCONF listening-port reply. */
|
/* Receive REPLCONF listening-port reply. */
|
||||||
if (server.repl_state == REPL_STATE_RECEIVE_PORT) {
|
if (server.repl_state == REPL_STATE_RECEIVE_PORT_REPLY) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
|
err = receiveSynchronousResponse(conn);
|
||||||
/* Ignore the error if any, not all the Redis versions support
|
/* Ignore the error if any, not all the Redis versions support
|
||||||
* REPLCONF listening-port. */
|
* REPLCONF listening-port. */
|
||||||
if (err[0] == '-') {
|
if (err[0] == '-') {
|
||||||
@ -2221,30 +2280,16 @@ void syncWithMaster(connection *conn) {
|
|||||||
"REPLCONF listening-port: %s", err);
|
"REPLCONF listening-port: %s", err);
|
||||||
}
|
}
|
||||||
sdsfree(err);
|
sdsfree(err);
|
||||||
server.repl_state = REPL_STATE_SEND_IP;
|
server.repl_state = REPL_STATE_RECEIVE_IP_REPLY;
|
||||||
}
|
|
||||||
|
|
||||||
/* Skip REPLCONF ip-address if there is no slave-announce-ip option set. */
|
|
||||||
if (server.repl_state == REPL_STATE_SEND_IP &&
|
|
||||||
server.slave_announce_ip == NULL)
|
|
||||||
{
|
|
||||||
server.repl_state = REPL_STATE_SEND_CAPA;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set the slave ip, so that Master's INFO command can list the
|
|
||||||
* slave IP address port correctly in case of port forwarding or NAT. */
|
|
||||||
if (server.repl_state == REPL_STATE_SEND_IP) {
|
|
||||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
|
|
||||||
"ip-address",server.slave_announce_ip, NULL);
|
|
||||||
if (err) goto write_error;
|
|
||||||
sdsfree(err);
|
|
||||||
server.repl_state = REPL_STATE_RECEIVE_IP;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (server.repl_state == REPL_STATE_RECEIVE_IP_REPLY && !server.slave_announce_ip)
|
||||||
|
server.repl_state = REPL_STATE_RECEIVE_CAPA_REPLY;
|
||||||
|
|
||||||
/* Receive REPLCONF ip-address reply. */
|
/* Receive REPLCONF ip-address reply. */
|
||||||
if (server.repl_state == REPL_STATE_RECEIVE_IP) {
|
if (server.repl_state == REPL_STATE_RECEIVE_IP_REPLY) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
|
err = receiveSynchronousResponse(conn);
|
||||||
/* Ignore the error if any, not all the Redis versions support
|
/* Ignore the error if any, not all the Redis versions support
|
||||||
* REPLCONF listening-port. */
|
* REPLCONF listening-port. */
|
||||||
if (err[0] == '-') {
|
if (err[0] == '-') {
|
||||||
@ -2252,27 +2297,13 @@ void syncWithMaster(connection *conn) {
|
|||||||
"REPLCONF ip-address: %s", err);
|
"REPLCONF ip-address: %s", err);
|
||||||
}
|
}
|
||||||
sdsfree(err);
|
sdsfree(err);
|
||||||
server.repl_state = REPL_STATE_SEND_CAPA;
|
server.repl_state = REPL_STATE_RECEIVE_CAPA_REPLY;
|
||||||
}
|
|
||||||
|
|
||||||
/* Inform the master of our (slave) capabilities.
|
|
||||||
*
|
|
||||||
* EOF: supports EOF-style RDB transfer for diskless replication.
|
|
||||||
* PSYNC2: supports PSYNC v2, so understands +CONTINUE <new repl ID>.
|
|
||||||
*
|
|
||||||
* The master will ignore capabilities it does not understand. */
|
|
||||||
if (server.repl_state == REPL_STATE_SEND_CAPA) {
|
|
||||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
|
|
||||||
"capa","eof","capa","psync2",NULL);
|
|
||||||
if (err) goto write_error;
|
|
||||||
sdsfree(err);
|
|
||||||
server.repl_state = REPL_STATE_RECEIVE_CAPA;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Receive CAPA reply. */
|
/* Receive CAPA reply. */
|
||||||
if (server.repl_state == REPL_STATE_RECEIVE_CAPA) {
|
if (server.repl_state == REPL_STATE_RECEIVE_CAPA_REPLY) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
|
err = receiveSynchronousResponse(conn);
|
||||||
/* Ignore the error if any, not all the Redis versions support
|
/* Ignore the error if any, not all the Redis versions support
|
||||||
* REPLCONF capa. */
|
* REPLCONF capa. */
|
||||||
if (err[0] == '-') {
|
if (err[0] == '-') {
|
||||||
@ -2280,6 +2311,7 @@ void syncWithMaster(connection *conn) {
|
|||||||
"REPLCONF capa: %s", err);
|
"REPLCONF capa: %s", err);
|
||||||
}
|
}
|
||||||
sdsfree(err);
|
sdsfree(err);
|
||||||
|
err = NULL;
|
||||||
server.repl_state = REPL_STATE_SEND_PSYNC;
|
server.repl_state = REPL_STATE_SEND_PSYNC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2293,12 +2325,12 @@ void syncWithMaster(connection *conn) {
|
|||||||
err = sdsnew("Write error sending the PSYNC command.");
|
err = sdsnew("Write error sending the PSYNC command.");
|
||||||
goto write_error;
|
goto write_error;
|
||||||
}
|
}
|
||||||
server.repl_state = REPL_STATE_RECEIVE_PSYNC;
|
server.repl_state = REPL_STATE_RECEIVE_PSYNC_REPLY;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If reached this point, we should be in REPL_STATE_RECEIVE_PSYNC. */
|
/* If reached this point, we should be in REPL_STATE_RECEIVE_PSYNC. */
|
||||||
if (server.repl_state != REPL_STATE_RECEIVE_PSYNC) {
|
if (server.repl_state != REPL_STATE_RECEIVE_PSYNC_REPLY) {
|
||||||
serverLog(LL_WARNING,"syncWithMaster(): state machine error, "
|
serverLog(LL_WARNING,"syncWithMaster(): state machine error, "
|
||||||
"state should be RECEIVE_PSYNC but is %d",
|
"state should be RECEIVE_PSYNC but is %d",
|
||||||
server.repl_state);
|
server.repl_state);
|
||||||
@ -2393,7 +2425,7 @@ error:
|
|||||||
server.repl_state = REPL_STATE_CONNECT;
|
server.repl_state = REPL_STATE_CONNECT;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
write_error: /* Handle sendSynchronousCommand(SYNC_CMD_WRITE) errors. */
|
write_error: /* Handle sendCommand() errors. */
|
||||||
serverLog(LL_WARNING,"Sending command to master in replication handshake: %s", err);
|
serverLog(LL_WARNING,"Sending command to master in replication handshake: %s", err);
|
||||||
sdsfree(err);
|
sdsfree(err);
|
||||||
goto error;
|
goto error;
|
||||||
@ -3205,7 +3237,7 @@ void replicationCron(void) {
|
|||||||
int manual_failover_in_progress =
|
int manual_failover_in_progress =
|
||||||
server.cluster_enabled &&
|
server.cluster_enabled &&
|
||||||
server.cluster->mf_end &&
|
server.cluster->mf_end &&
|
||||||
clientsArePaused();
|
checkClientPauseTimeoutAndReturnIfPaused();
|
||||||
|
|
||||||
if (!manual_failover_in_progress) {
|
if (!manual_failover_in_progress) {
|
||||||
ping_argv[0] = createStringObject("PING",4);
|
ping_argv[0] = createStringObject("PING",4);
|
||||||
|
@ -366,10 +366,7 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
|||||||
lua_gettable(lua,-2);
|
lua_gettable(lua,-2);
|
||||||
t = lua_type(lua,-1);
|
t = lua_type(lua,-1);
|
||||||
if (t == LUA_TSTRING) {
|
if (t == LUA_TSTRING) {
|
||||||
sds err = sdsnew(lua_tostring(lua,-1));
|
addReplyErrorFormat(c,"-%s",lua_tostring(lua,-1));
|
||||||
sdsmapchars(err,"\r\n"," ",2);
|
|
||||||
addReplySds(c,sdscatprintf(sdsempty(),"-%s\r\n",err));
|
|
||||||
sdsfree(err);
|
|
||||||
lua_pop(lua,2);
|
lua_pop(lua,2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -410,9 +407,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
|||||||
lua_pushnil(lua); /* Use nil to start iteration. */
|
lua_pushnil(lua); /* Use nil to start iteration. */
|
||||||
while (lua_next(lua,-2)) {
|
while (lua_next(lua,-2)) {
|
||||||
/* Stack now: table, key, value */
|
/* Stack now: table, key, value */
|
||||||
luaReplyToRedisReply(c, lua); /* Return value. */
|
lua_pushvalue(lua,-2); /* Dup key before consuming. */
|
||||||
lua_pushvalue(lua,-1); /* Dup key before consuming. */
|
|
||||||
luaReplyToRedisReply(c, lua); /* Return key. */
|
luaReplyToRedisReply(c, lua); /* Return key. */
|
||||||
|
luaReplyToRedisReply(c, lua); /* Return value. */
|
||||||
/* Stack now: table, key. */
|
/* Stack now: table, key. */
|
||||||
maplen++;
|
maplen++;
|
||||||
}
|
}
|
||||||
@ -721,7 +718,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
|||||||
server.lua_write_dirty &&
|
server.lua_write_dirty &&
|
||||||
server.lua_repl != PROPAGATE_NONE)
|
server.lua_repl != PROPAGATE_NONE)
|
||||||
{
|
{
|
||||||
execCommandPropagateMulti(server.lua_caller);
|
execCommandPropagateMulti(server.lua_caller->db->id);
|
||||||
server.lua_multi_emitted = 1;
|
server.lua_multi_emitted = 1;
|
||||||
/* Now we are in the MULTI context, the lua_client should be
|
/* Now we are in the MULTI context, the lua_client should be
|
||||||
* flag as CLIENT_MULTI. */
|
* flag as CLIENT_MULTI. */
|
||||||
@ -1638,7 +1635,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
|||||||
if (server.lua_replicate_commands) {
|
if (server.lua_replicate_commands) {
|
||||||
preventCommandPropagation(c);
|
preventCommandPropagation(c);
|
||||||
if (server.lua_multi_emitted) {
|
if (server.lua_multi_emitted) {
|
||||||
execCommandPropagateExec(c);
|
execCommandPropagateExec(c->db->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1710,11 +1707,16 @@ void evalShaCommand(client *c) {
|
|||||||
void scriptCommand(client *c) {
|
void scriptCommand(client *c) {
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"DEBUG (yes|sync|no) -- Set the debug mode for subsequent scripts executed.",
|
"DEBUG (YES|SYNC|NO)",
|
||||||
"EXISTS <sha1> [<sha1> ...] -- Return information about the existence of the scripts in the script cache.",
|
" Set the debug mode for subsequent scripts executed.",
|
||||||
"FLUSH -- Flush the Lua scripts cache. Very dangerous on replicas.",
|
"EXISTS <sha1> [<sha1> ...]",
|
||||||
"KILL -- Kill the currently executing Lua script.",
|
" Return information about the existence of the scripts in the script cache.",
|
||||||
"LOAD <script> -- Load a script into the scripts cache, without executing it.",
|
"FLUSH",
|
||||||
|
" Flush the Lua scripts cache. Very dangerous on replicas.",
|
||||||
|
"KILL",
|
||||||
|
" Kill the currently executing Lua script.",
|
||||||
|
"LOAD <script>",
|
||||||
|
" Load a script into the scripts cache without executing it.",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
@ -1740,11 +1742,11 @@ NULL
|
|||||||
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
|
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
|
||||||
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
|
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
|
||||||
if (server.lua_caller == NULL) {
|
if (server.lua_caller == NULL) {
|
||||||
addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n"));
|
addReplyError(c,"-NOTBUSY No scripts in execution right now.");
|
||||||
} else if (server.lua_caller->flags & CLIENT_MASTER) {
|
} else if (server.lua_caller->flags & CLIENT_MASTER) {
|
||||||
addReplySds(c,sdsnew("-UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed.\r\n"));
|
addReplyError(c,"-UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed.");
|
||||||
} else if (server.lua_write_dirty) {
|
} else if (server.lua_write_dirty) {
|
||||||
addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.\r\n"));
|
addReplyError(c,"-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.");
|
||||||
} else {
|
} else {
|
||||||
server.lua_kill = 1;
|
server.lua_kill = 1;
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
@ -1765,7 +1767,7 @@ NULL
|
|||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
c->flags |= CLIENT_LUA_DEBUG_SYNC;
|
c->flags |= CLIENT_LUA_DEBUG_SYNC;
|
||||||
} else {
|
} else {
|
||||||
addReplyError(c,"Use SCRIPT DEBUG yes/sync/no");
|
addReplyError(c,"Use SCRIPT DEBUG YES/SYNC/NO");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
50
src/sds.c
50
src/sds.c
@ -642,7 +642,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
|||||||
/* To avoid continuous reallocations, let's start with a buffer that
|
/* To avoid continuous reallocations, let's start with a buffer that
|
||||||
* can hold at least two times the format string itself. It's not the
|
* can hold at least two times the format string itself. It's not the
|
||||||
* best heuristic but seems to work in practice. */
|
* best heuristic but seems to work in practice. */
|
||||||
s = sdsMakeRoomFor(s, initlen + strlen(fmt)*2);
|
s = sdsMakeRoomFor(s, strlen(fmt)*2);
|
||||||
va_start(ap,fmt);
|
va_start(ap,fmt);
|
||||||
f = fmt; /* Next format specifier byte to process. */
|
f = fmt; /* Next format specifier byte to process. */
|
||||||
i = initlen; /* Position of the next byte to write to dest str. */
|
i = initlen; /* Position of the next byte to write to dest str. */
|
||||||
@ -1159,8 +1159,8 @@ void sds_free(void *ptr) { s_free(ptr); }
|
|||||||
|
|
||||||
#ifdef REDIS_TEST
|
#ifdef REDIS_TEST
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <limits.h>
|
||||||
#include "testhelp.h"
|
#include "testhelp.h"
|
||||||
#include "limits.h"
|
|
||||||
|
|
||||||
#define UNUSED(x) (void)(x)
|
#define UNUSED(x) (void)(x)
|
||||||
int sdsTest(int argc, char **argv) {
|
int sdsTest(int argc, char **argv) {
|
||||||
@ -1171,12 +1171,12 @@ int sdsTest(int argc, char **argv) {
|
|||||||
sds x = sdsnew("foo"), y;
|
sds x = sdsnew("foo"), y;
|
||||||
|
|
||||||
test_cond("Create a string and obtain the length",
|
test_cond("Create a string and obtain the length",
|
||||||
sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0)
|
sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0);
|
||||||
|
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
x = sdsnewlen("foo",2);
|
x = sdsnewlen("foo",2);
|
||||||
test_cond("Create a string with specified length",
|
test_cond("Create a string with specified length",
|
||||||
sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0)
|
sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0);
|
||||||
|
|
||||||
x = sdscat(x,"bar");
|
x = sdscat(x,"bar");
|
||||||
test_cond("Strings concatenation",
|
test_cond("Strings concatenation",
|
||||||
@ -1184,22 +1184,22 @@ int sdsTest(int argc, char **argv) {
|
|||||||
|
|
||||||
x = sdscpy(x,"a");
|
x = sdscpy(x,"a");
|
||||||
test_cond("sdscpy() against an originally longer string",
|
test_cond("sdscpy() against an originally longer string",
|
||||||
sdslen(x) == 1 && memcmp(x,"a\0",2) == 0)
|
sdslen(x) == 1 && memcmp(x,"a\0",2) == 0);
|
||||||
|
|
||||||
x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk");
|
x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk");
|
||||||
test_cond("sdscpy() against an originally shorter string",
|
test_cond("sdscpy() against an originally shorter string",
|
||||||
sdslen(x) == 33 &&
|
sdslen(x) == 33 &&
|
||||||
memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0)
|
memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0);
|
||||||
|
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
x = sdscatprintf(sdsempty(),"%d",123);
|
x = sdscatprintf(sdsempty(),"%d",123);
|
||||||
test_cond("sdscatprintf() seems working in the base case",
|
test_cond("sdscatprintf() seems working in the base case",
|
||||||
sdslen(x) == 3 && memcmp(x,"123\0",4) == 0)
|
sdslen(x) == 3 && memcmp(x,"123\0",4) == 0);
|
||||||
|
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
x = sdscatprintf(sdsempty(),"a%cb",0);
|
x = sdscatprintf(sdsempty(),"a%cb",0);
|
||||||
test_cond("sdscatprintf() seems working with \\0 inside of result",
|
test_cond("sdscatprintf() seems working with \\0 inside of result",
|
||||||
sdslen(x) == 3 && memcmp(x,"a\0""b\0",4) == 0)
|
sdslen(x) == 3 && memcmp(x,"a\0""b\0",4) == 0);
|
||||||
|
|
||||||
{
|
{
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
@ -1209,7 +1209,7 @@ int sdsTest(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
x = sdscatprintf(sdsempty(),"%0*d",(int)sizeof(etalon),0);
|
x = sdscatprintf(sdsempty(),"%0*d",(int)sizeof(etalon),0);
|
||||||
test_cond("sdscatprintf() can print 1MB",
|
test_cond("sdscatprintf() can print 1MB",
|
||||||
sdslen(x) == sizeof(etalon) && memcmp(x,etalon,sizeof(etalon)) == 0)
|
sdslen(x) == sizeof(etalon) && memcmp(x,etalon,sizeof(etalon)) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
@ -1218,7 +1218,7 @@ int sdsTest(int argc, char **argv) {
|
|||||||
test_cond("sdscatfmt() seems working in the base case",
|
test_cond("sdscatfmt() seems working in the base case",
|
||||||
sdslen(x) == 60 &&
|
sdslen(x) == 60 &&
|
||||||
memcmp(x,"--Hello Hi! World -9223372036854775808,"
|
memcmp(x,"--Hello Hi! World -9223372036854775808,"
|
||||||
"9223372036854775807--",60) == 0)
|
"9223372036854775807--",60) == 0);
|
||||||
printf("[%s]\n",x);
|
printf("[%s]\n",x);
|
||||||
|
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
@ -1226,85 +1226,85 @@ int sdsTest(int argc, char **argv) {
|
|||||||
x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX);
|
x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX);
|
||||||
test_cond("sdscatfmt() seems working with unsigned numbers",
|
test_cond("sdscatfmt() seems working with unsigned numbers",
|
||||||
sdslen(x) == 35 &&
|
sdslen(x) == 35 &&
|
||||||
memcmp(x,"--4294967295,18446744073709551615--",35) == 0)
|
memcmp(x,"--4294967295,18446744073709551615--",35) == 0);
|
||||||
|
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
x = sdsnew(" x ");
|
x = sdsnew(" x ");
|
||||||
sdstrim(x," x");
|
sdstrim(x," x");
|
||||||
test_cond("sdstrim() works when all chars match",
|
test_cond("sdstrim() works when all chars match",
|
||||||
sdslen(x) == 0)
|
sdslen(x) == 0);
|
||||||
|
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
x = sdsnew(" x ");
|
x = sdsnew(" x ");
|
||||||
sdstrim(x," ");
|
sdstrim(x," ");
|
||||||
test_cond("sdstrim() works when a single char remains",
|
test_cond("sdstrim() works when a single char remains",
|
||||||
sdslen(x) == 1 && x[0] == 'x')
|
sdslen(x) == 1 && x[0] == 'x');
|
||||||
|
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
x = sdsnew("xxciaoyyy");
|
x = sdsnew("xxciaoyyy");
|
||||||
sdstrim(x,"xy");
|
sdstrim(x,"xy");
|
||||||
test_cond("sdstrim() correctly trims characters",
|
test_cond("sdstrim() correctly trims characters",
|
||||||
sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
|
sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0);
|
||||||
|
|
||||||
y = sdsdup(x);
|
y = sdsdup(x);
|
||||||
sdsrange(y,1,1);
|
sdsrange(y,1,1);
|
||||||
test_cond("sdsrange(...,1,1)",
|
test_cond("sdsrange(...,1,1)",
|
||||||
sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
|
sdslen(y) == 1 && memcmp(y,"i\0",2) == 0);
|
||||||
|
|
||||||
sdsfree(y);
|
sdsfree(y);
|
||||||
y = sdsdup(x);
|
y = sdsdup(x);
|
||||||
sdsrange(y,1,-1);
|
sdsrange(y,1,-1);
|
||||||
test_cond("sdsrange(...,1,-1)",
|
test_cond("sdsrange(...,1,-1)",
|
||||||
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
|
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0);
|
||||||
|
|
||||||
sdsfree(y);
|
sdsfree(y);
|
||||||
y = sdsdup(x);
|
y = sdsdup(x);
|
||||||
sdsrange(y,-2,-1);
|
sdsrange(y,-2,-1);
|
||||||
test_cond("sdsrange(...,-2,-1)",
|
test_cond("sdsrange(...,-2,-1)",
|
||||||
sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
|
sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0);
|
||||||
|
|
||||||
sdsfree(y);
|
sdsfree(y);
|
||||||
y = sdsdup(x);
|
y = sdsdup(x);
|
||||||
sdsrange(y,2,1);
|
sdsrange(y,2,1);
|
||||||
test_cond("sdsrange(...,2,1)",
|
test_cond("sdsrange(...,2,1)",
|
||||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
|
sdslen(y) == 0 && memcmp(y,"\0",1) == 0);
|
||||||
|
|
||||||
sdsfree(y);
|
sdsfree(y);
|
||||||
y = sdsdup(x);
|
y = sdsdup(x);
|
||||||
sdsrange(y,1,100);
|
sdsrange(y,1,100);
|
||||||
test_cond("sdsrange(...,1,100)",
|
test_cond("sdsrange(...,1,100)",
|
||||||
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
|
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0);
|
||||||
|
|
||||||
sdsfree(y);
|
sdsfree(y);
|
||||||
y = sdsdup(x);
|
y = sdsdup(x);
|
||||||
sdsrange(y,100,100);
|
sdsrange(y,100,100);
|
||||||
test_cond("sdsrange(...,100,100)",
|
test_cond("sdsrange(...,100,100)",
|
||||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
|
sdslen(y) == 0 && memcmp(y,"\0",1) == 0);
|
||||||
|
|
||||||
sdsfree(y);
|
sdsfree(y);
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
x = sdsnew("foo");
|
x = sdsnew("foo");
|
||||||
y = sdsnew("foa");
|
y = sdsnew("foa");
|
||||||
test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0)
|
test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0);
|
||||||
|
|
||||||
sdsfree(y);
|
sdsfree(y);
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
x = sdsnew("bar");
|
x = sdsnew("bar");
|
||||||
y = sdsnew("bar");
|
y = sdsnew("bar");
|
||||||
test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0)
|
test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0);
|
||||||
|
|
||||||
sdsfree(y);
|
sdsfree(y);
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
x = sdsnew("aar");
|
x = sdsnew("aar");
|
||||||
y = sdsnew("bar");
|
y = sdsnew("bar");
|
||||||
test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
|
test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0);
|
||||||
|
|
||||||
sdsfree(y);
|
sdsfree(y);
|
||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
x = sdsnewlen("\a\n\0foo\r",7);
|
x = sdsnewlen("\a\n\0foo\r",7);
|
||||||
y = sdscatrepr(sdsempty(),x,sdslen(x));
|
y = sdscatrepr(sdsempty(),x,sdslen(x));
|
||||||
test_cond("sdscatrepr(...data...)",
|
test_cond("sdscatrepr(...data...)",
|
||||||
memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
|
memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0);
|
||||||
|
|
||||||
{
|
{
|
||||||
unsigned int oldfree;
|
unsigned int oldfree;
|
||||||
@ -1343,7 +1343,7 @@ int sdsTest(int argc, char **argv) {
|
|||||||
sdsfree(x);
|
sdsfree(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
test_report()
|
test_report();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -469,7 +469,7 @@ struct redisCommand sentinelcmds[] = {
|
|||||||
{"client",clientCommand,-2,"admin random @connection",0,NULL,0,0,0,0,0},
|
{"client",clientCommand,-2,"admin random @connection",0,NULL,0,0,0,0,0},
|
||||||
{"shutdown",shutdownCommand,-1,"admin",0,NULL,0,0,0,0,0},
|
{"shutdown",shutdownCommand,-1,"admin",0,NULL,0,0,0,0,0},
|
||||||
{"auth",authCommand,-2,"no-auth fast @connection",0,NULL,0,0,0,0,0},
|
{"auth",authCommand,-2,"no-auth fast @connection",0,NULL,0,0,0,0,0},
|
||||||
{"hello",helloCommand,-2,"no-auth fast @connection",0,NULL,0,0,0,0,0},
|
{"hello",helloCommand,-1,"no-auth fast @connection",0,NULL,0,0,0,0,0},
|
||||||
{"acl",aclCommand,-2,"admin",0,NULL,0,0,0,0,0,0},
|
{"acl",aclCommand,-2,"admin",0,NULL,0,0,0,0,0,0},
|
||||||
{"command",commandCommand,-1, "random @connection", 0,NULL,0,0,0,0,0,0}
|
{"command",commandCommand,-1, "random @connection", 0,NULL,0,0,0,0,0,0}
|
||||||
};
|
};
|
||||||
@ -3090,25 +3090,45 @@ int sentinelIsQuorumReachable(sentinelRedisInstance *master, int *usableptr) {
|
|||||||
void sentinelCommand(client *c) {
|
void sentinelCommand(client *c) {
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"MASTERS -- Show a list of monitored masters and their state.",
|
"CKQUORUM <master-name>",
|
||||||
"MASTER <master-name> -- Show the state and info of the specified master.",
|
" Check if the current Sentinel configuration is able to reach the quorum",
|
||||||
"REPLICAS <master-name> -- Show a list of replicas for this master and their state.",
|
" needed to failover a master and the majority needed to authorize the",
|
||||||
"SENTINELS <master-name> -- Show a list of Sentinel instances for this master and their state.",
|
" failover.",
|
||||||
"MYID -- Show Current Sentinel Id",
|
"GET-MASTER-ADDR-BY-NAME <master-name>",
|
||||||
"IS-MASTER-DOWN-BY-ADDR <ip> <port> <current-epoch> <runid> -- Check if the master specified by ip:port is down from current Sentinel's point of view.",
|
" Return the ip and port number of the master with that name.",
|
||||||
"GET-MASTER-ADDR-BY-NAME <master-name> -- Return the ip and port number of the master with that name.",
|
"FAILOVER <master-name>",
|
||||||
"RESET <pattern> -- Reset masters for specific master name matching this pattern.",
|
" Manually failover a master node without asking for agreement from other",
|
||||||
"FAILOVER <master-name> -- Manually failover a master node without asking for agreement from other Sentinels",
|
" Sentinels",
|
||||||
"PENDING-SCRIPTS -- Get pending scripts information.",
|
"FLUSHCONFIG",
|
||||||
"MONITOR <name> <ip> <port> <quorum> -- Start monitoring a new master with the specified name, ip, port and quorum.",
|
" Force Sentinel to rewrite its configuration on disk, including the current",
|
||||||
"FLUSHCONFIG -- Force Sentinel to rewrite its configuration on disk, including the current Sentinel state.",
|
" Sentinel state.",
|
||||||
"REMOVE <master-name> -- Remove master from Sentinel's monitor list.",
|
"INFO-CACHE <master-name>",
|
||||||
"CKQUORUM <master-name> -- Check if the current Sentinel configuration is able to reach the quorum needed to failover a master "
|
" Return last cached INFO output from masters and all its replicas.",
|
||||||
"and the majority needed to authorize the failover.",
|
"IS-MASTER-DOWN-BY-ADDR <ip> <port> <current-epoch> <runid>",
|
||||||
"SET <master-name> <option> <value> -- Set configuration paramters for certain masters.",
|
" Check if the master specified by ip:port is down from current Sentinel's",
|
||||||
"INFO-CACHE <master-name> -- Return last cached INFO output from masters and all its replicas.",
|
" point of view.",
|
||||||
"SIMULATE-FAILURE (crash-after-election|crash-after-promotion|help) -- Simulate a Sentinel crash.",
|
"MASTER <master-name>",
|
||||||
"HELP -- Prints this help.",
|
" Show the state and info of the specified master.",
|
||||||
|
"MASTERS",
|
||||||
|
" Show a list of monitored masters and their state.",
|
||||||
|
"MONITOR <name> <ip> <port> <quorum>",
|
||||||
|
" Start monitoring a new master with the specified name, ip, port and quorum.",
|
||||||
|
"MYID",
|
||||||
|
" Return the ID of the Sentinel instance.",
|
||||||
|
"PENDING-SCRIPTS",
|
||||||
|
" Get pending scripts information.",
|
||||||
|
"REMOVE <master-name>",
|
||||||
|
" Remove master from Sentinel's monitor list.",
|
||||||
|
"REPLICAS <master-name>",
|
||||||
|
" Show a list of replicas for this master and their state.",
|
||||||
|
"RESET <pattern>",
|
||||||
|
" Reset masters for specific master name matching this pattern.",
|
||||||
|
"SENTINELS <master-name>",
|
||||||
|
" Show a list of Sentinel instances for this master and their state.",
|
||||||
|
"SET <master-name> <option> <value>",
|
||||||
|
" Set configuration paramters for certain masters.",
|
||||||
|
"SIMULATE-FAILURE (CRASH-AFTER-ELECTION|CRASH-AFTER-PROMOTION|HELP)",
|
||||||
|
" Simulate a Sentinel crash.",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
@ -3446,7 +3466,7 @@ numargserr:
|
|||||||
/* SENTINEL INFO [section] */
|
/* SENTINEL INFO [section] */
|
||||||
void sentinelInfoCommand(client *c) {
|
void sentinelInfoCommand(client *c) {
|
||||||
if (c->argc > 2) {
|
if (c->argc > 2) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3579,14 +3599,13 @@ void sentinelSetCommand(client *c) {
|
|||||||
"Reconfiguration of scripts path is denied for "
|
"Reconfiguration of scripts path is denied for "
|
||||||
"security reasons. Check the deny-scripts-reconfig "
|
"security reasons. Check the deny-scripts-reconfig "
|
||||||
"configuration directive in your Sentinel configuration");
|
"configuration directive in your Sentinel configuration");
|
||||||
return;
|
goto seterr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen(value) && access(value,X_OK) == -1) {
|
if (strlen(value) && access(value,X_OK) == -1) {
|
||||||
addReplyError(c,
|
addReplyError(c,
|
||||||
"Notification script seems non existing or non executable");
|
"Notification script seems non existing or non executable");
|
||||||
if (changes) sentinelFlushConfig();
|
goto seterr;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
sdsfree(ri->notification_script);
|
sdsfree(ri->notification_script);
|
||||||
ri->notification_script = strlen(value) ? sdsnew(value) : NULL;
|
ri->notification_script = strlen(value) ? sdsnew(value) : NULL;
|
||||||
@ -3599,15 +3618,14 @@ void sentinelSetCommand(client *c) {
|
|||||||
"Reconfiguration of scripts path is denied for "
|
"Reconfiguration of scripts path is denied for "
|
||||||
"security reasons. Check the deny-scripts-reconfig "
|
"security reasons. Check the deny-scripts-reconfig "
|
||||||
"configuration directive in your Sentinel configuration");
|
"configuration directive in your Sentinel configuration");
|
||||||
return;
|
goto seterr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen(value) && access(value,X_OK) == -1) {
|
if (strlen(value) && access(value,X_OK) == -1) {
|
||||||
addReplyError(c,
|
addReplyError(c,
|
||||||
"Client reconfiguration script seems non existing or "
|
"Client reconfiguration script seems non existing or "
|
||||||
"non executable");
|
"non executable");
|
||||||
if (changes) sentinelFlushConfig();
|
goto seterr;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
sdsfree(ri->client_reconfig_script);
|
sdsfree(ri->client_reconfig_script);
|
||||||
ri->client_reconfig_script = strlen(value) ? sdsnew(value) : NULL;
|
ri->client_reconfig_script = strlen(value) ? sdsnew(value) : NULL;
|
||||||
@ -3657,8 +3675,7 @@ void sentinelSetCommand(client *c) {
|
|||||||
} else {
|
} else {
|
||||||
addReplyErrorFormat(c,"Unknown option or number of arguments for "
|
addReplyErrorFormat(c,"Unknown option or number of arguments for "
|
||||||
"SENTINEL SET '%s'", option);
|
"SENTINEL SET '%s'", option);
|
||||||
if (changes) sentinelFlushConfig();
|
goto seterr;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Log the event. */
|
/* Log the event. */
|
||||||
@ -3684,9 +3701,11 @@ void sentinelSetCommand(client *c) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
badfmt: /* Bad format errors */
|
badfmt: /* Bad format errors */
|
||||||
if (changes) sentinelFlushConfig();
|
|
||||||
addReplyErrorFormat(c,"Invalid argument '%s' for SENTINEL SET '%s'",
|
addReplyErrorFormat(c,"Invalid argument '%s' for SENTINEL SET '%s'",
|
||||||
(char*)c->argv[badarg]->ptr,option);
|
(char*)c->argv[badarg]->ptr,option);
|
||||||
|
seterr:
|
||||||
|
if (changes) sentinelFlushConfig();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Our fake PUBLISH command: it is actually useful only to receive hello messages
|
/* Our fake PUBLISH command: it is actually useful only to receive hello messages
|
||||||
|
488
src/server.c
488
src/server.c
@ -34,6 +34,7 @@
|
|||||||
#include "bio.h"
|
#include "bio.h"
|
||||||
#include "latency.h"
|
#include "latency.h"
|
||||||
#include "atomicvar.h"
|
#include "atomicvar.h"
|
||||||
|
#include "mt19937-64.h"
|
||||||
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
@ -58,6 +59,10 @@
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Our shared "common" objects */
|
/* Our shared "common" objects */
|
||||||
|
|
||||||
struct sharedObjectsStruct shared;
|
struct sharedObjectsStruct shared;
|
||||||
@ -115,9 +120,9 @@ struct redisServer server; /* Server global state */
|
|||||||
*
|
*
|
||||||
* write: Write command (may modify the key space).
|
* write: Write command (may modify the key space).
|
||||||
*
|
*
|
||||||
* read-only: All the non special commands just reading from keys without
|
* read-only: Commands just reading from keys without changing the content.
|
||||||
* changing the content, or returning other information like
|
* Note that commands that don't read from the keyspace such as
|
||||||
* the TIME command. Special commands such administrative commands
|
* TIME, SELECT, INFO, administrative commands, and connection
|
||||||
* or transaction related commands (multi, exec, discard, ...)
|
* or transaction related commands (multi, exec, discard, ...)
|
||||||
* are not flagged as read-only commands, since they affect the
|
* are not flagged as read-only commands, since they affect the
|
||||||
* server or the connection in other ways.
|
* server or the connection in other ways.
|
||||||
@ -159,6 +164,13 @@ struct redisServer server; /* Server global state */
|
|||||||
* us time. Note that commands that may trigger a DEL as a side
|
* us time. Note that commands that may trigger a DEL as a side
|
||||||
* effect (like SET) are not fast commands.
|
* effect (like SET) are not fast commands.
|
||||||
*
|
*
|
||||||
|
* may-replicate: Command may produce replication traffic, but should be
|
||||||
|
* allowed under circumstances where write commands are disallowed.
|
||||||
|
* Examples include PUBLISH, which replicates pubsub messages,and
|
||||||
|
* EVAL, which may execute write commands, which are replicated,
|
||||||
|
* or may just execute read commands. A command can not be marked
|
||||||
|
* both "write" and "may-replicate"
|
||||||
|
*
|
||||||
* The following additional flags are only used in order to put commands
|
* The following additional flags are only used in order to put commands
|
||||||
* in a specific ACL category. Commands can have multiple ACL categories.
|
* in a specific ACL category. Commands can have multiple ACL categories.
|
||||||
*
|
*
|
||||||
@ -287,11 +299,11 @@ struct redisCommand redisCommandTable[] = {
|
|||||||
"write use-memory @list",
|
"write use-memory @list",
|
||||||
0,NULL,1,1,1,0,0,0},
|
0,NULL,1,1,1,0,0,0},
|
||||||
|
|
||||||
{"rpop",rpopCommand,2,
|
{"rpop",rpopCommand,-2,
|
||||||
"write fast @list",
|
"write fast @list",
|
||||||
0,NULL,1,1,1,0,0,0},
|
0,NULL,1,1,1,0,0,0},
|
||||||
|
|
||||||
{"lpop",lpopCommand,2,
|
{"lpop",lpopCommand,-2,
|
||||||
"write fast @list",
|
"write fast @list",
|
||||||
0,NULL,1,1,1,0,0,0},
|
0,NULL,1,1,1,0,0,0},
|
||||||
|
|
||||||
@ -463,6 +475,10 @@ struct redisCommand redisCommandTable[] = {
|
|||||||
"read-only @sortedset",
|
"read-only @sortedset",
|
||||||
0,NULL,1,1,1,0,0,0},
|
0,NULL,1,1,1,0,0,0},
|
||||||
|
|
||||||
|
{"zrangestore",zrangestoreCommand,-5,
|
||||||
|
"write use-memory @sortedset",
|
||||||
|
0,NULL,1,2,1,0,0,0},
|
||||||
|
|
||||||
{"zrangebyscore",zrangebyscoreCommand,-4,
|
{"zrangebyscore",zrangebyscoreCommand,-4,
|
||||||
"read-only @sortedset",
|
"read-only @sortedset",
|
||||||
0,NULL,1,1,1,0,0,0},
|
0,NULL,1,1,1,0,0,0},
|
||||||
@ -685,7 +701,7 @@ struct redisCommand redisCommandTable[] = {
|
|||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"echo",echoCommand,2,
|
{"echo",echoCommand,2,
|
||||||
"read-only fast @connection",
|
"fast @connection",
|
||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"save",saveCommand,1,
|
{"save",saveCommand,1,
|
||||||
@ -705,7 +721,7 @@ struct redisCommand redisCommandTable[] = {
|
|||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"lastsave",lastsaveCommand,1,
|
{"lastsave",lastsaveCommand,1,
|
||||||
"read-only random fast ok-loading ok-stale @admin @dangerous",
|
"random fast ok-loading ok-stale @admin @dangerous",
|
||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"type",typeCommand,2,
|
{"type",typeCommand,2,
|
||||||
@ -781,7 +797,7 @@ struct redisCommand redisCommandTable[] = {
|
|||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"role",roleCommand,1,
|
{"role",roleCommand,1,
|
||||||
"ok-loading ok-stale no-script fast read-only @dangerous",
|
"ok-loading ok-stale no-script fast @dangerous",
|
||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"debug",debugCommand,-2,
|
{"debug",debugCommand,-2,
|
||||||
@ -809,7 +825,7 @@ struct redisCommand redisCommandTable[] = {
|
|||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"publish",publishCommand,3,
|
{"publish",publishCommand,3,
|
||||||
"pub-sub ok-loading ok-stale fast",
|
"pub-sub ok-loading ok-stale fast may-replicate",
|
||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"pubsub",pubsubCommand,-2,
|
{"pubsub",pubsubCommand,-2,
|
||||||
@ -868,18 +884,18 @@ struct redisCommand redisCommandTable[] = {
|
|||||||
"admin no-script random ok-loading ok-stale @connection",
|
"admin no-script random ok-loading ok-stale @connection",
|
||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"hello",helloCommand,-2,
|
{"hello",helloCommand,-1,
|
||||||
"no-auth no-script fast no-monitor ok-loading ok-stale no-slowlog @connection",
|
"no-auth no-script fast no-monitor ok-loading ok-stale no-slowlog @connection",
|
||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
/* EVAL can modify the dataset, however it is not flagged as a write
|
/* EVAL can modify the dataset, however it is not flagged as a write
|
||||||
* command since we do the check while running commands from Lua. */
|
* command since we do the check while running commands from Lua. */
|
||||||
{"eval",evalCommand,-3,
|
{"eval",evalCommand,-3,
|
||||||
"no-script @scripting",
|
"no-script may-replicate @scripting",
|
||||||
0,evalGetKeys,0,0,0,0,0,0},
|
0,evalGetKeys,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"evalsha",evalShaCommand,-3,
|
{"evalsha",evalShaCommand,-3,
|
||||||
"no-script @scripting",
|
"no-script may-replicate @scripting",
|
||||||
0,evalGetKeys,0,0,0,0,0,0},
|
0,evalGetKeys,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"slowlog",slowlogCommand,-2,
|
{"slowlog",slowlogCommand,-2,
|
||||||
@ -887,11 +903,11 @@ struct redisCommand redisCommandTable[] = {
|
|||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"script",scriptCommand,-2,
|
{"script",scriptCommand,-2,
|
||||||
"no-script @scripting",
|
"no-script may-replicate @scripting",
|
||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"time",timeCommand,1,
|
{"time",timeCommand,1,
|
||||||
"read-only random fast ok-loading ok-stale",
|
"random fast ok-loading ok-stale",
|
||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"bitop",bitopCommand,-4,
|
{"bitop",bitopCommand,-4,
|
||||||
@ -968,16 +984,19 @@ struct redisCommand redisCommandTable[] = {
|
|||||||
* we claim that the representation, even if accessible, is an internal
|
* we claim that the representation, even if accessible, is an internal
|
||||||
* affair, and the command is semantically read only. */
|
* affair, and the command is semantically read only. */
|
||||||
{"pfcount",pfcountCommand,-2,
|
{"pfcount",pfcountCommand,-2,
|
||||||
"read-only @hyperloglog",
|
"read-only may-replicate @hyperloglog",
|
||||||
0,NULL,1,-1,1,0,0,0},
|
0,NULL,1,-1,1,0,0,0},
|
||||||
|
|
||||||
{"pfmerge",pfmergeCommand,-2,
|
{"pfmerge",pfmergeCommand,-2,
|
||||||
"write use-memory @hyperloglog",
|
"write use-memory @hyperloglog",
|
||||||
0,NULL,1,-1,1,0,0,0},
|
0,NULL,1,-1,1,0,0,0},
|
||||||
|
|
||||||
|
/* Unlike PFCOUNT that is considered as a read-only command (although
|
||||||
|
* it changes a bit), PFDEBUG may change the entire key when converting
|
||||||
|
* from sparse to dense representation */
|
||||||
{"pfdebug",pfdebugCommand,-3,
|
{"pfdebug",pfdebugCommand,-3,
|
||||||
"admin write",
|
"admin write use-memory @hyperloglog",
|
||||||
0,NULL,0,0,0,0,0,0},
|
0,NULL,2,2,1,0,0,0},
|
||||||
|
|
||||||
{"xadd",xaddCommand,-5,
|
{"xadd",xaddCommand,-5,
|
||||||
"write use-memory fast random @stream",
|
"write use-memory fast random @stream",
|
||||||
@ -1023,6 +1042,10 @@ struct redisCommand redisCommandTable[] = {
|
|||||||
"write random fast @stream",
|
"write random fast @stream",
|
||||||
0,NULL,1,1,1,0,0,0},
|
0,NULL,1,1,1,0,0,0},
|
||||||
|
|
||||||
|
{"xautoclaim",xautoclaimCommand,-6,
|
||||||
|
"write random fast @stream",
|
||||||
|
0,NULL,1,1,1,0,0,0},
|
||||||
|
|
||||||
{"xinfo",xinfoCommand,-2,
|
{"xinfo",xinfoCommand,-2,
|
||||||
"read-only random @stream",
|
"read-only random @stream",
|
||||||
0,NULL,2,2,1,0,0,0},
|
0,NULL,2,2,1,0,0,0},
|
||||||
@ -1059,7 +1082,7 @@ struct redisCommand redisCommandTable[] = {
|
|||||||
"read-only @string",
|
"read-only @string",
|
||||||
0,lcsGetKeys,0,0,0,0,0,0},
|
0,lcsGetKeys,0,0,0,0,0,0},
|
||||||
|
|
||||||
{"reset",resetCommand,-1,
|
{"reset",resetCommand,1,
|
||||||
"no-script ok-stale ok-loading fast @connection",
|
"no-script ok-stale ok-loading fast @connection",
|
||||||
0,NULL,0,0,0,0,0,0}
|
0,NULL,0,0,0,0,0,0}
|
||||||
};
|
};
|
||||||
@ -1547,12 +1570,33 @@ void updateDictResizePolicy(void) {
|
|||||||
dictDisableResize();
|
dictDisableResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *strChildType(int type) {
|
||||||
|
switch(type) {
|
||||||
|
case CHILD_TYPE_RDB: return "RDB";
|
||||||
|
case CHILD_TYPE_AOF: return "AOF";
|
||||||
|
case CHILD_TYPE_LDB: return "LDB";
|
||||||
|
case CHILD_TYPE_MODULE: return "MODULE";
|
||||||
|
default: return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Return true if there are active children processes doing RDB saving,
|
/* Return true if there are active children processes doing RDB saving,
|
||||||
* AOF rewriting, or some side process spawned by a loaded module. */
|
* AOF rewriting, or some side process spawned by a loaded module. */
|
||||||
int hasActiveChildProcess() {
|
int hasActiveChildProcess() {
|
||||||
return server.rdb_child_pid != -1 ||
|
return server.child_pid != -1;
|
||||||
server.aof_child_pid != -1 ||
|
}
|
||||||
server.module_child_pid != -1;
|
|
||||||
|
void resetChildState() {
|
||||||
|
server.child_type = CHILD_TYPE_NONE;
|
||||||
|
server.child_pid = -1;
|
||||||
|
server.stat_current_cow_bytes = 0;
|
||||||
|
updateDictResizePolicy();
|
||||||
|
closeChildInfoPipe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return if child type is mutual exclusive with other fork children */
|
||||||
|
int isMutuallyExclusiveChildType(int type) {
|
||||||
|
return type == CHILD_TYPE_RDB || type == CHILD_TYPE_AOF || type == CHILD_TYPE_MODULE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return true if this instance has persistence completely turned off:
|
/* Return true if this instance has persistence completely turned off:
|
||||||
@ -1874,29 +1918,30 @@ void checkChildrenDone(void) {
|
|||||||
|
|
||||||
if (pid == -1) {
|
if (pid == -1) {
|
||||||
serverLog(LL_WARNING,"wait3() returned an error: %s. "
|
serverLog(LL_WARNING,"wait3() returned an error: %s. "
|
||||||
"rdb_child_pid = %d, aof_child_pid = %d, module_child_pid = %d",
|
"child_type: %s, child_pid = %d",
|
||||||
strerror(errno),
|
strerror(errno),
|
||||||
(int) server.rdb_child_pid,
|
strChildType(server.child_type),
|
||||||
(int) server.aof_child_pid,
|
(int) server.child_pid);
|
||||||
(int) server.module_child_pid);
|
} else if (pid == server.child_pid) {
|
||||||
} else if (pid == server.rdb_child_pid) {
|
if (server.child_type == CHILD_TYPE_RDB) {
|
||||||
backgroundSaveDoneHandler(exitcode,bysignal);
|
backgroundSaveDoneHandler(exitcode, bysignal);
|
||||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
} else if (server.child_type == CHILD_TYPE_AOF) {
|
||||||
} else if (pid == server.aof_child_pid) {
|
backgroundRewriteDoneHandler(exitcode, bysignal);
|
||||||
backgroundRewriteDoneHandler(exitcode,bysignal);
|
} else if (server.child_type == CHILD_TYPE_MODULE) {
|
||||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
ModuleForkDoneHandler(exitcode, bysignal);
|
||||||
} else if (pid == server.module_child_pid) {
|
} else {
|
||||||
ModuleForkDoneHandler(exitcode,bysignal);
|
serverPanic("Unknown child type %d for child pid %d", server.child_type, server.child_pid);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||||
|
resetChildState();
|
||||||
} else {
|
} else {
|
||||||
if (!ldbRemoveChild(pid)) {
|
if (!ldbRemoveChild(pid)) {
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Warning, detected child with unmatched pid: %ld",
|
"Warning, detected child with unmatched pid: %ld",
|
||||||
(long)pid);
|
(long) pid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateDictResizePolicy();
|
|
||||||
closeChildInfoPipe();
|
|
||||||
|
|
||||||
/* start any pending forks immediately. */
|
/* start any pending forks immediately. */
|
||||||
replicationStartPendingFork();
|
replicationStartPendingFork();
|
||||||
@ -2065,6 +2110,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
/* Check if a background saving or AOF rewrite in progress terminated. */
|
/* Check if a background saving or AOF rewrite in progress terminated. */
|
||||||
if (hasActiveChildProcess() || ldbPendingChildren())
|
if (hasActiveChildProcess() || ldbPendingChildren())
|
||||||
{
|
{
|
||||||
|
run_with_period(1000) receiveChildInfo();
|
||||||
checkChildrenDone();
|
checkChildrenDone();
|
||||||
} else {
|
} else {
|
||||||
/* If there is not a background saving/rewrite in progress check if
|
/* If there is not a background saving/rewrite in progress check if
|
||||||
@ -2124,8 +2170,8 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
flushAppendOnlyFile(0);
|
flushAppendOnlyFile(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clear the paused clients flag if needed. */
|
/* Clear the paused clients state if needed. */
|
||||||
clientsArePaused(); /* Don't check return value, just use the side effect.*/
|
checkClientPauseTimeoutAndReturnIfPaused();
|
||||||
|
|
||||||
/* Replication cron function -- used to reconnect to master,
|
/* Replication cron function -- used to reconnect to master,
|
||||||
* detect transfer failures, start background RDB transfers and so forth. */
|
* detect transfer failures, start background RDB transfers and so forth. */
|
||||||
@ -2183,12 +2229,16 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
|
|
||||||
|
|
||||||
void blockingOperationStarts() {
|
void blockingOperationStarts() {
|
||||||
updateCachedTime(0);
|
if(!server.blocking_op_nesting++){
|
||||||
server.blocked_last_cron = server.mstime;
|
updateCachedTime(0);
|
||||||
|
server.blocked_last_cron = server.mstime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void blockingOperationEnds() {
|
void blockingOperationEnds() {
|
||||||
server.blocked_last_cron = 0;
|
if(!(--server.blocking_op_nesting)){
|
||||||
|
server.blocked_last_cron = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function fill in the role of serverCron during RDB or AOF loading, and
|
/* This function fill in the role of serverCron during RDB or AOF loading, and
|
||||||
@ -2225,6 +2275,7 @@ void whileBlockedCron() {
|
|||||||
activeDefragCycle();
|
activeDefragCycle();
|
||||||
|
|
||||||
server.blocked_last_cron += hz_ms;
|
server.blocked_last_cron += hz_ms;
|
||||||
|
|
||||||
/* Increment cronloop so that run_with_period works. */
|
/* Increment cronloop so that run_with_period works. */
|
||||||
server.cronloops++;
|
server.cronloops++;
|
||||||
}
|
}
|
||||||
@ -2317,8 +2368,12 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
|||||||
* during the previous event loop iteration. Note that we do this after
|
* during the previous event loop iteration. Note that we do this after
|
||||||
* processUnblockedClients(), so if there are multiple pipelined WAITs
|
* processUnblockedClients(), so if there are multiple pipelined WAITs
|
||||||
* and the just unblocked WAIT gets blocked again, we don't have to wait
|
* and the just unblocked WAIT gets blocked again, we don't have to wait
|
||||||
* a server cron cycle in absence of other event loop events. See #6623. */
|
* a server cron cycle in absence of other event loop events. See #6623.
|
||||||
if (server.get_ack_from_slaves) {
|
*
|
||||||
|
* We also don't send the ACKs while clients are paused, since it can
|
||||||
|
* increment the replication backlog, they'll be sent after the pause
|
||||||
|
* if we are still the master. */
|
||||||
|
if (server.get_ack_from_slaves && !checkClientPauseTimeoutAndReturnIfPaused()) {
|
||||||
robj *argv[3];
|
robj *argv[3];
|
||||||
|
|
||||||
argv[0] = createStringObject("REPLCONF",8);
|
argv[0] = createStringObject("REPLCONF",8);
|
||||||
@ -2376,9 +2431,9 @@ void afterSleep(struct aeEventLoop *eventLoop) {
|
|||||||
void createSharedObjects(void) {
|
void createSharedObjects(void) {
|
||||||
int j;
|
int j;
|
||||||
|
|
||||||
|
/* Shared command responses */
|
||||||
shared.crlf = createObject(OBJ_STRING,sdsnew("\r\n"));
|
shared.crlf = createObject(OBJ_STRING,sdsnew("\r\n"));
|
||||||
shared.ok = createObject(OBJ_STRING,sdsnew("+OK\r\n"));
|
shared.ok = createObject(OBJ_STRING,sdsnew("+OK\r\n"));
|
||||||
shared.err = createObject(OBJ_STRING,sdsnew("-ERR\r\n"));
|
|
||||||
shared.emptybulk = createObject(OBJ_STRING,sdsnew("$0\r\n\r\n"));
|
shared.emptybulk = createObject(OBJ_STRING,sdsnew("$0\r\n\r\n"));
|
||||||
shared.czero = createObject(OBJ_STRING,sdsnew(":0\r\n"));
|
shared.czero = createObject(OBJ_STRING,sdsnew(":0\r\n"));
|
||||||
shared.cone = createObject(OBJ_STRING,sdsnew(":1\r\n"));
|
shared.cone = createObject(OBJ_STRING,sdsnew(":1\r\n"));
|
||||||
@ -2386,8 +2441,14 @@ void createSharedObjects(void) {
|
|||||||
shared.pong = createObject(OBJ_STRING,sdsnew("+PONG\r\n"));
|
shared.pong = createObject(OBJ_STRING,sdsnew("+PONG\r\n"));
|
||||||
shared.queued = createObject(OBJ_STRING,sdsnew("+QUEUED\r\n"));
|
shared.queued = createObject(OBJ_STRING,sdsnew("+QUEUED\r\n"));
|
||||||
shared.emptyscan = createObject(OBJ_STRING,sdsnew("*2\r\n$1\r\n0\r\n*0\r\n"));
|
shared.emptyscan = createObject(OBJ_STRING,sdsnew("*2\r\n$1\r\n0\r\n*0\r\n"));
|
||||||
|
shared.space = createObject(OBJ_STRING,sdsnew(" "));
|
||||||
|
shared.colon = createObject(OBJ_STRING,sdsnew(":"));
|
||||||
|
shared.plus = createObject(OBJ_STRING,sdsnew("+"));
|
||||||
|
|
||||||
|
/* Shared command error responses */
|
||||||
shared.wrongtypeerr = createObject(OBJ_STRING,sdsnew(
|
shared.wrongtypeerr = createObject(OBJ_STRING,sdsnew(
|
||||||
"-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"));
|
"-WRONGTYPE Operation against a key holding the wrong kind of value\r\n"));
|
||||||
|
shared.err = createObject(OBJ_STRING,sdsnew("-ERR\r\n"));
|
||||||
shared.nokeyerr = createObject(OBJ_STRING,sdsnew(
|
shared.nokeyerr = createObject(OBJ_STRING,sdsnew(
|
||||||
"-ERR no such key\r\n"));
|
"-ERR no such key\r\n"));
|
||||||
shared.syntaxerr = createObject(OBJ_STRING,sdsnew(
|
shared.syntaxerr = createObject(OBJ_STRING,sdsnew(
|
||||||
@ -2418,9 +2479,6 @@ void createSharedObjects(void) {
|
|||||||
"-NOREPLICAS Not enough good replicas to write.\r\n"));
|
"-NOREPLICAS Not enough good replicas to write.\r\n"));
|
||||||
shared.busykeyerr = createObject(OBJ_STRING,sdsnew(
|
shared.busykeyerr = createObject(OBJ_STRING,sdsnew(
|
||||||
"-BUSYKEY Target key name already exists.\r\n"));
|
"-BUSYKEY Target key name already exists.\r\n"));
|
||||||
shared.space = createObject(OBJ_STRING,sdsnew(" "));
|
|
||||||
shared.colon = createObject(OBJ_STRING,sdsnew(":"));
|
|
||||||
shared.plus = createObject(OBJ_STRING,sdsnew("+"));
|
|
||||||
|
|
||||||
/* The shared NULL depends on the protocol version. */
|
/* The shared NULL depends on the protocol version. */
|
||||||
shared.null[0] = NULL;
|
shared.null[0] = NULL;
|
||||||
@ -2945,9 +3003,9 @@ void resetServerStats(void) {
|
|||||||
atomicSet(server.stat_net_input_bytes, 0);
|
atomicSet(server.stat_net_input_bytes, 0);
|
||||||
atomicSet(server.stat_net_output_bytes, 0);
|
atomicSet(server.stat_net_output_bytes, 0);
|
||||||
server.stat_unexpected_error_replies = 0;
|
server.stat_unexpected_error_replies = 0;
|
||||||
|
server.stat_total_error_replies = 0;
|
||||||
server.stat_dump_payload_sanitizations = 0;
|
server.stat_dump_payload_sanitizations = 0;
|
||||||
server.aof_delayed_fsync = 0;
|
server.aof_delayed_fsync = 0;
|
||||||
server.blocked_last_cron = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make the thread killable at any time, so that kill threads functions
|
/* Make the thread killable at any time, so that kill threads functions
|
||||||
@ -2978,6 +3036,7 @@ void initServer(void) {
|
|||||||
server.in_fork_child = CHILD_TYPE_NONE;
|
server.in_fork_child = CHILD_TYPE_NONE;
|
||||||
server.main_thread_id = pthread_self();
|
server.main_thread_id = pthread_self();
|
||||||
server.current_client = NULL;
|
server.current_client = NULL;
|
||||||
|
server.errors = raxNew();
|
||||||
server.fixed_time_expire = 0;
|
server.fixed_time_expire = 0;
|
||||||
server.clients = listCreate();
|
server.clients = listCreate();
|
||||||
server.clients_index = raxNew();
|
server.clients_index = raxNew();
|
||||||
@ -2992,9 +3051,12 @@ void initServer(void) {
|
|||||||
server.ready_keys = listCreate();
|
server.ready_keys = listCreate();
|
||||||
server.clients_waiting_acks = listCreate();
|
server.clients_waiting_acks = listCreate();
|
||||||
server.get_ack_from_slaves = 0;
|
server.get_ack_from_slaves = 0;
|
||||||
server.clients_paused = 0;
|
server.client_pause_type = 0;
|
||||||
|
server.paused_clients = listCreate();
|
||||||
server.events_processed_while_blocked = 0;
|
server.events_processed_while_blocked = 0;
|
||||||
server.system_memory_size = zmalloc_get_memory_size();
|
server.system_memory_size = zmalloc_get_memory_size();
|
||||||
|
server.blocked_last_cron = 0;
|
||||||
|
server.blocking_op_nesting = 0;
|
||||||
|
|
||||||
if ((server.tls_port || server.tls_replication || server.tls_cluster)
|
if ((server.tls_port || server.tls_replication || server.tls_cluster)
|
||||||
&& tlsConfigure(&server.tls_ctx_config) == C_ERR) {
|
&& tlsConfigure(&server.tls_ctx_config) == C_ERR) {
|
||||||
@ -3064,9 +3126,9 @@ void initServer(void) {
|
|||||||
server.in_eval = 0;
|
server.in_eval = 0;
|
||||||
server.in_exec = 0;
|
server.in_exec = 0;
|
||||||
server.propagate_in_transaction = 0;
|
server.propagate_in_transaction = 0;
|
||||||
server.rdb_child_pid = -1;
|
server.client_pause_in_transaction = 0;
|
||||||
server.aof_child_pid = -1;
|
server.child_pid = -1;
|
||||||
server.module_child_pid = -1;
|
server.child_type = CHILD_TYPE_NONE;
|
||||||
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||||
server.rdb_pipe_conns = NULL;
|
server.rdb_pipe_conns = NULL;
|
||||||
server.rdb_pipe_numconns = 0;
|
server.rdb_pipe_numconns = 0;
|
||||||
@ -3076,7 +3138,7 @@ void initServer(void) {
|
|||||||
server.rdb_bgsave_scheduled = 0;
|
server.rdb_bgsave_scheduled = 0;
|
||||||
server.child_info_pipe[0] = -1;
|
server.child_info_pipe[0] = -1;
|
||||||
server.child_info_pipe[1] = -1;
|
server.child_info_pipe[1] = -1;
|
||||||
server.child_info_data.magic = 0;
|
server.child_info_nread = 0;
|
||||||
aofRewriteBufferReset();
|
aofRewriteBufferReset();
|
||||||
server.aof_buf = sdsempty();
|
server.aof_buf = sdsempty();
|
||||||
server.lastsave = time(NULL); /* At startup we consider the DB saved. */
|
server.lastsave = time(NULL); /* At startup we consider the DB saved. */
|
||||||
@ -3088,6 +3150,7 @@ void initServer(void) {
|
|||||||
/* A few stats we don't want to reset: server startup time, and peak mem. */
|
/* A few stats we don't want to reset: server startup time, and peak mem. */
|
||||||
server.stat_starttime = time(NULL);
|
server.stat_starttime = time(NULL);
|
||||||
server.stat_peak_memory = 0;
|
server.stat_peak_memory = 0;
|
||||||
|
server.stat_current_cow_bytes = 0;
|
||||||
server.stat_rdb_cow_bytes = 0;
|
server.stat_rdb_cow_bytes = 0;
|
||||||
server.stat_aof_cow_bytes = 0;
|
server.stat_aof_cow_bytes = 0;
|
||||||
server.stat_module_cow_bytes = 0;
|
server.stat_module_cow_bytes = 0;
|
||||||
@ -3230,6 +3293,8 @@ int populateCommandTableParseFlags(struct redisCommand *c, char *strflags) {
|
|||||||
c->flags |= CMD_FAST | CMD_CATEGORY_FAST;
|
c->flags |= CMD_FAST | CMD_CATEGORY_FAST;
|
||||||
} else if (!strcasecmp(flag,"no-auth")) {
|
} else if (!strcasecmp(flag,"no-auth")) {
|
||||||
c->flags |= CMD_NO_AUTH;
|
c->flags |= CMD_NO_AUTH;
|
||||||
|
} else if (!strcasecmp(flag,"may-replicate")) {
|
||||||
|
c->flags |= CMD_MAY_REPLICATE;
|
||||||
} else {
|
} else {
|
||||||
/* Parse ACL categories here if the flag name starts with @. */
|
/* Parse ACL categories here if the flag name starts with @. */
|
||||||
uint64_t catflag;
|
uint64_t catflag;
|
||||||
@ -3284,11 +3349,18 @@ void resetCommandTableStats(void) {
|
|||||||
c = (struct redisCommand *) dictGetVal(de);
|
c = (struct redisCommand *) dictGetVal(de);
|
||||||
c->microseconds = 0;
|
c->microseconds = 0;
|
||||||
c->calls = 0;
|
c->calls = 0;
|
||||||
|
c->rejected_calls = 0;
|
||||||
|
c->failed_calls = 0;
|
||||||
}
|
}
|
||||||
dictReleaseIterator(di);
|
dictReleaseIterator(di);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void resetErrorTableStats(void) {
|
||||||
|
raxFreeWithCallback(server.errors, zfree);
|
||||||
|
server.errors = raxNew();
|
||||||
|
}
|
||||||
|
|
||||||
/* ========================== Redis OP Array API ============================ */
|
/* ========================== Redis OP Array API ============================ */
|
||||||
|
|
||||||
void redisOpArrayInit(redisOpArray *oa) {
|
void redisOpArrayInit(redisOpArray *oa) {
|
||||||
@ -3374,6 +3446,18 @@ struct redisCommand *lookupCommandOrOriginal(sds name) {
|
|||||||
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
|
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
|
||||||
int flags)
|
int flags)
|
||||||
{
|
{
|
||||||
|
/* Propagate a MULTI request once we encounter the first command which
|
||||||
|
* is a write command.
|
||||||
|
* This way we'll deliver the MULTI/..../EXEC block as a whole and
|
||||||
|
* both the AOF and the replication link will have the same consistency
|
||||||
|
* and atomicity guarantees. */
|
||||||
|
if (server.in_exec && !server.propagate_in_transaction)
|
||||||
|
execCommandPropagateMulti(dbid);
|
||||||
|
|
||||||
|
/* This needs to be unreachable since the dataset should be fixed during
|
||||||
|
* client pause, otherwise data may be lossed during a failover. */
|
||||||
|
serverAssert(!(areClientsPaused() && !server.client_pause_in_transaction));
|
||||||
|
|
||||||
if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF)
|
if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF)
|
||||||
feedAppendOnlyFile(cmd,dbid,argv,argc);
|
feedAppendOnlyFile(cmd,dbid,argv,argc);
|
||||||
if (flags & PROPAGATE_REPL)
|
if (flags & PROPAGATE_REPL)
|
||||||
@ -3412,6 +3496,7 @@ void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
|
|||||||
* Redis command implementation in order to to force the propagation of a
|
* Redis command implementation in order to to force the propagation of a
|
||||||
* specific command execution into AOF / Replication. */
|
* specific command execution into AOF / Replication. */
|
||||||
void forceCommandPropagation(client *c, int flags) {
|
void forceCommandPropagation(client *c, int flags) {
|
||||||
|
serverAssert(c->cmd->flags & (CMD_WRITE | CMD_MAY_REPLICATE));
|
||||||
if (flags & PROPAGATE_REPL) c->flags |= CLIENT_FORCE_REPL;
|
if (flags & PROPAGATE_REPL) c->flags |= CLIENT_FORCE_REPL;
|
||||||
if (flags & PROPAGATE_AOF) c->flags |= CLIENT_FORCE_AOF;
|
if (flags & PROPAGATE_AOF) c->flags |= CLIENT_FORCE_AOF;
|
||||||
}
|
}
|
||||||
@ -3475,6 +3560,7 @@ void call(client *c, int flags) {
|
|||||||
ustime_t start, duration;
|
ustime_t start, duration;
|
||||||
int client_old_flags = c->flags;
|
int client_old_flags = c->flags;
|
||||||
struct redisCommand *real_cmd = c->cmd;
|
struct redisCommand *real_cmd = c->cmd;
|
||||||
|
static long long prev_err_count;
|
||||||
|
|
||||||
server.fixed_time_expire++;
|
server.fixed_time_expire++;
|
||||||
|
|
||||||
@ -3495,6 +3581,7 @@ void call(client *c, int flags) {
|
|||||||
|
|
||||||
/* Call the command. */
|
/* Call the command. */
|
||||||
dirty = server.dirty;
|
dirty = server.dirty;
|
||||||
|
prev_err_count = server.stat_total_error_replies;
|
||||||
updateCachedTime(0);
|
updateCachedTime(0);
|
||||||
start = server.ustime;
|
start = server.ustime;
|
||||||
c->cmd->proc(c);
|
c->cmd->proc(c);
|
||||||
@ -3502,6 +3589,14 @@ void call(client *c, int flags) {
|
|||||||
dirty = server.dirty-dirty;
|
dirty = server.dirty-dirty;
|
||||||
if (dirty < 0) dirty = 0;
|
if (dirty < 0) dirty = 0;
|
||||||
|
|
||||||
|
/* Update failed command calls if required.
|
||||||
|
* We leverage a static variable (prev_err_count) to retain
|
||||||
|
* the counter across nested function calls and avoid logging
|
||||||
|
* the same error twice. */
|
||||||
|
if ((server.stat_total_error_replies - prev_err_count) > 0) {
|
||||||
|
real_cmd->failed_calls++;
|
||||||
|
}
|
||||||
|
|
||||||
/* After executing command, we will close the client after writing entire
|
/* After executing command, we will close the client after writing entire
|
||||||
* reply if it is set 'CLIENT_CLOSE_AFTER_COMMAND' flag. */
|
* reply if it is set 'CLIENT_CLOSE_AFTER_COMMAND' flag. */
|
||||||
if (c->flags & CLIENT_CLOSE_AFTER_COMMAND) {
|
if (c->flags & CLIENT_CLOSE_AFTER_COMMAND) {
|
||||||
@ -3604,7 +3699,7 @@ void call(client *c, int flags) {
|
|||||||
!(c->flags & CLIENT_MULTI) &&
|
!(c->flags & CLIENT_MULTI) &&
|
||||||
!(flags & CMD_CALL_NOWRAP))
|
!(flags & CMD_CALL_NOWRAP))
|
||||||
{
|
{
|
||||||
execCommandPropagateMulti(c);
|
execCommandPropagateMulti(c->db->id);
|
||||||
multi_emitted = 1;
|
multi_emitted = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3619,13 +3714,19 @@ void call(client *c, int flags) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (multi_emitted) {
|
if (multi_emitted) {
|
||||||
execCommandPropagateExec(c);
|
execCommandPropagateExec(c->db->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
redisOpArrayFree(&server.also_propagate);
|
redisOpArrayFree(&server.also_propagate);
|
||||||
}
|
}
|
||||||
server.also_propagate = prev_also_propagate;
|
server.also_propagate = prev_also_propagate;
|
||||||
|
|
||||||
|
/* Client pause takes effect after a transaction has finished. This needs
|
||||||
|
* to be located after everything is propagated. */
|
||||||
|
if (!server.in_exec && server.client_pause_in_transaction) {
|
||||||
|
server.client_pause_in_transaction = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* If the client has keys tracking enabled for client side caching,
|
/* If the client has keys tracking enabled for client side caching,
|
||||||
* make sure to remember the keys it fetched via this command. */
|
* make sure to remember the keys it fetched via this command. */
|
||||||
if (c->cmd->flags & CMD_READONLY) {
|
if (c->cmd->flags & CMD_READONLY) {
|
||||||
@ -3640,6 +3741,7 @@ void call(client *c, int flags) {
|
|||||||
|
|
||||||
server.fixed_time_expire--;
|
server.fixed_time_expire--;
|
||||||
server.stat_numcommands++;
|
server.stat_numcommands++;
|
||||||
|
prev_err_count = server.stat_total_error_replies;
|
||||||
|
|
||||||
/* Record peak memory after each command and before the eviction that runs
|
/* Record peak memory after each command and before the eviction that runs
|
||||||
* before the next command. */
|
* before the next command. */
|
||||||
@ -3655,6 +3757,7 @@ void call(client *c, int flags) {
|
|||||||
* Note: 'reply' is expected to end with \r\n */
|
* Note: 'reply' is expected to end with \r\n */
|
||||||
void rejectCommand(client *c, robj *reply) {
|
void rejectCommand(client *c, robj *reply) {
|
||||||
flagTransaction(c);
|
flagTransaction(c);
|
||||||
|
if (c->cmd) c->cmd->rejected_calls++;
|
||||||
if (c->cmd && c->cmd->proc == execCommand) {
|
if (c->cmd && c->cmd->proc == execCommand) {
|
||||||
execCommandAbort(c, reply->ptr);
|
execCommandAbort(c, reply->ptr);
|
||||||
} else {
|
} else {
|
||||||
@ -3664,6 +3767,7 @@ void rejectCommand(client *c, robj *reply) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void rejectCommandFormat(client *c, const char *fmt, ...) {
|
void rejectCommandFormat(client *c, const char *fmt, ...) {
|
||||||
|
if (c->cmd) c->cmd->rejected_calls++;
|
||||||
flagTransaction(c);
|
flagTransaction(c);
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap,fmt);
|
va_start(ap,fmt);
|
||||||
@ -3674,10 +3778,11 @@ void rejectCommandFormat(client *c, const char *fmt, ...) {
|
|||||||
sdsmapchars(s, "\r\n", " ", 2);
|
sdsmapchars(s, "\r\n", " ", 2);
|
||||||
if (c->cmd && c->cmd->proc == execCommand) {
|
if (c->cmd && c->cmd->proc == execCommand) {
|
||||||
execCommandAbort(c, s);
|
execCommandAbort(c, s);
|
||||||
|
sdsfree(s);
|
||||||
} else {
|
} else {
|
||||||
|
/* The following frees 's'. */
|
||||||
addReplyErrorSds(c, s);
|
addReplyErrorSds(c, s);
|
||||||
}
|
}
|
||||||
sdsfree(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Returns 1 for commands that may have key names in their arguments, but have
|
/* Returns 1 for commands that may have key names in their arguments, but have
|
||||||
@ -3735,6 +3840,8 @@ int processCommand(client *c) {
|
|||||||
(c->cmd->proc == execCommand && (c->mstate.cmd_inv_flags & CMD_STALE));
|
(c->cmd->proc == execCommand && (c->mstate.cmd_inv_flags & CMD_STALE));
|
||||||
int is_denyloading_command = !(c->cmd->flags & CMD_LOADING) ||
|
int is_denyloading_command = !(c->cmd->flags & CMD_LOADING) ||
|
||||||
(c->cmd->proc == execCommand && (c->mstate.cmd_inv_flags & CMD_LOADING));
|
(c->cmd->proc == execCommand && (c->mstate.cmd_inv_flags & CMD_LOADING));
|
||||||
|
int is_may_replicate_command = (c->cmd->flags & (CMD_WRITE | CMD_MAY_REPLICATE)) ||
|
||||||
|
(c->cmd->proc == execCommand && (c->mstate.cmd_flags & (CMD_WRITE | CMD_MAY_REPLICATE)));
|
||||||
|
|
||||||
/* Check if the user is authenticated. This check is skipped in case
|
/* Check if the user is authenticated. This check is skipped in case
|
||||||
* the default user is flagged as "nopass" and is active. */
|
* the default user is flagged as "nopass" and is active. */
|
||||||
@ -3789,6 +3896,7 @@ int processCommand(client *c) {
|
|||||||
flagTransaction(c);
|
flagTransaction(c);
|
||||||
}
|
}
|
||||||
clusterRedirectClient(c,n,hashslot,error_code);
|
clusterRedirectClient(c,n,hashslot,error_code);
|
||||||
|
c->cmd->rejected_calls++;
|
||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3932,6 +4040,17 @@ int processCommand(client *c) {
|
|||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If the server is paused, block the client until
|
||||||
|
* the pause has ended. Replicas are never paused. */
|
||||||
|
if (!(c->flags & CLIENT_SLAVE) &&
|
||||||
|
((server.client_pause_type == CLIENT_PAUSE_ALL) ||
|
||||||
|
(server.client_pause_type == CLIENT_PAUSE_WRITE && is_may_replicate_command)))
|
||||||
|
{
|
||||||
|
c->bpop.timeout = 0;
|
||||||
|
blockClient(c,BLOCKED_PAUSE);
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* Exec the command */
|
/* Exec the command */
|
||||||
if (c->flags & CLIENT_MULTI &&
|
if (c->flags & CLIENT_MULTI &&
|
||||||
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
|
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
|
||||||
@ -3946,9 +4065,22 @@ int processCommand(client *c) {
|
|||||||
if (listLength(server.ready_keys))
|
if (listLength(server.ready_keys))
|
||||||
handleClientsBlockedOnKeys();
|
handleClientsBlockedOnKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ====================== Error lookup and execution ===================== */
|
||||||
|
|
||||||
|
void incrementErrorCount(const char *fullerr, size_t namelen) {
|
||||||
|
struct redisError *error = raxFind(server.errors,(unsigned char*)fullerr,namelen);
|
||||||
|
if (error == raxNotFound) {
|
||||||
|
error = zmalloc(sizeof(*error));
|
||||||
|
error->count = 0;
|
||||||
|
raxInsert(server.errors,(unsigned char*)fullerr,namelen,error,NULL);
|
||||||
|
}
|
||||||
|
error->count++;
|
||||||
|
}
|
||||||
|
|
||||||
/*================================== Shutdown =============================== */
|
/*================================== Shutdown =============================== */
|
||||||
|
|
||||||
/* Close listening sockets. Also unlink the unix domain socket if
|
/* Close listening sockets. Also unlink the unix domain socket if
|
||||||
@ -3990,25 +4122,28 @@ int prepareForShutdown(int flags) {
|
|||||||
/* Kill the saving child if there is a background saving in progress.
|
/* Kill the saving child if there is a background saving in progress.
|
||||||
We want to avoid race conditions, for instance our saving child may
|
We want to avoid race conditions, for instance our saving child may
|
||||||
overwrite the synchronous saving did by SHUTDOWN. */
|
overwrite the synchronous saving did by SHUTDOWN. */
|
||||||
if (server.rdb_child_pid != -1) {
|
if (server.child_type == CHILD_TYPE_RDB) {
|
||||||
serverLog(LL_WARNING,"There is a child saving an .rdb. Killing it!");
|
serverLog(LL_WARNING,"There is a child saving an .rdb. Killing it!");
|
||||||
/* Note that, in killRDBChild, we call rdbRemoveTempFile that will
|
killRDBChild();
|
||||||
* do close fd(in order to unlink file actully) in background thread.
|
/* Note that, in killRDBChild normally has backgroundSaveDoneHandler
|
||||||
|
* doing it's cleanup, but in this case this code will not be reached,
|
||||||
|
* so we need to call rdbRemoveTempFile which will close fd(in order
|
||||||
|
* to unlink file actully) in background thread.
|
||||||
* The temp rdb file fd may won't be closed when redis exits quickly,
|
* The temp rdb file fd may won't be closed when redis exits quickly,
|
||||||
* but OS will close this fd when process exits. */
|
* but OS will close this fd when process exits. */
|
||||||
killRDBChild();
|
rdbRemoveTempFile(server.child_pid, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Kill module child if there is one. */
|
/* Kill module child if there is one. */
|
||||||
if (server.module_child_pid != -1) {
|
if (server.child_type == CHILD_TYPE_MODULE) {
|
||||||
serverLog(LL_WARNING,"There is a module fork child. Killing it!");
|
serverLog(LL_WARNING,"There is a module fork child. Killing it!");
|
||||||
TerminateModuleForkChild(server.module_child_pid,0);
|
TerminateModuleForkChild(server.child_pid,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server.aof_state != AOF_OFF) {
|
if (server.aof_state != AOF_OFF) {
|
||||||
/* Kill the AOF saving child as the AOF we already have may be longer
|
/* Kill the AOF saving child as the AOF we already have may be longer
|
||||||
* but contains the full dataset anyway. */
|
* but contains the full dataset anyway. */
|
||||||
if (server.aof_child_pid != -1) {
|
if (server.child_type == CHILD_TYPE_AOF) {
|
||||||
/* If we have AOF enabled but haven't written the AOF yet, don't
|
/* If we have AOF enabled but haven't written the AOF yet, don't
|
||||||
* shutdown or else the dataset will be lost. */
|
* shutdown or else the dataset will be lost. */
|
||||||
if (server.aof_state == AOF_WAIT_REWRITE) {
|
if (server.aof_state == AOF_WAIT_REWRITE) {
|
||||||
@ -4170,6 +4305,7 @@ void addReplyCommand(client *c, struct redisCommand *cmd) {
|
|||||||
flagcount += addReplyCommandFlag(c,cmd,CMD_ASKING, "asking");
|
flagcount += addReplyCommandFlag(c,cmd,CMD_ASKING, "asking");
|
||||||
flagcount += addReplyCommandFlag(c,cmd,CMD_FAST, "fast");
|
flagcount += addReplyCommandFlag(c,cmd,CMD_FAST, "fast");
|
||||||
flagcount += addReplyCommandFlag(c,cmd,CMD_NO_AUTH, "no_auth");
|
flagcount += addReplyCommandFlag(c,cmd,CMD_NO_AUTH, "no_auth");
|
||||||
|
flagcount += addReplyCommandFlag(c,cmd,CMD_MAY_REPLICATE, "may_replicate");
|
||||||
if (cmdHasMovableKeys(cmd)) {
|
if (cmdHasMovableKeys(cmd)) {
|
||||||
addReplyStatus(c, "movablekeys");
|
addReplyStatus(c, "movablekeys");
|
||||||
flagcount += 1;
|
flagcount += 1;
|
||||||
@ -4191,10 +4327,14 @@ void commandCommand(client *c) {
|
|||||||
|
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"(no subcommand) -- Return details about all Redis commands.",
|
"(no subcommand)",
|
||||||
"COUNT -- Return the total number of commands in this Redis server.",
|
" Return details about all Redis commands.",
|
||||||
"GETKEYS <full-command> -- Return the keys from a full Redis command.",
|
"COUNT",
|
||||||
"INFO [command-name ...] -- Return details about multiple Redis commands.",
|
" Return the total number of commands in this Redis server.",
|
||||||
|
"GETKEYS <full-command>",
|
||||||
|
" Return the keys from a full Redis command.",
|
||||||
|
"INFO [<command-name> ...]",
|
||||||
|
" Return details about multiple Redis commands.",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
@ -4524,6 +4664,7 @@ sds genRedisInfoString(const char *section) {
|
|||||||
info = sdscatprintf(info,
|
info = sdscatprintf(info,
|
||||||
"# Persistence\r\n"
|
"# Persistence\r\n"
|
||||||
"loading:%d\r\n"
|
"loading:%d\r\n"
|
||||||
|
"current_cow_size:%zu\r\n"
|
||||||
"rdb_changes_since_last_save:%lld\r\n"
|
"rdb_changes_since_last_save:%lld\r\n"
|
||||||
"rdb_bgsave_in_progress:%d\r\n"
|
"rdb_bgsave_in_progress:%d\r\n"
|
||||||
"rdb_last_save_time:%jd\r\n"
|
"rdb_last_save_time:%jd\r\n"
|
||||||
@ -4542,24 +4683,25 @@ sds genRedisInfoString(const char *section) {
|
|||||||
"module_fork_in_progress:%d\r\n"
|
"module_fork_in_progress:%d\r\n"
|
||||||
"module_fork_last_cow_size:%zu\r\n",
|
"module_fork_last_cow_size:%zu\r\n",
|
||||||
server.loading,
|
server.loading,
|
||||||
|
server.stat_current_cow_bytes,
|
||||||
server.dirty,
|
server.dirty,
|
||||||
server.rdb_child_pid != -1,
|
server.child_type == CHILD_TYPE_RDB,
|
||||||
(intmax_t)server.lastsave,
|
(intmax_t)server.lastsave,
|
||||||
(server.lastbgsave_status == C_OK) ? "ok" : "err",
|
(server.lastbgsave_status == C_OK) ? "ok" : "err",
|
||||||
(intmax_t)server.rdb_save_time_last,
|
(intmax_t)server.rdb_save_time_last,
|
||||||
(intmax_t)((server.rdb_child_pid == -1) ?
|
(intmax_t)((server.child_type != CHILD_TYPE_RDB) ?
|
||||||
-1 : time(NULL)-server.rdb_save_time_start),
|
-1 : time(NULL)-server.rdb_save_time_start),
|
||||||
server.stat_rdb_cow_bytes,
|
server.stat_rdb_cow_bytes,
|
||||||
server.aof_state != AOF_OFF,
|
server.aof_state != AOF_OFF,
|
||||||
server.aof_child_pid != -1,
|
server.child_type == CHILD_TYPE_AOF,
|
||||||
server.aof_rewrite_scheduled,
|
server.aof_rewrite_scheduled,
|
||||||
(intmax_t)server.aof_rewrite_time_last,
|
(intmax_t)server.aof_rewrite_time_last,
|
||||||
(intmax_t)((server.aof_child_pid == -1) ?
|
(intmax_t)((server.child_type != CHILD_TYPE_AOF) ?
|
||||||
-1 : time(NULL)-server.aof_rewrite_time_start),
|
-1 : time(NULL)-server.aof_rewrite_time_start),
|
||||||
(server.aof_lastbgrewrite_status == C_OK) ? "ok" : "err",
|
(server.aof_lastbgrewrite_status == C_OK) ? "ok" : "err",
|
||||||
(server.aof_last_write_status == C_OK) ? "ok" : "err",
|
(server.aof_last_write_status == C_OK) ? "ok" : "err",
|
||||||
server.stat_aof_cow_bytes,
|
server.stat_aof_cow_bytes,
|
||||||
server.module_child_pid != -1,
|
server.child_type == CHILD_TYPE_MODULE,
|
||||||
server.stat_module_cow_bytes);
|
server.stat_module_cow_bytes);
|
||||||
|
|
||||||
if (server.aof_enabled) {
|
if (server.aof_enabled) {
|
||||||
@ -4665,6 +4807,7 @@ sds genRedisInfoString(const char *section) {
|
|||||||
"tracking_total_items:%lld\r\n"
|
"tracking_total_items:%lld\r\n"
|
||||||
"tracking_total_prefixes:%lld\r\n"
|
"tracking_total_prefixes:%lld\r\n"
|
||||||
"unexpected_error_replies:%lld\r\n"
|
"unexpected_error_replies:%lld\r\n"
|
||||||
|
"total_error_replies:%lld\r\n"
|
||||||
"dump_payload_sanitizations:%lld\r\n"
|
"dump_payload_sanitizations:%lld\r\n"
|
||||||
"total_reads_processed:%lld\r\n"
|
"total_reads_processed:%lld\r\n"
|
||||||
"total_writes_processed:%lld\r\n"
|
"total_writes_processed:%lld\r\n"
|
||||||
@ -4702,6 +4845,7 @@ sds genRedisInfoString(const char *section) {
|
|||||||
(unsigned long long) trackingGetTotalItems(),
|
(unsigned long long) trackingGetTotalItems(),
|
||||||
(unsigned long long) trackingGetTotalPrefixes(),
|
(unsigned long long) trackingGetTotalPrefixes(),
|
||||||
server.stat_unexpected_error_replies,
|
server.stat_unexpected_error_replies,
|
||||||
|
server.stat_total_error_replies,
|
||||||
server.stat_dump_payload_sanitizations,
|
server.stat_dump_payload_sanitizations,
|
||||||
stat_total_reads_processed,
|
stat_total_reads_processed,
|
||||||
stat_total_writes_processed,
|
stat_total_writes_processed,
|
||||||
@ -4892,14 +5036,33 @@ sds genRedisInfoString(const char *section) {
|
|||||||
di = dictGetSafeIterator(server.commands);
|
di = dictGetSafeIterator(server.commands);
|
||||||
while((de = dictNext(di)) != NULL) {
|
while((de = dictNext(di)) != NULL) {
|
||||||
c = (struct redisCommand *) dictGetVal(de);
|
c = (struct redisCommand *) dictGetVal(de);
|
||||||
if (!c->calls) continue;
|
if (!c->calls && !c->failed_calls && !c->rejected_calls)
|
||||||
|
continue;
|
||||||
info = sdscatprintf(info,
|
info = sdscatprintf(info,
|
||||||
"cmdstat_%s:calls=%lld,usec=%lld,usec_per_call=%.2f\r\n",
|
"cmdstat_%s:calls=%lld,usec=%lld,usec_per_call=%.2f"
|
||||||
|
",rejected_calls=%lld,failed_calls=%lld\r\n",
|
||||||
c->name, c->calls, c->microseconds,
|
c->name, c->calls, c->microseconds,
|
||||||
(c->calls == 0) ? 0 : ((float)c->microseconds/c->calls));
|
(c->calls == 0) ? 0 : ((float)c->microseconds/c->calls),
|
||||||
|
c->rejected_calls, c->failed_calls);
|
||||||
}
|
}
|
||||||
dictReleaseIterator(di);
|
dictReleaseIterator(di);
|
||||||
}
|
}
|
||||||
|
/* Error statistics */
|
||||||
|
if (allsections || defsections || !strcasecmp(section,"errorstats")) {
|
||||||
|
if (sections++) info = sdscat(info,"\r\n");
|
||||||
|
info = sdscat(info, "# Errorstats\r\n");
|
||||||
|
raxIterator ri;
|
||||||
|
raxStart(&ri,server.errors);
|
||||||
|
raxSeek(&ri,"^",NULL,0);
|
||||||
|
struct redisError *e;
|
||||||
|
while(raxNext(&ri)) {
|
||||||
|
e = (struct redisError *) ri.data;
|
||||||
|
info = sdscatprintf(info,
|
||||||
|
"errorstat_%.*s:count=%lld\r\n",
|
||||||
|
(int)ri.key_len, ri.key, e->count);
|
||||||
|
}
|
||||||
|
raxStop(&ri);
|
||||||
|
}
|
||||||
|
|
||||||
/* Cluster */
|
/* Cluster */
|
||||||
if (allsections || defsections || !strcasecmp(section,"cluster")) {
|
if (allsections || defsections || !strcasecmp(section,"cluster")) {
|
||||||
@ -4944,7 +5107,7 @@ void infoCommand(client *c) {
|
|||||||
char *section = c->argc == 2 ? c->argv[1]->ptr : "default";
|
char *section = c->argc == 2 ? c->argv[1]->ptr : "default";
|
||||||
|
|
||||||
if (c->argc > 2) {
|
if (c->argc > 2) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sds info = genRedisInfoString(section);
|
sds info = genRedisInfoString(section);
|
||||||
@ -4971,6 +5134,21 @@ void monitorCommand(client *c) {
|
|||||||
|
|
||||||
/* =================================== Main! ================================ */
|
/* =================================== Main! ================================ */
|
||||||
|
|
||||||
|
int checkIgnoreWarning(const char *warning) {
|
||||||
|
int argc, j;
|
||||||
|
sds *argv = sdssplitargs(server.ignore_warnings, &argc);
|
||||||
|
if (argv == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (j = 0; j < argc; j++) {
|
||||||
|
char *flag = argv[j];
|
||||||
|
if (!strcasecmp(flag, warning))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sdsfreesplitres(argv,argc);
|
||||||
|
return j < argc;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
int linuxOvercommitMemoryValue(void) {
|
int linuxOvercommitMemoryValue(void) {
|
||||||
FILE *fp = fopen("/proc/sys/vm/overcommit_memory","r");
|
FILE *fp = fopen("/proc/sys/vm/overcommit_memory","r");
|
||||||
@ -4994,6 +5172,113 @@ void linuxMemoryWarnings(void) {
|
|||||||
serverLog(LL_WARNING,"WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never').");
|
serverLog(LL_WARNING,"WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never').");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __arm64__
|
||||||
|
|
||||||
|
/* Get size in kilobytes of the Shared_Dirty pages of the calling process for the
|
||||||
|
* memory map corresponding to the provided address, or -1 on error. */
|
||||||
|
static int smapsGetSharedDirty(unsigned long addr) {
|
||||||
|
int ret, in_mapping = 0, val = -1;
|
||||||
|
unsigned long from, to;
|
||||||
|
char buf[64];
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f = fopen("/proc/self/smaps", "r");
|
||||||
|
serverAssert(f);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
if (!fgets(buf, sizeof(buf), f))
|
||||||
|
break;
|
||||||
|
|
||||||
|
ret = sscanf(buf, "%lx-%lx", &from, &to);
|
||||||
|
if (ret == 2)
|
||||||
|
in_mapping = from <= addr && addr < to;
|
||||||
|
|
||||||
|
if (in_mapping && !memcmp(buf, "Shared_Dirty:", 13)) {
|
||||||
|
ret = sscanf(buf, "%*s %d", &val);
|
||||||
|
serverAssert(ret == 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Older arm64 Linux kernels have a bug that could lead to data corruption
|
||||||
|
* during background save in certain scenarios. This function checks if the
|
||||||
|
* kernel is affected.
|
||||||
|
* The bug was fixed in commit ff1712f953e27f0b0718762ec17d0adb15c9fd0b
|
||||||
|
* titled: "arm64: pgtable: Ensure dirty bit is preserved across pte_wrprotect()"
|
||||||
|
* Return 1 if the kernel seems to be affected, and 0 otherwise. */
|
||||||
|
int linuxMadvFreeForkBugCheck(void) {
|
||||||
|
int ret, pipefd[2];
|
||||||
|
pid_t pid;
|
||||||
|
char *p, *q, bug_found = 0;
|
||||||
|
const long map_size = 3 * 4096;
|
||||||
|
|
||||||
|
/* Create a memory map that's in our full control (not one used by the allocator). */
|
||||||
|
p = mmap(NULL, map_size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||||
|
serverAssert(p != MAP_FAILED);
|
||||||
|
|
||||||
|
q = p + 4096;
|
||||||
|
|
||||||
|
/* Split the memory map in 3 pages by setting their protection as RO|RW|RO to prevent
|
||||||
|
* Linux from merging this memory map with adjacent VMAs. */
|
||||||
|
ret = mprotect(q, 4096, PROT_READ | PROT_WRITE);
|
||||||
|
serverAssert(!ret);
|
||||||
|
|
||||||
|
/* Write to the page once to make it resident */
|
||||||
|
*(volatile char*)q = 0;
|
||||||
|
|
||||||
|
/* Tell the kernel that this page is free to be reclaimed. */
|
||||||
|
#ifndef MADV_FREE
|
||||||
|
#define MADV_FREE 8
|
||||||
|
#endif
|
||||||
|
ret = madvise(q, 4096, MADV_FREE);
|
||||||
|
serverAssert(!ret);
|
||||||
|
|
||||||
|
/* Write to the page after being marked for freeing, this is supposed to take
|
||||||
|
* ownership of that page again. */
|
||||||
|
*(volatile char*)q = 0;
|
||||||
|
|
||||||
|
/* Create a pipe for the child to return the info to the parent. */
|
||||||
|
ret = pipe(pipefd);
|
||||||
|
serverAssert(!ret);
|
||||||
|
|
||||||
|
/* Fork the process. */
|
||||||
|
pid = fork();
|
||||||
|
serverAssert(pid >= 0);
|
||||||
|
if (!pid) {
|
||||||
|
/* Child: check if the page is marked as dirty, expecing 4 (kB).
|
||||||
|
* A value of 0 means the kernel is affected by the bug. */
|
||||||
|
if (!smapsGetSharedDirty((unsigned long)q))
|
||||||
|
bug_found = 1;
|
||||||
|
|
||||||
|
ret = write(pipefd[1], &bug_found, 1);
|
||||||
|
serverAssert(ret == 1);
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
} else {
|
||||||
|
/* Read the result from the child. */
|
||||||
|
ret = read(pipefd[0], &bug_found, 1);
|
||||||
|
serverAssert(ret == 1);
|
||||||
|
|
||||||
|
/* Reap the child pid. */
|
||||||
|
serverAssert(waitpid(pid, NULL, 0) == pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cleanup */
|
||||||
|
ret = close(pipefd[0]);
|
||||||
|
serverAssert(!ret);
|
||||||
|
ret = close(pipefd[1]);
|
||||||
|
serverAssert(!ret);
|
||||||
|
ret = munmap(p, map_size);
|
||||||
|
serverAssert(!ret);
|
||||||
|
|
||||||
|
return bug_found;
|
||||||
|
}
|
||||||
|
#endif /* __arm64__ */
|
||||||
#endif /* __linux__ */
|
#endif /* __linux__ */
|
||||||
|
|
||||||
void createPidFile(void) {
|
void createPidFile(void) {
|
||||||
@ -5189,10 +5474,22 @@ void closeClildUnusedResourceAfterFork() {
|
|||||||
closeListeningSockets(0);
|
closeListeningSockets(0);
|
||||||
if (server.cluster_enabled && server.cluster_config_file_lock_fd != -1)
|
if (server.cluster_enabled && server.cluster_config_file_lock_fd != -1)
|
||||||
close(server.cluster_config_file_lock_fd); /* don't care if this fails */
|
close(server.cluster_config_file_lock_fd); /* don't care if this fails */
|
||||||
|
|
||||||
|
/* Clear server.pidfile, this is the parent pidfile which should not
|
||||||
|
* be touched (or deleted) by the child (on exit / crash) */
|
||||||
|
zfree(server.pidfile);
|
||||||
|
server.pidfile = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* purpose is one of CHILD_TYPE_ types */
|
/* purpose is one of CHILD_TYPE_ types */
|
||||||
int redisFork(int purpose) {
|
int redisFork(int purpose) {
|
||||||
|
if (isMutuallyExclusiveChildType(purpose)) {
|
||||||
|
if (hasActiveChildProcess())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
openChildInfoPipe();
|
||||||
|
}
|
||||||
|
|
||||||
int childpid;
|
int childpid;
|
||||||
long long start = ustime();
|
long long start = ustime();
|
||||||
if ((childpid = fork()) == 0) {
|
if ((childpid = fork()) == 0) {
|
||||||
@ -5208,23 +5505,38 @@ int redisFork(int purpose) {
|
|||||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
||||||
if (childpid == -1) {
|
if (childpid == -1) {
|
||||||
|
if (isMutuallyExclusiveChildType(purpose)) closeChildInfoPipe();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The child_pid and child_type are only for mutual exclusive children.
|
||||||
|
* other child types should handle and store their pid's in dedicated variables.
|
||||||
|
*
|
||||||
|
* Today, we allows CHILD_TYPE_LDB to run in parallel with the other fork types:
|
||||||
|
* - it isn't used for production, so it will not make the server be less efficient
|
||||||
|
* - used for debugging, and we don't want to block it from running while other
|
||||||
|
* forks are running (like RDB and AOF) */
|
||||||
|
if (isMutuallyExclusiveChildType(purpose)) {
|
||||||
|
server.child_pid = childpid;
|
||||||
|
server.child_type = purpose;
|
||||||
|
server.stat_current_cow_bytes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDictResizePolicy();
|
||||||
}
|
}
|
||||||
return childpid;
|
return childpid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendChildCOWInfo(int ptype, char *pname) {
|
void sendChildCOWInfo(int ptype, int on_exit, char *pname) {
|
||||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
||||||
|
|
||||||
if (private_dirty) {
|
if (private_dirty) {
|
||||||
serverLog(LL_NOTICE,
|
serverLog(on_exit ? LL_NOTICE : LL_VERBOSE,
|
||||||
"%s: %zu MB of memory used by copy-on-write",
|
"%s: %zu MB of memory used by copy-on-write",
|
||||||
pname, private_dirty/(1024*1024));
|
pname, private_dirty);
|
||||||
}
|
}
|
||||||
|
|
||||||
server.child_info_data.cow_size = private_dirty;
|
sendChildInfo(ptype, on_exit, private_dirty);
|
||||||
sendChildInfo(ptype);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void memtest(size_t megabytes, int passes);
|
void memtest(size_t megabytes, int passes);
|
||||||
@ -5436,6 +5748,7 @@ int main(int argc, char **argv) {
|
|||||||
srand(time(NULL)^getpid());
|
srand(time(NULL)^getpid());
|
||||||
srandom(time(NULL)^getpid());
|
srandom(time(NULL)^getpid());
|
||||||
gettimeofday(&tv,NULL);
|
gettimeofday(&tv,NULL);
|
||||||
|
init_genrand64(((long long) tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid());
|
||||||
crc64_init();
|
crc64_init();
|
||||||
|
|
||||||
uint8_t hashseed[16];
|
uint8_t hashseed[16];
|
||||||
@ -5564,7 +5877,16 @@ int main(int argc, char **argv) {
|
|||||||
serverLog(LL_WARNING,"Server initialized");
|
serverLog(LL_WARNING,"Server initialized");
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
linuxMemoryWarnings();
|
linuxMemoryWarnings();
|
||||||
#endif
|
#if defined (__arm64__)
|
||||||
|
if (linuxMadvFreeForkBugCheck()) {
|
||||||
|
serverLog(LL_WARNING,"WARNING Your kernel has a bug that could lead to data corruption during background save. Please upgrade to the latest stable kernel.");
|
||||||
|
if (!checkIgnoreWarning("ARM64-COW-BUG")) {
|
||||||
|
serverLog(LL_WARNING,"Redis will now exit to prevent data corruption. Note that it is possible to suppress this warning by setting the following config: ignore-warnings ARM64-COW-BUG");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* __arm64__ */
|
||||||
|
#endif /* __linux__ */
|
||||||
moduleInitModulesSystemLast();
|
moduleInitModulesSystemLast();
|
||||||
moduleLoadFromQueue();
|
moduleLoadFromQueue();
|
||||||
ACLLoadUsersAtStartup();
|
ACLLoadUsersAtStartup();
|
||||||
|
167
src/server.h
167
src/server.h
@ -181,33 +181,34 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
|
|||||||
#define CMD_ASKING (1ULL<<13) /* "cluster-asking" flag */
|
#define CMD_ASKING (1ULL<<13) /* "cluster-asking" flag */
|
||||||
#define CMD_FAST (1ULL<<14) /* "fast" flag */
|
#define CMD_FAST (1ULL<<14) /* "fast" flag */
|
||||||
#define CMD_NO_AUTH (1ULL<<15) /* "no-auth" flag */
|
#define CMD_NO_AUTH (1ULL<<15) /* "no-auth" flag */
|
||||||
|
#define CMD_MAY_REPLICATE (1ULL<<16) /* "may-replicate" flag */
|
||||||
|
|
||||||
/* Command flags used by the module system. */
|
/* Command flags used by the module system. */
|
||||||
#define CMD_MODULE_GETKEYS (1ULL<<16) /* Use the modules getkeys interface. */
|
#define CMD_MODULE_GETKEYS (1ULL<<17) /* Use the modules getkeys interface. */
|
||||||
#define CMD_MODULE_NO_CLUSTER (1ULL<<17) /* Deny on Redis Cluster. */
|
#define CMD_MODULE_NO_CLUSTER (1ULL<<18) /* Deny on Redis Cluster. */
|
||||||
|
|
||||||
/* Command flags that describe ACLs categories. */
|
/* Command flags that describe ACLs categories. */
|
||||||
#define CMD_CATEGORY_KEYSPACE (1ULL<<18)
|
#define CMD_CATEGORY_KEYSPACE (1ULL<<19)
|
||||||
#define CMD_CATEGORY_READ (1ULL<<19)
|
#define CMD_CATEGORY_READ (1ULL<<20)
|
||||||
#define CMD_CATEGORY_WRITE (1ULL<<20)
|
#define CMD_CATEGORY_WRITE (1ULL<<21)
|
||||||
#define CMD_CATEGORY_SET (1ULL<<21)
|
#define CMD_CATEGORY_SET (1ULL<<22)
|
||||||
#define CMD_CATEGORY_SORTEDSET (1ULL<<22)
|
#define CMD_CATEGORY_SORTEDSET (1ULL<<23)
|
||||||
#define CMD_CATEGORY_LIST (1ULL<<23)
|
#define CMD_CATEGORY_LIST (1ULL<<24)
|
||||||
#define CMD_CATEGORY_HASH (1ULL<<24)
|
#define CMD_CATEGORY_HASH (1ULL<<25)
|
||||||
#define CMD_CATEGORY_STRING (1ULL<<25)
|
#define CMD_CATEGORY_STRING (1ULL<<26)
|
||||||
#define CMD_CATEGORY_BITMAP (1ULL<<26)
|
#define CMD_CATEGORY_BITMAP (1ULL<<27)
|
||||||
#define CMD_CATEGORY_HYPERLOGLOG (1ULL<<27)
|
#define CMD_CATEGORY_HYPERLOGLOG (1ULL<<28)
|
||||||
#define CMD_CATEGORY_GEO (1ULL<<28)
|
#define CMD_CATEGORY_GEO (1ULL<<29)
|
||||||
#define CMD_CATEGORY_STREAM (1ULL<<29)
|
#define CMD_CATEGORY_STREAM (1ULL<<30)
|
||||||
#define CMD_CATEGORY_PUBSUB (1ULL<<30)
|
#define CMD_CATEGORY_PUBSUB (1ULL<<31)
|
||||||
#define CMD_CATEGORY_ADMIN (1ULL<<31)
|
#define CMD_CATEGORY_ADMIN (1ULL<<32)
|
||||||
#define CMD_CATEGORY_FAST (1ULL<<32)
|
#define CMD_CATEGORY_FAST (1ULL<<33)
|
||||||
#define CMD_CATEGORY_SLOW (1ULL<<33)
|
#define CMD_CATEGORY_SLOW (1ULL<<34)
|
||||||
#define CMD_CATEGORY_BLOCKING (1ULL<<34)
|
#define CMD_CATEGORY_BLOCKING (1ULL<<35)
|
||||||
#define CMD_CATEGORY_DANGEROUS (1ULL<<35)
|
#define CMD_CATEGORY_DANGEROUS (1ULL<<36)
|
||||||
#define CMD_CATEGORY_CONNECTION (1ULL<<36)
|
#define CMD_CATEGORY_CONNECTION (1ULL<<37)
|
||||||
#define CMD_CATEGORY_TRANSACTION (1ULL<<37)
|
#define CMD_CATEGORY_TRANSACTION (1ULL<<38)
|
||||||
#define CMD_CATEGORY_SCRIPTING (1ULL<<38)
|
#define CMD_CATEGORY_SCRIPTING (1ULL<<39)
|
||||||
|
|
||||||
/* AOF states */
|
/* AOF states */
|
||||||
#define AOF_OFF 0 /* AOF is off */
|
#define AOF_OFF 0 /* AOF is off */
|
||||||
@ -250,10 +251,8 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
|
|||||||
#define CLIENT_PENDING_READ (1<<29) /* The client has pending reads and was put
|
#define CLIENT_PENDING_READ (1<<29) /* The client has pending reads and was put
|
||||||
in the list of clients we can read
|
in the list of clients we can read
|
||||||
from. */
|
from. */
|
||||||
#define CLIENT_PENDING_COMMAND (1<<30) /* Used in threaded I/O to signal after
|
#define CLIENT_PENDING_COMMAND (1<<30) /* Indicates the client has a fully
|
||||||
we return single threaded that the
|
* parsed command ready for execution. */
|
||||||
client has already pending commands
|
|
||||||
to be executed. */
|
|
||||||
#define CLIENT_TRACKING (1ULL<<31) /* Client enabled keys tracking in order to
|
#define CLIENT_TRACKING (1ULL<<31) /* Client enabled keys tracking in order to
|
||||||
perform client side caching. */
|
perform client side caching. */
|
||||||
#define CLIENT_TRACKING_BROKEN_REDIR (1ULL<<32) /* Target client is invalid. */
|
#define CLIENT_TRACKING_BROKEN_REDIR (1ULL<<32) /* Target client is invalid. */
|
||||||
@ -280,7 +279,8 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
|
|||||||
#define BLOCKED_MODULE 3 /* Blocked by a loadable module. */
|
#define BLOCKED_MODULE 3 /* Blocked by a loadable module. */
|
||||||
#define BLOCKED_STREAM 4 /* XREAD. */
|
#define BLOCKED_STREAM 4 /* XREAD. */
|
||||||
#define BLOCKED_ZSET 5 /* BZPOP et al. */
|
#define BLOCKED_ZSET 5 /* BZPOP et al. */
|
||||||
#define BLOCKED_NUM 6 /* Number of blocked states. */
|
#define BLOCKED_PAUSE 6 /* Blocked by CLIENT PAUSE */
|
||||||
|
#define BLOCKED_NUM 7 /* Number of blocked states. */
|
||||||
|
|
||||||
/* Client request types */
|
/* Client request types */
|
||||||
#define PROTO_REQ_INLINE 1
|
#define PROTO_REQ_INLINE 1
|
||||||
@ -299,24 +299,23 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
|
|||||||
|
|
||||||
/* Slave replication state. Used in server.repl_state for slaves to remember
|
/* Slave replication state. Used in server.repl_state for slaves to remember
|
||||||
* what to do next. */
|
* what to do next. */
|
||||||
#define REPL_STATE_NONE 0 /* No active replication */
|
typedef enum {
|
||||||
#define REPL_STATE_CONNECT 1 /* Must connect to master */
|
REPL_STATE_NONE = 0, /* No active replication */
|
||||||
#define REPL_STATE_CONNECTING 2 /* Connecting to master */
|
REPL_STATE_CONNECT, /* Must connect to master */
|
||||||
/* --- Handshake states, must be ordered --- */
|
REPL_STATE_CONNECTING, /* Connecting to master */
|
||||||
#define REPL_STATE_RECEIVE_PONG 3 /* Wait for PING reply */
|
/* --- Handshake states, must be ordered --- */
|
||||||
#define REPL_STATE_SEND_AUTH 4 /* Send AUTH to master */
|
REPL_STATE_RECEIVE_PING_REPLY, /* Wait for PING reply */
|
||||||
#define REPL_STATE_RECEIVE_AUTH 5 /* Wait for AUTH reply */
|
REPL_STATE_SEND_HANDSHAKE, /* Send handshake sequance to master */
|
||||||
#define REPL_STATE_SEND_PORT 6 /* Send REPLCONF listening-port */
|
REPL_STATE_RECEIVE_AUTH_REPLY, /* Wait for AUTH reply */
|
||||||
#define REPL_STATE_RECEIVE_PORT 7 /* Wait for REPLCONF reply */
|
REPL_STATE_RECEIVE_PORT_REPLY, /* Wait for REPLCONF reply */
|
||||||
#define REPL_STATE_SEND_IP 8 /* Send REPLCONF ip-address */
|
REPL_STATE_RECEIVE_IP_REPLY, /* Wait for REPLCONF reply */
|
||||||
#define REPL_STATE_RECEIVE_IP 9 /* Wait for REPLCONF reply */
|
REPL_STATE_RECEIVE_CAPA_REPLY, /* Wait for REPLCONF reply */
|
||||||
#define REPL_STATE_SEND_CAPA 10 /* Send REPLCONF capa */
|
REPL_STATE_SEND_PSYNC, /* Send PSYNC */
|
||||||
#define REPL_STATE_RECEIVE_CAPA 11 /* Wait for REPLCONF reply */
|
REPL_STATE_RECEIVE_PSYNC_REPLY, /* Wait for PSYNC reply */
|
||||||
#define REPL_STATE_SEND_PSYNC 12 /* Send PSYNC */
|
/* --- End of handshake states --- */
|
||||||
#define REPL_STATE_RECEIVE_PSYNC 13 /* Wait for PSYNC reply */
|
REPL_STATE_TRANSFER, /* Receiving .rdb from master */
|
||||||
/* --- End of handshake states --- */
|
REPL_STATE_CONNECTED, /* Connected to master */
|
||||||
#define REPL_STATE_TRANSFER 14 /* Receiving .rdb from master */
|
} repl_state;
|
||||||
#define REPL_STATE_CONNECTED 15 /* Connected to master */
|
|
||||||
|
|
||||||
/* State of slaves from the POV of the master. Used in client->replstate.
|
/* State of slaves from the POV of the master. Used in client->replstate.
|
||||||
* In SEND_BULK and ONLINE state the slave receives new updates
|
* In SEND_BULK and ONLINE state the slave receives new updates
|
||||||
@ -437,6 +436,14 @@ extern int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT];
|
|||||||
#define PROPAGATE_AOF 1
|
#define PROPAGATE_AOF 1
|
||||||
#define PROPAGATE_REPL 2
|
#define PROPAGATE_REPL 2
|
||||||
|
|
||||||
|
/* Client pause types, larger types are more restrictive
|
||||||
|
* pause types than smaller pause types. */
|
||||||
|
typedef enum {
|
||||||
|
CLIENT_PAUSE_OFF = 0, /* Pause no commands */
|
||||||
|
CLIENT_PAUSE_WRITE, /* Pause write commands */
|
||||||
|
CLIENT_PAUSE_ALL /* Pause all commands */
|
||||||
|
} pause_type;
|
||||||
|
|
||||||
/* RDB active child save type. */
|
/* RDB active child save type. */
|
||||||
#define RDB_CHILD_TYPE_NONE 0
|
#define RDB_CHILD_TYPE_NONE 0
|
||||||
#define RDB_CHILD_TYPE_DISK 1 /* RDB is written to disk. */
|
#define RDB_CHILD_TYPE_DISK 1 /* RDB is written to disk. */
|
||||||
@ -605,7 +612,7 @@ typedef struct RedisModuleIO {
|
|||||||
iovar.ver = 0; \
|
iovar.ver = 0; \
|
||||||
iovar.key = keyptr; \
|
iovar.key = keyptr; \
|
||||||
iovar.ctx = NULL; \
|
iovar.ctx = NULL; \
|
||||||
} while(0);
|
} while(0)
|
||||||
|
|
||||||
/* This is a structure used to export DEBUG DIGEST capabilities to Redis
|
/* This is a structure used to export DEBUG DIGEST capabilities to Redis
|
||||||
* modules. We want to capture both the ordered and unordered elements of
|
* modules. We want to capture both the ordered and unordered elements of
|
||||||
@ -621,7 +628,7 @@ typedef struct RedisModuleDigest {
|
|||||||
#define moduleInitDigestContext(mdvar) do { \
|
#define moduleInitDigestContext(mdvar) do { \
|
||||||
memset(mdvar.o,0,sizeof(mdvar.o)); \
|
memset(mdvar.o,0,sizeof(mdvar.o)); \
|
||||||
memset(mdvar.x,0,sizeof(mdvar.x)); \
|
memset(mdvar.x,0,sizeof(mdvar.x)); \
|
||||||
} while(0);
|
} while(0)
|
||||||
|
|
||||||
/* Objects encoding. Some kind of objects like Strings and Hashes can be
|
/* Objects encoding. Some kind of objects like Strings and Hashes can be
|
||||||
* internally represented in multiple ways. The 'encoding' field of the object
|
* internally represented in multiple ways. The 'encoding' field of the object
|
||||||
@ -894,6 +901,7 @@ typedef struct client {
|
|||||||
sds peerid; /* Cached peer ID. */
|
sds peerid; /* Cached peer ID. */
|
||||||
sds sockname; /* Cached connection target address. */
|
sds sockname; /* Cached connection target address. */
|
||||||
listNode *client_list_node; /* list node in client list */
|
listNode *client_list_node; /* list node in client list */
|
||||||
|
listNode *paused_list_node; /* list node within the pause list */
|
||||||
RedisModuleUserChangedFunc auth_callback; /* Module callback to execute
|
RedisModuleUserChangedFunc auth_callback; /* Module callback to execute
|
||||||
* when the authenticated user
|
* when the authenticated user
|
||||||
* changes. */
|
* changes. */
|
||||||
@ -1099,7 +1107,6 @@ struct clusterState;
|
|||||||
#undef hz
|
#undef hz
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define CHILD_INFO_MAGIC 0xC17DDA7A12345678LL
|
|
||||||
#define CHILD_TYPE_NONE 0
|
#define CHILD_TYPE_NONE 0
|
||||||
#define CHILD_TYPE_RDB 1
|
#define CHILD_TYPE_RDB 1
|
||||||
#define CHILD_TYPE_AOF 2
|
#define CHILD_TYPE_AOF 2
|
||||||
@ -1123,6 +1130,7 @@ struct redisServer {
|
|||||||
dict *commands; /* Command table */
|
dict *commands; /* Command table */
|
||||||
dict *orig_commands; /* Command table before command renaming. */
|
dict *orig_commands; /* Command table before command renaming. */
|
||||||
aeEventLoop *el;
|
aeEventLoop *el;
|
||||||
|
rax *errors; /* Errors table */
|
||||||
redisAtomic unsigned int lruclock; /* Clock for LRU eviction */
|
redisAtomic unsigned int lruclock; /* Clock for LRU eviction */
|
||||||
volatile sig_atomic_t shutdown_asap; /* SHUTDOWN needed ASAP */
|
volatile sig_atomic_t shutdown_asap; /* SHUTDOWN needed ASAP */
|
||||||
int activerehashing; /* Incremental rehash in serverCron() */
|
int activerehashing; /* Incremental rehash in serverCron() */
|
||||||
@ -1137,6 +1145,8 @@ struct redisServer {
|
|||||||
int in_eval; /* Are we inside EVAL? */
|
int in_eval; /* Are we inside EVAL? */
|
||||||
int in_exec; /* Are we inside EXEC? */
|
int in_exec; /* Are we inside EXEC? */
|
||||||
int propagate_in_transaction; /* Make sure we don't propagate nested MULTI/EXEC */
|
int propagate_in_transaction; /* Make sure we don't propagate nested MULTI/EXEC */
|
||||||
|
char *ignore_warnings; /* Config: warnings that should be ignored. */
|
||||||
|
int client_pause_in_transaction; /* Was a client pause executed during this Exec? */
|
||||||
/* Modules */
|
/* Modules */
|
||||||
dict *moduleapi; /* Exported core APIs dictionary for modules. */
|
dict *moduleapi; /* Exported core APIs dictionary for modules. */
|
||||||
dict *sharedapi; /* Like moduleapi but containing the APIs that
|
dict *sharedapi; /* Like moduleapi but containing the APIs that
|
||||||
@ -1145,7 +1155,8 @@ struct redisServer {
|
|||||||
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
|
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
|
||||||
client blocked on a module command needs
|
client blocked on a module command needs
|
||||||
to be processed. */
|
to be processed. */
|
||||||
pid_t module_child_pid; /* PID of module child */
|
pid_t child_pid; /* PID of current child */
|
||||||
|
int child_type; /* Type of current child */
|
||||||
/* Networking */
|
/* Networking */
|
||||||
int port; /* TCP listening port */
|
int port; /* TCP listening port */
|
||||||
int tls_port; /* TLS listening port */
|
int tls_port; /* TLS listening port */
|
||||||
@ -1170,8 +1181,9 @@ struct redisServer {
|
|||||||
rax *clients_timeout_table; /* Radix tree for blocked clients timeouts. */
|
rax *clients_timeout_table; /* Radix tree for blocked clients timeouts. */
|
||||||
long fixed_time_expire; /* If > 0, expire keys against server.mstime. */
|
long fixed_time_expire; /* If > 0, expire keys against server.mstime. */
|
||||||
rax *clients_index; /* Active clients dictionary by client ID. */
|
rax *clients_index; /* Active clients dictionary by client ID. */
|
||||||
int clients_paused; /* True if clients are currently paused */
|
pause_type client_pause_type; /* True if clients are currently paused */
|
||||||
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
|
list *paused_clients; /* List of pause clients */
|
||||||
|
mstime_t client_pause_end_time; /* Time when we undo clients_paused */
|
||||||
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
|
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
|
||||||
dict *migrate_cached_sockets;/* MIGRATE cached sockets */
|
dict *migrate_cached_sockets;/* MIGRATE cached sockets */
|
||||||
redisAtomic uint64_t next_client_id; /* Next client unique ID. Incremental. */
|
redisAtomic uint64_t next_client_id; /* Next client unique ID. Incremental. */
|
||||||
@ -1227,11 +1239,13 @@ struct redisServer {
|
|||||||
struct malloc_stats cron_malloc_stats; /* sampled in serverCron(). */
|
struct malloc_stats cron_malloc_stats; /* sampled in serverCron(). */
|
||||||
redisAtomic long long stat_net_input_bytes; /* Bytes read from network. */
|
redisAtomic long long stat_net_input_bytes; /* Bytes read from network. */
|
||||||
redisAtomic long long stat_net_output_bytes; /* Bytes written to network. */
|
redisAtomic long long stat_net_output_bytes; /* Bytes written to network. */
|
||||||
|
size_t stat_current_cow_bytes; /* Copy on write bytes while child is active. */
|
||||||
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
|
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
|
||||||
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
|
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
|
||||||
size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
|
size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
|
||||||
uint64_t stat_clients_type_memory[CLIENT_TYPE_COUNT];/* Mem usage by type */
|
uint64_t stat_clients_type_memory[CLIENT_TYPE_COUNT];/* Mem usage by type */
|
||||||
long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */
|
long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */
|
||||||
|
long long stat_total_error_replies; /* Total number of issued error replies ( command + rejected errors ) */
|
||||||
long long stat_dump_payload_sanitizations; /* Number deep dump payloads integrity validations. */
|
long long stat_dump_payload_sanitizations; /* Number deep dump payloads integrity validations. */
|
||||||
long long stat_io_reads_processed; /* Number of read events processed by IO / Main threads */
|
long long stat_io_reads_processed; /* Number of read events processed by IO / Main threads */
|
||||||
long long stat_io_writes_processed; /* Number of write events processed by IO / Main threads */
|
long long stat_io_writes_processed; /* Number of write events processed by IO / Main threads */
|
||||||
@ -1280,7 +1294,6 @@ struct redisServer {
|
|||||||
off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */
|
off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */
|
||||||
int aof_flush_sleep; /* Micros to sleep before flush. (used by tests) */
|
int aof_flush_sleep; /* Micros to sleep before flush. (used by tests) */
|
||||||
int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */
|
int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */
|
||||||
pid_t aof_child_pid; /* PID if rewriting process */
|
|
||||||
list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */
|
list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */
|
||||||
sds aof_buf; /* AOF buffer, written before entering the event loop */
|
sds aof_buf; /* AOF buffer, written before entering the event loop */
|
||||||
int aof_fd; /* File descriptor of currently selected AOF file */
|
int aof_fd; /* File descriptor of currently selected AOF file */
|
||||||
@ -1310,7 +1323,6 @@ struct redisServer {
|
|||||||
/* RDB persistence */
|
/* RDB persistence */
|
||||||
long long dirty; /* Changes to DB from the last save */
|
long long dirty; /* Changes to DB from the last save */
|
||||||
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
|
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
|
||||||
pid_t rdb_child_pid; /* PID of RDB saving child */
|
|
||||||
struct saveparam *saveparams; /* Save points array for RDB */
|
struct saveparam *saveparams; /* Save points array for RDB */
|
||||||
int saveparamslen; /* Number of saving points */
|
int saveparamslen; /* Number of saving points */
|
||||||
char *rdb_filename; /* Name of RDB file */
|
char *rdb_filename; /* Name of RDB file */
|
||||||
@ -1342,11 +1354,7 @@ struct redisServer {
|
|||||||
* value means fractions of microsecons (on average). */
|
* value means fractions of microsecons (on average). */
|
||||||
/* Pipe and data structures for child -> parent info sharing. */
|
/* Pipe and data structures for child -> parent info sharing. */
|
||||||
int child_info_pipe[2]; /* Pipe used to write the child_info_data. */
|
int child_info_pipe[2]; /* Pipe used to write the child_info_data. */
|
||||||
struct {
|
int child_info_nread; /* Num of bytes of the last read from pipe */
|
||||||
int process_type; /* AOF or RDB child? */
|
|
||||||
size_t cow_size; /* Copy on write size. */
|
|
||||||
unsigned long long magic; /* Magic value to make sure data is valid. */
|
|
||||||
} child_info_data;
|
|
||||||
/* Propagation of commands in AOF / replication */
|
/* Propagation of commands in AOF / replication */
|
||||||
redisOpArray also_propagate; /* Additional command to propagate. */
|
redisOpArray also_propagate; /* Additional command to propagate. */
|
||||||
/* Logging */
|
/* Logging */
|
||||||
@ -1386,7 +1394,7 @@ struct redisServer {
|
|||||||
int repl_diskless_sync_delay; /* Delay to start a diskless repl BGSAVE. */
|
int repl_diskless_sync_delay; /* Delay to start a diskless repl BGSAVE. */
|
||||||
/* Replication (slave) */
|
/* Replication (slave) */
|
||||||
char *masteruser; /* AUTH with this user and masterauth with master */
|
char *masteruser; /* AUTH with this user and masterauth with master */
|
||||||
char *masterauth; /* AUTH with this password with master */
|
sds masterauth; /* AUTH with this password with master */
|
||||||
char *masterhost; /* Hostname of master */
|
char *masterhost; /* Hostname of master */
|
||||||
int masterport; /* Port of master */
|
int masterport; /* Port of master */
|
||||||
int repl_timeout; /* Timeout after N seconds of master idle */
|
int repl_timeout; /* Timeout after N seconds of master idle */
|
||||||
@ -1467,6 +1475,7 @@ struct redisServer {
|
|||||||
int daylight_active; /* Currently in daylight saving time. */
|
int daylight_active; /* Currently in daylight saving time. */
|
||||||
mstime_t mstime; /* 'unixtime' in milliseconds. */
|
mstime_t mstime; /* 'unixtime' in milliseconds. */
|
||||||
ustime_t ustime; /* 'unixtime' in microseconds. */
|
ustime_t ustime; /* 'unixtime' in microseconds. */
|
||||||
|
size_t blocking_op_nesting; /* Nesting level of blocking operation, used to reset blocked_last_cron. */
|
||||||
long long blocked_last_cron; /* Indicate the mstime of the last time we did cron jobs from a blocking operation */
|
long long blocked_last_cron; /* Indicate the mstime of the last time we did cron jobs from a blocking operation */
|
||||||
/* Pubsub */
|
/* Pubsub */
|
||||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||||
@ -1580,7 +1589,7 @@ struct redisCommand {
|
|||||||
int firstkey; /* The first argument that's a key (0 = no keys) */
|
int firstkey; /* The first argument that's a key (0 = no keys) */
|
||||||
int lastkey; /* The last argument that's a key */
|
int lastkey; /* The last argument that's a key */
|
||||||
int keystep; /* The step between first and last key */
|
int keystep; /* The step between first and last key */
|
||||||
long long microseconds, calls;
|
long long microseconds, calls, rejected_calls, failed_calls;
|
||||||
int id; /* Command ID. This is a progressive ID starting from 0 that
|
int id; /* Command ID. This is a progressive ID starting from 0 that
|
||||||
is assigned at runtime, and is used in order to check
|
is assigned at runtime, and is used in order to check
|
||||||
ACLs. A connection is able to execute a given command if
|
ACLs. A connection is able to execute a given command if
|
||||||
@ -1588,6 +1597,10 @@ struct redisCommand {
|
|||||||
bit set in the bitmap of allowed commands. */
|
bit set in the bitmap of allowed commands. */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct redisError {
|
||||||
|
long long count;
|
||||||
|
};
|
||||||
|
|
||||||
struct redisFunctionSym {
|
struct redisFunctionSym {
|
||||||
char *name;
|
char *name;
|
||||||
unsigned long pointer;
|
unsigned long pointer;
|
||||||
@ -1753,6 +1766,7 @@ void addReplyBulkLongLong(client *c, long long ll);
|
|||||||
void addReply(client *c, robj *obj);
|
void addReply(client *c, robj *obj);
|
||||||
void addReplySds(client *c, sds s);
|
void addReplySds(client *c, sds s);
|
||||||
void addReplyBulkSds(client *c, sds s);
|
void addReplyBulkSds(client *c, sds s);
|
||||||
|
void setDeferredReplyBulkSds(client *c, void *node, sds s);
|
||||||
void addReplyErrorObject(client *c, robj *err);
|
void addReplyErrorObject(client *c, robj *err);
|
||||||
void addReplyErrorSds(client *c, sds err);
|
void addReplyErrorSds(client *c, sds err);
|
||||||
void addReplyError(client *c, const char *err);
|
void addReplyError(client *c, const char *err);
|
||||||
@ -1791,8 +1805,10 @@ char *getClientTypeName(int class);
|
|||||||
void flushSlavesOutputBuffers(void);
|
void flushSlavesOutputBuffers(void);
|
||||||
void disconnectSlaves(void);
|
void disconnectSlaves(void);
|
||||||
int listenToPort(int port, int *fds, int *count);
|
int listenToPort(int port, int *fds, int *count);
|
||||||
void pauseClients(mstime_t duration);
|
void pauseClients(mstime_t duration, pause_type type);
|
||||||
int clientsArePaused(void);
|
void unpauseClients(void);
|
||||||
|
int areClientsPaused(void);
|
||||||
|
int checkClientPauseTimeoutAndReturnIfPaused(void);
|
||||||
void processEventsWhileBlocked(void);
|
void processEventsWhileBlocked(void);
|
||||||
void loadingCron(void);
|
void loadingCron(void);
|
||||||
void whileBlockedCron();
|
void whileBlockedCron();
|
||||||
@ -1826,12 +1842,14 @@ void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **pr
|
|||||||
void disableTracking(client *c);
|
void disableTracking(client *c);
|
||||||
void trackingRememberKeys(client *c);
|
void trackingRememberKeys(client *c);
|
||||||
void trackingInvalidateKey(client *c, robj *keyobj);
|
void trackingInvalidateKey(client *c, robj *keyobj);
|
||||||
void trackingInvalidateKeysOnFlush(int dbid);
|
void trackingInvalidateKeysOnFlush(int async);
|
||||||
|
void freeTrackingRadixTreeAsync(rax *rt);
|
||||||
void trackingLimitUsedSlots(void);
|
void trackingLimitUsedSlots(void);
|
||||||
uint64_t trackingGetTotalItems(void);
|
uint64_t trackingGetTotalItems(void);
|
||||||
uint64_t trackingGetTotalKeys(void);
|
uint64_t trackingGetTotalKeys(void);
|
||||||
uint64_t trackingGetTotalPrefixes(void);
|
uint64_t trackingGetTotalPrefixes(void);
|
||||||
void trackingBroadcastInvalidationMessages(void);
|
void trackingBroadcastInvalidationMessages(void);
|
||||||
|
int checkPrefixCollisionsOrReply(client *c, robj **prefix, size_t numprefix);
|
||||||
|
|
||||||
/* List data type */
|
/* List data type */
|
||||||
void listTypeTryConversion(robj *subject, robj *value);
|
void listTypeTryConversion(robj *subject, robj *value);
|
||||||
@ -1849,7 +1867,7 @@ void listTypeConvert(robj *subject, int enc);
|
|||||||
robj *listTypeDup(robj *o);
|
robj *listTypeDup(robj *o);
|
||||||
void unblockClientWaitingData(client *c);
|
void unblockClientWaitingData(client *c);
|
||||||
void popGenericCommand(client *c, int where);
|
void popGenericCommand(client *c, int where);
|
||||||
void listElementsRemoved(client *c, robj *key, int where, robj *o);
|
void listElementsRemoved(client *c, robj *key, int where, robj *o, long count);
|
||||||
|
|
||||||
/* MULTI/EXEC/WATCH... */
|
/* MULTI/EXEC/WATCH... */
|
||||||
void unwatchAllKeys(client *c);
|
void unwatchAllKeys(client *c);
|
||||||
@ -1857,12 +1875,12 @@ void initClientMultiState(client *c);
|
|||||||
void freeClientMultiState(client *c);
|
void freeClientMultiState(client *c);
|
||||||
void queueMultiCommand(client *c);
|
void queueMultiCommand(client *c);
|
||||||
void touchWatchedKey(redisDb *db, robj *key);
|
void touchWatchedKey(redisDb *db, robj *key);
|
||||||
void touchWatchedKeysOnFlush(int dbid);
|
void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with);
|
||||||
void discardTransaction(client *c);
|
void discardTransaction(client *c);
|
||||||
void flagTransaction(client *c);
|
void flagTransaction(client *c);
|
||||||
void execCommandAbort(client *c, sds error);
|
void execCommandAbort(client *c, sds error);
|
||||||
void execCommandPropagateMulti(client *c);
|
void execCommandPropagateMulti(int dbid);
|
||||||
void execCommandPropagateExec(client *c);
|
void execCommandPropagateExec(int dbid);
|
||||||
void beforePropagateMultiOrExec(int multi);
|
void beforePropagateMultiOrExec(int multi);
|
||||||
|
|
||||||
/* Redis object implementation */
|
/* Redis object implementation */
|
||||||
@ -1993,13 +2011,15 @@ void restartAOFAfterSYNC();
|
|||||||
/* Child info */
|
/* Child info */
|
||||||
void openChildInfoPipe(void);
|
void openChildInfoPipe(void);
|
||||||
void closeChildInfoPipe(void);
|
void closeChildInfoPipe(void);
|
||||||
void sendChildInfo(int process_type);
|
void sendChildInfo(int process_type, int on_exit, size_t cow_size);
|
||||||
void receiveChildInfo(void);
|
void receiveChildInfo(void);
|
||||||
|
|
||||||
/* Fork helpers */
|
/* Fork helpers */
|
||||||
int redisFork(int type);
|
int redisFork(int type);
|
||||||
int hasActiveChildProcess();
|
int hasActiveChildProcess();
|
||||||
void sendChildCOWInfo(int ptype, char *pname);
|
void resetChildState();
|
||||||
|
int isMutuallyExclusiveChildType(int type);
|
||||||
|
void sendChildCOWInfo(int ptype, int on_exit, char *pname);
|
||||||
|
|
||||||
/* acl.c -- Authentication related prototypes. */
|
/* acl.c -- Authentication related prototypes. */
|
||||||
extern rax *Users;
|
extern rax *Users;
|
||||||
@ -2104,6 +2124,7 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
|||||||
size_t freeMemoryGetNotCountedMemory();
|
size_t freeMemoryGetNotCountedMemory();
|
||||||
int overMaxmemoryAfterAlloc(size_t moremem);
|
int overMaxmemoryAfterAlloc(size_t moremem);
|
||||||
int processCommand(client *c);
|
int processCommand(client *c);
|
||||||
|
int processPendingCommandsAndResetClient(client *c);
|
||||||
void setupSignalHandlers(void);
|
void setupSignalHandlers(void);
|
||||||
void removeSignalHandlers(void);
|
void removeSignalHandlers(void);
|
||||||
struct redisCommand *lookupCommand(sds name);
|
struct redisCommand *lookupCommand(sds name);
|
||||||
@ -2132,7 +2153,9 @@ void updateDictResizePolicy(void);
|
|||||||
int htNeedsResize(dict *dict);
|
int htNeedsResize(dict *dict);
|
||||||
void populateCommandTable(void);
|
void populateCommandTable(void);
|
||||||
void resetCommandTableStats(void);
|
void resetCommandTableStats(void);
|
||||||
|
void resetErrorTableStats(void);
|
||||||
void adjustOpenFilesLimit(void);
|
void adjustOpenFilesLimit(void);
|
||||||
|
void incrementErrorCount(const char *fullerr, size_t namelen);
|
||||||
void closeListeningSockets(int unlink_unix_socket);
|
void closeListeningSockets(int unlink_unix_socket);
|
||||||
void updateCachedTime(int update_daylight_info);
|
void updateCachedTime(int update_daylight_info);
|
||||||
void resetServerStats(void);
|
void resetServerStats(void);
|
||||||
@ -2260,7 +2283,7 @@ void discardDbBackup(dbBackup *buckup, int flags, void(callback)(void*));
|
|||||||
|
|
||||||
int selectDb(client *c, int id);
|
int selectDb(client *c, int id);
|
||||||
void signalModifiedKey(client *c, redisDb *db, robj *key);
|
void signalModifiedKey(client *c, redisDb *db, robj *key);
|
||||||
void signalFlushedDb(int dbid);
|
void signalFlushedDb(int dbid, int async);
|
||||||
unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count);
|
unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count);
|
||||||
unsigned int countKeysInSlot(unsigned int hashslot);
|
unsigned int countKeysInSlot(unsigned int hashslot);
|
||||||
unsigned int delKeysInSlot(unsigned int hashslot);
|
unsigned int delKeysInSlot(unsigned int hashslot);
|
||||||
@ -2507,6 +2530,7 @@ void zinterstoreCommand(client *c);
|
|||||||
void zdiffstoreCommand(client *c);
|
void zdiffstoreCommand(client *c);
|
||||||
void zunionCommand(client *c);
|
void zunionCommand(client *c);
|
||||||
void zinterCommand(client *c);
|
void zinterCommand(client *c);
|
||||||
|
void zrangestoreCommand(client *c);
|
||||||
void zdiffCommand(client *c);
|
void zdiffCommand(client *c);
|
||||||
void zscanCommand(client *c);
|
void zscanCommand(client *c);
|
||||||
void hkeysCommand(client *c);
|
void hkeysCommand(client *c);
|
||||||
@ -2575,6 +2599,7 @@ void xsetidCommand(client *c);
|
|||||||
void xackCommand(client *c);
|
void xackCommand(client *c);
|
||||||
void xpendingCommand(client *c);
|
void xpendingCommand(client *c);
|
||||||
void xclaimCommand(client *c);
|
void xclaimCommand(client *c);
|
||||||
|
void xautoclaimCommand(client *c);
|
||||||
void xinfoCommand(client *c);
|
void xinfoCommand(client *c);
|
||||||
void xdelCommand(client *c);
|
void xdelCommand(client *c);
|
||||||
void xtrimCommand(client *c);
|
void xtrimCommand(client *c);
|
||||||
|
@ -142,11 +142,15 @@ void slowlogReset(void) {
|
|||||||
void slowlogCommand(client *c) {
|
void slowlogCommand(client *c) {
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
"GET [count] -- Return top entries from the slowlog (default: 10)."
|
"GET [<count>]",
|
||||||
" Entries are made of:",
|
" Return top <count> entries from the slowlog (default: 10). Entries are",
|
||||||
" id, timestamp, time in microseconds, arguments array, client IP and port, client name",
|
" made of:",
|
||||||
"LEN -- Return the length of the slowlog.",
|
" id, timestamp, time in microseconds, arguments array, client IP and port,",
|
||||||
"RESET -- Reset the slowlog.",
|
" client name",
|
||||||
|
"LEN",
|
||||||
|
" Return the length of the slowlog.",
|
||||||
|
"RESET",
|
||||||
|
" Reset the slowlog.",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
|
@ -256,7 +256,7 @@ void sortCommand(client *c) {
|
|||||||
getop++;
|
getop++;
|
||||||
j++;
|
j++;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
syntax_error++;
|
syntax_error++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -270,7 +270,7 @@ void sortCommand(client *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Lookup the key to sort. It must be of the right types */
|
/* Lookup the key to sort. It must be of the right types */
|
||||||
if (storekey)
|
if (!storekey)
|
||||||
sortval = lookupKeyRead(c->db,c->argv[1]);
|
sortval = lookupKeyRead(c->db,c->argv[1]);
|
||||||
else
|
else
|
||||||
sortval = lookupKeyWrite(c->db,c->argv[1]);
|
sortval = lookupKeyWrite(c->db,c->argv[1]);
|
||||||
@ -279,7 +279,7 @@ void sortCommand(client *c) {
|
|||||||
sortval->type != OBJ_ZSET)
|
sortval->type != OBJ_ZSET)
|
||||||
{
|
{
|
||||||
listRelease(operations);
|
listRelease(operations);
|
||||||
addReply(c,shared.wrongtypeerr);
|
addReplyErrorObject(c,shared.wrongtypeerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,7 +644,7 @@ void hsetCommand(client *c) {
|
|||||||
}
|
}
|
||||||
signalModifiedKey(c,c->db,c->argv[1]);
|
signalModifiedKey(c,c->db,c->argv[1]);
|
||||||
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||||
server.dirty++;
|
server.dirty += (c->argc - 2)/2;
|
||||||
}
|
}
|
||||||
|
|
||||||
void hincrbyCommand(client *c) {
|
void hincrbyCommand(client *c) {
|
||||||
|
262
src/t_list.c
262
src/t_list.c
@ -206,7 +206,7 @@ robj *listTypeDup(robj *o) {
|
|||||||
lobj->encoding = OBJ_ENCODING_QUICKLIST;
|
lobj->encoding = OBJ_ENCODING_QUICKLIST;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
serverPanic("Wrong encoding.");
|
serverPanic("Unknown list encoding");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return lobj;
|
return lobj;
|
||||||
@ -216,72 +216,58 @@ robj *listTypeDup(robj *o) {
|
|||||||
* List Commands
|
* List Commands
|
||||||
*----------------------------------------------------------------------------*/
|
*----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
void pushGenericCommand(client *c, int where) {
|
/* Implements LPUSH/RPUSH/LPUSHX/RPUSHX.
|
||||||
int j, pushed = 0;
|
* 'xx': push if key exists. */
|
||||||
robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
|
void pushGenericCommand(client *c, int where, int xx) {
|
||||||
|
int j;
|
||||||
|
|
||||||
if (checkType(c,lobj,OBJ_LIST)) {
|
robj *lobj = lookupKeyWrite(c->db, c->argv[1]);
|
||||||
return;
|
if (checkType(c,lobj,OBJ_LIST)) return;
|
||||||
}
|
if (!lobj) {
|
||||||
|
if (xx) {
|
||||||
for (j = 2; j < c->argc; j++) {
|
addReply(c, shared.czero);
|
||||||
if (!lobj) {
|
return;
|
||||||
lobj = createQuicklistObject();
|
|
||||||
quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
|
|
||||||
server.list_compress_depth);
|
|
||||||
dbAdd(c->db,c->argv[1],lobj);
|
|
||||||
}
|
}
|
||||||
listTypePush(lobj,c->argv[j],where);
|
|
||||||
pushed++;
|
lobj = createQuicklistObject();
|
||||||
|
quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
|
||||||
|
server.list_compress_depth);
|
||||||
|
dbAdd(c->db,c->argv[1],lobj);
|
||||||
}
|
}
|
||||||
addReplyLongLong(c, (lobj ? listTypeLength(lobj) : 0));
|
|
||||||
if (pushed) {
|
|
||||||
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
|
|
||||||
|
|
||||||
signalModifiedKey(c,c->db,c->argv[1]);
|
|
||||||
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
|
|
||||||
}
|
|
||||||
server.dirty += pushed;
|
|
||||||
}
|
|
||||||
|
|
||||||
void lpushCommand(client *c) {
|
|
||||||
pushGenericCommand(c,LIST_HEAD);
|
|
||||||
}
|
|
||||||
|
|
||||||
void rpushCommand(client *c) {
|
|
||||||
pushGenericCommand(c,LIST_TAIL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pushxGenericCommand(client *c, int where) {
|
|
||||||
int j, pushed = 0;
|
|
||||||
robj *subject;
|
|
||||||
|
|
||||||
if ((subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
|
||||||
checkType(c,subject,OBJ_LIST)) return;
|
|
||||||
|
|
||||||
for (j = 2; j < c->argc; j++) {
|
for (j = 2; j < c->argc; j++) {
|
||||||
listTypePush(subject,c->argv[j],where);
|
listTypePush(lobj,c->argv[j],where);
|
||||||
pushed++;
|
server.dirty++;
|
||||||
}
|
}
|
||||||
|
|
||||||
addReplyLongLong(c,listTypeLength(subject));
|
addReplyLongLong(c, listTypeLength(lobj));
|
||||||
|
|
||||||
if (pushed) {
|
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
|
||||||
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
|
signalModifiedKey(c,c->db,c->argv[1]);
|
||||||
signalModifiedKey(c,c->db,c->argv[1]);
|
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
|
||||||
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
|
|
||||||
}
|
|
||||||
server.dirty += pushed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LPUSH <key> <element> [<element> ...] */
|
||||||
|
void lpushCommand(client *c) {
|
||||||
|
pushGenericCommand(c,LIST_HEAD,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RPUSH <key> <element> [<element> ...] */
|
||||||
|
void rpushCommand(client *c) {
|
||||||
|
pushGenericCommand(c,LIST_TAIL,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LPUSHX <key> <element> [<element> ...] */
|
||||||
void lpushxCommand(client *c) {
|
void lpushxCommand(client *c) {
|
||||||
pushxGenericCommand(c,LIST_HEAD);
|
pushGenericCommand(c,LIST_HEAD,1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* RPUSH <key> <element> [<element> ...] */
|
||||||
void rpushxCommand(client *c) {
|
void rpushxCommand(client *c) {
|
||||||
pushxGenericCommand(c,LIST_TAIL);
|
pushGenericCommand(c,LIST_TAIL,1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LINSERT <key> (BEFORE|AFTER) <pivot> <element> */
|
||||||
void linsertCommand(client *c) {
|
void linsertCommand(client *c) {
|
||||||
int where;
|
int where;
|
||||||
robj *subject;
|
robj *subject;
|
||||||
@ -294,7 +280,7 @@ void linsertCommand(client *c) {
|
|||||||
} else if (strcasecmp(c->argv[2]->ptr,"before") == 0) {
|
} else if (strcasecmp(c->argv[2]->ptr,"before") == 0) {
|
||||||
where = LIST_HEAD;
|
where = LIST_HEAD;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,12 +312,14 @@ void linsertCommand(client *c) {
|
|||||||
addReplyLongLong(c,listTypeLength(subject));
|
addReplyLongLong(c,listTypeLength(subject));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LLEN <key> */
|
||||||
void llenCommand(client *c) {
|
void llenCommand(client *c) {
|
||||||
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
|
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
|
||||||
if (o == NULL || checkType(c,o,OBJ_LIST)) return;
|
if (o == NULL || checkType(c,o,OBJ_LIST)) return;
|
||||||
addReplyLongLong(c,listTypeLength(o));
|
addReplyLongLong(c,listTypeLength(o));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LINDEX <key> <index> */
|
||||||
void lindexCommand(client *c) {
|
void lindexCommand(client *c) {
|
||||||
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]);
|
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]);
|
||||||
if (o == NULL || checkType(c,o,OBJ_LIST)) return;
|
if (o == NULL || checkType(c,o,OBJ_LIST)) return;
|
||||||
@ -359,6 +347,7 @@ void lindexCommand(client *c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LSET <key> <index> <element> */
|
||||||
void lsetCommand(client *c) {
|
void lsetCommand(client *c) {
|
||||||
robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
|
robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
|
||||||
if (o == NULL || checkType(c,o,OBJ_LIST)) return;
|
if (o == NULL || checkType(c,o,OBJ_LIST)) return;
|
||||||
@ -373,7 +362,7 @@ void lsetCommand(client *c) {
|
|||||||
int replaced = quicklistReplaceAtIndex(ql, index,
|
int replaced = quicklistReplaceAtIndex(ql, index,
|
||||||
value->ptr, sdslen(value->ptr));
|
value->ptr, sdslen(value->ptr));
|
||||||
if (!replaced) {
|
if (!replaced) {
|
||||||
addReply(c,shared.outofrangeerr);
|
addReplyErrorObject(c,shared.outofrangeerr);
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
signalModifiedKey(c,c->db,c->argv[1]);
|
signalModifiedKey(c,c->db,c->argv[1]);
|
||||||
@ -385,53 +374,15 @@ void lsetCommand(client *c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void listElementsRemoved(client *c, robj *key, int where, robj *o) {
|
/* A helper for replying with a list's range between the inclusive start and end
|
||||||
char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
|
* indexes as multi-bulk, with support for negative indexes. Note that start
|
||||||
|
* must be less than end or an empty array is returned. When the reverse
|
||||||
|
* argument is set to a non-zero value, the reply is reversed so that elements
|
||||||
|
* are returned from end to start. */
|
||||||
|
void addListRangeReply(client *c, robj *o, long start, long end, int reverse) {
|
||||||
|
long rangelen, llen = listTypeLength(o);
|
||||||
|
|
||||||
notifyKeyspaceEvent(NOTIFY_LIST, event, key, c->db->id);
|
/* Convert negative indexes. */
|
||||||
if (listTypeLength(o) == 0) {
|
|
||||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", key, c->db->id);
|
|
||||||
dbDelete(c->db, key);
|
|
||||||
}
|
|
||||||
signalModifiedKey(c, c->db, key);
|
|
||||||
server.dirty++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void popGenericCommand(client *c, int where) {
|
|
||||||
robj *o = lookupKeyWriteOrReply(c, c->argv[1], shared.null[c->resp]);
|
|
||||||
if (o == NULL || checkType(c, o, OBJ_LIST))
|
|
||||||
return;
|
|
||||||
|
|
||||||
robj *value = listTypePop(o, where);
|
|
||||||
if (value == NULL) {
|
|
||||||
addReplyNull(c);
|
|
||||||
} else {
|
|
||||||
addReplyBulk(c,value);
|
|
||||||
decrRefCount(value);
|
|
||||||
listElementsRemoved(c,c->argv[1],where,o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void lpopCommand(client *c) {
|
|
||||||
popGenericCommand(c,LIST_HEAD);
|
|
||||||
}
|
|
||||||
|
|
||||||
void rpopCommand(client *c) {
|
|
||||||
popGenericCommand(c,LIST_TAIL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void lrangeCommand(client *c) {
|
|
||||||
robj *o;
|
|
||||||
long start, end, llen, rangelen;
|
|
||||||
|
|
||||||
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
|
|
||||||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
|
|
||||||
|
|
||||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyarray)) == NULL
|
|
||||||
|| checkType(c,o,OBJ_LIST)) return;
|
|
||||||
llen = listTypeLength(o);
|
|
||||||
|
|
||||||
/* convert negative indexes */
|
|
||||||
if (start < 0) start = llen+start;
|
if (start < 0) start = llen+start;
|
||||||
if (end < 0) end = llen+end;
|
if (end < 0) end = llen+end;
|
||||||
if (start < 0) start = 0;
|
if (start < 0) start = 0;
|
||||||
@ -448,7 +399,9 @@ void lrangeCommand(client *c) {
|
|||||||
/* Return the result in form of a multi-bulk reply */
|
/* Return the result in form of a multi-bulk reply */
|
||||||
addReplyArrayLen(c,rangelen);
|
addReplyArrayLen(c,rangelen);
|
||||||
if (o->encoding == OBJ_ENCODING_QUICKLIST) {
|
if (o->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||||
listTypeIterator *iter = listTypeInitIterator(o, start, LIST_TAIL);
|
int from = reverse ? end : start;
|
||||||
|
int direction = reverse ? LIST_HEAD : LIST_TAIL;
|
||||||
|
listTypeIterator *iter = listTypeInitIterator(o,from,direction);
|
||||||
|
|
||||||
while(rangelen--) {
|
while(rangelen--) {
|
||||||
listTypeEntry entry;
|
listTypeEntry entry;
|
||||||
@ -462,10 +415,98 @@ void lrangeCommand(client *c) {
|
|||||||
}
|
}
|
||||||
listTypeReleaseIterator(iter);
|
listTypeReleaseIterator(iter);
|
||||||
} else {
|
} else {
|
||||||
serverPanic("List encoding is not QUICKLIST!");
|
serverPanic("Unknown list encoding");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A housekeeping helper for list elements popping tasks. */
|
||||||
|
void listElementsRemoved(client *c, robj *key, int where, robj *o, long count) {
|
||||||
|
char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
|
||||||
|
|
||||||
|
notifyKeyspaceEvent(NOTIFY_LIST, event, key, c->db->id);
|
||||||
|
if (listTypeLength(o) == 0) {
|
||||||
|
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", key, c->db->id);
|
||||||
|
dbDelete(c->db, key);
|
||||||
|
}
|
||||||
|
signalModifiedKey(c, c->db, key);
|
||||||
|
server.dirty += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implements the generic list pop operation for LPOP/RPOP.
|
||||||
|
* The where argument specifies which end of the list is operated on. An
|
||||||
|
* optional count may be provided as the third argument of the client's
|
||||||
|
* command. */
|
||||||
|
void popGenericCommand(client *c, int where) {
|
||||||
|
long count = 0;
|
||||||
|
robj *value;
|
||||||
|
|
||||||
|
if (c->argc > 3) {
|
||||||
|
addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
|
||||||
|
c->cmd->name);
|
||||||
|
return;
|
||||||
|
} else if (c->argc == 3) {
|
||||||
|
/* Parse the optional count argument. */
|
||||||
|
if (getPositiveLongFromObjectOrReply(c,c->argv[2],&count,NULL) != C_OK)
|
||||||
|
return;
|
||||||
|
if (count == 0) {
|
||||||
|
/* Fast exit path. */
|
||||||
|
addReplyNullArray(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
robj *o = lookupKeyWriteOrReply(c, c->argv[1], shared.null[c->resp]);
|
||||||
|
if (o == NULL || checkType(c, o, OBJ_LIST))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!count) {
|
||||||
|
/* Pop a single element. This is POP's original behavior that replies
|
||||||
|
* with a bulk string. */
|
||||||
|
value = listTypePop(o,where);
|
||||||
|
serverAssert(value != NULL);
|
||||||
|
addReplyBulk(c,value);
|
||||||
|
decrRefCount(value);
|
||||||
|
listElementsRemoved(c,c->argv[1],where,o,1);
|
||||||
|
} else {
|
||||||
|
/* Pop a range of elements. An addition to the original POP command,
|
||||||
|
* which replies with a multi-bulk. */
|
||||||
|
long llen = listTypeLength(o);
|
||||||
|
long rangelen = (count > llen) ? llen : count;
|
||||||
|
long rangestart = (where == LIST_HEAD) ? 0 : -rangelen;
|
||||||
|
long rangeend = (where == LIST_HEAD) ? rangelen - 1 : -1;
|
||||||
|
int reverse = (where == LIST_HEAD) ? 0 : 1;
|
||||||
|
|
||||||
|
addListRangeReply(c,o,rangestart,rangeend,reverse);
|
||||||
|
quicklistDelRange(o->ptr,rangestart,rangelen);
|
||||||
|
listElementsRemoved(c,c->argv[1],where,o,rangelen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LPOP <key> [count] */
|
||||||
|
void lpopCommand(client *c) {
|
||||||
|
popGenericCommand(c,LIST_HEAD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RPOP <key> [count] */
|
||||||
|
void rpopCommand(client *c) {
|
||||||
|
popGenericCommand(c,LIST_TAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LRANGE <key> <start> <stop> */
|
||||||
|
void lrangeCommand(client *c) {
|
||||||
|
robj *o;
|
||||||
|
long start, end;
|
||||||
|
|
||||||
|
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
|
||||||
|
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
|
||||||
|
|
||||||
|
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyarray)) == NULL
|
||||||
|
|| checkType(c,o,OBJ_LIST)) return;
|
||||||
|
|
||||||
|
addListRangeReply(c,o,start,end,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LTRIM <key> <start> <stop> */
|
||||||
void ltrimCommand(client *c) {
|
void ltrimCommand(client *c) {
|
||||||
robj *o;
|
robj *o;
|
||||||
long start, end, llen, ltrim, rtrim;
|
long start, end, llen, ltrim, rtrim;
|
||||||
@ -508,7 +549,7 @@ void ltrimCommand(client *c) {
|
|||||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||||
}
|
}
|
||||||
signalModifiedKey(c,c->db,c->argv[1]);
|
signalModifiedKey(c,c->db,c->argv[1]);
|
||||||
server.dirty++;
|
server.dirty += (ltrim + rtrim);
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -566,7 +607,7 @@ void lposCommand(client *c) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -629,6 +670,7 @@ void lposCommand(client *c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LREM <key> <count> <element> */
|
||||||
void lremCommand(client *c) {
|
void lremCommand(client *c) {
|
||||||
robj *subject, *obj;
|
robj *subject, *obj;
|
||||||
obj = c->argv[3];
|
obj = c->argv[3];
|
||||||
@ -698,7 +740,7 @@ int getListPositionFromObjectOrReply(client *c, robj *arg, int *position) {
|
|||||||
} else if (strcasecmp(arg->ptr,"left") == 0) {
|
} else if (strcasecmp(arg->ptr,"left") == 0) {
|
||||||
*position = LIST_HEAD;
|
*position = LIST_HEAD;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
return C_OK;
|
return C_OK;
|
||||||
@ -729,10 +771,6 @@ void lmoveGenericCommand(client *c, int wherefrom, int whereto) {
|
|||||||
if (checkType(c,dobj,OBJ_LIST)) return;
|
if (checkType(c,dobj,OBJ_LIST)) return;
|
||||||
value = listTypePop(sobj,wherefrom);
|
value = listTypePop(sobj,wherefrom);
|
||||||
serverAssert(value); /* assertion for valgrind (avoid NPD) */
|
serverAssert(value); /* assertion for valgrind (avoid NPD) */
|
||||||
/* We saved touched key, and protect it, since lmoveHandlePush
|
|
||||||
* may change the client command argument vector (it does not
|
|
||||||
* currently). */
|
|
||||||
incrRefCount(touchedkey);
|
|
||||||
lmoveHandlePush(c,c->argv[2],dobj,value,whereto);
|
lmoveHandlePush(c,c->argv[2],dobj,value,whereto);
|
||||||
|
|
||||||
/* listTypePop returns an object with its refcount incremented */
|
/* listTypePop returns an object with its refcount incremented */
|
||||||
@ -749,7 +787,6 @@ void lmoveGenericCommand(client *c, int wherefrom, int whereto) {
|
|||||||
touchedkey,c->db->id);
|
touchedkey,c->db->id);
|
||||||
}
|
}
|
||||||
signalModifiedKey(c,c->db,touchedkey);
|
signalModifiedKey(c,c->db,touchedkey);
|
||||||
decrRefCount(touchedkey);
|
|
||||||
server.dirty++;
|
server.dirty++;
|
||||||
if (c->cmd->proc == blmoveCommand) {
|
if (c->cmd->proc == blmoveCommand) {
|
||||||
rewriteClientCommandVector(c,5,shared.lmove,
|
rewriteClientCommandVector(c,5,shared.lmove,
|
||||||
@ -761,6 +798,7 @@ void lmoveGenericCommand(client *c, int wherefrom, int whereto) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LMOVE <source> <destination> (LEFT|RIGHT) (LEFT|RIGHT) */
|
||||||
void lmoveCommand(client *c) {
|
void lmoveCommand(client *c) {
|
||||||
int wherefrom, whereto;
|
int wherefrom, whereto;
|
||||||
if (getListPositionFromObjectOrReply(c,c->argv[3],&wherefrom)
|
if (getListPositionFromObjectOrReply(c,c->argv[3],&wherefrom)
|
||||||
@ -893,7 +931,7 @@ void blockingPopGenericCommand(client *c, int where) {
|
|||||||
addReplyBulk(c,c->argv[j]);
|
addReplyBulk(c,c->argv[j]);
|
||||||
addReplyBulk(c,value);
|
addReplyBulk(c,value);
|
||||||
decrRefCount(value);
|
decrRefCount(value);
|
||||||
listElementsRemoved(c,c->argv[j],where,o);
|
listElementsRemoved(c,c->argv[j],where,o,1);
|
||||||
|
|
||||||
/* Replicate it as an [LR]POP instead of B[LR]POP. */
|
/* Replicate it as an [LR]POP instead of B[LR]POP. */
|
||||||
rewriteClientCommandVector(c,2,
|
rewriteClientCommandVector(c,2,
|
||||||
@ -917,10 +955,12 @@ void blockingPopGenericCommand(client *c, int where) {
|
|||||||
blockForKeys(c,BLOCKED_LIST,c->argv + 1,c->argc - 2,timeout,NULL,&pos,NULL);
|
blockForKeys(c,BLOCKED_LIST,c->argv + 1,c->argc - 2,timeout,NULL,&pos,NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* BLPOP <key> [<key> ...] <timeout> */
|
||||||
void blpopCommand(client *c) {
|
void blpopCommand(client *c) {
|
||||||
blockingPopGenericCommand(c,LIST_HEAD);
|
blockingPopGenericCommand(c,LIST_HEAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* BLPOP <key> [<key> ...] <timeout> */
|
||||||
void brpopCommand(client *c) {
|
void brpopCommand(client *c) {
|
||||||
blockingPopGenericCommand(c,LIST_TAIL);
|
blockingPopGenericCommand(c,LIST_TAIL);
|
||||||
}
|
}
|
||||||
@ -947,6 +987,7 @@ void blmoveGenericCommand(client *c, int wherefrom, int whereto, mstime_t timeou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* BLMOVE <source> <destination> (LEFT|RIGHT) (LEFT|RIGHT) <timeout> */
|
||||||
void blmoveCommand(client *c) {
|
void blmoveCommand(client *c) {
|
||||||
mstime_t timeout;
|
mstime_t timeout;
|
||||||
int wherefrom, whereto;
|
int wherefrom, whereto;
|
||||||
@ -959,6 +1000,7 @@ void blmoveCommand(client *c) {
|
|||||||
blmoveGenericCommand(c,wherefrom,whereto,timeout);
|
blmoveGenericCommand(c,wherefrom,whereto,timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* BRPOPLPUSH <source> <destination> <timeout> */
|
||||||
void brpoplpushCommand(client *c) {
|
void brpoplpushCommand(client *c) {
|
||||||
mstime_t timeout;
|
mstime_t timeout;
|
||||||
if (getTimeoutFromObjectOrReply(c,c->argv[3],&timeout,UNIT_SECONDS)
|
if (getTimeoutFromObjectOrReply(c,c->argv[3],&timeout,UNIT_SECONDS)
|
||||||
|
@ -476,7 +476,7 @@ void spopWithCountCommand(client *c) {
|
|||||||
|
|
||||||
/* Generate an SPOP keyspace notification */
|
/* Generate an SPOP keyspace notification */
|
||||||
notifyKeyspaceEvent(NOTIFY_SET,"spop",c->argv[1],c->db->id);
|
notifyKeyspaceEvent(NOTIFY_SET,"spop",c->argv[1],c->db->id);
|
||||||
server.dirty += count;
|
server.dirty += (count >= size) ? size : count;
|
||||||
|
|
||||||
/* CASE 1:
|
/* CASE 1:
|
||||||
* The number of requested elements is greater than or equal to
|
* The number of requested elements is greater than or equal to
|
||||||
@ -492,7 +492,6 @@ void spopWithCountCommand(client *c) {
|
|||||||
/* Propagate this command as a DEL operation */
|
/* Propagate this command as a DEL operation */
|
||||||
rewriteClientCommandVector(c,2,shared.del,c->argv[1]);
|
rewriteClientCommandVector(c,2,shared.del,c->argv[1]);
|
||||||
signalModifiedKey(c,c->db,c->argv[1]);
|
signalModifiedKey(c,c->db,c->argv[1]);
|
||||||
server.dirty++;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -594,7 +593,6 @@ void spopWithCountCommand(client *c) {
|
|||||||
decrRefCount(propargv[0]);
|
decrRefCount(propargv[0]);
|
||||||
preventCommandPropagation(c);
|
preventCommandPropagation(c);
|
||||||
signalModifiedKey(c,c->db,c->argv[1]);
|
signalModifiedKey(c,c->db,c->argv[1]);
|
||||||
server.dirty++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void spopCommand(client *c) {
|
void spopCommand(client *c) {
|
||||||
@ -607,7 +605,7 @@ void spopCommand(client *c) {
|
|||||||
spopWithCountCommand(c);
|
spopWithCountCommand(c);
|
||||||
return;
|
return;
|
||||||
} else if (c->argc > 3) {
|
} else if (c->argc > 3) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -803,7 +801,7 @@ void srandmemberCommand(client *c) {
|
|||||||
srandmemberWithCountCommand(c);
|
srandmemberWithCountCommand(c);
|
||||||
return;
|
return;
|
||||||
} else if (c->argc > 3) {
|
} else if (c->argc > 3) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
810
src/t_stream.c
810
src/t_stream.c
File diff suppressed because it is too large
Load Diff
@ -153,7 +153,7 @@ void setCommand(client *c) {
|
|||||||
expire = next;
|
expire = next;
|
||||||
j++;
|
j++;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -528,7 +528,7 @@ void stralgoCommand(client *c) {
|
|||||||
if (!strcasecmp(c->argv[1]->ptr,"lcs")) {
|
if (!strcasecmp(c->argv[1]->ptr,"lcs")) {
|
||||||
stralgoLCS(c);
|
stralgoLCS(c);
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -589,7 +589,7 @@ void stralgoLCS(client *c) {
|
|||||||
b = objb->ptr;
|
b = objb->ptr;
|
||||||
j += 2;
|
j += 2;
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
681
src/t_zset.c
681
src/t_zset.c
@ -1701,7 +1701,7 @@ void zaddGenericCommand(client *c, int flags) {
|
|||||||
* we expect any number of score-element pairs. */
|
* we expect any number of score-element pairs. */
|
||||||
elements = c->argc-scoreidx;
|
elements = c->argc-scoreidx;
|
||||||
if (elements % 2 || !elements) {
|
if (elements % 2 || !elements) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
elements /= 2; /* Now this holds the number of score-element pairs. */
|
elements /= 2; /* Now this holds the number of score-element pairs. */
|
||||||
@ -1822,11 +1822,15 @@ void zremCommand(client *c) {
|
|||||||
addReplyLongLong(c,deleted);
|
addReplyLongLong(c,deleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ZRANGE_AUTO = 0,
|
||||||
|
ZRANGE_RANK,
|
||||||
|
ZRANGE_SCORE,
|
||||||
|
ZRANGE_LEX,
|
||||||
|
} zrange_type;
|
||||||
|
|
||||||
/* Implements ZREMRANGEBYRANK, ZREMRANGEBYSCORE, ZREMRANGEBYLEX commands. */
|
/* Implements ZREMRANGEBYRANK, ZREMRANGEBYSCORE, ZREMRANGEBYLEX commands. */
|
||||||
#define ZRANGE_RANK 0
|
void zremrangeGenericCommand(client *c, zrange_type rangetype) {
|
||||||
#define ZRANGE_SCORE 1
|
|
||||||
#define ZRANGE_LEX 2
|
|
||||||
void zremrangeGenericCommand(client *c, int rangetype) {
|
|
||||||
robj *key = c->argv[1];
|
robj *key = c->argv[1];
|
||||||
robj *zobj;
|
robj *zobj;
|
||||||
int keyremoved = 0;
|
int keyremoved = 0;
|
||||||
@ -1834,22 +1838,28 @@ void zremrangeGenericCommand(client *c, int rangetype) {
|
|||||||
zrangespec range;
|
zrangespec range;
|
||||||
zlexrangespec lexrange;
|
zlexrangespec lexrange;
|
||||||
long start, end, llen;
|
long start, end, llen;
|
||||||
|
char *notify_type = NULL;
|
||||||
|
|
||||||
/* Step 1: Parse the range. */
|
/* Step 1: Parse the range. */
|
||||||
if (rangetype == ZRANGE_RANK) {
|
if (rangetype == ZRANGE_RANK) {
|
||||||
|
notify_type = "zremrangebyrank";
|
||||||
if ((getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK) ||
|
if ((getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != C_OK) ||
|
||||||
(getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK))
|
(getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != C_OK))
|
||||||
return;
|
return;
|
||||||
} else if (rangetype == ZRANGE_SCORE) {
|
} else if (rangetype == ZRANGE_SCORE) {
|
||||||
|
notify_type = "zremrangebyscore";
|
||||||
if (zslParseRange(c->argv[2],c->argv[3],&range) != C_OK) {
|
if (zslParseRange(c->argv[2],c->argv[3],&range) != C_OK) {
|
||||||
addReplyError(c,"min or max is not a float");
|
addReplyError(c,"min or max is not a float");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (rangetype == ZRANGE_LEX) {
|
} else if (rangetype == ZRANGE_LEX) {
|
||||||
|
notify_type = "zremrangebylex";
|
||||||
if (zslParseLexRange(c->argv[2],c->argv[3],&lexrange) != C_OK) {
|
if (zslParseLexRange(c->argv[2],c->argv[3],&lexrange) != C_OK) {
|
||||||
addReplyError(c,"min or max not valid string range item");
|
addReplyError(c,"min or max not valid string range item");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
serverPanic("unknown rangetype %d", (int)rangetype);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Step 2: Lookup & range sanity checks if needed. */
|
/* Step 2: Lookup & range sanity checks if needed. */
|
||||||
@ -1875,6 +1885,7 @@ void zremrangeGenericCommand(client *c, int rangetype) {
|
|||||||
/* Step 3: Perform the range deletion operation. */
|
/* Step 3: Perform the range deletion operation. */
|
||||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||||
switch(rangetype) {
|
switch(rangetype) {
|
||||||
|
case ZRANGE_AUTO:
|
||||||
case ZRANGE_RANK:
|
case ZRANGE_RANK:
|
||||||
zobj->ptr = zzlDeleteRangeByRank(zobj->ptr,start+1,end+1,&deleted);
|
zobj->ptr = zzlDeleteRangeByRank(zobj->ptr,start+1,end+1,&deleted);
|
||||||
break;
|
break;
|
||||||
@ -1892,6 +1903,7 @@ void zremrangeGenericCommand(client *c, int rangetype) {
|
|||||||
} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
|
} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||||
zset *zs = zobj->ptr;
|
zset *zs = zobj->ptr;
|
||||||
switch(rangetype) {
|
switch(rangetype) {
|
||||||
|
case ZRANGE_AUTO:
|
||||||
case ZRANGE_RANK:
|
case ZRANGE_RANK:
|
||||||
deleted = zslDeleteRangeByRank(zs->zsl,start+1,end+1,zs->dict);
|
deleted = zslDeleteRangeByRank(zs->zsl,start+1,end+1,zs->dict);
|
||||||
break;
|
break;
|
||||||
@ -1913,9 +1925,8 @@ void zremrangeGenericCommand(client *c, int rangetype) {
|
|||||||
|
|
||||||
/* Step 4: Notifications and reply. */
|
/* Step 4: Notifications and reply. */
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
char *event[3] = {"zremrangebyrank","zremrangebyscore","zremrangebylex"};
|
|
||||||
signalModifiedKey(c,c->db,key);
|
signalModifiedKey(c,c->db,key);
|
||||||
notifyKeyspaceEvent(NOTIFY_ZSET,event[rangetype],key,c->db->id);
|
notifyKeyspaceEvent(NOTIFY_ZSET,notify_type,key,c->db->id);
|
||||||
if (keyremoved)
|
if (keyremoved)
|
||||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||||
}
|
}
|
||||||
@ -2525,7 +2536,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
|||||||
|
|
||||||
/* test if the expected number of keys would overflow */
|
/* test if the expected number of keys would overflow */
|
||||||
if (setnum > (c->argc-(numkeysIndex+1))) {
|
if (setnum > (c->argc-(numkeysIndex+1))) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2536,7 +2547,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
|||||||
if (obj != NULL) {
|
if (obj != NULL) {
|
||||||
if (obj->type != OBJ_ZSET && obj->type != OBJ_SET) {
|
if (obj->type != OBJ_ZSET && obj->type != OBJ_SET) {
|
||||||
zfree(src);
|
zfree(src);
|
||||||
addReply(c,shared.wrongtypeerr);
|
addReplyErrorObject(c,shared.wrongtypeerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2582,7 +2593,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
|||||||
aggregate = REDIS_AGGR_MAX;
|
aggregate = REDIS_AGGR_MAX;
|
||||||
} else {
|
} else {
|
||||||
zfree(src);
|
zfree(src);
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
j++; remaining--;
|
j++; remaining--;
|
||||||
@ -2594,7 +2605,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
|||||||
withscores = 1;
|
withscores = 1;
|
||||||
} else {
|
} else {
|
||||||
zfree(src);
|
zfree(src);
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2778,27 +2789,180 @@ void zdiffCommand(client *c) {
|
|||||||
zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_DIFF);
|
zunionInterDiffGenericCommand(c, NULL, 1, SET_OP_DIFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void zrangeGenericCommand(client *c, int reverse) {
|
typedef enum {
|
||||||
robj *key = c->argv[1];
|
ZRANGE_DIRECTION_AUTO = 0,
|
||||||
robj *zobj;
|
ZRANGE_DIRECTION_FORWARD,
|
||||||
int withscores = 0;
|
ZRANGE_DIRECTION_REVERSE
|
||||||
long start;
|
} zrange_direction;
|
||||||
long end;
|
|
||||||
long llen;
|
|
||||||
long rangelen;
|
|
||||||
|
|
||||||
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
|
typedef enum {
|
||||||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
|
ZRANGE_CONSUMER_TYPE_CLIENT = 0,
|
||||||
|
ZRANGE_CONSUMER_TYPE_INTERNAL
|
||||||
|
} zrange_consumer_type;
|
||||||
|
|
||||||
if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) {
|
typedef struct zrange_result_handler zrange_result_handler;
|
||||||
withscores = 1;
|
|
||||||
} else if (c->argc >= 5) {
|
typedef void (*zrangeResultBeginFunction)(zrange_result_handler *c);
|
||||||
addReply(c,shared.syntaxerr);
|
typedef void (*zrangeResultFinalizeFunction)(
|
||||||
return;
|
zrange_result_handler *c, size_t result_count);
|
||||||
|
typedef void (*zrangeResultEmitCBufferFunction)(
|
||||||
|
zrange_result_handler *c, const void *p, size_t len, double score);
|
||||||
|
typedef void (*zrangeResultEmitLongLongFunction)(
|
||||||
|
zrange_result_handler *c, long long ll, double score);
|
||||||
|
|
||||||
|
void zrangeGenericCommand (zrange_result_handler *handler, int argc_start, int store,
|
||||||
|
zrange_type rangetype, zrange_direction direction);
|
||||||
|
|
||||||
|
/* Interface struct for ZRANGE/ZRANGESTORE generic implementation.
|
||||||
|
* There is one implementation of this interface that sends a RESP reply to clients.
|
||||||
|
* and one implementation that stores the range result into a zset object. */
|
||||||
|
struct zrange_result_handler {
|
||||||
|
zrange_consumer_type type;
|
||||||
|
client *client;
|
||||||
|
robj *dstkey;
|
||||||
|
robj *dstobj;
|
||||||
|
void *userdata;
|
||||||
|
int withscores;
|
||||||
|
int should_emit_array_length;
|
||||||
|
zrangeResultBeginFunction beginResultEmission;
|
||||||
|
zrangeResultFinalizeFunction finalizeResultEmission;
|
||||||
|
zrangeResultEmitCBufferFunction emitResultFromCBuffer;
|
||||||
|
zrangeResultEmitLongLongFunction emitResultFromLongLong;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Result handler methods for responding the ZRANGE to clients. */
|
||||||
|
static void zrangeResultBeginClient(zrange_result_handler *handler) {
|
||||||
|
handler->userdata = addReplyDeferredLen(handler->client);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void zrangeResultEmitCBufferToClient(zrange_result_handler *handler,
|
||||||
|
const void *value, size_t value_length_in_bytes, double score)
|
||||||
|
{
|
||||||
|
if (handler->should_emit_array_length) {
|
||||||
|
addReplyArrayLen(handler->client, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL
|
addReplyBulkCBuffer(handler->client, value, value_length_in_bytes);
|
||||||
|| checkType(c,zobj,OBJ_ZSET)) return;
|
|
||||||
|
if (handler->withscores) {
|
||||||
|
addReplyDouble(handler->client, score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void zrangeResultEmitLongLongToClient(zrange_result_handler *handler,
|
||||||
|
long long value, double score)
|
||||||
|
{
|
||||||
|
if (handler->should_emit_array_length) {
|
||||||
|
addReplyArrayLen(handler->client, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
addReplyBulkLongLong(handler->client, value);
|
||||||
|
|
||||||
|
if (handler->withscores) {
|
||||||
|
addReplyDouble(handler->client, score);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void zrangeResultFinalizeClient(zrange_result_handler *handler,
|
||||||
|
size_t result_count)
|
||||||
|
{
|
||||||
|
if (handler->withscores && (handler->client->resp == 2)) {
|
||||||
|
result_count *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDeferredArrayLen(handler->client, handler->userdata, result_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Result handler methods for storing the ZRANGESTORE to a zset. */
|
||||||
|
static void zrangeResultBeginStore(zrange_result_handler *handler)
|
||||||
|
{
|
||||||
|
handler->dstobj = createZsetZiplistObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void zrangeResultEmitCBufferForStore(zrange_result_handler *handler,
|
||||||
|
const void *value, size_t value_length_in_bytes, double score)
|
||||||
|
{
|
||||||
|
double newscore;
|
||||||
|
int retflags = 0;
|
||||||
|
sds ele = sdsnewlen(value, value_length_in_bytes);
|
||||||
|
int retval = zsetAdd(handler->dstobj, score, ele, &retflags, &newscore);
|
||||||
|
sdsfree(ele);
|
||||||
|
serverAssert(retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void zrangeResultEmitLongLongForStore(zrange_result_handler *handler,
|
||||||
|
long long value, double score)
|
||||||
|
{
|
||||||
|
double newscore;
|
||||||
|
int retflags = 0;
|
||||||
|
sds ele = sdsfromlonglong(value);
|
||||||
|
int retval = zsetAdd(handler->dstobj, score, ele, &retflags, &newscore);
|
||||||
|
sdsfree(ele);
|
||||||
|
serverAssert(retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void zrangeResultFinalizeStore(zrange_result_handler *handler, size_t result_count)
|
||||||
|
{
|
||||||
|
if (result_count) {
|
||||||
|
setKey(handler->client, handler->client->db, handler->dstkey, handler->dstobj);
|
||||||
|
addReplyLongLong(handler->client, result_count);
|
||||||
|
notifyKeyspaceEvent(NOTIFY_ZSET, "zrangestore", handler->dstkey, handler->client->db->id);
|
||||||
|
server.dirty++;
|
||||||
|
} else {
|
||||||
|
addReply(handler->client, shared.czero);
|
||||||
|
if (dbDelete(handler->client->db, handler->dstkey)) {
|
||||||
|
signalModifiedKey(handler->client, handler->client->db, handler->dstkey);
|
||||||
|
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", handler->dstkey, handler->client->db->id);
|
||||||
|
server.dirty++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decrRefCount(handler->dstobj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize the consumer interface type with the requested type. */
|
||||||
|
static void zrangeResultHandlerInit(zrange_result_handler *handler,
|
||||||
|
client *client, zrange_consumer_type type)
|
||||||
|
{
|
||||||
|
memset(handler, 0, sizeof(*handler));
|
||||||
|
|
||||||
|
handler->client = client;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ZRANGE_CONSUMER_TYPE_CLIENT:
|
||||||
|
handler->beginResultEmission = zrangeResultBeginClient;
|
||||||
|
handler->finalizeResultEmission = zrangeResultFinalizeClient;
|
||||||
|
handler->emitResultFromCBuffer = zrangeResultEmitCBufferToClient;
|
||||||
|
handler->emitResultFromLongLong = zrangeResultEmitLongLongToClient;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ZRANGE_CONSUMER_TYPE_INTERNAL:
|
||||||
|
handler->beginResultEmission = zrangeResultBeginStore;
|
||||||
|
handler->finalizeResultEmission = zrangeResultFinalizeStore;
|
||||||
|
handler->emitResultFromCBuffer = zrangeResultEmitCBufferForStore;
|
||||||
|
handler->emitResultFromLongLong = zrangeResultEmitLongLongForStore;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void zrangeResultHandlerScoreEmissionEnable(zrange_result_handler *handler) {
|
||||||
|
handler->withscores = 1;
|
||||||
|
handler->should_emit_array_length = (handler->client->resp > 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void zrangeResultHandlerDestinationKeySet (zrange_result_handler *handler,
|
||||||
|
robj *dstkey)
|
||||||
|
{
|
||||||
|
handler->dstkey = dstkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This command implements ZRANGE, ZREVRANGE. */
|
||||||
|
void genericZrangebyrankCommand(zrange_result_handler *handler,
|
||||||
|
robj *zobj, long start, long end, int withscores, int reverse) {
|
||||||
|
|
||||||
|
client *c = handler->client;
|
||||||
|
long llen;
|
||||||
|
long rangelen;
|
||||||
|
size_t result_cardinality;
|
||||||
|
|
||||||
/* Sanitize indexes. */
|
/* Sanitize indexes. */
|
||||||
llen = zsetLength(zobj);
|
llen = zsetLength(zobj);
|
||||||
@ -2806,22 +2970,17 @@ void zrangeGenericCommand(client *c, int reverse) {
|
|||||||
if (end < 0) end = llen+end;
|
if (end < 0) end = llen+end;
|
||||||
if (start < 0) start = 0;
|
if (start < 0) start = 0;
|
||||||
|
|
||||||
|
handler->beginResultEmission(handler);
|
||||||
|
|
||||||
/* Invariant: start >= 0, so this test will be true when end < 0.
|
/* Invariant: start >= 0, so this test will be true when end < 0.
|
||||||
* The range is empty when start > end or start >= length. */
|
* The range is empty when start > end or start >= length. */
|
||||||
if (start > end || start >= llen) {
|
if (start > end || start >= llen) {
|
||||||
addReply(c,shared.emptyarray);
|
handler->finalizeResultEmission(handler, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (end >= llen) end = llen-1;
|
if (end >= llen) end = llen-1;
|
||||||
rangelen = (end-start)+1;
|
rangelen = (end-start)+1;
|
||||||
|
result_cardinality = rangelen;
|
||||||
/* Return the result in form of a multi-bulk reply. RESP3 clients
|
|
||||||
* will receive sub arrays with score->element, while RESP2 returned
|
|
||||||
* a flat array. */
|
|
||||||
if (withscores && c->resp == 2)
|
|
||||||
addReplyArrayLen(c, rangelen*2);
|
|
||||||
else
|
|
||||||
addReplyArrayLen(c, rangelen);
|
|
||||||
|
|
||||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||||
unsigned char *zl = zobj->ptr;
|
unsigned char *zl = zobj->ptr;
|
||||||
@ -2829,6 +2988,7 @@ void zrangeGenericCommand(client *c, int reverse) {
|
|||||||
unsigned char *vstr;
|
unsigned char *vstr;
|
||||||
unsigned int vlen;
|
unsigned int vlen;
|
||||||
long long vlong;
|
long long vlong;
|
||||||
|
double score = 0.0;
|
||||||
|
|
||||||
if (reverse)
|
if (reverse)
|
||||||
eptr = ziplistIndex(zl,-2-(2*start));
|
eptr = ziplistIndex(zl,-2-(2*start));
|
||||||
@ -2842,12 +3002,14 @@ void zrangeGenericCommand(client *c, int reverse) {
|
|||||||
serverAssertWithInfo(c,zobj,eptr != NULL && sptr != NULL);
|
serverAssertWithInfo(c,zobj,eptr != NULL && sptr != NULL);
|
||||||
serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
|
serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
|
||||||
|
|
||||||
if (withscores && c->resp > 2) addReplyArrayLen(c,2);
|
if (withscores) /* don't bother to extract the score if it's gonna be ignored. */
|
||||||
if (vstr == NULL)
|
score = zzlGetScore(sptr);
|
||||||
addReplyBulkLongLong(c,vlong);
|
|
||||||
else
|
if (vstr == NULL) {
|
||||||
addReplyBulkCBuffer(c,vstr,vlen);
|
handler->emitResultFromLongLong(handler, vlong, score);
|
||||||
if (withscores) addReplyDouble(c,zzlGetScore(sptr));
|
} else {
|
||||||
|
handler->emitResultFromCBuffer(handler, vstr, vlen, score);
|
||||||
|
}
|
||||||
|
|
||||||
if (reverse)
|
if (reverse)
|
||||||
zzlPrev(zl,&eptr,&sptr);
|
zzlPrev(zl,&eptr,&sptr);
|
||||||
@ -2859,7 +3021,6 @@ void zrangeGenericCommand(client *c, int reverse) {
|
|||||||
zset *zs = zobj->ptr;
|
zset *zs = zobj->ptr;
|
||||||
zskiplist *zsl = zs->zsl;
|
zskiplist *zsl = zs->zsl;
|
||||||
zskiplistNode *ln;
|
zskiplistNode *ln;
|
||||||
sds ele;
|
|
||||||
|
|
||||||
/* Check if starting point is trivial, before doing log(N) lookup. */
|
/* Check if starting point is trivial, before doing log(N) lookup. */
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
@ -2874,83 +3035,53 @@ void zrangeGenericCommand(client *c, int reverse) {
|
|||||||
|
|
||||||
while(rangelen--) {
|
while(rangelen--) {
|
||||||
serverAssertWithInfo(c,zobj,ln != NULL);
|
serverAssertWithInfo(c,zobj,ln != NULL);
|
||||||
ele = ln->ele;
|
sds ele = ln->ele;
|
||||||
if (withscores && c->resp > 2) addReplyArrayLen(c,2);
|
handler->emitResultFromCBuffer(handler, ele, sdslen(ele), ln->score);
|
||||||
addReplyBulkCBuffer(c,ele,sdslen(ele));
|
|
||||||
if (withscores) addReplyDouble(c,ln->score);
|
|
||||||
ln = reverse ? ln->backward : ln->level[0].forward;
|
ln = reverse ? ln->backward : ln->level[0].forward;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
serverPanic("Unknown sorted set encoding");
|
serverPanic("Unknown sorted set encoding");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handler->finalizeResultEmission(handler, result_cardinality);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ZRANGESTORE <dst> <src> <min> <max> [BYSCORE | BYLEX] [REV] [LIMIT offset count] */
|
||||||
|
void zrangestoreCommand (client *c) {
|
||||||
|
robj *dstkey = c->argv[1];
|
||||||
|
zrange_result_handler handler;
|
||||||
|
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_INTERNAL);
|
||||||
|
zrangeResultHandlerDestinationKeySet(&handler, dstkey);
|
||||||
|
zrangeGenericCommand(&handler, 2, 1, ZRANGE_AUTO, ZRANGE_DIRECTION_AUTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ZRANGE <key> <min> <max> [BYSCORE | BYLEX] [REV] [WITHSCORES] [LIMIT offset count] */
|
||||||
void zrangeCommand(client *c) {
|
void zrangeCommand(client *c) {
|
||||||
zrangeGenericCommand(c,0);
|
zrange_result_handler handler;
|
||||||
|
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
|
||||||
|
zrangeGenericCommand(&handler, 1, 0, ZRANGE_AUTO, ZRANGE_DIRECTION_AUTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ZREVRANGE <key> <min> <max> [WITHSCORES] */
|
||||||
void zrevrangeCommand(client *c) {
|
void zrevrangeCommand(client *c) {
|
||||||
zrangeGenericCommand(c,1);
|
zrange_result_handler handler;
|
||||||
|
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
|
||||||
|
zrangeGenericCommand(&handler, 1, 0, ZRANGE_RANK, ZRANGE_DIRECTION_REVERSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This command implements ZRANGEBYSCORE, ZREVRANGEBYSCORE. */
|
/* This command implements ZRANGEBYSCORE, ZREVRANGEBYSCORE. */
|
||||||
void genericZrangebyscoreCommand(client *c, int reverse) {
|
void genericZrangebyscoreCommand(zrange_result_handler *handler,
|
||||||
zrangespec range;
|
zrangespec *range, robj *zobj, int withscores, long offset,
|
||||||
robj *key = c->argv[1];
|
long limit, int reverse) {
|
||||||
robj *zobj;
|
|
||||||
long offset = 0, limit = -1;
|
client *c = handler->client;
|
||||||
int withscores = 0;
|
|
||||||
unsigned long rangelen = 0;
|
unsigned long rangelen = 0;
|
||||||
void *replylen = NULL;
|
|
||||||
int minidx, maxidx;
|
|
||||||
|
|
||||||
/* Parse the range arguments. */
|
handler->beginResultEmission(handler);
|
||||||
if (reverse) {
|
|
||||||
/* Range is given as [max,min] */
|
|
||||||
maxidx = 2; minidx = 3;
|
|
||||||
} else {
|
|
||||||
/* Range is given as [min,max] */
|
|
||||||
minidx = 2; maxidx = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zslParseRange(c->argv[minidx],c->argv[maxidx],&range) != C_OK) {
|
|
||||||
addReplyError(c,"min or max is not a float");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Parse optional extra arguments. Note that ZCOUNT will exactly have
|
|
||||||
* 4 arguments, so we'll never enter the following code path. */
|
|
||||||
if (c->argc > 4) {
|
|
||||||
int remaining = c->argc - 4;
|
|
||||||
int pos = 4;
|
|
||||||
|
|
||||||
while (remaining) {
|
|
||||||
if (remaining >= 1 && !strcasecmp(c->argv[pos]->ptr,"withscores")) {
|
|
||||||
pos++; remaining--;
|
|
||||||
withscores = 1;
|
|
||||||
} else if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
|
|
||||||
if ((getLongFromObjectOrReply(c, c->argv[pos+1], &offset, NULL)
|
|
||||||
!= C_OK) ||
|
|
||||||
(getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL)
|
|
||||||
!= C_OK))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pos += 3; remaining -= 3;
|
|
||||||
} else {
|
|
||||||
addReply(c,shared.syntaxerr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ok, lookup the key and get the range */
|
|
||||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL ||
|
|
||||||
checkType(c,zobj,OBJ_ZSET)) return;
|
|
||||||
|
|
||||||
/* For invalid offset, return directly. */
|
/* For invalid offset, return directly. */
|
||||||
if (offset > 0 && offset >= (long)zsetLength(zobj)) {
|
if (offset > 0 && offset >= (long)zsetLength(zobj)) {
|
||||||
addReply(c,shared.emptyarray);
|
handler->finalizeResultEmission(handler, 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2960,29 +3091,17 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
|||||||
unsigned char *vstr;
|
unsigned char *vstr;
|
||||||
unsigned int vlen;
|
unsigned int vlen;
|
||||||
long long vlong;
|
long long vlong;
|
||||||
double score;
|
|
||||||
|
|
||||||
/* If reversed, get the last node in range as starting point. */
|
/* If reversed, get the last node in range as starting point. */
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
eptr = zzlLastInRange(zl,&range);
|
eptr = zzlLastInRange(zl,range);
|
||||||
} else {
|
} else {
|
||||||
eptr = zzlFirstInRange(zl,&range);
|
eptr = zzlFirstInRange(zl,range);
|
||||||
}
|
|
||||||
|
|
||||||
/* No "first" element in the specified interval. */
|
|
||||||
if (eptr == NULL) {
|
|
||||||
addReply(c,shared.emptyarray);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get score pointer for the first element. */
|
/* Get score pointer for the first element. */
|
||||||
serverAssertWithInfo(c,zobj,eptr != NULL);
|
if (eptr)
|
||||||
sptr = ziplistNext(zl,eptr);
|
sptr = ziplistNext(zl,eptr);
|
||||||
|
|
||||||
/* We don't know in advance how many matching elements there are in the
|
|
||||||
* list, so we push this object that will represent the multi-bulk
|
|
||||||
* length in the output buffer, and will "fix" it later */
|
|
||||||
replylen = addReplyDeferredLen(c);
|
|
||||||
|
|
||||||
/* If there is an offset, just traverse the number of elements without
|
/* If there is an offset, just traverse the number of elements without
|
||||||
* checking the score because that is done in the next loop. */
|
* checking the score because that is done in the next loop. */
|
||||||
@ -2995,13 +3114,13 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (eptr && limit--) {
|
while (eptr && limit--) {
|
||||||
score = zzlGetScore(sptr);
|
double score = zzlGetScore(sptr);
|
||||||
|
|
||||||
/* Abort when the node is no longer in range. */
|
/* Abort when the node is no longer in range. */
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
if (!zslValueGteMin(score,&range)) break;
|
if (!zslValueGteMin(score,range)) break;
|
||||||
} else {
|
} else {
|
||||||
if (!zslValueLteMax(score,&range)) break;
|
if (!zslValueLteMax(score,range)) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We know the element exists, so ziplistGet should always
|
/* We know the element exists, so ziplistGet should always
|
||||||
@ -3009,13 +3128,11 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
|||||||
serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
|
serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
|
||||||
|
|
||||||
rangelen++;
|
rangelen++;
|
||||||
if (withscores && c->resp > 2) addReplyArrayLen(c,2);
|
|
||||||
if (vstr == NULL) {
|
if (vstr == NULL) {
|
||||||
addReplyBulkLongLong(c,vlong);
|
handler->emitResultFromLongLong(handler, vlong, score);
|
||||||
} else {
|
} else {
|
||||||
addReplyBulkCBuffer(c,vstr,vlen);
|
handler->emitResultFromCBuffer(handler, vstr, vlen, score);
|
||||||
}
|
}
|
||||||
if (withscores) addReplyDouble(c,score);
|
|
||||||
|
|
||||||
/* Move to next node */
|
/* Move to next node */
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
@ -3031,22 +3148,11 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
|||||||
|
|
||||||
/* If reversed, get the last node in range as starting point. */
|
/* If reversed, get the last node in range as starting point. */
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
ln = zslLastInRange(zsl,&range);
|
ln = zslLastInRange(zsl,range);
|
||||||
} else {
|
} else {
|
||||||
ln = zslFirstInRange(zsl,&range);
|
ln = zslFirstInRange(zsl,range);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* No "first" element in the specified interval. */
|
|
||||||
if (ln == NULL) {
|
|
||||||
addReply(c,shared.emptyarray);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We don't know in advance how many matching elements there are in the
|
|
||||||
* list, so we push this object that will represent the multi-bulk
|
|
||||||
* length in the output buffer, and will "fix" it later */
|
|
||||||
replylen = addReplyDeferredLen(c);
|
|
||||||
|
|
||||||
/* If there is an offset, just traverse the number of elements without
|
/* If there is an offset, just traverse the number of elements without
|
||||||
* checking the score because that is done in the next loop. */
|
* checking the score because that is done in the next loop. */
|
||||||
while (ln && offset--) {
|
while (ln && offset--) {
|
||||||
@ -3060,15 +3166,14 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
|||||||
while (ln && limit--) {
|
while (ln && limit--) {
|
||||||
/* Abort when the node is no longer in range. */
|
/* Abort when the node is no longer in range. */
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
if (!zslValueGteMin(ln->score,&range)) break;
|
if (!zslValueGteMin(ln->score,range)) break;
|
||||||
} else {
|
} else {
|
||||||
if (!zslValueLteMax(ln->score,&range)) break;
|
if (!zslValueLteMax(ln->score,range)) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
rangelen++;
|
rangelen++;
|
||||||
if (withscores && c->resp > 2) addReplyArrayLen(c,2);
|
handler->emitResultFromCBuffer(handler, ln->ele, sdslen(ln->ele),
|
||||||
addReplyBulkCBuffer(c,ln->ele,sdslen(ln->ele));
|
((withscores) ? ln->score : ln->score));
|
||||||
if (withscores) addReplyDouble(c,ln->score);
|
|
||||||
|
|
||||||
/* Move to next node */
|
/* Move to next node */
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
@ -3081,16 +3186,21 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
|
|||||||
serverPanic("Unknown sorted set encoding");
|
serverPanic("Unknown sorted set encoding");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withscores && c->resp == 2) rangelen *= 2;
|
handler->finalizeResultEmission(handler, rangelen);
|
||||||
setDeferredArrayLen(c, replylen, rangelen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ZRANGEBYSCORE <key> <min> <max> [WITHSCORES] [LIMIT offset count] */
|
||||||
void zrangebyscoreCommand(client *c) {
|
void zrangebyscoreCommand(client *c) {
|
||||||
genericZrangebyscoreCommand(c,0);
|
zrange_result_handler handler;
|
||||||
|
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
|
||||||
|
zrangeGenericCommand(&handler, 1, 0, ZRANGE_SCORE, ZRANGE_DIRECTION_FORWARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ZREVRANGEBYSCORE <key> <min> <max> [WITHSCORES] [LIMIT offset count] */
|
||||||
void zrevrangebyscoreCommand(client *c) {
|
void zrevrangebyscoreCommand(client *c) {
|
||||||
genericZrangebyscoreCommand(c,1);
|
zrange_result_handler handler;
|
||||||
|
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
|
||||||
|
zrangeGenericCommand(&handler, 1, 0, ZRANGE_SCORE, ZRANGE_DIRECTION_REVERSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void zcountCommand(client *c) {
|
void zcountCommand(client *c) {
|
||||||
@ -3250,58 +3360,14 @@ void zlexcountCommand(client *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* This command implements ZRANGEBYLEX, ZREVRANGEBYLEX. */
|
/* This command implements ZRANGEBYLEX, ZREVRANGEBYLEX. */
|
||||||
void genericZrangebylexCommand(client *c, int reverse) {
|
void genericZrangebylexCommand(zrange_result_handler *handler,
|
||||||
zlexrangespec range;
|
zlexrangespec *range, robj *zobj, int withscores, long offset, long limit,
|
||||||
robj *key = c->argv[1];
|
int reverse)
|
||||||
robj *zobj;
|
{
|
||||||
long offset = 0, limit = -1;
|
client *c = handler->client;
|
||||||
unsigned long rangelen = 0;
|
unsigned long rangelen = 0;
|
||||||
void *replylen = NULL;
|
|
||||||
int minidx, maxidx;
|
|
||||||
|
|
||||||
/* Parse the range arguments. */
|
handler->beginResultEmission(handler);
|
||||||
if (reverse) {
|
|
||||||
/* Range is given as [max,min] */
|
|
||||||
maxidx = 2; minidx = 3;
|
|
||||||
} else {
|
|
||||||
/* Range is given as [min,max] */
|
|
||||||
minidx = 2; maxidx = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zslParseLexRange(c->argv[minidx],c->argv[maxidx],&range) != C_OK) {
|
|
||||||
addReplyError(c,"min or max not valid string range item");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Parse optional extra arguments. Note that ZCOUNT will exactly have
|
|
||||||
* 4 arguments, so we'll never enter the following code path. */
|
|
||||||
if (c->argc > 4) {
|
|
||||||
int remaining = c->argc - 4;
|
|
||||||
int pos = 4;
|
|
||||||
|
|
||||||
while (remaining) {
|
|
||||||
if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
|
|
||||||
if ((getLongFromObjectOrReply(c, c->argv[pos+1], &offset, NULL) != C_OK) ||
|
|
||||||
(getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) {
|
|
||||||
zslFreeLexRange(&range);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pos += 3; remaining -= 3;
|
|
||||||
} else {
|
|
||||||
zslFreeLexRange(&range);
|
|
||||||
addReply(c,shared.syntaxerr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ok, lookup the key and get the range */
|
|
||||||
if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL ||
|
|
||||||
checkType(c,zobj,OBJ_ZSET))
|
|
||||||
{
|
|
||||||
zslFreeLexRange(&range);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||||
unsigned char *zl = zobj->ptr;
|
unsigned char *zl = zobj->ptr;
|
||||||
@ -3312,26 +3378,14 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
|||||||
|
|
||||||
/* If reversed, get the last node in range as starting point. */
|
/* If reversed, get the last node in range as starting point. */
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
eptr = zzlLastInLexRange(zl,&range);
|
eptr = zzlLastInLexRange(zl,range);
|
||||||
} else {
|
} else {
|
||||||
eptr = zzlFirstInLexRange(zl,&range);
|
eptr = zzlFirstInLexRange(zl,range);
|
||||||
}
|
|
||||||
|
|
||||||
/* No "first" element in the specified interval. */
|
|
||||||
if (eptr == NULL) {
|
|
||||||
addReply(c,shared.emptyarray);
|
|
||||||
zslFreeLexRange(&range);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get score pointer for the first element. */
|
/* Get score pointer for the first element. */
|
||||||
serverAssertWithInfo(c,zobj,eptr != NULL);
|
if (eptr)
|
||||||
sptr = ziplistNext(zl,eptr);
|
sptr = ziplistNext(zl,eptr);
|
||||||
|
|
||||||
/* We don't know in advance how many matching elements there are in the
|
|
||||||
* list, so we push this object that will represent the multi-bulk
|
|
||||||
* length in the output buffer, and will "fix" it later */
|
|
||||||
replylen = addReplyDeferredLen(c);
|
|
||||||
|
|
||||||
/* If there is an offset, just traverse the number of elements without
|
/* If there is an offset, just traverse the number of elements without
|
||||||
* checking the score because that is done in the next loop. */
|
* checking the score because that is done in the next loop. */
|
||||||
@ -3344,11 +3398,15 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (eptr && limit--) {
|
while (eptr && limit--) {
|
||||||
|
double score = 0;
|
||||||
|
if (withscores) /* don't bother to extract the score if it's gonna be ignored. */
|
||||||
|
score = zzlGetScore(sptr);
|
||||||
|
|
||||||
/* Abort when the node is no longer in range. */
|
/* Abort when the node is no longer in range. */
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
if (!zzlLexValueGteMin(eptr,&range)) break;
|
if (!zzlLexValueGteMin(eptr,range)) break;
|
||||||
} else {
|
} else {
|
||||||
if (!zzlLexValueLteMax(eptr,&range)) break;
|
if (!zzlLexValueLteMax(eptr,range)) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We know the element exists, so ziplistGet should always
|
/* We know the element exists, so ziplistGet should always
|
||||||
@ -3357,9 +3415,9 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
|||||||
|
|
||||||
rangelen++;
|
rangelen++;
|
||||||
if (vstr == NULL) {
|
if (vstr == NULL) {
|
||||||
addReplyBulkLongLong(c,vlong);
|
handler->emitResultFromLongLong(handler, vlong, score);
|
||||||
} else {
|
} else {
|
||||||
addReplyBulkCBuffer(c,vstr,vlen);
|
handler->emitResultFromCBuffer(handler, vstr, vlen, score);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Move to next node */
|
/* Move to next node */
|
||||||
@ -3376,23 +3434,11 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
|||||||
|
|
||||||
/* If reversed, get the last node in range as starting point. */
|
/* If reversed, get the last node in range as starting point. */
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
ln = zslLastInLexRange(zsl,&range);
|
ln = zslLastInLexRange(zsl,range);
|
||||||
} else {
|
} else {
|
||||||
ln = zslFirstInLexRange(zsl,&range);
|
ln = zslFirstInLexRange(zsl,range);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* No "first" element in the specified interval. */
|
|
||||||
if (ln == NULL) {
|
|
||||||
addReply(c,shared.emptyarray);
|
|
||||||
zslFreeLexRange(&range);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We don't know in advance how many matching elements there are in the
|
|
||||||
* list, so we push this object that will represent the multi-bulk
|
|
||||||
* length in the output buffer, and will "fix" it later */
|
|
||||||
replylen = addReplyDeferredLen(c);
|
|
||||||
|
|
||||||
/* If there is an offset, just traverse the number of elements without
|
/* If there is an offset, just traverse the number of elements without
|
||||||
* checking the score because that is done in the next loop. */
|
* checking the score because that is done in the next loop. */
|
||||||
while (ln && offset--) {
|
while (ln && offset--) {
|
||||||
@ -3406,13 +3452,13 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
|||||||
while (ln && limit--) {
|
while (ln && limit--) {
|
||||||
/* Abort when the node is no longer in range. */
|
/* Abort when the node is no longer in range. */
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
if (!zslLexValueGteMin(ln->ele,&range)) break;
|
if (!zslLexValueGteMin(ln->ele,range)) break;
|
||||||
} else {
|
} else {
|
||||||
if (!zslLexValueLteMax(ln->ele,&range)) break;
|
if (!zslLexValueLteMax(ln->ele,range)) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
rangelen++;
|
rangelen++;
|
||||||
addReplyBulkCBuffer(c,ln->ele,sdslen(ln->ele));
|
handler->emitResultFromCBuffer(handler, ln->ele, sdslen(ln->ele), ln->score);
|
||||||
|
|
||||||
/* Move to next node */
|
/* Move to next node */
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
@ -3425,16 +3471,171 @@ void genericZrangebylexCommand(client *c, int reverse) {
|
|||||||
serverPanic("Unknown sorted set encoding");
|
serverPanic("Unknown sorted set encoding");
|
||||||
}
|
}
|
||||||
|
|
||||||
zslFreeLexRange(&range);
|
handler->finalizeResultEmission(handler, rangelen);
|
||||||
setDeferredArrayLen(c, replylen, rangelen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ZRANGEBYLEX <key> <min> <max> [LIMIT offset count] */
|
||||||
void zrangebylexCommand(client *c) {
|
void zrangebylexCommand(client *c) {
|
||||||
genericZrangebylexCommand(c,0);
|
zrange_result_handler handler;
|
||||||
|
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
|
||||||
|
zrangeGenericCommand(&handler, 1, 0, ZRANGE_LEX, ZRANGE_DIRECTION_FORWARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ZREVRANGEBYLEX <key> <min> <max> [LIMIT offset count] */
|
||||||
void zrevrangebylexCommand(client *c) {
|
void zrevrangebylexCommand(client *c) {
|
||||||
genericZrangebylexCommand(c,1);
|
zrange_result_handler handler;
|
||||||
|
zrangeResultHandlerInit(&handler, c, ZRANGE_CONSUMER_TYPE_CLIENT);
|
||||||
|
zrangeGenericCommand(&handler, 1, 0, ZRANGE_LEX, ZRANGE_DIRECTION_REVERSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function handles ZRANGE and ZRANGESTORE, and also the deprecated
|
||||||
|
* Z[REV]RANGE[BYPOS|BYLEX] commands.
|
||||||
|
*
|
||||||
|
* The simple ZRANGE and ZRANGESTORE can take _AUTO in rangetype and direction,
|
||||||
|
* other command pass explicit value.
|
||||||
|
*
|
||||||
|
* The argc_start points to the src key argument, so following syntax is like:
|
||||||
|
* <src> <min> <max> [BYSCORE | BYLEX] [REV] [WITHSCORES] [LIMIT offset count]
|
||||||
|
*/
|
||||||
|
void zrangeGenericCommand(zrange_result_handler *handler, int argc_start, int store,
|
||||||
|
zrange_type rangetype, zrange_direction direction)
|
||||||
|
{
|
||||||
|
client *c = handler->client;
|
||||||
|
robj *key = c->argv[argc_start];
|
||||||
|
robj *zobj;
|
||||||
|
zrangespec range;
|
||||||
|
zlexrangespec lexrange;
|
||||||
|
int minidx = argc_start + 1;
|
||||||
|
int maxidx = argc_start + 2;
|
||||||
|
|
||||||
|
/* Options common to all */
|
||||||
|
long opt_start = 0;
|
||||||
|
long opt_end = 0;
|
||||||
|
int opt_withscores = 0;
|
||||||
|
long opt_offset = 0;
|
||||||
|
long opt_limit = -1;
|
||||||
|
|
||||||
|
/* Step 1: Skip the <src> <min> <max> args and parse remaining optional arguments. */
|
||||||
|
for (int j=argc_start + 3; j < c->argc; j++) {
|
||||||
|
int leftargs = c->argc-j-1;
|
||||||
|
if (!store && !strcasecmp(c->argv[j]->ptr,"withscores")) {
|
||||||
|
opt_withscores = 1;
|
||||||
|
} else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) {
|
||||||
|
if ((getLongFromObjectOrReply(c, c->argv[j+1], &opt_offset, NULL) != C_OK) ||
|
||||||
|
(getLongFromObjectOrReply(c, c->argv[j+2], &opt_limit, NULL) != C_OK))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
j += 2;
|
||||||
|
} else if (direction == ZRANGE_DIRECTION_AUTO &&
|
||||||
|
!strcasecmp(c->argv[j]->ptr,"rev"))
|
||||||
|
{
|
||||||
|
direction = ZRANGE_DIRECTION_REVERSE;
|
||||||
|
} else if (rangetype == ZRANGE_AUTO &&
|
||||||
|
!strcasecmp(c->argv[j]->ptr,"bylex"))
|
||||||
|
{
|
||||||
|
rangetype = ZRANGE_LEX;
|
||||||
|
} else if (rangetype == ZRANGE_AUTO &&
|
||||||
|
!strcasecmp(c->argv[j]->ptr,"byscore"))
|
||||||
|
{
|
||||||
|
rangetype = ZRANGE_SCORE;
|
||||||
|
} else {
|
||||||
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use defaults if not overriden by arguments. */
|
||||||
|
if (direction == ZRANGE_DIRECTION_AUTO)
|
||||||
|
direction = ZRANGE_DIRECTION_FORWARD;
|
||||||
|
if (rangetype == ZRANGE_AUTO)
|
||||||
|
rangetype = ZRANGE_RANK;
|
||||||
|
|
||||||
|
/* Check for conflicting arguments. */
|
||||||
|
if (opt_limit != -1 && rangetype == ZRANGE_RANK) {
|
||||||
|
addReplyError(c,"syntax error, LIMIT is only supported in combination with either BYSCORE or BYLEX");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (opt_withscores && rangetype == ZRANGE_LEX) {
|
||||||
|
addReplyError(c,"syntax error, WITHSCORES not supported in combination with BYLEX");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction == ZRANGE_DIRECTION_REVERSE &&
|
||||||
|
((ZRANGE_SCORE == rangetype) || (ZRANGE_LEX == rangetype)))
|
||||||
|
{
|
||||||
|
/* Range is given as [max,min] */
|
||||||
|
int tmp = maxidx;
|
||||||
|
maxidx = minidx;
|
||||||
|
minidx = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Step 2: Parse the range. */
|
||||||
|
switch (rangetype) {
|
||||||
|
case ZRANGE_AUTO:
|
||||||
|
case ZRANGE_RANK:
|
||||||
|
/* Z[REV]RANGE, ZRANGESTORE [REV]RANGE */
|
||||||
|
if ((getLongFromObjectOrReply(c, c->argv[minidx], &opt_start,NULL) != C_OK) ||
|
||||||
|
(getLongFromObjectOrReply(c, c->argv[maxidx], &opt_end,NULL) != C_OK))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ZRANGE_SCORE:
|
||||||
|
/* Z[REV]RANGEBYSCORE, ZRANGESTORE [REV]RANGEBYSCORE */
|
||||||
|
if (zslParseRange(c->argv[minidx], c->argv[maxidx], &range) != C_OK) {
|
||||||
|
addReplyError(c, "min or max is not a float");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ZRANGE_LEX:
|
||||||
|
/* Z[REV]RANGEBYLEX, ZRANGESTORE [REV]RANGEBYLEX */
|
||||||
|
if (zslParseLexRange(c->argv[minidx], c->argv[maxidx], &lexrange) != C_OK) {
|
||||||
|
addReplyError(c, "min or max not valid string range item");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt_withscores || store) {
|
||||||
|
zrangeResultHandlerScoreEmissionEnable(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Step 3: Lookup the key and get the range. */
|
||||||
|
if (((zobj = lookupKeyReadOrReply(c, key, shared.emptyarray)) == NULL)
|
||||||
|
|| checkType(c, zobj, OBJ_ZSET)) {
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Step 4: Pass this to the command-specific handler. */
|
||||||
|
switch (rangetype) {
|
||||||
|
case ZRANGE_AUTO:
|
||||||
|
case ZRANGE_RANK:
|
||||||
|
genericZrangebyrankCommand(handler, zobj, opt_start, opt_end,
|
||||||
|
opt_withscores || store, direction == ZRANGE_DIRECTION_REVERSE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ZRANGE_SCORE:
|
||||||
|
genericZrangebyscoreCommand(handler, &range, zobj, opt_withscores || store,
|
||||||
|
opt_offset, opt_limit, direction == ZRANGE_DIRECTION_REVERSE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ZRANGE_LEX:
|
||||||
|
genericZrangebylexCommand(handler, &lexrange, zobj, opt_withscores || store,
|
||||||
|
opt_offset, opt_limit, direction == ZRANGE_DIRECTION_REVERSE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Instead of returning here, we'll just fall-through the clean-up. */
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
|
||||||
|
if (rangetype == ZRANGE_LEX) {
|
||||||
|
zslFreeLexRange(&lexrange);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void zcardCommand(client *c) {
|
void zcardCommand(client *c) {
|
||||||
@ -3632,7 +3833,7 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey
|
|||||||
/* ZPOPMIN key [<count>] */
|
/* ZPOPMIN key [<count>] */
|
||||||
void zpopminCommand(client *c) {
|
void zpopminCommand(client *c) {
|
||||||
if (c->argc > 3) {
|
if (c->argc > 3) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
genericZpopCommand(c,&c->argv[1],1,ZSET_MIN,0,
|
genericZpopCommand(c,&c->argv[1],1,ZSET_MIN,0,
|
||||||
@ -3642,7 +3843,7 @@ void zpopminCommand(client *c) {
|
|||||||
/* ZMAXPOP key [<count>] */
|
/* ZMAXPOP key [<count>] */
|
||||||
void zpopmaxCommand(client *c) {
|
void zpopmaxCommand(client *c) {
|
||||||
if (c->argc > 3) {
|
if (c->argc > 3) {
|
||||||
addReply(c,shared.syntaxerr);
|
addReplyErrorObject(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
genericZpopCommand(c,&c->argv[1],1,ZSET_MAX,0,
|
genericZpopCommand(c,&c->argv[1],1,ZSET_MAX,0,
|
||||||
|
@ -44,7 +44,7 @@ int __test_num = 0;
|
|||||||
#define test_cond(descr,_c) do { \
|
#define test_cond(descr,_c) do { \
|
||||||
__test_num++; printf("%d - %s: ", __test_num, descr); \
|
__test_num++; printf("%d - %s: ", __test_num, descr); \
|
||||||
if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \
|
if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \
|
||||||
} while(0);
|
} while(0)
|
||||||
#define test_report() do { \
|
#define test_report() do { \
|
||||||
printf("%d tests, %d passed, %d failed\n", __test_num, \
|
printf("%d tests, %d passed, %d failed\n", __test_num, \
|
||||||
__test_num-__failed_tests, __failed_tests); \
|
__test_num-__failed_tests, __failed_tests); \
|
||||||
@ -52,6 +52,6 @@ int __test_num = 0;
|
|||||||
printf("=== WARNING === We have failed tests here...\n"); \
|
printf("=== WARNING === We have failed tests here...\n"); \
|
||||||
exit(1); \
|
exit(1); \
|
||||||
} \
|
} \
|
||||||
} while(0);
|
} while(0)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -99,6 +99,57 @@ void disableTracking(client *c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int stringCheckPrefix(unsigned char *s1, size_t s1_len, unsigned char *s2, size_t s2_len) {
|
||||||
|
size_t min_length = s1_len < s2_len ? s1_len : s2_len;
|
||||||
|
return memcmp(s1,s2,min_length) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if any of the provided prefixes collide with one another or
|
||||||
|
* with an existing prefix for the client. A collision is defined as two
|
||||||
|
* prefixes that will emit an invalidation for the same key. If no prefix
|
||||||
|
* collision is found, 1 is return, otherwise 0 is returned and the client
|
||||||
|
* has an error emitted describing the error. */
|
||||||
|
int checkPrefixCollisionsOrReply(client *c, robj **prefixes, size_t numprefix) {
|
||||||
|
for (size_t i = 0; i < numprefix; i++) {
|
||||||
|
/* Check input list has no overlap with existing prefixes. */
|
||||||
|
if (c->client_tracking_prefixes) {
|
||||||
|
raxIterator ri;
|
||||||
|
raxStart(&ri,c->client_tracking_prefixes);
|
||||||
|
raxSeek(&ri,"^",NULL,0);
|
||||||
|
while(raxNext(&ri)) {
|
||||||
|
if (stringCheckPrefix(ri.key,ri.key_len,
|
||||||
|
prefixes[i]->ptr,sdslen(prefixes[i]->ptr)))
|
||||||
|
{
|
||||||
|
sds collision = sdsnewlen(ri.key,ri.key_len);
|
||||||
|
addReplyErrorFormat(c,
|
||||||
|
"Prefix '%s' overlaps with an existing prefix '%s'. "
|
||||||
|
"Prefixes for a single client must not overlap.",
|
||||||
|
(unsigned char *)prefixes[i]->ptr,
|
||||||
|
(unsigned char *)collision);
|
||||||
|
sdsfree(collision);
|
||||||
|
raxStop(&ri);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raxStop(&ri);
|
||||||
|
}
|
||||||
|
/* Check input has no overlap with itself. */
|
||||||
|
for (size_t j = i + 1; j < numprefix; j++) {
|
||||||
|
if (stringCheckPrefix(prefixes[i]->ptr,sdslen(prefixes[i]->ptr),
|
||||||
|
prefixes[j]->ptr,sdslen(prefixes[j]->ptr)))
|
||||||
|
{
|
||||||
|
addReplyErrorFormat(c,
|
||||||
|
"Prefix '%s' overlaps with another provided prefix '%s'. "
|
||||||
|
"Prefixes for a single client must not overlap.",
|
||||||
|
(unsigned char *)prefixes[i]->ptr,
|
||||||
|
(unsigned char *)prefixes[j]->ptr);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Set the client 'c' to track the prefix 'prefix'. If the client 'c' is
|
/* Set the client 'c' to track the prefix 'prefix'. If the client 'c' is
|
||||||
* already registered for the specified prefix, no operation is performed. */
|
* already registered for the specified prefix, no operation is performed. */
|
||||||
void enableBcastTrackingForPrefix(client *c, char *prefix, size_t plen) {
|
void enableBcastTrackingForPrefix(client *c, char *prefix, size_t plen) {
|
||||||
@ -350,19 +401,22 @@ void trackingInvalidateKey(client *c, robj *keyobj) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* This function is called when one or all the Redis databases are
|
/* This function is called when one or all the Redis databases are
|
||||||
* flushed (dbid == -1 in case of FLUSHALL). Caching keys are not
|
* flushed. Caching keys are not specific for each DB but are global:
|
||||||
* specific for each DB but are global: currently what we do is send a
|
* currently what we do is send a special notification to clients with
|
||||||
* special notification to clients with tracking enabled, sending a
|
* tracking enabled, sending a RESP NULL, which means, "all the keys",
|
||||||
* RESP NULL, which means, "all the keys", in order to avoid flooding
|
* in order to avoid flooding clients with many invalidation messages
|
||||||
* clients with many invalidation messages for all the keys they may
|
* for all the keys they may hold.
|
||||||
* hold.
|
|
||||||
*/
|
*/
|
||||||
void freeTrackingRadixTree(void *rt) {
|
void freeTrackingRadixTreeCallback(void *rt) {
|
||||||
raxFree(rt);
|
raxFree(rt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void freeTrackingRadixTree(rax *rt) {
|
||||||
|
raxFreeWithCallback(rt,freeTrackingRadixTreeCallback);
|
||||||
|
}
|
||||||
|
|
||||||
/* A RESP NULL is sent to indicate that all keys are invalid */
|
/* A RESP NULL is sent to indicate that all keys are invalid */
|
||||||
void trackingInvalidateKeysOnFlush(int dbid) {
|
void trackingInvalidateKeysOnFlush(int async) {
|
||||||
if (server.tracking_clients) {
|
if (server.tracking_clients) {
|
||||||
listNode *ln;
|
listNode *ln;
|
||||||
listIter li;
|
listIter li;
|
||||||
@ -376,8 +430,12 @@ void trackingInvalidateKeysOnFlush(int dbid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* In case of FLUSHALL, reclaim all the memory used by tracking. */
|
/* In case of FLUSHALL, reclaim all the memory used by tracking. */
|
||||||
if (dbid == -1 && TrackingTable) {
|
if (TrackingTable) {
|
||||||
raxFreeWithCallback(TrackingTable,freeTrackingRadixTree);
|
if (async) {
|
||||||
|
freeTrackingRadixTreeAsync(TrackingTable);
|
||||||
|
} else {
|
||||||
|
freeTrackingRadixTree(TrackingTable);
|
||||||
|
}
|
||||||
TrackingTable = raxNew();
|
TrackingTable = raxNew();
|
||||||
TrackingTableTotalItems = 0;
|
TrackingTableTotalItems = 0;
|
||||||
}
|
}
|
||||||
|
@ -431,19 +431,21 @@ unsigned int zipStoreEntryEncoding(unsigned char *p, unsigned char encoding, uns
|
|||||||
/* Encode the length of the previous entry and write it to "p". This only
|
/* Encode the length of the previous entry and write it to "p". This only
|
||||||
* uses the larger encoding (required in __ziplistCascadeUpdate). */
|
* uses the larger encoding (required in __ziplistCascadeUpdate). */
|
||||||
int zipStorePrevEntryLengthLarge(unsigned char *p, unsigned int len) {
|
int zipStorePrevEntryLengthLarge(unsigned char *p, unsigned int len) {
|
||||||
|
uint32_t u32;
|
||||||
if (p != NULL) {
|
if (p != NULL) {
|
||||||
p[0] = ZIP_BIG_PREVLEN;
|
p[0] = ZIP_BIG_PREVLEN;
|
||||||
memcpy(p+1,&len,sizeof(len));
|
u32 = len;
|
||||||
|
memcpy(p+1,&u32,sizeof(u32));
|
||||||
memrev32ifbe(p+1);
|
memrev32ifbe(p+1);
|
||||||
}
|
}
|
||||||
return 1+sizeof(len);
|
return 1 + sizeof(uint32_t);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Encode the length of the previous entry and write it to "p". Return the
|
/* Encode the length of the previous entry and write it to "p". Return the
|
||||||
* number of bytes needed to encode this length if "p" is NULL. */
|
* number of bytes needed to encode this length if "p" is NULL. */
|
||||||
unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) {
|
unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) {
|
||||||
if (p == NULL) {
|
if (p == NULL) {
|
||||||
return (len < ZIP_BIG_PREVLEN) ? 1 : sizeof(len)+1;
|
return (len < ZIP_BIG_PREVLEN) ? 1 : sizeof(uint32_t) + 1;
|
||||||
} else {
|
} else {
|
||||||
if (len < ZIP_BIG_PREVLEN) {
|
if (len < ZIP_BIG_PREVLEN) {
|
||||||
p[0] = len;
|
p[0] = len;
|
||||||
@ -1711,7 +1713,7 @@ int ziplistTest(int argc, char **argv) {
|
|||||||
if (p == NULL) {
|
if (p == NULL) {
|
||||||
printf("No entry\n");
|
printf("No entry\n");
|
||||||
} else {
|
} else {
|
||||||
printf("ERROR: Out of range index should return NULL, returned offset: %ld\n", p-zl);
|
printf("ERROR: Out of range index should return NULL, returned offset: %ld\n", (long)(p-zl));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
@ -1761,7 +1763,7 @@ int ziplistTest(int argc, char **argv) {
|
|||||||
if (p == NULL) {
|
if (p == NULL) {
|
||||||
printf("No entry\n");
|
printf("No entry\n");
|
||||||
} else {
|
} else {
|
||||||
printf("ERROR: Out of range index should return NULL, returned offset: %ld\n", p-zl);
|
printf("ERROR: Out of range index should return NULL, returned offset: %ld\n", (long)(p-zl));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
@ -580,15 +580,18 @@ size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
|
|||||||
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
|
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
struct proc_regioninfo pri;
|
struct proc_regioninfo pri;
|
||||||
if (proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri, PROC_PIDREGIONINFO_SIZE) ==
|
if (pid == -1) pid = getpid();
|
||||||
PROC_PIDREGIONINFO_SIZE) {
|
if (proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri,
|
||||||
if (!strcmp(field, "Private_Dirty:")) {
|
PROC_PIDREGIONINFO_SIZE) == PROC_PIDREGIONINFO_SIZE)
|
||||||
return (size_t)pri.pri_pages_dirtied * 4096;
|
{
|
||||||
} else if (!strcmp(field, "Rss:")) {
|
int pagesize = getpagesize();
|
||||||
return (size_t)pri.pri_pages_resident * 4096;
|
if (!strcmp(field, "Private_Dirty:")) {
|
||||||
} else if (!strcmp(field, "AnonHugePages:")) {
|
return (size_t)pri.pri_pages_dirtied * pagesize;
|
||||||
|
} else if (!strcmp(field, "Rss:")) {
|
||||||
|
return (size_t)pri.pri_pages_resident * pagesize;
|
||||||
|
} else if (!strcmp(field, "AnonHugePages:")) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
|
@ -57,6 +57,11 @@ proc CI {n field} {
|
|||||||
get_info_field [R $n cluster info] $field
|
get_info_field [R $n cluster info] $field
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Return the value of the specified INFO field.
|
||||||
|
proc s {n field} {
|
||||||
|
get_info_field [R $n info] $field
|
||||||
|
}
|
||||||
|
|
||||||
# Assuming nodes are reest, this function performs slots allocation.
|
# Assuming nodes are reest, this function performs slots allocation.
|
||||||
# Only the first 'n' nodes are used.
|
# Only the first 'n' nodes are used.
|
||||||
proc cluster_allocate_slots {n} {
|
proc cluster_allocate_slots {n} {
|
||||||
|
@ -15,6 +15,7 @@ set replica [Rn 1]
|
|||||||
|
|
||||||
test "Cant read from replica without READONLY" {
|
test "Cant read from replica without READONLY" {
|
||||||
$primary SET a 1
|
$primary SET a 1
|
||||||
|
wait_for_ofs_sync $primary $replica
|
||||||
catch {$replica GET a} err
|
catch {$replica GET a} err
|
||||||
assert {[string range $err 0 4] eq {MOVED}}
|
assert {[string range $err 0 4] eq {MOVED}}
|
||||||
}
|
}
|
||||||
@ -28,6 +29,7 @@ test "Can preform HSET primary and HGET from replica" {
|
|||||||
$primary HSET h a 1
|
$primary HSET h a 1
|
||||||
$primary HSET h b 2
|
$primary HSET h b 2
|
||||||
$primary HSET h c 3
|
$primary HSET h c 3
|
||||||
|
wait_for_ofs_sync $primary $replica
|
||||||
assert {[$replica HGET h a] eq {1}}
|
assert {[$replica HGET h a] eq {1}}
|
||||||
assert {[$replica HGET h b] eq {2}}
|
assert {[$replica HGET h b] eq {2}}
|
||||||
assert {[$replica HGET h c] eq {3}}
|
assert {[$replica HGET h c] eq {3}}
|
||||||
|
@ -22,6 +22,8 @@ test "Right to restore backups when fail to diskless load " {
|
|||||||
|
|
||||||
$replica READONLY
|
$replica READONLY
|
||||||
$replica config set repl-diskless-load swapdb
|
$replica config set repl-diskless-load swapdb
|
||||||
|
$replica config set appendonly no
|
||||||
|
$replica config set save ""
|
||||||
$replica config rewrite
|
$replica config rewrite
|
||||||
$master config set repl-backlog-size 1024
|
$master config set repl-backlog-size 1024
|
||||||
$master config set repl-diskless-sync yes
|
$master config set repl-diskless-sync yes
|
||||||
@ -38,7 +40,8 @@ test "Right to restore backups when fail to diskless load " {
|
|||||||
assert_equal {1} [$replica get $slot0_key]
|
assert_equal {1} [$replica get $slot0_key]
|
||||||
assert_equal $slot0_key [$replica CLUSTER GETKEYSINSLOT 0 1]
|
assert_equal $slot0_key [$replica CLUSTER GETKEYSINSLOT 0 1]
|
||||||
|
|
||||||
# Kill the replica
|
# Save an RDB and kill the replica
|
||||||
|
$replica save
|
||||||
kill_instance redis $replica_id
|
kill_instance redis $replica_id
|
||||||
|
|
||||||
# Delete the key from master
|
# Delete the key from master
|
||||||
@ -60,13 +63,12 @@ test "Right to restore backups when fail to diskless load " {
|
|||||||
restart_instance redis $replica_id
|
restart_instance redis $replica_id
|
||||||
$replica READONLY
|
$replica READONLY
|
||||||
|
|
||||||
# Start full sync
|
# Start full sync, wait till after db is flushed (backed up)
|
||||||
wait_for_condition 500 10 {
|
wait_for_condition 500 10 {
|
||||||
[string match "*sync*" [$replica role]]
|
[s $replica_id loading] eq 1
|
||||||
} else {
|
} else {
|
||||||
fail "Fail to full sync"
|
fail "Fail to full sync"
|
||||||
}
|
}
|
||||||
after 100
|
|
||||||
|
|
||||||
# Kill master, abort full sync
|
# Kill master, abort full sync
|
||||||
kill_instance redis $master_id
|
kill_instance redis $master_id
|
||||||
|
45
tests/cluster/tests/18-info.tcl
Normal file
45
tests/cluster/tests/18-info.tcl
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Check cluster info stats
|
||||||
|
|
||||||
|
source "../tests/includes/init-tests.tcl"
|
||||||
|
|
||||||
|
test "Create a primary with a replica" {
|
||||||
|
create_cluster 2 0
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Cluster should start ok" {
|
||||||
|
assert_cluster_state ok
|
||||||
|
}
|
||||||
|
|
||||||
|
set primary1 [Rn 0]
|
||||||
|
set primary2 [Rn 1]
|
||||||
|
|
||||||
|
proc cmdstat {instace cmd} {
|
||||||
|
return [cmdrstat $cmd $instace]
|
||||||
|
}
|
||||||
|
|
||||||
|
proc errorstat {instace cmd} {
|
||||||
|
return [errorrstat $cmd $instace]
|
||||||
|
}
|
||||||
|
|
||||||
|
test "errorstats: rejected call due to MOVED Redirection" {
|
||||||
|
$primary1 config resetstat
|
||||||
|
$primary2 config resetstat
|
||||||
|
assert_match {} [errorstat $primary1 MOVED]
|
||||||
|
assert_match {} [errorstat $primary2 MOVED]
|
||||||
|
# we know that one will have a MOVED reply and one will succeed
|
||||||
|
catch {$primary1 set key b} replyP1
|
||||||
|
catch {$primary2 set key b} replyP2
|
||||||
|
# sort servers so we know which one failed
|
||||||
|
if {$replyP1 eq {OK}} {
|
||||||
|
assert_match {MOVED*} $replyP2
|
||||||
|
set pok $primary1
|
||||||
|
set perr $primary2
|
||||||
|
} else {
|
||||||
|
assert_match {MOVED*} $replyP1
|
||||||
|
set pok $primary2
|
||||||
|
set perr $primary1
|
||||||
|
}
|
||||||
|
assert_match {} [errorstat $pok MOVED]
|
||||||
|
assert_match {*count=1*} [errorstat $perr MOVED]
|
||||||
|
assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat $perr set]
|
||||||
|
}
|
@ -198,3 +198,94 @@ test {client freed during loading} {
|
|||||||
exec kill [srv 0 pid]
|
exec kill [srv 0 pid]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Our COW metrics (Private_Dirty) work only on Linux
|
||||||
|
set system_name [string tolower [exec uname -s]]
|
||||||
|
if {$system_name eq {linux}} {
|
||||||
|
|
||||||
|
start_server {overrides {save ""}} {
|
||||||
|
test {Test child sending COW info} {
|
||||||
|
# make sure that rdb_last_cow_size and current_cow_size are zero (the test using new server),
|
||||||
|
# so that the comparisons during the test will be valid
|
||||||
|
assert {[s current_cow_size] == 0}
|
||||||
|
assert {[s rdb_last_cow_size] == 0}
|
||||||
|
|
||||||
|
# using a 200us delay, the bgsave is empirically taking about 10 seconds.
|
||||||
|
# we need it to take more than some 5 seconds, since redis only report COW once a second.
|
||||||
|
r config set rdb-key-save-delay 200
|
||||||
|
r config set loglevel debug
|
||||||
|
|
||||||
|
# populate the db with 10k keys of 4k each
|
||||||
|
set rd [redis_deferring_client 0]
|
||||||
|
set size 4096
|
||||||
|
set cmd_count 10000
|
||||||
|
for {set k 0} {$k < $cmd_count} {incr k} {
|
||||||
|
$rd set key$k [string repeat A $size]
|
||||||
|
}
|
||||||
|
|
||||||
|
for {set k 0} {$k < $cmd_count} {incr k} {
|
||||||
|
catch { $rd read }
|
||||||
|
}
|
||||||
|
|
||||||
|
$rd close
|
||||||
|
|
||||||
|
# start background rdb save
|
||||||
|
r bgsave
|
||||||
|
|
||||||
|
# on each iteration, we will write some key to the server to trigger copy-on-write, and
|
||||||
|
# wait to see that it reflected in INFO.
|
||||||
|
set iteration 1
|
||||||
|
while 1 {
|
||||||
|
# take a sample before writing new data to the server
|
||||||
|
set cow_size [s current_cow_size]
|
||||||
|
if {$::verbose} {
|
||||||
|
puts "COW info before copy-on-write: $cow_size"
|
||||||
|
}
|
||||||
|
|
||||||
|
# trigger copy-on-write
|
||||||
|
r setrange key$iteration 0 [string repeat B $size]
|
||||||
|
|
||||||
|
# wait to see that current_cow_size value updated (as long as the child is in progress)
|
||||||
|
wait_for_condition 80 100 {
|
||||||
|
[s rdb_bgsave_in_progress] == 0 ||
|
||||||
|
[s current_cow_size] >= $cow_size + $size
|
||||||
|
} else {
|
||||||
|
if {$::verbose} {
|
||||||
|
puts "COW info on fail: [s current_cow_size]"
|
||||||
|
puts [exec tail -n 100 < [srv 0 stdout]]
|
||||||
|
}
|
||||||
|
fail "COW info wasn't reported"
|
||||||
|
}
|
||||||
|
|
||||||
|
# for no accurate, stop after 2 iterations
|
||||||
|
if {!$::accurate && $iteration == 2} {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
# stop iterating if the bgsave completed
|
||||||
|
if { [s rdb_bgsave_in_progress] == 0 } {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
incr iteration 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# make sure we saw report of current_cow_size
|
||||||
|
if {$iteration < 2 && $::verbose} {
|
||||||
|
puts [exec tail -n 100 < [srv 0 stdout]]
|
||||||
|
}
|
||||||
|
assert_morethan_equal $iteration 2
|
||||||
|
|
||||||
|
# if bgsave completed, check that rdb_last_cow_size (fork exit report)
|
||||||
|
# is at least 90% of last rdb_active_cow_size.
|
||||||
|
if { [s rdb_bgsave_in_progress] == 0 } {
|
||||||
|
set final_cow [s rdb_last_cow_size]
|
||||||
|
set cow_size [expr $cow_size * 0.9]
|
||||||
|
if {$final_cow < $cow_size && $::verbose} {
|
||||||
|
puts [exec tail -n 100 < [srv 0 stdout]]
|
||||||
|
}
|
||||||
|
assert_morethan_equal $final_cow $cow_size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ;# system_name
|
||||||
|
@ -51,18 +51,31 @@ void timerHandler(RedisModuleCtx *ctx, void *data) {
|
|||||||
RedisModule_Replicate(ctx,"INCR","c","timer");
|
RedisModule_Replicate(ctx,"INCR","c","timer");
|
||||||
times++;
|
times++;
|
||||||
|
|
||||||
if (times < 10)
|
if (times < 3)
|
||||||
RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
|
RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
|
||||||
else
|
else
|
||||||
times = 0;
|
times = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int propagateTestTimerCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
|
||||||
|
RedisModuleTimerID timer_id =
|
||||||
|
RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
|
||||||
|
REDISMODULE_NOT_USED(timer_id);
|
||||||
|
|
||||||
|
RedisModule_ReplyWithSimpleString(ctx,"OK");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* The thread entry point. */
|
/* The thread entry point. */
|
||||||
void *threadMain(void *arg) {
|
void *threadMain(void *arg) {
|
||||||
REDISMODULE_NOT_USED(arg);
|
REDISMODULE_NOT_USED(arg);
|
||||||
RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(NULL);
|
RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(NULL);
|
||||||
RedisModule_SelectDb(ctx,9); /* Tests ran in database number 9. */
|
RedisModule_SelectDb(ctx,9); /* Tests ran in database number 9. */
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
RedisModule_ThreadSafeContextLock(ctx);
|
RedisModule_ThreadSafeContextLock(ctx);
|
||||||
RedisModule_Replicate(ctx,"INCR","c","a-from-thread");
|
RedisModule_Replicate(ctx,"INCR","c","a-from-thread");
|
||||||
RedisModule_Replicate(ctx,"INCR","c","b-from-thread");
|
RedisModule_Replicate(ctx,"INCR","c","b-from-thread");
|
||||||
@ -72,15 +85,11 @@ void *threadMain(void *arg) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int propagateTestCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
int propagateTestThreadCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
{
|
{
|
||||||
REDISMODULE_NOT_USED(argv);
|
REDISMODULE_NOT_USED(argv);
|
||||||
REDISMODULE_NOT_USED(argc);
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
|
||||||
RedisModuleTimerID timer_id =
|
|
||||||
RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
|
|
||||||
REDISMODULE_NOT_USED(timer_id);
|
|
||||||
|
|
||||||
pthread_t tid;
|
pthread_t tid;
|
||||||
if (pthread_create(&tid,NULL,threadMain,NULL) != 0)
|
if (pthread_create(&tid,NULL,threadMain,NULL) != 0)
|
||||||
return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
|
return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
|
||||||
@ -90,7 +99,7 @@ int propagateTestCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc
|
|||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int propagateTest2Command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
int propagateTestSimpleCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
{
|
{
|
||||||
REDISMODULE_NOT_USED(argv);
|
REDISMODULE_NOT_USED(argv);
|
||||||
REDISMODULE_NOT_USED(argc);
|
REDISMODULE_NOT_USED(argc);
|
||||||
@ -102,7 +111,7 @@ int propagateTest2Command(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
|
|||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int propagateTest3Command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
int propagateTestMixedCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
{
|
{
|
||||||
REDISMODULE_NOT_USED(argv);
|
REDISMODULE_NOT_USED(argv);
|
||||||
REDISMODULE_NOT_USED(argc);
|
REDISMODULE_NOT_USED(argc);
|
||||||
@ -129,18 +138,23 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||||||
if (RedisModule_Init(ctx,"propagate-test",1,REDISMODULE_APIVER_1)
|
if (RedisModule_Init(ctx,"propagate-test",1,REDISMODULE_APIVER_1)
|
||||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||||
|
|
||||||
if (RedisModule_CreateCommand(ctx,"propagate-test",
|
if (RedisModule_CreateCommand(ctx,"propagate-test.timer",
|
||||||
propagateTestCommand,
|
propagateTestTimerCommand,
|
||||||
"",1,1,1) == REDISMODULE_ERR)
|
"",1,1,1) == REDISMODULE_ERR)
|
||||||
return REDISMODULE_ERR;
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
if (RedisModule_CreateCommand(ctx,"propagate-test-2",
|
if (RedisModule_CreateCommand(ctx,"propagate-test.thread",
|
||||||
propagateTest2Command,
|
propagateTestThreadCommand,
|
||||||
"",1,1,1) == REDISMODULE_ERR)
|
"",1,1,1) == REDISMODULE_ERR)
|
||||||
return REDISMODULE_ERR;
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
if (RedisModule_CreateCommand(ctx,"propagate-test-3",
|
if (RedisModule_CreateCommand(ctx,"propagate-test.simple",
|
||||||
propagateTest3Command,
|
propagateTestSimpleCommand,
|
||||||
|
"",1,1,1) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"propagate-test.mixed",
|
||||||
|
propagateTestMixedCommand,
|
||||||
"",1,1,1) == REDISMODULE_ERR)
|
"",1,1,1) == REDISMODULE_ERR)
|
||||||
return REDISMODULE_ERR;
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
@ -214,20 +214,19 @@ proc ::redis::redis_multi_bulk_read {id fd} {
|
|||||||
proc ::redis::redis_read_map {id fd} {
|
proc ::redis::redis_read_map {id fd} {
|
||||||
set count [redis_read_line $fd]
|
set count [redis_read_line $fd]
|
||||||
if {$count == -1} return {}
|
if {$count == -1} return {}
|
||||||
set l {}
|
set d {}
|
||||||
set err {}
|
set err {}
|
||||||
for {set i 0} {$i < $count} {incr i} {
|
for {set i 0} {$i < $count} {incr i} {
|
||||||
if {[catch {
|
if {[catch {
|
||||||
set t {}
|
set k [redis_read_reply $id $fd] ; # key
|
||||||
lappend t [redis_read_reply $id $fd] ; # key
|
set v [redis_read_reply $id $fd] ; # value
|
||||||
lappend t [redis_read_reply $id $fd] ; # value
|
dict set d $k $v
|
||||||
lappend l $t
|
|
||||||
} e] && $err eq {}} {
|
} e] && $err eq {}} {
|
||||||
set err $e
|
set err $e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if {$err ne {}} {return -code error $err}
|
if {$err ne {}} {return -code error $err}
|
||||||
return $l
|
return $d
|
||||||
}
|
}
|
||||||
|
|
||||||
proc ::redis::redis_read_line fd {
|
proc ::redis::redis_read_line fd {
|
||||||
|
@ -50,11 +50,17 @@ proc kill_server config {
|
|||||||
tags {"leaks"} {
|
tags {"leaks"} {
|
||||||
test "Check for memory leaks (pid $pid)" {
|
test "Check for memory leaks (pid $pid)" {
|
||||||
set output {0 leaks}
|
set output {0 leaks}
|
||||||
catch {exec leaks $pid} output
|
catch {exec leaks $pid} output option
|
||||||
if {[string match {*process does not exist*} $output] ||
|
# In a few tests we kill the server process, so leaks will not find it.
|
||||||
[string match {*cannot examine*} $output]} {
|
# It'll exits with exit code >1 on error, so we ignore these.
|
||||||
# In a few tests we kill the server process.
|
if {[dict exists $option -errorcode]} {
|
||||||
set output "0 leaks"
|
set details [dict get $option -errorcode]
|
||||||
|
if {[lindex $details 0] eq "CHILDSTATUS"} {
|
||||||
|
set status [lindex $details 2]
|
||||||
|
if {$status > 1} {
|
||||||
|
set output "0 leaks"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
set output
|
set output
|
||||||
} {*0 leaks*}
|
} {*0 leaks*}
|
||||||
|
@ -31,36 +31,48 @@ proc assert_match {pattern value} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proc assert_failed {expected_err detail} {
|
||||||
|
if {$detail ne ""} {
|
||||||
|
set detail "(detail: $detail)"
|
||||||
|
} else {
|
||||||
|
set detail "(context: [info frame -2])"
|
||||||
|
}
|
||||||
|
error "assertion:$expected_err $detail"
|
||||||
|
}
|
||||||
|
|
||||||
proc assert_equal {value expected {detail ""}} {
|
proc assert_equal {value expected {detail ""}} {
|
||||||
if {$expected ne $value} {
|
if {$expected ne $value} {
|
||||||
if {$detail ne ""} {
|
assert_failed "Expected '$value' to be equal to '$expected'" $detail
|
||||||
set detail "(detail: $detail)"
|
|
||||||
} else {
|
|
||||||
set detail "(context: [info frame -1])"
|
|
||||||
}
|
|
||||||
error "assertion:Expected '$value' to be equal to '$expected' $detail"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proc assert_lessthan {value expected {detail ""}} {
|
proc assert_lessthan {value expected {detail ""}} {
|
||||||
if {!($value < $expected)} {
|
if {!($value < $expected)} {
|
||||||
if {$detail ne ""} {
|
assert_failed "Expected '$value' to be less than '$expected'" $detail
|
||||||
set detail "(detail: $detail)"
|
}
|
||||||
} else {
|
}
|
||||||
set detail "(context: [info frame -1])"
|
|
||||||
}
|
proc assert_lessthan_equal {value expected {detail ""}} {
|
||||||
error "assertion:Expected '$value' to be lessthan to '$expected' $detail"
|
if {!($value <= $expected)} {
|
||||||
|
assert_failed "Expected '$value' to be less than or equal to '$expected'" $detail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proc assert_morethan {value expected {detail ""}} {
|
||||||
|
if {!($value > $expected)} {
|
||||||
|
assert_failed "Expected '$value' to be more than '$expected'" $detail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proc assert_morethan_equal {value expected {detail ""}} {
|
||||||
|
if {!($value >= $expected)} {
|
||||||
|
assert_failed "Expected '$value' to be more than or equal to '$expected'" $detail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
proc assert_range {value min max {detail ""}} {
|
proc assert_range {value min max {detail ""}} {
|
||||||
if {!($value <= $max && $value >= $min)} {
|
if {!($value <= $max && $value >= $min)} {
|
||||||
if {$detail ne ""} {
|
assert_failed "Expected '$value' to be between to '$min' and '$max'" $detail
|
||||||
set detail "(detail: $detail)"
|
|
||||||
} else {
|
|
||||||
set detail "(context: [info frame -1])"
|
|
||||||
}
|
|
||||||
error "assertion:Expected '$value' to be between to '$min' and '$max' $detail"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -561,6 +561,12 @@ proc cmdrstat {cmd r} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proc errorrstat {cmd r} {
|
||||||
|
if {[regexp "\r\nerrorstat_$cmd:(.*?)\r\n" [$r info errorstats] _ value]} {
|
||||||
|
set _ $value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
proc generate_fuzzy_traffic_on_key {key duration} {
|
proc generate_fuzzy_traffic_on_key {key duration} {
|
||||||
# Commands per type, blocking commands removed
|
# Commands per type, blocking commands removed
|
||||||
# TODO: extract these from help.h or elsewhere, and improve to include other types
|
# TODO: extract these from help.h or elsewhere, and improve to include other types
|
||||||
|
@ -18,6 +18,7 @@ set ::all_tests {
|
|||||||
unit/protocol
|
unit/protocol
|
||||||
unit/keyspace
|
unit/keyspace
|
||||||
unit/scan
|
unit/scan
|
||||||
|
unit/info
|
||||||
unit/type/string
|
unit/type/string
|
||||||
unit/type/incr
|
unit/type/incr
|
||||||
unit/type/list
|
unit/type/list
|
||||||
|
@ -25,3 +25,44 @@ start_server {tags {"auth"} overrides {requirepass foobar}} {
|
|||||||
r incr foo
|
r incr foo
|
||||||
} {101}
|
} {101}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start_server {tags {"auth_binary_password"}} {
|
||||||
|
test {AUTH fails when binary password is wrong} {
|
||||||
|
r config set requirepass "abc\x00def"
|
||||||
|
catch {r auth abc} err
|
||||||
|
set _ $err
|
||||||
|
} {WRONGPASS*}
|
||||||
|
|
||||||
|
test {AUTH succeeds when binary password is correct} {
|
||||||
|
r config set requirepass "abc\x00def"
|
||||||
|
r auth "abc\x00def"
|
||||||
|
} {OK}
|
||||||
|
|
||||||
|
start_server {tags {"masterauth"}} {
|
||||||
|
set master [srv -1 client]
|
||||||
|
set master_host [srv -1 host]
|
||||||
|
set master_port [srv -1 port]
|
||||||
|
set slave [srv 0 client]
|
||||||
|
|
||||||
|
test {MASTERAUTH test with binary password} {
|
||||||
|
$master config set requirepass "abc\x00def"
|
||||||
|
|
||||||
|
# Configure the replica with masterauth
|
||||||
|
set loglines [count_log_lines 0]
|
||||||
|
$slave slaveof $master_host $master_port
|
||||||
|
$slave config set masterauth "abc"
|
||||||
|
|
||||||
|
# Verify replica is not able to sync with master
|
||||||
|
wait_for_log_messages 0 {"*Unable to AUTH to MASTER*"} $loglines 1000 10
|
||||||
|
assert_equal {down} [s 0 master_link_status]
|
||||||
|
|
||||||
|
# Test replica with the correct masterauth
|
||||||
|
$slave config set masterauth "abc\x00def"
|
||||||
|
wait_for_condition 50 100 {
|
||||||
|
[s 0 master_link_status] eq {up}
|
||||||
|
} else {
|
||||||
|
fail "Can't turn the instance into a replica"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -74,6 +74,49 @@ start_server {tags {"geo"}} {
|
|||||||
r geoadd nyc -73.9454966 40.747533 "lic market"
|
r geoadd nyc -73.9454966 40.747533 "lic market"
|
||||||
} {0}
|
} {0}
|
||||||
|
|
||||||
|
test {GEOADD update with CH option} {
|
||||||
|
assert_equal 1 [r geoadd nyc CH 40.747533 -73.9454966 "lic market"]
|
||||||
|
lassign [lindex [r geopos nyc "lic market"] 0] x1 y1
|
||||||
|
assert {abs($x1) - 40.747 < 0.001}
|
||||||
|
assert {abs($y1) - 73.945 < 0.001}
|
||||||
|
} {}
|
||||||
|
|
||||||
|
test {GEOADD update with NX option} {
|
||||||
|
assert_equal 0 [r geoadd nyc NX -73.9454966 40.747533 "lic market"]
|
||||||
|
lassign [lindex [r geopos nyc "lic market"] 0] x1 y1
|
||||||
|
assert {abs($x1) - 40.747 < 0.001}
|
||||||
|
assert {abs($y1) - 73.945 < 0.001}
|
||||||
|
} {}
|
||||||
|
|
||||||
|
test {GEOADD update with XX option} {
|
||||||
|
assert_equal 0 [r geoadd nyc XX -83.9454966 40.747533 "lic market"]
|
||||||
|
lassign [lindex [r geopos nyc "lic market"] 0] x1 y1
|
||||||
|
assert {abs($x1) - 83.945 < 0.001}
|
||||||
|
assert {abs($y1) - 40.747 < 0.001}
|
||||||
|
} {}
|
||||||
|
|
||||||
|
test {GEOADD update with CH NX option} {
|
||||||
|
r geoadd nyc CH NX -73.9454966 40.747533 "lic market"
|
||||||
|
} {0}
|
||||||
|
|
||||||
|
test {GEOADD update with CH XX option} {
|
||||||
|
r geoadd nyc CH XX -73.9454966 40.747533 "lic market"
|
||||||
|
} {1}
|
||||||
|
|
||||||
|
test {GEOADD update with XX NX option will return syntax error} {
|
||||||
|
catch {
|
||||||
|
r geoadd nyc xx nx -73.9454966 40.747533 "lic market"
|
||||||
|
} err
|
||||||
|
set err
|
||||||
|
} {ERR*syntax*}
|
||||||
|
|
||||||
|
test {GEOADD update with invalid option} {
|
||||||
|
catch {
|
||||||
|
r geoadd nyc ch xx foo -73.9454966 40.747533 "lic market"
|
||||||
|
} err
|
||||||
|
set err
|
||||||
|
} {ERR*syntax*}
|
||||||
|
|
||||||
test {GEOADD invalid coordinates} {
|
test {GEOADD invalid coordinates} {
|
||||||
catch {
|
catch {
|
||||||
r geoadd nyc -73.9454966 40.747533 "lic market" \
|
r geoadd nyc -73.9454966 40.747533 "lic market" \
|
||||||
@ -135,6 +178,19 @@ start_server {tags {"geo"}} {
|
|||||||
r georadius nyc -73.9798091 40.7598464 10 km COUNT 3
|
r georadius nyc -73.9798091 40.7598464 10 km COUNT 3
|
||||||
} {{central park n/q/r} 4545 {union square}}
|
} {{central park n/q/r} 4545 {union square}}
|
||||||
|
|
||||||
|
test {GEORADIUS with ANY not sorted by default} {
|
||||||
|
r georadius nyc -73.9798091 40.7598464 10 km COUNT 3 ANY
|
||||||
|
} {{wtc one} {union square} {central park n/q/r}}
|
||||||
|
|
||||||
|
test {GEORADIUS with ANY sorted by ASC} {
|
||||||
|
r georadius nyc -73.9798091 40.7598464 10 km COUNT 3 ANY ASC
|
||||||
|
} {{central park n/q/r} {union square} {wtc one}}
|
||||||
|
|
||||||
|
test {GEORADIUS with ANY but no COUNT} {
|
||||||
|
catch {r georadius nyc -73.9798091 40.7598464 10 km ANY ASC} e
|
||||||
|
set e
|
||||||
|
} {ERR*ANY*requires*COUNT*}
|
||||||
|
|
||||||
test {GEORADIUS with COUNT but missing integer argument} {
|
test {GEORADIUS with COUNT but missing integer argument} {
|
||||||
catch {r georadius nyc -73.9798091 40.7598464 10 km COUNT} e
|
catch {r georadius nyc -73.9798091 40.7598464 10 km COUNT} e
|
||||||
set e
|
set e
|
||||||
|
153
tests/unit/info.tcl
Normal file
153
tests/unit/info.tcl
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
proc cmdstat {cmd} {
|
||||||
|
return [cmdrstat $cmd r]
|
||||||
|
}
|
||||||
|
|
||||||
|
proc errorstat {cmd} {
|
||||||
|
return [errorrstat $cmd r]
|
||||||
|
}
|
||||||
|
|
||||||
|
start_server {tags {"info"}} {
|
||||||
|
start_server {} {
|
||||||
|
|
||||||
|
test {errorstats: failed call authentication error} {
|
||||||
|
r config resetstat
|
||||||
|
assert_match {} [errorstat ERR]
|
||||||
|
assert_equal [s total_error_replies] 0
|
||||||
|
catch {r auth k} e
|
||||||
|
assert_match {ERR AUTH*} $e
|
||||||
|
assert_match {*count=1*} [errorstat ERR]
|
||||||
|
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
|
||||||
|
assert_equal [s total_error_replies] 1
|
||||||
|
r config resetstat
|
||||||
|
assert_match {} [errorstat ERR]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {errorstats: failed call within MULTI/EXEC} {
|
||||||
|
r config resetstat
|
||||||
|
assert_match {} [errorstat ERR]
|
||||||
|
assert_equal [s total_error_replies] 0
|
||||||
|
r multi
|
||||||
|
r set a b
|
||||||
|
r auth a
|
||||||
|
catch {r exec} e
|
||||||
|
assert_match {ERR AUTH*} $e
|
||||||
|
assert_match {*count=1*} [errorstat ERR]
|
||||||
|
assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat set]
|
||||||
|
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat auth]
|
||||||
|
assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat exec]
|
||||||
|
assert_equal [s total_error_replies] 1
|
||||||
|
|
||||||
|
# MULTI/EXEC command errors should still be pinpointed to him
|
||||||
|
catch {r exec} e
|
||||||
|
assert_match {ERR EXEC without MULTI} $e
|
||||||
|
assert_match {*calls=2,*,rejected_calls=0,failed_calls=1} [cmdstat exec]
|
||||||
|
assert_match {*count=2*} [errorstat ERR]
|
||||||
|
assert_equal [s total_error_replies] 2
|
||||||
|
}
|
||||||
|
|
||||||
|
test {errorstats: failed call within LUA} {
|
||||||
|
r config resetstat
|
||||||
|
assert_match {} [errorstat ERR]
|
||||||
|
assert_equal [s total_error_replies] 0
|
||||||
|
catch {r eval {redis.pcall('XGROUP', 'CREATECONSUMER', 's1', 'mygroup', 'consumer') return } 0} e
|
||||||
|
assert_match {*count=1*} [errorstat ERR]
|
||||||
|
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat xgroup]
|
||||||
|
assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat eval]
|
||||||
|
|
||||||
|
# EVAL command errors should still be pinpointed to him
|
||||||
|
catch {r eval a} e
|
||||||
|
assert_match {ERR wrong*} $e
|
||||||
|
assert_match {*calls=1,*,rejected_calls=1,failed_calls=0} [cmdstat eval]
|
||||||
|
assert_match {*count=2*} [errorstat ERR]
|
||||||
|
assert_equal [s total_error_replies] 2
|
||||||
|
}
|
||||||
|
|
||||||
|
test {errorstats: failed call NOGROUP error} {
|
||||||
|
r config resetstat
|
||||||
|
assert_match {} [errorstat NOGROUP]
|
||||||
|
r del mystream
|
||||||
|
r XADD mystream * f v
|
||||||
|
catch {r XGROUP CREATECONSUMER mystream mygroup consumer} e
|
||||||
|
assert_match {NOGROUP*} $e
|
||||||
|
assert_match {*count=1*} [errorstat NOGROUP]
|
||||||
|
assert_match {*calls=1,*,rejected_calls=0,failed_calls=1} [cmdstat xgroup]
|
||||||
|
r config resetstat
|
||||||
|
assert_match {} [errorstat NOGROUP]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {errorstats: rejected call unknown command} {
|
||||||
|
r config resetstat
|
||||||
|
assert_equal [s total_error_replies] 0
|
||||||
|
assert_match {} [errorstat ERR]
|
||||||
|
catch {r asdf} e
|
||||||
|
assert_match {ERR unknown*} $e
|
||||||
|
assert_match {*count=1*} [errorstat ERR]
|
||||||
|
assert_equal [s total_error_replies] 1
|
||||||
|
r config resetstat
|
||||||
|
assert_match {} [errorstat ERR]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {errorstats: rejected call within MULTI/EXEC} {
|
||||||
|
r config resetstat
|
||||||
|
assert_equal [s total_error_replies] 0
|
||||||
|
assert_match {} [errorstat ERR]
|
||||||
|
r multi
|
||||||
|
catch {r set} e
|
||||||
|
assert_match {ERR wrong number of arguments*} $e
|
||||||
|
catch {r exec} e
|
||||||
|
assert_match {EXECABORT*} $e
|
||||||
|
assert_match {*count=1*} [errorstat ERR]
|
||||||
|
assert_equal [s total_error_replies] 1
|
||||||
|
assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat set]
|
||||||
|
assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat multi]
|
||||||
|
assert_match {*calls=1,*,rejected_calls=0,failed_calls=0} [cmdstat exec]
|
||||||
|
assert_equal [s total_error_replies] 1
|
||||||
|
r config resetstat
|
||||||
|
assert_match {} [errorstat ERR]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {errorstats: rejected call due to wrong arity} {
|
||||||
|
r config resetstat
|
||||||
|
assert_equal [s total_error_replies] 0
|
||||||
|
assert_match {} [errorstat ERR]
|
||||||
|
catch {r set k} e
|
||||||
|
assert_match {ERR wrong number of arguments*} $e
|
||||||
|
assert_match {*count=1*} [errorstat ERR]
|
||||||
|
assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat set]
|
||||||
|
# ensure that after a rejected command, valid ones are counted properly
|
||||||
|
r set k1 v1
|
||||||
|
r set k2 v2
|
||||||
|
assert_match {calls=2,*,rejected_calls=1,failed_calls=0} [cmdstat set]
|
||||||
|
assert_equal [s total_error_replies] 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test {errorstats: rejected call by OOM error} {
|
||||||
|
r config resetstat
|
||||||
|
assert_equal [s total_error_replies] 0
|
||||||
|
assert_match {} [errorstat OOM]
|
||||||
|
r config set maxmemory 1
|
||||||
|
catch {r set a b} e
|
||||||
|
assert_match {OOM*} $e
|
||||||
|
assert_match {*count=1*} [errorstat OOM]
|
||||||
|
assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat set]
|
||||||
|
assert_equal [s total_error_replies] 1
|
||||||
|
r config resetstat
|
||||||
|
assert_match {} [errorstat OOM]
|
||||||
|
}
|
||||||
|
|
||||||
|
test {errorstats: rejected call by authorization error} {
|
||||||
|
r config resetstat
|
||||||
|
assert_equal [s total_error_replies] 0
|
||||||
|
assert_match {} [errorstat NOPERM]
|
||||||
|
r ACL SETUSER alice on >p1pp0 ~cached:* +get +info +config
|
||||||
|
r auth alice p1pp0
|
||||||
|
catch {r set a b} e
|
||||||
|
assert_match {NOPERM*} $e
|
||||||
|
assert_match {*count=1*} [errorstat NOPERM]
|
||||||
|
assert_match {*calls=0,*,rejected_calls=1,failed_calls=0} [cmdstat set]
|
||||||
|
assert_equal [s total_error_replies] 1
|
||||||
|
r config resetstat
|
||||||
|
assert_match {} [errorstat NOPERM]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@ start_server {tags {"memefficiency"}} {
|
|||||||
|
|
||||||
run_solo {defrag} {
|
run_solo {defrag} {
|
||||||
start_server {tags {"defrag"} overrides {appendonly yes auto-aof-rewrite-percentage 0 save ""}} {
|
start_server {tags {"defrag"} overrides {appendonly yes auto-aof-rewrite-percentage 0 save ""}} {
|
||||||
if {[string match {*jemalloc*} [s mem_allocator]]} {
|
if {[string match {*jemalloc*} [s mem_allocator]] && [r debug mallctl arenas.page] <= 8192} {
|
||||||
test "Active defrag" {
|
test "Active defrag" {
|
||||||
r config set hz 100
|
r config set hz 100
|
||||||
r config set activedefrag no
|
r config set activedefrag no
|
||||||
|
@ -14,25 +14,103 @@ tags "modules" {
|
|||||||
# Start the replication process...
|
# Start the replication process...
|
||||||
$replica replicaof $master_host $master_port
|
$replica replicaof $master_host $master_port
|
||||||
wait_for_sync $replica
|
wait_for_sync $replica
|
||||||
|
|
||||||
after 1000
|
after 1000
|
||||||
$master propagate-test
|
|
||||||
|
|
||||||
wait_for_condition 5000 10 {
|
test {module propagates from timer} {
|
||||||
([$replica get timer] eq "10") && \
|
set repl [attach_to_replication_stream]
|
||||||
([$replica get a-from-thread] eq "10")
|
|
||||||
} else {
|
$master propagate-test.timer
|
||||||
fail "The two counters don't match the expected value."
|
|
||||||
|
wait_for_condition 5000 10 {
|
||||||
|
[$replica get timer] eq "3"
|
||||||
|
} else {
|
||||||
|
fail "The two counters don't match the expected value."
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_replication_stream $repl {
|
||||||
|
{select *}
|
||||||
|
{multi}
|
||||||
|
{incr timer}
|
||||||
|
{exec}
|
||||||
|
{multi}
|
||||||
|
{incr timer}
|
||||||
|
{exec}
|
||||||
|
{multi}
|
||||||
|
{incr timer}
|
||||||
|
{exec}
|
||||||
|
}
|
||||||
|
close_replication_stream $repl
|
||||||
}
|
}
|
||||||
|
|
||||||
$master propagate-test-2
|
test {module propagates from thread} {
|
||||||
$master propagate-test-3
|
set repl [attach_to_replication_stream]
|
||||||
$master multi
|
|
||||||
$master propagate-test-2
|
|
||||||
$master propagate-test-3
|
|
||||||
$master exec
|
|
||||||
wait_for_ofs_sync $master $replica
|
|
||||||
|
|
||||||
|
$master propagate-test.thread
|
||||||
|
|
||||||
|
wait_for_condition 5000 10 {
|
||||||
|
[$replica get a-from-thread] eq "3"
|
||||||
|
} else {
|
||||||
|
fail "The two counters don't match the expected value."
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_replication_stream $repl {
|
||||||
|
{select *}
|
||||||
|
{incr a-from-thread}
|
||||||
|
{incr b-from-thread}
|
||||||
|
{incr a-from-thread}
|
||||||
|
{incr b-from-thread}
|
||||||
|
{incr a-from-thread}
|
||||||
|
{incr b-from-thread}
|
||||||
|
}
|
||||||
|
close_replication_stream $repl
|
||||||
|
}
|
||||||
|
|
||||||
|
test {module propagates from from command} {
|
||||||
|
set repl [attach_to_replication_stream]
|
||||||
|
|
||||||
|
$master propagate-test.simple
|
||||||
|
$master propagate-test.mixed
|
||||||
|
|
||||||
|
# Note the 'after-call' propagation below is out of order (known limitation)
|
||||||
|
assert_replication_stream $repl {
|
||||||
|
{select *}
|
||||||
|
{multi}
|
||||||
|
{incr counter-1}
|
||||||
|
{incr counter-2}
|
||||||
|
{exec}
|
||||||
|
{multi}
|
||||||
|
{incr using-call}
|
||||||
|
{incr after-call}
|
||||||
|
{incr counter-1}
|
||||||
|
{incr counter-2}
|
||||||
|
{exec}
|
||||||
|
}
|
||||||
|
close_replication_stream $repl
|
||||||
|
}
|
||||||
|
|
||||||
|
test {module propagates from from multi-exec} {
|
||||||
|
set repl [attach_to_replication_stream]
|
||||||
|
|
||||||
|
$master multi
|
||||||
|
$master propagate-test.simple
|
||||||
|
$master propagate-test.mixed
|
||||||
|
$master exec
|
||||||
|
wait_for_ofs_sync $master $replica
|
||||||
|
|
||||||
|
# Note the 'after-call' propagation below is out of order (known limitation)
|
||||||
|
assert_replication_stream $repl {
|
||||||
|
{select *}
|
||||||
|
{multi}
|
||||||
|
{incr counter-1}
|
||||||
|
{incr counter-2}
|
||||||
|
{incr using-call}
|
||||||
|
{incr after-call}
|
||||||
|
{incr counter-1}
|
||||||
|
{incr counter-2}
|
||||||
|
{exec}
|
||||||
|
}
|
||||||
|
close_replication_stream $repl
|
||||||
|
}
|
||||||
assert_equal [s -1 unexpected_error_replies] 0
|
assert_equal [s -1 unexpected_error_replies] 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,11 +125,11 @@ tags "modules aof" {
|
|||||||
r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite.
|
r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite.
|
||||||
waitForBgrewriteaof r
|
waitForBgrewriteaof r
|
||||||
|
|
||||||
r propagate-test-2
|
r propagate-test.simple
|
||||||
r propagate-test-3
|
r propagate-test.mixed
|
||||||
r multi
|
r multi
|
||||||
r propagate-test-2
|
r propagate-test.simple
|
||||||
r propagate-test-3
|
r propagate-test.mixed
|
||||||
r exec
|
r exec
|
||||||
|
|
||||||
# Load the AOF
|
# Load the AOF
|
||||||
|
@ -196,6 +196,29 @@ start_server {tags {"multi"}} {
|
|||||||
r exec
|
r exec
|
||||||
} {PONG}
|
} {PONG}
|
||||||
|
|
||||||
|
test {SWAPDB is able to touch the watched keys that exist} {
|
||||||
|
r flushall
|
||||||
|
r select 0
|
||||||
|
r set x 30
|
||||||
|
r watch x ;# make sure x (set to 30) doesn't change (SWAPDB will "delete" it)
|
||||||
|
r swapdb 0 1
|
||||||
|
r multi
|
||||||
|
r ping
|
||||||
|
r exec
|
||||||
|
} {}
|
||||||
|
|
||||||
|
test {SWAPDB is able to touch the watched keys that do not exist} {
|
||||||
|
r flushall
|
||||||
|
r select 1
|
||||||
|
r set x 30
|
||||||
|
r select 0
|
||||||
|
r watch x ;# make sure the key x (currently missing) doesn't change (SWAPDB will create it)
|
||||||
|
r swapdb 0 1
|
||||||
|
r multi
|
||||||
|
r ping
|
||||||
|
r exec
|
||||||
|
} {}
|
||||||
|
|
||||||
test {WATCH is able to remember the DB a key belongs to} {
|
test {WATCH is able to remember the DB a key belongs to} {
|
||||||
r select 5
|
r select 5
|
||||||
r set x 30
|
r set x 30
|
||||||
@ -299,10 +322,14 @@ start_server {tags {"multi"}} {
|
|||||||
r multi
|
r multi
|
||||||
r del foo
|
r del foo
|
||||||
r exec
|
r exec
|
||||||
|
|
||||||
|
# add another command so that when we see it we know multi-exec wasn't
|
||||||
|
# propagated
|
||||||
|
r incr foo
|
||||||
|
|
||||||
assert_replication_stream $repl {
|
assert_replication_stream $repl {
|
||||||
{select *}
|
{select *}
|
||||||
{multi}
|
{incr foo}
|
||||||
{exec}
|
|
||||||
}
|
}
|
||||||
close_replication_stream $repl
|
close_replication_stream $repl
|
||||||
}
|
}
|
||||||
@ -521,4 +548,83 @@ start_server {tags {"multi"}} {
|
|||||||
|
|
||||||
list $m $res
|
list $m $res
|
||||||
} {OK {{} {} {} {} {} {} {} {}}}
|
} {OK {{} {} {} {} {} {} {} {}}}
|
||||||
|
|
||||||
|
test {MULTI propagation of PUBLISH} {
|
||||||
|
set repl [attach_to_replication_stream]
|
||||||
|
|
||||||
|
# make sure that PUBLISH inside MULTI is propagated in a transaction
|
||||||
|
r multi
|
||||||
|
r publish bla bla
|
||||||
|
r exec
|
||||||
|
|
||||||
|
assert_replication_stream $repl {
|
||||||
|
{select *}
|
||||||
|
{multi}
|
||||||
|
{publish bla bla}
|
||||||
|
{exec}
|
||||||
|
}
|
||||||
|
close_replication_stream $repl
|
||||||
|
}
|
||||||
|
|
||||||
|
test {MULTI propagation of SCRIPT LOAD} {
|
||||||
|
set repl [attach_to_replication_stream]
|
||||||
|
|
||||||
|
# make sure that SCRIPT LOAD inside MULTI is propagated in a transaction
|
||||||
|
r multi
|
||||||
|
r script load {redis.call('set', KEYS[1], 'foo')}
|
||||||
|
set res [r exec]
|
||||||
|
set sha [lindex $res 0]
|
||||||
|
|
||||||
|
assert_replication_stream $repl {
|
||||||
|
{select *}
|
||||||
|
{multi}
|
||||||
|
{script load *}
|
||||||
|
{exec}
|
||||||
|
}
|
||||||
|
close_replication_stream $repl
|
||||||
|
}
|
||||||
|
|
||||||
|
test {MULTI propagation of SCRIPT LOAD} {
|
||||||
|
set repl [attach_to_replication_stream]
|
||||||
|
|
||||||
|
# make sure that EVAL inside MULTI is propagated in a transaction
|
||||||
|
r config set lua-replicate-commands no
|
||||||
|
r multi
|
||||||
|
r eval {redis.call('set', KEYS[1], 'bar')} 1 bar
|
||||||
|
r exec
|
||||||
|
|
||||||
|
assert_replication_stream $repl {
|
||||||
|
{select *}
|
||||||
|
{multi}
|
||||||
|
{eval *}
|
||||||
|
{exec}
|
||||||
|
}
|
||||||
|
close_replication_stream $repl
|
||||||
|
}
|
||||||
|
|
||||||
|
tags {"stream"} {
|
||||||
|
test {MULTI propagation of XREADGROUP} {
|
||||||
|
# stream is a special case because it calls propagate() directly for XREADGROUP
|
||||||
|
set repl [attach_to_replication_stream]
|
||||||
|
|
||||||
|
r XADD mystream * foo bar
|
||||||
|
r XGROUP CREATE mystream mygroup 0
|
||||||
|
|
||||||
|
# make sure the XCALIM (propagated by XREADGROUP) is indeed inside MULTI/EXEC
|
||||||
|
r multi
|
||||||
|
r XREADGROUP GROUP mygroup consumer1 STREAMS mystream ">"
|
||||||
|
r exec
|
||||||
|
|
||||||
|
assert_replication_stream $repl {
|
||||||
|
{select *}
|
||||||
|
{xadd *}
|
||||||
|
{xgroup CREATE *}
|
||||||
|
{multi}
|
||||||
|
{xclaim *}
|
||||||
|
{exec}
|
||||||
|
}
|
||||||
|
close_replication_stream $repl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
200
tests/unit/pause.tcl
Normal file
200
tests/unit/pause.tcl
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
start_server {tags {"pause"}} {
|
||||||
|
test "Test read commands are not blocked by client pause" {
|
||||||
|
r client PAUSE 100000000 WRITE
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
$rd GET FOO
|
||||||
|
$rd PING
|
||||||
|
$rd INFO
|
||||||
|
assert_equal [s 0 blocked_clients] 0
|
||||||
|
r client unpause
|
||||||
|
$rd close
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Test write commands are paused by RO" {
|
||||||
|
r client PAUSE 100000000 WRITE
|
||||||
|
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
$rd SET FOO BAR
|
||||||
|
wait_for_condition 50 100 {
|
||||||
|
[s 0 blocked_clients] eq {1}
|
||||||
|
} else {
|
||||||
|
fail "Clients are not blocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
r client unpause
|
||||||
|
assert_match "OK" [$rd read]
|
||||||
|
$rd close
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Test special commands are paused by RO" {
|
||||||
|
r PFADD pause-hll test
|
||||||
|
r client PAUSE 100000000 WRITE
|
||||||
|
|
||||||
|
# Test that pfcount, which can replicate, is also blocked
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
$rd PFCOUNT pause-hll
|
||||||
|
wait_for_condition 50 100 {
|
||||||
|
[s 0 blocked_clients] eq {1}
|
||||||
|
} else {
|
||||||
|
fail "Clients are not blocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test that publish, which adds the message to the replication
|
||||||
|
# stream is blocked.
|
||||||
|
set rd2 [redis_deferring_client]
|
||||||
|
$rd2 publish foo bar
|
||||||
|
wait_for_condition 50 100 {
|
||||||
|
[s 0 blocked_clients] eq {2}
|
||||||
|
} else {
|
||||||
|
fail "Clients are not blocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test that SCRIPT LOAD, which is replicated.
|
||||||
|
set rd3 [redis_deferring_client]
|
||||||
|
$rd3 script load "return 1"
|
||||||
|
wait_for_condition 50 100 {
|
||||||
|
[s 0 blocked_clients] eq {3}
|
||||||
|
} else {
|
||||||
|
fail "Clients are not blocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
r client unpause
|
||||||
|
assert_match "1" [$rd read]
|
||||||
|
assert_match "0" [$rd2 read]
|
||||||
|
assert_match "*" [$rd3 read]
|
||||||
|
$rd close
|
||||||
|
$rd2 close
|
||||||
|
$rd3 close
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Test read/admin mutli-execs are not blocked by pause RO" {
|
||||||
|
r SET FOO BAR
|
||||||
|
r client PAUSE 100000000 WRITE
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
$rd MULTI
|
||||||
|
assert_equal [$rd read] "OK"
|
||||||
|
$rd PING
|
||||||
|
assert_equal [$rd read] "QUEUED"
|
||||||
|
$rd GET FOO
|
||||||
|
assert_equal [$rd read] "QUEUED"
|
||||||
|
$rd EXEC
|
||||||
|
assert_equal [s 0 blocked_clients] 0
|
||||||
|
r client unpause
|
||||||
|
assert_match "PONG BAR" [$rd read]
|
||||||
|
$rd close
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Test write mutli-execs are blocked by pause RO" {
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
$rd MULTI
|
||||||
|
assert_equal [$rd read] "OK"
|
||||||
|
$rd SET FOO BAR
|
||||||
|
r client PAUSE 100000000 WRITE
|
||||||
|
assert_equal [$rd read] "QUEUED"
|
||||||
|
$rd EXEC
|
||||||
|
wait_for_condition 50 100 {
|
||||||
|
[s 0 blocked_clients] eq {1}
|
||||||
|
} else {
|
||||||
|
fail "Clients are not blocked"
|
||||||
|
}
|
||||||
|
r client unpause
|
||||||
|
assert_match "OK" [$rd read]
|
||||||
|
$rd close
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Test scripts are blocked by pause RO" {
|
||||||
|
r client PAUSE 100000000 WRITE
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
$rd EVAL "return 1" 0
|
||||||
|
|
||||||
|
wait_for_condition 50 100 {
|
||||||
|
[s 0 blocked_clients] eq {1}
|
||||||
|
} else {
|
||||||
|
fail "Clients are not blocked"
|
||||||
|
}
|
||||||
|
r client unpause
|
||||||
|
assert_match "1" [$rd read]
|
||||||
|
$rd close
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Test multiple clients can be queued up and unblocked" {
|
||||||
|
r client PAUSE 100000000 WRITE
|
||||||
|
set clients [list [redis_deferring_client] [redis_deferring_client] [redis_deferring_client]]
|
||||||
|
foreach client $clients {
|
||||||
|
$client SET FOO BAR
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_condition 50 100 {
|
||||||
|
[s 0 blocked_clients] eq {3}
|
||||||
|
} else {
|
||||||
|
fail "Clients are not blocked"
|
||||||
|
}
|
||||||
|
r client unpause
|
||||||
|
foreach client $clients {
|
||||||
|
assert_match "OK" [$client read]
|
||||||
|
$client close
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Test clients with syntax errors will get responses immediately" {
|
||||||
|
r client PAUSE 100000000 WRITE
|
||||||
|
catch {r set FOO} err
|
||||||
|
assert_match "ERR wrong number of arguments for *" $err
|
||||||
|
r client unpause
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Test both active and passive expiries are skipped during client pause" {
|
||||||
|
set expired_keys [s 0 expired_keys]
|
||||||
|
r multi
|
||||||
|
r set foo bar PX 10
|
||||||
|
r set bar foo PX 10
|
||||||
|
r client PAUSE 100000000 WRITE
|
||||||
|
r exec
|
||||||
|
|
||||||
|
wait_for_condition 10 100 {
|
||||||
|
[r get foo] == {} && [r get bar] == {}
|
||||||
|
} else {
|
||||||
|
fail "Keys were never logically expired"
|
||||||
|
}
|
||||||
|
|
||||||
|
# No keys should actually have been expired
|
||||||
|
assert_match $expired_keys [s 0 expired_keys]
|
||||||
|
|
||||||
|
r client unpause
|
||||||
|
|
||||||
|
# Force the keys to expire
|
||||||
|
r get foo
|
||||||
|
r get bar
|
||||||
|
|
||||||
|
# Now that clients have been unpaused, expires should go through
|
||||||
|
assert_match [expr $expired_keys + 2] [s 0 expired_keys]
|
||||||
|
}
|
||||||
|
|
||||||
|
test "Test that client pause starts at the end of a transaction" {
|
||||||
|
r MULTI
|
||||||
|
r SET FOO1 BAR
|
||||||
|
r client PAUSE 100000000 WRITE
|
||||||
|
r SET FOO2 BAR
|
||||||
|
r exec
|
||||||
|
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
$rd SET FOO3 BAR
|
||||||
|
|
||||||
|
wait_for_condition 50 100 {
|
||||||
|
[s 0 blocked_clients] eq {1}
|
||||||
|
} else {
|
||||||
|
fail "Clients are not blocked"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_match "BAR" [r GET FOO1]
|
||||||
|
assert_match "BAR" [r GET FOO2]
|
||||||
|
assert_match "" [r GET FOO3]
|
||||||
|
|
||||||
|
r client unpause
|
||||||
|
assert_match "OK" [$rd read]
|
||||||
|
$rd close
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure we unpause at the end
|
||||||
|
r client unpause
|
||||||
|
}
|
@ -563,6 +563,30 @@ start_server {tags {"scripting"}} {
|
|||||||
} e
|
} e
|
||||||
set e
|
set e
|
||||||
} {*wrong number*}
|
} {*wrong number*}
|
||||||
|
|
||||||
|
test {Script with RESP3 map} {
|
||||||
|
set expected_dict [dict create field value]
|
||||||
|
set expected_list [list field value]
|
||||||
|
|
||||||
|
# Sanity test for RESP3 without scripts
|
||||||
|
r HELLO 3
|
||||||
|
r hset hash field value
|
||||||
|
set res [r hgetall hash]
|
||||||
|
assert_equal $res $expected_dict
|
||||||
|
|
||||||
|
# Test RESP3 client with script in both RESP2 and RESP3 modes
|
||||||
|
set res [r eval {redis.setresp(3); return redis.call('hgetall', KEYS[1])} 1 hash]
|
||||||
|
assert_equal $res $expected_dict
|
||||||
|
set res [r eval {redis.setresp(2); return redis.call('hgetall', KEYS[1])} 1 hash]
|
||||||
|
assert_equal $res $expected_list
|
||||||
|
|
||||||
|
# Test RESP2 client with script in both RESP2 and RESP3 modes
|
||||||
|
r HELLO 2
|
||||||
|
set res [r eval {redis.setresp(3); return redis.call('hgetall', KEYS[1])} 1 hash]
|
||||||
|
assert_equal $res $expected_list
|
||||||
|
set res [r eval {redis.setresp(2); return redis.call('hgetall', KEYS[1])} 1 hash]
|
||||||
|
assert_equal $res $expected_list
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Start a new server since the last test in this stanza will kill the
|
# Start a new server since the last test in this stanza will kill the
|
||||||
|
@ -132,7 +132,24 @@ start_server {tags {"tracking"}} {
|
|||||||
|
|
||||||
test {HELLO 3 reply is correct} {
|
test {HELLO 3 reply is correct} {
|
||||||
set reply [r HELLO 3]
|
set reply [r HELLO 3]
|
||||||
assert {[lindex $reply 2] eq {proto 3}}
|
assert_equal [dict get $reply proto] 3
|
||||||
|
}
|
||||||
|
|
||||||
|
test {HELLO without protover} {
|
||||||
|
set reply [r HELLO 3]
|
||||||
|
assert_equal [dict get $reply proto] 3
|
||||||
|
|
||||||
|
set reply [r HELLO]
|
||||||
|
assert_equal [dict get $reply proto] 3
|
||||||
|
|
||||||
|
set reply [r HELLO 2]
|
||||||
|
assert_equal [dict get $reply proto] 2
|
||||||
|
|
||||||
|
set reply [r HELLO]
|
||||||
|
assert_equal [dict get $reply proto] 2
|
||||||
|
|
||||||
|
# restore RESP3 for next test
|
||||||
|
r HELLO 3
|
||||||
}
|
}
|
||||||
|
|
||||||
test {RESP3 based basic invalidation} {
|
test {RESP3 based basic invalidation} {
|
||||||
@ -308,6 +325,24 @@ start_server {tags {"tracking"}} {
|
|||||||
assert {$ping_reply eq {PONG}}
|
assert {$ping_reply eq {PONG}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {BCAST with prefix collisions throw errors} {
|
||||||
|
set r [redis_client]
|
||||||
|
catch {$r CLIENT TRACKING ON BCAST PREFIX FOOBAR PREFIX FOO} output
|
||||||
|
assert_match {ERR Prefix 'FOOBAR'*'FOO'*} $output
|
||||||
|
|
||||||
|
catch {$r CLIENT TRACKING ON BCAST PREFIX FOO PREFIX FOOBAR} output
|
||||||
|
assert_match {ERR Prefix 'FOO'*'FOOBAR'*} $output
|
||||||
|
|
||||||
|
$r CLIENT TRACKING ON BCAST PREFIX FOO PREFIX BAR
|
||||||
|
catch {$r CLIENT TRACKING ON BCAST PREFIX FO} output
|
||||||
|
assert_match {ERR Prefix 'FO'*'FOO'*} $output
|
||||||
|
|
||||||
|
catch {$r CLIENT TRACKING ON BCAST PREFIX BARB} output
|
||||||
|
assert_match {ERR Prefix 'BARB'*'BAR'*} $output
|
||||||
|
|
||||||
|
$r CLIENT TRACKING OFF
|
||||||
|
}
|
||||||
|
|
||||||
test {Tracking gets notification on tracking table key eviction} {
|
test {Tracking gets notification on tracking table key eviction} {
|
||||||
$rd_redirection HELLO 2
|
$rd_redirection HELLO 2
|
||||||
r CLIENT TRACKING off
|
r CLIENT TRACKING off
|
||||||
@ -398,6 +433,85 @@ start_server {tags {"tracking"}} {
|
|||||||
assert {$total_prefixes == 1}
|
assert {$total_prefixes == 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {CLIENT TRACKINGINFO provides reasonable results when tracking off} {
|
||||||
|
r CLIENT TRACKING off
|
||||||
|
set res [r client trackinginfo]
|
||||||
|
set flags [dict get $res flags]
|
||||||
|
assert_equal {off} $flags
|
||||||
|
set redirect [dict get $res redirect]
|
||||||
|
assert_equal {-1} $redirect
|
||||||
|
set prefixes [dict get $res prefixes]
|
||||||
|
assert_equal {} $prefixes
|
||||||
|
}
|
||||||
|
|
||||||
|
test {CLIENT TRACKINGINFO provides reasonable results when tracking on} {
|
||||||
|
r CLIENT TRACKING on
|
||||||
|
set res [r client trackinginfo]
|
||||||
|
set flags [dict get $res flags]
|
||||||
|
assert_equal {on} $flags
|
||||||
|
set redirect [dict get $res redirect]
|
||||||
|
assert_equal {0} $redirect
|
||||||
|
set prefixes [dict get $res prefixes]
|
||||||
|
assert_equal {} $prefixes
|
||||||
|
}
|
||||||
|
|
||||||
|
test {CLIENT TRACKINGINFO provides reasonable results when tracking on with options} {
|
||||||
|
r CLIENT TRACKING on REDIRECT $redir_id noloop
|
||||||
|
set res [r client trackinginfo]
|
||||||
|
set flags [dict get $res flags]
|
||||||
|
assert_equal {on noloop} $flags
|
||||||
|
set redirect [dict get $res redirect]
|
||||||
|
assert_equal $redir_id $redirect
|
||||||
|
set prefixes [dict get $res prefixes]
|
||||||
|
assert_equal {} $prefixes
|
||||||
|
}
|
||||||
|
|
||||||
|
test {CLIENT TRACKINGINFO provides reasonable results when tracking optin} {
|
||||||
|
r CLIENT TRACKING off
|
||||||
|
r CLIENT TRACKING on optin
|
||||||
|
set res [r client trackinginfo]
|
||||||
|
set flags [dict get $res flags]
|
||||||
|
assert_equal {on optin} $flags
|
||||||
|
set redirect [dict get $res redirect]
|
||||||
|
assert_equal {0} $redirect
|
||||||
|
set prefixes [dict get $res prefixes]
|
||||||
|
assert_equal {} $prefixes
|
||||||
|
|
||||||
|
r CLIENT CACHING yes
|
||||||
|
set res [r client trackinginfo]
|
||||||
|
set flags [dict get $res flags]
|
||||||
|
assert_equal {on optin caching-yes} $flags
|
||||||
|
}
|
||||||
|
|
||||||
|
test {CLIENT TRACKINGINFO provides reasonable results when tracking optout} {
|
||||||
|
r CLIENT TRACKING off
|
||||||
|
r CLIENT TRACKING on optout
|
||||||
|
set res [r client trackinginfo]
|
||||||
|
set flags [dict get $res flags]
|
||||||
|
assert_equal {on optout} $flags
|
||||||
|
set redirect [dict get $res redirect]
|
||||||
|
assert_equal {0} $redirect
|
||||||
|
set prefixes [dict get $res prefixes]
|
||||||
|
assert_equal {} $prefixes
|
||||||
|
|
||||||
|
r CLIENT CACHING no
|
||||||
|
set res [r client trackinginfo]
|
||||||
|
set flags [dict get $res flags]
|
||||||
|
assert_equal {on optout caching-no} $flags
|
||||||
|
}
|
||||||
|
|
||||||
|
test {CLIENT TRACKINGINFO provides reasonable results when tracking bcast mode} {
|
||||||
|
r CLIENT TRACKING off
|
||||||
|
r CLIENT TRACKING on BCAST PREFIX foo PREFIX bar
|
||||||
|
set res [r client trackinginfo]
|
||||||
|
set flags [dict get $res flags]
|
||||||
|
assert_equal {on bcast} $flags
|
||||||
|
set redirect [dict get $res redirect]
|
||||||
|
assert_equal {0} $redirect
|
||||||
|
set prefixes [lsort [dict get $res prefixes]]
|
||||||
|
assert_equal {bar foo} $prefixes
|
||||||
|
}
|
||||||
|
|
||||||
$rd_redirection close
|
$rd_redirection close
|
||||||
$rd close
|
$rd close
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,17 @@ start_server {
|
|||||||
r lpop non-existing-list
|
r lpop non-existing-list
|
||||||
} {}
|
} {}
|
||||||
|
|
||||||
|
test {R/LPOP with the optional count argument} {
|
||||||
|
assert_equal 7 [r lpush listcount aa bb cc dd ee ff gg]
|
||||||
|
assert_equal {} [r lpop listcount 0]
|
||||||
|
assert_equal {gg} [r lpop listcount 1]
|
||||||
|
assert_equal {ff ee} [r lpop listcount 2]
|
||||||
|
assert_equal {aa bb} [r rpop listcount 2]
|
||||||
|
assert_equal {cc} [r rpop listcount 1]
|
||||||
|
assert_equal {dd} [r rpop listcount 123]
|
||||||
|
assert_error "*ERR*range*" {r lpop forbarqaz -123}
|
||||||
|
}
|
||||||
|
|
||||||
test {Variadic RPUSH/LPUSH} {
|
test {Variadic RPUSH/LPUSH} {
|
||||||
r del mylist
|
r del mylist
|
||||||
assert_equal 4 [r lpush mylist a b c d]
|
assert_equal 4 [r lpush mylist a b c d]
|
||||||
@ -947,6 +958,12 @@ start_server {
|
|||||||
assert_equal {} [r lrange nosuchkey 0 1]
|
assert_equal {} [r lrange nosuchkey 0 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {LRANGE with start > end yields an empty array for backward compatibility} {
|
||||||
|
create_list mylist "1 2 3"
|
||||||
|
assert_equal {} [r lrange mylist 1 0]
|
||||||
|
assert_equal {} [r lrange mylist -1 -2]
|
||||||
|
}
|
||||||
|
|
||||||
foreach {type large} [array get largevalue] {
|
foreach {type large} [array get largevalue] {
|
||||||
proc trim_list {type min max} {
|
proc trim_list {type min max} {
|
||||||
upvar 1 large large
|
upvar 1 large large
|
||||||
|
@ -27,7 +27,7 @@ start_server {
|
|||||||
# and not the element "foo bar" which was pre existing in the
|
# and not the element "foo bar" which was pre existing in the
|
||||||
# stream (see previous test)
|
# stream (see previous test)
|
||||||
set reply [
|
set reply [
|
||||||
r XREADGROUP GROUP mygroup client-1 STREAMS mystream ">"
|
r XREADGROUP GROUP mygroup consumer-1 STREAMS mystream ">"
|
||||||
]
|
]
|
||||||
assert {[llength [lindex $reply 0 1]] == 2}
|
assert {[llength [lindex $reply 0 1]] == 2}
|
||||||
lindex $reply 0 1 0 1
|
lindex $reply 0 1 0 1
|
||||||
@ -39,13 +39,13 @@ start_server {
|
|||||||
r XADD mystream * d 4
|
r XADD mystream * d 4
|
||||||
# Read a few elements using a different consumer name
|
# Read a few elements using a different consumer name
|
||||||
set reply [
|
set reply [
|
||||||
r XREADGROUP GROUP mygroup client-2 STREAMS mystream ">"
|
r XREADGROUP GROUP mygroup consumer-2 STREAMS mystream ">"
|
||||||
]
|
]
|
||||||
assert {[llength [lindex $reply 0 1]] == 2}
|
assert {[llength [lindex $reply 0 1]] == 2}
|
||||||
assert {[lindex $reply 0 1 0 1] eq {c 3}}
|
assert {[lindex $reply 0 1 0 1] eq {c 3}}
|
||||||
|
|
||||||
set r1 [r XREADGROUP GROUP mygroup client-1 COUNT 10 STREAMS mystream 0]
|
set r1 [r XREADGROUP GROUP mygroup consumer-1 COUNT 10 STREAMS mystream 0]
|
||||||
set r2 [r XREADGROUP GROUP mygroup client-2 COUNT 10 STREAMS mystream 0]
|
set r2 [r XREADGROUP GROUP mygroup consumer-2 COUNT 10 STREAMS mystream 0]
|
||||||
assert {[lindex $r1 0 1 0 1] eq {a 1}}
|
assert {[lindex $r1 0 1 0 1] eq {a 1}}
|
||||||
assert {[lindex $r2 0 1 0 1] eq {c 3}}
|
assert {[lindex $r2 0 1 0 1] eq {c 3}}
|
||||||
}
|
}
|
||||||
@ -56,9 +56,9 @@ start_server {
|
|||||||
for {set j 0} {$j < 4} {incr j} {
|
for {set j 0} {$j < 4} {incr j} {
|
||||||
set item [lindex $pending $j]
|
set item [lindex $pending $j]
|
||||||
if {$j < 2} {
|
if {$j < 2} {
|
||||||
set owner client-1
|
set owner consumer-1
|
||||||
} else {
|
} else {
|
||||||
set owner client-2
|
set owner consumer-2
|
||||||
}
|
}
|
||||||
assert {[lindex $item 1] eq $owner}
|
assert {[lindex $item 1] eq $owner}
|
||||||
assert {[lindex $item 1] eq $owner}
|
assert {[lindex $item 1] eq $owner}
|
||||||
@ -66,7 +66,7 @@ start_server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test {XPENDING can return single consumer items} {
|
test {XPENDING can return single consumer items} {
|
||||||
set pending [r XPENDING mystream mygroup - + 10 client-1]
|
set pending [r XPENDING mystream mygroup - + 10 consumer-1]
|
||||||
assert {[llength $pending] == 2}
|
assert {[llength $pending] == 2}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,9 +77,9 @@ start_server {
|
|||||||
|
|
||||||
test {XPENDING with IDLE} {
|
test {XPENDING with IDLE} {
|
||||||
after 20
|
after 20
|
||||||
set pending [r XPENDING mystream mygroup IDLE 99999999 - + 10 client-1]
|
set pending [r XPENDING mystream mygroup IDLE 99999999 - + 10 consumer-1]
|
||||||
assert {[llength $pending] == 0}
|
assert {[llength $pending] == 0}
|
||||||
set pending [r XPENDING mystream mygroup IDLE 1 - + 10 client-1]
|
set pending [r XPENDING mystream mygroup IDLE 1 - + 10 consumer-1]
|
||||||
assert {[llength $pending] == 2}
|
assert {[llength $pending] == 2}
|
||||||
set pending [r XPENDING mystream mygroup IDLE 99999999 - + 10]
|
set pending [r XPENDING mystream mygroup IDLE 99999999 - + 10]
|
||||||
assert {[llength $pending] == 0}
|
assert {[llength $pending] == 0}
|
||||||
@ -101,12 +101,12 @@ start_server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test {XACK is able to remove items from the client/group PEL} {
|
test {XACK is able to remove items from the consumer/group PEL} {
|
||||||
set pending [r XPENDING mystream mygroup - + 10 client-1]
|
set pending [r XPENDING mystream mygroup - + 10 consumer-1]
|
||||||
set id1 [lindex $pending 0 0]
|
set id1 [lindex $pending 0 0]
|
||||||
set id2 [lindex $pending 1 0]
|
set id2 [lindex $pending 1 0]
|
||||||
assert {[r XACK mystream mygroup $id1] eq 1}
|
assert {[r XACK mystream mygroup $id1] eq 1}
|
||||||
set pending [r XPENDING mystream mygroup - + 10 client-1]
|
set pending [r XPENDING mystream mygroup - + 10 consumer-1]
|
||||||
assert {[llength $pending] == 1}
|
assert {[llength $pending] == 1}
|
||||||
set id [lindex $pending 0 0]
|
set id [lindex $pending 0 0]
|
||||||
assert {$id eq $id2}
|
assert {$id eq $id2}
|
||||||
@ -242,52 +242,52 @@ start_server {
|
|||||||
set id3 [r XADD mystream * c 3]
|
set id3 [r XADD mystream * c 3]
|
||||||
r XGROUP CREATE mystream mygroup 0
|
r XGROUP CREATE mystream mygroup 0
|
||||||
|
|
||||||
# Client 1 reads item 1 from the stream without acknowledgements.
|
# Consumer 1 reads item 1 from the stream without acknowledgements.
|
||||||
# Client 2 then claims pending item 1 from the PEL of client 1
|
# Consumer 2 then claims pending item 1 from the PEL of consumer 1
|
||||||
set reply [
|
set reply [
|
||||||
r XREADGROUP GROUP mygroup client1 count 1 STREAMS mystream >
|
r XREADGROUP GROUP mygroup consumer1 count 1 STREAMS mystream >
|
||||||
]
|
]
|
||||||
assert {[llength [lindex $reply 0 1 0 1]] == 2}
|
assert {[llength [lindex $reply 0 1 0 1]] == 2}
|
||||||
assert {[lindex $reply 0 1 0 1] eq {a 1}}
|
assert {[lindex $reply 0 1 0 1] eq {a 1}}
|
||||||
|
|
||||||
# make sure the entry is present in both the gorup, and the right consumer
|
# make sure the entry is present in both the gorup, and the right consumer
|
||||||
assert {[llength [r XPENDING mystream mygroup - + 10]] == 1}
|
assert {[llength [r XPENDING mystream mygroup - + 10]] == 1}
|
||||||
assert {[llength [r XPENDING mystream mygroup - + 10 client1]] == 1}
|
assert {[llength [r XPENDING mystream mygroup - + 10 consumer1]] == 1}
|
||||||
assert {[llength [r XPENDING mystream mygroup - + 10 client2]] == 0}
|
assert {[llength [r XPENDING mystream mygroup - + 10 consumer2]] == 0}
|
||||||
|
|
||||||
r debug sleep 0.2
|
after 200
|
||||||
set reply [
|
set reply [
|
||||||
r XCLAIM mystream mygroup client2 10 $id1
|
r XCLAIM mystream mygroup consumer2 10 $id1
|
||||||
]
|
]
|
||||||
assert {[llength [lindex $reply 0 1]] == 2}
|
assert {[llength [lindex $reply 0 1]] == 2}
|
||||||
assert {[lindex $reply 0 1] eq {a 1}}
|
assert {[lindex $reply 0 1] eq {a 1}}
|
||||||
|
|
||||||
# make sure the entry is present in both the gorup, and the right consumer
|
# make sure the entry is present in both the gorup, and the right consumer
|
||||||
assert {[llength [r XPENDING mystream mygroup - + 10]] == 1}
|
assert {[llength [r XPENDING mystream mygroup - + 10]] == 1}
|
||||||
assert {[llength [r XPENDING mystream mygroup - + 10 client1]] == 0}
|
assert {[llength [r XPENDING mystream mygroup - + 10 consumer1]] == 0}
|
||||||
assert {[llength [r XPENDING mystream mygroup - + 10 client2]] == 1}
|
assert {[llength [r XPENDING mystream mygroup - + 10 consumer2]] == 1}
|
||||||
|
|
||||||
# Client 1 reads another 2 items from stream
|
# Consumer 1 reads another 2 items from stream
|
||||||
r XREADGROUP GROUP mygroup client1 count 2 STREAMS mystream >
|
r XREADGROUP GROUP mygroup consumer1 count 2 STREAMS mystream >
|
||||||
r debug sleep 0.2
|
after 200
|
||||||
|
|
||||||
# Delete item 2 from the stream. Now client 1 has PEL that contains
|
# Delete item 2 from the stream. Now consumer 1 has PEL that contains
|
||||||
# only item 3. Try to use client 2 to claim the deleted item 2
|
# only item 3. Try to use consumer 2 to claim the deleted item 2
|
||||||
# from the PEL of client 1, this should return nil
|
# from the PEL of consumer 1, this should return nil
|
||||||
r XDEL mystream $id2
|
r XDEL mystream $id2
|
||||||
set reply [
|
set reply [
|
||||||
r XCLAIM mystream mygroup client2 10 $id2
|
r XCLAIM mystream mygroup consumer2 10 $id2
|
||||||
]
|
]
|
||||||
assert {[llength $reply] == 1}
|
assert {[llength $reply] == 1}
|
||||||
assert_equal "" [lindex $reply 0]
|
assert_equal "" [lindex $reply 0]
|
||||||
|
|
||||||
# Delete item 3 from the stream. Now client 1 has PEL that is empty.
|
# Delete item 3 from the stream. Now consumer 1 has PEL that is empty.
|
||||||
# Try to use client 2 to claim the deleted item 3 from the PEL
|
# Try to use consumer 2 to claim the deleted item 3 from the PEL
|
||||||
# of client 1, this should return nil
|
# of consumer 1, this should return nil
|
||||||
r debug sleep 0.2
|
after 200
|
||||||
r XDEL mystream $id3
|
r XDEL mystream $id3
|
||||||
set reply [
|
set reply [
|
||||||
r XCLAIM mystream mygroup client2 10 $id3
|
r XCLAIM mystream mygroup consumer2 10 $id3
|
||||||
]
|
]
|
||||||
assert {[llength $reply] == 1}
|
assert {[llength $reply] == 1}
|
||||||
assert_equal "" [lindex $reply 0]
|
assert_equal "" [lindex $reply 0]
|
||||||
@ -301,16 +301,16 @@ start_server {
|
|||||||
set id3 [r XADD mystream * c 3]
|
set id3 [r XADD mystream * c 3]
|
||||||
r XGROUP CREATE mystream mygroup 0
|
r XGROUP CREATE mystream mygroup 0
|
||||||
|
|
||||||
# Client 1 reads item 1 from the stream without acknowledgements.
|
# Consumer 1 reads item 1 from the stream without acknowledgements.
|
||||||
# Client 2 then claims pending item 1 from the PEL of client 1
|
# Consumer 2 then claims pending item 1 from the PEL of consumer 1
|
||||||
set reply [
|
set reply [
|
||||||
r XREADGROUP GROUP mygroup client1 count 1 STREAMS mystream >
|
r XREADGROUP GROUP mygroup consumer1 count 1 STREAMS mystream >
|
||||||
]
|
]
|
||||||
assert {[llength [lindex $reply 0 1 0 1]] == 2}
|
assert {[llength [lindex $reply 0 1 0 1]] == 2}
|
||||||
assert {[lindex $reply 0 1 0 1] eq {a 1}}
|
assert {[lindex $reply 0 1 0 1] eq {a 1}}
|
||||||
r debug sleep 0.2
|
after 200
|
||||||
set reply [
|
set reply [
|
||||||
r XCLAIM mystream mygroup client2 10 $id1
|
r XCLAIM mystream mygroup consumer2 10 $id1
|
||||||
]
|
]
|
||||||
assert {[llength [lindex $reply 0 1]] == 2}
|
assert {[llength [lindex $reply 0 1]] == 2}
|
||||||
assert {[lindex $reply 0 1] eq {a 1}}
|
assert {[lindex $reply 0 1] eq {a 1}}
|
||||||
@ -321,10 +321,10 @@ start_server {
|
|||||||
assert {[llength [lindex $reply 0]] == 4}
|
assert {[llength [lindex $reply 0]] == 4}
|
||||||
assert {[lindex $reply 0 3] == 2}
|
assert {[lindex $reply 0 3] == 2}
|
||||||
|
|
||||||
# Client 3 then claims pending item 1 from the PEL of client 2 using JUSTID
|
# Consumer 3 then claims pending item 1 from the PEL of consumer 2 using JUSTID
|
||||||
r debug sleep 0.2
|
after 200
|
||||||
set reply [
|
set reply [
|
||||||
r XCLAIM mystream mygroup client3 10 $id1 JUSTID
|
r XCLAIM mystream mygroup consumer3 10 $id1 JUSTID
|
||||||
]
|
]
|
||||||
assert {[llength $reply] == 1}
|
assert {[llength $reply] == 1}
|
||||||
assert {[lindex $reply 0] eq $id1}
|
assert {[lindex $reply 0] eq $id1}
|
||||||
@ -344,17 +344,122 @@ start_server {
|
|||||||
set id3 [r XADD mystream * c 3]
|
set id3 [r XADD mystream * c 3]
|
||||||
r XGROUP CREATE mystream mygroup 0
|
r XGROUP CREATE mystream mygroup 0
|
||||||
|
|
||||||
set reply [r XREADGROUP GROUP mygroup client1 count 1 STREAMS mystream >]
|
set reply [r XREADGROUP GROUP mygroup consumer1 count 1 STREAMS mystream >]
|
||||||
assert {[llength [lindex $reply 0 1 0 1]] == 2}
|
assert {[llength [lindex $reply 0 1 0 1]] == 2}
|
||||||
assert {[lindex $reply 0 1 0 1] eq {a 1}}
|
assert {[lindex $reply 0 1 0 1] eq {a 1}}
|
||||||
r debug sleep 0.2
|
after 200
|
||||||
# re-claim with the same consumer that already has it
|
# re-claim with the same consumer that already has it
|
||||||
assert {[llength [r XCLAIM mystream mygroup client1 10 $id1]] == 1}
|
assert {[llength [r XCLAIM mystream mygroup consumer1 10 $id1]] == 1}
|
||||||
|
|
||||||
# make sure the entry is still in the PEL
|
# make sure the entry is still in the PEL
|
||||||
set reply [r XPENDING mystream mygroup - + 10]
|
set reply [r XPENDING mystream mygroup - + 10]
|
||||||
assert {[llength $reply] == 1}
|
assert {[llength $reply] == 1}
|
||||||
assert {[lindex $reply 0 1] eq {client1}}
|
assert {[lindex $reply 0 1] eq {consumer1}}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {XAUTOCLAIM can claim PEL items from another consumer} {
|
||||||
|
# Add 3 items into the stream, and create a consumer group
|
||||||
|
r del mystream
|
||||||
|
set id1 [r XADD mystream * a 1]
|
||||||
|
set id2 [r XADD mystream * b 2]
|
||||||
|
set id3 [r XADD mystream * c 3]
|
||||||
|
r XGROUP CREATE mystream mygroup 0
|
||||||
|
|
||||||
|
# Consumer 1 reads item 1 from the stream without acknowledgements.
|
||||||
|
# Consumer 2 then claims pending item 1 from the PEL of consumer 1
|
||||||
|
set reply [r XREADGROUP GROUP mygroup consumer1 count 1 STREAMS mystream >]
|
||||||
|
assert_equal [llength [lindex $reply 0 1 0 1]] 2
|
||||||
|
assert_equal [lindex $reply 0 1 0 1] {a 1}
|
||||||
|
after 200
|
||||||
|
set reply [r XAUTOCLAIM mystream mygroup consumer2 10 - COUNT 1]
|
||||||
|
assert_equal [llength $reply] 2
|
||||||
|
assert_equal [lindex $reply 0] $id1
|
||||||
|
assert_equal [llength [lindex $reply 1]] 1
|
||||||
|
assert_equal [llength [lindex $reply 1 0]] 2
|
||||||
|
assert_equal [llength [lindex $reply 1 0 1]] 2
|
||||||
|
assert_equal [lindex $reply 1 0 1] {a 1}
|
||||||
|
|
||||||
|
# Consumer 1 reads another 2 items from stream
|
||||||
|
r XREADGROUP GROUP mygroup consumer1 count 2 STREAMS mystream >
|
||||||
|
|
||||||
|
# For min-idle-time
|
||||||
|
after 200
|
||||||
|
|
||||||
|
# Delete item 2 from the stream. Now consumer 1 has PEL that contains
|
||||||
|
# only item 3. Try to use consumer 2 to claim the deleted item 2
|
||||||
|
# from the PEL of consumer 1, this should return nil
|
||||||
|
r XDEL mystream $id2
|
||||||
|
set reply [r XAUTOCLAIM mystream mygroup consumer2 10 - COUNT 2]
|
||||||
|
# id1 is self-claimed here but not id2 ('count' was set to 2)
|
||||||
|
assert_equal [llength $reply] 2
|
||||||
|
assert_equal [lindex $reply 0] $id2
|
||||||
|
assert_equal [llength [lindex $reply 1]] 2
|
||||||
|
assert_equal [llength [lindex $reply 1 0]] 2
|
||||||
|
assert_equal [llength [lindex $reply 1 0 1]] 2
|
||||||
|
assert_equal [lindex $reply 1 0 1] {a 1}
|
||||||
|
assert_equal [lindex $reply 1 1] ""
|
||||||
|
|
||||||
|
# Delete item 3 from the stream. Now consumer 1 has PEL that is empty.
|
||||||
|
# Try to use consumer 2 to claim the deleted item 3 from the PEL
|
||||||
|
# of consumer 1, this should return nil
|
||||||
|
after 200
|
||||||
|
r XDEL mystream $id3
|
||||||
|
set reply [r XAUTOCLAIM mystream mygroup consumer2 10 - JUSTID]
|
||||||
|
# id1 is self-claimed here but not id2 and id3 ('count' is default 100)
|
||||||
|
|
||||||
|
# we also test the JUSTID modifier here. note that, when using JUSTID,
|
||||||
|
# deleted entries are returned in reply (consistent with XCLAIM).
|
||||||
|
|
||||||
|
assert_equal [llength $reply] 2
|
||||||
|
assert_equal [lindex $reply 0] "0-0"
|
||||||
|
assert_equal [llength [lindex $reply 1]] 3
|
||||||
|
assert_equal [lindex $reply 1 0] $id1
|
||||||
|
assert_equal [lindex $reply 1 1] $id2
|
||||||
|
assert_equal [lindex $reply 1 2] $id3
|
||||||
|
}
|
||||||
|
|
||||||
|
test {XAUTOCLAIM as an iterator} {
|
||||||
|
# Add 5 items into the stream, and create a consumer group
|
||||||
|
r del mystream
|
||||||
|
set id1 [r XADD mystream * a 1]
|
||||||
|
set id2 [r XADD mystream * b 2]
|
||||||
|
set id3 [r XADD mystream * c 3]
|
||||||
|
set id4 [r XADD mystream * d 4]
|
||||||
|
set id5 [r XADD mystream * e 5]
|
||||||
|
r XGROUP CREATE mystream mygroup 0
|
||||||
|
|
||||||
|
# Read 5 messages into consumer1
|
||||||
|
r XREADGROUP GROUP mygroup consumer1 count 90 STREAMS mystream >
|
||||||
|
|
||||||
|
# For min-idle-time
|
||||||
|
after 200
|
||||||
|
|
||||||
|
# Claim 2 entries
|
||||||
|
set reply [r XAUTOCLAIM mystream mygroup consumer2 10 - COUNT 2]
|
||||||
|
assert_equal [llength $reply] 2
|
||||||
|
set cursor [lindex $reply 0]
|
||||||
|
assert_equal $cursor $id2
|
||||||
|
assert_equal [llength [lindex $reply 1]] 2
|
||||||
|
assert_equal [llength [lindex $reply 1 0 1]] 2
|
||||||
|
assert_equal [lindex $reply 1 0 1] {a 1}
|
||||||
|
|
||||||
|
# Claim 2 more entries
|
||||||
|
set reply [r XAUTOCLAIM mystream mygroup consumer2 10 ($cursor COUNT 2]
|
||||||
|
assert_equal [llength $reply] 2
|
||||||
|
set cursor [lindex $reply 0]
|
||||||
|
assert_equal $cursor $id4
|
||||||
|
assert_equal [llength [lindex $reply 1]] 2
|
||||||
|
assert_equal [llength [lindex $reply 1 0 1]] 2
|
||||||
|
assert_equal [lindex $reply 1 0 1] {c 3}
|
||||||
|
|
||||||
|
# Claim last entry
|
||||||
|
set reply [r XAUTOCLAIM mystream mygroup consumer2 10 ($cursor COUNT 2]
|
||||||
|
assert_equal [llength $reply] 2
|
||||||
|
set cursor [lindex $reply 0]
|
||||||
|
assert_equal $cursor {0-0}
|
||||||
|
assert_equal [llength [lindex $reply 1]] 1
|
||||||
|
assert_equal [llength [lindex $reply 1 0 1]] 2
|
||||||
|
assert_equal [lindex $reply 1 0 1] {e 5}
|
||||||
}
|
}
|
||||||
|
|
||||||
test {XINFO FULL output} {
|
test {XINFO FULL output} {
|
||||||
@ -477,7 +582,7 @@ start_server {
|
|||||||
assert {$curr_grpinfo == $grpinfo}
|
assert {$curr_grpinfo == $grpinfo}
|
||||||
set n_consumers [lindex $grpinfo 3]
|
set n_consumers [lindex $grpinfo 3]
|
||||||
|
|
||||||
# Bob should be created only when there will be new data for this client
|
# Bob should be created only when there will be new data for this consumer
|
||||||
assert_equal $n_consumers 2
|
assert_equal $n_consumers 2
|
||||||
set reply [r xinfo consumers mystream mygroup]
|
set reply [r xinfo consumers mystream mygroup]
|
||||||
set consumer_info [lindex $reply 0]
|
set consumer_info [lindex $reply 0]
|
||||||
|
@ -94,6 +94,7 @@ start_server {
|
|||||||
r XADD mystream MAXLEN 5 * yitem $j
|
r XADD mystream MAXLEN 5 * yitem $j
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert {[r xlen mystream] == 5}
|
||||||
set res [r xrange mystream - +]
|
set res [r xrange mystream - +]
|
||||||
set expected 995
|
set expected 995
|
||||||
foreach r $res {
|
foreach r $res {
|
||||||
@ -138,6 +139,39 @@ start_server {
|
|||||||
assert_equal [lindex $items 1 1] {item 2 value b}
|
assert_equal [lindex $items 1 1] {item 2 value b}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {XADD with MINID option} {
|
||||||
|
r DEL mystream
|
||||||
|
for {set j 1} {$j < 1001} {incr j} {
|
||||||
|
set minid 1000
|
||||||
|
if {$j >= 5} {
|
||||||
|
set minid [expr {$j-5}]
|
||||||
|
}
|
||||||
|
if {rand() < 0.9} {
|
||||||
|
r XADD mystream MINID $minid $j xitem $j
|
||||||
|
} else {
|
||||||
|
r XADD mystream MINID $minid $j yitem $j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert {[r xlen mystream] == 6}
|
||||||
|
set res [r xrange mystream - +]
|
||||||
|
set expected 995
|
||||||
|
foreach r $res {
|
||||||
|
assert {[lindex $r 1 1] == $expected}
|
||||||
|
incr expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {XTRIM with MINID option} {
|
||||||
|
r DEL mystream
|
||||||
|
r XADD mystream 1-0 f v
|
||||||
|
r XADD mystream 2-0 f v
|
||||||
|
r XADD mystream 3-0 f v
|
||||||
|
r XADD mystream 4-0 f v
|
||||||
|
r XADD mystream 5-0 f v
|
||||||
|
r XTRIM mystream MINID = 3-0
|
||||||
|
assert_equal [r XRANGE mystream - +] {{3-0 {f v}} {4-0 {f v}} {5-0 {f v}}}
|
||||||
|
}
|
||||||
|
|
||||||
test {XADD mass insertion and XLEN} {
|
test {XADD mass insertion and XLEN} {
|
||||||
r DEL mystream
|
r DEL mystream
|
||||||
r multi
|
r multi
|
||||||
@ -448,7 +482,49 @@ start_server {
|
|||||||
assert {[r XLEN mystream] == 400}
|
assert {[r XLEN mystream] == 400}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {XADD with LIMIT consecutive calls} {
|
||||||
|
r del mystream
|
||||||
|
r config set stream-node-max-entries 10
|
||||||
|
for {set j 0} {$j < 100} {incr j} {
|
||||||
|
r XADD mystream * xitem v
|
||||||
|
}
|
||||||
|
r XADD mystream MAXLEN ~ 55 LIMIT 30 * xitem v
|
||||||
|
assert {[r xlen mystream] == 71}
|
||||||
|
r XADD mystream MAXLEN ~ 55 LIMIT 30 * xitem v
|
||||||
|
assert {[r xlen mystream] == 62}
|
||||||
|
r config set stream-node-max-entries 100
|
||||||
|
}
|
||||||
|
|
||||||
|
test {XTRIM with ~ is limited} {
|
||||||
|
r del mystream
|
||||||
|
r config set stream-node-max-entries 1
|
||||||
|
for {set j 0} {$j < 102} {incr j} {
|
||||||
|
r XADD mystream * xitem v
|
||||||
|
}
|
||||||
|
r XTRIM mystream MAXLEN ~ 1
|
||||||
|
assert {[r xlen mystream] == 2}
|
||||||
|
r config set stream-node-max-entries 100
|
||||||
|
}
|
||||||
|
|
||||||
|
test {XTRIM without ~ is not limited} {
|
||||||
|
r del mystream
|
||||||
|
r config set stream-node-max-entries 1
|
||||||
|
for {set j 0} {$j < 102} {incr j} {
|
||||||
|
r XADD mystream * xitem v
|
||||||
|
}
|
||||||
|
r XTRIM mystream MAXLEN 1
|
||||||
|
assert {[r xlen mystream] == 1}
|
||||||
|
r config set stream-node-max-entries 100
|
||||||
|
}
|
||||||
|
|
||||||
|
test {XTRIM without ~ and with LIMIT} {
|
||||||
|
r del mystream
|
||||||
|
r config set stream-node-max-entries 1
|
||||||
|
for {set j 0} {$j < 102} {incr j} {
|
||||||
|
r XADD mystream * xitem v
|
||||||
|
}
|
||||||
|
assert_error ERR* {r XTRIM mystream MAXLEN 1 LIMIT 30}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start_server {tags {"stream"} overrides {appendonly yes}} {
|
start_server {tags {"stream"} overrides {appendonly yes}} {
|
||||||
@ -466,6 +542,22 @@ start_server {tags {"stream"} overrides {appendonly yes}} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start_server {tags {"stream"} overrides {appendonly yes}} {
|
||||||
|
test {XADD with MINID > lastid can propagate correctly} {
|
||||||
|
for {set j 0} {$j < 100} {incr j} {
|
||||||
|
set id [expr {$j+1}]
|
||||||
|
r XADD mystream $id xitem v
|
||||||
|
}
|
||||||
|
r XADD mystream MINID 1 * xitem v
|
||||||
|
incr j
|
||||||
|
assert {[r xlen mystream] == $j}
|
||||||
|
r debug loadaof
|
||||||
|
r XADD mystream * xitem v
|
||||||
|
incr j
|
||||||
|
assert {[r xlen mystream] == $j}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
start_server {tags {"stream"} overrides {appendonly yes}} {
|
start_server {tags {"stream"} overrides {appendonly yes}} {
|
||||||
test {XADD with ~ MAXLEN can propagate correctly} {
|
test {XADD with ~ MAXLEN can propagate correctly} {
|
||||||
for {set j 0} {$j < 100} {incr j} {
|
for {set j 0} {$j < 100} {incr j} {
|
||||||
@ -482,6 +574,52 @@ start_server {tags {"stream"} overrides {appendonly yes}} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries 10}} {
|
||||||
|
test {XADD with ~ MAXLEN and LIMIT can propagate correctly} {
|
||||||
|
for {set j 0} {$j < 100} {incr j} {
|
||||||
|
r XADD mystream * xitem v
|
||||||
|
}
|
||||||
|
r XADD mystream MAXLEN ~ 55 LIMIT 30 * xitem v
|
||||||
|
assert {[r xlen mystream] == 71}
|
||||||
|
r config set stream-node-max-entries 1
|
||||||
|
r debug loadaof
|
||||||
|
r XADD mystream * xitem v
|
||||||
|
assert {[r xlen mystream] == 72}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start_server {tags {"stream"} overrides {appendonly yes}} {
|
||||||
|
test {XADD with ~ MINID can propagate correctly} {
|
||||||
|
for {set j 0} {$j < 100} {incr j} {
|
||||||
|
set id [expr {$j+1}]
|
||||||
|
r XADD mystream $id xitem v
|
||||||
|
}
|
||||||
|
r XADD mystream MINID ~ $j * xitem v
|
||||||
|
incr j
|
||||||
|
assert {[r xlen mystream] == $j}
|
||||||
|
r config set stream-node-max-entries 1
|
||||||
|
r debug loadaof
|
||||||
|
r XADD mystream * xitem v
|
||||||
|
incr j
|
||||||
|
assert {[r xlen mystream] == $j}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries 10}} {
|
||||||
|
test {XADD with ~ MINID and LIMIT can propagate correctly} {
|
||||||
|
for {set j 0} {$j < 100} {incr j} {
|
||||||
|
set id [expr {$j+1}]
|
||||||
|
r XADD mystream $id xitem v
|
||||||
|
}
|
||||||
|
r XADD mystream MINID ~ 55 LIMIT 30 * xitem v
|
||||||
|
assert {[r xlen mystream] == 71}
|
||||||
|
r config set stream-node-max-entries 1
|
||||||
|
r debug loadaof
|
||||||
|
r XADD mystream * xitem v
|
||||||
|
assert {[r xlen mystream] == 72}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries 10}} {
|
start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries 10}} {
|
||||||
test {XTRIM with ~ MAXLEN can propagate correctly} {
|
test {XTRIM with ~ MAXLEN can propagate correctly} {
|
||||||
for {set j 0} {$j < 100} {incr j} {
|
for {set j 0} {$j < 100} {incr j} {
|
||||||
|
@ -1472,4 +1472,86 @@ start_server {tags {"zset"}} {
|
|||||||
}
|
}
|
||||||
r config set zset-max-ziplist-entries $original_max
|
r config set zset-max-ziplist-entries $original_max
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {ZRANGESTORE basic} {
|
||||||
|
r flushall
|
||||||
|
r zadd z1 1 a 2 b 3 c 4 d
|
||||||
|
set res [r zrangestore z2 z1 0 -1]
|
||||||
|
assert_equal $res 4
|
||||||
|
r zrange z2 0 -1 withscores
|
||||||
|
} {a 1 b 2 c 3 d 4}
|
||||||
|
|
||||||
|
test {ZRANGESTORE range} {
|
||||||
|
set res [r zrangestore z2 z1 1 2]
|
||||||
|
assert_equal $res 2
|
||||||
|
r zrange z2 0 -1 withscores
|
||||||
|
} {b 2 c 3}
|
||||||
|
|
||||||
|
test {ZRANGESTORE BYLEX} {
|
||||||
|
set res [r zrangestore z2 z1 \[b \[c BYLEX]
|
||||||
|
assert_equal $res 2
|
||||||
|
r zrange z2 0 -1 withscores
|
||||||
|
} {b 2 c 3}
|
||||||
|
|
||||||
|
test {ZRANGESTORE BYSCORE} {
|
||||||
|
set res [r zrangestore z2 z1 1 2 BYSCORE]
|
||||||
|
assert_equal $res 2
|
||||||
|
r zrange z2 0 -1 withscores
|
||||||
|
} {a 1 b 2}
|
||||||
|
|
||||||
|
test {ZRANGESTORE BYSCORE LIMIT} {
|
||||||
|
set res [r zrangestore z2 z1 0 5 BYSCORE LIMIT 0 2]
|
||||||
|
assert_equal $res 2
|
||||||
|
r zrange z2 0 -1 withscores
|
||||||
|
} {a 1 b 2}
|
||||||
|
|
||||||
|
test {ZRANGESTORE BYSCORE REV LIMIT} {
|
||||||
|
set res [r zrangestore z2 z1 5 0 BYSCORE REV LIMIT 0 2]
|
||||||
|
assert_equal $res 2
|
||||||
|
r zrange z2 0 -1 withscores
|
||||||
|
} {c 3 d 4}
|
||||||
|
|
||||||
|
test {ZRANGE BYSCORE REV LIMIT} {
|
||||||
|
r zrange z1 5 0 BYSCORE REV LIMIT 0 2 WITHSCORES
|
||||||
|
} {d 4 c 3}
|
||||||
|
|
||||||
|
test {ZRANGESTORE - empty range} {
|
||||||
|
set res [r zrangestore z2 z1 5 6]
|
||||||
|
assert_equal $res 0
|
||||||
|
r exists z2
|
||||||
|
} {0}
|
||||||
|
|
||||||
|
test {ZRANGESTORE BYLEX - empty range} {
|
||||||
|
set res [r zrangestore z2 z1 \[f \[g BYLEX]
|
||||||
|
assert_equal $res 0
|
||||||
|
r exists z2
|
||||||
|
} {0}
|
||||||
|
|
||||||
|
test {ZRANGESTORE BYSCORE - empty range} {
|
||||||
|
set res [r zrangestore z2 z1 5 6 BYSCORE]
|
||||||
|
assert_equal $res 0
|
||||||
|
r exists z2
|
||||||
|
} {0}
|
||||||
|
|
||||||
|
test {ZRANGE BYLEX} {
|
||||||
|
r zrange z1 \[b \[c BYLEX
|
||||||
|
} {b c}
|
||||||
|
|
||||||
|
test {ZRANGESTORE invalid syntax} {
|
||||||
|
catch {r zrangestore z2 z1 0 -1 limit 1 2} err
|
||||||
|
assert_match "*syntax*" $err
|
||||||
|
catch {r zrangestore z2 z1 0 -1 WITHSCORES} err
|
||||||
|
assert_match "*syntax*" $err
|
||||||
|
}
|
||||||
|
|
||||||
|
test {ZRANGE invalid syntax} {
|
||||||
|
catch {r zrange z1 0 -1 limit 1 2} err
|
||||||
|
assert_match "*syntax*" $err
|
||||||
|
catch {r zrange z1 0 -1 BYLEX WITHSCORES} err
|
||||||
|
assert_match "*syntax*" $err
|
||||||
|
catch {r zrevrange z1 0 -1 BYSCORE} err
|
||||||
|
assert_match "*syntax*" $err
|
||||||
|
catch {r zrangebyscore z1 0 -1 REV} err
|
||||||
|
assert_match "*syntax*" $err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ start_server {} {
|
|||||||
set slave [srv 0 client]
|
set slave [srv 0 client]
|
||||||
set slave_host [srv 0 host]
|
set slave_host [srv 0 host]
|
||||||
set slave_port [srv 0 port]
|
set slave_port [srv 0 port]
|
||||||
|
set slave_pid [srv 0 pid]
|
||||||
set master [srv -1 client]
|
set master [srv -1 client]
|
||||||
set master_host [srv -1 host]
|
set master_host [srv -1 host]
|
||||||
set master_port [srv -1 port]
|
set master_port [srv -1 port]
|
||||||
@ -33,13 +34,25 @@ start_server {} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test {WAIT should not acknowledge 1 additional copy if slave is blocked} {
|
test {WAIT should not acknowledge 1 additional copy if slave is blocked} {
|
||||||
set cmd [rediscli $slave_host $slave_port "debug sleep 5"]
|
exec kill -SIGSTOP $slave_pid
|
||||||
exec {*}$cmd > /dev/null 2> /dev/null &
|
|
||||||
after 1000 ;# Give redis-cli the time to execute the command.
|
|
||||||
$master set foo 0
|
$master set foo 0
|
||||||
$master incr foo
|
$master incr foo
|
||||||
$master incr foo
|
$master incr foo
|
||||||
$master incr foo
|
$master incr foo
|
||||||
assert {[$master wait 1 3000] == 0}
|
assert {[$master wait 1 1000] == 0}
|
||||||
|
exec kill -SIGCONT $slave_pid
|
||||||
|
assert {[$master wait 1 1000] == 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {WAIT implicitly blocks on client pause since ACKs aren't sent} {
|
||||||
|
exec kill -SIGSTOP $slave_pid
|
||||||
|
$master multi
|
||||||
|
$master incr foo
|
||||||
|
$master client pause 10000 write
|
||||||
|
$master exec
|
||||||
|
assert {[$master wait 1 1000] == 0}
|
||||||
|
$master client unpause
|
||||||
|
exec kill -SIGCONT $slave_pid
|
||||||
|
assert {[$master wait 1 1000] == 1}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user