Compare commits

..
110 Commits
Author SHA1 Message Date
antirez 464fef9bf8 Redis 2.8.7. 2014-03-05 14:42:50 +01:00
antirez aab16ead92 Cast saveparams[].seconds to long for %ld format specifier. 2014-03-05 11:26:50 +01:00
antirez 86afc0706d Merge branch '2.8' of github.com:/antirez/redis into 2.8 2014-03-05 10:22:56 +01:00
antirez 55b8f6ec1c Document why we update peak memory in INFO. 2014-03-05 10:16:20 +01:00
Matt Stancliff 647a261465 Force INFO used_memory_peak to match peak memory
used_memory_peak only updates in serverCron every server.hz,
but Redis can use more memory and a user can request memory
INFO before used_memory_peak gets updated in the next
cron run.

This patch updates used_memory_peak to the current
memory usage if the current memory usage is higher
than the recorded used_memory_peak value.

(And it only calls zmalloc_used_memory() once instead of
twice as it was doing before.)
2014-03-05 10:16:16 +01:00
antirez 9c66cd91be Sentinel test: Makefile target added. 2014-03-05 10:16:12 +01:00
michael-grunder 6991792943 Improved bigkeys with progress, pipelining and summary
This commit reworks the redis-cli --bigkeys command to provide more
information about our progress as well as output summary information
when we're done.

 - We now show an approximate percentage completion as we go
 - Hiredis pipelining is used for TYPE and SIZE retreival
 - A summary of keyspace distribution and overall breakout at the end
2014-03-05 10:16:06 +01:00
antirez 7d65b7199a BITPOS fuzzy testing. 2014-03-05 10:16:02 +01:00
antirez c19cfde65d Basic BITPOS tests. 2014-03-05 10:15:55 +01:00
antirez 55f4b20f31 Sentinel test: set less time sensitive defaults.
This commit sets the failover timeout to 30 seconds instead of the 180
seconds default, and allows to reconfigure multiple slaves at the same
time.

This makes tests less sensible to timing, with the result that there are
less false positives due to normal behaviors that require time to
succeed or to be retried.

However the long term solution is probably some way in order to detect
when a test failed because of timing issues (for example split brain
during leader election) and retry it.
2014-03-05 10:15:32 +01:00
antirez 1606978a0b Sentinel: more aggressive failover start desynchronization.
Sentinel needs to avoid split brain conditions due to multiple sentinels
trying to get voted at the exact same time.

So far some desynchronization was provided by fluctuating server.hz,
that is the frequency of the timer function call. However the
desynchonization provided in this way was not enough when using many
Sentinel instances, especially when a large quorum value is used in
order to force a greater degree of agreement (more than N/2+1).

It was verified that it was likely to trigger a split brain
condition, forcing the system to try again after a timeout.
Usually the system will succeed after a few retries, but this is not
optimal.

This commit desynchronizes instances in a more effective way to make it
likely that the first attempt will be successful.
2014-03-05 10:15:32 +01:00
antirez 6200191943 CONFIG REWRITE should be logged at WARNING level. 2014-03-05 10:15:32 +01:00
antirez 2af2173a60 Sentinel test: debugging console improved. 2014-03-05 10:15:32 +01:00
antirez 1dc1e31c2b Sentinel test: initial debugging console. 2014-03-05 10:15:32 +01:00
antirez b132121a71 Sentinel test: be more patient in create_redis_master_slave_cluster. 2014-03-05 10:15:32 +01:00
antirez d1d706c923 Sentiel test: add test start time in output. 2014-03-05 10:15:32 +01:00
antirez c99cd2fd40 Sentinel test: use 1000 as retry in initial 00 unit test. 2014-03-05 10:15:32 +01:00
antirez f33d4a6c24 Sentinel test: initial tests in 03 unit. 2014-03-05 10:15:32 +01:00
antirez c2d99d49b5 Sentinel test: foreach_instance_id now supports 'continue'. 2014-03-05 10:15:32 +01:00
antirez e40f8378bd Sentienl test: fixed typo in unit 03 top comment. 2014-03-05 10:15:32 +01:00
antirez a0335e18ba Sentinel test: Makefile target added. 2014-02-28 16:00:17 +01:00
antirez 950cb76e7b BITPOS fuzzy testing. 2014-02-27 15:58:34 +01:00
antirez 42e3630d77 Basic BITPOS tests. 2014-02-27 15:58:30 +01:00
antirez 1892b56224 warnigns -> warnings in redisBitpos(). 2014-02-27 15:58:27 +01:00
antirez 2c8036f7b2 More consistent BITPOS behavior with bit=0 and ranges.
With the new behavior it is possible to specify just the start in the
range (the end will be assumed to be the first byte), or it is possible
to specify both start and end.

