Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2e59c2715 | ||
|
|
99fc582f8c | ||
|
|
3580bb485a | ||
|
|
c3510af1c0 | ||
|
|
771f8ad0e7 | ||
|
|
3ce742d1d5 | ||
|
|
b59585396b | ||
|
|
6826af1b50 | ||
|
|
611372fa97 | ||
|
|
3bd3240660 | ||
|
|
79349affd5 | ||
|
|
80dec5e4df | ||
|
|
a2ec9a9068 | ||
|
|
88c6c669c3 | ||
|
|
6182dad37c | ||
|
|
4ebc7e3738 | ||
|
|
72257f4b87 | ||
|
|
7054f2a029 | ||
|
|
7a20f09647 | ||
|
|
001775f5fb | ||
|
|
c68189a19f | ||
|
|
ff8c81873a | ||
|
|
e3b71a1c02 | ||
|
|
0937377aa2 | ||
|
|
686839b477 | ||
|
|
6d0e408a27 | ||
|
|
ba2edc4191 | ||
|
|
9c2063fb8f | ||
|
|
ffa8f479a5 | ||
|
|
42091a79bb | ||
|
|
38241c4b1e | ||
|
|
9de0755869 | ||
|
|
a31a0b4336 | ||
|
|
6b0e36ffc6 | ||
|
|
bd48ff69b0 | ||
|
|
3703112671 | ||
|
|
ccd2c18c1c | ||
|
|
daa5d2a6c9 | ||
|
|
ca0720b694 | ||
|
|
ffe742f92a | ||
|
|
6fb9e2d717 | ||
|
|
eb9e1526e6 | ||
|
|
a6970570e3 | ||
|
|
fe0ab7d234 | ||
|
|
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 | ||
|
|
71f7eb2590 | ||
|
|
df00b6ac05 | ||
|
|
b8db8a0c48 | ||
|
|
8ed0b49525 | ||
|
|
560e548dc6 | ||
|
|
0ca750d94a | ||
|
|
1f9580c125 | ||
|
|
bb207007ca | ||
|
|
b7dc3204a6 | ||
|
|
2e0ba7bb5b | ||
|
|
52cf0975c7 | ||
|
|
6bcc370c9d | ||
|
|
ea2bffa030 | ||
|
|
bca65866ff | ||
|
|
e4c51759f5 | ||
|
|
51f71e2d8e | ||
|
|
05d219a6fa | ||
|
|
2a1a31ca9d | ||
|
|
418d3d358a | ||
|
|
0a1a236e3e | ||
|
|
27d06111db | ||
|
|
5b7c16137d | ||
|
|
d07d4a876c | ||
|
|
9d1997706c | ||
|
|
4ad219adc8 | ||
|
|
c6db326d1d | ||
|
|
4456ee1173 | ||
|
|
5fa937d956 | ||
|
|
9ba9d78ada | ||
|
|
d33f3689d7 | ||
|
|
fb8a480f54 | ||
|
|
d62bbfda16 | ||
|
|
090bcc946f | ||
|
|
ddd529bc3b | ||
|
|
673c42bb4e | ||
|
|
33f6f35f34 | ||
|
|
8eb1cb3b52 | ||
|
|
993e0ede76 | ||
|
|
2507e366b2 | ||
|
|
f9e9448a5b | ||
|
|
45c60a903a | ||
|
|
5e75e681fe | ||
|
|
8995634014 | ||
|
|
9a8ae5a553 | ||
|
|
0ff078d8d0 | ||
|
|
25ba2e9607 | ||
|
|
563d6b3f98 | ||
|
|
b6610a569d | ||
|
|
26cf5c8ac6 | ||
|
|
303cc97ff9 | ||
|
|
75bf5a4a4a | ||
|
|
8d0083ba25 | ||
|
|
fba0b23e72 | ||
|
|
dceaca1f69 | ||
|
|
bf307cfb4d | ||
|
|
4f9d30b33b | ||
|
|
b67f39da09 | ||
|
|
4c53178c6c | ||
|
|
7f6743a581 | ||
|
|
edae78999c | ||
|
|
3d7263aa41 | ||
|
|
333453646c | ||
|
|
4d650a3baa | ||
|
|
dd0ac4ac72 | ||
|
|
75347ada7f | ||
|
|
83333b08d0 | ||
|
|
50d140e90b | ||
|
|
2eb8a46061 | ||
|
|
a46f841df3 | ||
|
|
e5c577e679 |
+176
@@ -14,6 +14,182 @@ 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.8 ] Release date: 25 Mar 2014
|
||||
|
||||
# UPGRADE URGENCY: HIGH for Redis, LOW for Sentinel. There is a potentially
|
||||
critical bug fix causing data loss in Redis but it requires
|
||||
a combination of disk full and the use of the
|
||||
SHUTDOWN command.
|
||||
|
||||
* [FIX] Fixed data loss when SHUTDOWN was used with a disk full condition.
|
||||
* [FIX] Fixed a memory leak in the SORT syntax error processing.
|
||||
* [FIX] When Sentinel down-after-milliseconds parameter is modified at runtime
|
||||
now it gets propagated to all the slaves and sentinel instances
|
||||
of the master.
|
||||
* [FIX] `install_server.sh` script finally fixed.
|
||||
* [FIX] Different fixes to maxclients handling.
|
||||
|
||||
* [NEW] Sentinels are now able to send update messages in a peer-to-peer
|
||||
fashion even if no Redis instances are available. Now the Sentinel
|
||||
liveness property that the most updated configuration in a given
|
||||
partition is propagated to all the Sentinels is extended to partitions
|
||||
without reachable instances.
|
||||
* [NEW] Sentinel safety properties are now ensured in a crash-recovery system
|
||||
model since some state is persisted on disk before replying to other
|
||||
nodes, and reloaded at startup.
|
||||
* [NEW] Sentinel now uses CLIENT SETNAME so that it is easy to identify
|
||||
Sentinels using CLIENT LIST among other clients.
|
||||
* [NEW] Sentinel failure detection and reconnection code improved.
|
||||
* [NEW] Use all 24 bits (instead of 22) for the Redis objects LRU field.
|
||||
Note that the new LRU algorithm using eviction pools was not backported
|
||||
from unstable for safery / code maturity concerns.
|
||||
* [NEW] Majory speedup for the INFO command (it is now 6 times faster).
|
||||
* [NEW] More Sentinel unit tests.
|
||||
* [NEW] New command DEBUG ERROR returns the specified error. Example:
|
||||
DEBUG ERROR "LOADING database". This is handy to write Redis client
|
||||
libraries unit tests.
|
||||
* [NEW] redis-cli now supports multi-line editing via updated linenoise lib.
|
||||
|
||||
Thanks to Matt Stancliff and Jan-Erik Rediger for the work done in the context
|
||||
of this release.
|
||||
|
||||
--[ 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.
|
||||
|
||||
* [FIX] Makefile compatibility with non common make variants improved.
|
||||
* [FIX] SDIFF crash in very unlikely to trigger state fixed.
|
||||
* [FIX] Config rewriting fixed: don't wipe options unknown to the rewrite
|
||||
process.
|
||||
* [FIX] Set TCP port to 0 works again to disable TCP networking.
|
||||
* [FIX] Fixed replication with old Redis instances as masters by not
|
||||
sending REPLCONF ACK to them.
|
||||
* [FIX] Fix keyspace notifications rewrite and CONFIG GET output.
|
||||
* [FIX] Fix RESTORE TTL handling in 32 bit systems (32 bit overflow).
|
||||
|
||||
* [NEW] Sentinel now has a run time configuration API.
|
||||
* [NEW] Log when we lost connection with master or slave.
|
||||
* [NEW] When instance is turned from slave to master now inherits the
|
||||
old master replication offset when possible. This improves the
|
||||
Sentinel failover procedure.
|
||||
|
||||
--[ Redis 2.8.3 ] Release date: 11 Dec 2013
|
||||
|
||||
# UPGRADE URGENCY: MODERATE for Redis, HIGH for Sentinel.
|
||||
|
||||
* [FIX] Sentinel instance role sampling fixed, the system is now more
|
||||
reliable during failover and when reconfiguring instances with
|
||||
non matching configuration.
|
||||
* [FIX] Inline requests are now handled even when terminated with just LF.
|
||||
* [FIX] Replication timeout handling greatly improved, now the slave is able
|
||||
to ping the master while removing the old data from memory, and while
|
||||
loading the new RDB file. This avoid false timeouts sensed by
|
||||
masters.
|
||||
* [FIX] Fixed a replication bug involving 32 bit instances and big datasets
|
||||
hard to compress that resulted into more than 2GB of RDB file sent.
|
||||
* [FIX] Return error for inline requests with unbalanced quotes.
|
||||
* [FIX] Publish the slave replication offset even when disconnected from the
|
||||
master if there is still a cached master instance.
|
||||
|
||||
--[ Redis 2.8.2 ] Release date: 2 Dec 2013
|
||||
|
||||
# UPGRADE URGENCY: MODERATE for both Redis and Sentinel.
|
||||
|
||||
* [FIX] Sentinel better desynchronization to avoid split-brain elections
|
||||
where no Sentinel managed to get elected.
|
||||
* [FIX] Stop accepting writes on "MISCONF" error only if master, not slave.
|
||||
* [FIX] Reply to PING with an error on "MISCONF" errors.
|
||||
|
||||
--[ Redis 2.8.1 ] Release date: 25 Nov 2013
|
||||
|
||||
# UPGRADE URGENCY: LOW for Redis, CRITICAL for Senitnel. You don't need to
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ each source file that you contribute.
|
||||
|
||||
1. Drop a message to the Redis Google Group with a proposal of semantics/API.
|
||||
|
||||
2. If in steps 1 you get an acknowledge from the project leaders, use the
|
||||
2. If in step 1 you get an acknowledge from the project leaders, use the
|
||||
following procedure to submit a patch:
|
||||
|
||||
a. Fork Redis on github ( http://help.github.com/fork-a-repo/ )
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -130,7 +130,7 @@ it the proper way for a production system, we have a script doing this
|
||||
for Ubuntu and Debian systems:
|
||||
|
||||
% cd utils
|
||||
% ./install_server
|
||||
% ./install_server.sh
|
||||
|
||||
The script will ask you a few questions and will setup everything you need
|
||||
to run Redis properly as a background daemon that will start again on
|
||||
|
||||
Vendored
+693
-229
File diff suppressed because it is too large
Load Diff
Vendored
+34
-23
@@ -3,39 +3,44 @@
|
||||
*
|
||||
* See linenoise.c for more information.
|
||||
*
|
||||
* ------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __LINENOISE_H
|
||||
#define __LINENOISE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct linenoiseCompletions {
|
||||
size_t len;
|
||||
char **cvec;
|
||||
@@ -43,13 +48,19 @@ typedef struct linenoiseCompletions {
|
||||
|
||||
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
|
||||
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
|
||||
void linenoiseAddCompletion(linenoiseCompletions *, char *);
|
||||
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
|
||||
|
||||
char *linenoise(const char *prompt);
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
int linenoiseHistorySave(char *filename);
|
||||
int linenoiseHistoryLoad(char *filename);
|
||||
int linenoiseHistorySave(const char *filename);
|
||||
int linenoiseHistoryLoad(const char *filename);
|
||||
void linenoiseClearScreen(void);
|
||||
void linenoiseSetMultiLine(int ml);
|
||||
void linenoisePrintKeyCodes(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __LINENOISE_H */
|
||||
|
||||
+45
-23
@@ -12,6 +12,26 @@
|
||||
#
|
||||
# units are case insensitive so 1GB 1Gb 1gB are all the same.
|
||||
|
||||
################################## INCLUDES ###################################
|
||||
|
||||
# Include one or more other config files here. This is useful if you
|
||||
# have a standard template that goes to all Redis server but also need
|
||||
# to customize a few per-server settings. Include files can include
|
||||
# other files, so use this wisely.
|
||||
#
|
||||
# Notice option "include" won't be rewritten by command "CONFIG REWRITE"
|
||||
# from admin or Redis Sentinel. Since Redis always uses the last processed
|
||||
# line as value of a configuration directive, you'd better put includes
|
||||
# at the beginning of this file to avoid overwriting config change at runtime.
|
||||
#
|
||||
# If instead you are interested in using includes to override configuration
|
||||
# options, it is better to use include as the last line.
|
||||
#
|
||||
# include /path/to/local.conf
|
||||
# include /path/to/other.conf
|
||||
|
||||
################################ GENERAL #####################################
|
||||
|
||||
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
|
||||
daemonize no
|
||||
@@ -24,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
|
||||
@@ -34,7 +63,7 @@ port 6379
|
||||
# bind 192.168.1.100 10.0.0.1
|
||||
# bind 127.0.0.1
|
||||
|
||||
# Specify the path for the unix socket that will be used to listen for
|
||||
# Specify the path for the Unix socket that will be used to listen for
|
||||
# incoming connections. There is no default, so Redis will not listen
|
||||
# on a unix socket when not specified.
|
||||
#
|
||||
@@ -68,7 +97,7 @@ tcp-keepalive 0
|
||||
# warning (only very important / critical messages are logged)
|
||||
loglevel notice
|
||||
|
||||
# Specify the log file name. Also the emptry string can be used to force
|
||||
# Specify the log file name. Also the empty string can be used to force
|
||||
# Redis to log on the standard output. Note that if you use standard
|
||||
# output for logging but daemonize, logs will be sent to /dev/null
|
||||
logfile ""
|
||||
@@ -88,7 +117,7 @@ logfile ""
|
||||
# dbid is a number between 0 and 'databases'-1
|
||||
databases 16
|
||||
|
||||
################################ SNAPSHOTTING #################################
|
||||
################################ SNAPSHOTTING ################################
|
||||
#
|
||||
# Save the DB on disk:
|
||||
#
|
||||
@@ -116,16 +145,16 @@ save 60 10000
|
||||
|
||||
# By default Redis will stop accepting writes if RDB snapshots are enabled
|
||||
# (at least one save point) and the latest background save failed.
|
||||
# This will make the user aware (in an hard way) that data is not persisting
|
||||
# This will make the user aware (in a hard way) that data is not persisting
|
||||
# on disk properly, otherwise chances are that no one will notice and some
|
||||
# distater will happen.
|
||||
# disaster will happen.
|
||||
#
|
||||
# If the background saving process will start working again Redis will
|
||||
# automatically allow writes again.
|
||||
#
|
||||
# However if you have setup your proper monitoring of the Redis server
|
||||
# and persistence, you may want to disable this feature so that Redis will
|
||||
# continue to work as usually even if there are problems with disk,
|
||||
# continue to work as usual even if there are problems with disk,
|
||||
# permissions, and so forth.
|
||||
stop-writes-on-bgsave-error yes
|
||||
|
||||
@@ -197,7 +226,7 @@ slave-serve-stale-data yes
|
||||
# Note: read only slaves are not designed to be exposed to untrusted clients
|
||||
# on the internet. It's just a protection layer against misuse of the instance.
|
||||
# Still a read only slave exports by default all the administrative commands
|
||||
# such as CONFIG, DEBUG, and so forth. To a limited extend you can improve
|
||||
# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
|
||||
# security of read only slaves using 'rename-command' to shadow all the
|
||||
# administrative / dangerous commands.
|
||||
slave-read-only yes
|
||||
@@ -263,7 +292,7 @@ repl-disable-tcp-nodelay no
|
||||
#
|
||||
# A slave with a low priority number is considered better for promotion, so
|
||||
# for instance if there are three slaves with priority 10, 100, 25 Sentinel will
|
||||
# pick the one wtih priority 10, that is the lowest.
|
||||
# pick the one with priority 10, that is the lowest.
|
||||
#
|
||||
# However a special priority of 0 marks the slave as not able to perform the
|
||||
# role of master, so a slave with priority of 0 will never be selected by
|
||||
@@ -343,7 +372,7 @@ slave-priority 100
|
||||
|
||||
# Don't use more memory than the specified amount of bytes.
|
||||
# When the memory limit is reached Redis will try to remove keys
|
||||
# accordingly to the eviction policy selected (see maxmemmory-policy).
|
||||
# according to the eviction policy selected (see maxmemory-policy).
|
||||
#
|
||||
# If Redis can't remove keys according to the policy, or if the policy is
|
||||
# set to 'noeviction', Redis will start to reply with errors to commands
|
||||
@@ -351,7 +380,7 @@ slave-priority 100
|
||||
# to reply to read-only commands like GET.
|
||||
#
|
||||
# This option is usually useful when using Redis as an LRU cache, or to set
|
||||
# an hard memory limit for an instance (using the 'noeviction' policy).
|
||||
# a hard memory limit for an instance (using the 'noeviction' policy).
|
||||
#
|
||||
# WARNING: If you have slaves attached to an instance with maxmemory on,
|
||||
# the size of the output buffers needed to feed the slaves are subtracted
|
||||
@@ -420,7 +449,8 @@ slave-priority 100
|
||||
appendonly no
|
||||
|
||||
# The name of the append only file (default: "appendonly.aof")
|
||||
# appendfilename appendonly.aof
|
||||
|
||||
appendfilename "appendonly.aof"
|
||||
|
||||
# The fsync() call tells the Operating System to actually write data on disk
|
||||
# instead to wait for more data in the output buffer. Some OS will really flush
|
||||
@@ -467,6 +497,7 @@ appendfsync everysec
|
||||
#
|
||||
# If you have latency problems turn this to "yes". Otherwise leave it as
|
||||
# "no" that is the safest pick from the point of view of durability.
|
||||
|
||||
no-appendfsync-on-rewrite no
|
||||
|
||||
# Automatic rewrite of the append only file.
|
||||
@@ -607,7 +638,7 @@ zset-max-ziplist-value 64
|
||||
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation Redis uses (see dict.c)
|
||||
# performs a lazy rehashing: the more operation you run into an hash table
|
||||
# performs a lazy rehashing: the more operation you run into a hash table
|
||||
# that is rehashing, the more rehashing "steps" are performed, so if the
|
||||
# server is idle the rehashing is never complete and some more memory is used
|
||||
# by the hash table.
|
||||
@@ -633,7 +664,7 @@ activerehashing yes
|
||||
#
|
||||
# normal -> normal clients
|
||||
# slave -> slave clients and MONITOR clients
|
||||
# pubsub -> clients subcribed to at least one pubsub channel or pattern
|
||||
# pubsub -> clients subscribed to at least one pubsub channel or pattern
|
||||
#
|
||||
# The syntax of every client-output-buffer-limit directive is the following:
|
||||
#
|
||||
@@ -662,7 +693,7 @@ client-output-buffer-limit slave 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
|
||||
# Redis calls an internal function to perform many background tasks, like
|
||||
# closing connections of clients in timeot, purging expired keys that are
|
||||
# closing connections of clients in timeout, purging expired keys that are
|
||||
# never requested, and so forth.
|
||||
#
|
||||
# Not all tasks are performed with the same frequency, but Redis checks for
|
||||
@@ -684,12 +715,3 @@ hz 10
|
||||
# big latency spikes.
|
||||
aof-rewrite-incremental-fsync yes
|
||||
|
||||
################################## INCLUDES ###################################
|
||||
|
||||
# Include one or more other config files here. This is useful if you
|
||||
# have a standard template that goes to all Redis server but also need
|
||||
# to customize a few per-server settings. Include files can include
|
||||
# other files, so use this wisely.
|
||||
#
|
||||
# include /path/to/local.conf
|
||||
# include /path/to/other.conf
|
||||
|
||||
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 $*
|
||||
+5
-5
@@ -6,7 +6,7 @@ port 26379
|
||||
|
||||
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
|
||||
#
|
||||
# Tells Sentinel to monitor this slave, and to consider it in O_DOWN
|
||||
# Tells Sentinel to monitor this master, and to consider it in O_DOWN
|
||||
# (Objectively Down) state only if at least <quorum> sentinels agree.
|
||||
#
|
||||
# Note that whatever is the ODOWN quorum, a Sentinel will require to
|
||||
@@ -63,7 +63,7 @@ sentinel parallel-syncs mymaster 1
|
||||
# times the failover timeout.
|
||||
#
|
||||
# - The time needed for a slave replicating to a wrong master according
|
||||
# to a Sentinel currnet configuration, to be forced to replicate
|
||||
# to a Sentinel current configuration, to be forced to replicate
|
||||
# with the right master, is exactly the failover timeout (counting since
|
||||
# the moment a Sentinel detected the misconfiguration).
|
||||
#
|
||||
@@ -86,10 +86,10 @@ sentinel failover-timeout mymaster 180000
|
||||
# or to reconfigure clients after a failover. The scripts are executed
|
||||
# with the following rules for error handling:
|
||||
#
|
||||
# If script exists with "1" the execution is retried later (up to a maximum
|
||||
# If script exits with "1" the execution is retried later (up to a maximum
|
||||
# number of times currently set to 10).
|
||||
#
|
||||
# If script exists with "2" (or an higher value) the script execution is
|
||||
# If script exits with "2" (or an higher value) the script execution is
|
||||
# not retried.
|
||||
#
|
||||
# If script terminates because it receives a signal the behavior is the same
|
||||
@@ -102,7 +102,7 @@ sentinel failover-timeout mymaster 180000
|
||||
#
|
||||
# sentinel notification-script <master-name> <script-path>
|
||||
#
|
||||
# Call the specified notification script for any sentienl event that is
|
||||
# Call the specified notification script for any sentinel event that is
|
||||
# generated in the WARNING level (for instance -sdown, -odown, and so forth).
|
||||
# This script should notify the system administrator via email, SMS, or any
|
||||
# other messaging system, that there is something wrong with the monitored
|
||||
|
||||
+9
-2
@@ -55,15 +55,19 @@ FINAL_LIBS=-lm
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
INSTALL=cp -pf
|
||||
FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lpthread
|
||||
else ifeq ($(uname_S),Darwin)
|
||||
|
||||
else
|
||||
ifeq ($(uname_S),Darwin)
|
||||
# Darwin (nothing to do)
|
||||
else
|
||||
# All the other OSes (notably Linux)
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
FINAL_LIBS+= -pthread
|
||||
endif
|
||||
endif
|
||||
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
|
||||
@@ -200,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
-9
@@ -1,5 +1,5 @@
|
||||
adlist.o: adlist.c adlist.h zmalloc.h
|
||||
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c
|
||||
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c ae_select.c ae_evport.c ae_epoll.c
|
||||
ae_epoll.o: ae_epoll.c
|
||||
ae_evport.o: ae_evport.c
|
||||
ae_kqueue.o: ae_kqueue.c
|
||||
@@ -24,7 +24,7 @@ db.o: db.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
debug.o: debug.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h crc64.h bio.h
|
||||
dict.o: dict.c fmacros.h dict.h zmalloc.h
|
||||
dict.o: dict.c fmacros.h dict.h zmalloc.h redisassert.h
|
||||
endianconv.o: endianconv.c
|
||||
intset.o: intset.c intset.h zmalloc.h endianconv.h config.h
|
||||
lzf_c.o: lzf_c.c lzfP.h
|
||||
@@ -70,18 +70,18 @@ replication.o: replication.c redis.h fmacros.h config.h \
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
|
||||
rio.h
|
||||
rio.o: rio.c fmacros.h rio.h sds.h util.h crc64.h
|
||||
rio.o: rio.c fmacros.h rio.h sds.h util.h crc64.h config.h redis.h \
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h dict.h adlist.h \
|
||||
zmalloc.h anet.h ziplist.h intset.h version.h rdb.h
|
||||
scripting.o: scripting.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h rand.h \
|
||||
../deps/lua/src/lauxlib.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/lualib.h
|
||||
../deps/lua/src/lauxlib.h ../deps/lua/src/lualib.h
|
||||
sds.o: sds.c sds.h zmalloc.h
|
||||
sentinel.o: sentinel.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h \
|
||||
../deps/hiredis/hiredis.h ../deps/hiredis/async.h \
|
||||
../deps/hiredis/hiredis.h
|
||||
../deps/hiredis/hiredis.h ../deps/hiredis/async.h
|
||||
setproctitle.o: setproctitle.c
|
||||
sha1.o: sha1.c sha1.h config.h
|
||||
slowlog.o: slowlog.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
@@ -108,7 +108,8 @@ t_string.o: t_string.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
t_zset.o: t_zset.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
util.o: util.c fmacros.h util.h
|
||||
ziplist.o: ziplist.c zmalloc.h util.h ziplist.h endianconv.h config.h
|
||||
util.o: util.c fmacros.h util.h sds.h
|
||||
ziplist.o: ziplist.c zmalloc.h util.h sds.h ziplist.h endianconv.h \
|
||||
config.h redisassert.h
|
||||
zipmap.o: zipmap.c zmalloc.h endianconv.h config.h
|
||||
zmalloc.o: zmalloc.c config.h zmalloc.h
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
zfree(state);
|
||||
return -1;
|
||||
}
|
||||
state->epfd = epoll_create(1024); /* 1024 is just an hint for the kernel */
|
||||
state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
|
||||
if (state->epfd == -1) {
|
||||
zfree(state->events);
|
||||
zfree(state);
|
||||
|
||||
+49
-28
@@ -163,12 +163,21 @@ int anetTcpKeepAlive(char *err, int fd)
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len)
|
||||
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to
|
||||
* do the actual work. It resolves the hostname "host" and set the string
|
||||
* representation of the IP address into the buffer pointed by "ipbuf".
|
||||
*
|
||||
* If flags is set to ANET_IP_ONLY the function only resolves hostnames
|
||||
* that are actually already IPv4 or IPv6 addresses. This turns the function
|
||||
* into a validating / normalizing function. */
|
||||
int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
|
||||
int flags)
|
||||
{
|
||||
struct addrinfo hints, *info;
|
||||
int rv;
|
||||
|
||||
memset(&hints,0,sizeof(hints));
|
||||
if (flags & ANET_IP_ONLY) hints.ai_flags = AI_NUMERICHOST;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM; /* specify socktype to avoid dups */
|
||||
|
||||
@@ -188,6 +197,14 @@ int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len)
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
|
||||
return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_NONE);
|
||||
}
|
||||
|
||||
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
|
||||
return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_IP_ONLY);
|
||||
}
|
||||
|
||||
static int anetSetReuseAddr(char *err, int fd) {
|
||||
int yes = 1;
|
||||
/* Make sure connection-intensive things like the redis benckmark
|
||||
@@ -219,43 +236,50 @@ static int anetCreateSocket(char *err, int domain) {
|
||||
#define ANET_CONNECT_NONBLOCK 1
|
||||
static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
|
||||
{
|
||||
int s, rv;
|
||||
char _port[6]; /* strlen("65535"); */
|
||||
int s = ANET_ERR, rv;
|
||||
char portstr[6]; /* strlen("65535") + 1; */
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
|
||||
snprintf(_port,6,"%d",port);
|
||||
snprintf(portstr,sizeof(portstr),"%d",port);
|
||||
memset(&hints,0,sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
|
||||
if ((rv = getaddrinfo(addr,portstr,&hints,&servinfo)) != 0) {
|
||||
anetSetError(err, "%s", gai_strerror(rv));
|
||||
return ANET_ERR;
|
||||
}
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
/* Try to create the socket and to connect it.
|
||||
* If we fail in the socket() call, or on connect(), we retry with
|
||||
* the next entry in servinfo. */
|
||||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
|
||||
continue;
|
||||
|
||||
/* if we set err then goto cleanup, otherwise next */
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
|
||||
if (flags & ANET_CONNECT_NONBLOCK && anetNonBlock(err,s) != ANET_OK)
|
||||
goto error;
|
||||
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
|
||||
if (errno == EINPROGRESS && flags & ANET_CONNECT_NONBLOCK) goto end;
|
||||
/* If the socket is non-blocking, it is ok for connect() to
|
||||
* return an EINPROGRESS error here. */
|
||||
if (errno == EINPROGRESS && flags & ANET_CONNECT_NONBLOCK)
|
||||
goto end;
|
||||
close(s);
|
||||
s = ANET_ERR;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* break with the socket */
|
||||
/* If we ended an iteration of the for loop without errors, we
|
||||
* have a connected socket. Let's return to the caller. */
|
||||
goto end;
|
||||
}
|
||||
if (p == NULL) {
|
||||
if (p == NULL)
|
||||
anetSetError(err, "creating socket: %s", strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
s = ANET_ERR;
|
||||
if (s != ANET_ERR) {
|
||||
close(s);
|
||||
s = ANET_ERR;
|
||||
}
|
||||
end:
|
||||
freeaddrinfo(servinfo);
|
||||
return s;
|
||||
@@ -337,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;
|
||||
@@ -365,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") */
|
||||
@@ -387,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) {
|
||||
@@ -402,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;
|
||||
@@ -423,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);
|
||||
@@ -481,7 +502,7 @@ int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) {
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) {
|
||||
*port = 0;
|
||||
if (port) *port = 0;
|
||||
ip[0] = '?';
|
||||
ip[1] = '\0';
|
||||
return -1;
|
||||
@@ -503,7 +524,7 @@ int anetSockName(int fd, char *ip, size_t ip_len, int *port) {
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
if (getsockname(fd,(struct sockaddr*)&sa,&salen) == -1) {
|
||||
*port = 0;
|
||||
if (port) *port = 0;
|
||||
ip[0] = '?';
|
||||
ip[1] = '\0';
|
||||
return -1;
|
||||
|
||||
+8
-3
@@ -35,6 +35,10 @@
|
||||
#define ANET_ERR -1
|
||||
#define ANET_ERR_LEN 256
|
||||
|
||||
/* Flags used with certain functions. */
|
||||
#define ANET_NONE 0
|
||||
#define ANET_IP_ONLY (1<<0)
|
||||
|
||||
#if defined(__sun)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
@@ -45,9 +49,10 @@ int anetUnixConnect(char *err, char *path);
|
||||
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 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 anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
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;
|
||||
|
||||
@@ -771,7 +821,7 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Write either the key or the value of the currently selected item of an hash.
|
||||
/* Write either the key or the value of the currently selected item of a hash.
|
||||
* The 'hi' argument passes a valid Redis hash iterator.
|
||||
* The 'what' filed specifies if to write a key or a value and can be
|
||||
* either REDIS_HASH_KEY or REDIS_HASH_VALUE.
|
||||
@@ -918,9 +968,9 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
}
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
fflush(fp);
|
||||
aof_fsync(fileno(fp));
|
||||
fclose(fp);
|
||||
if (fflush(fp) == EOF) goto werr;
|
||||
if (aof_fsync(fileno(fp)) == -1) goto werr;
|
||||
if (fclose(fp) == EOF) goto werr;
|
||||
|
||||
/* Use RENAME to make sure the DB file is changed atomically only
|
||||
* if the generate DB file is ok. */
|
||||
|
||||
+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);
|
||||
}
|
||||
}
|
||||
|
||||
+70
-33
@@ -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;
|
||||
|
||||
@@ -563,7 +568,7 @@ void configSetCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxclients")) {
|
||||
int orig_value = server.maxclients;
|
||||
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 1) goto badfmt;
|
||||
|
||||
/* Try to check if the OS is capable of supporting so many FDs. */
|
||||
server.maxclients = ll;
|
||||
@@ -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);
|
||||
@@ -1107,8 +1113,8 @@ void configGetCommand(redisClient *c) {
|
||||
/* We use the following dictionary type to store where a configuration
|
||||
* option is mentioned in the old configuration file, so it's
|
||||
* like "maxmemory" -> list of line numbers (first line is zero). */
|
||||
unsigned int dictSdsHash(const void *key);
|
||||
int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2);
|
||||
unsigned int dictSdsCaseHash(const void *key);
|
||||
int dictSdsKeyCaseCompare(void *privdata, const void *key1, const void *key2);
|
||||
void dictSdsDestructor(void *privdata, void *val);
|
||||
void dictListDestructor(void *privdata, void *val);
|
||||
|
||||
@@ -1117,17 +1123,27 @@ void dictListDestructor(void *privdata, void *val);
|
||||
void rewriteConfigSentinelOption(struct rewriteConfigState *state);
|
||||
|
||||
dictType optionToLineDictType = {
|
||||
dictSdsHash, /* hash function */
|
||||
dictSdsCaseHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCompare, /* key compare */
|
||||
dictSdsKeyCaseCompare, /* key compare */
|
||||
dictSdsDestructor, /* key destructor */
|
||||
dictListDestructor /* val destructor */
|
||||
};
|
||||
|
||||
dictType optionSetDictType = {
|
||||
dictSdsCaseHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCaseCompare, /* key compare */
|
||||
dictSdsDestructor, /* key destructor */
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
/* The config rewrite state. */
|
||||
struct rewriteConfigState {
|
||||
dict *option_to_line; /* Option -> list of config file lines map */
|
||||
dict *rewritten; /* Dictionary of already processed options */
|
||||
int numlines; /* Number of lines in current config */
|
||||
sds *lines; /* Current lines as an array of sds strings */
|
||||
int has_tail; /* True if we already added directives that were
|
||||
@@ -1151,6 +1167,16 @@ void rewriteConfigAddLineNumberToOption(struct rewriteConfigState *state, sds op
|
||||
listAddNodeTail(l,(void*)(long)linenum);
|
||||
}
|
||||
|
||||
/* Add the specified option to the set of processed options.
|
||||
* This is useful as only unused lines of processed options will be blanked
|
||||
* in the config file, while options the rewrite process does not understand
|
||||
* remain untouched. */
|
||||
void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, char *option) {
|
||||
sds opt = sdsnew(option);
|
||||
|
||||
if (dictAdd(state->rewritten,opt,NULL) != DICT_OK) sdsfree(opt);
|
||||
}
|
||||
|
||||
/* Read the old file, split it into lines to populate a newly created
|
||||
* config rewrite state, and return it to the caller.
|
||||
*
|
||||
@@ -1165,6 +1191,7 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
|
||||
if (fp == NULL && errno != ENOENT) return NULL;
|
||||
|
||||
state->option_to_line = dictCreate(&optionToLineDictType,NULL);
|
||||
state->rewritten = dictCreate(&optionSetDictType,NULL);
|
||||
state->numlines = 0;
|
||||
state->lines = NULL;
|
||||
state->has_tail = 0;
|
||||
@@ -1232,6 +1259,8 @@ void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sd
|
||||
sds o = sdsnew(option);
|
||||
list *l = dictFetchValue(state->option_to_line,o);
|
||||
|
||||
rewriteConfigMarkAsProcessed(state,option);
|
||||
|
||||
if (!l && !force) {
|
||||
/* Option not used previously, and we are not forced to use it. */
|
||||
sdsfree(line);
|
||||
@@ -1288,7 +1317,6 @@ void rewriteConfigBytesOption(struct rewriteConfigState *state, char *option, lo
|
||||
rewriteConfigFormatMemory(buf,sizeof(buf),value);
|
||||
line = sdscatprintf(sdsempty(),"%s %s",option,buf);
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
|
||||
}
|
||||
|
||||
/* Rewrite a yes/no option. */
|
||||
@@ -1307,7 +1335,10 @@ void rewriteConfigStringOption(struct rewriteConfigState *state, char *option, c
|
||||
|
||||
/* String options set to NULL need to be not present at all in the
|
||||
* configuration file to be set to NULL again at the next reboot. */
|
||||
if (value == NULL) return;
|
||||
if (value == NULL) {
|
||||
rewriteConfigMarkAsProcessed(state,option);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compare the strings as sds strings to have a binary safe comparison. */
|
||||
if (defvalue && strcmp(value,defvalue) == 0) force = 0;
|
||||
@@ -1388,16 +1419,21 @@ 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. */
|
||||
rewriteConfigMarkAsProcessed(state,"save");
|
||||
}
|
||||
|
||||
/* Rewrite the dir option, always using absolute paths.*/
|
||||
void rewriteConfigDirOption(struct rewriteConfigState *state) {
|
||||
char cwd[1024];
|
||||
|
||||
if (getcwd(cwd,sizeof(cwd)) == NULL) return; /* no rewrite on error. */
|
||||
if (getcwd(cwd,sizeof(cwd)) == NULL) {
|
||||
rewriteConfigMarkAsProcessed(state,"dir");
|
||||
return; /* no rewrite on error. */
|
||||
}
|
||||
rewriteConfigStringOption(state,"dir",cwd,NULL);
|
||||
}
|
||||
|
||||
@@ -1408,23 +1444,15 @@ void rewriteConfigSlaveofOption(struct rewriteConfigState *state) {
|
||||
|
||||
/* If this is a master, we want all the slaveof config options
|
||||
* in the file to be removed. */
|
||||
if (server.masterhost == NULL) return;
|
||||
if (server.masterhost == NULL) {
|
||||
rewriteConfigMarkAsProcessed(state,"slaveof");
|
||||
return;
|
||||
}
|
||||
line = sdscatprintf(sdsempty(),"%s %s %d", option,
|
||||
server.masterhost, server.masterport);
|
||||
rewriteConfigRewriteLine(state,option,line,1);
|
||||
}
|
||||
|
||||
/* Rewrite the appendonly option. */
|
||||
void rewriteConfigAppendonlyOption(struct rewriteConfigState *state) {
|
||||
int force = server.aof_state != REDIS_AOF_OFF;
|
||||
char *option = "appendonly";
|
||||
sds line;
|
||||
|
||||
line = sdscatprintf(sdsempty(),"%s %s", option,
|
||||
(server.aof_state == REDIS_AOF_OFF) ? "no" : "yes");
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite the notify-keyspace-events option. */
|
||||
void rewriteConfigNotifykeyspaceeventsOption(struct rewriteConfigState *state) {
|
||||
int force = server.notify_keyspace_events != 0;
|
||||
@@ -1473,7 +1501,10 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
|
||||
char *option = "bind";
|
||||
|
||||
/* Nothing to rewrite if we don't have bind addresses. */
|
||||
if (server.bindaddr_count == 0) return;
|
||||
if (server.bindaddr_count == 0) {
|
||||
rewriteConfigMarkAsProcessed(state,option);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Rewrite as bind <addr1> <addr2> ... <addrN> */
|
||||
addresses = sdsjoin(server.bindaddr,server.bindaddr_count," ");
|
||||
@@ -1509,6 +1540,7 @@ sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
|
||||
void rewriteConfigReleaseState(struct rewriteConfigState *state) {
|
||||
sdsfreesplitres(state->lines,state->numlines);
|
||||
dictRelease(state->option_to_line);
|
||||
dictRelease(state->rewritten);
|
||||
zfree(state);
|
||||
}
|
||||
|
||||
@@ -1526,6 +1558,14 @@ void rewriteConfigRemoveOrphaned(struct rewriteConfigState *state) {
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
list *l = dictGetVal(de);
|
||||
sds option = dictGetKey(de);
|
||||
|
||||
/* Don't blank lines about options the rewrite process
|
||||
* don't understand. */
|
||||
if (dictFind(state->rewritten,option) == NULL) {
|
||||
redisLog(REDIS_DEBUG,"Not rewritten option: %s", option);
|
||||
continue;
|
||||
}
|
||||
|
||||
while(listLength(l)) {
|
||||
listNode *ln = listFirst(l);
|
||||
@@ -1615,11 +1655,10 @@ int rewriteConfig(char *path) {
|
||||
/* Step 2: rewrite every single option, replacing or appending it inside
|
||||
* the rewrite state. */
|
||||
|
||||
/* TODO: Turn every default into a define, use it also in
|
||||
* initServerConfig(). */
|
||||
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);
|
||||
@@ -1652,6 +1691,8 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigBytesOption(state,"repl-backlog-ttl",server.repl_backlog_time_limit,REDIS_DEFAULT_REPL_BACKLOG_TIME_LIMIT);
|
||||
rewriteConfigYesNoOption(state,"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay,REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY);
|
||||
rewriteConfigNumericalOption(state,"slave-priority",server.slave_priority,REDIS_DEFAULT_SLAVE_PRIORITY);
|
||||
rewriteConfigNumericalOption(state,"min-slaves-to-write",server.repl_min_slaves_to_write,REDIS_DEFAULT_MIN_SLAVES_TO_WRITE);
|
||||
rewriteConfigNumericalOption(state,"min-slaves-max-lag",server.repl_min_slaves_max_lag,REDIS_DEFAULT_MIN_SLAVES_MAX_LAG);
|
||||
rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL);
|
||||
rewriteConfigNumericalOption(state,"maxclients",server.maxclients,REDIS_MAX_CLIENTS);
|
||||
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,REDIS_DEFAULT_MAXMEMORY);
|
||||
@@ -1664,7 +1705,8 @@ int rewriteConfig(char *path) {
|
||||
"noeviction", REDIS_MAXMEMORY_NO_EVICTION,
|
||||
NULL, REDIS_DEFAULT_MAXMEMORY_POLICY);
|
||||
rewriteConfigNumericalOption(state,"maxmemory-samples",server.maxmemory_samples,REDIS_DEFAULT_MAXMEMORY_SAMPLES);
|
||||
rewriteConfigAppendonlyOption(state);
|
||||
rewriteConfigYesNoOption(state,"appendonly",server.aof_state != REDIS_AOF_OFF,0);
|
||||
rewriteConfigStringOption(state,"appendfilename",server.aof_filename,REDIS_DEFAULT_AOF_FILENAME);
|
||||
rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,
|
||||
"everysec", AOF_FSYNC_EVERYSEC,
|
||||
"always", AOF_FSYNC_ALWAYS,
|
||||
@@ -1718,14 +1760,7 @@ void configCommand(redisClient *c) {
|
||||
configGetCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"resetstat")) {
|
||||
if (c->argc != 2) goto badarity;
|
||||
server.stat_keyspace_hits = 0;
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
server.stat_rejected_conn = 0;
|
||||
server.stat_fork_time = 0;
|
||||
server.aof_delayed_fsync = 0;
|
||||
resetServerStats();
|
||||
resetCommandTableStats();
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"rewrite")) {
|
||||
@@ -1735,8 +1770,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 {
|
||||
|
||||
@@ -166,14 +166,14 @@ int dbDelete(redisDb *db, robj *key) {
|
||||
}
|
||||
}
|
||||
|
||||
long long emptyDb() {
|
||||
long long emptyDb(void(callback)(void*)) {
|
||||
int j;
|
||||
long long removed = 0;
|
||||
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
removed += dictSize(server.db[j].dict);
|
||||
dictEmpty(server.db[j].dict);
|
||||
dictEmpty(server.db[j].expires);
|
||||
dictEmpty(server.db[j].dict,callback);
|
||||
dictEmpty(server.db[j].expires,callback);
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
@@ -209,14 +209,14 @@ void signalFlushedDb(int dbid) {
|
||||
void flushdbCommand(redisClient *c) {
|
||||
server.dirty += dictSize(c->db->dict);
|
||||
signalFlushedDb(c->db->id);
|
||||
dictEmpty(c->db->dict);
|
||||
dictEmpty(c->db->expires);
|
||||
dictEmpty(c->db->dict,NULL);
|
||||
dictEmpty(c->db->expires,NULL);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
void flushallCommand(redisClient *c) {
|
||||
signalFlushedDb(-1);
|
||||
server.dirty += emptyDb();
|
||||
server.dirty += emptyDb(NULL);
|
||||
addReply(c,shared.ok);
|
||||
if (server.rdb_child_pid != -1) {
|
||||
kill(server.rdb_child_pid,SIGUSR1);
|
||||
@@ -360,7 +360,7 @@ int parseScanCursorOrReply(redisClient *c, robj *o, unsigned long *cursor) {
|
||||
}
|
||||
|
||||
/* This command implements SCAN, HSCAN and SSCAN commands.
|
||||
* If object 'o' is passed, then it must be an Hash or Set object, otherwise
|
||||
* If object 'o' is passed, then it must be a Hash or Set object, otherwise
|
||||
* if 'o' is NULL the command will operate on the dictionary associated with
|
||||
* the current database.
|
||||
*
|
||||
@@ -368,7 +368,7 @@ int parseScanCursorOrReply(redisClient *c, robj *o, unsigned long *cursor) {
|
||||
* the client arguments vector is a key so it skips it before iterating
|
||||
* in order to parse options.
|
||||
*
|
||||
* In the case of an Hash object the function returns both the field and value
|
||||
* In the case of a Hash object the function returns both the field and value
|
||||
* of every element on the Hash. */
|
||||
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
int rv;
|
||||
@@ -423,12 +423,12 @@ void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
/* Step 2: Iterate the collection.
|
||||
*
|
||||
* Note that if the object is encoded with a ziplist, intset, or any other
|
||||
* representation that is not an hash table, we are sure that it is also
|
||||
* representation that is not a hash table, we are sure that it is also
|
||||
* composed of a small number of elements. So to avoid taking state we
|
||||
* just return everything inside the object in a single call, setting the
|
||||
* cursor to zero to signal the end of the iteration. */
|
||||
|
||||
/* Handle the case of an hash table. */
|
||||
/* Handle the case of a hash table. */
|
||||
ht = NULL;
|
||||
if (o == NULL) {
|
||||
ht = c->db->dict;
|
||||
@@ -510,7 +510,7 @@ void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
listDelNode(keys, node);
|
||||
}
|
||||
|
||||
/* If this is an hash or a sorted set, we have a flat list of
|
||||
/* If this is a hash or a sorted set, we have a flat list of
|
||||
* key-value elements, so if this element was filtered, remove the
|
||||
* value, or skip it if it was not filtered: we only match keys. */
|
||||
if (o && (o->type == REDIS_ZSET || o->type == REDIS_HASH)) {
|
||||
@@ -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++;
|
||||
|
||||
+9
-2
@@ -261,7 +261,7 @@ void debugCommand(redisClient *c) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
}
|
||||
emptyDb();
|
||||
emptyDb(NULL);
|
||||
if (rdbLoad(server.rdb_filename) != REDIS_OK) {
|
||||
addReplyError(c,"Error trying to load the RDB dump");
|
||||
return;
|
||||
@@ -269,7 +269,7 @@ void debugCommand(redisClient *c) {
|
||||
redisLog(REDIS_WARNING,"DB reloaded by DEBUG RELOAD");
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) {
|
||||
emptyDb();
|
||||
emptyDb(NULL);
|
||||
if (loadAppendOnlyFile(server.aof_filename) != REDIS_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
@@ -363,6 +363,13 @@ void debugCommand(redisClient *c) {
|
||||
{
|
||||
server.active_expire_enabled = atoi(c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"error") && c->argc == 3) {
|
||||
sds errstr = sdsnewlen("-",1);
|
||||
|
||||
errstr = sdscatsds(errstr,c->argv[2]->ptr);
|
||||
errstr = sdsmapchars(errstr,"\n\r"," ",2); /* no newlines in errors. */
|
||||
errstr = sdscatlen(errstr,"\r\n",2);
|
||||
addReplySds(c,errstr);
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown DEBUG subcommand or wrong number of arguments for '%s'",
|
||||
(char*)c->argv[1]->ptr);
|
||||
|
||||
+12
-11
@@ -53,7 +53,7 @@
|
||||
* around when there is a child performing saving operations.
|
||||
*
|
||||
* Note that even when dict_can_resize is set to 0, not all resizes are
|
||||
* prevented: an hash table is still allowed to grow if the ratio between
|
||||
* prevented: a hash table is still allowed to grow if the ratio between
|
||||
* the number of elements and the buckets > dict_force_resize_ratio. */
|
||||
static int dict_can_resize = 1;
|
||||
static unsigned int dict_force_resize_ratio = 5;
|
||||
@@ -239,7 +239,7 @@ int dictExpand(dict *d, unsigned long size)
|
||||
/* Performs N steps of incremental rehashing. Returns 1 if there are still
|
||||
* keys to move from the old to the new hash table, otherwise 0 is returned.
|
||||
* Note that a rehashing step consists in moving a bucket (that may have more
|
||||
* thank one key as we use chaining) from the old to the new hash table. */
|
||||
* than one key as we use chaining) from the old to the new hash table. */
|
||||
int dictRehash(dict *d, int n) {
|
||||
if (!dictIsRehashing(d)) return 0;
|
||||
|
||||
@@ -444,14 +444,15 @@ int dictDeleteNoFree(dict *ht, const void *key) {
|
||||
}
|
||||
|
||||
/* Destroy an entire dictionary */
|
||||
int _dictClear(dict *d, dictht *ht)
|
||||
{
|
||||
int _dictClear(dict *d, dictht *ht, void(callback)(void *)) {
|
||||
unsigned long i;
|
||||
|
||||
/* Free all the elements */
|
||||
for (i = 0; i < ht->size && ht->used > 0; i++) {
|
||||
dictEntry *he, *nextHe;
|
||||
|
||||
if (callback && (i & 65535) == 0) callback(d->privdata);
|
||||
|
||||
if ((he = ht->table[i]) == NULL) continue;
|
||||
while(he) {
|
||||
nextHe = he->next;
|
||||
@@ -472,8 +473,8 @@ int _dictClear(dict *d, dictht *ht)
|
||||
/* Clear & Release the hash table */
|
||||
void dictRelease(dict *d)
|
||||
{
|
||||
_dictClear(d,&d->ht[0]);
|
||||
_dictClear(d,&d->ht[1]);
|
||||
_dictClear(d,&d->ht[0],NULL);
|
||||
_dictClear(d,&d->ht[1],NULL);
|
||||
zfree(d);
|
||||
}
|
||||
|
||||
@@ -694,7 +695,7 @@ static unsigned long rev(unsigned long v) {
|
||||
* (where SIZE-1 is always the mask that is equivalent to taking the rest
|
||||
* of the division between the Hash of the key and SIZE).
|
||||
*
|
||||
* For example if the current hash table size is 64, the mask is
|
||||
* For example if the current hash table size is 16, the mask is
|
||||
* (in binary) 1111. The position of a key in the hash table will be always
|
||||
* the last four bits of the hash output, and so forth.
|
||||
*
|
||||
@@ -853,7 +854,7 @@ static unsigned long _dictNextPower(unsigned long size)
|
||||
}
|
||||
|
||||
/* Returns the index of a free slot that can be populated with
|
||||
* an hash entry for the given 'key'.
|
||||
* a hash entry for the given 'key'.
|
||||
* If the key already exists, -1 is returned.
|
||||
*
|
||||
* Note that if we are in the process of rehashing the hash table, the
|
||||
@@ -882,9 +883,9 @@ static int _dictKeyIndex(dict *d, const void *key)
|
||||
return idx;
|
||||
}
|
||||
|
||||
void dictEmpty(dict *d) {
|
||||
_dictClear(d,&d->ht[0]);
|
||||
_dictClear(d,&d->ht[1]);
|
||||
void dictEmpty(dict *d, void(callback)(void*)) {
|
||||
_dictClear(d,&d->ht[0],callback);
|
||||
_dictClear(d,&d->ht[1],callback);
|
||||
d->rehashidx = -1;
|
||||
d->iterators = 0;
|
||||
}
|
||||
|
||||
+1
-1
@@ -160,7 +160,7 @@ dictEntry *dictGetRandomKey(dict *d);
|
||||
void dictPrintStats(dict *d);
|
||||
unsigned int dictGenHashFunction(const void *key, int len);
|
||||
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
|
||||
void dictEmpty(dict *d);
|
||||
void dictEmpty(dict *d, void(callback)(void*));
|
||||
void dictEnableResize(void);
|
||||
void dictDisableResize(void);
|
||||
int dictRehash(dict *d, int n);
|
||||
|
||||
+4
-3
@@ -84,7 +84,7 @@ void dumpCommand(redisClient *c) {
|
||||
|
||||
/* RESTORE key ttl serialized-value */
|
||||
void restoreCommand(redisClient *c) {
|
||||
long ttl;
|
||||
long long ttl;
|
||||
rio payload;
|
||||
int type;
|
||||
robj *obj;
|
||||
@@ -96,7 +96,7 @@ void restoreCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
/* Check if the TTL value makes sense */
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&ttl,NULL) != REDIS_OK) {
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[2],&ttl,NULL) != REDIS_OK) {
|
||||
return;
|
||||
} else if (ttl < 0) {
|
||||
addReplyError(c,"Invalid TTL value, must be >= 0");
|
||||
@@ -104,7 +104,8 @@ void restoreCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
/* Verify RDB version and data checksum. */
|
||||
if (verifyDumpPayload(c->argv[3]->ptr,sdslen(c->argv[3]->ptr)) == REDIS_ERR) {
|
||||
if (verifyDumpPayload(c->argv[3]->ptr,sdslen(c->argv[3]->ptr)) == REDIS_ERR)
|
||||
{
|
||||
addReplyError(c,"DUMP payload version or checksum are wrong");
|
||||
return;
|
||||
}
|
||||
|
||||
+57
-26
@@ -100,9 +100,7 @@ redisClient *createClient(int fd) {
|
||||
c->bpop.keys = dictCreate(&setDictType,NULL);
|
||||
c->bpop.timeout = 0;
|
||||
c->bpop.target = NULL;
|
||||
c->io_keys = listCreate();
|
||||
c->watched_keys = listCreate();
|
||||
listSetFreeMethod(c->io_keys,decrRefCountVoid);
|
||||
c->pubsub_channels = dictCreate(&setDictType,NULL);
|
||||
c->pubsub_patterns = listCreate();
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
@@ -572,7 +570,7 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == AE_ERR) {
|
||||
if (cfd == ANET_ERR) {
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
@@ -587,7 +585,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
cfd = anetUnixAccept(server.neterr, fd);
|
||||
if (cfd == AE_ERR) {
|
||||
if (cfd == ANET_ERR) {
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
@@ -639,24 +637,33 @@ void freeClient(redisClient *c) {
|
||||
*
|
||||
* Note that before doing this we make sure that the client is not in
|
||||
* some unexpected state, by checking its flags. */
|
||||
if (server.master &&
|
||||
(c->flags & REDIS_MASTER) &&
|
||||
!(c->flags & (REDIS_CLOSE_AFTER_REPLY|
|
||||
REDIS_CLOSE_ASAP|
|
||||
REDIS_BLOCKED|
|
||||
REDIS_UNBLOCKED)))
|
||||
{
|
||||
replicationCacheMaster(c);
|
||||
return;
|
||||
if (server.master && c->flags & REDIS_MASTER) {
|
||||
redisLog(REDIS_WARNING,"Connection with master lost.");
|
||||
if (!(c->flags & (REDIS_CLOSE_AFTER_REPLY|
|
||||
REDIS_CLOSE_ASAP|
|
||||
REDIS_BLOCKED|
|
||||
REDIS_UNBLOCKED)))
|
||||
{
|
||||
replicationCacheMaster(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Note that if the client we are freeing is blocked into a blocking
|
||||
* call, we have to set querybuf to NULL *before* to call
|
||||
* unblockClientWaitingData() to avoid processInputBuffer() will get
|
||||
* called. Also it is important to remove the file events after
|
||||
* this, because this call adds the READABLE event. */
|
||||
/* Log link disconnection with 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) {
|
||||
redisLog(REDIS_WARNING,"Connection with slave %s:%d lost.",
|
||||
ip, c->slave_listening_port);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free the query buffer */
|
||||
sdsfree(c->querybuf);
|
||||
c->querybuf = NULL;
|
||||
|
||||
/* Deallocate structures used to block on blocking ops. */
|
||||
if (c->flags & REDIS_BLOCKED)
|
||||
unblockClientWaitingData(c);
|
||||
dictRelease(c->bpop.keys);
|
||||
@@ -664,11 +671,13 @@ void freeClient(redisClient *c) {
|
||||
/* UNWATCH all the keys */
|
||||
unwatchAllKeys(c);
|
||||
listRelease(c->watched_keys);
|
||||
|
||||
/* Unsubscribe from all the pubsub channels */
|
||||
pubsubUnsubscribeAllChannels(c,0);
|
||||
pubsubUnsubscribeAllPatterns(c,0);
|
||||
dictRelease(c->pubsub_channels);
|
||||
listRelease(c->pubsub_patterns);
|
||||
|
||||
/* Close socket, unregister events, and remove list of replies and
|
||||
* accumulated arguments. */
|
||||
if (c->fd != -1) {
|
||||
@@ -678,22 +687,24 @@ void freeClient(redisClient *c) {
|
||||
}
|
||||
listRelease(c->reply);
|
||||
freeClientArgv(c);
|
||||
|
||||
/* Remove from the list of clients */
|
||||
if (c->fd != -1) {
|
||||
ln = listSearchKey(server.clients,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(server.clients,ln);
|
||||
}
|
||||
|
||||
/* When client was just unblocked because of a blocking operation,
|
||||
* remove it from the list with unblocked clients. */
|
||||
* remove it from the list of unblocked clients. */
|
||||
if (c->flags & REDIS_UNBLOCKED) {
|
||||
ln = listSearchKey(server.unblocked_clients,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(server.unblocked_clients,ln);
|
||||
}
|
||||
listRelease(c->io_keys);
|
||||
/* Master/slave cleanup.
|
||||
* Case 1: we lost the connection with a slave. */
|
||||
|
||||
/* Master/slave cleanup Case 1:
|
||||
* we lost the connection with a slave. */
|
||||
if (c->flags & REDIS_SLAVE) {
|
||||
if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1)
|
||||
close(c->repldbfd);
|
||||
@@ -709,7 +720,8 @@ void freeClient(redisClient *c) {
|
||||
refreshGoodSlavesCount();
|
||||
}
|
||||
|
||||
/* Case 2: we lost the connection with the master. */
|
||||
/* Master/slave cleanup Case 2:
|
||||
* we lost the connection with the master. */
|
||||
if (c->flags & REDIS_MASTER) replicationHandleMasterDisconnection();
|
||||
|
||||
/* If this client was scheduled for async freeing we need to remove it
|
||||
@@ -720,7 +732,8 @@ void freeClient(redisClient *c) {
|
||||
listDelNode(server.clients_to_close,ln);
|
||||
}
|
||||
|
||||
/* Release memory */
|
||||
/* Release other dynamically allocated client structure fields,
|
||||
* and finally release the client structure itself. */
|
||||
if (c->name) decrRefCount(c->name);
|
||||
zfree(c->argv);
|
||||
freeClientMultiState(c);
|
||||
@@ -840,11 +853,14 @@ void resetClient(redisClient *c) {
|
||||
}
|
||||
|
||||
int processInlineBuffer(redisClient *c) {
|
||||
char *newline = strstr(c->querybuf,"\r\n");
|
||||
char *newline;
|
||||
int argc, j;
|
||||
sds *argv, aux;
|
||||
size_t querylen;
|
||||
|
||||
/* Search for end of line */
|
||||
newline = strchr(c->querybuf,'\n');
|
||||
|
||||
/* Nothing to do without a \r\n */
|
||||
if (newline == NULL) {
|
||||
if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {
|
||||
@@ -854,11 +870,26 @@ int processInlineBuffer(redisClient *c) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Handle the \r\n case. */
|
||||
if (newline && newline != c->querybuf && *(newline-1) == '\r')
|
||||
newline--;
|
||||
|
||||
/* Split the input buffer up to the \r\n */
|
||||
querylen = newline-(c->querybuf);
|
||||
aux = sdsnewlen(c->querybuf,querylen);
|
||||
argv = sdssplitargs(aux,&argc);
|
||||
sdsfree(aux);
|
||||
if (argv == NULL) {
|
||||
addReplyError(c,"Protocol error: unbalanced quotes in request");
|
||||
setProtocolError(c,0);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Newline from slaves can be used to refresh the last ACK time.
|
||||
* This is useful for a slave to ping back while loading a big
|
||||
* RDB file. */
|
||||
if (querylen == 0 && c->flags & REDIS_SLAVE)
|
||||
c->repl_ack_time = server.unixtime;
|
||||
|
||||
/* Leave data after the first line of the query in the buffer */
|
||||
sdsrange(c->querybuf,querylen+2,-1);
|
||||
@@ -1148,7 +1179,7 @@ void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
*biggest_input_buffer = bib;
|
||||
}
|
||||
|
||||
/* This is an helper function for getClientPeerId().
|
||||
/* This is a helper function for getClientPeerId().
|
||||
* It writes the specified ip/port to "peerid" as a null termiated string
|
||||
* in the form ip:port if ip does not contain ":" itself, otherwise
|
||||
* [ip]:port format is used (for IPv6 addresses basically). */
|
||||
|
||||
+12
-10
@@ -67,17 +67,19 @@ int keyspaceEventsStringToFlags(char *classes) {
|
||||
sds keyspaceEventsFlagsToString(int flags) {
|
||||
sds res;
|
||||
|
||||
if ((flags & REDIS_NOTIFY_ALL) == REDIS_NOTIFY_ALL)
|
||||
return sdsnew("A");
|
||||
res = sdsempty();
|
||||
if (flags & REDIS_NOTIFY_GENERIC) res = sdscatlen(res,"g",1);
|
||||
if (flags & REDIS_NOTIFY_STRING) res = sdscatlen(res,"$",1);
|
||||
if (flags & REDIS_NOTIFY_LIST) res = sdscatlen(res,"l",1);
|
||||
if (flags & REDIS_NOTIFY_SET) res = sdscatlen(res,"s",1);
|
||||
if (flags & REDIS_NOTIFY_HASH) res = sdscatlen(res,"h",1);
|
||||
if (flags & REDIS_NOTIFY_ZSET) res = sdscatlen(res,"z",1);
|
||||
if (flags & REDIS_NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
|
||||
if (flags & REDIS_NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
|
||||
if ((flags & REDIS_NOTIFY_ALL) == REDIS_NOTIFY_ALL) {
|
||||
res = sdscatlen(res,"A",1);
|
||||
} else {
|
||||
if (flags & REDIS_NOTIFY_GENERIC) res = sdscatlen(res,"g",1);
|
||||
if (flags & REDIS_NOTIFY_STRING) res = sdscatlen(res,"$",1);
|
||||
if (flags & REDIS_NOTIFY_LIST) res = sdscatlen(res,"l",1);
|
||||
if (flags & REDIS_NOTIFY_SET) res = sdscatlen(res,"s",1);
|
||||
if (flags & REDIS_NOTIFY_HASH) res = sdscatlen(res,"h",1);
|
||||
if (flags & REDIS_NOTIFY_ZSET) res = sdscatlen(res,"z",1);
|
||||
if (flags & REDIS_NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
|
||||
if (flags & REDIS_NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
|
||||
}
|
||||
if (flags & REDIS_NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
|
||||
if (flags & REDIS_NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
|
||||
return res;
|
||||
|
||||
+1
-1
@@ -587,7 +587,7 @@ unsigned long estimateObjectIdleTime(robj *o) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This is an helper function for the DEBUG command. We need to lookup keys
|
||||
/* This is a helper function for the DEBUG command. We need to lookup keys
|
||||
* without any modification of LRU or other parameters. */
|
||||
robj *objectCommandLookup(redisClient *c, robj *key) {
|
||||
dictEntry *de;
|
||||
|
||||
@@ -690,9 +690,9 @@ int rdbSave(char *filename) {
|
||||
rioWrite(&rdb,&cksum,8);
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
fflush(fp);
|
||||
fsync(fileno(fp));
|
||||
fclose(fp);
|
||||
if (fflush(fp) == EOF) goto werr;
|
||||
if (fsync(fileno(fp)) == -1) goto werr;
|
||||
if (fclose(fp) == EOF) goto werr;
|
||||
|
||||
/* Use RENAME to make sure the DB file is changed atomically only
|
||||
* if the generate DB file is ok. */
|
||||
@@ -892,7 +892,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
|
||||
o = createHashObject();
|
||||
|
||||
/* Too many entries? Use an hash table. */
|
||||
/* Too many entries? Use a hash table. */
|
||||
if (len > server.hash_max_ziplist_entries)
|
||||
hashTypeConvert(o, REDIS_ENCODING_HT);
|
||||
|
||||
@@ -1064,7 +1064,14 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
||||
if (server.rdb_checksum)
|
||||
rioGenericUpdateChecksum(r, buf, 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) {
|
||||
(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);
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
#define REDIS_ENCODING_RAW 0 /* Raw representation */
|
||||
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
|
||||
#define REDIS_ENCODING_ZIPMAP 2 /* Encoded as zipmap */
|
||||
#define REDIS_ENCODING_HT 3 /* Encoded as an hash table */
|
||||
#define REDIS_ENCODING_HT 3 /* Encoded as a hash table */
|
||||
|
||||
/* Object types only used for dumping to disk */
|
||||
#define REDIS_EXPIRETIME_MS 252
|
||||
|
||||
+453
-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);
|
||||
}
|
||||
@@ -834,6 +865,7 @@ static void repl() {
|
||||
sds *argv;
|
||||
|
||||
config.interactive = 1;
|
||||
linenoiseSetMultiLine(1);
|
||||
linenoiseSetCompletionCallback(completionCallback);
|
||||
|
||||
/* Only use history when stdin is a tty. */
|
||||
@@ -929,6 +961,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 +1003,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 +1051,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 +1094,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 +1115,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 +1167,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 +1322,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 +1742,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 +1860,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 +1919,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.
|
||||
|
||||
+171
-68
@@ -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 ============================ */
|
||||
@@ -370,7 +371,7 @@ void exitFromChild(int retcode) {
|
||||
|
||||
/*====================== Hash table type implementation ==================== */
|
||||
|
||||
/* This is an hash table type that uses the SDS dynamic strings library as
|
||||
/* This is a hash table type that uses the SDS dynamic strings library as
|
||||
* keys and radis objects as values (objects can hold SDS strings,
|
||||
* lists, sets). */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1010,6 +1016,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
if (zmalloc_used_memory() > server.stat_peak_memory)
|
||||
server.stat_peak_memory = zmalloc_used_memory();
|
||||
|
||||
/* Sample the RSS here since this is a relatively slow call. */
|
||||
server.resident_set_size = zmalloc_get_rss();
|
||||
|
||||
/* We received a SIGTERM, shutting down here in a safe way, as it is
|
||||
* not ok doing so inside the signal handler. */
|
||||
if (server.shutdown_asap) {
|
||||
@@ -1120,10 +1129,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 +1284,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;
|
||||
@@ -1302,7 +1321,7 @@ void initServerConfig() {
|
||||
server.aof_rewrite_incremental_fsync = REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
|
||||
server.pidfile = zstrdup(REDIS_DEFAULT_PID_FILE);
|
||||
server.rdb_filename = zstrdup(REDIS_DEFAULT_RDB_FILENAME);
|
||||
server.aof_filename = zstrdup("appendonly.aof");
|
||||
server.aof_filename = zstrdup(REDIS_DEFAULT_AOF_FILENAME);
|
||||
server.requirepass = NULL;
|
||||
server.rdb_compression = REDIS_DEFAULT_RDB_COMPRESSION;
|
||||
server.rdb_checksum = REDIS_DEFAULT_RDB_CHECKSUM;
|
||||
@@ -1398,21 +1417,21 @@ void initServerConfig() {
|
||||
}
|
||||
|
||||
/* This function will try to raise the max number of open files accordingly to
|
||||
* the configured max number of clients. It will also account for 32 additional
|
||||
* file descriptors as we need a few more for persistence, listening
|
||||
* sockets, log files and so forth.
|
||||
* the configured max number of clients. It also reserves a number of file
|
||||
* descriptors (REDIS_MIN_RESERVED_FDS) for extra operations of
|
||||
* persistence, listening sockets, log files and so forth.
|
||||
*
|
||||
* If it will not be possible to set the limit accordingly to the configured
|
||||
* max number of clients, the function will do the reverse setting
|
||||
* server.maxclients to the value that we can actually handle. */
|
||||
void adjustOpenFilesLimit(void) {
|
||||
rlim_t maxfiles = server.maxclients+32;
|
||||
rlim_t maxfiles = server.maxclients+REDIS_MIN_RESERVED_FDS;
|
||||
struct rlimit limit;
|
||||
|
||||
if (getrlimit(RLIMIT_NOFILE,&limit) == -1) {
|
||||
redisLog(REDIS_WARNING,"Unable to obtain the current NOFILE limit (%s), assuming 1024 and setting the max clients configuration accordingly.",
|
||||
strerror(errno));
|
||||
server.maxclients = 1024-32;
|
||||
server.maxclients = 1024-REDIS_MIN_RESERVED_FDS;
|
||||
} else {
|
||||
rlim_t oldlimit = limit.rlim_cur;
|
||||
|
||||
@@ -1420,22 +1439,54 @@ void adjustOpenFilesLimit(void) {
|
||||
* for our needs. */
|
||||
if (oldlimit < maxfiles) {
|
||||
rlim_t f;
|
||||
|
||||
int setrlimit_error = 0;
|
||||
|
||||
/* Try to set the file limit to match 'maxfiles' or at least
|
||||
* to the higher value supported less than maxfiles. */
|
||||
f = maxfiles;
|
||||
while(f > oldlimit) {
|
||||
int decr_step = 16;
|
||||
|
||||
limit.rlim_cur = f;
|
||||
limit.rlim_max = f;
|
||||
if (setrlimit(RLIMIT_NOFILE,&limit) != -1) break;
|
||||
f -= 128;
|
||||
setrlimit_error = errno;
|
||||
|
||||
/* We failed to set file limit to 'f'. Try with a
|
||||
* smaller limit decrementing by a few FDs per iteration. */
|
||||
if (f < decr_step) break;
|
||||
f -= decr_step;
|
||||
}
|
||||
|
||||
/* Assume that the limit we get initially is still valid if
|
||||
* our last try was even lower. */
|
||||
if (f < oldlimit) f = oldlimit;
|
||||
|
||||
if (f != maxfiles) {
|
||||
server.maxclients = f-32;
|
||||
redisLog(REDIS_WARNING,"Unable to set the max number of files limit to %d (%s), setting the max clients configuration to %d.",
|
||||
(int) maxfiles, strerror(errno), (int) server.maxclients);
|
||||
int old_maxclients = server.maxclients;
|
||||
server.maxclients = f-REDIS_MIN_RESERVED_FDS;
|
||||
if (server.maxclients < 1) {
|
||||
redisLog(REDIS_WARNING,"Your current 'ulimit -n' "
|
||||
"of %llu is not enough for Redis to start. "
|
||||
"Please increase your open file limit to at least "
|
||||
"%llu. Exiting.", oldlimit, maxfiles);
|
||||
exit(1);
|
||||
}
|
||||
redisLog(REDIS_WARNING,"You requested maxclients of %d "
|
||||
"requiring at least %llu max file descriptors.",
|
||||
old_maxclients, maxfiles);
|
||||
redisLog(REDIS_WARNING,"Redis can't set maximum open files "
|
||||
"to %llu because of OS error: %s.",
|
||||
maxfiles, strerror(setrlimit_error));
|
||||
redisLog(REDIS_WARNING,"Current maximum open files is %llu. "
|
||||
"maxclients has been reduced to %d to compensate for "
|
||||
"low ulimit. "
|
||||
"If you need higher maxclients increase 'ulimit -n'.",
|
||||
oldlimit, server.maxclients);
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,"Max number of open files set to %d",
|
||||
(int) maxfiles);
|
||||
redisLog(REDIS_NOTICE,"Increased maximum number of open files "
|
||||
"to %llu (it was originally set to %llu).",
|
||||
maxfiles, oldlimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1469,9 +1520,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 +1532,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,
|
||||
@@ -1496,6 +1551,27 @@ int listenToPort(int port, int *fds, int *count) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Resets the stats that we expose via INFO or other means that we want
|
||||
* to reset via CONFIG RESETSTAT. The function is also used in order to
|
||||
* initialize these fields in initServer() at server startup. */
|
||||
void resetServerStats(void) {
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
server.stat_evictedkeys = 0;
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_keyspace_hits = 0;
|
||||
server.stat_fork_time = 0;
|
||||
server.stat_rejected_conn = 0;
|
||||
server.stat_sync_full = 0;
|
||||
server.stat_sync_partial_ok = 0;
|
||||
server.stat_sync_partial_err = 0;
|
||||
memset(server.ops_sec_samples,0,sizeof(server.ops_sec_samples));
|
||||
server.ops_sec_idx = 0;
|
||||
server.ops_sec_last_sample_time = mstime();
|
||||
server.ops_sec_last_sample_ops = 0;
|
||||
}
|
||||
|
||||
void initServer() {
|
||||
int j;
|
||||
|
||||
@@ -1523,13 +1599,15 @@ void initServer() {
|
||||
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
|
||||
|
||||
/* Open the TCP listening socket for the user commands. */
|
||||
if (listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)
|
||||
if (server.port != 0 &&
|
||||
listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)
|
||||
exit(1);
|
||||
|
||||
/* 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);
|
||||
@@ -1566,27 +1644,16 @@ void initServer() {
|
||||
server.rdb_save_time_last = -1;
|
||||
server.rdb_save_time_start = -1;
|
||||
server.dirty = 0;
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
server.stat_evictedkeys = 0;
|
||||
resetServerStats();
|
||||
/* A few stats we don't want to reset: server startup time, and peak mem. */
|
||||
server.stat_starttime = time(NULL);
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_keyspace_hits = 0;
|
||||
server.stat_peak_memory = 0;
|
||||
server.stat_fork_time = 0;
|
||||
server.stat_rejected_conn = 0;
|
||||
server.stat_sync_full = 0;
|
||||
server.stat_sync_partial_ok = 0;
|
||||
server.stat_sync_partial_err = 0;
|
||||
memset(server.ops_sec_samples,0,sizeof(server.ops_sec_samples));
|
||||
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.resident_set_size = 0;
|
||||
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. */
|
||||
@@ -1785,7 +1852,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
|
||||
@@ -1801,9 +1868,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. */
|
||||
@@ -1918,19 +1986,29 @@ int processCommand(redisClient *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Don't accept write commands if there are problems persisting on disk. */
|
||||
if (server.stop_writes_on_bgsave_err &&
|
||||
server.saveparamslen > 0
|
||||
&& server.lastbgsave_status == REDIS_ERR &&
|
||||
c->cmd->flags & REDIS_CMD_WRITE)
|
||||
/* 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.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;
|
||||
}
|
||||
|
||||
/* Don't accept write commands if there are not enough good slaves and
|
||||
* used configured the min-slaves-to-write option. */
|
||||
* user configured the min-slaves-to-write option. */
|
||||
if (server.repl_min_slaves_to_write &&
|
||||
server.repl_min_slaves_max_lag &&
|
||||
c->cmd->flags & REDIS_CMD_WRITE &&
|
||||
@@ -2070,7 +2148,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;
|
||||
}
|
||||
|
||||
@@ -2193,14 +2272,21 @@ sds genRedisInfoString(char *section) {
|
||||
|
||||
/* Server */
|
||||
if (allsections || defsections || !strcasecmp(section,"server")) {
|
||||
struct utsname name;
|
||||
static int call_uname = 1;
|
||||
static struct utsname name;
|
||||
char *mode;
|
||||
|
||||
if (server.sentinel_mode) mode = "sentinel";
|
||||
else mode = "standalone";
|
||||
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
uname(&name);
|
||||
|
||||
if (call_uname) {
|
||||
/* Uname can be slow and is always the same output. Cache it. */
|
||||
uname(&name);
|
||||
call_uname = 0;
|
||||
}
|
||||
|
||||
info = sdscatprintf(info,
|
||||
"# Server\r\n"
|
||||
"redis_version:%s\r\n"
|
||||
@@ -2261,8 +2347,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,
|
||||
@@ -2275,13 +2369,13 @@ 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.resident_set_size,
|
||||
server.stat_peak_memory,
|
||||
peak_hmem,
|
||||
((long long)lua_gc(server.lua,LUA_GCCOUNT,0))*1024LL,
|
||||
zmalloc_get_fragmentation_ratio(),
|
||||
zmalloc_get_fragmentation_ratio(server.resident_set_size),
|
||||
ZMALLOC_LIB
|
||||
);
|
||||
}
|
||||
@@ -2303,7 +2397,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,
|
||||
@@ -2318,7 +2413,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,
|
||||
@@ -2413,6 +2509,13 @@ sds genRedisInfoString(char *section) {
|
||||
"role:%s\r\n",
|
||||
server.masterhost == NULL ? "master" : "slave");
|
||||
if (server.masterhost) {
|
||||
long long slave_repl_offset = 1;
|
||||
|
||||
if (server.master)
|
||||
slave_repl_offset = server.master->reploff;
|
||||
else if (server.cached_master)
|
||||
slave_repl_offset = server.cached_master->reploff;
|
||||
|
||||
info = sdscatprintf(info,
|
||||
"master_host:%s\r\n"
|
||||
"master_port:%d\r\n"
|
||||
@@ -2427,7 +2530,7 @@ sds genRedisInfoString(char *section) {
|
||||
server.master ?
|
||||
((int)(server.unixtime-server.master->lastinteraction)) : -1,
|
||||
server.repl_state == REDIS_REPL_TRANSFER,
|
||||
server.master ? server.master->reploff : -1
|
||||
slave_repl_offset
|
||||
);
|
||||
|
||||
if (server.repl_state == REDIS_REPL_TRANSFER) {
|
||||
@@ -2987,10 +3090,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");
|
||||
}
|
||||
|
||||
+29
-17
@@ -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
|
||||
@@ -113,6 +114,7 @@
|
||||
#define REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY 0
|
||||
#define REDIS_DEFAULT_MAXMEMORY 0
|
||||
#define REDIS_DEFAULT_MAXMEMORY_SAMPLES 3
|
||||
#define REDIS_DEFAULT_AOF_FILENAME "appendonly.aof"
|
||||
#define REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE 0
|
||||
#define REDIS_DEFAULT_ACTIVE_REHASHING 1
|
||||
#define REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1
|
||||
@@ -121,6 +123,7 @@
|
||||
#define REDIS_IP_STR_LEN INET6_ADDRSTRLEN
|
||||
#define REDIS_PEER_ID_LEN (REDIS_IP_STR_LEN+32) /* Must be enough for ip:port */
|
||||
#define REDIS_BINDADDR_MAX 16
|
||||
#define REDIS_MIN_RESERVED_FDS 32
|
||||
|
||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
|
||||
@@ -137,9 +140,9 @@
|
||||
#define REDIS_LONGSTR_SIZE 21 /* Bytes needed for long -> str */
|
||||
#define REDIS_AOF_AUTOSYNC_BYTES (1024*1024*32) /* fdatasync every 32MB */
|
||||
/* When configuring the Redis eventloop, we setup it so that the total number
|
||||
* of file descriptors we can handle are server.maxclients + FDSET_INCR
|
||||
* of file descriptors we can handle are server.maxclients + RESERVED_FDS + FDSET_INCR
|
||||
* that is our safety margin. */
|
||||
#define REDIS_EVENTLOOP_FDSET_INCR 128
|
||||
#define REDIS_EVENTLOOP_FDSET_INCR (REDIS_MIN_RESERVED_FDS+96)
|
||||
|
||||
/* Hash table parameters */
|
||||
#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */
|
||||
@@ -228,7 +231,7 @@
|
||||
#define REDIS_MASTER_FORCE_REPLY (1<<13) /* Queue replies even if is master */
|
||||
#define REDIS_FORCE_AOF (1<<14) /* Force AOF propagation of current cmd. */
|
||||
#define REDIS_FORCE_REPL (1<<15) /* Force replication of current cmd. */
|
||||
#define REDIS_PRE_PSYNC_SLAVE (1<<16) /* Slave don't understand PSYNC. */
|
||||
#define REDIS_PRE_PSYNC (1<<16) /* Instance don't understand PSYNC. */
|
||||
|
||||
/* Client request types */
|
||||
#define REDIS_REQ_INLINE 1
|
||||
@@ -366,16 +369,18 @@
|
||||
* 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 */
|
||||
#define REDIS_LRU_CLOCK_MAX ((1<<21)-1) /* Max value of obj->lru */
|
||||
#define REDIS_LRU_CLOCK_RESOLUTION 10 /* LRU clock resolution in seconds */
|
||||
#define REDIS_LRU_BITS 24
|
||||
#define REDIS_LRU_CLOCK_MAX ((1<<REDIS_LRU_BITS)-1) /* Max value of obj->lru */
|
||||
#define REDIS_LRU_CLOCK_RESOLUTION 1 /* LRU clock resolution in seconds */
|
||||
typedef struct redisObject {
|
||||
unsigned type:4;
|
||||
unsigned notused:2; /* Not used */
|
||||
unsigned encoding:4;
|
||||
unsigned lru:22; /* lru time (relative to server.lruclock) */
|
||||
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
|
||||
int refcount;
|
||||
void *ptr;
|
||||
} robj;
|
||||
@@ -466,7 +471,7 @@ typedef struct redisClient {
|
||||
int authenticated; /* when requirepass is non-NULL */
|
||||
int replstate; /* replication state if this is a slave */
|
||||
int repldbfd; /* replication DB file descriptor */
|
||||
long repldboff; /* replication DB file offset */
|
||||
off_t repldboff; /* replication DB file offset */
|
||||
off_t repldbsize; /* replication DB file size */
|
||||
long long reploff; /* replication offset if this is our master */
|
||||
long long repl_ack_off; /* replication ack offset, if this is a slave */
|
||||
@@ -475,8 +480,6 @@ typedef struct redisClient {
|
||||
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
|
||||
multiState mstate; /* MULTI/EXEC state */
|
||||
blockingState bpop; /* blocking state */
|
||||
list *io_keys; /* Keys this client is waiting to be loaded from the
|
||||
* swap file in order to continue. */
|
||||
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
|
||||
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
|
||||
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
|
||||
@@ -572,8 +575,7 @@ struct redisServer {
|
||||
dict *commands; /* Command table */
|
||||
dict *orig_commands; /* Command table before command renaming. */
|
||||
aeEventLoop *el;
|
||||
unsigned lruclock:22; /* Clock incrementing every minute, for LRU */
|
||||
unsigned lruclock_padding:10;
|
||||
unsigned lruclock:REDIS_LRU_BITS; /* Clock for LRU eviction */
|
||||
int shutdown_asap; /* SHUTDOWN needed ASAP */
|
||||
int activerehashing; /* Incremental rehash in serverCron() */
|
||||
char *requirepass; /* Pass for AUTH command, or NULL */
|
||||
@@ -584,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 */
|
||||
@@ -623,6 +626,7 @@ struct redisServer {
|
||||
long long slowlog_entry_id; /* SLOWLOG current entry ID */
|
||||
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
|
||||
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */
|
||||
size_t resident_set_size; /* RSS sampled in serverCron(). */
|
||||
/* The following two are used to track instantaneous "load" in terms
|
||||
* of operations per second. */
|
||||
long long ops_sec_last_sample_time; /* Timestamp of last sample (in ms) */
|
||||
@@ -660,6 +664,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 */
|
||||
@@ -727,7 +733,7 @@ struct redisServer {
|
||||
list *repl_scriptcache_fifo; /* First in, first out LRU eviction. */
|
||||
int repl_scriptcache_size; /* Max number of elements. */
|
||||
/* Limits */
|
||||
unsigned int maxclients; /* Max number of simultaneous clients */
|
||||
int maxclients; /* Max number of simultaneous clients */
|
||||
unsigned long long maxmemory; /* Max number of memory bytes to use */
|
||||
int maxmemory_policy; /* Policy for key eviction */
|
||||
int maxmemory_samples; /* Pricision of random sampling */
|
||||
@@ -761,8 +767,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
|
||||
@@ -1025,6 +1031,9 @@ void replicationScriptCacheInit(void);
|
||||
void replicationScriptCacheFlush(void);
|
||||
void replicationScriptCacheAdd(sds sha1);
|
||||
int replicationScriptCacheExists(sds sha1);
|
||||
void replicationSetMaster(char *ip, int port);
|
||||
void replicationUnsetMaster(void);
|
||||
void replicationSendNewlineToMaster(void);
|
||||
|
||||
/* Generic persistence functions */
|
||||
void startLoading(FILE *fp);
|
||||
@@ -1094,6 +1103,8 @@ void populateCommandTable(void);
|
||||
void resetCommandTableStats(void);
|
||||
void adjustOpenFilesLimit(void);
|
||||
void closeListeningSockets(int unlink_unix_socket);
|
||||
void updateCachedTime(void);
|
||||
void resetServerStats(void);
|
||||
|
||||
/* Set data type */
|
||||
robj *setTypeCreate(robj *value);
|
||||
@@ -1165,7 +1176,7 @@ void setKey(redisDb *db, robj *key, robj *val);
|
||||
int dbExists(redisDb *db, robj *key);
|
||||
robj *dbRandomKey(redisDb *db);
|
||||
int dbDelete(redisDb *db, robj *key);
|
||||
long long emptyDb();
|
||||
long long emptyDb(void(callback)(void*));
|
||||
int selectDb(redisClient *c, int id);
|
||||
void signalModifiedKey(redisDb *db, robj *key);
|
||||
void signalFlushedDb(int dbid);
|
||||
@@ -1337,6 +1348,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__)
|
||||
|
||||
+85
-27
@@ -458,7 +458,7 @@ void syncCommand(redisClient *c) {
|
||||
/* If a slave uses SYNC, we are dealing with an old implementation
|
||||
* of the replication protocol (like redis-cli --slave). Flag the client
|
||||
* so that we don't expect to receive REPLCONF ACK feedbacks. */
|
||||
c->flags |= REDIS_PRE_PSYNC_SLAVE;
|
||||
c->flags |= REDIS_PRE_PSYNC;
|
||||
}
|
||||
|
||||
/* Full resynchronization. */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -701,6 +704,31 @@ void replicationAbortSyncTransfer(void) {
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
}
|
||||
|
||||
/* Avoid the master to detect the slave is timing out while loading the
|
||||
* RDB file in initial synchronization. We send a single newline character
|
||||
* that is valid protocol but is guaranteed to either be sent entierly or
|
||||
* not, since the byte is indivisible.
|
||||
*
|
||||
* The function is called in two contexts: while we flush the current
|
||||
* data with emptyDb(), and while we load the new data received as an
|
||||
* RDB file from the master. */
|
||||
void replicationSendNewlineToMaster(void) {
|
||||
static time_t newline_sent;
|
||||
if (time(NULL) != newline_sent) {
|
||||
newline_sent = time(NULL);
|
||||
if (write(server.repl_transfer_s,"\n",1) == -1) {
|
||||
/* Pinging back in this stage is best-effort. */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Callback used by emptyDb() while flushing away old data to load
|
||||
* the new dataset received by the master. */
|
||||
void replicationEmptyDbCallback(void *privdata) {
|
||||
REDIS_NOTUSED(privdata);
|
||||
replicationSendNewlineToMaster();
|
||||
}
|
||||
|
||||
/* Asynchronously read the SYNC payload we receive from a master */
|
||||
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
|
||||
void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
@@ -780,14 +808,15 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
replicationAbortSyncTransfer();
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Loading DB in memory");
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Flushing old data");
|
||||
signalFlushedDb(-1);
|
||||
emptyDb();
|
||||
emptyDb(replicationEmptyDbCallback);
|
||||
/* Before loading the DB into memory we need to delete the readable
|
||||
* handler, otherwise it will get called recursively since
|
||||
* rdbLoad() will call the event loop to process events from time to
|
||||
* time for non blocking loading. */
|
||||
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Loading DB in memory");
|
||||
if (rdbLoad(server.rdb_filename) != REDIS_OK) {
|
||||
redisLog(REDIS_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
|
||||
replicationAbortSyncTransfer();
|
||||
@@ -803,6 +832,10 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
server.master->reploff = server.repl_master_initial_offset;
|
||||
memcpy(server.master->replrunid, server.repl_master_runid,
|
||||
sizeof(server.repl_master_runid));
|
||||
/* If master offset is set to -1, this master is old and is not
|
||||
* PSYNC capable, so we flag it accordingly. */
|
||||
if (server.master->reploff == -1)
|
||||
server.master->flags |= REDIS_PRE_PSYNC;
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
|
||||
/* Restart the AOF subsystem now that we finished the sync. This
|
||||
* will trigger an AOF rewrite, and when done will start appending
|
||||
@@ -965,7 +998,7 @@ int slaveTryPartialResynchronization(int fd) {
|
||||
|
||||
/* If we reach this point we receied either an error since the master does
|
||||
* not understand PSYNC, or an unexpected reply from the master.
|
||||
* Reply with PSYNC_NOT_SUPPORTED in both cases. */
|
||||
* Return PSYNC_NOT_SUPPORTED to the caller in both cases. */
|
||||
|
||||
if (strncmp(reply,"-ERR",4)) {
|
||||
/* If it's not an error, log the unexpected event. */
|
||||
@@ -1205,16 +1238,46 @@ int cancelReplicationHandshake(void) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Set replication to the specified master address and port. */
|
||||
void replicationSetMaster(char *ip, int port) {
|
||||
sdsfree(server.masterhost);
|
||||
server.masterhost = sdsdup(ip);
|
||||
server.masterport = port;
|
||||
if (server.master) freeClient(server.master);
|
||||
disconnectSlaves(); /* Force our slaves to resync with us as well. */
|
||||
replicationDiscardCachedMaster(); /* Don't try a PSYNC. */
|
||||
freeReplicationBacklog(); /* Don't allow our chained slaves to PSYNC. */
|
||||
cancelReplicationHandshake();
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
server.master_repl_offset = 0;
|
||||
}
|
||||
|
||||
/* Cancel replication, setting the instance as a master itself. */
|
||||
void replicationUnsetMaster(void) {
|
||||
if (server.masterhost == NULL) return; /* Nothing to do. */
|
||||
sdsfree(server.masterhost);
|
||||
server.masterhost = NULL;
|
||||
if (server.master) {
|
||||
if (listLength(server.slaves) == 0) {
|
||||
/* If this instance is turned into a master and there are no
|
||||
* slaves, it inherits the replication offset from the master.
|
||||
* Under certain conditions this makes replicas comparable by
|
||||
* replication offset to understand what is the most updated. */
|
||||
server.master_repl_offset = server.master->reploff;
|
||||
freeReplicationBacklog();
|
||||
}
|
||||
freeClient(server.master);
|
||||
}
|
||||
replicationDiscardCachedMaster();
|
||||
cancelReplicationHandshake();
|
||||
server.repl_state = REDIS_REPL_NONE;
|
||||
}
|
||||
|
||||
void slaveofCommand(redisClient *c) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"no") &&
|
||||
!strcasecmp(c->argv[2]->ptr,"one")) {
|
||||
if (server.masterhost) {
|
||||
sdsfree(server.masterhost);
|
||||
server.masterhost = NULL;
|
||||
if (server.master) freeClient(server.master);
|
||||
replicationDiscardCachedMaster();
|
||||
cancelReplicationHandshake();
|
||||
server.repl_state = REDIS_REPL_NONE;
|
||||
replicationUnsetMaster();
|
||||
redisLog(REDIS_NOTICE,"MASTER MODE enabled (user request)");
|
||||
}
|
||||
} else {
|
||||
@@ -1232,15 +1295,7 @@ void slaveofCommand(redisClient *c) {
|
||||
}
|
||||
/* There was no previous master or the user specified a different one,
|
||||
* we can continue. */
|
||||
sdsfree(server.masterhost);
|
||||
server.masterhost = sdsdup(c->argv[1]->ptr);
|
||||
server.masterport = port;
|
||||
if (server.master) freeClient(server.master);
|
||||
disconnectSlaves(); /* Force our slaves to resync with us as well. */
|
||||
replicationDiscardCachedMaster(); /* Don't try a PSYNC. */
|
||||
freeReplicationBacklog(); /* Don't allow our chained slaves to PSYNC. */
|
||||
cancelReplicationHandshake();
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
replicationSetMaster(c->argv[1]->ptr, port);
|
||||
redisLog(REDIS_NOTICE,"SLAVE OF %s:%d enabled (user request)",
|
||||
server.masterhost, server.masterport);
|
||||
}
|
||||
@@ -1388,7 +1443,7 @@ void refreshGoodSlavesCount(void) {
|
||||
* connected slave, in order to be able to replicate EVALSHA as it is without
|
||||
* translating it to EVAL every time it is possible.
|
||||
*
|
||||
* We use a capped collection implemented by an hash table for fast lookup
|
||||
* We use a capped collection implemented by a hash table for fast lookup
|
||||
* of scripts we can send as EVALSHA, plus a linked list that is used for
|
||||
* eviction of the oldest entry when the max number of items is reached.
|
||||
*
|
||||
@@ -1433,7 +1488,7 @@ void replicationScriptCacheInit(void) {
|
||||
* to reclaim otherwise unused memory.
|
||||
*/
|
||||
void replicationScriptCacheFlush(void) {
|
||||
dictEmpty(server.repl_scriptcache_dict);
|
||||
dictEmpty(server.repl_scriptcache_dict,NULL);
|
||||
listRelease(server.repl_scriptcache_fifo);
|
||||
server.repl_scriptcache_fifo = listCreate();
|
||||
}
|
||||
@@ -1506,8 +1561,11 @@ void replicationCron(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Send ACK to master from time to time. */
|
||||
if (server.masterhost && server.master)
|
||||
/* Send ACK to master from time to time.
|
||||
* Note that we do not send periodic acks to masters that don't
|
||||
* support PSYNC and replication offsets. */
|
||||
if (server.masterhost && server.master &&
|
||||
!(server.master->flags & REDIS_PRE_PSYNC))
|
||||
replicationSendAck();
|
||||
|
||||
/* If we have attached slaves, PING them from time to time.
|
||||
@@ -1551,7 +1609,7 @@ void replicationCron(void) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate != REDIS_REPL_ONLINE) continue;
|
||||
if (slave->flags & REDIS_PRE_PSYNC_SLAVE) continue;
|
||||
if (slave->flags & REDIS_PRE_PSYNC) continue;
|
||||
if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout)
|
||||
{
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+5
-4
@@ -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;
|
||||
@@ -895,12 +895,12 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
/* Select the right DB in the context of the Lua client */
|
||||
selectDb(server.lua_client,c->db->id);
|
||||
|
||||
/* Set an hook in order to be able to stop the script execution if it
|
||||
/* Set a hook in order to be able to stop the script execution if it
|
||||
* is running for too much time.
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1059,7 +1060,7 @@ void scriptCommand(redisClient *c) {
|
||||
if (server.lua_caller == NULL) {
|
||||
addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n"));
|
||||
} else if (server.lua_write_dirty) {
|
||||
addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in an hard way using the SHUTDOWN NOSAVE command.\r\n"));
|
||||
addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.\r\n"));
|
||||
} else {
|
||||
server.lua_kill = 1;
|
||||
addReply(c,shared.ok);
|
||||
|
||||
@@ -292,24 +292,37 @@ sds sdscpy(sds s, const char *t) {
|
||||
/* Like sdscatpritf() but gets va_list instead of being variadic. */
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
va_list cpy;
|
||||
char *buf, *t;
|
||||
size_t buflen = 16;
|
||||
char staticbuf[1024], *buf = staticbuf, *t;
|
||||
size_t buflen = strlen(fmt)*2;
|
||||
|
||||
while(1) {
|
||||
/* We try to start using a static buffer for speed.
|
||||
* If not possible we revert to heap allocation. */
|
||||
if (buflen > sizeof(staticbuf)) {
|
||||
buf = zmalloc(buflen);
|
||||
if (buf == NULL) return NULL;
|
||||
} else {
|
||||
buflen = sizeof(staticbuf);
|
||||
}
|
||||
|
||||
/* Try with buffers two times bigger every time we fail to
|
||||
* fit the string in the current buffer size. */
|
||||
while(1) {
|
||||
buf[buflen-2] = '\0';
|
||||
va_copy(cpy,ap);
|
||||
vsnprintf(buf, buflen, fmt, cpy);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
zfree(buf);
|
||||
if (buf != staticbuf) zfree(buf);
|
||||
buflen *= 2;
|
||||
buf = zmalloc(buflen);
|
||||
if (buf == NULL) return NULL;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Finally concat the obtained string to the SDS string and return it. */
|
||||
t = sdscat(s, buf);
|
||||
zfree(buf);
|
||||
if (buf != staticbuf) zfree(buf);
|
||||
return t;
|
||||
}
|
||||
|
||||
@@ -383,7 +396,7 @@ sds sdstrim(sds s, const char *cset) {
|
||||
* Example:
|
||||
*
|
||||
* s = sdsnew("Hello World");
|
||||
* sdstrim(s,1,-1); => "ello Worl"
|
||||
* sdsrange(s,1,-1); => "ello World"
|
||||
*/
|
||||
void sdsrange(sds s, int start, int end) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
@@ -582,7 +595,7 @@ int is_hex_digit(char c) {
|
||||
(c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
/* Helper function for sdssplitargs() that converts an hex digit into an
|
||||
/* Helper function for sdssplitargs() that converts a hex digit into an
|
||||
* integer from 0 to 15 */
|
||||
int hex_digit_to_int(char c) {
|
||||
switch(c) {
|
||||
|
||||
+521
-169
File diff suppressed because it is too large
Load Diff
+11
-4
@@ -48,8 +48,8 @@ redisSortOperation *createSortOperation(int type, robj *pattern) {
|
||||
* 1) The first occurrence of '*' in 'pattern' is substituted with 'subst'.
|
||||
*
|
||||
* 2) If 'pattern' matches the "->" string, everything on the left of
|
||||
* the arrow is treated as the name of an hash field, and the part on the
|
||||
* left as the key name containing an hash. The value of the specified
|
||||
* the arrow is treated as the name of a hash field, and the part on the
|
||||
* left as the key name containing a hash. The value of the specified
|
||||
* field is returned.
|
||||
*
|
||||
* 3) If 'pattern' equals "#", the function simply returns 'subst' itself so
|
||||
@@ -231,8 +231,15 @@ void sortCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"alpha")) {
|
||||
alpha = 1;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) {
|
||||
if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return;
|
||||
if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL)
|
||||
!= REDIS_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL)
|
||||
!= REDIS_OK))
|
||||
{
|
||||
decrRefCount(sortval);
|
||||
listRelease(operations);
|
||||
return;
|
||||
}
|
||||
j+=2;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) {
|
||||
storekey = c->argv[j+1];
|
||||
|
||||
+3
-3
@@ -837,7 +837,7 @@ void unblockClientWaitingData(redisClient *c) {
|
||||
dictReleaseIterator(di);
|
||||
|
||||
/* Cleanup the client structure */
|
||||
dictEmpty(c->bpop.keys);
|
||||
dictEmpty(c->bpop.keys,NULL);
|
||||
if (c->bpop.target) {
|
||||
decrRefCount(c->bpop.target);
|
||||
c->bpop.target = NULL;
|
||||
@@ -850,7 +850,7 @@ void unblockClientWaitingData(redisClient *c) {
|
||||
|
||||
/* If the specified key has clients blocked waiting for list pushes, this
|
||||
* function will put the key reference into the server.ready_keys list.
|
||||
* Note that db->ready_keys is an hash table that allows us to avoid putting
|
||||
* Note that db->ready_keys is a hash table that allows us to avoid putting
|
||||
* the same key again and again in the list in case of multiple pushes
|
||||
* made by a script or in the context of MULTI/EXEC.
|
||||
*
|
||||
@@ -878,7 +878,7 @@ void signalListAsReady(redisClient *c, robj *key) {
|
||||
redisAssert(dictAdd(c->db->ready_keys,key,NULL) == DICT_OK);
|
||||
}
|
||||
|
||||
/* This is an helper function for handleClientsBlockedOnLists(). It's work
|
||||
/* This is a helper function for handleClientsBlockedOnLists(). It's work
|
||||
* is to serve a specific client (receiver) that is blocked on 'key'
|
||||
* in the context of the specified 'db', doing the following:
|
||||
*
|
||||
|
||||
@@ -825,6 +825,7 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
|
||||
while((ele = setTypeNextObject(si)) != NULL) {
|
||||
for (j = 1; j < setnum; j++) {
|
||||
if (!sets[j]) continue; /* no key is an empty set. */
|
||||
if (sets[j] == sets[0]) break; /* same set! */
|
||||
if (setTypeIsMember(sets[j],ele)) break;
|
||||
}
|
||||
if (j == setnum) {
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@
|
||||
* in order to get O(log(N)) INSERT and REMOVE operations into a sorted
|
||||
* data structure.
|
||||
*
|
||||
* The elements are added to an hash table mapping Redis objects to scores.
|
||||
* The elements are added to a hash table mapping Redis objects to scores.
|
||||
* At the same time the elements are added to a skip list mapping scores
|
||||
* to Redis objects (so objects are sorted by scores in this "view"). */
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.8.1"
|
||||
#define REDIS_VERSION "2.8.8"
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
* efficient.
|
||||
*
|
||||
* The Redis Hash type uses this data structure for hashes composed of a small
|
||||
* number of elements, to switch to an hash table once a given number of
|
||||
* number of elements, to switch to a hash table once a given number of
|
||||
* elements is reached.
|
||||
*
|
||||
* Given that many times Redis Hashes are used to represent objects composed
|
||||
|
||||
+3
-3
@@ -176,7 +176,7 @@ void *zrealloc(void *ptr, size_t size) {
|
||||
}
|
||||
|
||||
/* Provide zmalloc_size() for systems where this function is not provided by
|
||||
* malloc itself, given that in that case we store an header with this
|
||||
* malloc itself, given that in that case we store a header with this
|
||||
* information as the first bytes of every allocation. */
|
||||
#ifndef HAVE_MALLOC_SIZE
|
||||
size_t zmalloc_size(void *ptr) {
|
||||
@@ -321,8 +321,8 @@ size_t zmalloc_get_rss(void) {
|
||||
#endif
|
||||
|
||||
/* Fragmentation = RSS / allocated-bytes */
|
||||
float zmalloc_get_fragmentation_ratio(void) {
|
||||
return (float)zmalloc_get_rss()/zmalloc_used_memory();
|
||||
float zmalloc_get_fragmentation_ratio(size_t rss) {
|
||||
return (float)rss/zmalloc_used_memory();
|
||||
}
|
||||
|
||||
#if defined(HAVE_PROC_SMAPS)
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ char *zstrdup(const char *s);
|
||||
size_t zmalloc_used_memory(void);
|
||||
void zmalloc_enable_thread_safeness(void);
|
||||
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
|
||||
float zmalloc_get_fragmentation_ratio(void);
|
||||
float zmalloc_get_fragmentation_ratio(size_t rss);
|
||||
size_t zmalloc_get_rss(void);
|
||||
size_t zmalloc_get_private_dirty(void);
|
||||
void zlibc_free(void *ptr);
|
||||
|
||||
@@ -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,83 @@
|
||||
# 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
|
||||
if {[instance_is_killed redis $id]} continue
|
||||
wait_for_condition 1000 50 {
|
||||
[RI $id master_port] == $master_port
|
||||
} else {
|
||||
fail "Redis slave $id is replicating from wrong master"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc 03_crash_and_failover {} {
|
||||
uplevel 1 {
|
||||
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
|
||||
03_crash_and_failover
|
||||
03_test_slaves_replication
|
||||
|
||||
test "Kill a slave instance" {
|
||||
foreach_redis_id id {
|
||||
if {$id == $master_id} continue
|
||||
set killed_slave_id $id
|
||||
kill_instance redis $id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
03_crash_and_failover
|
||||
03_test_slaves_replication
|
||||
|
||||
test "Wait for failover to end" {
|
||||
set inprogress 1
|
||||
while {$inprogress} {
|
||||
set inprogress 0
|
||||
foreach_sentinel_id id {
|
||||
if {[dict exists [S $id SENTINEL MASTER mymaster] failover-state]} {
|
||||
incr inprogress
|
||||
}
|
||||
}
|
||||
if {$inprogress} {after 100}
|
||||
}
|
||||
}
|
||||
|
||||
test "Restart killed slave and test replication of slaves again..." {
|
||||
restart_instance redis $killed_slave_id
|
||||
}
|
||||
|
||||
# Now we check if the slave rejoining the partition is reconfigured even
|
||||
# if the failover finished.
|
||||
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,396 @@
|
||||
# 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 == 3} {
|
||||
break
|
||||
} 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]
|
||||
}
|
||||
|
||||
# Return true of the instance of the specified type/id is killed.
|
||||
proc instance_is_killed {type id} {
|
||||
set pid [get_instance_attrib $type $id pid]
|
||||
return $pid == -1
|
||||
}
|
||||
|
||||
# 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
|
||||
}
|
||||
+22
-12
@@ -40,6 +40,10 @@ proc kill_server config {
|
||||
test "Check for memory leaks (pid $pid)" {
|
||||
set output {0 leaks}
|
||||
catch {exec leaks $pid} output
|
||||
if {[string match {*process does not exist*} $output]} {
|
||||
# In a few tests we kill the server process.
|
||||
set output "0 leaks"
|
||||
}
|
||||
set output
|
||||
} {*0 leaks*}
|
||||
}
|
||||
@@ -79,7 +83,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 +105,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 +211,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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,16 @@ start_server {tags {"dump"}} {
|
||||
r get foo
|
||||
} {bar}
|
||||
|
||||
test {RESTORE can set an expire that overflows a 32 bit integer} {
|
||||
r set foo bar
|
||||
set encoded [r dump foo]
|
||||
r del foo
|
||||
r restore foo 2569591501 $encoded
|
||||
set ttl [r pttl foo]
|
||||
assert {$ttl >= (2569591501-3000) && $ttl <= 2569591501}
|
||||
r get foo
|
||||
} {bar}
|
||||
|
||||
test {RESTORE returns an error of the key already exists} {
|
||||
r set foo bar
|
||||
set e {}
|
||||
|
||||
@@ -60,6 +60,14 @@ start_server {tags {"protocol"}} {
|
||||
assert_error "*wrong*arguments*ping*" {r ping x y z}
|
||||
}
|
||||
|
||||
test "Unbalanced number of quotes" {
|
||||
reconnect
|
||||
r write "set \"\"\"test-key\"\"\" test-value\r\n"
|
||||
r write "ping\r\n"
|
||||
r flush
|
||||
assert_error "*unbalanced*" {r read}
|
||||
}
|
||||
|
||||
set c 0
|
||||
foreach seq [list "\x00" "*\x00" "$\x00"] {
|
||||
incr c
|
||||
|
||||
@@ -356,4 +356,15 @@ start_server {tags {"pubsub"}} {
|
||||
r config set maxmemory 0
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: test CONFIG GET/SET of event flags" {
|
||||
r config set notify-keyspace-events gKE
|
||||
assert_equal {gKE} [lindex [r config get notify-keyspace-events] 1]
|
||||
r config set notify-keyspace-events {$lshzxeKE}
|
||||
assert_equal {$lshzxeKE} [lindex [r config get notify-keyspace-events] 1]
|
||||
r config set notify-keyspace-events KA
|
||||
assert_equal {AK} [lindex [r config get notify-keyspace-events] 1]
|
||||
r config set notify-keyspace-events EA
|
||||
assert_equal {AE} [lindex [r config get notify-keyspace-events] 1]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +214,12 @@ start_server {
|
||||
r sdiff set1 set2 set3
|
||||
} {}
|
||||
|
||||
test "SDIFF with same set two times" {
|
||||
r del set1
|
||||
r sadd set1 a b c 1 2 3 4 5 6
|
||||
r sdiff set1 set1
|
||||
} {}
|
||||
|
||||
test "SDIFF fuzzing" {
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
unset -nocomplain s
|
||||
|
||||
+116
-61
@@ -30,46 +30,48 @@
|
||||
# this scripts should be run as root
|
||||
|
||||
die () {
|
||||
echo "ERROR: $1. Aborting!"
|
||||
echo "ERROR: $1. Aborting!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
#Absolute path to this script
|
||||
SCRIPT=$(readlink -f $0)
|
||||
#Absolute path this script is in
|
||||
SCRIPTPATH=$(dirname $SCRIPT)
|
||||
|
||||
#Initial defaults
|
||||
_REDIS_PORT=6379
|
||||
|
||||
echo "Welcome to the redis service installer"
|
||||
echo "This script will help you easily set up a running redis server
|
||||
echo "This script will help you easily set up a running redis server"
|
||||
echo
|
||||
|
||||
"
|
||||
|
||||
#check for root user TODO: replace this with a call to "id"
|
||||
if [ `whoami` != "root" ] ; then
|
||||
#check for root user
|
||||
if [ "$(id -u)" -ne 0 ] ; then
|
||||
echo "You must run this script as root. Sorry!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
#Read the redis port
|
||||
read -p "Please select the redis port for this instance: [$_REDIS_PORT] " REDIS_PORT
|
||||
if [ ! `echo $REDIS_PORT | egrep "^[0-9]+\$"` ] ; then
|
||||
read -p "Please select the redis port for this instance: [$_REDIS_PORT] " REDIS_PORT
|
||||
if ! echo $REDIS_PORT | egrep -q '^[0-9]+$' ; then
|
||||
echo "Selecting default: $_REDIS_PORT"
|
||||
REDIS_PORT=$_REDIS_PORT
|
||||
REDIS_PORT=$_REDIS_PORT
|
||||
fi
|
||||
|
||||
#read the redis config file
|
||||
_REDIS_CONFIG_FILE="/etc/redis/$REDIS_PORT.conf"
|
||||
read -p "Please select the redis config file name [$_REDIS_CONFIG_FILE] " REDIS_CONFIG_FILE
|
||||
if [ !"$REDIS_CONFIG_FILE" ] ; then
|
||||
if [ -z "$REDIS_CONFIG_FILE" ] ; then
|
||||
REDIS_CONFIG_FILE=$_REDIS_CONFIG_FILE
|
||||
echo "Selected default - $REDIS_CONFIG_FILE"
|
||||
fi
|
||||
#try and create it
|
||||
mkdir -p `dirname "$REDIS_CONFIG_FILE"` || die "Could not create redis config directory"
|
||||
|
||||
#read the redis log file path
|
||||
_REDIS_LOG_FILE="/var/log/redis_$REDIS_PORT.log"
|
||||
read -p "Please select the redis log file name [$_REDIS_LOG_FILE] " REDIS_LOG_FILE
|
||||
if [ !"$REDIS_LOG_FILE" ] ; then
|
||||
if [ -z "$REDIS_LOG_FILE" ] ; then
|
||||
REDIS_LOG_FILE=$_REDIS_LOG_FILE
|
||||
echo "Selected default - $REDIS_LOG_FILE"
|
||||
fi
|
||||
@@ -78,55 +80,71 @@ fi
|
||||
#get the redis data directory
|
||||
_REDIS_DATA_DIR="/var/lib/redis/$REDIS_PORT"
|
||||
read -p "Please select the data directory for this instance [$_REDIS_DATA_DIR] " REDIS_DATA_DIR
|
||||
if [ !"$REDIS_DATA_DIR" ] ; then
|
||||
if [ -z "$REDIS_DATA_DIR" ] ; then
|
||||
REDIS_DATA_DIR=$_REDIS_DATA_DIR
|
||||
echo "Selected default - $REDIS_DATA_DIR"
|
||||
fi
|
||||
mkdir -p $REDIS_DATA_DIR || die "Could not create redis data directory"
|
||||
|
||||
#get the redis executable path
|
||||
_REDIS_EXECUTABLE=`which redis-server`
|
||||
_REDIS_EXECUTABLE=`command -v redis-server`
|
||||
read -p "Please select the redis executable path [$_REDIS_EXECUTABLE] " REDIS_EXECUTABLE
|
||||
if [ ! -f "$REDIS_EXECUTABLE" ] ; then
|
||||
if [ ! -x "$REDIS_EXECUTABLE" ] ; then
|
||||
REDIS_EXECUTABLE=$_REDIS_EXECUTABLE
|
||||
|
||||
if [ ! -f "$REDIS_EXECUTABLE" ] ; then
|
||||
|
||||
if [ ! -x "$REDIS_EXECUTABLE" ] ; then
|
||||
echo "Mmmmm... it seems like you don't have a redis executable. Did you run make install yet?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
|
||||
#render the tmplates
|
||||
TMP_FILE="/tmp/$REDIS_PORT.conf"
|
||||
DEFAULT_CONFIG="../redis.conf"
|
||||
INIT_TPL_FILE="./redis_init_script.tpl"
|
||||
INIT_SCRIPT_DEST="/etc/init.d/redis_$REDIS_PORT"
|
||||
PIDFILE="/var/run/redis_$REDIS_PORT.pid"
|
||||
|
||||
|
||||
|
||||
#check the default for redis cli
|
||||
CLI_EXEC=`which redis-cli`
|
||||
if [ ! "$CLI_EXEC" ] ; then
|
||||
CLI_EXEC=`command -v redis-cli`
|
||||
if [ -z "$CLI_EXEC" ] ; then
|
||||
CLI_EXEC=`dirname $REDIS_EXECUTABLE`"/redis-cli"
|
||||
fi
|
||||
|
||||
echo "Selected config:"
|
||||
|
||||
echo "Port : $REDIS_PORT"
|
||||
echo "Config file : $REDIS_CONFIG_FILE"
|
||||
echo "Log file : $REDIS_LOG_FILE"
|
||||
echo "Data dir : $REDIS_DATA_DIR"
|
||||
echo "Executable : $REDIS_EXECUTABLE"
|
||||
echo "Cli Executable : $CLI_EXEC"
|
||||
|
||||
read -p "Is this ok? Then press ENTER to go on or Ctrl-C to abort." _UNUSED_
|
||||
|
||||
mkdir -p `dirname "$REDIS_CONFIG_FILE"` || die "Could not create redis config directory"
|
||||
mkdir -p `dirname "$REDIS_LOG_FILE"` || die "Could not create redis log dir"
|
||||
mkdir -p "$REDIS_DATA_DIR" || die "Could not create redis data directory"
|
||||
|
||||
#render the templates
|
||||
TMP_FILE="/tmp/${REDIS_PORT}.conf"
|
||||
DEFAULT_CONFIG="${SCRIPTPATH}/../redis.conf"
|
||||
INIT_TPL_FILE="${SCRIPTPATH}/redis_init_script.tpl"
|
||||
INIT_SCRIPT_DEST="/etc/init.d/redis_${REDIS_PORT}"
|
||||
PIDFILE="/var/run/redis_${REDIS_PORT}.pid"
|
||||
|
||||
if [ ! -f "$DEFAULT_CONFIG" ]; then
|
||||
echo "Mmmmm... the default config is missing. Did you switch to the utils directory?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#Generate config file from the default config file as template
|
||||
#changing only the stuff we're controlling from this script
|
||||
echo "## Generated by install_server.sh ##" > $TMP_FILE
|
||||
|
||||
SED_EXPR="s#^port [0-9]{4}\$#port ${REDIS_PORT}#;\
|
||||
s#^logfile .+\$#logfile ${REDIS_LOG_FILE}#;\
|
||||
s#^dir .+\$#dir ${REDIS_DATA_DIR}#;\
|
||||
s#^pidfile .+\$#pidfile ${PIDFILE}#;\
|
||||
s#^daemonize no\$#daemonize yes#;"
|
||||
echo $SED_EXPR
|
||||
read -r SED_EXPR <<-EOF
|
||||
s#^port [0-9]{4}\$#port ${REDIS_PORT}#; \
|
||||
s#^logfile .+\$#logfile ${REDIS_LOG_FILE}#; \
|
||||
s#^dir .+\$#dir ${REDIS_DATA_DIR}#; \
|
||||
s#^pidfile .+\$#pidfile ${PIDFILE}#; \
|
||||
s#^daemonize no\$#daemonize yes#;
|
||||
EOF
|
||||
sed -r "$SED_EXPR" $DEFAULT_CONFIG >> $TMP_FILE
|
||||
|
||||
#cat $TPL_FILE | while read line; do eval "echo \"$line\"" >> $TMP_FILE; done
|
||||
cp -f $TMP_FILE $REDIS_CONFIG_FILE || exit 1
|
||||
cp $TMP_FILE $REDIS_CONFIG_FILE || die "Could not write redis config file $REDIS_CONFIG_FILE"
|
||||
|
||||
#Generate sample script from template file
|
||||
rm -f $TMP_FILE
|
||||
@@ -138,7 +156,7 @@ REDIS_INIT_HEADER=\
|
||||
#Configurations injected by install_server below....\n\n
|
||||
EXEC=$REDIS_EXECUTABLE\n
|
||||
CLIEXEC=$CLI_EXEC\n
|
||||
PIDFILE=$PIDFILE\n
|
||||
PIDFILE=\"$PIDFILE\"\n
|
||||
CONF=\"$REDIS_CONFIG_FILE\"\n\n
|
||||
REDISPORT=\"$REDIS_PORT\"\n\n
|
||||
###############\n\n"
|
||||
@@ -146,45 +164,82 @@ REDISPORT=\"$REDIS_PORT\"\n\n
|
||||
REDIS_CHKCONFIG_INFO=\
|
||||
"# REDHAT chkconfig header\n\n
|
||||
# chkconfig: - 58 74\n
|
||||
# description: redis_6379 is the redis daemon.\n
|
||||
# description: redis_${REDIS_PORT} is the redis daemon.\n
|
||||
### BEGIN INIT INFO\n
|
||||
# Provides: redis_6379\n
|
||||
# Required-Start: $network $local_fs $remote_fs\n
|
||||
# Required-Stop: $network $local_fs $remote_fs\n
|
||||
# Required-Start: \$network \$local_fs \$remote_fs\n
|
||||
# Required-Stop: \$network \$local_fs \$remote_fs\n
|
||||
# Default-Start: 2 3 4 5\n
|
||||
# Default-Stop: 0 1 6\n
|
||||
# Should-Start: $syslog $named\n
|
||||
# Should-Stop: $syslog $named\n
|
||||
# Short-Description: start and stop redis_6379\n
|
||||
# Should-Start: \$syslog \$named\n
|
||||
# Should-Stop: \$syslog \$named\n
|
||||
# Short-Description: start and stop redis_${REDIS_PORT}\n
|
||||
# Description: Redis daemon\n
|
||||
### END INIT INFO\n\n"
|
||||
|
||||
if [ !`which chkconfig` ] ; then
|
||||
#combine the header and the template (which is actually a static footer)
|
||||
echo $REDIS_INIT_HEADER > $TMP_FILE && cat $INIT_TPL_FILE >> $TMP_FILE || die "Could not write init script to $TMP_FILE"
|
||||
else
|
||||
if command -v chkconfig >/dev/null; then
|
||||
#if we're a box with chkconfig on it we want to include info for chkconfig
|
||||
echo -e $REDIS_INIT_HEADER $REDIS_CHKCONFIG_INFO > $TMP_FILE && cat $INIT_TPL_FILE >> $TMP_FILE || die "Could not write init script to $TMP_FILE"
|
||||
echo "$REDIS_INIT_HEADER" "$REDIS_CHKCONFIG_INFO" > $TMP_FILE && cat $INIT_TPL_FILE >> $TMP_FILE || die "Could not write init script to $TMP_FILE"
|
||||
else
|
||||
#combine the header and the template (which is actually a static footer)
|
||||
echo "$REDIS_INIT_HEADER" > $TMP_FILE && cat $INIT_TPL_FILE >> $TMP_FILE || die "Could not write init script to $TMP_FILE"
|
||||
fi
|
||||
|
||||
###
|
||||
# Generate sample script from template file
|
||||
# - No need to check which system we are on. The init info are comments and
|
||||
# do not interfere with update_rc.d systems. Additionally:
|
||||
# Ubuntu/debian by default does not come with chkconfig, but does issue a
|
||||
# warning if init info is not available.
|
||||
|
||||
cat > ${TMP_FILE} <<EOT
|
||||
#/bin/sh
|
||||
#Configurations injected by install_server below....
|
||||
|
||||
EXEC=$REDIS_EXECUTABLE
|
||||
CLIEXEC=$CLI_EXEC
|
||||
PIDFILE=$PIDFILE
|
||||
CONF="$REDIS_CONFIG_FILE"
|
||||
REDISPORT="$REDIS_PORT"
|
||||
###############
|
||||
# SysV Init Information
|
||||
# chkconfig: - 58 74
|
||||
# description: redis_${REDIS_PORT} is the redis daemon.
|
||||
### BEGIN INIT INFO
|
||||
# Provides: redis_${REDIS_PORT}
|
||||
# Required-Start: \$network \$local_fs \$remote_fs
|
||||
# Required-Stop: \$network \$local_fs \$remote_fs
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Should-Start: \$syslog \$named
|
||||
# Should-Stop: \$syslog \$named
|
||||
# Short-Description: start and stop redis_${REDIS_PORT}
|
||||
# Description: Redis daemon
|
||||
### END INIT INFO
|
||||
|
||||
EOT
|
||||
cat ${INIT_TPL_FILE} >> ${TMP_FILE}
|
||||
|
||||
#copy to /etc/init.d
|
||||
cp -f $TMP_FILE $INIT_SCRIPT_DEST && chmod +x $INIT_SCRIPT_DEST || die "Could not copy redis init script to $INIT_SCRIPT_DEST"
|
||||
cp $TMP_FILE $INIT_SCRIPT_DEST && \
|
||||
chmod +x $INIT_SCRIPT_DEST || die "Could not copy redis init script to $INIT_SCRIPT_DEST"
|
||||
echo "Copied $TMP_FILE => $INIT_SCRIPT_DEST"
|
||||
|
||||
#Install the service
|
||||
echo "Installing service..."
|
||||
if [ !`which chkconfig` ] ; then
|
||||
#if we're not a chkconfig box assume we're able to use update-rc.d
|
||||
update-rc.d redis_$REDIS_PORT defaults && echo "Success!"
|
||||
else
|
||||
if command -v chkconfig >/dev/null 2>&1; then
|
||||
# we're chkconfig, so lets add to chkconfig and put in runlevel 345
|
||||
chkconfig --add redis_$REDIS_PORT && echo "Successfully added to chkconfig!"
|
||||
chkconfig --level 345 redis_$REDIS_PORT on && echo "Successfully added to runlevels 345!"
|
||||
chkconfig --add redis_${REDIS_PORT} && echo "Successfully added to chkconfig!"
|
||||
chkconfig --level 345 redis_${REDIS_PORT} on && echo "Successfully added to runlevels 345!"
|
||||
elif command -v update-rc.d >/dev/null 2>&1; then
|
||||
#if we're not a chkconfig box assume we're able to use update-rc.d
|
||||
update-rc.d redis_${REDIS_PORT} defaults && echo "Success!"
|
||||
else
|
||||
echo "No supported init tool found."
|
||||
fi
|
||||
|
||||
|
||||
/etc/init.d/redis_$REDIS_PORT start || die "Failed starting service..."
|
||||
|
||||
#tada
|
||||
echo "Installation successful!"
|
||||
exit 0
|
||||
|
||||
|
||||
+26
-14
@@ -3,29 +3,41 @@ case "$1" in
|
||||
start)
|
||||
if [ -f $PIDFILE ]
|
||||
then
|
||||
echo "$PIDFILE exists, process is already running or crashed"
|
||||
echo "$PIDFILE exists, process is already running or crashed"
|
||||
else
|
||||
echo "Starting Redis server..."
|
||||
$EXEC $CONF
|
||||
echo "Starting Redis server..."
|
||||
$EXEC $CONF
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if [ ! -f $PIDFILE ]
|
||||
then
|
||||
echo "$PIDFILE does not exist, process is not running"
|
||||
echo "$PIDFILE does not exist, process is not running"
|
||||
else
|
||||
PID=$(cat $PIDFILE)
|
||||
echo "Stopping ..."
|
||||
$CLIEXEC -p $REDISPORT shutdown
|
||||
while [ -x /proc/${PID} ]
|
||||
do
|
||||
echo "Waiting for Redis to shutdown ..."
|
||||
sleep 1
|
||||
done
|
||||
echo "Redis stopped"
|
||||
PID=$(cat $PIDFILE)
|
||||
echo "Stopping ..."
|
||||
$CLIEXEC -p $REDISPORT shutdown
|
||||
while [ -x /proc/${PID} ]
|
||||
do
|
||||
echo "Waiting for Redis to shutdown ..."
|
||||
sleep 1
|
||||
done
|
||||
echo "Redis stopped"
|
||||
fi
|
||||
;;
|
||||
status)
|
||||
if [ ! -f $PIDFILE ]
|
||||
then
|
||||
echo 'Redis is not running'
|
||||
else
|
||||
echo "Redis is running ($(<$PIDFILE))"
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Please use start or stop as first argument"
|
||||
echo "Please use start, stop, restart or status as first argument"
|
||||
;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user