Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
464fef9bf8 | ||
|
|
aab16ead92 | ||
|
|
86afc0706d | ||
|
|
55b8f6ec1c | ||
|
|
647a261465 | ||
|
|
9c66cd91be | ||
|
|
6991792943 | ||
|
|
7d65b7199a | ||
|
|
c19cfde65d | ||
|
|
55f4b20f31 | ||
|
|
1606978a0b | ||
|
|
6200191943 | ||
|
|
2af2173a60 | ||
|
|
1dc1e31c2b | ||
|
|
b132121a71 | ||
|
|
d1d706c923 | ||
|
|
c99cd2fd40 | ||
|
|
f33d4a6c24 | ||
|
|
c2d99d49b5 | ||
|
|
e40f8378bd | ||
|
|
a0335e18ba | ||
|
|
950cb76e7b | ||
|
|
42e3630d77 | ||
|
|
1892b56224 | ||
|
|
2c8036f7b2 | ||
|
|
3294f74fef | ||
|
|
82d2e295b8 | ||
|
|
a3eb3f9c3b | ||
|
|
30a92b6c76 | ||
|
|
c1cc28f230 | ||
|
|
d79f9ebdb5 | ||
|
|
25e2791ec3 | ||
|
|
c955b47d62 | ||
|
|
bfe4c560e6 | ||
|
|
580ed0768f | ||
|
|
d90e3e3a70 | ||
|
|
89d95ec2e7 | ||
|
|
65194d11c3 | ||
|
|
16c2189d4e | ||
|
|
1b2bcd4210 | ||
|
|
6ec3dc2c42 | ||
|
|
02ae6cb01a | ||
|
|
fe05112029 | ||
|
|
cf76fdd04f | ||
|
|
9e34e296cf | ||
|
|
dfacfafc2a | ||
|
|
2fab983c69 | ||
|
|
a6d560db64 | ||
|
|
3f16fee39a | ||
|
|
e8e2ac072c | ||
|
|
85fa77e02e | ||
|
|
2d48d7c2fc | ||
|
|
6e6106797e | ||
|
|
96162c0c58 | ||
|
|
39eacde136 | ||
|
|
28aa15a6ea | ||
|
|
3917224f7c | ||
|
|
001ab29915 | ||
|
|
d31a394148 | ||
|
|
d83ab8f9d4 | ||
|
|
1dfab3d107 | ||
|
|
aee165e084 | ||
|
|
73b290cc5b | ||
|
|
3ced78dc6c | ||
|
|
6441a41f94 | ||
|
|
05dde71919 | ||
|
|
c499d82f28 | ||
|
|
c01aff1234 | ||
|
|
56cb037a86 | ||
|
|
d4edf63bc4 | ||
|
|
722d4f0c41 | ||
|
|
b26660e130 | ||
|
|
ac5fd5a519 | ||
|
|
890559ea1c | ||
|
|
905c55d5ae | ||
|
|
1b345ec3f0 | ||
|
|
351e790032 | ||
|
|
f71c7de88e | ||
|
|
0886c98fef | ||
|
|
4237f14a8a | ||
|
|
5efee4f0bb | ||
|
|
1d4d9e7b14 | ||
|
|
85492dcfef | ||
|
|
eb10bb31ac | ||
|
|
ebdb37cea7 | ||
|
|
9b73a274ac | ||
|
|
96973a7c33 | ||
|
|
fadbbdd3f4 | ||
|
|
688d32e16b | ||
|
|
3e4968339b | ||
|
|
301a0cfc69 | ||
|
|
4e809a9a19 | ||
|
|
ddcf160309 | ||
|
|
c5bc592650 | ||
|
|
5201ca0ca1 | ||
|
|
3da5cbe5bb | ||
|
|
0be31e2d22 | ||
|
|
66304fb122 | ||
|
|
1406112ec4 | ||
|
|
917b851491 | ||
|
|
8dda9dbef0 | ||
|
|
f0652c37a5 | ||
|
|
a2c9d38a9f | ||
|
|
89a731b3aa | ||
|
|
ecfefde760 | ||
|
|
43503ae5d6 | ||
|
|
b9c84d4aef | ||
|
|
248e916550 | ||
|
|
d401022d14 | ||
|
|
4ea91aadfb |
@@ -14,6 +14,96 @@ HIGH: There is a critical bug that may affect a subset of users. Upgrade!
|
||||
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--[ Redis 2.8.7 ] Release date: 5 Mar 2014
|
||||
|
||||
# UPGRADE URGENCY: LOW for Redis, LOW for Sentinel. However this release adds
|
||||
new features so users may want to upgrade in order to
|
||||
exploit the new functionalities.
|
||||
|
||||
* [FIX] Sometimes the absolute config file path was obtained in a wrong way.
|
||||
This happened when there was a "dir" directive inside the config file
|
||||
and at the same time the configuration file was given as a relative
|
||||
path to redis-server or redis-sentinel executables.
|
||||
* [FIX] redis-cli: Automatically enter --slave mode when SYNC or PSYNC are
|
||||
called during an interactive session.
|
||||
* [FIX] Sentinel "IDONTKNOW" error removed as it does not made sense with the
|
||||
new Sentinel design. This error was actually a fix for a design error
|
||||
in the first implementation of Sentinel.
|
||||
* [FIX] Sentinel: added a missing exit() call to abort after config file
|
||||
checks at startup. This error was introduced with an improvement in
|
||||
a previous 2.8 release.
|
||||
* [FIX] BITCOUNT: fixed unaligned access causing issues in sparc and other
|
||||
archs not capable of dealing with unaligned accesses. This also makes
|
||||
the code faster in archs where unaligned accesses are allowed.
|
||||
* [FIX] Sentinel: better nodes fail over start time desynchronization to avoid
|
||||
split-brain during the voting process needed to get authorization to
|
||||
fail over. This means the system is less likely to need to retry
|
||||
and will fail over faster. No changes in behavior / correctness.
|
||||
* [FIX] Force INFO used_memory_peak to match peak memory. This generated some
|
||||
confusion among users even if it was not an actual bug.
|
||||
|
||||
* [NEW] Sentinel unit tests and framework. More tests needed and units must
|
||||
be improved in order to have less false positives, but it is a start
|
||||
and features a debugging console that is useful to fix tests or to
|
||||
inspect bugs causing tests failures.
|
||||
* [NEW] New Sentinel events: +/-monitor and +set used to monitor when an
|
||||
instance to monitor is added or removed, or when a configuration
|
||||
is modified via SENTINEL SET.
|
||||
* [NEW] Redis-cli updated to use SCAN instead of random sampling via
|
||||
RANDOMKEY in order to implement --bigkeys feature. Moreover the
|
||||
implementation now supports pipelining and reports more information
|
||||
at the end of the scan. Much faster, much better. A special thank
|
||||
you to Michael Grunder for this improvement.
|
||||
* [NEW] redis-cli now supports a new --intrinsic-latency mode that is able
|
||||
to meter the latency of a system due to kernel / hypervisor.
|
||||
How to use it is explained at http://redis.io/topics/latency.
|
||||
* [NEW] New command BITPOS: find first bit set or clear in a bitmap.
|
||||
* [NEW] CONFIG REWRITE calls are now logged.
|
||||
|
||||
--[ Redis 2.8.6 ] Release date: 13 Feb 2014
|
||||
|
||||
# UPGRADE URGENCY: HIGH for Redis, LOW for Sentinel. Redis users using Lua
|
||||
scripts with EVALSHA and attached slaves and/or AOF
|
||||
persistence should consider upgrading ASAP.
|
||||
|
||||
* [FIX] Fixed an critical EVALSHA script cache bug: scripts executed may not
|
||||
propagate to AOF / Slaves correctly under certain conditions.
|
||||
See issue #1549 at Github for more information.
|
||||
* [FIX] Fixed multiple bugs resulting into closing the link with master or slave
|
||||
during replication without good reasons. This will result in useless
|
||||
resynchronizations, or infinite loops where the replication link can't
|
||||
be established.
|
||||
* [FIX] Don't count the time needed to populate the buffers of clients waiting
|
||||
in MONITOR mode when populating the Slow Log entries.
|
||||
|
||||
* [NEW] AOF write errors (like no space on device) no longer abort Redis if the
|
||||
fsync policy is none or every second. The database enters a read-only
|
||||
mode where every write is refused with an error. Normal operations are
|
||||
restored as soon as Redis is able to append again data to the AOF file.
|
||||
* [NEW] Sentinel now accepts SHUTDOWN command.
|
||||
|
||||
--[ Redis 2.8.5 ] Release date: 4 Feb 2014
|
||||
|
||||
# UPGRADE URGENCY: HIGH for Redis, LOW for Sentinel. Redis users using Lua
|
||||
scripts with expires, and Redis users relying on the
|
||||
ability of Redis to block writes on RDB saving errors
|
||||
should plan to upgrade ASAP.
|
||||
|
||||
* [FIX] Fixed a replication bug caused by Lua scripts + expired keys: keys could
|
||||
expire in the middle of scripts causing non-deterministic behavior.
|
||||
* [FIX] MISCONFIG error if condition fixed, the server was no longer able
|
||||
to stop writes on RDB misconfiguration after this error was introduced.
|
||||
* [FIX] REDIS_AOF_REWRITE_MIN_SIZE is now 64mb like example redis.conf default.
|
||||
* [FIX] Perform fflush() before fsync() in rio.c (bug without actual effects).
|
||||
* [FIX] Don't log MONITOR clients as disconnecting slaves.
|
||||
* [FIX] SENTINEL MASTER arity check fixed. Crashed the Sentinel instance when
|
||||
the command was given without arguments.
|
||||
|
||||
* [NEW] Allow CONFIG and SHUTDOWN while in stale-slave state.
|
||||
* [NEW] Support for configurable TCP listen(2) backlog size.
|
||||
* [NEW] redis-cli supports SCAN via the --scan and --pattern options.
|
||||
* [NEW] SENTINEL SET master quorum via runtime API implemented.
|
||||
|
||||
--[ Redis 2.8.4 ] Release date: 13 Jan 2014
|
||||
|
||||
# UPGRADE URGENCY: MODERATE for Redis and Sentinel.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2006-2012, Salvatore Sanfilippo
|
||||
Copyright (c) 2006-2014, Salvatore Sanfilippo
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -44,6 +44,15 @@ pidfile /var/run/redis.pid
|
||||
# If port 0 is specified Redis will not listen on a TCP socket.
|
||||
port 6379
|
||||
|
||||
# TCP listen() backlog.
|
||||
#
|
||||
# In high requests-per-second environments you need an high backlog in order
|
||||
# to avoid slow clients connections issues. Note that the Linux kernel
|
||||
# will silently truncate it to the value of /proc/sys/net/core/somaxconn so
|
||||
# make sure to raise both the value of somaxconn and tcp_max_syn_backlog
|
||||
# in order to get the desired effect.
|
||||
tcp-backlog 511
|
||||
|
||||
# By default Redis listens for connections from all the network interfaces
|
||||
# available on the server. It is possible to listen to just one or multiple
|
||||
# interfaces using the "bind" configuration directive, followed by one or
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||
done
|
||||
|
||||
if [ -z $TCLSH ]
|
||||
then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis Sentinel test"
|
||||
exit 1
|
||||
fi
|
||||
$TCLSH tests/sentinel.tcl $*
|
||||
@@ -204,6 +204,9 @@ distclean: clean
|
||||
test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
@(cd ..; ./runtest)
|
||||
|
||||
test-sentinel: $(REDIS_SENTINEL_NAME)
|
||||
@(cd ..; ./runtest-sentinel)
|
||||
|
||||
check: test
|
||||
|
||||
lcov:
|
||||
|
||||
+10
-13
@@ -361,17 +361,14 @@ int anetWrite(int fd, char *buf, int count)
|
||||
return totlen;
|
||||
}
|
||||
|
||||
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) {
|
||||
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog) {
|
||||
if (bind(s,sa,len) == -1) {
|
||||
anetSetError(err, "bind: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
/* Use a backlog of 512 entries. We pass 511 to the listen() call because
|
||||
* the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1);
|
||||
* which will thus give us a backlog of 512 entries */
|
||||
if (listen(s, 511) == -1) {
|
||||
if (listen(s, backlog) == -1) {
|
||||
anetSetError(err, "listen: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
@@ -389,7 +386,7 @@ static int anetV6Only(char *err, int s) {
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af)
|
||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog)
|
||||
{
|
||||
int s, rv;
|
||||
char _port[6]; /* strlen("65535") */
|
||||
@@ -411,7 +408,7 @@ static int _anetTcpServer(char *err, int port, char *bindaddr, int af)
|
||||
|
||||
if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen) == ANET_ERR) goto error;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) goto error;
|
||||
goto end;
|
||||
}
|
||||
if (p == NULL) {
|
||||
@@ -426,17 +423,17 @@ end:
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetTcpServer(char *err, int port, char *bindaddr)
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog)
|
||||
{
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET);
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog);
|
||||
}
|
||||
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr)
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog)
|
||||
{
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET6);
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog);
|
||||
}
|
||||
|
||||
int anetUnixServer(char *err, char *path, mode_t perm)
|
||||
int anetUnixServer(char *err, char *path, mode_t perm, int backlog)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_un sa;
|
||||
@@ -447,7 +444,7 @@ int anetUnixServer(char *err, char *path, mode_t perm)
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa)) == ANET_ERR)
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa),backlog) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
if (perm)
|
||||
chmod(sa.sun_path, perm);
|
||||
|
||||
+3
-3
@@ -50,9 +50,9 @@ int anetUnixNonBlockConnect(char *err, char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr);
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr);
|
||||
int anetUnixServer(char *err, char *path, mode_t perm);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog);
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog);
|
||||
int anetUnixServer(char *err, char *path, mode_t perm, int backlog);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
|
||||
int anetUnixAccept(char *err, int serversock);
|
||||
int anetWrite(int fd, char *buf, int count);
|
||||
|
||||
@@ -126,7 +126,7 @@ void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
|
||||
}
|
||||
|
||||
/* Write the buffer (possibly composed of multiple blocks) into the specified
|
||||
* fd. If no short write or any other error happens -1 is returned,
|
||||
* fd. If a short write or any other error happens -1 is returned,
|
||||
* otherwise the number of bytes written is returned. */
|
||||
ssize_t aofRewriteBufferWrite(int fd) {
|
||||
listNode *ln;
|
||||
@@ -226,6 +226,7 @@ int startAppendOnly(void) {
|
||||
*
|
||||
* However if force is set to 1 we'll write regardless of the background
|
||||
* fsync. */
|
||||
#define AOF_WRITE_LOG_ERROR_RATE 30 /* Seconds between errors logging. */
|
||||
void flushAppendOnlyFile(int force) {
|
||||
ssize_t nwritten;
|
||||
int sync_in_progress = 0;
|
||||
@@ -267,27 +268,76 @@ void flushAppendOnlyFile(int force) {
|
||||
* or alike */
|
||||
nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
|
||||
if (nwritten != (signed)sdslen(server.aof_buf)) {
|
||||
/* Ooops, we are in troubles. The best thing to do for now is
|
||||
* aborting instead of giving the illusion that everything is
|
||||
* working as expected. */
|
||||
static time_t last_write_error_log = 0;
|
||||
int can_log = 0;
|
||||
|
||||
/* Limit logging rate to 1 line per AOF_WRITE_LOG_ERROR_RATE seconds. */
|
||||
if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) {
|
||||
can_log = 1;
|
||||
last_write_error_log = server.unixtime;
|
||||
}
|
||||
|
||||
/* Lof the AOF write error and record the error code. */
|
||||
if (nwritten == -1) {
|
||||
redisLog(REDIS_WARNING,"Exiting on error writing to the append-only file: %s",strerror(errno));
|
||||
if (can_log) {
|
||||
redisLog(REDIS_WARNING,"Error writing to the AOF file: %s",
|
||||
strerror(errno));
|
||||
server.aof_last_write_errno = errno;
|
||||
}
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"Exiting on short write while writing to "
|
||||
"the append-only file: %s (nwritten=%ld, "
|
||||
"expected=%ld)",
|
||||
strerror(errno),
|
||||
(long)nwritten,
|
||||
(long)sdslen(server.aof_buf));
|
||||
if (can_log) {
|
||||
redisLog(REDIS_WARNING,"Short write while writing to "
|
||||
"the AOF file: (nwritten=%lld, "
|
||||
"expected=%lld)",
|
||||
(long long)nwritten,
|
||||
(long long)sdslen(server.aof_buf));
|
||||
}
|
||||
|
||||
if (ftruncate(server.aof_fd, server.aof_current_size) == -1) {
|
||||
redisLog(REDIS_WARNING, "Could not remove short write "
|
||||
"from the append-only file. Redis may refuse "
|
||||
"to load the AOF the next time it starts. "
|
||||
"ftruncate: %s", strerror(errno));
|
||||
if (can_log) {
|
||||
redisLog(REDIS_WARNING, "Could not remove short write "
|
||||
"from the append-only file. Redis may refuse "
|
||||
"to load the AOF the next time it starts. "
|
||||
"ftruncate: %s", strerror(errno));
|
||||
}
|
||||
} else {
|
||||
/* If the ftrunacate() succeeded we can set nwritten to
|
||||
* -1 since there is no longer partial data into the AOF. */
|
||||
nwritten = -1;
|
||||
}
|
||||
server.aof_last_write_errno = ENOSPC;
|
||||
}
|
||||
|
||||
/* Handle the AOF write error. */
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
/* We can't recover when the fsync policy is ALWAYS since the
|
||||
* reply for the client is already in the output buffers, and we
|
||||
* have the contract with the user that on acknowledged write data
|
||||
* is synched on disk. */
|
||||
redisLog(REDIS_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
|
||||
exit(1);
|
||||
} else {
|
||||
/* Recover from failed write leaving data into the buffer. However
|
||||
* set an error to stop accepting writes as long as the error
|
||||
* condition is not cleared. */
|
||||
server.aof_last_write_status = REDIS_ERR;
|
||||
|
||||
/* Trim the sds buffer if there was a partial write, and there
|
||||
* was no way to undo it with ftruncate(2). */
|
||||
if (nwritten > 0) {
|
||||
server.aof_current_size += nwritten;
|
||||
sdsrange(server.aof_buf,nwritten,-1);
|
||||
}
|
||||
return; /* We'll try again on the next call... */
|
||||
}
|
||||
} else {
|
||||
/* Successful write(2). If AOF was in error state, restore the
|
||||
* OK state and log the event. */
|
||||
if (server.aof_last_write_status == REDIS_ERR) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"AOF write error looks solved, Redis can write again.");
|
||||
server.aof_last_write_status = REDIS_OK;
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
server.aof_current_size += nwritten;
|
||||
|
||||
|
||||
+184
-3
@@ -60,11 +60,18 @@ static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
|
||||
* work with a input string length up to 512 MB. */
|
||||
size_t redisPopcount(void *s, long count) {
|
||||
size_t bits = 0;
|
||||
unsigned char *p;
|
||||
uint32_t *p4 = s;
|
||||
unsigned char *p = s;
|
||||
uint32_t *p4;
|
||||
static const unsigned char bitsinbyte[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
|
||||
|
||||
/* Count initial bytes not aligned to 32 bit. */
|
||||
while((unsigned long)p & 3 && count) {
|
||||
bits += bitsinbyte[*p++];
|
||||
count--;
|
||||
}
|
||||
|
||||
/* Count bits 16 bytes at a time */
|
||||
p4 = (uint32_t*)p;
|
||||
while(count>=16) {
|
||||
uint32_t aux1, aux2, aux3, aux4;
|
||||
|
||||
@@ -87,12 +94,99 @@ size_t redisPopcount(void *s, long count) {
|
||||
((((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +
|
||||
((((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24);
|
||||
}
|
||||
/* Count the remaining bytes */
|
||||
/* Count the remaining bytes. */
|
||||
p = (unsigned char*)p4;
|
||||
while(count--) bits += bitsinbyte[*p++];
|
||||
return bits;
|
||||
}
|
||||
|
||||
/* Return the position of the first bit set to one (if 'bit' is 1) or
|
||||
* zero (if 'bit' is 0) in the bitmap starting at 's' and long 'count' bytes.
|
||||
*
|
||||
* The function is guaranteed to return a value >= 0 if 'bit' is 0 since if
|
||||
* no zero bit is found, it returns count*8 assuming the string is zero
|
||||
* padded on the right. However if 'bit' is 1 it is possible that there is
|
||||
* not a single set bit in the bitmap. In this special case -1 is returned. */
|
||||
long redisBitpos(void *s, long count, int bit) {
|
||||
unsigned long *l;
|
||||
unsigned char *c;
|
||||
unsigned long skipval, word = 0, one;
|
||||
long pos = 0; /* Position of bit, to return to the caller. */
|
||||
int j;
|
||||
|
||||
/* Process whole words first, seeking for first word that is not
|
||||
* all ones or all zeros respectively if we are lookig for zeros
|
||||
* or ones. This is much faster with large strings having contiguous
|
||||
* blocks of 1 or 0 bits compared to the vanilla bit per bit processing.
|
||||
*
|
||||
* Note that if we start from an address that is not aligned
|
||||
* to sizeof(unsigned long) we consume it byte by byte until it is
|
||||
* aligned. */
|
||||
|
||||
/* Skip initial bits not aligned to sizeof(unsigned long) byte by byte. */
|
||||
skipval = bit ? 0 : UCHAR_MAX;
|
||||
c = (unsigned char*) s;
|
||||
while((unsigned long)c & (sizeof(*l)-1) && count) {
|
||||
if (*c != skipval) break;
|
||||
c++;
|
||||
count--;
|
||||
pos += 8;
|
||||
}
|
||||
|
||||
/* Skip bits with full word step. */
|
||||
skipval = bit ? 0 : ULONG_MAX;
|
||||
l = (unsigned long*) c;
|
||||
while (count >= sizeof(*l)) {
|
||||
if (*l != skipval) break;
|
||||
l++;
|
||||
count -= sizeof(*l);
|
||||
pos += sizeof(*l)*8;
|
||||
}
|
||||
|
||||
/* Load bytes into "word" considering the first byte as the most significant
|
||||
* (we basically consider it as written in big endian, since we consider the
|
||||
* string as a set of bits from left to right, with the first bit at position
|
||||
* zero.
|
||||
*
|
||||
* Note that the loading is designed to work even when the bytes left
|
||||
* (count) are less than a full word. We pad it with zero on the right. */
|
||||
c = (unsigned char*)l;
|
||||
for (j = 0; j < sizeof(*l); j++) {
|
||||
word <<= 8;
|
||||
if (count) {
|
||||
word |= *c;
|
||||
c++;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Special case:
|
||||
* If bits in the string are all zero and we are looking for one,
|
||||
* return -1 to signal that there is not a single "1" in the whole
|
||||
* string. This can't happen when we are looking for "0" as we assume
|
||||
* that the right of the string is zero padded. */
|
||||
if (bit == 1 && word == 0) return -1;
|
||||
|
||||
/* Last word left, scan bit by bit. The first thing we need is to
|
||||
* have a single "1" set in the most significant position in an
|
||||
* unsigned long. We don't know the size of the long so we use a
|
||||
* simple trick. */
|
||||
one = ULONG_MAX; /* All bits set to 1.*/
|
||||
one >>= 1; /* All bits set to 1 but the MSB. */
|
||||
one = ~one; /* All bits set to 0 but the MSB. */
|
||||
|
||||
while(one) {
|
||||
if (((one & word) != 0) == bit) return pos;
|
||||
pos++;
|
||||
one >>= 1;
|
||||
}
|
||||
|
||||
/* If we reached this point, there is a bug in the algorithm, since
|
||||
* the case of no match is handled as a special case before. */
|
||||
redisPanic("End of redisBitpos() reached.");
|
||||
return 0; /* Just to avoid warnings. */
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Bits related string commands: GETBIT, SETBIT, BITCOUNT, BITOP.
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -410,3 +504,90 @@ void bitcountCommand(redisClient *c) {
|
||||
addReplyLongLong(c,redisPopcount(p+start,bytes));
|
||||
}
|
||||
}
|
||||
|
||||
/* BITPOS key bit [start [end]] */
|
||||
void bitposCommand(redisClient *c) {
|
||||
robj *o;
|
||||
long bit, start, end, strlen;
|
||||
unsigned char *p;
|
||||
char llbuf[32];
|
||||
int end_given = 0;
|
||||
|
||||
/* Parse the bit argument to understand what we are looking for, set
|
||||
* or clear bits. */
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&bit,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (bit != 0 && bit != 1) {
|
||||
addReplyError(c, "The bit argument must be 1 or 0.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the key does not exist, from our point of view it is an infinite
|
||||
* array of 0 bits. If the user is looking for the fist clear bit return 0,
|
||||
* If the user is looking for the first set bit, return -1. */
|
||||
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
|
||||
addReplyLongLong(c, bit ? -1 : 0);
|
||||
return;
|
||||
}
|
||||
if (checkType(c,o,REDIS_STRING)) return;
|
||||
|
||||
/* Set the 'p' pointer to the string, that can be just a stack allocated
|
||||
* array if our string was integer encoded. */
|
||||
if (o->encoding == REDIS_ENCODING_INT) {
|
||||
p = (unsigned char*) llbuf;
|
||||
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
|
||||
} else {
|
||||
p = (unsigned char*) o->ptr;
|
||||
strlen = sdslen(o->ptr);
|
||||
}
|
||||
|
||||
/* Parse start/end range if any. */
|
||||
if (c->argc == 4 || c->argc == 5) {
|
||||
if (getLongFromObjectOrReply(c,c->argv[3],&start,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (c->argc == 5) {
|
||||
if (getLongFromObjectOrReply(c,c->argv[4],&end,NULL) != REDIS_OK)
|
||||
return;
|
||||
end_given = 1;
|
||||
} else {
|
||||
end = strlen-1;
|
||||
}
|
||||
/* Convert negative indexes */
|
||||
if (start < 0) start = strlen+start;
|
||||
if (end < 0) end = strlen+end;
|
||||
if (start < 0) start = 0;
|
||||
if (end < 0) end = 0;
|
||||
if (end >= strlen) end = strlen-1;
|
||||
} else if (c->argc == 3) {
|
||||
/* The whole string. */
|
||||
start = 0;
|
||||
end = strlen-1;
|
||||
} else {
|
||||
/* Syntax error. */
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* For empty ranges (start > end) we return -1 as an empty range does
|
||||
* not contain a 0 nor a 1. */
|
||||
if (start > end) {
|
||||
addReplyLongLong(c, -1);
|
||||
} else {
|
||||
long bytes = end-start+1;
|
||||
long pos = redisBitpos(p+start,bytes,bit);
|
||||
|
||||
/* If we are looking for clear bits, and the user specified an exact
|
||||
* range with start-end, we can't consider the right of the range as
|
||||
* zero padded (as we do when no explicit end is given).
|
||||
*
|
||||
* So if redisBitpos() returns the first bit outside the range,
|
||||
* we return -1 to the caller, to mean, in the specified range there
|
||||
* is not a single "0" bit. */
|
||||
if (end_given && bit == 0 && pos == bytes*8) {
|
||||
addReplyLongLong(c,-1);
|
||||
return;
|
||||
}
|
||||
if (pos != -1) pos += start*8; /* Adjust for the bytes we skipped. */
|
||||
addReplyLongLong(c,pos);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -125,6 +125,11 @@ void loadServerConfigFromString(char *config) {
|
||||
if (server.port < 0 || server.port > 65535) {
|
||||
err = "Invalid port"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"tcp-backlog") && argc == 2) {
|
||||
server.tcp_backlog = atoi(argv[1]);
|
||||
if (server.tcp_backlog < 0) {
|
||||
err = "Invalid backlog value"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"bind") && argc >= 2) {
|
||||
int j, addresses = argc-1;
|
||||
|
||||
@@ -935,6 +940,7 @@ void configGetCommand(redisClient *c) {
|
||||
config_get_numerical_field("slowlog-max-len",
|
||||
server.slowlog_max_len);
|
||||
config_get_numerical_field("port",server.port);
|
||||
config_get_numerical_field("tcp-backlog",server.tcp_backlog);
|
||||
config_get_numerical_field("databases",server.dbnum);
|
||||
config_get_numerical_field("repl-ping-slave-period",server.repl_ping_slave_period);
|
||||
config_get_numerical_field("repl-timeout",server.repl_timeout);
|
||||
@@ -1413,7 +1419,7 @@ void rewriteConfigSaveOption(struct rewriteConfigState *state) {
|
||||
* resulting into no RDB persistence as expected. */
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
line = sdscatprintf(sdsempty(),"save %ld %d",
|
||||
server.saveparams[j].seconds, server.saveparams[j].changes);
|
||||
(long) server.saveparams[j].seconds, server.saveparams[j].changes);
|
||||
rewriteConfigRewriteLine(state,"save",line,1);
|
||||
}
|
||||
/* Mark "save" as processed in case server.saveparamslen is zero. */
|
||||
@@ -1652,6 +1658,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigYesNoOption(state,"daemonize",server.daemonize,0);
|
||||
rewriteConfigStringOption(state,"pidfile",server.pidfile,REDIS_DEFAULT_PID_FILE);
|
||||
rewriteConfigNumericalOption(state,"port",server.port,REDIS_SERVERPORT);
|
||||
rewriteConfigNumericalOption(state,"tcp-backlog",server.tcp_backlog,REDIS_TCP_BACKLOG);
|
||||
rewriteConfigBindOption(state);
|
||||
rewriteConfigStringOption(state,"unixsocket",server.unixsocket,NULL);
|
||||
rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,REDIS_DEFAULT_UNIX_SOCKET_PERM);
|
||||
@@ -1770,8 +1777,10 @@ void configCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
if (rewriteConfig(server.configfile) == -1) {
|
||||
redisLog(REDIS_WARNING,"CONFIG REWRITE failed: %s", strerror(errno));
|
||||
addReplyErrorFormat(c,"Rewriting config file: %s", strerror(errno));
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"CONFIG REWRITE executed with success.");
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -595,11 +595,13 @@ void shutdownCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* SHUTDOWN can be called even while the server is in "loading" state.
|
||||
* When this happens we need to make sure no attempt is performed to save
|
||||
/* When SHUTDOWN is called while the server is loading a dataset in
|
||||
* memory we need to make sure no attempt is performed to save
|
||||
* the dataset on shutdown (otherwise it could overwrite the current DB
|
||||
* with half-read data). */
|
||||
if (server.loading)
|
||||
* with half-read data).
|
||||
*
|
||||
* Also when in Sentinel mode clear the SAVE flag and force NOSAVE. */
|
||||
if (server.loading || server.sentinel_mode)
|
||||
flags = (flags & ~REDIS_SHUTDOWN_SAVE) | REDIS_SHUTDOWN_NOSAVE;
|
||||
if (prepareForShutdown(flags) == REDIS_OK) exit(0);
|
||||
addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
|
||||
@@ -755,13 +757,21 @@ void propagateExpire(redisDb *db, robj *key) {
|
||||
}
|
||||
|
||||
int expireIfNeeded(redisDb *db, robj *key) {
|
||||
long long when = getExpire(db,key);
|
||||
mstime_t when = getExpire(db,key);
|
||||
mstime_t now;
|
||||
|
||||
if (when < 0) return 0; /* No expire for this key */
|
||||
|
||||
/* Don't expire anything while loading. It will be done later. */
|
||||
if (server.loading) return 0;
|
||||
|
||||
/* If we are in the context of a Lua script, we claim that time is
|
||||
* blocked to when the Lua script started. This way a key can expire
|
||||
* only the first time it is accessed and not in the middle of the
|
||||
* script execution, making propagation to slaves / AOF consistent.
|
||||
* See issue #1525 on Github for more information. */
|
||||
now = server.lua_caller ? server.lua_time_start : mstime();
|
||||
|
||||
/* If we are running in the context of a slave, return ASAP:
|
||||
* the slave key expiration is controlled by the master that will
|
||||
* send us synthesized DEL operations for expired keys.
|
||||
@@ -769,12 +779,10 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
* Still we try to return the right information to the caller,
|
||||
* that is, 0 if we think the key should be still valid, 1 if
|
||||
* we think the key is expired at this time. */
|
||||
if (server.masterhost != NULL) {
|
||||
return mstime() > when;
|
||||
}
|
||||
if (server.masterhost != NULL) return now > when;
|
||||
|
||||
/* Return when this key has not expired */
|
||||
if (mstime() <= when) return 0;
|
||||
if (now <= when) return 0;
|
||||
|
||||
/* Delete the key */
|
||||
server.stat_expiredkeys++;
|
||||
|
||||
+1
-1
@@ -650,7 +650,7 @@ void freeClient(redisClient *c) {
|
||||
}
|
||||
|
||||
/* Log link disconnection with slave */
|
||||
if (c->flags & REDIS_SLAVE) {
|
||||
if ((c->flags & REDIS_SLAVE) && !(c->flags & REDIS_MONITOR)) {
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
|
||||
if (anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1) {
|
||||
|
||||
@@ -1066,6 +1066,10 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
||||
if (server.loading_process_events_interval_bytes &&
|
||||
(r->processed_bytes + len)/server.loading_process_events_interval_bytes > r->processed_bytes/server.loading_process_events_interval_bytes)
|
||||
{
|
||||
/* The DB can take some non trivial amount of time to load. Update
|
||||
* our cached time since it is used to create and update the last
|
||||
* interaction time with clients and for other important things. */
|
||||
updateCachedTime();
|
||||
if (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER)
|
||||
replicationSendNewlineToMaster();
|
||||
loadingProgress(r->processed_bytes);
|
||||
|
||||
+452
-91
@@ -58,7 +58,7 @@
|
||||
#define OUTPUT_RAW 1
|
||||
#define OUTPUT_CSV 2
|
||||
#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||
#define REDIS_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
|
||||
#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
|
||||
|
||||
static redisContext *context;
|
||||
static struct config {
|
||||
@@ -81,6 +81,10 @@ static struct config {
|
||||
int pipe_timeout;
|
||||
int getrdb_mode;
|
||||
int stat_mode;
|
||||
int scan_mode;
|
||||
int intrinsic_latency_mode;
|
||||
int intrinsic_latency_duration;
|
||||
char *pattern;
|
||||
char *rdb_filename;
|
||||
int bigkeys;
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
@@ -92,6 +96,7 @@ static struct config {
|
||||
} config;
|
||||
|
||||
static void usage();
|
||||
static void slaveMode(void);
|
||||
char *redisGitSHA1(void);
|
||||
char *redisGitDirty(void);
|
||||
|
||||
@@ -99,14 +104,18 @@ char *redisGitDirty(void);
|
||||
* Utility functions
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static long long mstime(void) {
|
||||
static long long ustime(void) {
|
||||
struct timeval tv;
|
||||
long long mst;
|
||||
long long ust;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
mst = ((long long)tv.tv_sec)*1000;
|
||||
mst += tv.tv_usec/1000;
|
||||
return mst;
|
||||
ust = ((long long)tv.tv_sec)*1000000;
|
||||
ust += tv.tv_usec;
|
||||
return ust;
|
||||
}
|
||||
|
||||
static long long mstime(void) {
|
||||
return ustime()/1000;
|
||||
}
|
||||
|
||||
static void cliRefreshPrompt(void) {
|
||||
@@ -597,6 +606,8 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
|
||||
if (!strcasecmp(command,"subscribe") ||
|
||||
!strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
|
||||
if (!strcasecmp(command,"sync") ||
|
||||
!strcasecmp(command,"psync")) config.slave_mode = 1;
|
||||
|
||||
/* Setup argument length */
|
||||
argvlen = malloc(argc*sizeof(size_t));
|
||||
@@ -618,6 +629,13 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.slave_mode) {
|
||||
printf("Entering slave output mode... (press Ctrl-C to quit)\n");
|
||||
slaveMode();
|
||||
config.slave_mode = 0;
|
||||
return REDIS_ERR; /* Error = slaveMode lost connection to master */
|
||||
}
|
||||
|
||||
if (cliReadReply(output_raw) != REDIS_OK) {
|
||||
free(argvlen);
|
||||
return REDIS_ERR;
|
||||
@@ -711,6 +729,13 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.slave_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--stat")) {
|
||||
config.stat_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--scan")) {
|
||||
config.scan_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--pattern") && !lastarg) {
|
||||
config.pattern = argv[++i];
|
||||
} else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
|
||||
config.intrinsic_latency_mode = 1;
|
||||
config.intrinsic_latency_duration = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--rdb") && !lastarg) {
|
||||
config.getrdb_mode = 1;
|
||||
config.rdb_filename = argv[++i];
|
||||
@@ -770,33 +795,37 @@ static void usage() {
|
||||
"redis-cli %s\n"
|
||||
"\n"
|
||||
"Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
|
||||
" -h <hostname> Server hostname (default: 127.0.0.1)\n"
|
||||
" -p <port> Server port (default: 6379)\n"
|
||||
" -s <socket> Server socket (overrides hostname and port)\n"
|
||||
" -a <password> Password to use when connecting to the server\n"
|
||||
" -r <repeat> Execute specified command N times\n"
|
||||
" -h <hostname> Server hostname (default: 127.0.0.1).\n"
|
||||
" -p <port> Server port (default: 6379).\n"
|
||||
" -s <socket> Server socket (overrides hostname and port).\n"
|
||||
" -a <password> Password to use when connecting to the server.\n"
|
||||
" -r <repeat> Execute specified command N times.\n"
|
||||
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
|
||||
" It is possible to specify sub-second times like -i 0.1\n"
|
||||
" -n <db> Database number\n"
|
||||
" -x Read last argument from STDIN\n"
|
||||
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n"
|
||||
" -c Enable cluster mode (follow -ASK and -MOVED redirections)\n"
|
||||
" It is possible to specify sub-second times like -i 0.1.\n"
|
||||
" -n <db> Database number.\n"
|
||||
" -x Read last argument from STDIN.\n"
|
||||
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n"
|
||||
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
|
||||
" --raw Use raw formatting for replies (default when STDOUT is\n"
|
||||
" not a tty)\n"
|
||||
" --csv Output in CSV format\n"
|
||||
" --latency Enter a special mode continuously sampling latency\n"
|
||||
" not a tty).\n"
|
||||
" --csv Output in CSV format.\n"
|
||||
" --latency Enter a special mode continuously sampling latency.\n"
|
||||
" --latency-history Like --latency but tracking latency changes over time.\n"
|
||||
" Default time interval is 15 sec. Change it using -i.\n"
|
||||
" --slave Simulate a slave showing commands received from the master\n"
|
||||
" --slave Simulate a slave showing commands received from the master.\n"
|
||||
" --rdb <filename> Transfer an RDB dump from remote server to local file.\n"
|
||||
" --pipe Transfer raw Redis protocol from stdin to server\n"
|
||||
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data\n"
|
||||
" --pipe Transfer raw Redis protocol from stdin to server.\n"
|
||||
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
|
||||
" no reply is received within <n> seconds.\n"
|
||||
" Default timeout: %d. Use 0 to wait forever.\n"
|
||||
" --bigkeys Sample Redis keys looking for big keys\n"
|
||||
" --eval <file> Send an EVAL command using the Lua script at <file>\n"
|
||||
" --help Output this help and exit\n"
|
||||
" --version Output version and exit\n"
|
||||
" --bigkeys Sample Redis keys looking for big keys.\n"
|
||||
" --scan List all keys using the SCAN command.\n"
|
||||
" --pattern <pat> Useful with --scan to specify a SCAN pattern.\n"
|
||||
" --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
|
||||
" The test will run for the specified amount of seconds.\n"
|
||||
" --eval <file> Send an EVAL command using the Lua script at <file>.\n"
|
||||
" --help Output this help and exit.\n"
|
||||
" --version Output version and exit.\n"
|
||||
"\n"
|
||||
"Examples:\n"
|
||||
" cat /etc/passwd | redis-cli -x set mypasswd\n"
|
||||
@@ -804,12 +833,14 @@ static void usage() {
|
||||
" redis-cli -r 100 lpush mylist x\n"
|
||||
" redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
|
||||
" redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n"
|
||||
" redis-cli --scan --pattern '*:12345*'\n"
|
||||
"\n"
|
||||
" (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n"
|
||||
"\n"
|
||||
"When no command is given, redis-cli starts in interactive mode.\n"
|
||||
"Type \"help\" in interactive mode for information on available commands.\n"
|
||||
"\n",
|
||||
version, REDIS_DEFAULT_PIPE_TIMEOUT);
|
||||
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
|
||||
sdsfree(version);
|
||||
exit(1);
|
||||
}
|
||||
@@ -929,6 +960,10 @@ static int noninteractive(int argc, char **argv) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Eval mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static int evalMode(int argc, char **argv) {
|
||||
sds script = sdsempty();
|
||||
FILE *fp;
|
||||
@@ -967,6 +1002,10 @@ static int evalMode(int argc, char **argv) {
|
||||
return cliSendCommand(argc+3-got_comma, argv2, config.repeat);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Latency and latency history modes
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
#define LATENCY_SAMPLE_RATE 10 /* milliseconds. */
|
||||
#define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */
|
||||
static void latencyMode(void) {
|
||||
@@ -1011,6 +1050,10 @@ static void latencyMode(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Slave mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* Sends SYNC and reads the number of bytes in the payload. Used both by
|
||||
* slaveMode() and getRDB(). */
|
||||
unsigned long long sendSync(int fd) {
|
||||
@@ -1050,6 +1093,7 @@ static void slaveMode(void) {
|
||||
int fd = context->fd;
|
||||
unsigned long long payload = sendSync(fd);
|
||||
char buf[1024];
|
||||
int original_output = config.output;
|
||||
|
||||
fprintf(stderr,"SYNC with master, discarding %llu "
|
||||
"bytes of bulk transfer...\n", payload);
|
||||
@@ -1070,8 +1114,13 @@ static void slaveMode(void) {
|
||||
/* Now we can use hiredis to read the incoming protocol. */
|
||||
config.output = OUTPUT_CSV;
|
||||
while (cliReadReply(0) == REDIS_OK);
|
||||
config.output = original_output;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* RDB transfer mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* This function implements --rdb, so it uses the replication protocol in order
|
||||
* to fetch the RDB file from a remote server. */
|
||||
static void getRDB(void) {
|
||||
@@ -1117,6 +1166,10 @@ static void getRDB(void) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Bulk import (pipe) mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static void pipeMode(void) {
|
||||
int fd = context->fd;
|
||||
long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
|
||||
@@ -1268,92 +1321,287 @@ static void pipeMode(void) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Find big keys
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
#define TYPE_STRING 0
|
||||
#define TYPE_LIST 1
|
||||
#define TYPE_SET 2
|
||||
#define TYPE_HASH 3
|
||||
#define TYPE_ZSET 4
|
||||
#define TYPE_NONE 5
|
||||
|
||||
static redisReply *sendScan(unsigned long long *it) {
|
||||
redisReply *reply = redisCommand(context, "SCAN %llu", *it);
|
||||
|
||||
/* Handle any error conditions */
|
||||
if(reply == NULL) {
|
||||
fprintf(stderr, "\nI/O error\n");
|
||||
exit(1);
|
||||
} else if(reply->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "SCAN error: %s\n", reply->str);
|
||||
exit(1);
|
||||
} else if(reply->type != REDIS_REPLY_ARRAY) {
|
||||
fprintf(stderr, "Non ARRAY response from SCAN!\n");
|
||||
exit(1);
|
||||
} else if(reply->elements != 2) {
|
||||
fprintf(stderr, "Invalid element count from SCAN!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Validate our types are correct */
|
||||
assert(reply->element[0]->type == REDIS_REPLY_STRING);
|
||||
assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
|
||||
|
||||
/* Update iterator */
|
||||
*it = atoi(reply->element[0]->str);
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
static int getDbSize(void) {
|
||||
redisReply *reply;
|
||||
int size;
|
||||
|
||||
reply = redisCommand(context, "DBSIZE");
|
||||
|
||||
if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
|
||||
fprintf(stderr, "Couldn't determine DBSIZE!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Grab the number of keys and free our reply */
|
||||
size = reply->integer;
|
||||
freeReplyObject(reply);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static int toIntType(char *key, char *type) {
|
||||
if(!strcmp(type, "string")) {
|
||||
return TYPE_STRING;
|
||||
} else if(!strcmp(type, "list")) {
|
||||
return TYPE_LIST;
|
||||
} else if(!strcmp(type, "set")) {
|
||||
return TYPE_SET;
|
||||
} else if(!strcmp(type, "hash")) {
|
||||
return TYPE_HASH;
|
||||
} else if(!strcmp(type, "zset")) {
|
||||
return TYPE_ZSET;
|
||||
} else if(!strcmp(type, "none")) {
|
||||
return TYPE_NONE;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void getKeyTypes(redisReply *keys, int *types) {
|
||||
redisReply *reply;
|
||||
int i;
|
||||
|
||||
/* Pipeline TYPE commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
redisAppendCommand(context, "TYPE %s", keys->element[i]->str);
|
||||
}
|
||||
|
||||
/* Retrieve types */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
|
||||
fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n",
|
||||
keys->element[i]->str, context->err, context->errstr);
|
||||
exit(1);
|
||||
} else if(reply->type != REDIS_REPLY_STATUS) {
|
||||
fprintf(stderr, "Invalid reply type (%d) for TYPE on key '%s'!\n",
|
||||
reply->type, keys->element[i]->str);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
types[i] = toIntType(keys->element[i]->str, reply->str);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void getKeySizes(redisReply *keys, int *types,
|
||||
unsigned long long *sizes)
|
||||
{
|
||||
redisReply *reply;
|
||||
char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"};
|
||||
int i;
|
||||
|
||||
/* Pipeline size commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
/* Skip keys that were deleted */
|
||||
if(types[i]==TYPE_NONE)
|
||||
continue;
|
||||
|
||||
redisAppendCommand(context, "%s %s", sizecmds[types[i]],
|
||||
keys->element[i]->str);
|
||||
}
|
||||
|
||||
/* Retreive sizes */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
/* Skip keys that dissapeared between SCAN and TYPE */
|
||||
if(types[i] == TYPE_NONE) {
|
||||
sizes[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Retreive size */
|
||||
if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
|
||||
fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n",
|
||||
keys->element[i]->str, context->err, context->errstr);
|
||||
exit(1);
|
||||
} else if(reply->type != REDIS_REPLY_INTEGER) {
|
||||
/* Theoretically the key could have been removed and
|
||||
* added as a different type between TYPE and SIZE */
|
||||
fprintf(stderr,
|
||||
"Warning: %s on '%s' failed (may have changed type)\n",
|
||||
sizecmds[types[i]], keys->element[i]->str);
|
||||
sizes[i] = 0;
|
||||
} else {
|
||||
sizes[i] = reply->integer;
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void findBigKeys(void) {
|
||||
unsigned long long biggest[5] = {0,0,0,0,0};
|
||||
unsigned long long samples = 0;
|
||||
redisReply *reply1, *reply2, *reply3 = NULL;
|
||||
char *sizecmd, *typename[] = {"string","list","set","hash","zset"};
|
||||
unsigned long long biggest[5] = {0}, counts[5] = {0}, totalsize[5] = {0};
|
||||
unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0;
|
||||
sds maxkeys[5] = {0};
|
||||
char *typename[] = {"string","list","set","hash","zset"};
|
||||
char *typeunit[] = {"bytes","items","members","fields","members"};
|
||||
int type;
|
||||
redisReply *reply, *keys;
|
||||
int type, *types=NULL, arrsize=0, i;
|
||||
double pct;
|
||||
|
||||
printf("\n# Press ctrl+c when you have had enough of it... :)\n");
|
||||
printf("# You can use -i 0.1 to sleep 0.1 sec every 100 sampled keys\n");
|
||||
printf("# in order to reduce server load (usually not needed).\n\n");
|
||||
while(1) {
|
||||
/* Sample with RANDOMKEY */
|
||||
reply1 = redisCommand(context,"RANDOMKEY");
|
||||
if (reply1 == NULL) {
|
||||
fprintf(stderr,"\nI/O error\n");
|
||||
exit(1);
|
||||
} else if (reply1->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "RANDOMKEY error: %s\n",
|
||||
reply1->str);
|
||||
exit(1);
|
||||
} else if (reply1->type == REDIS_REPLY_NIL) {
|
||||
fprintf(stderr, "It looks like the database is empty!\n");
|
||||
/* Total keys pre scanning */
|
||||
total_keys = getDbSize();
|
||||
|
||||
/* Status message */
|
||||
printf("\n# Scanning the entire keyspace to find biggest keys as well as\n");
|
||||
printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n");
|
||||
printf("# per 100 SCAN commands (not usually needed).\n\n");
|
||||
|
||||
/* New up sds strings to keep track of overall biggest per type */
|
||||
for(i=0;i<TYPE_NONE; i++) {
|
||||
maxkeys[i] = sdsempty();
|
||||
if(!maxkeys[i]) {
|
||||
fprintf(stderr, "Failed to allocate memory for largest key names!");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the key type */
|
||||
reply2 = redisCommand(context,"TYPE %s",reply1->str);
|
||||
assert(reply2 && reply2->type == REDIS_REPLY_STATUS);
|
||||
samples++;
|
||||
/* SCAN loop */
|
||||
do {
|
||||
/* Calculate approximate percentage completion */
|
||||
pct = 100 * (double)sampled/total_keys;
|
||||
|
||||
/* Get the key "size" */
|
||||
if (!strcmp(reply2->str,"string")) {
|
||||
sizecmd = "STRLEN";
|
||||
type = TYPE_STRING;
|
||||
} else if (!strcmp(reply2->str,"list")) {
|
||||
sizecmd = "LLEN";
|
||||
type = TYPE_LIST;
|
||||
} else if (!strcmp(reply2->str,"set")) {
|
||||
sizecmd = "SCARD";
|
||||
type = TYPE_SET;
|
||||
} else if (!strcmp(reply2->str,"hash")) {
|
||||
sizecmd = "HLEN";
|
||||
type = TYPE_HASH;
|
||||
} else if (!strcmp(reply2->str,"zset")) {
|
||||
sizecmd = "ZCARD";
|
||||
type = TYPE_ZSET;
|
||||
} else if (!strcmp(reply2->str,"none")) {
|
||||
freeReplyObject(reply1);
|
||||
freeReplyObject(reply2);
|
||||
continue;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown key type '%s' for key '%s'\n",
|
||||
reply2->str, reply1->str);
|
||||
exit(1);
|
||||
/* Grab some keys and point to the keys array */
|
||||
reply = sendScan(&it);
|
||||
keys = reply->element[1];
|
||||
|
||||
/* Reallocate our type and size array if we need to */
|
||||
if(keys->elements > arrsize) {
|
||||
types = zrealloc(types, sizeof(int)*keys->elements);
|
||||
sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements);
|
||||
|
||||
if(!types || !sizes) {
|
||||
fprintf(stderr, "Failed to allocate storage for keys!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
arrsize = keys->elements;
|
||||
}
|
||||
|
||||
reply3 = redisCommand(context,"%s %s", sizecmd, reply1->str);
|
||||
if (reply3 && reply3->type == REDIS_REPLY_INTEGER) {
|
||||
if (biggest[type] < reply3->integer) {
|
||||
printf("Biggest %-6s found so far '%s' with %llu %s.\n",
|
||||
typename[type], reply1->str,
|
||||
(unsigned long long) reply3->integer,
|
||||
typeunit[type]);
|
||||
biggest[type] = reply3->integer;
|
||||
/* Retreive types and then sizes */
|
||||
getKeyTypes(keys, types);
|
||||
getKeySizes(keys, types, sizes);
|
||||
|
||||
/* Now update our stats */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
if((type = types[i]) == TYPE_NONE)
|
||||
continue;
|
||||
|
||||
totalsize[type] += sizes[i];
|
||||
counts[type]++;
|
||||
totlen += keys->element[i]->len;
|
||||
sampled++;
|
||||
|
||||
if(biggest[type]<sizes[i]) {
|
||||
printf(
|
||||
"[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
|
||||
pct, typename[type], keys->element[i]->str, sizes[i],
|
||||
typeunit[type]);
|
||||
|
||||
/* Keep track of biggest key name for this type */
|
||||
maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str);
|
||||
if(!maxkeys[type]) {
|
||||
fprintf(stderr, "Failed to allocate memory for key!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Keep track of the biggest size for this type */
|
||||
biggest[type] = sizes[i];
|
||||
}
|
||||
|
||||
/* Update overall progress */
|
||||
if(sampled % 1000000 == 0) {
|
||||
printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
|
||||
}
|
||||
}
|
||||
|
||||
if ((samples % 1000000) == 0)
|
||||
printf("(%llu keys sampled)\n", samples);
|
||||
|
||||
if ((samples % 100) == 0 && config.interval)
|
||||
/* Sleep if we've been directed to do so */
|
||||
if(sampled && (sampled %100) == 0 && config.interval) {
|
||||
usleep(config.interval);
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
} while(it != 0);
|
||||
|
||||
freeReplyObject(reply1);
|
||||
freeReplyObject(reply2);
|
||||
if (reply3) freeReplyObject(reply3);
|
||||
if(types) zfree(types);
|
||||
if(sizes) zfree(sizes);
|
||||
|
||||
/* We're done */
|
||||
printf("\n-------- summary -------\n\n");
|
||||
|
||||
printf("Sampled %llu keys in the keyspace!\n", sampled);
|
||||
printf("Total key length in bytes is %llu (avg len %.2f)\n\n",
|
||||
totlen, totlen ? (double)totlen/sampled : 0);
|
||||
|
||||
/* Output the biggest keys we found, for types we did find */
|
||||
for(i=0;i<TYPE_NONE;i++) {
|
||||
if(sdslen(maxkeys[i])>0) {
|
||||
printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i],
|
||||
biggest[i], typeunit[i]);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
for(i=0;i<TYPE_NONE;i++) {
|
||||
printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
|
||||
counts[i], typename[i], totalsize[i], typeunit[i],
|
||||
sampled ? 100 * (double)counts[i]/sampled : 0,
|
||||
counts[i] ? (double)totalsize[i]/counts[i] : 0);
|
||||
}
|
||||
|
||||
/* Free sds strings containing max keys */
|
||||
for(i=0;i<TYPE_NONE;i++) {
|
||||
sdsfree(maxkeys[i]);
|
||||
}
|
||||
|
||||
/* Success! */
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Stats mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* Return the specified INFO field from the INFO command output "info".
|
||||
* A new buffer is allocated for the result, that needs to be free'd.
|
||||
* If the field is not found NULL is returned. */
|
||||
@@ -1493,6 +1741,106 @@ static void statMode() {
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Scan mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static void scanMode() {
|
||||
redisReply *reply;
|
||||
unsigned long long cur = 0;
|
||||
|
||||
do {
|
||||
if (config.pattern)
|
||||
reply = redisCommand(context,"SCAN %llu MATCH %s",
|
||||
cur,config.pattern);
|
||||
else
|
||||
reply = redisCommand(context,"SCAN %llu",cur);
|
||||
if (reply == NULL) {
|
||||
printf("I/O error\n");
|
||||
exit(1);
|
||||
} else if (reply->type == REDIS_REPLY_ERROR) {
|
||||
printf("ERROR: %s\n", reply->str);
|
||||
exit(1);
|
||||
} else {
|
||||
int j;
|
||||
|
||||
cur = strtoull(reply->element[0]->str,NULL,10);
|
||||
for (j = 0; j < reply->element[1]->elements; j++)
|
||||
printf("%s\n", reply->element[1]->element[j]->str);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
} while(cur != 0);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Intrisic latency mode.
|
||||
*
|
||||
* Measure max latency of a running process that does not result from
|
||||
* syscalls. Basically this software should provide an hint about how much
|
||||
* time the kernel leaves the process without a chance to run.
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* This is just some computation the compiler can't optimize out.
|
||||
* Should run in less than 100-200 microseconds even using very
|
||||
* slow hardware. Runs in less than 10 microseconds in modern HW. */
|
||||
unsigned long compute_something_fast(void) {
|
||||
unsigned char s[256], i, j, t;
|
||||
int count = 1000, k;
|
||||
unsigned long output = 0;
|
||||
|
||||
for (k = 0; k < 256; k++) s[k] = k;
|
||||
|
||||
i = 0;
|
||||
j = 0;
|
||||
while(count--) {
|
||||
i++;
|
||||
j = j + s[i];
|
||||
t = s[i];
|
||||
s[i] = s[j];
|
||||
s[j] = t;
|
||||
output += s[(s[i]+s[j])&255];
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static void intrinsicLatencyMode(void) {
|
||||
long long test_end, run_time, max_latency = 0, runs = 0;
|
||||
|
||||
run_time = config.intrinsic_latency_duration*1000000;
|
||||
test_end = ustime() + run_time;
|
||||
|
||||
while(1) {
|
||||
long long start, end, latency;
|
||||
|
||||
start = ustime();
|
||||
compute_something_fast();
|
||||
end = ustime();
|
||||
latency = end-start;
|
||||
runs++;
|
||||
if (latency <= 0) continue;
|
||||
|
||||
/* Reporting */
|
||||
if (latency > max_latency) {
|
||||
max_latency = latency;
|
||||
printf("Max latency so far: %lld microseconds.\n", max_latency);
|
||||
}
|
||||
|
||||
if (end > test_end) {
|
||||
printf("\n%lld total runs (avg %lld microseconds per run).\n",
|
||||
runs, run_time/runs);
|
||||
printf("Worst run took %.02fx times the avarege.\n",
|
||||
(double) max_latency / (run_time/runs));
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Program main()
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int firstarg;
|
||||
|
||||
@@ -1511,9 +1859,13 @@ int main(int argc, char **argv) {
|
||||
config.cluster_mode = 0;
|
||||
config.slave_mode = 0;
|
||||
config.getrdb_mode = 0;
|
||||
config.stat_mode = 0;
|
||||
config.scan_mode = 0;
|
||||
config.intrinsic_latency_mode = 0;
|
||||
config.pattern = NULL;
|
||||
config.rdb_filename = NULL;
|
||||
config.pipe_mode = 0;
|
||||
config.pipe_timeout = REDIS_DEFAULT_PIPE_TIMEOUT;
|
||||
config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
|
||||
config.bigkeys = 0;
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
@@ -1566,6 +1918,15 @@ int main(int argc, char **argv) {
|
||||
statMode();
|
||||
}
|
||||
|
||||
/* Scan mode */
|
||||
if (config.scan_mode) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
scanMode();
|
||||
}
|
||||
|
||||
/* Intrinsic latency mode */
|
||||
if (config.intrinsic_latency_mode) intrinsicLatencyMode();
|
||||
|
||||
/* Start interactive mode when no command is provided */
|
||||
if (argc == 0 && !config.eval) {
|
||||
/* Note that in repl mode we don't abort on connection error.
|
||||
|
||||
+72
-31
@@ -218,7 +218,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"save",saveCommand,1,"ars",0,NULL,0,0,0,0,0},
|
||||
{"bgsave",bgsaveCommand,1,"ar",0,NULL,0,0,0,0,0},
|
||||
{"bgrewriteaof",bgrewriteaofCommand,1,"ar",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"arl",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"arlt",0,NULL,0,0,0,0,0},
|
||||
{"lastsave",lastsaveCommand,1,"rR",0,NULL,0,0,0,0,0},
|
||||
{"type",typeCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"multi",multiCommand,1,"rs",0,NULL,0,0,0,0,0},
|
||||
@@ -237,7 +237,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"persist",persistCommand,2,"w",0,NULL,1,1,1,0,0},
|
||||
{"slaveof",slaveofCommand,3,"ast",0,NULL,0,0,0,0,0},
|
||||
{"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"config",configCommand,-2,"ar",0,NULL,0,0,0,0,0},
|
||||
{"config",configCommand,-2,"art",0,NULL,0,0,0,0,0},
|
||||
{"subscribe",subscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0},
|
||||
{"unsubscribe",unsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0},
|
||||
{"psubscribe",psubscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0},
|
||||
@@ -257,7 +257,8 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0},
|
||||
{"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0},
|
||||
{"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
|
||||
{"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0}
|
||||
{"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0},
|
||||
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0}
|
||||
};
|
||||
|
||||
/*============================ Utility functions ============================ */
|
||||
@@ -954,6 +955,15 @@ void databasesCron(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* We take a cached value of the unix time in the global state because with
|
||||
* virtual memory and aging there is to store the current time in objects at
|
||||
* every object access, and accuracy is not needed. To access a global var is
|
||||
* a lot faster than calling time(NULL) */
|
||||
void updateCachedTime(void) {
|
||||
server.unixtime = time(NULL);
|
||||
server.mstime = mstime();
|
||||
}
|
||||
|
||||
/* This is our timer interrupt, called server.hz times per second.
|
||||
* Here is where we do a number of things that need to be done asynchronously.
|
||||
* For instance:
|
||||
@@ -983,12 +993,8 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
* handler if we don't return here fast enough. */
|
||||
if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period);
|
||||
|
||||
/* We take a cached value of the unix time in the global state because
|
||||
* with virtual memory and aging there is to store the current time
|
||||
* in objects at every object access, and accuracy is not needed.
|
||||
* To access a global var is faster than calling time(NULL) */
|
||||
server.unixtime = time(NULL);
|
||||
server.mstime = mstime();
|
||||
/* Update the time cache. */
|
||||
updateCachedTime();
|
||||
|
||||
run_with_period(100) trackOperationsPerSecond();
|
||||
|
||||
@@ -1120,10 +1126,19 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
}
|
||||
|
||||
|
||||
/* If we postponed an AOF buffer flush, let's try to do it every time the
|
||||
* cron function is called. */
|
||||
/* AOF postponed flush: Try at every cron cycle if the slow fsync
|
||||
* completed. */
|
||||
if (server.aof_flush_postponed_start) flushAppendOnlyFile(0);
|
||||
|
||||
/* AOF write errors: in this case we have a buffer to flush as well and
|
||||
* clear the AOF error in case of success to make the DB writable again,
|
||||
* however to try every second is enough in case of 'hz' is set to
|
||||
* an higher frequency. */
|
||||
run_with_period(1000) {
|
||||
if (server.aof_last_write_status == REDIS_ERR)
|
||||
flushAppendOnlyFile(0);
|
||||
}
|
||||
|
||||
/* Close clients that need to be closed asynchronous */
|
||||
freeClientsInAsyncFreeQueue();
|
||||
|
||||
@@ -1266,6 +1281,7 @@ void initServerConfig() {
|
||||
server.runid[REDIS_RUN_ID_SIZE] = '\0';
|
||||
server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
|
||||
server.port = REDIS_SERVERPORT;
|
||||
server.tcp_backlog = REDIS_TCP_BACKLOG;
|
||||
server.bindaddr_count = 0;
|
||||
server.unixsocket = NULL;
|
||||
server.unixsocketperm = REDIS_DEFAULT_UNIX_SOCKET_PERM;
|
||||
@@ -1469,9 +1485,11 @@ int listenToPort(int port, int *fds, int *count) {
|
||||
if (server.bindaddr[j] == NULL) {
|
||||
/* Bind * for both IPv6 and IPv4, we enter here only if
|
||||
* server.bindaddr_count == 0. */
|
||||
fds[*count] = anetTcp6Server(server.neterr,port,NULL);
|
||||
fds[*count] = anetTcp6Server(server.neterr,port,NULL,
|
||||
server.tcp_backlog);
|
||||
if (fds[*count] != ANET_ERR) (*count)++;
|
||||
fds[*count] = anetTcpServer(server.neterr,port,NULL);
|
||||
fds[*count] = anetTcpServer(server.neterr,port,NULL,
|
||||
server.tcp_backlog);
|
||||
if (fds[*count] != ANET_ERR) (*count)++;
|
||||
/* Exit the loop if we were able to bind * on IPv4 or IPv6,
|
||||
* otherwise fds[*count] will be ANET_ERR and we'll print an
|
||||
@@ -1479,10 +1497,12 @@ int listenToPort(int port, int *fds, int *count) {
|
||||
if (*count) break;
|
||||
} else if (strchr(server.bindaddr[j],':')) {
|
||||
/* Bind IPv6 address. */
|
||||
fds[*count] = anetTcp6Server(server.neterr,port,server.bindaddr[j]);
|
||||
fds[*count] = anetTcp6Server(server.neterr,port,server.bindaddr[j],
|
||||
server.tcp_backlog);
|
||||
} else {
|
||||
/* Bind IPv4 address. */
|
||||
fds[*count] = anetTcpServer(server.neterr,port,server.bindaddr[j]);
|
||||
fds[*count] = anetTcpServer(server.neterr,port,server.bindaddr[j],
|
||||
server.tcp_backlog);
|
||||
}
|
||||
if (fds[*count] == ANET_ERR) {
|
||||
redisLog(REDIS_WARNING,
|
||||
@@ -1530,7 +1550,8 @@ void initServer() {
|
||||
/* Open the listening Unix domain socket. */
|
||||
if (server.unixsocket != NULL) {
|
||||
unlink(server.unixsocket); /* don't care if this fails */
|
||||
server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketperm);
|
||||
server.sofd = anetUnixServer(server.neterr,server.unixsocket,
|
||||
server.unixsocketperm, server.tcp_backlog);
|
||||
if (server.sofd == ANET_ERR) {
|
||||
redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);
|
||||
exit(1);
|
||||
@@ -1584,10 +1605,11 @@ void initServer() {
|
||||
server.ops_sec_idx = 0;
|
||||
server.ops_sec_last_sample_time = mstime();
|
||||
server.ops_sec_last_sample_ops = 0;
|
||||
server.unixtime = time(NULL);
|
||||
server.mstime = mstime();
|
||||
server.lastbgsave_status = REDIS_OK;
|
||||
server.aof_last_write_status = REDIS_OK;
|
||||
server.aof_last_write_errno = 0;
|
||||
server.repl_good_slaves_count = 0;
|
||||
updateCachedTime();
|
||||
|
||||
/* Create the serverCron() time event, that's our main way to process
|
||||
* background operations. */
|
||||
@@ -1786,7 +1808,7 @@ void forceCommandPropagation(redisClient *c, int flags) {
|
||||
|
||||
/* Call() is the core of Redis execution of a command */
|
||||
void call(redisClient *c, int flags) {
|
||||
long long dirty, start = ustime(), duration;
|
||||
long long dirty, start, duration;
|
||||
int client_old_flags = c->flags;
|
||||
|
||||
/* Sent the command to clients in MONITOR mode, only if the commands are
|
||||
@@ -1802,9 +1824,10 @@ void call(redisClient *c, int flags) {
|
||||
c->flags &= ~(REDIS_FORCE_AOF|REDIS_FORCE_REPL);
|
||||
redisOpArrayInit(&server.also_propagate);
|
||||
dirty = server.dirty;
|
||||
start = ustime();
|
||||
c->cmd->proc(c);
|
||||
dirty = server.dirty-dirty;
|
||||
duration = ustime()-start;
|
||||
dirty = server.dirty-dirty;
|
||||
|
||||
/* When EVAL is called loading the AOF we don't want commands called
|
||||
* from Lua to go into the slowlog or to populate statistics. */
|
||||
@@ -1921,15 +1944,22 @@ int processCommand(redisClient *c) {
|
||||
|
||||
/* Don't accept write commands if there are problems persisting on disk
|
||||
* and if this is a master instance. */
|
||||
if (server.stop_writes_on_bgsave_err &&
|
||||
server.saveparamslen > 0
|
||||
&& server.lastbgsave_status == REDIS_ERR &&
|
||||
server.masterhost != NULL &&
|
||||
if (((server.stop_writes_on_bgsave_err &&
|
||||
server.saveparamslen > 0 &&
|
||||
server.lastbgsave_status == REDIS_ERR) ||
|
||||
server.aof_last_write_status == REDIS_ERR) &&
|
||||
server.masterhost == NULL &&
|
||||
(c->cmd->flags & REDIS_CMD_WRITE ||
|
||||
c->cmd->proc == pingCommand))
|
||||
{
|
||||
flagTransaction(c);
|
||||
addReply(c, shared.bgsaveerr);
|
||||
if (server.aof_last_write_status == REDIS_OK)
|
||||
addReply(c, shared.bgsaveerr);
|
||||
else
|
||||
addReplySds(c,
|
||||
sdscatprintf(sdsempty(),
|
||||
"-MISCONF Errors writing to the AOF file: %s\r\n",
|
||||
strerror(server.aof_last_write_errno)));
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -2074,7 +2104,8 @@ int prepareForShutdown(int flags) {
|
||||
}
|
||||
/* Close the listening sockets. Apparently this allows faster restarts. */
|
||||
closeListeningSockets(1);
|
||||
redisLog(REDIS_WARNING,"Redis is now ready to exit, bye bye...");
|
||||
redisLog(REDIS_WARNING,"%s is now ready to exit, bye bye...",
|
||||
server.sentinel_mode ? "Sentinel" : "Redis");
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -2265,8 +2296,16 @@ sds genRedisInfoString(char *section) {
|
||||
if (allsections || defsections || !strcasecmp(section,"memory")) {
|
||||
char hmem[64];
|
||||
char peak_hmem[64];
|
||||
size_t zmalloc_used = zmalloc_used_memory();
|
||||
|
||||
bytesToHuman(hmem,zmalloc_used_memory());
|
||||
/* Peak memory is updated from time to time by serverCron() so it
|
||||
* may happen that the instantaneous value is slightly bigger than
|
||||
* the peak value. This may confuse users, so we update the peak
|
||||
* if found smaller than the current memory usage. */
|
||||
if (zmalloc_used > server.stat_peak_memory)
|
||||
server.stat_peak_memory = zmalloc_used;
|
||||
|
||||
bytesToHuman(hmem,zmalloc_used);
|
||||
bytesToHuman(peak_hmem,server.stat_peak_memory);
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
info = sdscatprintf(info,
|
||||
@@ -2279,7 +2318,7 @@ sds genRedisInfoString(char *section) {
|
||||
"used_memory_lua:%lld\r\n"
|
||||
"mem_fragmentation_ratio:%.2f\r\n"
|
||||
"mem_allocator:%s\r\n",
|
||||
zmalloc_used_memory(),
|
||||
zmalloc_used,
|
||||
hmem,
|
||||
zmalloc_get_rss(),
|
||||
server.stat_peak_memory,
|
||||
@@ -2307,7 +2346,8 @@ sds genRedisInfoString(char *section) {
|
||||
"aof_rewrite_scheduled:%d\r\n"
|
||||
"aof_last_rewrite_time_sec:%jd\r\n"
|
||||
"aof_current_rewrite_time_sec:%jd\r\n"
|
||||
"aof_last_bgrewrite_status:%s\r\n",
|
||||
"aof_last_bgrewrite_status:%s\r\n"
|
||||
"aof_last_write_status:%s\r\n",
|
||||
server.loading,
|
||||
server.dirty,
|
||||
server.rdb_child_pid != -1,
|
||||
@@ -2322,7 +2362,8 @@ sds genRedisInfoString(char *section) {
|
||||
(intmax_t)server.aof_rewrite_time_last,
|
||||
(intmax_t)((server.aof_child_pid == -1) ?
|
||||
-1 : time(NULL)-server.aof_rewrite_time_start),
|
||||
(server.aof_lastbgrewrite_status == REDIS_OK) ? "ok" : "err");
|
||||
(server.aof_lastbgrewrite_status == REDIS_OK) ? "ok" : "err",
|
||||
(server.aof_last_write_status == REDIS_OK) ? "ok" : "err");
|
||||
|
||||
if (server.aof_state != REDIS_AOF_OFF) {
|
||||
info = sdscatprintf(info,
|
||||
@@ -2998,10 +3039,10 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
j++;
|
||||
}
|
||||
if (configfile) server.configfile = getAbsolutePath(configfile);
|
||||
resetServerSaveParams();
|
||||
loadServerConfig(configfile,options);
|
||||
sdsfree(options);
|
||||
if (configfile) server.configfile = getAbsolutePath(configfile);
|
||||
} else {
|
||||
redisLog(REDIS_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis");
|
||||
}
|
||||
|
||||
+11
-3
@@ -71,6 +71,7 @@
|
||||
#define REDIS_MIN_HZ 1
|
||||
#define REDIS_MAX_HZ 500
|
||||
#define REDIS_SERVERPORT 6379 /* TCP port */
|
||||
#define REDIS_TCP_BACKLOG 511 /* TCP listen backlog */
|
||||
#define REDIS_MAXIDLETIME 0 /* default client timeout: infinite */
|
||||
#define REDIS_DEFAULT_DBNUM 16
|
||||
#define REDIS_CONFIGLINE_MAX 1024
|
||||
@@ -81,7 +82,7 @@
|
||||
#define REDIS_SHARED_BULKHDR_LEN 32
|
||||
#define REDIS_MAX_LOGMSG_LEN 1024 /* Default maximum length of syslog messages */
|
||||
#define REDIS_AOF_REWRITE_PERC 100
|
||||
#define REDIS_AOF_REWRITE_MIN_SIZE (1024*1024)
|
||||
#define REDIS_AOF_REWRITE_MIN_SIZE (64*1024*1024)
|
||||
#define REDIS_AOF_REWRITE_ITEMS_PER_CMD 64
|
||||
#define REDIS_SLOWLOG_LOG_SLOWER_THAN 10000
|
||||
#define REDIS_SLOWLOG_MAX_LEN 128
|
||||
@@ -367,6 +368,8 @@
|
||||
* Data types
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
typedef long long mstime_t; /* millisecond time type. */
|
||||
|
||||
/* A redis object, that is a type able to hold a string / list / set */
|
||||
|
||||
/* The actual Redis Object */
|
||||
@@ -583,6 +586,7 @@ struct redisServer {
|
||||
int sentinel_mode; /* True if this instance is a Sentinel. */
|
||||
/* Networking */
|
||||
int port; /* TCP listening port */
|
||||
int tcp_backlog; /* TCP listen() backlog */
|
||||
char *bindaddr[REDIS_BINDADDR_MAX]; /* Addresses we should bind to */
|
||||
int bindaddr_count; /* Number of addresses in server.bindaddr[] */
|
||||
char *unixsocket; /* UNIX socket path */
|
||||
@@ -659,6 +663,8 @@ struct redisServer {
|
||||
int aof_lastbgrewrite_status; /* REDIS_OK or REDIS_ERR */
|
||||
unsigned long aof_delayed_fsync; /* delayed AOF fsync() counter */
|
||||
int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
|
||||
int aof_last_write_status; /* REDIS_OK or REDIS_ERR */
|
||||
int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */
|
||||
/* RDB persistence */
|
||||
long long dirty; /* Changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
|
||||
@@ -760,8 +766,8 @@ struct redisServer {
|
||||
redisClient *lua_client; /* The "fake client" to query Redis from Lua */
|
||||
redisClient *lua_caller; /* The client running EVAL right now, or NULL */
|
||||
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
|
||||
long long lua_time_limit; /* Script timeout in seconds */
|
||||
long long lua_time_start; /* Start time of script */
|
||||
mstime_t lua_time_limit; /* Script timeout in milliseconds */
|
||||
mstime_t lua_time_start; /* Start time of script, milliseconds time */
|
||||
int lua_write_dirty; /* True if a write command was called during the
|
||||
execution of the current script. */
|
||||
int lua_random_dirty; /* True if a random command was called during the
|
||||
@@ -1096,6 +1102,7 @@ void populateCommandTable(void);
|
||||
void resetCommandTableStats(void);
|
||||
void adjustOpenFilesLimit(void);
|
||||
void closeListeningSockets(int unlink_unix_socket);
|
||||
void updateCachedTime();
|
||||
|
||||
/* Set data type */
|
||||
robj *setTypeCreate(robj *value);
|
||||
@@ -1339,6 +1346,7 @@ void scriptCommand(redisClient *c);
|
||||
void timeCommand(redisClient *c);
|
||||
void bitopCommand(redisClient *c);
|
||||
void bitcountCommand(redisClient *c);
|
||||
void bitposCommand(redisClient *c);
|
||||
void replconfCommand(redisClient *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
|
||||
+6
-3
@@ -602,9 +602,11 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
if ((nwritten = write(fd,buf,buflen)) == -1) {
|
||||
redisLog(REDIS_VERBOSE,"Write error sending DB to slave: %s",
|
||||
strerror(errno));
|
||||
freeClient(slave);
|
||||
if (errno != EAGAIN) {
|
||||
redisLog(REDIS_WARNING,"Write error sending DB to slave: %s",
|
||||
strerror(errno));
|
||||
freeClient(slave);
|
||||
}
|
||||
return;
|
||||
}
|
||||
slave->repldboff += nwritten;
|
||||
@@ -616,6 +618,7 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
slave->repl_ack_time = server.unixtime;
|
||||
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE,
|
||||
sendReplyToClient, slave) == AE_ERR) {
|
||||
redisLog(REDIS_WARNING,"Unable to register writable event for slave bulk transfer: %s", strerror(errno));
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
|
||||
if (r->io.file.autosync &&
|
||||
r->io.file.buffered >= r->io.file.autosync)
|
||||
{
|
||||
fflush(r->io.file.fp);
|
||||
aof_fsync(fileno(r->io.file.fp));
|
||||
r->io.file.buffered = 0;
|
||||
}
|
||||
|
||||
+3
-2
@@ -441,7 +441,7 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
REDIS_NOTUSED(ar);
|
||||
REDIS_NOTUSED(lua);
|
||||
|
||||
elapsed = (ustime()/1000) - server.lua_time_start;
|
||||
elapsed = mstime() - server.lua_time_start;
|
||||
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
|
||||
redisLog(REDIS_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
|
||||
server.lua_timedout = 1;
|
||||
@@ -900,7 +900,7 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
* We set the hook only if the time limit is enabled as the hook will
|
||||
* make the Lua script execution slower. */
|
||||
server.lua_caller = c;
|
||||
server.lua_time_start = ustime()/1000;
|
||||
server.lua_time_start = mstime();
|
||||
server.lua_kill = 0;
|
||||
if (server.lua_time_limit > 0 && server.masterhost == NULL) {
|
||||
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
|
||||
@@ -958,6 +958,7 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
rewriteClientCommandArgument(c,0,
|
||||
resetRefCount(createStringObject("EVAL",4)));
|
||||
rewriteClientCommandArgument(c,1,script);
|
||||
forceCommandPropagation(c,REDIS_PROPAGATE_REPL|REDIS_PROPAGATE_AOF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+65
-16
@@ -44,8 +44,6 @@ extern char **environ;
|
||||
|
||||
/* ======================== Sentinel global state =========================== */
|
||||
|
||||
typedef long long mstime_t; /* millisecond time type. */
|
||||
|
||||
/* Address object, used to describe an ip:port pair. */
|
||||
typedef struct sentinelAddr {
|
||||
char *ip;
|
||||
@@ -80,12 +78,13 @@ typedef struct sentinelAddr {
|
||||
#define SENTINEL_TILT_TRIGGER 2000
|
||||
#define SENTINEL_TILT_PERIOD (SENTINEL_PING_PERIOD*30)
|
||||
#define SENTINEL_DEFAULT_SLAVE_PRIORITY 100
|
||||
#define SENTINEL_SLAVE_RECONF_RETRY_PERIOD 10000
|
||||
#define SENTINEL_SLAVE_RECONF_TIMEOUT 10000
|
||||
#define SENTINEL_DEFAULT_PARALLEL_SYNCS 1
|
||||
#define SENTINEL_MIN_LINK_RECONNECT_PERIOD 15000
|
||||
#define SENTINEL_DEFAULT_FAILOVER_TIMEOUT (60*3*1000)
|
||||
#define SENTINEL_MAX_PENDING_COMMANDS 100
|
||||
#define SENTINEL_ELECTION_TIMEOUT 10000
|
||||
#define SENTINEL_MAX_DESYNC 1000
|
||||
|
||||
/* Failover machine different states. */
|
||||
#define SENTINEL_FAILOVER_STATE_NONE 0 /* No failover in progress. */
|
||||
@@ -329,6 +328,7 @@ void sentinelDiscardReplyCallback(redisAsyncContext *c, void *reply, void *privd
|
||||
int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port);
|
||||
char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char *req_runid, uint64_t *leader_epoch);
|
||||
void sentinelFlushConfig(void);
|
||||
void sentinelGenerateInitialMonitorEvents(void);
|
||||
|
||||
/* ========================= Dictionary types =============================== */
|
||||
|
||||
@@ -379,7 +379,8 @@ struct redisCommand sentinelcmds[] = {
|
||||
{"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0},
|
||||
{"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0}
|
||||
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
|
||||
};
|
||||
|
||||
/* This function overwrites a few normal Redis config default with Sentinel
|
||||
@@ -418,10 +419,20 @@ void initSentinel(void) {
|
||||
void sentinelIsRunning(void) {
|
||||
redisLog(REDIS_WARNING,"Sentinel runid is %s", server.runid);
|
||||
|
||||
if (server.configfile == NULL || access(server.configfile,W_OK) == -1) {
|
||||
redisLog(REDIS_WARNING,"Sentinel started without a config file, or config file not writable. Exiting...");
|
||||
if (server.configfile == NULL) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Sentinel started without a config file. Exiting...");
|
||||
exit(1);
|
||||
} else if (access(server.configfile,W_OK) == -1) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Sentinel config file %s is not writable: %s. Exiting...",
|
||||
server.configfile,strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* We want to generate a +monitor event for every configured master
|
||||
* at startup. */
|
||||
sentinelGenerateInitialMonitorEvents();
|
||||
}
|
||||
|
||||
/* ============================== sentinelAddr ============================== */
|
||||
@@ -553,6 +564,22 @@ void sentinelEvent(int level, char *type, sentinelRedisInstance *ri,
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called only at startup and is used to generate a
|
||||
* +monitor event for every configured master. The same events are also
|
||||
* generated when a master to monitor is added at runtime via the
|
||||
* SENTINEL MONITOR command. */
|
||||
void sentinelGenerateInitialMonitorEvents(void) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
di = dictGetIterator(sentinel.masters);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *ri = dictGetVal(de);
|
||||
sentinelEvent(REDIS_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* ============================ script execution ============================ */
|
||||
|
||||
/* Release a script job structure and all the associated data. */
|
||||
@@ -1766,6 +1793,14 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
ri->role_reported_time = mstime();
|
||||
ri->role_reported = role;
|
||||
if (role == SRI_SLAVE) ri->slave_conf_change_time = mstime();
|
||||
/* Log the event with +role-change if the new role is coherent or
|
||||
* with -role-change if there is a mismatch with the current config. */
|
||||
sentinelEvent(REDIS_VERBOSE,
|
||||
((ri->flags & (SRI_MASTER|SRI_SLAVE)) == role) ?
|
||||
"+role-change" : "-role-change",
|
||||
ri, "%@ new reported role is %s",
|
||||
role == SRI_MASTER ? "master" : "slave",
|
||||
ri->flags & SRI_MASTER ? "master" : "slave");
|
||||
}
|
||||
|
||||
/* Handle master -> slave role switch. */
|
||||
@@ -2386,6 +2421,7 @@ void sentinelCommand(redisClient *c) {
|
||||
/* SENTINEL MASTER <name> */
|
||||
sentinelRedisInstance *ri;
|
||||
|
||||
if (c->argc != 3) goto numargserr;
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
addReplySentinelRedisInstance(c,ri);
|
||||
@@ -2455,8 +2491,6 @@ void sentinelCommand(redisClient *c) {
|
||||
ri = sentinelGetMasterByName(c->argv[2]->ptr);
|
||||
if (ri == NULL) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
} else if (ri->info_refresh == 0) {
|
||||
addReplySds(c,sdsnew("-IDONTKNOW I have not enough information to reply. Please ask another Sentinel.\r\n"));
|
||||
} else {
|
||||
sentinelAddr *addr = sentinelGetCurrentMasterAddress(ri);
|
||||
|
||||
@@ -2491,6 +2525,7 @@ void sentinelCommand(redisClient *c) {
|
||||
sentinelPendingScriptsCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"monitor")) {
|
||||
/* SENTINEL MONITOR <name> <ip> <port> <quorum> */
|
||||
sentinelRedisInstance *ri;
|
||||
long quorum, port;
|
||||
char buf[32];
|
||||
|
||||
@@ -2506,9 +2541,11 @@ void sentinelCommand(redisClient *c) {
|
||||
addReplyError(c,"Invalid IP address specified");
|
||||
return;
|
||||
}
|
||||
if (createSentinelRedisInstance(c->argv[2]->ptr,SRI_MASTER,
|
||||
c->argv[3]->ptr,port,quorum,NULL) == NULL)
|
||||
{
|
||||
|
||||
/* Parameters are valid. Try to create the master instance. */
|
||||
ri = createSentinelRedisInstance(c->argv[2]->ptr,SRI_MASTER,
|
||||
c->argv[3]->ptr,port,quorum,NULL);
|
||||
if (ri == NULL) {
|
||||
switch(errno) {
|
||||
case EBUSY:
|
||||
addReplyError(c,"Duplicated master name");
|
||||
@@ -2522,6 +2559,7 @@ void sentinelCommand(redisClient *c) {
|
||||
}
|
||||
} else {
|
||||
sentinelFlushConfig();
|
||||
sentinelEvent(REDIS_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"remove")) {
|
||||
@@ -2530,6 +2568,7 @@ void sentinelCommand(redisClient *c) {
|
||||
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
sentinelEvent(REDIS_WARNING,"-monitor",ri,"%@");
|
||||
dictDelete(sentinel.masters,c->argv[2]->ptr);
|
||||
sentinelFlushConfig();
|
||||
addReply(c,shared.ok);
|
||||
@@ -2669,12 +2708,19 @@ void sentinelSetCommand(redisClient *c) {
|
||||
sdsfree(ri->auth_pass);
|
||||
ri->auth_pass = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"quorum")) {
|
||||
/* quorum <count> */
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0)
|
||||
goto badfmt;
|
||||
ri->quorum = ll;
|
||||
changes++;
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unknown option '%s' for SENTINEL SET",
|
||||
option);
|
||||
if (changes) sentinelFlushConfig();
|
||||
return;
|
||||
}
|
||||
sentinelEvent(REDIS_WARNING,"+set",ri,"%@ %s %s",option,value);
|
||||
}
|
||||
|
||||
if (changes) sentinelFlushConfig();
|
||||
@@ -2898,7 +2944,7 @@ char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char
|
||||
* time to now, in order to force a delay before we can start a
|
||||
* failover for the same master. */
|
||||
if (strcasecmp(master->leader,server.runid))
|
||||
master->failover_start_time = mstime();
|
||||
master->failover_start_time = mstime()+rand()%SENTINEL_MAX_DESYNC;
|
||||
}
|
||||
|
||||
*leader_epoch = master->leader_epoch;
|
||||
@@ -3043,7 +3089,7 @@ void sentinelStartFailover(sentinelRedisInstance *master) {
|
||||
sentinelEvent(REDIS_WARNING,"+new-epoch",master,"%llu",
|
||||
(unsigned long long) sentinel.current_epoch);
|
||||
sentinelEvent(REDIS_WARNING,"+try-failover",master,"%@");
|
||||
master->failover_start_time = mstime();
|
||||
master->failover_start_time = mstime()+rand()%SENTINEL_MAX_DESYNC;
|
||||
master->failover_state_change_time = mstime();
|
||||
}
|
||||
|
||||
@@ -3353,14 +3399,17 @@ void sentinelFailoverReconfNextSlave(sentinelRedisInstance *master) {
|
||||
/* Skip the promoted slave, and already configured slaves. */
|
||||
if (slave->flags & (SRI_PROMOTED|SRI_RECONF_DONE)) continue;
|
||||
|
||||
/* Clear the SRI_RECONF_SENT flag if too much time elapsed without
|
||||
* the slave moving forward to the next state. */
|
||||
/* If too much time elapsed without the slave moving forward to
|
||||
* the next state, consider it reconfigured even if it is not.
|
||||
* Sentinels will detect the slave as misconfigured and fix its
|
||||
* configuration later. */
|
||||
if ((slave->flags & SRI_RECONF_SENT) &&
|
||||
(mstime() - slave->slave_reconf_sent_time) >
|
||||
SENTINEL_SLAVE_RECONF_RETRY_PERIOD)
|
||||
SENTINEL_SLAVE_RECONF_TIMEOUT)
|
||||
{
|
||||
sentinelEvent(REDIS_NOTICE,"-slave-reconf-sent-timeout",slave,"%@");
|
||||
slave->flags &= ~SRI_RECONF_SENT;
|
||||
slave->flags |= SRI_RECONF_DONE;
|
||||
}
|
||||
|
||||
/* Nothing to do for instances that are disconnected or already
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.8.4"
|
||||
#define REDIS_VERSION "2.8.7"
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../sentinel-tests/includes/init-tests.tcl"
|
||||
|
||||
if {$::simulate_error} {
|
||||
test "This test will fail" {
|
||||
fail "Simulated error"
|
||||
}
|
||||
}
|
||||
|
||||
test "Basic failover works if the master is down" {
|
||||
set old_port [RI $master_id tcp_port]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
kill_instance redis $master_id
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
|
||||
}
|
||||
|
||||
test "New master [join $addr {:}] role matches" {
|
||||
assert {[RI $master_id role] eq {master}}
|
||||
}
|
||||
|
||||
test "All the other slaves now point to the new master" {
|
||||
foreach_redis_id id {
|
||||
if {$id != $master_id && $id != 0} {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI $id master_port] == [lindex $addr 1]
|
||||
} else {
|
||||
fail "Redis ID $id not configured to replicate with new master"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "The old master eventually gets reconfigured as a slave" {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 0 master_port] == [lindex $addr 1]
|
||||
} else {
|
||||
fail "Old master not reconfigured as slave of new master"
|
||||
}
|
||||
}
|
||||
|
||||
test "ODOWN is not possible without N (quorum) Sentinels reports" {
|
||||
foreach_sentinel_id id {
|
||||
S $id SENTINEL SET mymaster quorum [expr $sentinels+1]
|
||||
}
|
||||
set old_port [RI $master_id tcp_port]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
kill_instance redis $master_id
|
||||
|
||||
# Make sure failover did not happened.
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
restart_instance redis $master_id
|
||||
}
|
||||
|
||||
test "Failover is not possible without majority agreement" {
|
||||
foreach_sentinel_id id {
|
||||
S $id SENTINEL SET mymaster quorum $quorum
|
||||
}
|
||||
|
||||
# Crash majority of sentinels
|
||||
for {set id 0} {$id < $quorum} {incr id} {
|
||||
kill_instance sentinel $id
|
||||
}
|
||||
|
||||
# Kill the current master
|
||||
kill_instance redis $master_id
|
||||
|
||||
# Make sure failover did not happened.
|
||||
set addr [S $quorum SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
restart_instance redis $master_id
|
||||
|
||||
# Cleanup: restart Sentinels to monitor the master.
|
||||
for {set id 0} {$id < $quorum} {incr id} {
|
||||
restart_instance sentinel $id
|
||||
}
|
||||
}
|
||||
|
||||
test "Failover works if we configure for absolute agreement" {
|
||||
foreach_sentinel_id id {
|
||||
S $id SENTINEL SET mymaster quorum $sentinels
|
||||
}
|
||||
|
||||
# Wait for Sentinels to monitor the master again
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[dict get [S $id SENTINEL MASTER mymaster] info-refresh] < 100000
|
||||
} else {
|
||||
fail "At least one Sentinel is not monitoring the master"
|
||||
}
|
||||
}
|
||||
|
||||
kill_instance redis $master_id
|
||||
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
|
||||
|
||||
# Set the min ODOWN agreement back to strict majority.
|
||||
foreach_sentinel_id id {
|
||||
S $id SENTINEL SET mymaster quorum $quorum
|
||||
}
|
||||
}
|
||||
|
||||
test "New master [join $addr {:}] role matches" {
|
||||
assert {[RI $master_id role] eq {master}}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
# Test Sentinel configuration consistency after partitions heal.
|
||||
|
||||
source "../sentinel-tests/includes/init-tests.tcl"
|
||||
|
||||
test "We can failover with Sentinel 1 crashed" {
|
||||
set old_port [RI $master_id tcp_port]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
|
||||
# Crash Sentinel 1
|
||||
kill_instance sentinel 1
|
||||
|
||||
kill_instance redis $master_id
|
||||
foreach_sentinel_id id {
|
||||
if {$id != 1} {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "Sentinel $id did not received failover info"
|
||||
}
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
|
||||
}
|
||||
|
||||
test "After Sentinel 1 is restarted, its config gets updated" {
|
||||
restart_instance sentinel 1
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S 1 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "Restarted Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
|
||||
test "New master [join $addr {:}] role matches" {
|
||||
assert {[RI $master_id role] eq {master}}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# Check that slaves are reconfigured at a latter time if they are partitioned.
|
||||
#
|
||||
# Here we should test:
|
||||
# 1) That slaves point to the new master after failover.
|
||||
# 2) That partitioned slaves point to new master when they are partitioned
|
||||
# away during failover and return at a latter time.
|
||||
|
||||
source "../sentinel-tests/includes/init-tests.tcl"
|
||||
|
||||
proc 03_test_slaves_replication {} {
|
||||
uplevel 1 {
|
||||
test "Check that slaves replicate from current master" {
|
||||
set master_port [RI $master_id tcp_port]
|
||||
foreach_redis_id id {
|
||||
if {$id == $master_id} continue
|
||||
wait_for_condition 1000 50 {
|
||||
[RI $id master_port] == $master_port
|
||||
} else {
|
||||
fail "Redis slave $id is replicating from wrong master"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
03_test_slaves_replication
|
||||
|
||||
test "Crash the master and force a failover" {
|
||||
set old_port [RI $master_id tcp_port]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
kill_instance redis $master_id
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
|
||||
}
|
||||
|
||||
03_test_slaves_replication
|
||||
@@ -0,0 +1 @@
|
||||
# Test runtime reconfiguration command SENTINEL SET.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Test slave selection algorithm.
|
||||
#
|
||||
# This unit should test:
|
||||
# 1) That when there are no suitable slaves no failover is performed.
|
||||
# 2) That among the available slaves, the one with better offset is picked.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Initialization tests -- most units will start including this.
|
||||
|
||||
test "(init) Restart killed instances" {
|
||||
foreach type {redis sentinel} {
|
||||
foreach_${type}_id id {
|
||||
if {[get_instance_attrib $type $id pid] == -1} {
|
||||
puts -nonewline "$type/$id "
|
||||
flush stdout
|
||||
restart_instance $type $id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set redis_slaves 4
|
||||
test "(init) Create a master-slaves cluster of [expr $redis_slaves+1] instances" {
|
||||
create_redis_master_slave_cluster [expr {$redis_slaves+1}]
|
||||
}
|
||||
set master_id 0
|
||||
|
||||
test "(init) Sentinels can start monitoring a master" {
|
||||
set sentinels [llength $::sentinel_instances]
|
||||
set quorum [expr {$sentinels/2+1}]
|
||||
foreach_sentinel_id id {
|
||||
catch {S $id SENTINEL REMOVE mymaster}
|
||||
S $id SENTINEL MONITOR mymaster \
|
||||
[get_instance_attrib redis $master_id host] \
|
||||
[get_instance_attrib redis $master_id port] $quorum
|
||||
}
|
||||
foreach_sentinel_id id {
|
||||
assert {[S $id sentinel master mymaster] ne {}}
|
||||
S $id SENTINEL SET mymaster down-after-milliseconds 2000
|
||||
S $id SENTINEL SET mymaster failover-timeout 20000
|
||||
S $id SENTINEL SET mymaster parallel-syncs 10
|
||||
}
|
||||
}
|
||||
|
||||
test "(init) Sentinels can talk with the master" {
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 100 50 {
|
||||
[catch {S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster}] == 0
|
||||
} else {
|
||||
fail "Sentinel $id can't talk with the master."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "(init) Sentinels are able to auto-discover other sentinels" {
|
||||
set sentinels [llength $::sentinel_instances]
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 100 50 {
|
||||
[dict get [S $id SENTINEL MASTER mymaster] num-other-sentinels] == ($sentinels-1)
|
||||
} else {
|
||||
fail "At least some sentinel can't detect some other sentinel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "(init) Sentinels are able to auto-discover slaves" {
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 100 50 {
|
||||
[dict get [S $id SENTINEL MASTER mymaster] num-slaves] == $redis_slaves
|
||||
} else {
|
||||
fail "At least some sentinel can't detect some slave"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
redis_*
|
||||
sentinel_*
|
||||
@@ -0,0 +1,388 @@
|
||||
# Sentinel test suite. Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
|
||||
# This softare is released under the BSD License. See the COPYING file for
|
||||
# more information.
|
||||
|
||||
package require Tcl 8.5
|
||||
|
||||
set tcl_precision 17
|
||||
source tests/support/redis.tcl
|
||||
source tests/support/util.tcl
|
||||
source tests/support/server.tcl
|
||||
source tests/support/test.tcl
|
||||
|
||||
set ::verbose 0
|
||||
set ::pause_on_error 0
|
||||
set ::simulate_error 0
|
||||
set ::sentinel_instances {}
|
||||
set ::redis_instances {}
|
||||
set ::sentinel_base_port 20000
|
||||
set ::redis_base_port 30000
|
||||
set ::instances_count 5 ; # How many Sentinels / Instances we use at max
|
||||
set ::pids {} ; # We kill everything at exit
|
||||
set ::dirs {} ; # We remove all the temp dirs at exit
|
||||
set ::run_matching {} ; # If non empty, only tests matching pattern are run.
|
||||
|
||||
if {[catch {cd tests/sentinel-tmp}]} {
|
||||
puts "tests/sentinel-tmp directory not found."
|
||||
puts "Please run this test from the Redis source root."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Spawn a redis or sentinel instance, depending on 'type'.
|
||||
proc spawn_instance {type base_port count} {
|
||||
for {set j 0} {$j < $count} {incr j} {
|
||||
set port [find_available_port $base_port]
|
||||
incr base_port
|
||||
puts "Starting $type #$j at port $port"
|
||||
|
||||
# Create a directory for this Sentinel.
|
||||
set dirname "${type}_${j}"
|
||||
lappend ::dirs $dirname
|
||||
catch {exec rm -rf $dirname}
|
||||
file mkdir $dirname
|
||||
|
||||
# Write the Sentinel config file.
|
||||
set cfgfile [file join $dirname $type.conf]
|
||||
set cfg [open $cfgfile w]
|
||||
puts $cfg "port $port"
|
||||
puts $cfg "dir ./$dirname"
|
||||
puts $cfg "logfile log.txt"
|
||||
close $cfg
|
||||
|
||||
# Finally exec it and remember the pid for later cleanup.
|
||||
if {$type eq "redis"} {
|
||||
set prgname redis-server
|
||||
} else {
|
||||
set prgname redis-sentinel
|
||||
}
|
||||
set pid [exec ../../src/${prgname} $cfgfile &]
|
||||
lappend ::pids $pid
|
||||
|
||||
# Check availability
|
||||
if {[server_is_up 127.0.0.1 $port 100] == 0} {
|
||||
abort_sentinel_test "Problems starting $type #$j: ping timeout"
|
||||
}
|
||||
|
||||
# Push the instance into the right list
|
||||
lappend ::${type}_instances [list \
|
||||
pid $pid \
|
||||
host 127.0.0.1 \
|
||||
port $port \
|
||||
link [redis 127.0.0.1 $port] \
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
proc cleanup {} {
|
||||
puts "Cleaning up..."
|
||||
foreach pid $::pids {
|
||||
catch {exec kill -9 $pid}
|
||||
}
|
||||
foreach dir $::dirs {
|
||||
catch {exec rm -rf $dir}
|
||||
}
|
||||
}
|
||||
|
||||
proc abort_sentinel_test msg {
|
||||
puts "WARNING: Aborting the test."
|
||||
puts ">>>>>>>> $msg"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
proc parse_options {} {
|
||||
for {set j 0} {$j < [llength $::argv]} {incr j} {
|
||||
set opt [lindex $::argv $j]
|
||||
set val [lindex $::argv [expr $j+1]]
|
||||
if {$opt eq "--single"} {
|
||||
incr j
|
||||
set ::run_matching "*${val}*"
|
||||
} elseif {$opt eq "--pause-on-error"} {
|
||||
set ::pause_on_error 1
|
||||
} elseif {$opt eq "--fail"} {
|
||||
set ::simulate_error 1
|
||||
} elseif {$opt eq "--help"} {
|
||||
puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests."
|
||||
puts "\nOptions:"
|
||||
puts "--single <pattern> Only runs tests specified by pattern."
|
||||
puts "--pause-on-error Pause for manual inspection on error."
|
||||
puts "--fail Simulate a test failure."
|
||||
puts "--help Shows this help."
|
||||
exit 0
|
||||
} else {
|
||||
puts "Unknown option $opt"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc main {} {
|
||||
parse_options
|
||||
spawn_instance sentinel $::sentinel_base_port $::instances_count
|
||||
spawn_instance redis $::redis_base_port $::instances_count
|
||||
run_tests
|
||||
cleanup
|
||||
}
|
||||
|
||||
# If --pause-on-error option was passed at startup this function is called
|
||||
# on error in order to give the developer a chance to understand more about
|
||||
# the error condition while the instances are still running.
|
||||
proc pause_on_error {} {
|
||||
puts ""
|
||||
puts [colorstr yellow "*** Please inspect the error now ***"]
|
||||
puts "\nType \"continue\" to resume the test, \"help\" for help screen.\n"
|
||||
while 1 {
|
||||
puts -nonewline "> "
|
||||
flush stdout
|
||||
set line [gets stdin]
|
||||
set argv [split $line " "]
|
||||
set cmd [lindex $argv 0]
|
||||
if {$cmd eq {continue}} {
|
||||
break
|
||||
} elseif {$cmd eq {show-sentinel-logs}} {
|
||||
set count 10
|
||||
if {[lindex $argv 1] ne {}} {set count [lindex $argv 1]}
|
||||
foreach_sentinel_id id {
|
||||
puts "=== SENTINEL $id ===="
|
||||
puts [exec tail -$count sentinel_$id/log.txt]
|
||||
puts "---------------------\n"
|
||||
}
|
||||
} elseif {$cmd eq {ls}} {
|
||||
foreach_redis_id id {
|
||||
puts -nonewline "Redis $id"
|
||||
set errcode [catch {
|
||||
set str {}
|
||||
append str "@[RI $id tcp_port]: "
|
||||
append str "[RI $id role] "
|
||||
if {[RI $id role] eq {slave}} {
|
||||
append str "[RI $id master_host]:[RI $id master_port]"
|
||||
}
|
||||
set str
|
||||
} retval]
|
||||
if {$errcode} {
|
||||
puts " -- $retval"
|
||||
} else {
|
||||
puts $retval
|
||||
}
|
||||
}
|
||||
foreach_sentinel_id id {
|
||||
puts -nonewline "Sentinel $id"
|
||||
set errcode [catch {
|
||||
set str {}
|
||||
append str "@[SI $id tcp_port]: "
|
||||
append str "[join [S $id sentinel get-master-addr-by-name mymaster]]"
|
||||
set str
|
||||
} retval]
|
||||
if {$errcode} {
|
||||
puts " -- $retval"
|
||||
} else {
|
||||
puts $retval
|
||||
}
|
||||
}
|
||||
} elseif {$cmd eq {help}} {
|
||||
puts "ls List Sentinel and Redis instances."
|
||||
puts "show-sentinel-logs \[N\] Show latest N lines of logs."
|
||||
puts "S <id> cmd ... arg Call command in Sentinel <id>."
|
||||
puts "R <id> cmd ... arg Call command in Redis <id>."
|
||||
puts "SI <id> <field> Show Sentinel <id> INFO <field>."
|
||||
puts "RI <id> <field> Show Sentinel <id> INFO <field>."
|
||||
puts "continue Resume test."
|
||||
} else {
|
||||
set errcode [catch {eval $line} retval]
|
||||
if {$retval ne {}} {puts "$retval"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# We redefine 'test' as for Sentinel we don't use the server-client
|
||||
# architecture for the test, everything is sequential.
|
||||
proc test {descr code} {
|
||||
set ts [clock format [clock seconds] -format %H:%M:%S]
|
||||
puts -nonewline "$ts> $descr: "
|
||||
flush stdout
|
||||
|
||||
if {[catch {set retval [uplevel 1 $code]} error]} {
|
||||
if {[string match "assertion:*" $error]} {
|
||||
set msg [string range $error 10 end]
|
||||
puts [colorstr red $msg]
|
||||
if {$::pause_on_error} pause_on_error
|
||||
puts "(Jumping to next unit after error)"
|
||||
return -code continue
|
||||
} else {
|
||||
# Re-raise, let handler up the stack take care of this.
|
||||
error $error $::errorInfo
|
||||
}
|
||||
} else {
|
||||
puts [colorstr green OK]
|
||||
}
|
||||
}
|
||||
|
||||
proc run_tests {} {
|
||||
set tests [lsort [glob ../sentinel-tests/*]]
|
||||
foreach test $tests {
|
||||
if {$::run_matching ne {} && [string match $::run_matching $test] == 0} {
|
||||
continue
|
||||
}
|
||||
if {[file isdirectory $test]} continue
|
||||
puts [colorstr yellow "Testing unit: [lindex [file split $test] end]"]
|
||||
source $test
|
||||
}
|
||||
}
|
||||
|
||||
# The "S" command is used to interact with the N-th Sentinel.
|
||||
# The general form is:
|
||||
#
|
||||
# S <sentinel-id> command arg arg arg ...
|
||||
#
|
||||
# Example to ping the Sentinel 0 (first instance): S 0 PING
|
||||
proc S {n args} {
|
||||
set s [lindex $::sentinel_instances $n]
|
||||
[dict get $s link] {*}$args
|
||||
}
|
||||
|
||||
# Like R but to chat with Redis instances.
|
||||
proc R {n args} {
|
||||
set r [lindex $::redis_instances $n]
|
||||
[dict get $r link] {*}$args
|
||||
}
|
||||
|
||||
proc get_info_field {info field} {
|
||||
set fl [string length $field]
|
||||
append field :
|
||||
foreach line [split $info "\n"] {
|
||||
set line [string trim $line "\r\n "]
|
||||
if {[string range $line 0 $fl] eq $field} {
|
||||
return [string range $line [expr {$fl+1}] end]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
proc SI {n field} {
|
||||
get_info_field [S $n info] $field
|
||||
}
|
||||
|
||||
proc RI {n field} {
|
||||
get_info_field [R $n info] $field
|
||||
}
|
||||
|
||||
# Iterate over IDs of sentinel or redis instances.
|
||||
proc foreach_instance_id {instances idvar code} {
|
||||
upvar 1 $idvar id
|
||||
for {set id 0} {$id < [llength $instances]} {incr id} {
|
||||
set errcode [catch {uplevel 1 $code} result]
|
||||
if {$errcode == 1} {
|
||||
error $result $::errorInfo $::errorCode
|
||||
} elseif {$errcode == 4} {
|
||||
continue
|
||||
} elseif {$errcode != 0} {
|
||||
return -code $errcode $result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc foreach_sentinel_id {idvar code} {
|
||||
set errcode [catch {uplevel 1 [list foreach_instance_id $::sentinel_instances $idvar $code]} result]
|
||||
return -code $errcode $result
|
||||
}
|
||||
|
||||
proc foreach_redis_id {idvar code} {
|
||||
set errcode [catch {uplevel 1 [list foreach_instance_id $::redis_instances $idvar $code]} result]
|
||||
return -code $errcode $result
|
||||
}
|
||||
|
||||
# Get the specific attribute of the specified instance type, id.
|
||||
proc get_instance_attrib {type id attrib} {
|
||||
dict get [lindex [set ::${type}_instances] $id] $attrib
|
||||
}
|
||||
|
||||
# Set the specific attribute of the specified instance type, id.
|
||||
proc set_instance_attrib {type id attrib newval} {
|
||||
set d [lindex [set ::${type}_instances] $id]
|
||||
dict set d $attrib $newval
|
||||
lset ::${type}_instances $id $d
|
||||
}
|
||||
|
||||
# Create a master-slave cluster of the given number of total instances.
|
||||
# The first instance "0" is the master, all others are configured as
|
||||
# slaves.
|
||||
proc create_redis_master_slave_cluster n {
|
||||
foreach_redis_id id {
|
||||
if {$id == 0} {
|
||||
# Our master.
|
||||
R $id slaveof no one
|
||||
R $id flushall
|
||||
} elseif {$id < $n} {
|
||||
R $id slaveof [get_instance_attrib redis 0 host] \
|
||||
[get_instance_attrib redis 0 port]
|
||||
} else {
|
||||
# Instances not part of the cluster.
|
||||
R $id slaveof no one
|
||||
}
|
||||
}
|
||||
# Wait for all the slaves to sync.
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 0 connected_slaves] == ($n-1)
|
||||
} else {
|
||||
fail "Unable to create a master-slaves cluster."
|
||||
}
|
||||
}
|
||||
|
||||
proc get_instance_id_by_port {type port} {
|
||||
foreach_${type}_id id {
|
||||
if {[get_instance_attrib $type $id port] == $port} {
|
||||
return $id
|
||||
}
|
||||
}
|
||||
fail "Instance $type port $port not found."
|
||||
}
|
||||
|
||||
# Kill an instance of the specified type/id with SIGKILL.
|
||||
# This function will mark the instance PID as -1 to remember that this instance
|
||||
# is no longer running and will remove its PID from the list of pids that
|
||||
# we kill at cleanup.
|
||||
#
|
||||
# The instance can be restarted with restart-instance.
|
||||
proc kill_instance {type id} {
|
||||
set pid [get_instance_attrib $type $id pid]
|
||||
if {$pid == -1} {
|
||||
error "You tried to kill $type $id twice."
|
||||
}
|
||||
exec kill -9 $pid
|
||||
set_instance_attrib $type $id pid -1
|
||||
set_instance_attrib $type $id link you_tried_to_talk_with_killed_instance
|
||||
|
||||
# Remove the PID from the list of pids to kill at exit.
|
||||
set ::pids [lsearch -all -inline -not -exact $::pids $pid]
|
||||
}
|
||||
|
||||
# Restart an instance previously killed by kill_instance
|
||||
proc restart_instance {type id} {
|
||||
set dirname "${type}_${id}"
|
||||
set cfgfile [file join $dirname $type.conf]
|
||||
set port [get_instance_attrib $type $id port]
|
||||
|
||||
# Execute the instance with its old setup and append the new pid
|
||||
# file for cleanup.
|
||||
if {$type eq "redis"} {
|
||||
set prgname redis-server
|
||||
} else {
|
||||
set prgname redis-sentinel
|
||||
}
|
||||
set pid [exec ../../src/${prgname} $cfgfile &]
|
||||
set_instance_attrib $type $id pid $pid
|
||||
lappend ::pids $pid
|
||||
|
||||
# Check that the instance is running
|
||||
if {[server_is_up 127.0.0.1 $port 100] == 0} {
|
||||
abort_sentinel_test "Problems starting $type #$j: ping timeout"
|
||||
}
|
||||
|
||||
# Connect with it with a fresh link
|
||||
set_instance_attrib $type $id link [redis 127.0.0.1 $port]
|
||||
}
|
||||
|
||||
if {[catch main e]} {
|
||||
puts $::errorInfo
|
||||
cleanup
|
||||
}
|
||||
+18
-12
@@ -79,7 +79,7 @@ proc is_alive config {
|
||||
proc ping_server {host port} {
|
||||
set retval 0
|
||||
if {[catch {
|
||||
set fd [socket $::host $::port]
|
||||
set fd [socket $host $port]
|
||||
fconfigure $fd -translation binary
|
||||
puts $fd "PING\r\n"
|
||||
flush $fd
|
||||
@@ -101,6 +101,22 @@ proc ping_server {host port} {
|
||||
return $retval
|
||||
}
|
||||
|
||||
# Return 1 if the server at the specified addr is reachable by PING, otherwise
|
||||
# returns 0. Performs a try every 50 milliseconds for the specified number
|
||||
# of retries.
|
||||
proc server_is_up {host port retrynum} {
|
||||
after 10 ;# Use a small delay to make likely a first-try success.
|
||||
set retval 0
|
||||
while {[incr retrynum -1]} {
|
||||
if {[catch {ping_server $host $port} ping]} {
|
||||
set ping 0
|
||||
}
|
||||
if {$ping} {return 1}
|
||||
after 50
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
# doesn't really belong here, but highly coupled to code in start_server
|
||||
proc tags {tags code} {
|
||||
set ::tags [concat $::tags $tags]
|
||||
@@ -191,23 +207,13 @@ proc start_server {options {code undefined}} {
|
||||
# check that the server actually started
|
||||
# ugly but tries to be as fast as possible...
|
||||
if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
|
||||
set serverisup 0
|
||||
|
||||
if {$::verbose} {
|
||||
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
|
||||
}
|
||||
|
||||
after 10
|
||||
if {$code ne "undefined"} {
|
||||
while {[incr retrynum -1]} {
|
||||
catch {
|
||||
if {[ping_server $::host $::port]} {
|
||||
set serverisup 1
|
||||
}
|
||||
}
|
||||
if {$serverisup} break
|
||||
after 50
|
||||
}
|
||||
set serverisup [server_is_up $::host $::port $retrynum]
|
||||
} else {
|
||||
set serverisup 1
|
||||
}
|
||||
|
||||
+8
-32
@@ -53,41 +53,17 @@ proc assert_type {type key} {
|
||||
# executed.
|
||||
proc wait_for_condition {maxtries delay e _else_ elsescript} {
|
||||
while {[incr maxtries -1] >= 0} {
|
||||
if {[uplevel 1 [list expr $e]]} break
|
||||
set errcode [catch {uplevel 1 [list expr $e]} result]
|
||||
if {$errcode == 0} {
|
||||
if {$result} break
|
||||
} else {
|
||||
return -code $errcode $result
|
||||
}
|
||||
after $delay
|
||||
}
|
||||
if {$maxtries == -1} {
|
||||
uplevel 1 $elsescript
|
||||
}
|
||||
}
|
||||
|
||||
# Test if TERM looks like to support colors
|
||||
proc color_term {} {
|
||||
expr {[info exists ::env(TERM)] && [string match *xterm* $::env(TERM)]}
|
||||
}
|
||||
|
||||
proc colorstr {color str} {
|
||||
if {[color_term]} {
|
||||
set b 0
|
||||
if {[string range $color 0 4] eq {bold-}} {
|
||||
set b 1
|
||||
set color [string range $color 5 end]
|
||||
}
|
||||
switch $color {
|
||||
red {set colorcode {31}}
|
||||
green {set colorcode {32}}
|
||||
yellow {set colorcode {33}}
|
||||
blue {set colorcode {34}}
|
||||
magenta {set colorcode {35}}
|
||||
cyan {set colorcode {36}}
|
||||
white {set colorcode {37}}
|
||||
default {set colorcode {37}}
|
||||
}
|
||||
if {$colorcode ne {}} {
|
||||
return "\033\[$b;${colorcode};40m$str\033\[0m"
|
||||
}
|
||||
} else {
|
||||
return $str
|
||||
set errcode [catch [uplevel 1 $elsescript] result]
|
||||
return -code $errcode $result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -312,3 +312,48 @@ proc csvstring s {
|
||||
proc roundFloat f {
|
||||
format "%.10g" $f
|
||||
}
|
||||
|
||||
proc find_available_port start {
|
||||
for {set j $start} {$j < $start+1024} {incr j} {
|
||||
if {[catch {
|
||||
set fd [socket 127.0.0.1 $j]
|
||||
}]} {
|
||||
return $j
|
||||
} else {
|
||||
close $fd
|
||||
}
|
||||
}
|
||||
if {$j == $start+1024} {
|
||||
error "Can't find a non busy port in the $start-[expr {$start+1023}] range."
|
||||
}
|
||||
}
|
||||
|
||||
# Test if TERM looks like to support colors
|
||||
proc color_term {} {
|
||||
expr {[info exists ::env(TERM)] && [string match *xterm* $::env(TERM)]}
|
||||
}
|
||||
|
||||
proc colorstr {color str} {
|
||||
if {[color_term]} {
|
||||
set b 0
|
||||
if {[string range $color 0 4] eq {bold-}} {
|
||||
set b 1
|
||||
set color [string range $color 5 end]
|
||||
}
|
||||
switch $color {
|
||||
red {set colorcode {31}}
|
||||
green {set colorcode {32}}
|
||||
yellow {set colorcode {33}}
|
||||
blue {set colorcode {34}}
|
||||
magenta {set colorcode {35}}
|
||||
cyan {set colorcode {36}}
|
||||
white {set colorcode {37}}
|
||||
default {set colorcode {37}}
|
||||
}
|
||||
if {$colorcode ne {}} {
|
||||
return "\033\[$b;${colorcode};40m$str\033\[0m"
|
||||
}
|
||||
} else {
|
||||
return $str
|
||||
}
|
||||
}
|
||||
|
||||
+23
-16
@@ -164,21 +164,6 @@ proc cleanup {} {
|
||||
if {!$::quiet} {puts "OK"}
|
||||
}
|
||||
|
||||
proc find_available_port start {
|
||||
for {set j $start} {$j < $start+1024} {incr j} {
|
||||
if {[catch {
|
||||
set fd [socket 127.0.0.1 $j]
|
||||
}]} {
|
||||
return $j
|
||||
} else {
|
||||
close $fd
|
||||
}
|
||||
}
|
||||
if {$j == $start+1024} {
|
||||
error "Can't find a non busy port in the $start-[expr {$start+1023}] range."
|
||||
}
|
||||
}
|
||||
|
||||
proc test_server_main {} {
|
||||
cleanup
|
||||
set tclsh [info nameofexecutable]
|
||||
@@ -204,6 +189,7 @@ proc test_server_main {} {
|
||||
# Setup global state for the test server
|
||||
set ::idle_clients {}
|
||||
set ::active_clients {}
|
||||
array set ::active_clients_task {}
|
||||
array set ::clients_start_time {}
|
||||
set ::clients_time_history {}
|
||||
set ::failed_tests {}
|
||||
@@ -217,9 +203,12 @@ proc test_server_main {} {
|
||||
# may be used in the future in order to detect test clients taking too much
|
||||
# time to execute the task.
|
||||
proc test_server_cron {} {
|
||||
# Do some work here.
|
||||
after 100 test_server_cron
|
||||
}
|
||||
|
||||
proc accept_test_clients {fd addr port} {
|
||||
fconfigure $fd -encoding binary
|
||||
fileevent $fd readable [list read_from_test_client $fd]
|
||||
}
|
||||
|
||||
@@ -253,14 +242,17 @@ proc read_from_test_client fd {
|
||||
puts "\[$completed_tests_count/$all_tests_count [colorstr yellow $status]\]: $data ($elapsed seconds)"
|
||||
lappend ::clients_time_history $elapsed $data
|
||||
signal_idle_client $fd
|
||||
set ::active_clients_task($fd) DONE
|
||||
} elseif {$status eq {ok}} {
|
||||
if {!$::quiet} {
|
||||
puts "\[[colorstr green $status]\]: $data"
|
||||
}
|
||||
set ::active_clients_task($fd) "(OK) $data"
|
||||
} elseif {$status eq {err}} {
|
||||
set err "\[[colorstr red $status]\]: $data"
|
||||
puts $err
|
||||
lappend ::failed_tests $err
|
||||
set ::active_clients_task($fd) "(ERR) $data"
|
||||
} elseif {$status eq {exception}} {
|
||||
puts "\[[colorstr red $status]\]: $data"
|
||||
foreach p $::clients_pids {
|
||||
@@ -268,7 +260,7 @@ proc read_from_test_client fd {
|
||||
}
|
||||
exit 1
|
||||
} elseif {$status eq {testing}} {
|
||||
# No op
|
||||
set ::active_clients_task($fd) "(IN PROGRESS) $data"
|
||||
} else {
|
||||
if {!$::quiet} {
|
||||
puts "\[$status\]: $data"
|
||||
@@ -282,10 +274,24 @@ proc signal_idle_client fd {
|
||||
# Remove this fd from the list of active clients.
|
||||
set ::active_clients \
|
||||
[lsearch -all -inline -not -exact $::active_clients $fd]
|
||||
|
||||
if 0 {
|
||||
# The following loop is only useful for debugging tests that may
|
||||
# enter an infinite loop. Commented out normally.
|
||||
foreach x $::active_clients {
|
||||
if {[info exist ::active_clients_task($x)]} {
|
||||
puts "$x => $::active_clients_task($x)"
|
||||
} else {
|
||||
puts "$x => ???"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# New unit to process?
|
||||
if {$::next_test != [llength $::all_tests]} {
|
||||
if {!$::quiet} {
|
||||
puts [colorstr bold-white "Testing [lindex $::all_tests $::next_test]"]
|
||||
set ::active_clients_task($fd) "ASSIGNED: $fd ([lindex $::all_tests $::next_test])"
|
||||
}
|
||||
set ::clients_start_time($fd) [clock seconds]
|
||||
send_data_packet $fd run [lindex $::all_tests $::next_test]
|
||||
@@ -326,6 +332,7 @@ proc the_end {} {
|
||||
# to read the command, execute, reply... all this in a loop.
|
||||
proc test_client_main server_port {
|
||||
set ::test_server_fd [socket localhost $server_port]
|
||||
fconfigure $::test_server_fd -encoding binary
|
||||
send_data_packet $::test_server_fd ready [pid]
|
||||
while 1 {
|
||||
set bytes [gets $::test_server_fd]
|
||||
|
||||
+162
-1
@@ -52,7 +52,7 @@ start_server {tags {"bitops"}} {
|
||||
}
|
||||
}
|
||||
|
||||
test {BITCOUNT fuzzing} {
|
||||
test {BITCOUNT fuzzing without start/end} {
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
set str [randstring 0 3000]
|
||||
r set str $str
|
||||
@@ -60,6 +60,20 @@ start_server {tags {"bitops"}} {
|
||||
}
|
||||
}
|
||||
|
||||
test {BITCOUNT fuzzing with start/end} {
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
set str [randstring 0 3000]
|
||||
r set str $str
|
||||
set l [string length $str]
|
||||
set start [randomInt $l]
|
||||
set end [randomInt $l]
|
||||
if {$start > $end} {
|
||||
lassign [list $end $start] start end
|
||||
}
|
||||
assert {[r bitcount str $start $end] == [count_bits [string range $str $start $end]]}
|
||||
}
|
||||
}
|
||||
|
||||
test {BITCOUNT with start, end} {
|
||||
r set s "foobar"
|
||||
assert_equal [r bitcount s 0 -1] [count_bits "foobar"]
|
||||
@@ -84,6 +98,18 @@ start_server {tags {"bitops"}} {
|
||||
}
|
||||
} {1}
|
||||
|
||||
test {BITCOUNT misaligned prefix} {
|
||||
r del str
|
||||
r set str ab
|
||||
r bitcount str 1 -1
|
||||
} {3}
|
||||
|
||||
test {BITCOUNT misaligned prefix + full words + remainder} {
|
||||
r del str
|
||||
r set str __PPxxxxxxxxxxxxxxxxRR__
|
||||
r bitcount str 2 -3
|
||||
} {74}
|
||||
|
||||
test {BITOP NOT (empty string)} {
|
||||
r set s ""
|
||||
r bitop not dest s
|
||||
@@ -177,4 +203,139 @@ start_server {tags {"bitops"}} {
|
||||
r set a "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
r bitop or x a b
|
||||
} {32}
|
||||
|
||||
test {BITPOS bit=0 with empty key returns 0} {
|
||||
r del str
|
||||
r bitpos str 0
|
||||
} {0}
|
||||
|
||||
test {BITPOS bit=1 with empty key returns -1} {
|
||||
r del str
|
||||
r bitpos str 1
|
||||
} {-1}
|
||||
|
||||
test {BITPOS bit=0 with string less than 1 word works} {
|
||||
r set str "\xff\xf0\x00"
|
||||
r bitpos str 0
|
||||
} {12}
|
||||
|
||||
test {BITPOS bit=1 with string less than 1 word works} {
|
||||
r set str "\x00\x0f\x00"
|
||||
r bitpos str 1
|
||||
} {12}
|
||||
|
||||
test {BITPOS bit=0 starting at unaligned address} {
|
||||
r set str "\xff\xf0\x00"
|
||||
r bitpos str 0 1
|
||||
} {12}
|
||||
|
||||
test {BITPOS bit=1 starting at unaligned address} {
|
||||
r set str "\x00\x0f\xff"
|
||||
r bitpos str 1 1
|
||||
} {12}
|
||||
|
||||
test {BITPOS bit=0 unaligned+full word+reminder} {
|
||||
r del str
|
||||
r set str "\xff\xff\xff" ; # Prefix
|
||||
# Followed by two (or four in 32 bit systems) full words
|
||||
r append str "\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
r append str "\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
r append str "\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
# First zero bit.
|
||||
r append str "\x0f"
|
||||
assert {[r bitpos str 0] == 216}
|
||||
assert {[r bitpos str 0 1] == 216}
|
||||
assert {[r bitpos str 0 2] == 216}
|
||||
assert {[r bitpos str 0 3] == 216}
|
||||
assert {[r bitpos str 0 4] == 216}
|
||||
assert {[r bitpos str 0 5] == 216}
|
||||
assert {[r bitpos str 0 6] == 216}
|
||||
assert {[r bitpos str 0 7] == 216}
|
||||
assert {[r bitpos str 0 8] == 216}
|
||||
}
|
||||
|
||||
test {BITPOS bit=1 unaligned+full word+reminder} {
|
||||
r del str
|
||||
r set str "\x00\x00\x00" ; # Prefix
|
||||
# Followed by two (or four in 32 bit systems) full words
|
||||
r append str "\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
r append str "\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
r append str "\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
# First zero bit.
|
||||
r append str "\xf0"
|
||||
assert {[r bitpos str 1] == 216}
|
||||
assert {[r bitpos str 1 1] == 216}
|
||||
assert {[r bitpos str 1 2] == 216}
|
||||
assert {[r bitpos str 1 3] == 216}
|
||||
assert {[r bitpos str 1 4] == 216}
|
||||
assert {[r bitpos str 1 5] == 216}
|
||||
assert {[r bitpos str 1 6] == 216}
|
||||
assert {[r bitpos str 1 7] == 216}
|
||||
assert {[r bitpos str 1 8] == 216}
|
||||
}
|
||||
|
||||
test {BITPOS bit=1 returns -1 if string is all 0 bits} {
|
||||
r set str ""
|
||||
for {set j 0} {$j < 20} {incr j} {
|
||||
assert {[r bitpos str 1] == -1}
|
||||
r append str "\x00"
|
||||
}
|
||||
}
|
||||
|
||||
test {BITPOS bit=0 works with intervals} {
|
||||
r set str "\x00\xff\x00"
|
||||
assert {[r bitpos str 0 0 -1] == 0}
|
||||
assert {[r bitpos str 0 1 -1] == 16}
|
||||
assert {[r bitpos str 0 2 -1] == 16}
|
||||
assert {[r bitpos str 0 2 200] == 16}
|
||||
assert {[r bitpos str 0 1 1] == -1}
|
||||
}
|
||||
|
||||
test {BITPOS bit=1 works with intervals} {
|
||||
r set str "\x00\xff\x00"
|
||||
assert {[r bitpos str 1 0 -1] == 8}
|
||||
assert {[r bitpos str 1 1 -1] == 8}
|
||||
assert {[r bitpos str 1 2 -1] == -1}
|
||||
assert {[r bitpos str 1 2 200] == -1}
|
||||
assert {[r bitpos str 1 1 1] == 8}
|
||||
}
|
||||
|
||||
test {BITPOS bit=0 changes behavior if end is given} {
|
||||
r set str "\xff\xff\xff"
|
||||
assert {[r bitpos str 0] == 24}
|
||||
assert {[r bitpos str 0 0] == 24}
|
||||
assert {[r bitpos str 0 0 -1] == -1}
|
||||
}
|
||||
|
||||
test {BITPOS bit=1 fuzzy testing using SETBIT} {
|
||||
r del str
|
||||
set max 524288; # 64k
|
||||
set first_one_pos -1
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
assert {[r bitpos str 1] == $first_one_pos}
|
||||
set pos [randomInt $max]
|
||||
r setbit str $pos 1
|
||||
if {$first_one_pos == -1 || $first_one_pos > $pos} {
|
||||
# Update the position of the first 1 bit in the array
|
||||
# if the bit we set is on the left of the previous one.
|
||||
set first_one_pos $pos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {BITPOS bit=0 fuzzy testing using SETBIT} {
|
||||
set max 524288; # 64k
|
||||
set first_zero_pos $max
|
||||
r set str [string repeat "\xff" [expr $max/8]]
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
assert {[r bitpos str 0] == $first_zero_pos}
|
||||
set pos [randomInt $max]
|
||||
r setbit str $pos 0
|
||||
if {$first_zero_pos > $pos} {
|
||||
# Update the position of the first 0 bit in the array
|
||||
# if the bit we clear is on the left of the previous one.
|
||||
set first_zero_pos $pos
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,5 +417,17 @@ start_server {tags {"scripting repl"}} {
|
||||
}
|
||||
set res
|
||||
} {a 1}
|
||||
|
||||
test {EVALSHA replication when first call is readonly} {
|
||||
r del x
|
||||
r eval {if tonumber(KEYS[1]) > 0 then redis.call('incr', 'x') end} 1 0
|
||||
r evalsha 38fe3ddf5284a1d48f37f824b4c4e826879f3cb9 1 0
|
||||
r evalsha 38fe3ddf5284a1d48f37f824b4c4e826879f3cb9 1 1
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get x] eq {1}
|
||||
} else {
|
||||
fail "Expected 1 in x, but value is '[r -1 get x]'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user