This is useful to change the behavior of the command when looking for
zeros inside a string.

1) If the user specifies both start and end, and no 0 is found inside
   the range, the command returns -1.

2) If instead no range is specified, or just the start is given, even
   if in the actual string no 0 bit is found, the command returns the
   first bit on the right after the end of the string.

So for example if the string stored at key foo is "\xff\xff":

    BITPOS foo (returns 16)
    BITPOS foo 0 -1 (returns -1)
    BITPOS foo 0 (returns 16)

The idea is that when no end is given the user is just looking for the
first bit that is zero and can be set to 1 with SETBIT, as it is
"available". Instead when a specific range is given, we just look for a
zero within the boundaries of the range.
2014-02-27 15:58:21 +01:00
antirez 3294f74fef Initial implementation of BITPOS.
It appears to work but more stress testing, and both unit tests and
fuzzy testing, is needed in order to ensure the implementation is sane.
2014-02-27 15:58:14 +01:00
antirez 82d2e295b8 Added two more BITCOUNT tests stressing misaligned access. 2014-02-27 15:57:56 +01:00
antirez a3eb3f9c3b BITCOUNT fuzzy test with random start/end added.
It was verified in practice that this test is able to stress much more
the implementation by introducing errors that were only trivially to
detect with different offsets but impossible to detect starting always
at zero and counting bits the full length of the string.
2014-02-27 15:57:53 +01:00
antirez 30a92b6c76 Fix misaligned word access in redisPopcount(). 2014-02-27 15:57:49 +01:00
antirez c1cc28f230 warnigns -> warnings in redisBitpos(). 2014-02-27 15:55:56 +01:00
antirez d79f9ebdb5 More consistent BITPOS behavior with bit=0 and ranges.
With the new behavior it is possible to specify just the start in the
range (the end will be assumed to be the first byte), or it is possible
to specify both start and end.

This is useful to change the behavior of the command when looking for
zeros inside a string.

1) If the user specifies both start and end, and no 0 is found inside
   the range, the command returns -1.

2) If instead no range is specified, or just the start is given, even
   if in the actual string no 0 bit is found, the command returns the
   first bit on the right after the end of the string.

So for example if the string stored at key foo is "\xff\xff":

    BITPOS foo (returns 16)
    BITPOS foo 0 -1 (returns -1)
    BITPOS foo 0 (returns 16)

The idea is that when no end is given the user is just looking for the
first bit that is zero and can be set to 1 with SETBIT, as it is
"available". Instead when a specific range is given, we just look for a
zero within the boundaries of the range.
2014-02-27 15:55:47 +01:00
antirez 25e2791ec3 Initial implementation of BITPOS.
It appears to work but more stress testing, and both unit tests and
fuzzy testing, is needed in order to ensure the implementation is sane.
2014-02-27 15:54:31 +01:00
antirez c955b47d62 Added two more BITCOUNT tests stressing misaligned access. 2014-02-27 15:53:40 +01:00
antirez bfe4c560e6 BITCOUNT fuzzy test with random start/end added.
It was verified in practice that this test is able to stress much more
the implementation by introducing errors that were only trivially to
detect with different offsets but impossible to detect starting always
at zero and counting bits the full length of the string.
2014-02-27 15:53:40 +01:00
antirez 580ed0768f Fix misaligned word access in redisPopcount(). 2014-02-27 15:53:40 +01:00
antirez d90e3e3a70 Sentinel test: add stub for unit 04. 2014-02-25 15:55:37 +01:00
antirez 89d95ec2e7 Sentinel test: added TODO items in 02 unit. 2014-02-25 15:55:37 +01:00
antirez 65194d11c3 redis-cli: also remove useless uint8_t. 2014-02-25 15:09:15 +01:00
antirez 16c2189d4e redis-cli: don't use uint64_t where actually not needed.
The computation is just something to take the CPU busy, no need to use a
specific type. Since stdint.h was not included this prevented
compilation on certain systems.
2014-02-25 15:09:15 +01:00
antirez 1b2bcd4210 redis-cli: check argument existence for --pattern. 2014-02-25 15:09:15 +01:00
antirez 6ec3dc2c42 redis-cli: --intrinsic-latency run mode added. 2014-02-25 15:09:15 +01:00
antirez 02ae6cb01a redis-cli: added comments to split program in parts. 2014-02-25 15:09:15 +01:00
antirez fe05112029 Sentinel test: check role at end of unit 01. 2014-02-25 15:08:41 +01:00
michael-grunder cf76fdd04f Update --bigkeys to use SCAN
This commit changes the findBigKeys() function in redis-cli.c to use the new
SCAN command for iterating the keyspace, rather than RANDOMKEY.  Because we
can know when we're done using SCAN, it will exit after exhausting the keyspace.
2014-02-25 15:08:38 +01:00
antirez 9e34e296cf Sentinel test: kill masters instead of using DEBUG SLEEP in all tests. 2014-02-25 15:08:34 +01:00
antirez dfacfafc2a Sentinel test: restart instances left killed by previous unit.
An unit can abort in the middle for an error. The next unit should not
assume that the instances are in a clean state, and must restart what
was left killed.
2014-02-25 10:30:35 +01:00
antirez 2fab983c69 Sentinel test: jump to next unit on test failure.
Sentinel tests are designed to be dependent on the previous tests in the
same unit, so usually we can't continue with the next test in the same
unit if a previous test failed.
2014-02-25 10:30:35 +01:00
antirez a6d560db64 Sentinel test: test majority crashing Sentinels.
The test was previously performed by removing the master from the
Sentinel monitored masters. The test with the Sentinels crashed is
more similar to real-world partitions / failures.
2014-02-25 10:30:35 +01:00
antirez 3f16fee39a Sentinel test: restart_instance should refresh pid attrib.
Also kill_instance was modified to warn when a test will try to kill the
same instance multiple times for error.
2014-02-25 10:30:35 +01:00
antirez e8e2ac072c Sentinel test: more stuff mored from 00-base to init.
The area a number of mandatory tests to craete a stable setup for
testing that is not too sensitive to timing issues. All those tests
moved to includes/init-tests, and marked as (init).
2014-02-25 10:30:35 +01:00
antirez 85fa77e02e Sentinel: log quorum with +monitor event. 2014-02-25 10:30:35 +01:00
antirez 2d48d7c2fc Sentinel test: removed useless code to set SDOWN timeout.
The new common initialization code used to start a new unit already set
the timeout to 2000 milliseconds.
2014-02-25 10:30:35 +01:00
antirez 6e6106797e Sentinel: generate +monitor events at startup. 2014-02-25 10:30:35 +01:00
antirez 96162c0c58 Sentinel: log +monitor and +set events.
Now that we have a runtime configuration system, it is very important to
be able to log how the Sentinel configuration changes over time because
of API calls.
2014-02-25 10:30:35 +01:00
antirez 39eacde136 Sentinel: added missing exit(1) after checking for config file. 2014-02-25 10:30:35 +01:00
antirez 28aa15a6ea Sentinel test: tmp dir and gitignore added. 2014-02-25 10:30:35 +01:00
antirez 3917224f7c Sentinel test: minor fixes to --pause-on-error. 2014-02-25 10:30:35 +01:00
antirez 001ab29915 Sentinel test: --pause-on-error option added.
Pause the test with running instances available for state inspection on
error.
2014-02-25 10:30:35 +01:00
antirez d31a394148 Sentinel test: added empty units to fill later. 2014-02-25 10:30:35 +01:00
antirez d83ab8f9d4 Sentinel: IDONTKNOW error removed.
This error was conceived for the older version of Sentinel that worked
via master redirection and that was not able to get configuration
updates from other Sentinels via the Pub/Sub channel of masters or
slaves.

This reply does not make sense today, every Sentinel should reply with
the best information it has currently. The error will make even more
sense in the future since the plan is to allow Sentinels to update the
configuration of other Sentinels via gossip with a direct chat without
the prerequisite that they have at least a monitored instance in common.
2014-02-25 10:30:35 +01:00
antirez 1dfab3d107 Sentinel test: framework improved and conf-update unit added.
It is now possible to kill and restart sentinel or redis instances for
more real-world testing.

The 01 unit tests the capability of Sentinel to update the configuration
of Sentinels rejoining the cluster, however the test is pretty trivial
and more tests should be added.
2014-02-25 10:30:35 +01:00
antirez aee165e084 Sentinel test: move init tests as includes.
Most units will start with these two basic tests to create an
environment where the real tests are ran.
2014-02-25 10:30:35 +01:00
antirez 73b290cc5b Sentinel test: ability to run just a subset of test files. 2014-02-25 10:30:35 +01:00
Matt Stancliff 3ced78dc6c Auto-enter slaveMode when SYNC from redis-cli
If someone asks for SYNC or PSYNC from redis-cli,
automatically enter slaveMode (as if they ran
redis-cli --slave) and continue printing the replication
stream until either they Ctrl-C or the master gets disconnected.
2014-02-25 10:30:35 +01:00
antirez 6441a41f94 Sentinel: report instances role switch events.
This is useful mostly for debugging of issues.
2014-02-20 12:28:03 +01:00
antirez 05dde71919 Sentinel test: some reliability fixes to 00-base tests. 2014-02-20 12:28:03 +01:00
antirez c499d82f28 Sentinel test: check that role matches at end of 00-base. 2014-02-20 12:28:03 +01:00
antirez c01aff1234 Sentinel test: ODOWN and agreement. 2014-02-20 12:28:03 +01:00
antirez 56cb037a86 Sentinel test: check reconfig of slaves and old master. 2014-02-20 12:28:03 +01:00
antirez d4edf63bc4 Sentinel test: basic failover tested. Framework improvements. 2014-02-20 12:28:02 +01:00
antirez 722d4f0c41 Sentinel test: basic tests for MONITOR and auto-discovery. 2014-02-20 12:28:02 +01:00
antirez b26660e130 Sentinel test: info fields, master-slave setup, fixes. 2014-02-20 12:28:02 +01:00
antirez ac5fd5a519 Prefix test file names with numbers to force exec order. 2014-02-20 12:28:02 +01:00
antirez 890559ea1c Sentinel test: provide basic commands to access instances. 2014-02-20 12:28:02 +01:00
antirez 905c55d5ae Sentinel: SENTINEL_SLAVE_RECONF_RETRY_PERIOD -> RECONF_TIMEOUT
Rename define to match the new meaning.
2014-02-18 10:30:29 +01:00
antirez 1b345ec3f0 Sentinel: fix slave promotion timeout.
If we can't reconfigure a slave in time during failover, go forward as
anyway the slave will be fixed by Sentinels in the future, once they
detect it is misconfigured.

Otherwise a failover in progress may never terminate if for some reason
the slave is uncapable to sync with the master while at the same time
it is not disconnected.
2014-02-18 10:30:28 +01:00
antirez 351e790032 Sentinel: initial testing framework.
Nothing tested at all so far... Just the infrastructure spawning N
Sentinels and N Redis instances that the test will use again and again.
2014-02-17 17:38:58 +01:00
antirez f71c7de88e Test: colorstr moved to util.tcl. 2014-02-17 17:38:54 +01:00
antirez 0886c98fef Test: code to test server availability refactored.
Some inline test moved into server_is_up procedure.
Also find_available_port was moved into util since it is going
to be used for the Sentinel test as well.
2014-02-17 12:29:54 +01:00
antirez 4237f14a8a Get absoulte config file path before processig 'dir'.
The code tried to obtain the configuration file absolute path after
processing the configuration file. However if config file was a relative
path and a "dir" statement was processed reading the config, the absolute
path obtained was wrong.

With this fix the absolute path is obtained before processing the
configuration while the server is still in the original directory where
it was executed.
2014-02-17 12:14:19 +01:00
antirez 5efee4f0bb Sentinel: better specify startup errors due to config file.
Now it logs the file name if it is not accessible. Also there is a
different error for the missing config file case, and for the non
writable file case.
2014-02-17 12:10:12 +01:00
antirez 1d4d9e7b14 Redis 2.8.6. 2014-02-13 15:43:40 +01:00
antirez 85492dcfef Update cached time in rdbLoad() callback.
server.unixtime and server.mstime are cached less precise timestamps
that we use every time we don't need an accurate time representation and
a syscall would be too slow for the number of calls we require.

Such an example is the initialization and update process of the last
interaction time with the client, that is used for timeouts.

However rdbLoad() can take some time to load the DB, but at the same
time it did not updated the time during DB loading. This resulted in the
bug described in issue #1535, where in the replication process the slave
loads the DB, creates the redisClient representation of its master, but
the timestamp is so old that the master, under certain conditions, is
sensed as already "timed out".

Thanks to @yoav-steinberg and Redis Labs Inc for the bug report and
analysis.
2014-02-13 15:13:35 +01:00
antirez eb10bb31ac Log when CONFIG REWRITE goes bad. 2014-02-13 14:33:50 +01:00
antirez ebdb37cea7 Test: regression for issue #1549.
It was verified that reverting the commit that fixes the bug, the test
no longer passes.
2014-02-13 12:27:08 +01:00
antirez 9b73a274ac Fix script cache bug in the scripting engine.
This commit fixes a serious Lua scripting replication issue, described
by Github issue #1549. The root cause of the problem is that scripts
were put inside the script cache, assuming that slaves and AOF already
contained it, even if the scripts sometimes produced no changes in the
data set, and were not actaully propagated to AOF/slaves.

Example:

    eval "if tonumber(KEYS[1]) > 0 then redis.call('incr', 'x') end" 1 0

Then:

    evalsha <sha1 step 1 script> 1 0

At this step sha1 of the script is added to the replication script cache
(the script is marked as known to the slaves) and EVALSHA command is
transformed to EVAL. However it is not dirty (there is no changes to db),
so it is not propagated to the slaves. Then the script is called again:

    evalsha <sha1 step 1 script> 1 1

At this step master checks that the script already exists in the
replication script cache and doesn't transform it to EVAL command. It is
dirty and propagated to the slaves, but they fail to evaluate the script
as they don't have it in the script cache.

The fix is trivial and just uses the new API to force the propagation of
the executed command regardless of the dirty state of the data set.

Thank you to @minus-infinity on Github for finding the issue,
understanding the root cause, and fixing it.
2014-02-13 12:16:34 +01:00
antirez 96973a7c33 AOF write error: retry with a frequency of 1 hz. 2014-02-12 16:57:17 +01:00
antirez fadbbdd3f4 AOF: don't abort on write errors unless fsync is 'always'.
A system similar to the RDB write error handling is used, in which when
we can't write to the AOF file, writes are no longer accepted until we
are able to write again.

For fsync == always we still abort on errors since there is currently no
easy way to avoid replying with success to the user otherwise, and this
would violate the contract with the user of only acknowledging data
already secured on disk.
2014-02-12 16:57:13 +01:00
antirez 688d32e16b Don't count time to feed MONITORs in SLOWLOG. 2014-02-07 18:29:26 +01:00
antirez 3e4968339b Sentinel: allow SHUTDOWN command in Sentinel mode. 2014-02-07 11:22:30 +01:00
antirez 301a0cfc69 Check for EAGAIN in sendBulkToSlave().
Sometime an osx master with a Linux server over a slow link caused
a strange error where osx called the writable function for
the socket but actually apparently there was no room in the socket
buffer to accept the write: write(2) call returned an EAGAIN error,
that was not checked, so we considered write(2) == 0 always as a connection
reset, which was unfortunate since the bulk transfer has to start again.

Also more errors are logged with the WARNING level in the same code path
now.
2014-02-05 16:41:04 +01:00
antirez 4e809a9a19 Redis 2.8.5. 2014-02-04 11:17:21 +01:00
antirez ddcf160309 Move mstime_t define outside sentinel.c.
The define is now used in other parts of Redis 2.8 tree instead of long
long.

A nice side effect is that now 2.8 and unstable sentinel.c files are
identical as it should be.
2014-02-03 16:34:46 +01:00
antirez c5bc592650 Scripting: expire keys in scripts only at first access.
Keys expiring in the middle of the execution of Lua scripts are to
create inconsistencies in masters and / or AOF files. See the following
example:

    if redis.call("exists",KEYS[1]) == 1
    then
        redis.call("incr","mycounter")
    end

    if redis.call("exists",KEYS[1]) == 1
    then
        return redis.call("incr","mycounter")
    end

The script executes two times the same *if key exists then incrementcounter*
logic. However the two executions will work differently in the master and
the slaves, provided some unlucky timing happens.

In the master the first time the key may still exist, while the second time
the key may no longer exist. This will result in the key incremented just one
time. However as a side effect the master will generate a synthetic
`DEL` command in the replication channel in order to force the slaves to
expire the key (given that key expiration is master-driven).

When the same script will run in the slave, the key will no longer be
there, so the script will not increment the key.

The key idea used to implement the expire-at-first-lookup semantics was
provided by Marc Gravell.
2014-02-03 16:29:25 +01:00
antirez 5201ca0ca1 Allow CONFIG and SHUTDOWN while in stale-slave state. 2014-02-03 15:51:07 +01:00
antirez 3da5cbe5bb Scripting: use mstime() and mstime_t for lua_time_start.
server.lua_time_start is expressed in milliseconds. Use mstime_t instead
of long long, and populate it with mstime() instead of ustime()/1000.

Functionally identical but more natural.
2014-02-03 15:46:47 +01:00
PatrickJS 0be31e2d22 update copyright year 2014-02-03 11:19:25 +01:00
antirez 66304fb122 Test: fixed osx msg passing issue in testing framework.
The Redis test uses a server-clients model in order to parallelize the
execution of different tests. However in recent versions of osx not
setting the channel to a binary encoding caused issues even if AFAIK no
binary data is really sent via this channel. However now the channels
are deliberately set to a binary encoding and this solves the issue.

The exact issue was the test not terminating and giving the impression
of running forever, since test clients or servers were unable to
exchange the messages to continue.
2014-01-31 16:25:13 +01:00
antirez 1406112ec4 Redis.conf comment about tcp-backlog option improved. 2014-01-31 15:03:25 +01:00
antirez 917b851491 Option "backlog" renamed "tcp-backlog".
This is especially important since we already have a concept of backlog
(the replication backlog).
2014-01-31 15:03:22 +01:00
Nenad Merdanovic 8dda9dbef0 Add support for listen(2) backlog definition
In high RPS environments, the default listen backlog is not sufficient, so
giving users the power to configure it is the right approach, especially
since it requires only minor modifications to the code.
2014-01-31 15:03:19 +01:00
antirez f0652c37a5 Sentinel: check arity for SENTINEL MASTER command.
This fixes issue #1530.
2014-01-31 10:14:07 +01:00
antirez a2c9d38a9f SENTINEL SET master quorum implemented. 2014-01-28 11:23:51 +01:00
antirez 89a731b3aa Fixed inverted if condition in MISCONF error code path. 2014-01-28 10:10:56 +01:00
antirez ecfefde760 Don't log MONITOR clients as disconnecting slaves. 2014-01-25 11:54:02 +01:00
antirez 43503ae5d6 redis-cli --help output improved with --scan and periods. 2014-01-22 12:08:08 +01:00
antirez b9c84d4aef redis-cli: support for --scan option. 2014-01-22 12:08:04 +01:00
antirez 248e916550 Use fflush() before fsync() in rio.c.
Incremental flushing in rio.c is only used to avoid huge kernel buffers
synched to slow disks creating big latency spikes, so this fix has no
durability implications, however it is certainly more correct to make
sure that the FILE buffers are flushed to the kernel before calling
fsync on the file descriptor.

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