Compare commits

...
205 Commits
Author SHA1 Message Date
antirez 9545957a67 Scripting: native lua.exists() implementation.
This was a test to check how much faster native implementation would be.
In initial tests it does not look like is this huge win.

After all this is at least in part obvious. Now scripting.c tries to
avoid allocations of argument vectors, and turning ":1" accumulated in
the client buffer into a Lua type is a fast operation.
2015-03-06 16:24:44 -08:00
antirez 2bf27c3361 Transactions: Implementation of the IF command 2015-03-06 10:35:45 -08:00
antirez 509a6cc1e8 Fix iterator for issue #2438.
Itereator misuse due to analyzeLatencyForEvent() accessing the
dictionary during the iteration, without the iterator being
reclared as safe.
2015-03-04 11:48:19 -08:00
antirez c77081a45a Migrate: replace conditional with pre-computed value. 2015-02-27 22:33:54 +01:00
antirez 4f56f035a7 String: use new sdigits10() API in stringObjectLen().
Should be much faster, and regardless, the code is more obvious now
compared to generating a string just to get the return value of the
ll2stirng() function.
2015-02-27 16:09:17 +01:00
antirez 0e5e8ca9e6 Utils: Include stdint.h and fix signess in sdigits10(). 2015-02-27 16:03:02 +01:00
antirez 084a59c324 Test: HSTRLEN stress test of corner cases.
Main point here is to correctly report LLONG_MIN length, since to take
the absolute value we need care in sdigits10().
2015-02-27 15:44:44 +01:00
antirez 0ace1e6d04 Hash: HSTRLEN crash fixed when getting len of int-encoded value 2015-02-27 15:37:04 +01:00
antirez 4e54b85a19 Hash: HSTRLEN (was HVSTRLEN) improved.
1. HVSTRLEN -> HSTRLEN. It's unlikely one needs the length of the key,
   not clear how the API would work (by value does not make sense) and
   there will be better names anyway.
2. Default is to return 0 when field is missing.
3. Default is to return 0 when key is missing.
4. The implementation was slower than needed, and produced unnecessary COW.

Related issue #2415.
2015-02-27 15:31:55 +01:00
antirez 8855b8161f Merge branch 'unstable' of github.com:/antirez/redis into unstable 2015-02-27 15:24:25 +01:00
Salvatore Sanfilippo b49c00a79c Merge pull request #2415 from landmime/unstable
added a new hvstrlen command
2015-02-27 15:24:04 +01:00
antirez d8f8b0575f Hash: API to get value string len by field name. 2015-02-27 15:22:49 +01:00
antirez c95507881a Utils: added function to get radix 10 string length of signed integer. 2015-02-27 15:22:10 +01:00
antirez 7e6b4ea67b server.current_client fix and minor refactoring.
Thanks to @codeslinger (Toby DiPasquale) for identifying the issue.

Related to issue #2409.
2015-02-27 14:17:46 +01:00
antirez 832b0c7cce Improvements to PR #2425
1. Remove useless "cs" initialization.
2. Add a "select" var to capture a condition checked multiple times.
3. Avoid duplication of the same if (!copy) conditional.
4. Don't increment dirty if copy is given (no deletion is performed),
   otherwise we propagate MIGRATE when not needed.
2015-02-26 10:27:56 +01:00
Tommy Wangandantirez 7fda935ad3 Add last_dbid to migrateCachedSocket to avoid redundant SELECT
Avoid redundant SELECT calls when continuously migrating keys to
the same dbid within a target Redis instance.
2015-02-26 10:18:43 +01:00
antirez cc0d339bd1 utils/hashtable/rehashing.c test updated to use new API. 2015-02-25 13:02:04 +01:00
Salvatore Sanfilippo 126462ee36 Merge pull request #2405 from sisirkoppaka/unstable
rehashing.c: Fix compile error originating from SPOP rewrite
2015-02-25 12:52:23 +01:00
antirez 74354ceef5 Test: fixes a few tests after basic unit refactoring. 2015-02-25 10:37:52 +01:00
antirez 27c30b0e84 Cast sentlen to int before comparison wit bufpos.
This is safe since bufpos is small, inside the range of the local
client buffer.
2015-02-25 10:33:37 +01:00
antirez 954151f62b Test: split basic unit into string, incr, keyspace units. 2015-02-25 10:33:29 +01:00
Salvatore Sanfilippo 9454f7b3db Merge pull request #2050 from mattsta/bitops-no-overalloc
Bitops: Stop overallocating storage space on set
2015-02-25 10:18:07 +01:00
Salvatore Sanfilippo e00cb78f67 Merge pull request #2054 from mattsta/fix-set-sentinel-quorum
Sentinel: Add initial quorum bounds check
2015-02-25 10:09:40 +01:00
Matt Stancliffandantirez 47ab570441 Fix types broken by previous type cleanup
Revert some size_t back to off_t
Set reply_bytes needs to 64 bits everywhere
Revert bufpos to int since it's a max of 16k into buf[]
2015-02-24 17:39:59 +01:00
Salvatore Sanfilippo d83c810265 Merge pull request #2301 from mattsta/fix/lengths
Improve type correctness
2015-02-24 17:22:53 +01:00
Salvatore Sanfilippo 46bd13b806 Merge pull request #1966 from mattsta/fix-sentinel-info
Sentinel: Improve INFO command behavior
2015-02-24 17:20:09 +01:00
Salvatore Sanfilippo 3689a0582b Merge pull request #2273 from mattsta/improve/consistency/INFO/memory
Improve consistency of INFO MEMORY
2015-02-24 17:19:17 +01:00
Salvatore Sanfilippo 5ff00ac8c1 Merge pull request #2401 from mattsta/fix/quicklist/pop
Fix quicklist Pop()
2015-02-24 17:13:39 +01:00
antirez fc365a3a00 Change RENAME behavior when src and dst keys are the same.
Fixes issue #2392.
2015-02-23 11:24:24 +01:00
Jason Roth db9461e466 added a new hvstrlen command
the hvstrlen command returns the length of a hash field value
2015-02-21 15:01:18 +00:00
antirez 0aa5acc8f3 Give obvious instructions on how to bind all interfaces. 2015-02-19 22:47:32 +01:00
antirez 1024664247 Example redis.conf binds just to 127.0.0.1.
It's hard to pick a good approach here. A few arguments:

1) There are many exposed instances on the internet.
2) Changing the default when "bind" is not given is very dangerous,
   after an upgrade the server changes a fundamental behavior.
3) Usually Redis, when used in a proper way, will be protected *and*
   accessed often from other computers, so this new default is likely
   not what most people want.
4) However if users end with this default, they are using the example
   redis.conf: likely they are reading what is inside, and they'll see
   the warning.
2015-02-19 22:42:31 +01:00
Sisir Koppaka acb933a747 rehashing.c: Fix compile error originating from SPOP rewrite 2015-02-18 08:16:41 -05:00
Matt Stancliff 552e5908bb Fix quicklist tests for Pop()
Now the tests actually compare return values instead of just
verifying _something_ got returned.
2015-02-17 11:33:08 -05:00
John DoeandMatt Stancliff 395e11255c Fix quicklist Pop() result
Closes #2398
2015-02-17 11:31:08 -05:00
antirez cfe5eaaed6 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2015-02-14 17:19:37 +01:00
antirez 41a1fb8186 Version bumped to 3.1.999 (3.2 alpha versions). 2015-02-14 17:19:17 +01:00
Salvatore Sanfilippo e5760987e9 Merge pull request #2393 from badboy/readme-fix
Readme fix
2015-02-13 15:55:44 +01:00
Jan-Erik Rediger 07a89654da Link files in README 2015-02-13 15:51:38 +01:00
antirez 9795ad1bc2 Fix README links again.
Apparently no refernece-style links supported in Github markdown.
2015-02-13 15:39:44 +01:00
Salvatore Sanfilippo 78c44ca646 Fix links into README. 2015-02-13 15:37:19 +01:00
antirez f916a589b5 README: give idea about what Redis is. 2015-02-13 15:34:22 +01:00
Jan-Erik Rediger 2576864ba7 Use code tags where necessary, escape special markers 2015-02-13 15:10:20 +01:00
antirez c414db2f98 A few more README markdown conversions. 2015-02-13 14:57:08 +01:00
antirez 097b3079b4 Fix README minor grammar issues. 2015-02-13 14:55:53 +01:00
antirez cac9a900cb README -> README.md. 2015-02-13 14:55:09 +01:00
antirez f638f045ce redis-cli --latency-dist: one gray more, and --mono support. 2015-02-13 14:38:21 +01:00
antirez 3101d2bffb redis-cli --latency-dist, hopefully better palette.
Less grays: more readable palette since usually we have a non linear
distribution of percentages and very near gray tones are hard to take
apart. Final part of the palette is gradient from yellow to red. The red
part is hardly reached because of usual distribution of latencies, but
shows up mainly when latencies are very high because of the logarithmic
scale, this is coherent to what people expect: red = bad.
2015-02-13 12:52:46 +01:00
Salvatore Sanfilippo b1de179c01 Merge pull request #2390 from sawaman/fix-typo
Unify to uppercase the headline
2015-02-12 17:07:04 +01:00
antirez cd4e0f43df Extend memory unit support in CONFIG SET.
Related to PR #2357.
2015-02-12 16:51:20 +01:00
Salvatore Sanfilippo 866b3fc0ec Merge pull request #2357 from lamby/config-set-maxmemory-units
Support "1G" etc. units in CONFIG SET maxmemory
2015-02-12 16:42:09 +01:00
antirez 29b54db320 Better memtoll() error checking.
Related to PR #2357.
2015-02-12 16:40:46 +01:00
Masahiko Sawada 2b3eba056c Unify to uppercase the headline 2015-02-12 21:20:22 +09:00
antirez a1d9ec0d44 SPOP replication tests. 2015-02-11 10:52:28 +01:00
antirez dffbbb5a78 SPOP explicit tests for the three different code paths. 2015-02-11 10:52:28 +01:00
antirez a37d0f8b48 SPOP with count: fix replication for code path #3. 2015-02-11 10:52:28 +01:00
antirez 9feee428f2 SPOP: reimplemented for speed and better distribution.
The old version of SPOP with "count" argument used an API call of dict.c
which was actually designed for a different goal, and was not capable of
good distribution. We follow a different three-cases approach optimized
for different ratiion between sets and requested number of elements.

The implementation is simpler and allowed the removal of a large amount
of code.
2015-02-11 10:52:28 +01:00
antirez 55003f7a11 alsoPropagate: handle REDIS_CALL_PROPAGATE and AOF loading. 2015-02-11 10:52:28 +01:00
antirez cc7f0434b5 Change alsoPropagate() behavior to make it more usable.
Now the API automatically creates its argv copy and increment ref count
of passed objects.
2015-02-11 10:52:27 +01:00
antirez 6b5922dcbb SPOP with count: initial fixes to the implementation.
Severan problems are addressed but still a few missing.
Since replication of this command was more complex than others since it
needs to replicate multiple SREM commands, an old API able to do this
was reused (it was taken inside the implementation since it was pretty
obvious soon or later that would be useful). The API was improved a bit
so that now a command may opt-out for the standard command replication
when the server.dirty counter is incremented, in order to "manually"
replicate what it wants.
2015-02-11 10:52:27 +01:00
antirez 585d1a60bf Separate latency monitoring of eviction loop and eviction DELs. 2015-02-11 10:52:27 +01:00
antirez 8ddc14523f dict.c: reset emptylen when bucket is not empty.
Fixed by @oranagra, thank you.
2015-02-11 10:52:27 +01:00
antirez edda00b902 dict.c Rehashing visualization code snippet added to utils. 2015-02-11 10:52:27 +01:00
antirez 05841a6386 redis-cli --stat: show LOAD when loading. 2015-02-11 10:52:27 +01:00
antirez 8696874d75 Remove optional single-key path from evictionPoolPopulate(). 2015-02-11 10:52:27 +01:00
antirez 5792a217f8 dict.c: add dictGetSomeKeys(), specialized for eviction. 2015-02-11 10:52:27 +01:00
antirez f25fdd6246 dict.c: avoid code repetition in dictRehash().
Avoid code repetition introduced with PR #2367, also fixes the return
value to always return 0 if there is nothing more to rehash.
2015-02-11 10:52:27 +01:00
Sun Heandantirez 2385630d0d dict.c/dictRehash: check again to update 2015-02-11 10:52:26 +01:00
antirez 4f427bc298 dict.c: don't try buckets that are empty for sure in dictGetRandomKey().
This is very similar to the optimization applied to dictGetRandomKeys,
but applied to the single key variant.

Related to issue #2306.
2015-02-11 10:52:26 +01:00
antirez 1bcf67a75f dict.c: dictGetRandomKeys() optimization for big->small table case.
Related to issue #2306.
2015-02-11 10:52:26 +01:00
antirez 88cd9ebc09 dict.c: dictGetRandomKeys() visit pattern optimization.
We use the invariant that the original table ht[0] is never populated up
to the index before the current rehashing index.

Related to issue #2306.
2015-02-11 10:52:26 +01:00
antirez cd0fcf11e7 dict.c: put a bound to max work dictRehash() call can do.
Related to issue #2306.
2015-02-11 10:52:26 +01:00
antirez 777020839a dict.c: prevent useless resize to same size.
Related to issue #2306.
2015-02-11 10:52:26 +01:00
antirez 170e41464d Less blocking dictGetRandomKeys().
Related to issue #2306.
2015-02-11 10:52:26 +01:00
antirez 5e3dcc522b Faster memory efficiency test.
This test on Linux was extremely slow, since in Tcl we can't enable
easily tcp-nodelay, so the busy loop used to take *a lot* with bigger
writes. Fixed using pipelining.
2015-02-10 14:47:45 +01:00
antirez e1fce55237 Added regression test for issue #2371. 2015-02-10 14:40:27 +01:00
antirez 9e9abe29fe HAVE_SYNC_FILE_RANGE should be protected by ifdef __linux__.
Related to issue #2372.
2015-02-10 09:29:14 +01:00
Salvatore Sanfilippo 50a57c67b2 Merge pull request #2372 from mariano-perez-rodriguez/patch-2
Fix HAVE_BACKTRACE on Linux
2015-02-10 09:27:46 +01:00
antirez bd128f7969 redis-cli --lru-test implemented (cache workload simulator). 2015-02-09 11:06:55 +01:00
antirez ca23b2a6b7 redis-cli: interactive reconnection for latency modes.
--stat mode already used to reconnect automatically if the server is no
longer available. This is useful since this is an interactive mode used
for debugging, however the same applies to --latency and --latency-dist
modes, so now both use the reconnecting command execution as well.

The reconnection code was modified to use basic VT100 escape sequences
in order to play better with different kinds of output on the screen
when the reconnection happens, and to hide the reconnection attempt
output when finally the reconnection happens.
2015-02-08 23:28:27 +01:00
antirez cfe21852e7 redis-cli --latecy-dist reverted to gray scale.
So far not able to find a color palette within the 256 colors which is
not confusing. However I believe it is a possible task, so will try
better later.
2015-02-08 17:39:42 +01:00
antirez 2723412b7b redis-cli --latency-dist now uses a color palette.
Still not happy with the result but low grays are hard to see in certain
monitors with a non perfect gamma.
2015-02-07 20:15:40 +01:00
antirez ace1acc532 redis-cli latency dist: add new top HL. 2015-02-07 18:06:14 +01:00
Mariano Pérez Rodríguez c83a739bd8 Fix for #2371
Fixing #2371 as per @mattsta's suggestion
2015-02-07 14:02:33 -03:00
antirez 414df143f5 Add missing latency-dest legend symbol. 2015-02-07 15:05:40 +01:00
antirez 2860cf4139 Initial implementation of redis-cli --latency-dist. 2015-02-07 15:00:38 +01:00
Chris Lamb ba74711e06 Support "1G" etc. units in CONFIG SET maxmemory
Signed-off-by: Chris Lamb <chris@chris-lamb.co.uk>
2015-02-04 18:36:38 +00:00
antirez 2ac7b5a8b4 Fix RDB corruption test after server behavior change. 2015-02-04 11:53:19 +01:00
antirez 78dd2b313e Merge branch 'unstable' of github.com:/antirez/redis into unstable 2015-02-04 11:26:31 +01:00
antirez c5dd686ecb Replication: put server.master client creation into separated function. 2015-02-04 11:26:20 +01:00
antirez 719c6c2b96 Suppress sign warning in sort.c.
Related to #2346.
2015-02-03 15:42:56 +01:00
antirez 96abf65900 Hopefully better sort.c optimization comments.
Related to #2346.
2015-02-03 15:38:21 +01:00
Salvatore Sanfilippo 5fbb36f9e7 Merge pull request #2346 from sunheehnus/sort
Little optimization & bug fix in sort.c
2015-02-03 15:32:38 +01:00
antirez 1dbd8e94a7 More obvious indentation in setCommand(). 2015-02-03 14:17:06 +01:00
antirez 51010007bc Merge branch 'unstable' of git://github.com/mihirvj/redis into set-pr 2015-02-03 14:13:30 +01:00
antirez fad758b322 Check RDB automatically in a few more cases. 2015-02-03 10:33:05 +01:00
antirez 7d1e158084 Handle redis-check-rdb as a standalone program.
This also makes it backward compatible in the usage, but for the command
name. However the old command name was less obvious so it is worth to
break it probably.

With the new setup the program main can perform argument parsing and
everything else useful for an RDB check regardless of the Redis server
itself.
2015-02-03 10:25:01 +01:00
antirez 45102a6f63 Norrow backtrace and setproctitle() to Linux+glibc.
Backtrace is a glibc extension, while setproctitle() implementation
depends on the memory layout and is partially libc dependent.
2015-02-03 10:09:21 +01:00
antirez 128c642d05 Ignore config.sh inside create-cluster script dir. 2015-02-03 09:34:20 +01:00
Salvatore Sanfilippo cdc0e9bfb5 Merge pull request #2318 from mattcollier/patch-1
Fix redis-cli CSV NIL output
2015-02-02 22:47:57 +01:00
Sun He c908774b9e sort.c: REDIS_LIST's dontsort optimization
also fix the situation "dontsort DESC" of a list
2015-02-02 11:29:20 +08:00
antirez 6502947a85 redis-check-rdb: initialize entry in case while is never entered. 2015-01-30 15:19:39 +01:00
antirez 79fa67cdb8 Cluster: Tcl script to check avg pfail->fail time. 2015-01-30 12:03:17 +01:00
antirez 233729fe7f Cluster: some bias towwards FAIL/PFAIL nodes in gossip sections.
This improves PFAIL -> FAIL switch. Too late at this point in the RC
releases to add proper PFAIL/FAIL separate dictionary to do this in a
less randomized way. Tested in practice with experiments that this
helps. PFAIL -> FAIL average with 20 nodes and node-timeout set to 5
seconds takes 2.5 seconds without this commit, 1 second with this
commit.
2015-01-30 11:55:36 +01:00
antirez 69b4f00d28 More correct wanted / maxiterations values in clusterSendPing(). 2015-01-30 11:23:27 +01:00
antirez 6b1c6334be Cluster: create-cluster script improved. 2015-01-30 10:41:45 +01:00
antirez e5a22064cc Cluster: magical 10% of nodes explained in comments. 2015-01-29 15:43:35 +01:00
antirez 1efacfe53d CLUSTER count-failure-reports command added. 2015-01-29 15:02:10 +01:00
antirez 3fd43062c8 Cluster: use a number of gossip sections proportional to cluster size.
Otherwise it is impossible to receive the majority of failure reports in
the node_timeout*2 window in larger clusters.

Still with a 200 nodes cluster, 20 gossip sections are a very reasonable
amount of bytes to send.

A side effect of this change is also fater cluster nodes joins for large
clusters, because the cluster layout makes less time to propagate.
2015-01-29 14:20:59 +01:00
antirez d59ad97d76 create-cluster script: sane default timeout. 2015-01-29 13:21:42 +01:00
antirez 69583be181 Ignore redis-check-rdb after the name switch. 2015-01-28 23:28:27 +01:00
antirez 8899f91a7f create-cluster script added.
Simple shell script to create / destroy Redis clusters for manual
testing.
2015-01-28 23:26:46 +01:00
Salvatore Sanfilippo d3ef6c94bf Merge pull request #1744 from mattsta/better-RDB-failure-error
Improve Loading RDB Failure Action
2015-01-28 17:30:27 +01:00
Matt Stancliff d8c7db1bdb Improve RDB error-on-load handling
Previouly if we loaded a corrupt RDB, Redis printed an error report
with a big "REPORT ON GITHUB" message at the bottom.  But, we know
RDB load failures are corrupt data, not corrupt code.

Now when RDB failure is detected (duplicate keys or unknown data
types in the file), we run check-rdb against the RDB then exit.  The
automatic check-rdb hopefully gives the user instant feedback
about what is wrong instead of providing a mysterious stack
trace.
2015-01-28 11:19:00 -05:00
Matt Stancliff 764b000c3e Remove code duplication from check-rdb
redis-check-rdb (previously redis-check-dump) had every RDB define
copy/pasted from rdb.h and some defines copied from redis.h.  Since
the initial copy, some constants had changed in Redis headers and
check-dump was using incorrect values.

Since check-rdb is now a mode of Redis, the old check-dump code
is cleaned up to:
  - replace all printf with redisLog (and remove \n from all strings)
  - remove all copy/pasted defines to use defines from rdb.h and redis.h
  - replace all malloc/free with zmalloc/zfree
  - remove unnecessary include headers
2015-01-28 11:18:18 -05:00
Matt Stancliff 145473acc5 Convert check-dump to Redis check-rdb mode
redis-check-dump is now named redis-check-rdb and it runs
as a mode of redis-server instead of an independent binary.

You can now use 'redis-server redis.conf --check-rdb' to check
the RDB defined in redis.conf.  Using argument --check-rdb
checks the RDB and exits.  We could potentially also allow
the server to continue starting if the RDB check succeeds.

This change also enables us to use RDB checking programatically
from inside Redis for certain failure conditions.
2015-01-28 11:18:16 -05:00
mattcollier 6ec5f1f780 Update redis-cli.c
Code was adding '\n'  (line 521) to the end of NIL values exlusively making csv output inconsistent.  Removed '\n'
2015-01-25 14:01:39 -05:00
antirez 9802ec3c83 Cluster: initialized not used fileds in gossip section.
Otherwise we risk sending not initialized data to other nodes, that may
contain anything. This was actually not possible only because the
initialization of the buffer where the cluster packets header is created
was larger than the 3 gossip sections we use, so the memory was already
all filled with zeroes by the memset().
2015-01-24 07:52:24 +01:00
antirez 8aaf5075c5 dict.c: make chaining strategy more clear in dictAddRaw(). 2015-01-23 18:11:05 +01:00
antirez 7885e1264e DEBUG structsize
Show sizes of a few important data structures in Redis. More missing.
2015-01-23 18:10:14 +01:00
antirez e22d75734e Avoid duplicated instance execution code in Cluster test. 2015-01-22 18:59:39 +01:00
antirez ca14440aa1 Merge branch 'clusterfixes' into unstable 2015-01-22 16:31:14 +01:00
antirez d5ba544e67 Cluster test: when valgrind is enabled, use a larger node-timeout.
Removes some percentage of timing related failures.
2015-01-22 16:08:21 +01:00
antirez e4d65e35e6 The seed must be static in getRandomHexChars(). 2015-01-22 11:10:50 +01:00
antirez a330b6ca97 The seed must be static in getRandomHexChars(). 2015-01-22 11:10:43 +01:00
antirez 9826038f0b counter must be static in getRandomHexChars(). 2015-01-22 11:00:26 +01:00
antirez 87301be151 getRandomHexChars(): use /dev/urandom just to seed.
On Darwin /dev/urandom depletes terribly fast. This is not an issue
normally, but with Redis Cluster we generate a lot of unique IDs, for
example during nodes handshakes. Our IDs need just to be unique without
other strong crypto requirements, so this commit turns the function into
something that gets a 20 bytes seed from /dev/urandom, and produces the
rest of the output just using SHA1 in counter mode.
2015-01-21 23:21:55 +01:00
antirez af8d1b4bda Merge branch 'clusterfixes' into unstable 2015-01-21 19:30:22 +01:00
antirez 7e79b3f51a Cluster test initialization: use transaction for reset + set-config-epoch.
Otherwise between the two commands other nodes may contact us making the
next SET-CONFIG-EPOCH call impossible.
2015-01-21 18:48:08 +01:00
Matt Stancliffandantirez 051a43e03a Fix cluster migrate memory leak
Fixes valgrind error:
48 bytes in 1 blocks are definitely lost in loss record 196 of 373
   at 0x4910D3: je_malloc (jemalloc.c:944)
   by 0x42807D: zmalloc (zmalloc.c:125)
   by 0x41FA0D: dictGetIterator (dict.c:543)
   by 0x41FA48: dictGetSafeIterator (dict.c:555)
   by 0x459B73: clusterHandleSlaveMigration (cluster.c:2776)
   by 0x45BF27: clusterCron (cluster.c:3123)
   by 0x423344: serverCron (redis.c:1239)
   by 0x41D6CD: aeProcessEvents (ae.c:311)
   by 0x41D8EA: aeMain (ae.c:455)
   by 0x41A84B: main (redis.c:3832)
2015-01-21 18:47:16 +01:00
Matt Stancliffandantirez 29049507ec Fix potential invalid read past end of array
If array has N elements, we can't read +1 if we are already at N.

Also, we need to move elements by their storage size in the array,
not just by individual bytes.
2015-01-21 18:01:03 +01:00
Matt Stancliffandantirez 30152554ea Fix cluster reset memory leak
[maybe] Fixes valgrind errors:
32 bytes in 4 blocks are definitely lost in loss record 107 of 228
   at 0x80EA447: je_malloc (jemalloc.c:944)
   by 0x806E59C: zrealloc (zmalloc.c:125)
   by 0x80A9AFC: clusterSetMaster (cluster.c:801)
   by 0x80AEDC9: clusterCommand (cluster.c:3994)
   by 0x80682A5: call (redis.c:2049)
   by 0x8068A20: processCommand (redis.c:2309)
   by 0x8076497: processInputBuffer (networking.c:1143)
   by 0x8073BAF: readQueryFromClient (networking.c:1208)
   by 0x8060E98: aeProcessEvents (ae.c:412)
   by 0x806123B: aeMain (ae.c:455)
   by 0x806C3DB: main (redis.c:3832)

64 bytes in 8 blocks are definitely lost in loss record 143 of 228
   at 0x80EA447: je_malloc (jemalloc.c:944)
   by 0x806E59C: zrealloc (zmalloc.c:125)
   by 0x80AAB40: clusterProcessPacket (cluster.c:801)
   by 0x80A847F: clusterReadHandler (cluster.c:1975)
   by 0x30000FF: ???

80 bytes in 10 blocks are definitely lost in loss record 148 of 228
   at 0x80EA447: je_malloc (jemalloc.c:944)
   by 0x806E59C: zrealloc (zmalloc.c:125)
   by 0x80AAB40: clusterProcessPacket (cluster.c:801)
   by 0x80A847F: clusterReadHandler (cluster.c:1975)
   by 0x2FFFFFF: ???
2015-01-21 17:51:57 +01:00
Matt Stancliffandantirez 72b8574cca Fix sending uninitialized bytes
Fixes valgrind error:
Syscall param write(buf) points to uninitialised byte(s)
   at 0x514C35D: ??? (syscall-template.S:81)
   by 0x456B81: clusterWriteHandler (cluster.c:1907)
   by 0x41D596: aeProcessEvents (ae.c:416)
   by 0x41D8EA: aeMain (ae.c:455)
   by 0x41A84B: main (redis.c:3832)
 Address 0x5f268e2 is 2,274 bytes inside a block of size 8,192 alloc'd
   at 0x4932D1: je_realloc (jemalloc.c:1297)
   by 0x428185: zrealloc (zmalloc.c:162)
   by 0x4269E0: sdsMakeRoomFor.part.0 (sds.c:142)
   by 0x426CD7: sdscatlen (sds.c:251)
   by 0x4579E7: clusterSendMessage (cluster.c:1995)
   by 0x45805A: clusterSendPing (cluster.c:2140)
   by 0x45BB03: clusterCron (cluster.c:2944)
   by 0x423344: serverCron (redis.c:1239)
   by 0x41D6CD: aeProcessEvents (ae.c:311)
   by 0x41D8EA: aeMain (ae.c:455)
   by 0x41A84B: main (redis.c:3832)
 Uninitialised value was created by a stack allocation
   at 0x457810: nodeUpdateAddressIfNeeded (cluster.c:1236)
2015-01-21 17:50:17 +01:00
antirez acb1d8debf Cluster test: wait for port to unbound in kill_instance.
Otherwise kill_instance + restart_instance in short succession will
still find the port busy and will fail.
2015-01-21 16:47:36 +01:00
antirez 4433f5a7f2 AOF rewrite: set iterator var to NULL when freed.
The cleanup code expects that if 'di' is not NULL, it is a valid
iterator that should be freed.

The result of this bug was a crash of the AOF rewriting process if an
error occurred after the DBs data are written and the iterator is no
longer valid.
2015-01-21 16:42:08 +01:00
antirez b3bf7584b0 Cluster/Sentinel test: also pause on abort_sentinel_test call. 2015-01-21 16:18:34 +01:00
antirez a5bb0a0774 Cluster/Sentinel test: pause on exceptions as well. 2015-01-21 16:13:30 +01:00
antirez 2601e3e461 Cluster: node deletion cleanup / centralization. 2015-01-21 16:03:43 +01:00
antirez 59ad6ac5fe Cluster: set the slaves->slaveof filed to NULL when master is freed.
Related to issue #2289.
2015-01-21 15:55:53 +01:00
Matt Stancliffandantirez 491d57abaa Add --track-origins=yes to valgrind 2015-01-21 15:48:19 +01:00
Matt Stancliffandantirez b0146aafeb Tell sentinel/cluster tests to allow valgrind 2015-01-21 15:04:12 +01:00
antirez 92cfab44b2 Fix gcc warning for lack of casting to char pointer. 2015-01-21 14:51:42 +01:00
antirez e467cf5db3 luaRedisGenericCommand(): log error at WARNING level when re-entered.
Rationale is that when re-entering, it is likely due to Lua debugging
hooks. Returning an error will be ignored in most cases, going totally
unnoticed. With the log at least we leave a trace.

Related to issue #2302.
2015-01-20 23:21:21 +01:00
antirez bc8675612f luaRedisGenericCommand() recursion: just return an error.
Instead of calling redisPanic() to abort the server.

Related to issue #2302.
2015-01-20 23:16:19 +01:00
antirez 839767ad0b Panic on recursive calls to luaRedisGenericCommand().
Related to issue #2302.
2015-01-20 18:02:26 +01:00
Matt Stancliff 53c082ec39 Improve networking type correctness
read() and write() return ssize_t (signed long), not int.

For other offsets, we can use the unsigned size_t type instead
of a signed offset (since our replication offsets and buffer
positions are never negative).
2015-01-19 14:10:12 -05:00
Matt Stancliff f704360462 Improve RDB type correctness
It's possible large objects could be larger than 'int', so let's
upgrade all size counters to ssize_t.

This also fixes rdbSaveObject serialized bytes calculation.
Since entire serializations of data structures can be large,
so we don't want to limit their calculated size to a 32 bit signed max.

This commit increases object size calculation and
cascades the change back up to serializedlength printing.

Before:
127.0.0.1:6379> debug object hihihi
... encoding:quicklist serializedlength:-2147483559 ...

After:
127.0.0.1:6379> debug object hihihi
... encoding:quicklist serializedlength:2147483737 ...
2015-01-19 14:10:12 -05:00
antirez cf76af6b9f Cluster: fetch my IP even if msg is not MEET for the first time.
In order to avoid that misconfigured cluster nodes at some time may
force an IP update on other nodes, it is required that nodes update
their own address only on MEET messages. However it does not make sense
to do this the first time a node is contacted and yet does not have an
IP, we just risk that myself->ip remains not assigned if there are
messages lost or cluster creation procedures that don't make sure
everybody is targeted by at least one incoming MEET message.

Also fix the logging of the IP switch avoiding the :-1 tail.
2015-01-13 10:50:34 +01:00
antirez 5b0f4a83ac Cluster: clusterMsgDataGossip structure, explict padding + minor stuff.
Also explicitly set version to 0, add a protocol version define, improve
comments in the gossip structure.

Note that the structure layout is the same after the change, we are just
making the padding explicit with an additional not used 16 bits field.
So this commit is still able to talk with the previous versions of
cluster nodes.
2015-01-13 10:40:09 +01:00
antirez 237ab727b9 Suppress valgrind error about write sending uninitialized data.
Valgrind checks that the buffers we transfer via syscalls are all
composed of bytes actually initialized. This is useful, it makes we able
to avoid leaking informations in non initialized parts fo messages
transferred to other hosts. This commit fixes one of such issues.
2015-01-13 09:31:37 +01:00
antirez f08586347d Revert "Use REDIS_SUPERVISED_NONE instead of 0."
This reverts commit 2c925b0c30.

Nevermind.
2015-01-12 15:58:23 +01:00
antirez 2c925b0c30 Use REDIS_SUPERVISED_NONE instead of 0. 2015-01-12 15:57:50 +01:00
antirez 10007cb78c Merge branch 'unstable' of github.com:/antirez/redis into unstable 2015-01-12 15:56:46 +01:00
Salvatore Sanfilippo e6416ca71c Merge pull request #2266 from mattsta/improve/supervised/startup
Three fixes: explicit supervise, pidfile create, remove memory leaks.
2015-01-12 15:56:36 +01:00
antirez 6274a6789d Cluster: initialize mf_end.
Can't be initialized by resetManualFailover() since it's actual state
the function uses, so we need to initialize it at startup time. Not
really a bug in practical terms, but showed up into valgrind and is not
technically correct anyway.
2015-01-12 15:55:00 +01:00
Matt Stancliff 5a685f35a9 Add maxmemory limit to INFO MEMORY
Since we have the eviction policy, we should have the memory limit too.
2015-01-09 17:18:37 -05:00
Matt Stancliff 6741bb981c Improve consistency of INFO MEMORY fields
Adds used_memory_rss_human and used_memory_lua_human to match
all the other fields reporting human-readable memory too.
2015-01-09 17:18:37 -05:00
Matt Stancliff eb7d67a3ab Remove RDB AUX memory leaks 2015-01-09 15:19:18 -05:00
Matt Stancliff 36a3b75355 Supervise redis processes only if configured
Adds configuration option 'supervised [no | upstart | systemd | auto]'

Also removed 'bzero' from the previous implementation because it's 2015.
(We could actually statically initialize those structs, but clang
throws an invalid warning when we try, so it looks bad even though it
isn't bad.)

Fixes #2264
2015-01-09 15:19:18 -05:00
Matt Stancliff 5e8b7e4f35 Define default pidfile when creating pid
We want pidfile to be NULL on startup so we can detect if the user
set an explicit value versus only using the default value.

Closes #1967
Fixes #2076
2015-01-09 15:19:18 -05:00
rebxandMatt Stancliff e6d499a446 Create PID file even if in foreground
Previously, Redis only wrote the pid file if
it was daemonizing, but many times it's useful to have
the pid written out even if you're in the foreground.

Some background for this is:
I usually run redis via daemontools. That entails running
redis-server on the foreground. Given that, I'd also want
redis-server to create a pidfile so other processes (e.g. nagios)
can run checks for that.

Closes #463
2015-01-09 15:19:18 -05:00
antirez 0d22121c27 Add "-lrt" in Makefile for Solaris.
This fix is from @NanXiao, however I was not able to retain authorship
because the Pull Request original repository was removed.
2015-01-09 11:53:51 +01:00
antirez 792a94153a Check for __sun macro in solarisfixes.h, not in includers. 2015-01-09 11:23:22 +01:00
antirez f3fd58eb4a Cluster test: also write from Lua script in resharding test. 2015-01-09 11:23:22 +01:00
antirez da95d22ad2 Prevent Lua scripts from violating Redis Cluster keyspace access rules.
Before this commit scripts were able to access / create keys outside the
set of hash slots served by the local node.
2015-01-09 11:23:22 +01:00
Salvatore Sanfilippo 1019c72930 Merge pull request #2265 from mattsta/fix/trib/create
Fix redis-trib creation failure
2015-01-08 19:43:14 +01:00
Matt Stancliff bf58f8b513 Remove end of line whitespace from redis-trib 2015-01-08 13:31:03 -05:00
Matt Stancliff 1c477f62bc Fix redis-trib cluster create
Under certain conditions the node list wasn't being fully populated
and 'create' would fail trying to call methods on nil objects.
2015-01-08 13:28:35 -05:00
antirez 622c69e93d README section about make distclean reworded / extended. 2015-01-08 16:35:05 +01:00
antirez b45e16e7ee Merge branch 'unstable' of github.com:/antirez/redis into unstable 2015-01-08 16:30:04 +01:00
Salvatore Sanfilippo 161ff77572 Merge pull request #2262 from HeartSaVioR/explain-make-distclean
Explain make distclean which seems not well known
2015-01-08 16:29:29 +01:00
Jungtaek Lim 219ab66cc8 Explain make distclean which seems not well known 2015-01-09 00:07:25 +09:00
antirez 42357668e8 Advertise Redis Cluster as experimental in redis.conf. 2015-01-08 14:41:34 +01:00
antirez a7722dc31b Typo fixed: fiels -> fields in rdbSaveInfoAuxFields().
Thx to @badboy.
2015-01-08 12:06:22 +01:00
antirez 4c0e8923a6 A few more AUX info fields added to RDB. 2015-01-08 09:52:59 +01:00
antirez 206cd219b6 RDB AUX fields support.
This commit introduces a new RDB data type called 'aux'. It is used in
order to insert inside an RDB file key-value pairs that may serve
different needs, without breaking backward compatibility when new
informations are embedded inside an RDB file. The contract between Redis
versions is to ignore unknown aux fields when encountered.

Aux fields can be used in order to:

1. Augment the RDB file with info like version of Redis that created the
RDB file, creation time, used memory while the RDB was created, and so
forth.
2. Add state about Redis inside the RDB file that we need to reload
later: replication offset, previos master run ID, in order to improve
failovers safety and allow partial resynchronization after a slave
restart.
3. Anything that we may want to add to RDB files without breaking the
ability of past versions of Redis to load the file.
2015-01-08 09:52:55 +01:00
antirez 1a30e7ded1 rdbLoad() refactoring to make it simpler to follow. 2015-01-08 09:52:51 +01:00
antirez e8614a1a77 New RDB v7 opcode: RESIZEDB.
The new opcode is an hint about the size of the dataset (keys and number
of expires) we are going to load for a given Redis database inside the
RDB file. Since hash tables are resized accordingly ASAP, useless
rehashing is avoided, speeding up load times significantly, in the order
of ~ 20% or more for larger data sets.

Related issue: #1719
2015-01-08 09:52:47 +01:00
antirez 32b10004e2 sdsnative() removed: New rdb.c API can load native strings. 2015-01-08 09:52:44 +01:00
antirez f699b5e801 Use RDB_LOAD_PLAIN to load quicklists and encoded types.
Before we needed to create a string object with an embedded SDS, adn
basically duplicate the SDS part into a plain zmalloc() allocation.
2015-01-08 09:52:40 +01:00
antirez 68bc02c36c RDB refactored to load plain strings from RDB. 2015-01-08 09:52:36 +01:00
Salvatore Sanfilippo 05ba119fbb Merge pull request #2143 from mattsta/quicklist
Quicklist (linked list + ziplist)
2015-01-08 09:51:55 +01:00
Matt Stancliff 5870e22423 Upgrade LZF to 3.6 (2011) from 3.5 (2009)
This is lzf_c and lzf_d from
http://dist.schmorp.de/liblzf/liblzf-3.6.tar.gz
2015-01-02 11:16:10 -05:00
Matt Stancliff 25e12d10be Set optional 'static' for Quicklist+Redis
This also defines REDIS_STATIC='' for building everything
inside src/ and everything inside deps/lua/.
2015-01-02 11:16:10 -05:00
Matt Stancliff 9e11d07909 Add more quicklist info to DEBUG OBJECT
Adds: ql_compressed (boolean, 1 if compression enabled for list, 0
otherwise)
Adds: ql_uncompressed_size (actual uncompressed size of all quicklistNodes)
Adds: ql_ziplist_max (quicklist max ziplist fill factor)

Compression ratio of the list is then ql_uncompressed_size / serializedlength

We report ql_uncompressed_size for all quicklists because serializedlength
is a _compressed_ representation anyway.

Sample output from a large list:
127.0.0.1:6379> llen abc
(integer) 38370061
127.0.0.1:6379> debug object abc
Value at:0x7ff97b51d140 refcount:1 encoding:quicklist serializedlength:19878335 lru:9718164 lru_seconds_idle:5 ql_nodes:21945 ql_avg_node:1748.46 ql_ziplist_max:-2 ql_compressed:0 ql_uncompressed_size:1643187761
(1.36s)

The 1.36s result time is because rdbSavedObjectLen() is serializing the
object, not because of any new stats reporting.

If we run DEBUG OBJECT on a compressed list, DEBUG OBJECT takes almost *zero*
time because rdbSavedObjectLen() reuses already-compressed ziplists:
127.0.0.1:6379> debug object abc
Value at:0x7fe5c5800040 refcount:1 encoding:quicklist serializedlength:19878335 lru:9718109 lru_seconds_idle:5 ql_nodes:21945 ql_avg_node:1748.46 ql_ziplist_max:-2 ql_compressed:1 ql_uncompressed_size:1643187761
2015-01-02 11:16:10 -05:00
Matt Stancliff 02bb515a09 Config: Add quicklist, remove old list options
This removes:
  - list-max-ziplist-entries
  - list-max-ziplist-value

This adds:
  - list-max-ziplist-size
  - list-compress-depth

Also updates config file with new sections and updates
tests to use quicklist settings instead of old list settings.
2015-01-02 11:16:10 -05:00
Matt Stancliff bbbbfb1442 Add branch prediction hints to quicklist
Actually makes a noticeable difference.

Branch hints were selected based on profiler hotspots.
2015-01-02 11:16:10 -05:00
Matt Stancliff 5f506b6d2b Cleanup quicklist style
Small fixes due to a new version of clang-format (it's less
crazy than the older version).
2015-01-02 11:16:09 -05:00
Matt Stancliff abdd1414a8 Allow compression of interior quicklist nodes
Let user set how many nodes to *not* compress.

We can specify a compression "depth" of how many nodes
to leave uncompressed on each end of the quicklist.

Depth 0 = disable compression.
Depth 1 = only leave head/tail uncompressed.
  - (read as: "skip 1 node on each end of the list before compressing")
Depth 2 = leave head, head->next, tail->prev, tail uncompressed.
  - ("skip 2 nodes on each end of the list before compressing")
Depth 3 = Depth 2 + head->next->next + tail->prev->prev
  - ("skip 3 nodes...")
etc.

This also:
  - updates RDB storage to use native quicklist compression (if node is
    already compressed) instead of uncompressing, generating the RDB string,
    then re-compressing the quicklist node.
  - internalizes the "fill" parameter for the quicklist so we don't
    need to pass it to _every_ function.  Now it's just a property of
    the list.
  - allows a runtime-configurable compression option, so we can
    expose a compresion parameter in the configuration file if people
    want to trade slight request-per-second performance for up to 90%+
    memory savings in some situations.
  - updates the quicklist tests to do multiple passes: 200k+ tests now.
2015-01-02 11:16:09 -05:00
Matt Stancliff 5127e39980 Add quicklist info to DEBUG OBJECT
Added field 'ql_nodes' and 'ql_avg_per_node'.

ql_nodes is the number of quicklist nodes in the quicklist.
ql_avg_node is the average fill level in each quicklist node. (LLEN / QL_NODES)

Sample output:
127.0.0.1:6379> DEBUG object b
Value at:0x7fa42bf2fed0 refcount:1 encoding:quicklist serializedlength:18489 lru:8983768 lru_seconds_idle:3 ql_nodes:430 ql_avg_per_node:511.73
127.0.0.1:6379> llen b
(integer) 220044
2015-01-02 11:16:09 -05:00
Matt Stancliff 8d7021892e Remove malloc failure checks
We trust zmalloc to kill the whole process on memory failure
2015-01-02 11:16:09 -05:00
Matt Stancliff e0d94a7b01 Increase test size for migrating large values
Previously, the old test ran 5,000 loops and used about 500k.

With quicklist, storing those same 5,000 loops takes up 24k, so the
"large value check" failed!

This increases the test to 20,000 loops which makes the object dump 96k.
2015-01-02 11:16:09 -05:00
Matt Stancliff 101b3a6e42 Convert quicklist RDB to store ziplist nodes
Turns out it's a huge improvement during save/reload/migrate/restore
because, with compression enabled, we're compressing 4k or 8k
chunks of data consisting of multiple elements in one ziplist
instead of compressing series of smaller individual elements.
2015-01-02 11:16:09 -05:00
Matt Stancliff 127c15e2b2 Convert RDB ziplist loading to sdsnative()
This saves us an unnecessary zmalloc, memcpy, and two frees.
2015-01-02 11:16:09 -05:00
Matt Stancliff e1619772db Add sdsnative()
Use the existing memory space for an SDS to convert it to a regular
character buffer so we don't need to allocate duplicate space just
to extract a usable buffer for native operations.
2015-01-02 11:16:08 -05:00
Matt Stancliff c6bf20c2a7 Add adaptive quicklist fill factor
Fill factor now has two options:
  - negative (1-5) for size-based ziplist filling
  - positive for length-based ziplist filling with implicit size cap.

Negative offsets define ziplist size limits of:
  -1: 4k
  -2: 8k
  -3: 16k
  -4: 32k
  -5: 64k

Positive offsets now automatically limit their max size to 8k.  Any
elements larger than 8k will be in individual nodes.

Positive ziplist fill factors will keep adding elements
to a ziplist until one of:
  - ziplist has FILL number of elements
    - or -
  - ziplist grows above our ziplist max size (currently 8k)

When using positive fill factors, if you insert a large
element (over 8k), that element will automatically allocate
an individual quicklist node with one element and no other elements will be
in the same ziplist inside that quicklist node.

When using negative fill factors, elements up to the size
limit can be added to one quicklist node.  If an element
is added larger than the max ziplist size, that element
will be allocated an individual ziplist in a new quicklist node.

Tests also updated to start testing at fill factor -5.
2015-01-02 11:16:08 -05:00
Matt Stancliff 60a9418ed9 redis-benchmark: Add RPUSH and RPOP tests 2015-01-02 11:16:08 -05:00
Matt Stancliff 0f15eb183b Free ziplist test lists during tests
Freeing our test lists helps keep valgrind output clean
2015-01-02 11:16:08 -05:00
Matt Stancliff 9d2dc0249c Add ziplistMerge()
This started out as #2158 by sunheehnus, but I kept rewriting it
until I could understand things more easily and get a few more
correctness guarantees out of the readability flow.

The original commit created and returned a new ziplist with the contents of
both input ziplists, but I prefer to grow one of the input ziplists
and destroy the other one.

So, instead of malloc+copy as in #2158, the merge now reallocs one of
the existing ziplists and copies the other ziplist into the new space.

Also added merge test cases to ziplistTest()
2015-01-02 11:16:08 -05:00
Matt Stancliff 5e362b84ab Add quicklist implementation
This replaces individual ziplist vs. linkedlist representations
for Redis list operations.

Big thanks for all the reviews and feedback from everybody in
https://github.com/antirez/redis/pull/2143
2015-01-02 11:16:08 -05:00
Mihir Joshi 352172a7ef Stricter options for SET command
- As per Antirez's suggestion, this commit raises an error when mutually
exclusive options are provided. Duplicate options are allowed.
2014-12-14 10:12:58 -05:00
Matt Stancliff badf0f008b Bitops: Stop overallocating storage space on set
Previously the string was created empty then re-sized
to fit the offset, but sds resize causes the sds to
over-allocate by at least 1 MB (which is a lot when
you are operating at bit-level access).

This also improves the speed of initial sets by 2% to 6%
based on quick testing.

Patch logic provided by @oranagra

Fixes #1918
2014-12-11 10:54:21 -05:00
Matt Stancliff 391fc9b633 Sentinel: Improve INFO command behavior
Improvements:
  - Return empty string if asking for non-existing section (INFO foo)
  - Fix potential memory leak (caused by sdsempty() then returned if >2 args)
  - Clean up argument parsing
  - Allow "all" as valid section (same as "default" or zero args currently)
  - Move strcasecmp to end of evaluation chain in conditionals

Also, since we're C99, I moved some variable declarations to be closer
to where they are actually used (saves us from needing to free an empty info
if detect argument errors up front).

Closes #1915
Closes #1966
2014-12-11 10:49:16 -05:00
Mihir Joshi e9b014cfac stricter options for SET command
Issue: #2157
As the SET command is parsed, it remembers which options are already set
and if a duplicate option is found, raises an error because it is
essentially an invalid syntax.

It still allows mutually exclusive options like EX and PX because taking
an option over another (precedence) is not essentially a syntactic
error.
2014-11-21 22:35:42 -05:00
Matt Stancliff 6739ef4447 Sentinel: Add initial quorum bounds check
Fixes #2054
2014-11-20 16:30:17 -05:00
82 changed files with 6155 additions and 1798 deletions
+1
View File
@@ -4,6 +4,7 @@
dump.rdb
redis-benchmark
redis-check-aof
redis-check-rdb
redis-check-dump
redis-cli
redis-sentinel
+56 -19
View File
@@ -1,18 +1,35 @@
Where to find complete Redis documentation?
-------------------------------------------
This README is just a fast *quick start* document. You can find more detailed documentation at http://redis.io.
This README is just a fast "quick start" document. You can find more detailed
documentation at http://redis.io
What is Redis?
--------------
Redis is often referred as a *data structures* server. What this means is that Redis provides access to mutable data structures via a set of commands, which are send using a *server-client* model with TCP sockets and a simple protocol. So different processes can query and modify the same data structures in a shared way.
Data structures implemented into Redis have a few special properties:
* Redis cares to store them on disk, even if they are always served and modified into the server memory. This means that Redis is fast, but that is also non-volatile.
* Implementation of data structures stress on memory efficiency, so data structures inside Redis will likely use less memory compared to the same data structure modeled using an high level programming language.
* Redis offers a number of features that are natural to find into a database, like replication, tunable levels of durability, cluster, high availability.
Another good example is to think at Redis as a more complex version of memcached, where the opeations are not just SETs and GETs, but operations to work with complex data types like Lists, Sets, ordered data structures, and so forth.
If you want to know more, this is a list of selected starting points:
* Introduction to Redis data types. http://redis.io/topics/data-types-intro
* Try Redis directly inside your browser. http://try.redis.io
* The full list of Redis commands. http://redis.io/commands
* There is much more inside the Redis official documentation. http://redis.io/documentation
Building Redis
--------------
Redis can be compiled and used on Linux, OSX, OpenBSD, NetBSD, FreeBSD.
We support big endian and little endian architectures.
We support big endian and little endian architectures, and both 32 bit
and 64 bit systems.
It may compile on Solaris derived systems (for instance SmartOS) but our
support for this platform is "best effort" and Redis is not guaranteed to
work as well as in Linux, OSX, and *BSD there.
support for this platform is *best effort* and Redis is not guaranteed to
work as well as in Linux, OSX, and \*BSD there.
It is as simple as:
@@ -26,20 +43,39 @@ After building Redis is a good idea to test it, using:
% make test
Fixing build problems with dependencies or cached build options
---------
Redis has some dependencies which are included into the `deps` directory.
`make` does not rebuild dependencies automatically, even if something in the
source code of dependencies is changes.
When you update the source code with `git pull` or when code inside the
dependencies tree is modified in any other way, make sure to use the following
command in order to really clean everything and rebuild from scratch:
make distclean
This will clean: jemalloc, lua, hiredis, linenoise.
Also if you force certain build options like 32bit target, no C compiler
optimizations (for debugging purposes), and other similar build time options,
those options are cached indefinitely until you issue a `make distclean`
command.
Fixing problems building 32 bit binaries
---------
If after building Redis with a 32 bit target you need to rebuild it
with a 64 bit target, or the other way around, you need to perform a
"make distclean" in the root directory of the Redis distribution.
`make distclean` in the root directory of the Redis distribution.
In case of build errors when trying to build a 32 bit binary of Redis, try
the following steps:
* Install the packages libc6-dev-i386 (also try g++-multilib).
* Try using the following command line instead of "make 32bit":
make CFLAGS="-m32 -march=native" LDFLAGS="-m32"
* Try using the following command line instead of `make 32bit`:
`make CFLAGS="-m32 -march=native" LDFLAGS="-m32"`
Allocator
---------
@@ -107,11 +143,9 @@ then in another terminal try the following:
(integer) 1
redis> incr mycounter
(integer) 2
redis>
redis>
You can find the list of all the available commands here:
http://redis.io/commands
You can find the list of all the available commands at http://redis.io/commands.
Installing Redis
-----------------
@@ -120,7 +154,7 @@ In order to install Redis binaries into /usr/local/bin just use:
% make install
You can use "make PREFIX=/some/other/directory install" if you wish to use a
You can use `make PREFIX=/some/other/directory install` if you wish to use a
different destination.
Make install will just install binaries in your system, but will not configure
@@ -137,7 +171,7 @@ to run Redis properly as a background daemon that will start again on
system reboots.
You'll be able to stop and start Redis using the script named
/etc/init.d/redis_<portnumber>, for instance /etc/init.d/redis_6379.
`/etc/init.d/redis_<portnumber>`, for instance `/etc/init.d/redis_6379`.
Code contributions
---
@@ -145,10 +179,13 @@ Code contributions
Note: by contributing code to the Redis project in any form, including sending
a pull request via Github, a code fragment or patch via private email or
public discussion groups, you agree to release your code under the terms
of the BSD license that you can find in the COPYING file included in the Redis
of the BSD license that you can find in the [COPYING][1] file included in the Redis
source distribution.
Please see the CONTRIBUTING file in this source distribution for more
Please see the [CONTRIBUTING][2] file in this source distribution for more
information.
Enjoy!
[1]: https://github.com/antirez/redis/blob/unstable/COPYING
[2]: https://github.com/antirez/redis/blob/unstable/CONTRIBUTING
+1 -1
View File
@@ -58,7 +58,7 @@ ifeq ($(uname_S),SunOS)
LUA_CFLAGS= -D__C99FEATURES__=1
endif
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL $(CFLAGS)
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL -DREDIS_STATIC='' $(CFLAGS)
LUA_LDFLAGS+= $(LDFLAGS)
# lua's Makefile defines AR="ar rcu", which is unusual, and makes it more
# challenging to cross-compile lua (and redis). These defines make it easier
+82 -23
View File
@@ -30,15 +30,30 @@
# include /path/to/local.conf
# include /path/to/other.conf
################################ GENERAL #####################################
################################## NETWORK #####################################
# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize no
# When running daemonized, Redis writes a pid file in /var/run/redis.pid by
# default. You can specify a custom pid file location here.
pidfile /var/run/redis.pid
# By default, if no "bind" configuration directive is specified, Redis listens
# for connections from all the network interfaces available on the server.
# It is possible to listen to just one or multiple selected interfaces using
# the "bind" configuration directive, followed by one or more IP addresses.
#
# Examples:
#
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1 ::1
#
# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the
# internet, binding to all the interfaces is dangerous and will expose the
# instance to everybody on the internet. So by default we uncomment the
# following bind directive, that will force Redis to listen only into
# the IPv4 lookback interface address (this means Redis will be able to
# accept connections only from clients running into the same computer it
# is running).
#
# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
# JUST UNCOMMENT THE FOLLOWING LINE.
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bind 127.0.0.1
# Accept connections on the specified port, default is 6379.
# If port 0 is specified Redis will not listen on a TCP socket.
@@ -53,16 +68,8 @@ port 6379
# 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
# more IP addresses.
# Unix socket.
#
# Examples:
#
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1
# Specify the path for the Unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
@@ -89,6 +96,27 @@ timeout 0
# A reasonable value for this option is 60 seconds.
tcp-keepalive 0
################################# GENERAL #####################################
# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize no
# If you run Redis from upstart or systemd, Redis can interact with your
# supervision tree. Options:
# supervised no - no supervision interaction
# supervised upstart - signal upstart by putting Redis into SIGSTOP mode
# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
# supervised auto - detect upstart or systemd method based on
# UPSTART_JOB or NOTIFY_SOCKET environment variables
# Note: these supervision methods only signal "process is ready."
# They do not enable continuous liveness pings back to your supervisor.
supervised no
# When running daemonized, Redis writes a pid file in /var/run/redis.pid by
# default. You can specify a custom pid file location here.
pidfile /var/run/redis.pid
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
@@ -619,6 +647,12 @@ lua-time-limit 5000
################################ REDIS CLUSTER ###############################
#
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# WARNING EXPERIMENTAL: Redis Cluster is considered to be stable code, however
# in order to mark it as "mature" we need to wait for a non trivial percentage
# of users to deploy it in production.
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#
# Normal Redis instances can't be part of a Redis Cluster; only nodes that are
# started as cluster nodes can. In order to start a Redis instance as a
# cluster node enable the cluster support uncommenting the following:
@@ -764,7 +798,7 @@ slowlog-max-len 128
# "CONFIG SET latency-monitor-threshold <milliseconds>" if needed.
latency-monitor-threshold 0
############################# Event notification ##############################
############################# EVENT NOTIFICATION ##############################
# Redis can notify Pub/Sub clients about events happening in the key space.
# This feature is documented at http://redis.io/topics/notifications
@@ -818,11 +852,36 @@ notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
# Similarly to hashes, small lists are also encoded in a special way in order
# to save a lot of space. The special representation is only used when
# you are under the following limits:
list-max-ziplist-entries 512
list-max-ziplist-value 64
# Lists are also encoded in a special way to save a lot of space.
# The number of entries allowed per internal list node can be specified
# as a fixed maximum size or a maximum number of elements.
# For a fixed maximum size, use -5 through -1, meaning:
# -5: max size: 64 Kb <-- not recommended for normal workloads
# -4: max size: 32 Kb <-- not recommended
# -3: max size: 16 Kb <-- probably not recommended
# -2: max size: 8 Kb <-- good
# -1: max size: 4 Kb <-- good
# Positive numbers mean store up to _exactly_ that number of elements
# per list node.
# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),
# but if your use case is unique, adjust the settings as necessary.
list-max-ziplist-size -2
# Lists may also be compressed.
# Compress depth is the number of quicklist ziplist nodes from *each* side of
# the list to *exclude* from compression. The head and tail of the list
# are always uncompressed for fast push/pop operations. Settings are:
# 0: disable all list compression
# 1: depth 1 means "don't start compressing until after 1 node into the list,
# going from either the head or tail"
# So: [head]->node->node->...->node->[tail]
# [head], [tail] will always be uncompressed; inner nodes will compress.
# 2: [head]->[next]->node->node->...->node->[prev]->[tail]
# 2 here means: don't compress head or head->next or tail->prev or tail,
# but compress all nodes between them.
# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail]
# etc.
list-compress-depth 0
# Sets have a special encoding in just one case: when a set is composed
# of just strings that happen to be integers in radix 10 in the range
+11 -12
View File
@@ -18,7 +18,7 @@ OPTIMIZATION?=-O2
DEPENDENCY_TARGETS=hiredis linenoise lua
# Default settings
STD=-std=c99 -pedantic
STD=-std=c99 -pedantic -DREDIS_STATIC=''
WARN=-Wall -W
OPT=$(OPTIMIZATION)
@@ -62,7 +62,7 @@ ifeq ($(uname_S),SunOS)
# SunOS
INSTALL=cp -pf
FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6
FINAL_LIBS+= -ldl -lnsl -lsocket -lresolv -lpthread
FINAL_LIBS+= -ldl -lnsl -lsocket -lresolv -lpthread -lrt
else
ifeq ($(uname_S),Darwin)
# Darwin (nothing to do)
@@ -117,17 +117,16 @@ endif
REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel
REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o
REDIS_CLI_NAME=redis-cli
REDIS_CLI_OBJ=anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
REDIS_BENCHMARK_NAME=redis-benchmark
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o redis-benchmark.o
REDIS_CHECK_DUMP_NAME=redis-check-dump
REDIS_CHECK_DUMP_OBJ=redis-check-dump.o lzf_c.o lzf_d.o crc64.o
REDIS_CHECK_RDB_NAME=redis-check-rdb
REDIS_CHECK_AOF_NAME=redis-check-aof
REDIS_CHECK_AOF_OBJ=redis-check-aof.o
all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME)
all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME)
@echo ""
@echo "Hint: It's a good idea to run 'make test' ;)"
@echo ""
@@ -178,6 +177,10 @@ $(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ)
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME)
# redis-check-rdb
$(REDIS_CHECK_RDB_NAME): $(REDIS_SERVER_NAME)
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_CHECK_RDB_NAME)
# redis-cli
$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS)
@@ -186,10 +189,6 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS)
# redis-check-dump
$(REDIS_CHECK_DUMP_NAME): $(REDIS_CHECK_DUMP_OBJ)
$(REDIS_LD) -o $@ $^ $(FINAL_LIBS)
# redis-check-aof
$(REDIS_CHECK_AOF_NAME): $(REDIS_CHECK_AOF_OBJ)
$(REDIS_LD) -o $@ $^ $(FINAL_LIBS)
@@ -201,7 +200,7 @@ $(REDIS_CHECK_AOF_NAME): $(REDIS_CHECK_AOF_OBJ)
$(REDIS_CC) -c $<
clean:
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html
.PHONY: clean
@@ -257,6 +256,6 @@ install: all
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_CHECK_DUMP_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_CHECK_RDB_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN)
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
+2 -2
View File
@@ -391,7 +391,7 @@ int anetUnixNonBlockConnect(char *err, char *path)
* (unless error or EOF condition is encountered) */
int anetRead(int fd, char *buf, int count)
{
int nread, totlen = 0;
ssize_t nread, totlen = 0;
while(totlen != count) {
nread = read(fd,buf,count-totlen);
if (nread == 0) return totlen;
@@ -406,7 +406,7 @@ int anetRead(int fd, char *buf, int count)
* (unless error is encountered) */
int anetWrite(int fd, char *buf, int count)
{
int nwritten, totlen = 0;
ssize_t nwritten, totlen = 0;
while(totlen != count) {
nwritten = write(fd,buf,count-totlen);
if (nwritten == 0) return totlen;
+11 -33
View File
@@ -770,52 +770,29 @@ int rioWriteBulkObject(rio *r, robj *obj) {
int rewriteListObject(rio *r, robj *key, robj *o) {
long long count = 0, items = listTypeLength(o);
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *zl = o->ptr;
unsigned char *p = ziplistIndex(zl,0);
unsigned char *vstr;
unsigned int vlen;
long long vlong;
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
quicklist *list = o->ptr;
quicklistIter *li = quicklistGetIterator(list, AL_START_HEAD);
quicklistEntry entry;
while(ziplistGet(p,&vstr,&vlen,&vlong)) {
while (quicklistNext(li,&entry)) {
if (count == 0) {
int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ?
REDIS_AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
}
if (vstr) {
if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0;
if (entry.value) {
if (rioWriteBulkString(r,(char*)entry.value,entry.sz) == 0) return 0;
} else {
if (rioWriteBulkLongLong(r,vlong) == 0) return 0;
if (rioWriteBulkLongLong(r,entry.longval) == 0) return 0;
}
p = ziplistNext(zl,p);
if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0;
items--;
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
list *list = o->ptr;
listNode *ln;
listIter li;
listRewind(list,&li);
while((ln = listNext(&li))) {
robj *eleobj = listNodeValue(ln);
if (count == 0) {
int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ?
REDIS_AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
}
if (rioWriteBulkObject(r,eleobj) == 0) return 0;
if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0;
items--;
}
quicklistReleaseIterator(li);
} else {
redisPanic("Unknown list encoding");
}
@@ -1105,6 +1082,7 @@ int rewriteAppendOnlyFile(char *filename) {
}
}
dictReleaseIterator(di);
di = NULL;
}
/* Do an initial slow fsync here while the parent is still sending
+3 -5
View File
@@ -229,19 +229,17 @@ void setbitCommand(redisClient *c) {
return;
}
byte = bitoffset >> 3;
o = lookupKeyWrite(c->db,c->argv[1]);
if (o == NULL) {
o = createObject(REDIS_STRING,sdsempty());
o = createObject(REDIS_STRING,sdsnewlen(NULL, byte+1));
dbAdd(c->db,c->argv[1],o);
} else {
if (checkType(c,o,REDIS_STRING)) return;
o = dbUnshareStringValue(c->db,c->argv[1],o);
o->ptr = sdsgrowzero(o->ptr,byte+1);
}
/* Grow sds value to the right length if necessary */
byte = bitoffset >> 3;
o->ptr = sdsgrowzero(o->ptr,byte+1);
/* Get current values */
byteval = ((uint8_t*)o->ptr)[byte];
bit = 7 - (bitoffset & 0x7);
-2
View File
@@ -118,9 +118,7 @@ void processUnblockedClients(void) {
/* Process remaining data in the input buffer. */
if (c->querybuf && sdslen(c->querybuf) > 0) {
server.current_client = c;
processInputBuffer(c);
server.current_client = NULL;
}
}
}
+176 -67
View File
@@ -40,6 +40,7 @@
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <math.h>
/* A global reference to myself is handy to make code more clear.
* Myself always points to server.cluster->myself, that is, the clusterNode
@@ -479,6 +480,7 @@ void clusterInit(void) {
* the IP address via MEET messages. */
myself->port = server.port;
server.cluster->mf_end = 0;
resetManualFailover();
}
@@ -782,8 +784,11 @@ int clusterNodeRemoveSlave(clusterNode *master, clusterNode *slave) {
for (j = 0; j < master->numslaves; j++) {
if (master->slaves[j] == slave) {
memmove(master->slaves+j,master->slaves+(j+1),
(master->numslaves-1)-j);
if ((j+1) < master->numslaves) {
int remaining_slaves = (master->numslaves - j) - 1;
memmove(master->slaves+j,master->slaves+(j+1),
(sizeof(*master->slaves) * remaining_slaves));
}
master->numslaves--;
return REDIS_OK;
}
@@ -818,15 +823,30 @@ int clusterCountNonFailingSlaves(clusterNode *n) {
return okslaves;
}
/* Low level cleanup of the node structure. Only called by clusterDelNode(). */
void freeClusterNode(clusterNode *n) {
sds nodename;
int j;
/* If the node is a master with associated slaves, we have to set
* all the slaves->slaveof fields to NULL (unknown). */
if (nodeIsMaster(n)) {
for (j = 0; j < n->numslaves; j++)
n->slaves[j]->slaveof = NULL;
}
/* Remove this node from the list of slaves of its master. */
if (nodeIsSlave(n) && n->slaveof) clusterNodeRemoveSlave(n->slaveof,n);
/* Unlink from the set of nodes. */
nodename = sdsnewlen(n->name, REDIS_CLUSTER_NAMELEN);
redisAssert(dictDelete(server.cluster->nodes,nodename) == DICT_OK);
sdsfree(nodename);
if (n->slaveof) clusterNodeRemoveSlave(n->slaveof, n);
/* Release link and associated data structures. */
if (n->link) freeClusterLink(n->link);
listRelease(n->fail_reports);
zfree(n->slaves);
zfree(n);
}
@@ -839,11 +859,16 @@ int clusterAddNode(clusterNode *node) {
return (retval == DICT_OK) ? REDIS_OK : REDIS_ERR;
}
/* Remove a node from the cluster:
* 1) Mark all the nodes handled by it as unassigned.
* 2) Remove all the failure reports sent by this node.
* 3) Free the node, that will in turn remove it from the hash table
* and from the list of slaves of its master, if it is a slave node.
/* Remove a node from the cluster. The functio performs the high level
* cleanup, calling freeClusterNode() for the low level cleanup.
* Here we do the following:
*
* 1) Mark all the slots handled by it as unassigned.
* 2) Remove all the failure reports sent by this node and referenced by
* other nodes.
* 3) Free the node with freeClusterNode() that will in turn remove it
* from the hash table and from the list of slaves of its master, if
* it is a slave node.
*/
void clusterDelNode(clusterNode *delnode) {
int j;
@@ -870,11 +895,7 @@ void clusterDelNode(clusterNode *delnode) {
}
dictReleaseIterator(di);
/* 3) Remove this node from its master's slaves if needed. */
if (nodeIsSlave(delnode) && delnode->slaveof)
clusterNodeRemoveSlave(delnode->slaveof,delnode);
/* 4) Free the node, unlinking it from the cluster. */
/* 3) Free the node, unlinking it from the cluster. */
freeClusterNode(delnode);
}
@@ -1118,6 +1139,7 @@ int clusterStartHandshake(char *ip, int port) {
/* Set norm_ip as the normalized string representation of the node
* IP address. */
memset(norm_ip,0,REDIS_IP_STR_LEN);
if (sa.ss_family == AF_INET)
inet_ntop(AF_INET,
(void*)&(((struct sockaddr_in *)&sa)->sin_addr),
@@ -1232,7 +1254,7 @@ void nodeIp2String(char *buf, clusterLink *link) {
* The function returns 0 if the node address is still the same,
* otherwise 1 is returned. */
int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link, int port) {
char ip[REDIS_IP_STR_LEN];
char ip[REDIS_IP_STR_LEN] = {0};
/* We don't proceed if the link is the same as the sender link, as this
* function is designed to see if the node link is consistent with the
@@ -1463,7 +1485,8 @@ int clusterProcessPacket(clusterLink *link) {
/* Perform sanity checks */
if (totlen < 16) return 1; /* At least signature, version, totlen, count. */
if (ntohs(hdr->ver) != 0) return 1; /* Can't handle versions other than 0.*/
if (ntohs(hdr->ver) != CLUSTER_PROTO_VER)
return 1; /* Can't handle versions other than the current one.*/
if (totlen > sdslen(link->rcvbuf)) return 1;
if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_PONG ||
type == CLUSTERMSG_TYPE_MEET)
@@ -1544,18 +1567,20 @@ int clusterProcessPacket(clusterLink *link) {
* later if we changed address, and those nodes will use our
* official address to connect to us. So by obtaining this address
* from the socket is a simple way to discover / update our own
* address in the cluster without it being hardcoded in the config. */
if (type == CLUSTERMSG_TYPE_MEET) {
* address in the cluster without it being hardcoded in the config.
*
* However if we don't have an address at all, we update the address
* even with a normal PING packet. If it's wrong it will be fixed
* by MEET later. */
if (type == CLUSTERMSG_TYPE_MEET || myself->ip[0] == '\0') {
char ip[REDIS_IP_STR_LEN];
if (anetSockName(link->fd,ip,sizeof(ip),NULL) != -1 &&
strcmp(ip,myself->ip))
{
memcpy(myself->ip,ip,REDIS_IP_STR_LEN);
anetFormatAddr(ip, sizeof(ip), myself->ip, -1);
redisLog(REDIS_WARNING,"IP address for this node updated to %s",
ip);
myself->ip);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
}
}
@@ -1606,7 +1631,7 @@ int clusterProcessPacket(clusterLink *link) {
}
/* Free this node as we already have it. This will
* cause the link to be freed as well. */
freeClusterNode(link->node);
clusterDelNode(link->node);
return 0;
}
@@ -2013,7 +2038,8 @@ void clusterBroadcastMessage(void *buf, size_t len) {
dictReleaseIterator(di);
}
/* Build the message header */
/* Build the message header. hdr must point to a buffer at least
* sizeof(clusterMsg) in bytes. */
void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
int totlen = 0;
uint64_t offset;
@@ -2027,6 +2053,7 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
myself->slaveof : myself;
memset(hdr,0,sizeof(*hdr));
hdr->ver = htons(CLUSTER_PROTO_VER);
hdr->sig[0] = 'R';
hdr->sig[1] = 'C';
hdr->sig[2] = 'm';
@@ -2073,40 +2100,90 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
/* Send a PING or PONG packet to the specified node, making sure to add enough
* gossip informations. */
void clusterSendPing(clusterLink *link, int type) {
unsigned char buf[sizeof(clusterMsg)+sizeof(clusterMsgDataGossip)*3];
clusterMsg *hdr = (clusterMsg*) buf;
int gossipcount = 0, totlen;
/* freshnodes is the number of nodes we can still use to populate the
* gossip section of the ping packet. Basically we start with the nodes
* we have in memory minus two (ourself and the node we are sending the
* message to). Every time we add a node we decrement the counter, so when
* it will drop to <= zero we know there is no more gossip info we can
* send. */
unsigned char *buf;
clusterMsg *hdr;
int gossipcount = 0; /* Number of gossip sections added so far. */
int wanted; /* Number of gossip sections we want to append if possible. */
int totlen; /* Total packet length. */
/* freshnodes is the max number of nodes we can hope to append at all:
* nodes available minus two (ourself and the node we are sending the
* message to). However practically there may be less valid nodes since
* nodes in handshake state, disconnected, are not considered. */
int freshnodes = dictSize(server.cluster->nodes)-2;
/* How many gossip sections we want to add? 1/10 of the number of nodes
* and anyway at least 3. Why 1/10?
*
* If we have N masters, with N/10 entries, and we consider that in
* node_timeout we exchange with each other node at least 4 packets
* (we ping in the worst case in node_timeout/2 time, and we also
* receive two pings from the host), we have a total of 8 packets
* in the node_timeout*2 falure reports validity time. So we have
* that, for a single PFAIL node, we can expect to receive the following
* number of failure reports (in the specified window of time):
*
* PROB * GOSSIP_ENTRIES_PER_PACKET * TOTAL_PACKETS:
*
* PROB = probability of being featured in a single gossip entry,
* which is 1 / NUM_OF_NODES.
* ENTRIES = 10.
* TOTAL_PACKETS = 2 * 4 * NUM_OF_MASTERS.
*
* If we assume we have just masters (so num of nodes and num of masters
* is the same), with 1/10 we always get over the majority, and specifically
* 80% of the number of nodes, to account for many masters failing at the
* same time.
*
* Since we have non-voting slaves that lower the probability of an entry
* to feature our node, we set the number of entires per packet as
* 10% of the total nodes we have. */
wanted = floor(dictSize(server.cluster->nodes)/10);
if (wanted < 3) wanted = 3;
if (wanted > freshnodes) wanted = freshnodes;
/* Compute the maxium totlen to allocate our buffer. We'll fix the totlen
* later according to the number of gossip sections we really were able
* to put inside the packet. */
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
totlen += (sizeof(clusterMsgDataGossip)*wanted);
/* Note: clusterBuildMessageHdr() expects the buffer to be always at least
* sizeof(clusterMsg) or more. */
if (totlen < (int)sizeof(clusterMsg)) totlen = sizeof(clusterMsg);
buf = zcalloc(totlen);
hdr = (clusterMsg*) buf;
/* Populate the header. */
if (link->node && type == CLUSTERMSG_TYPE_PING)
link->node->ping_sent = mstime();
clusterBuildMessageHdr(hdr,type);
/* Populate the gossip fields */
while(freshnodes > 0 && gossipcount < 3) {
int maxiterations = wanted*3;
while(freshnodes > 0 && gossipcount < wanted && maxiterations--) {
dictEntry *de = dictGetRandomKey(server.cluster->nodes);
clusterNode *this = dictGetVal(de);
clusterMsgDataGossip *gossip;
int j;
/* Don't include this node: the whole packet header is about us
* already, so we just gossip about other nodes. */
if (this == myself) continue;
/* Give a bias to FAIL/PFAIL nodes. */
if (maxiterations > wanted*2 &&
!(this->flags & (REDIS_NODE_PFAIL|REDIS_NODE_FAIL)))
continue;
/* In the gossip section don't include:
* 1) Myself.
* 2) Nodes in HANDSHAKE state.
* 1) Nodes in HANDSHAKE state.
* 3) Nodes with the NOADDR flag set.
* 4) Disconnected nodes if they don't have configured slots.
*/
if (this == myself ||
this->flags & (REDIS_NODE_HANDSHAKE|REDIS_NODE_NOADDR) ||
if (this->flags & (REDIS_NODE_HANDSHAKE|REDIS_NODE_NOADDR) ||
(this->link == NULL && this->numslots == 0))
{
freshnodes--; /* otherwise we may loop forever. */
continue;
freshnodes--; /* Tecnically not correct, but saves CPU. */
continue;
}
/* Check if we already added this node */
@@ -2125,13 +2202,19 @@ void clusterSendPing(clusterLink *link, int type) {
memcpy(gossip->ip,this->ip,sizeof(this->ip));
gossip->port = htons(this->port);
gossip->flags = htons(this->flags);
gossip->notused1 = 0;
gossip->notused2 = 0;
gossipcount++;
}
/* Ready to send... fix the totlen fiend and queue the message in the
* output buffer. */
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
totlen += (sizeof(clusterMsgDataGossip)*gossipcount);
hdr->count = htons(gossipcount);
hdr->totlen = htonl(totlen);
clusterSendMessage(link,buf,totlen);
zfree(buf);
}
/* Send a PONG packet to every connected node that's not in handshake state
@@ -2778,6 +2861,7 @@ void clusterHandleSlaveMigration(int max_slaves) {
}
}
}
dictReleaseIterator(di);
/* Step 4: perform the migration if there is a target, and if I'm the
* candidate. */
@@ -2899,7 +2983,7 @@ void clusterCron(void) {
/* A Node in HANDSHAKE state has a limited lifespan equal to the
* configured node timeout. */
if (nodeInHandshake(node) && now - node->ctime > handshake_timeout) {
freeClusterNode(node);
clusterDelNode(node);
continue;
}
@@ -4010,6 +4094,18 @@ void clusterCommand(redisClient *c) {
addReplyBulkCString(c,ni);
sdsfree(ni);
}
} else if (!strcasecmp(c->argv[1]->ptr,"count-failure-reports") &&
c->argc == 3)
{
/* CLUSTER COUNT-FAILURE-REPORTS <NODE ID> */
clusterNode *n = clusterLookupNode(c->argv[2]->ptr);
if (!n) {
addReplyErrorFormat(c,"Unknown node %s", (char*)c->argv[2]->ptr);
return;
} else {
addReplyLongLong(c,clusterNodeFailureReportsCount(n));
}
} else if (!strcasecmp(c->argv[1]->ptr,"failover") &&
(c->argc == 2 || c->argc == 3))
{
@@ -4266,11 +4362,12 @@ void restoreCommand(redisClient *c) {
typedef struct migrateCachedSocket {
int fd;
long last_dbid;
time_t last_use_time;
} migrateCachedSocket;
/* Return a TCP socket connected with the target instance, possibly returning
* a cached one.
/* Return a migrateCachedSocket containing a TCP socket connected with the
* target instance, possibly returning a cached one.
*
* This function is responsible of sending errors to the client if a
* connection can't be established. In this case -1 is returned.
@@ -4280,7 +4377,7 @@ typedef struct migrateCachedSocket {
* If the caller detects an error while using the socket, migrateCloseSocket()
* should be called so that the connection will be created from scratch
* the next time. */
int migrateGetSocket(redisClient *c, robj *host, robj *port, long timeout) {
migrateCachedSocket* migrateGetSocket(redisClient *c, robj *host, robj *port, long timeout) {
int fd;
sds name = sdsempty();
migrateCachedSocket *cs;
@@ -4293,7 +4390,7 @@ int migrateGetSocket(redisClient *c, robj *host, robj *port, long timeout) {
if (cs) {
sdsfree(name);
cs->last_use_time = server.unixtime;
return cs->fd;
return cs;
}
/* No cached socket, create one. */
@@ -4313,7 +4410,7 @@ int migrateGetSocket(redisClient *c, robj *host, robj *port, long timeout) {
sdsfree(name);
addReplyErrorFormat(c,"Can't connect to target node: %s",
server.neterr);
return -1;
return NULL;
}
anetEnableTcpNoDelay(server.neterr,fd);
@@ -4323,15 +4420,16 @@ int migrateGetSocket(redisClient *c, robj *host, robj *port, long timeout) {
addReplySds(c,
sdsnew("-IOERR error or timeout connecting to the client\r\n"));
close(fd);
return -1;
return NULL;
}
/* Add to the cache and return it to the caller. */
cs = zmalloc(sizeof(*cs));
cs->fd = fd;
cs->last_dbid = -1;
cs->last_use_time = server.unixtime;
dictAdd(server.migrate_cached_sockets,name,cs);
return fd;
return cs;
}
/* Free a migrate cached connection. */
@@ -4372,7 +4470,8 @@ void migrateCloseTimedoutSockets(void) {
/* MIGRATE host port key dbid timeout [COPY | REPLACE] */
void migrateCommand(redisClient *c) {
int fd, copy, replace, j;
migrateCachedSocket *cs;
int copy, replace, j;
long timeout;
long dbid;
long long ttl, expireat;
@@ -4414,15 +4513,20 @@ try_again:
}
/* Connect */
fd = migrateGetSocket(c,c->argv[1],c->argv[2],timeout);
if (fd == -1) return; /* error sent to the client by migrateGetSocket() */
cs = migrateGetSocket(c,c->argv[1],c->argv[2],timeout);
if (cs == NULL) return; /* error sent to the client by migrateGetSocket() */
rioInitWithBuffer(&cmd,sdsempty());
/* Send the SELECT command if the current DB is not already selected. */
int select = cs->last_dbid != dbid; /* Should we emit SELECT? */
if (select) {
redisAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',2));
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"SELECT",6));
redisAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,dbid));
}
/* Create RESTORE payload and generate the protocol to call the command. */
rioInitWithBuffer(&cmd,sdsempty());
redisAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',2));
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"SELECT",6));
redisAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,dbid));
expireat = getExpire(c->db,c->argv[3]);
if (expireat != -1) {
ttl = expireat-mstime();
@@ -4456,11 +4560,11 @@ try_again:
{
sds buf = cmd.io.buffer.ptr;
size_t pos = 0, towrite;
int nwritten = 0;
ssize_t nwritten = 0;
while ((towrite = sdslen(buf)-pos) > 0) {
towrite = (towrite > (64*1024) ? (64*1024) : towrite);
nwritten = syncWrite(fd,buf+pos,towrite,timeout);
nwritten = syncWrite(cs->fd,buf+pos,towrite,timeout);
if (nwritten != (signed)towrite) goto socket_wr_err;
pos += nwritten;
}
@@ -4472,28 +4576,33 @@ try_again:
char buf2[1024];
/* Read the two replies */
if (syncReadLine(fd, buf1, sizeof(buf1), timeout) <= 0)
if (select && syncReadLine(cs->fd, buf1, sizeof(buf1), timeout) <= 0)
goto socket_rd_err;
if (syncReadLine(fd, buf2, sizeof(buf2), timeout) <= 0)
if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0)
goto socket_rd_err;
if (buf1[0] == '-' || buf2[0] == '-') {
if ((select && buf1[0] == '-') || buf2[0] == '-') {
/* On error assume that last_dbid is no longer valid. */
cs->last_dbid = -1;
addReplyErrorFormat(c,"Target instance replied with error: %s",
(buf1[0] == '-') ? buf1+1 : buf2+1);
(select && buf1[0] == '-') ? buf1+1 : buf2+1);
} else {
/* Update the last_dbid in migrateCachedSocket */
cs->last_dbid = dbid;
robj *aux;
addReply(c,shared.ok);
if (!copy) {
/* No COPY option: remove the local key, signal the change. */
dbDelete(c->db,c->argv[3]);
signalModifiedKey(c->db,c->argv[3]);
}
addReply(c,shared.ok);
server.dirty++;
server.dirty++;
/* Translate MIGRATE as DEL for replication/AOF. */
aux = createStringObject("DEL",3);
rewriteClientCommandVector(c,2,aux,c->argv[3]);
decrRefCount(aux);
/* Translate MIGRATE as DEL for replication/AOF. */
aux = createStringObject("DEL",3);
rewriteClientCommandVector(c,2,aux,c->argv[3]);
decrRefCount(aux);
}
}
}
+6 -4
View File
@@ -163,10 +163,11 @@ typedef struct {
char nodename[REDIS_CLUSTER_NAMELEN];
uint32_t ping_sent;
uint32_t pong_received;
char ip[REDIS_IP_STR_LEN]; /* IP address last time it was seen */
uint16_t port; /* port last time it was seen */
uint16_t flags;
uint32_t notused; /* for 64 bit alignment */
char ip[REDIS_IP_STR_LEN]; /* IP address last time it was seen */
uint16_t port; /* port last time it was seen */
uint16_t flags; /* node->flags copy */
uint16_t notused1; /* Some room for future improvements. */
uint32_t notused2;
} clusterMsgDataGossip;
typedef struct {
@@ -211,6 +212,7 @@ union clusterMsgData {
} update;
};
#define CLUSTER_PROTO_VER 0 /* Cluster bus protocol version. */
typedef struct {
char sig[4]; /* Siganture "RCmb" (Redis Cluster message bus). */
+72 -18
View File
@@ -60,6 +60,8 @@ clientBufferLimitsConfig clientBufferLimitsDefaults[REDIS_CLIENT_TYPE_COUNT] = {
* Config file parsing
*----------------------------------------------------------------------------*/
int supervisedToMode(const char *str);
int yesnotoi(char *s) {
if (!strcasecmp(s,"yes")) return 1;
else if (!strcasecmp(s,"no")) return 0;
@@ -397,9 +399,13 @@ void loadServerConfigFromString(char *config) {
} else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) {
server.hash_max_ziplist_value = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
server.list_max_ziplist_entries = memtoll(argv[1], NULL);
/* DEAD OPTION */
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
server.list_max_ziplist_value = memtoll(argv[1], NULL);
/* DEAD OPTION */
} else if (!strcasecmp(argv[0],"list-max-ziplist-size") && argc == 2) {
server.list_max_ziplist_size = atoi(argv[1]);
} else if (!strcasecmp(argv[0],"list-compress-depth") && argc == 2) {
server.list_compress_depth = atoi(argv[1]);
} else if (!strcasecmp(argv[0],"set-max-intset-entries") && argc == 2) {
server.set_max_intset_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"zset-max-ziplist-entries") && argc == 2) {
@@ -529,6 +535,15 @@ void loadServerConfigFromString(char *config) {
goto loaderr;
}
server.notify_keyspace_events = flags;
} else if (!strcasecmp(argv[0],"supervised") && argc == 2) {
int mode = supervisedToMode(argv[1]);
if (mode == -1) {
err = "Invalid option for 'supervised'. "
"Allowed values: 'upstart', 'systemd', 'auto', or 'no'";
goto loaderr;
}
server.supervised_mode = mode;
} else if (!strcasecmp(argv[0],"sentinel")) {
/* argc == 1 is handled by main() as we need to enter the sentinel
* mode ASAP. */
@@ -609,6 +624,7 @@ void loadServerConfig(char *filename, char *options) {
void configSetCommand(redisClient *c) {
robj *o;
long long ll;
int err;
redisAssertWithInfo(c,c->argv[2],sdsEncodedObject(c->argv[2]));
redisAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3]));
o = c->argv[3];
@@ -628,8 +644,8 @@ void configSetCommand(redisClient *c) {
zfree(server.masterauth);
server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0) goto badfmt;
ll = memtoll(o->ptr,&err);
if (err || ll < 0) goto badfmt;
server.maxmemory = ll;
if (server.maxmemory) {
if (server.maxmemory < zmalloc_used_memory()) {
@@ -795,12 +811,12 @@ void configSetCommand(redisClient *c) {
} else if (!strcasecmp(c->argv[2]->ptr,"hash-max-ziplist-value")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.hash_max_ziplist_value = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-entries")) {
} else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-size")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.list_max_ziplist_entries = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-value")) {
server.list_max_ziplist_size = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"list-compress-depth")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.list_max_ziplist_value = ll;
server.list_compress_depth = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"set-max-intset-entries")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.set_max_intset_entries = ll;
@@ -851,7 +867,6 @@ void configSetCommand(redisClient *c) {
* whole configuration string or accept it all, even if a single
* error in a single client class is present. */
for (j = 0; j < vlen; j++) {
char *eptr;
long val;
if ((j % 4) == 0) {
@@ -860,8 +875,8 @@ void configSetCommand(redisClient *c) {
goto badfmt;
}
} else {
val = strtoll(v[j], &eptr, 10);
if (eptr[0] != '\0' || val < 0) {
val = memtoll(v[j], &err);
if (err || val < 0) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
@@ -895,7 +910,8 @@ void configSetCommand(redisClient *c) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
server.repl_timeout = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-size")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
ll = memtoll(o->ptr,&err);
if (err || ll < 0) goto badfmt;
resizeReplicationBacklog(ll);
} else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-ttl")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
@@ -1018,6 +1034,33 @@ char *maxmemoryToString() {
return s;
}
int supervisedToMode(const char *str) {
int mode;
if (!strcasecmp(str,"upstart")) {
mode = REDIS_SUPERVISED_UPSTART;
} else if (!strcasecmp(str,"systemd")) {
mode = REDIS_SUPERVISED_SYSTEMD;
} else if (!strcasecmp(str,"auto")) {
mode = REDIS_SUPERVISED_AUTODETECT;
} else if (!strcasecmp(str,"no")) {
mode = REDIS_SUPERVISED_NONE;
} else {
mode = -1;
}
return mode;
}
char *supervisedToString(void) {
char *s;
switch(server.supervised_mode) {
case REDIS_SUPERVISED_UPSTART: s = "upstart"; break;
case REDIS_SUPERVISED_SYSTEMD: s = "systemd"; break;
case REDIS_SUPERVISED_AUTODETECT: s = "auto"; break;
case REDIS_SUPERVISED_NONE: s = "no"; break;
default: s = "no"; break;
}
return s;
}
void configGetCommand(redisClient *c) {
robj *o = c->argv[2];
void *replylen = addDeferredMultiBulkLength(c);
@@ -1047,10 +1090,10 @@ void configGetCommand(redisClient *c) {
server.hash_max_ziplist_entries);
config_get_numerical_field("hash-max-ziplist-value",
server.hash_max_ziplist_value);
config_get_numerical_field("list-max-ziplist-entries",
server.list_max_ziplist_entries);
config_get_numerical_field("list-max-ziplist-value",
server.list_max_ziplist_value);
config_get_numerical_field("list-max-ziplist-size",
server.list_max_ziplist_size);
config_get_numerical_field("list-compress-depth",
server.list_compress_depth);
config_get_numerical_field("set-max-intset-entries",
server.set_max_intset_entries);
config_get_numerical_field("zset-max-ziplist-entries",
@@ -1173,6 +1216,11 @@ void configGetCommand(redisClient *c) {
addReplyBulkCString(c,s);
matches++;
}
if (stringmatch(pattern,"supervised",0)) {
addReplyBulkCString(c,"supervised");
addReplyBulkCString(c,supervisedToString());
matches++;
}
if (stringmatch(pattern,"client-output-buffer-limit",0)) {
sds buf = sdsempty();
int j;
@@ -1857,8 +1905,8 @@ int rewriteConfig(char *path) {
rewriteConfigNotifykeyspaceeventsOption(state);
rewriteConfigNumericalOption(state,"hash-max-ziplist-entries",server.hash_max_ziplist_entries,REDIS_HASH_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"hash-max-ziplist-value",server.hash_max_ziplist_value,REDIS_HASH_MAX_ZIPLIST_VALUE);
rewriteConfigNumericalOption(state,"list-max-ziplist-entries",server.list_max_ziplist_entries,REDIS_LIST_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"list-max-ziplist-value",server.list_max_ziplist_value,REDIS_LIST_MAX_ZIPLIST_VALUE);
rewriteConfigNumericalOption(state,"list-max-ziplist-size",server.list_max_ziplist_size,REDIS_LIST_MAX_ZIPLIST_SIZE);
rewriteConfigNumericalOption(state,"list-compress-depth",server.list_compress_depth,REDIS_LIST_COMPRESS_DEPTH);
rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,REDIS_SET_MAX_INTSET_ENTRIES);
rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,REDIS_ZSET_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,REDIS_ZSET_MAX_ZIPLIST_VALUE);
@@ -1868,6 +1916,12 @@ int rewriteConfig(char *path) {
rewriteConfigNumericalOption(state,"hz",server.hz,REDIS_DEFAULT_HZ);
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,REDIS_DEFAULT_AOF_LOAD_TRUNCATED);
rewriteConfigEnumOption(state,"supervised",server.supervised_mode,
"upstart", REDIS_SUPERVISED_UPSTART,
"systemd", REDIS_SUPERVISED_SYSTEMD,
"auto", REDIS_SUPERVISED_AUTODETECT,
"no", REDIS_SUPERVISED_NONE,
NULL, REDIS_SUPERVISED_NONE);
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
/* Step 3: remove all the orphaned lines in the old file, that is, lines
+7 -4
View File
@@ -34,6 +34,11 @@
#include <AvailabilityMacros.h>
#endif
#ifdef __linux__
#include <linux/version.h>
#include <features.h>
#endif
/* Define redis_fstat to fstat or fstat64() */
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
#define redis_fstat fstat64
@@ -57,7 +62,7 @@
#endif
/* Test for backtrace() */
#if defined(__APPLE__) || defined(__linux__)
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__))
#define HAVE_BACKTRACE 1
#endif
@@ -92,8 +97,6 @@
/* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use
* the plain fsync() call. */
#ifdef __linux__
#include <linux/version.h>
#include <features.h>
#if defined(__GLIBC__) && defined(__GLIBC_PREREQ)
#if (LINUX_VERSION_CODE >= 0x020611 && __GLIBC_PREREQ(2, 6))
#define HAVE_SYNC_FILE_RANGE 1
@@ -118,7 +121,7 @@
#define USE_SETPROCTITLE
#endif
#if (defined __linux || defined __APPLE__)
#if ((defined __linux && defined(__GLIBC__)) || defined __APPLE__)
#define USE_SETPROCTITLE
#define INIT_SETPROCTITLE_REPLACEMENT
void spt_init(int argc, char *argv[]);
+10 -6
View File
@@ -62,7 +62,7 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
if (expireIfNeeded(db,key) == 1) {
/* Key expired. If we are in the context of a master, expireIfNeeded()
* returns 0 only when the key does not exist at all, so it's save
* returns 0 only when the key does not exist at all, so it's safe
* to return NULL ASAP. */
if (server.masterhost == NULL) return NULL;
@@ -688,16 +688,20 @@ void shutdownCommand(redisClient *c) {
void renameGenericCommand(redisClient *c, int nx) {
robj *o;
long long expire;
int samekey = 0;
/* To use the same key as src and dst is probably an error */
if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) {
addReply(c,shared.sameobjecterr);
return;
}
/* When source and dest key is the same, no operation is performed,
* if the key exists, however we still return an error on unexisting key. */
if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) samekey = 1;
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL)
return;
if (samekey) {
addReply(c,nx ? shared.czero : shared.ok);
return;
}
incrRefCount(o);
expire = getExpire(c->db,c->argv[1]);
if (lookupKeyWrite(c->db,c->argv[2]) != NULL) {
+44 -4
View File
@@ -301,13 +301,46 @@ void debugCommand(redisClient *c) {
val = dictGetVal(de);
strenc = strEncoding(val->encoding);
char extra[128] = {0};
if (val->encoding == REDIS_ENCODING_QUICKLIST) {
char *nextra = extra;
int remaining = sizeof(extra);
quicklist *ql = val->ptr;
/* Add number of quicklist nodes */
int used = snprintf(nextra, remaining, " ql_nodes:%u", ql->len);
nextra += used;
remaining -= used;
/* Add average quicklist fill factor */
double avg = (double)ql->count/ql->len;
used = snprintf(nextra, remaining, " ql_avg_node:%.2f", avg);
nextra += used;
remaining -= used;
/* Add quicklist fill level / max ziplist size */
used = snprintf(nextra, remaining, " ql_ziplist_max:%d", ql->fill);
nextra += used;
remaining -= used;
/* Add isCompressed? */
int compressed = ql->compress != 0;
used = snprintf(nextra, remaining, " ql_compressed:%d", compressed);
nextra += used;
remaining -= used;
/* Add total uncompressed size */
unsigned long sz = 0;
for (quicklistNode *node = ql->head; node; node = node->next) {
sz += node->sz;
}
used = snprintf(nextra, remaining, " ql_uncompressed_size:%lu", sz);
nextra += used;
remaining -= used;
}
addReplyStatusFormat(c,
"Value at:%p refcount:%d "
"encoding:%s serializedlength:%lld "
"lru:%d lru_seconds_idle:%llu",
"encoding:%s serializedlength:%zu "
"lru:%d lru_seconds_idle:%llu%s",
(void*)val, val->refcount,
strenc, (long long) rdbSavedObjectLen(val),
val->lru, estimateObjectIdleTime(val)/1000);
strenc, rdbSavedObjectLen(val),
val->lru, estimateObjectIdleTime(val)/1000, extra);
} else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) {
dictEntry *de;
robj *val;
@@ -385,6 +418,13 @@ void debugCommand(redisClient *c) {
errstr = sdsmapchars(errstr,"\n\r"," ",2); /* no newlines in errors. */
errstr = sdscatlen(errstr,"\r\n",2);
addReplySds(c,errstr);
} else if (!strcasecmp(c->argv[1]->ptr,"structsize") && c->argc == 2) {
sds sizes = sdsempty();
sizes = sdscatprintf(sizes,"bits:%d ", (sizeof(void*) == 8)?64:32);
sizes = sdscatprintf(sizes,"robj:%d ", (int)sizeof(robj));
sizes = sdscatprintf(sizes,"dictentry:%d ", (int)sizeof(dictEntry));
sizes = sdscatprintf(sizes,"sdshdr:%d", (int)sizeof(struct sdshdr));
addReplyBulkSds(c,sizes);
} else if (!strcasecmp(c->argv[1]->ptr,"jemalloc") && c->argc == 3) {
#if defined(USE_JEMALLOC)
if (!strcasecmp(c->argv[2]->ptr, "info")) {
+94 -36
View File
@@ -211,6 +211,9 @@ int dictExpand(dict *d, unsigned long size)
if (dictIsRehashing(d) || d->ht[0].used > size)
return DICT_ERR;
/* Rehashing to the same table size is not useful. */
if (realsize == d->ht[0].size) return DICT_ERR;
/* Allocate the new hash table and initialize all pointers to NULL */
n.size = realsize;
n.sizemask = realsize-1;
@@ -232,27 +235,27 @@ int dictExpand(dict *d, unsigned long size)
/* Performs N steps of incremental rehashing. Returns 1 if there are still
* keys to move from the old to the new hash table, otherwise 0 is returned.
*
* Note that a rehashing step consists in moving a bucket (that may have more
* than one key as we use chaining) from the old to the new hash table. */
* than one key as we use chaining) from the old to the new hash table, however
* since part of the hash table may be composed of empty spaces, it is not
* guaranteed that this function will rehash even a single bucket, since it
* will visit at max N*10 empty buckets in total, otherwise the amount of
* work it does would be unbound and the function may block for a long time. */
int dictRehash(dict *d, int n) {
int empty_visits = n*10; /* Max number of empty buckets to visit. */
if (!dictIsRehashing(d)) return 0;
while(n--) {
while(n-- && d->ht[0].used != 0) {
dictEntry *de, *nextde;
/* Check if we already rehashed the whole table... */
if (d->ht[0].used == 0) {
zfree(d->ht[0].table);
d->ht[0] = d->ht[1];
_dictReset(&d->ht[1]);
d->rehashidx = -1;
return 0;
}
/* Note that rehashidx can't overflow as we are sure there are more
* elements because ht[0].used != 0 */
assert(d->ht[0].size > (unsigned long)d->rehashidx);
while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
while(d->ht[0].table[d->rehashidx] == NULL) {
d->rehashidx++;
if (--empty_visits == 0) return 1;
}
de = d->ht[0].table[d->rehashidx];
/* Move all the keys in this bucket from the old to the new hash HT */
while(de) {
@@ -270,6 +273,17 @@ int dictRehash(dict *d, int n) {
d->ht[0].table[d->rehashidx] = NULL;
d->rehashidx++;
}
/* Check if we already rehashed the whole table... */
if (d->ht[0].used == 0) {
zfree(d->ht[0].table);
d->ht[0] = d->ht[1];
_dictReset(&d->ht[1]);
d->rehashidx = -1;
return 0;
}
/* More to rehash... */
return 1;
}
@@ -342,7 +356,10 @@ dictEntry *dictAddRaw(dict *d, void *key)
if ((index = _dictKeyIndex(d, key)) == -1)
return NULL;
/* Allocate the memory and store the new entry */
/* Allocate the memory and store the new entry.
* Insert the element in top, with the assumption that in a database
* system it is more likely that recently added entries are accessed
* more frequently. */
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
entry = zmalloc(sizeof(*entry));
entry->next = ht->table[index];
@@ -616,7 +633,11 @@ dictEntry *dictGetRandomKey(dict *d)
if (dictIsRehashing(d)) _dictRehashStep(d);
if (dictIsRehashing(d)) {
do {
h = random() % (d->ht[0].size+d->ht[1].size);
/* We are sure there are no elements in indexes from 0
* to rehashidx-1 */
h = d->rehashidx + (random() % (d->ht[0].size +
d->ht[1].size -
d->rehashidx));
he = (h >= d->ht[0].size) ? d->ht[1].table[h - d->ht[0].size] :
d->ht[0].table[h];
} while(he == NULL);
@@ -643,9 +664,12 @@ dictEntry *dictGetRandomKey(dict *d)
return he;
}
/* This is a version of dictGetRandomKey() that is modified in order to
* return multiple entries by jumping at a random place of the hash table
* and scanning linearly for entries.
/* This function samples the dictionary to return a few keys from random
* locations.
*
* It does not guarantee to return all the keys specified in 'count', nor
* it does guarantee to return non-duplicated elements, however it will make
* some effort to do both things.
*
* Returned pointers to hash table entries are stored into 'des' that
* points to an array of dictEntry pointers. The array must have room for
@@ -654,28 +678,65 @@ dictEntry *dictGetRandomKey(dict *d)
*
* The function returns the number of items stored into 'des', that may
* be less than 'count' if the hash table has less than 'count' elements
* inside.
* inside, or if not enough elements were found in a reasonable amount of
* steps.
*
* Note that this function is not suitable when you need a good distribution
* of the returned items, but only when you need to "sample" a given number
* of continuous elements to run some kind of algorithm or to produce
* statistics. However the function is much faster than dictGetRandomKey()
* at producing N elements, and the elements are guaranteed to be non
* repeating. */
unsigned int dictGetRandomKeys(dict *d, dictEntry **des, unsigned int count) {
int j; /* internal hash table id, 0 or 1. */
unsigned int stored = 0;
* at producing N elements. */
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
unsigned int j; /* internal hash table id, 0 or 1. */
unsigned int tables; /* 1 or 2 tables? */
unsigned int stored = 0, maxsizemask;
unsigned int maxsteps;
if (dictSize(d) < count) count = dictSize(d);
while(stored < count) {
for (j = 0; j < 2; j++) {
/* Pick a random point inside the hash table 0 or 1. */
unsigned int i = random() & d->ht[j].sizemask;
int size = d->ht[j].size;
maxsteps = count*10;
/* Make sure to visit every bucket by iterating 'size' times. */
while(size--) {
dictEntry *he = d->ht[j].table[i];
/* Try to do a rehashing work proportional to 'count'. */
for (j = 0; j < count; j++) {
if (dictIsRehashing(d))
_dictRehashStep(d);
else
break;
}
tables = dictIsRehashing(d) ? 2 : 1;
maxsizemask = d->ht[0].sizemask;
if (tables > 1 && maxsizemask < d->ht[1].sizemask)
maxsizemask = d->ht[1].sizemask;
/* Pick a random point inside the larger table. */
unsigned int i = random() & maxsizemask;
unsigned int emptylen = 0; /* Continuous empty entries so far. */
while(stored < count && maxsteps--) {
for (j = 0; j < tables; j++) {
/* Invariant of the dict.c rehashing: up to the indexes already
* visited in ht[0] during the rehashing, there are no populated
* buckets, so we can skip ht[0] for indexes between 0 and idx-1. */
if (tables == 2 && j == 0 && i < d->rehashidx) {
/* Moreover, if we are currently out of range in the second
* table, there will be no elements in both tables up to
* the current rehashing index, so we jump if possible.
* (this happens when going from big to small table). */
if (i >= d->ht[1].size) i = d->rehashidx;
continue;
}
if (i >= d->ht[j].size) continue; /* Out of range for this table. */
dictEntry *he = d->ht[j].table[i];
/* Count contiguous empty buckets, and jump to other
* locations if they reach 'count' (with a minimum of 5). */
if (he == NULL) {
emptylen++;
if (emptylen >= 5 && emptylen > count) {
i = random() & maxsizemask;
emptylen = 0;
}
} else {
emptylen = 0;
while (he) {
/* Collect all the elements of the buckets found non
* empty while iterating. */
@@ -685,14 +746,11 @@ unsigned int dictGetRandomKeys(dict *d, dictEntry **des, unsigned int count) {
stored++;
if (stored == count) return stored;
}
i = (i+1) & d->ht[j].sizemask;
}
/* If there is only one table and we iterated it all, we should
* already have 'count' elements. Assert this condition. */
assert(dictIsRehashing(d) != 0);
}
i = (i+1) & maxsizemask;
}
return stored; /* Never reached. */
return stored;
}
/* Function to reverse bits. Algorithm from:
+1 -1
View File
@@ -164,7 +164,7 @@ dictIterator *dictGetSafeIterator(dict *d);
dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *d);
unsigned int dictGetRandomKeys(dict *d, dictEntry **des, unsigned int count);
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
void dictPrintStats(dict *d);
unsigned int dictGenHashFunction(const void *key, int len);
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
-84
View File
@@ -261,90 +261,6 @@ int64_t intsetRandom(intset *is) {
return _intsetGet(is,rand()%intrev32ifbe(is->length));
}
/* How many times bigger should the set length be compared to the requested
* count of members for us to use the Floyd algorithm instead of
* the Knuth algorithm */
#define RANDOMMEMBERS_ALGORITHM_SELECTION_RATIO (2)
/* Copies 'count' random members from the set into the 'values' array.
* 'values' must be an array of int64_t values, of length 'count'.
* Returns the amount of items returned. If this amount is less than 'count',
* then the remaining 'values' are left uninitialized. */
int intsetRandomMembers(intset *is, int64_t* values, int count) {
/* We don't check that is and values are non-NULL - the caller must
* play nice. */
int length = intsetLen(is);
if (count > length) {
/* Return everything in the set */
count = length;
}
/* Choose between the Knuth shuffle algorithm, O(1) space, O(length) time,
* and the Floyd algorithm, O(length) space, O(count) time. */
if ((RANDOMMEMBERS_ALGORITHM_SELECTION_RATIO * count) > length) {
/* If the count of members requested is almost the length of the set,
* use the Knuth shuffle algorithm, O(1) space, O(length) time. */
/* First, fill the values array with unique random indexes inside
* the set. */
int in, im, rn, rm;
im = 0;
for (in = 0; in < length && im < count; in++) {
rn = length - in;
rm = count - im;
if (rand() % rn < rm) {
values[im++] = in;
}
}
} else {
/* If the length is considerably more than the count of members
* requested, use Robert Floyd's algorithm, O(length) space,
* O(count) time.
* Based on Jon Bentley's Programming Pearls */
int64_t *is_used = zcalloc(sizeof(int64_t) * length);
int in, im, r;
r = 0;
im = 0;
for (in = length - count; in < length && im < count; in++) {
/* Generate a random number r */
r = rand() % (in + 1);
/* Do we already have the value in r? */
if (is_used[r]) {
/* Use in instead of the generated number */
r = in;
}
values[im++] = r ;
/* Mark it as used */
is_used[r] = 1;
}
zfree(is_used);
}
/* Replace each random index with the value stored there in the intset */
uint8_t encoding = intrev32ifbe(is->encoding);
for (int currentValue = 0; currentValue < count; currentValue++) {
values[currentValue] =
_intsetGetEncoded(is, values[currentValue], encoding);
}
return count;
}
/* Sets the value to the value at the given position. When this position is
* out of range the function returns 0, when in range it returns 1. */
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value) {
-1
View File
@@ -43,7 +43,6 @@ intset *intsetAdd(intset *is, int64_t value, uint8_t *success);
intset *intsetRemove(intset *is, int64_t value, int *success);
uint8_t intsetFind(intset *is, int64_t value);
int64_t intsetRandom(intset *is);
int intsetRandomMembers(intset *is, int64_t* value, int count);
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
uint32_t intsetLen(intset *is);
size_t intsetBlobLen(intset *is);
+12 -2
View File
@@ -228,6 +228,7 @@ sds createLatencyReport(void) {
int advise_write_load_info = 0; /* Print info about AOF and write load. */
int advise_hz = 0; /* Use higher HZ. */
int advise_large_objects = 0; /* Deletion of large objects. */
int advise_mass_eviction = 0; /* Avoid mass eviction of keys. */
int advise_relax_fsync_policy = 0; /* appendfsync always is slow. */
int advise_disable_thp = 0; /* AnonHugePages detected. */
int advices = 0;
@@ -247,7 +248,7 @@ sds createLatencyReport(void) {
dictEntry *de;
int eventnum = 0;
di = dictGetIterator(server.latency_events);
di = dictGetSafeIterator(server.latency_events);
while((de = dictNext(di)) != NULL) {
char *event = dictGetKey(de);
struct latencyTimeSeries *ts = dictGetVal(de);
@@ -364,11 +365,16 @@ sds createLatencyReport(void) {
}
/* Eviction cycle. */
if (!strcasecmp(event,"eviction-cycle")) {
if (!strcasecmp(event,"eviction-del")) {
advise_large_objects = 1;
advices++;
}
if (!strcasecmp(event,"eviction-cycle")) {
advise_mass_eviction = 1;
advices++;
}
report = sdscatlen(report,"\n",1);
}
dictReleaseIterator(di);
@@ -452,6 +458,10 @@ sds createLatencyReport(void) {
report = sdscat(report,"- Deleting, expiring or evicting (because of maxmemory policy) large objects is a blocking operation. If you have very large objects that are often deleted, expired, or evicted, try to fragment those objects into multiple smaller objects.\n");
}
if (advise_mass_eviction) {
report = sdscat(report,"- Sudden changes to the 'maxmemory' setting via 'CONFIG SET', or allocation of large objects via sets or sorted sets intersections, STORE option of SORT, Redis Cluster large keys migrations (RESTORE command), may create sudden memory pressure forcing the server to block trying to evict keys. \n");
}
if (advise_disable_thp) {
report = sdscat(report,"- I detected a non zero amount of anonymous huge pages used by your process. This creates very serious latency events in different conditions, especially when Redis is persisting on disk. To disable THP support use the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled', make sure to also add it into /etc/rc.local so that the command will be executed again after a reboot. Note that even if you have already disabled THP, you still need to restart the Redis process to get rid of the huge pages already created.\n");
}
+4
View File
@@ -86,4 +86,8 @@ int THPIsEnabled(void);
(var) >= server.latency_monitor_threshold) \
latencyAddSample((event),(var));
/* Remove time from a nested event. */
#define latencyRemoveNestedEvent(event_var,nested_var) \
event_var += nested_var;
#endif /* __LATENCY_H */
+41 -15
View File
@@ -49,7 +49,7 @@
* the difference between 15 and 14 is very small
* for small blocks (and 14 is usually a bit faster).
* For a low-memory/faster configuration, use HLOG == 13;
* For best compression, use 15 or 16 (or more, up to 23).
* For best compression, use 15 or 16 (or more, up to 22).
*/
#ifndef HLOG
# define HLOG 16
@@ -94,7 +94,7 @@
/*
* Avoid assigning values to errno variable? for some embedding purposes
* (linux kernel for example), this is necessary. NOTE: this breaks
* the documentation in lzf.h.
* the documentation in lzf.h. Avoiding errno has no speed impact.
*/
#ifndef AVOID_ERRNO
# define AVOID_ERRNO 0
@@ -121,16 +121,52 @@
# define CHECK_INPUT 1
#endif
/*
* Whether to store pointers or offsets inside the hash table. On
* 64 bit architetcures, pointers take up twice as much space,
* and might also be slower. Default is to autodetect.
*/
/*#define LZF_USER_OFFSETS autodetect */
/*****************************************************************************/
/* nothing should be changed below */
#ifdef __cplusplus
# include <cstring>
# include <climits>
using namespace std;
#else
# include <string.h>
# include <limits.h>
#endif
#ifndef LZF_USE_OFFSETS
# if defined (WIN32)
# define LZF_USE_OFFSETS defined(_M_X64)
# else
# if __cplusplus > 199711L
# include <cstdint>
# else
# include <stdint.h>
# endif
# define LZF_USE_OFFSETS (UINTPTR_MAX > 0xffffffffU)
# endif
#endif
typedef unsigned char u8;
typedef const u8 *LZF_STATE[1 << (HLOG)];
#if LZF_USE_OFFSETS
# define LZF_HSLOT_BIAS ((const u8 *)in_data)
typedef unsigned int LZF_HSLOT;
#else
# define LZF_HSLOT_BIAS 0
typedef const u8 *LZF_HSLOT;
#endif
typedef LZF_HSLOT LZF_STATE[1 << (HLOG)];
#if !STRICT_ALIGN
/* for unaligned accesses we need a 16 bit datatype. */
# include <limits.h>
# if USHRT_MAX == 65535
typedef unsigned short u16;
# elif UINT_MAX == 65535
@@ -142,17 +178,7 @@ typedef const u8 *LZF_STATE[1 << (HLOG)];
#endif
#if ULTRA_FAST
# if defined(VERY_FAST)
# undef VERY_FAST
# endif
#endif
#if INIT_HTAB
# ifdef __cplusplus
# include <cstring>
# else
# include <string.h>
# endif
# undef VERY_FAST
#endif
#endif
+19 -23
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2000-2008 Marc Alexander Lehmann <schmorp@schmorp.de>
* Copyright (c) 2000-2010 Marc Alexander Lehmann <schmorp@schmorp.de>
*
* Redistribution and use in source and binary forms, with or without modifica-
* tion, are permitted provided that the following conditions are met:
@@ -40,8 +40,8 @@
/*
* don't play with this unless you benchmark!
* decompression is not dependent on the hash function
* the hashing function might seem strange, just believe me
* the data format is not dependent on the hash function.
* the hash function might seem strange, just believe me,
* it works ;)
*/
#ifndef FRST
@@ -89,9 +89,9 @@
/*
* compressed format
*
* 000LLLLL <L+1> ; literal
* LLLooooo oooooooo ; backref L
* 111ooooo LLLLLLLL oooooooo ; backref L+7
* 000LLLLL <L+1> ; literal, L+1=1..33 octets
* LLLooooo oooooooo ; backref L+1=1..7 octets, o+1=1..4096 offset
* 111ooooo LLLLLLLL oooooooo ; backref L+8 octets, o+1=1..4096 offset
*
*/
@@ -106,7 +106,6 @@ lzf_compress (const void *const in_data, unsigned int in_len,
#if !LZF_STATE_ARG
LZF_STATE htab;
#endif
const u8 **hslot;
const u8 *ip = (const u8 *)in_data;
u8 *op = (u8 *)out_data;
const u8 *in_end = ip + in_len;
@@ -133,10 +132,6 @@ lzf_compress (const void *const in_data, unsigned int in_len,
#if INIT_HTAB
memset (htab, 0, sizeof (htab));
# if 0
for (hslot = htab; hslot < htab + HSIZE; hslot++)
*hslot++ = ip;
# endif
#endif
lit = 0; op++; /* start run */
@@ -144,24 +139,23 @@ lzf_compress (const void *const in_data, unsigned int in_len,
hval = FRST (ip);
while (ip < in_end - 2)
{
LZF_HSLOT *hslot;
hval = NEXT (hval, ip);
hslot = htab + IDX (hval);
ref = *hslot; *hslot = ip;
ref = *hslot + LZF_HSLOT_BIAS; *hslot = ip - LZF_HSLOT_BIAS;
if (1
#if INIT_HTAB
&& ref < ip /* the next test will actually take care of this, but this is faster */
#endif
&& (off = ip - ref - 1) < MAX_OFF
&& ip + 4 < in_end
&& ref > (u8 *)in_data
#if STRICT_ALIGN
&& ref[0] == ip[0]
&& ref[1] == ip[1]
&& ref[2] == ip[2]
#if STRICT_ALIGN
&& ((ref[1] << 8) | ref[0]) == ((ip[1] << 8) | ip[0])
#else
&& *(u16 *)ref == *(u16 *)ip
&& ref[2] == ip[2]
#endif
)
{
@@ -170,12 +164,13 @@ lzf_compress (const void *const in_data, unsigned int in_len,
unsigned int maxlen = in_end - ip - len;
maxlen = maxlen > MAX_REF ? MAX_REF : maxlen;
if (expect_false (op + 3 + 1 >= out_end)) /* first a faster conservative test */
if (op - !lit + 3 + 1 >= out_end) /* second the exact but rare test */
return 0;
op [- lit - 1] = lit - 1; /* stop run */
op -= !lit; /* undo run if length is zero */
if (expect_false (op + 3 + 1 >= out_end))
return 0;
for (;;)
{
if (expect_true (maxlen > 16))
@@ -222,6 +217,7 @@ lzf_compress (const void *const in_data, unsigned int in_len,
}
*op++ = off;
lit = 0; op++; /* start run */
ip += len + 1;
@@ -237,12 +233,12 @@ lzf_compress (const void *const in_data, unsigned int in_len,
hval = FRST (ip);
hval = NEXT (hval, ip);
htab[IDX (hval)] = ip;
htab[IDX (hval)] = ip - LZF_HSLOT_BIAS;
ip++;
# if VERY_FAST && !ULTRA_FAST
hval = NEXT (hval, ip);
htab[IDX (hval)] = ip;
htab[IDX (hval)] = ip - LZF_HSLOT_BIAS;
ip++;
# endif
#else
@@ -251,7 +247,7 @@ lzf_compress (const void *const in_data, unsigned int in_len,
do
{
hval = NEXT (hval, ip);
htab[IDX (hval)] = ip;
htab[IDX (hval)] = ip - LZF_HSLOT_BIAS;
ip++;
}
while (len--);
+46 -11
View File
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2000-2007 Marc Alexander Lehmann <schmorp@schmorp.de>
* Copyright (c) 2000-2010 Marc Alexander Lehmann <schmorp@schmorp.de>
*
* Redistribution and use in source and binary forms, with or without modifica-
* tion, are permitted provided that the following conditions are met:
@@ -43,14 +43,14 @@
# define SET_ERRNO(n) errno = (n)
#endif
/*
#if USE_REP_MOVSB /* small win on amd, big loss on intel */
#if (__i386 || __amd64) && __GNUC__ >= 3
# define lzf_movsb(dst, src, len) \
asm ("rep movsb" \
: "=D" (dst), "=S" (src), "=c" (len) \
: "0" (dst), "1" (src), "2" (len));
#endif
*/
#endif
unsigned int
lzf_decompress (const void *const in_data, unsigned int in_len,
@@ -86,9 +86,17 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
#ifdef lzf_movsb
lzf_movsb (op, ip, ctrl);
#else
do
*op++ = *ip++;
while (--ctrl);
switch (ctrl)
{
case 32: *op++ = *ip++; case 31: *op++ = *ip++; case 30: *op++ = *ip++; case 29: *op++ = *ip++;
case 28: *op++ = *ip++; case 27: *op++ = *ip++; case 26: *op++ = *ip++; case 25: *op++ = *ip++;
case 24: *op++ = *ip++; case 23: *op++ = *ip++; case 22: *op++ = *ip++; case 21: *op++ = *ip++;
case 20: *op++ = *ip++; case 19: *op++ = *ip++; case 18: *op++ = *ip++; case 17: *op++ = *ip++;
case 16: *op++ = *ip++; case 15: *op++ = *ip++; case 14: *op++ = *ip++; case 13: *op++ = *ip++;
case 12: *op++ = *ip++; case 11: *op++ = *ip++; case 10: *op++ = *ip++; case 9: *op++ = *ip++;
case 8: *op++ = *ip++; case 7: *op++ = *ip++; case 6: *op++ = *ip++; case 5: *op++ = *ip++;
case 4: *op++ = *ip++; case 3: *op++ = *ip++; case 2: *op++ = *ip++; case 1: *op++ = *ip++;
}
#endif
}
else /* back reference */
@@ -134,12 +142,39 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
len += 2;
lzf_movsb (op, ref, len);
#else
*op++ = *ref++;
*op++ = *ref++;
switch (len)
{
default:
len += 2;
do
*op++ = *ref++;
while (--len);
if (op >= ref + len)
{
/* disjunct areas */
memcpy (op, ref, len);
op += len;
}
else
{
/* overlapping, use octte by octte copying */
do
*op++ = *ref++;
while (--len);
}
break;
case 9: *op++ = *ref++;
case 8: *op++ = *ref++;
case 7: *op++ = *ref++;
case 6: *op++ = *ref++;
case 5: *op++ = *ref++;
case 4: *op++ = *ref++;
case 3: *op++ = *ref++;
case 2: *op++ = *ref++;
case 1: *op++ = *ref++;
case 0: *op++ = *ref++; /* two octets more */
*op++ = *ref++;
}
#endif
}
}
+42
View File
@@ -304,6 +304,9 @@ void touchWatchedKeysOnFlush(int dbid) {
}
}
/* WATCH key key key ...
* Add keys to the set of watched keys. If those keys are modified
* before the next transaction is executed, the transaction aborts. */
void watchCommand(redisClient *c) {
int j;
@@ -316,8 +319,47 @@ void watchCommand(redisClient *c) {
addReply(c,shared.ok);
}
/* UNWATCH
* Flush the set of watched keys. */
void unwatchCommand(redisClient *c) {
unwatchAllKeys(c);
c->flags &= (~REDIS_DIRTY_CAS);
addReply(c,shared.ok);
}
/* IF XX key key key ...
* IF NX key key key ...
* Abort transaction if condition is not met. */
void ifCommand(redisClient *c) {
int j;
int xx = 0, nx = 0;
if (!(c->flags & REDIS_MULTI)) {
addReplyError(c,"IF outside MULTI is not allowed");
return;
}
/* Check if it's xx or nx option, trying to do it reasonably fast. */
char *a = c->argv[1]->ptr;
if ((a[0] == 'n' || a[0] == 'N') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0')
{
nx = 1;
} else if ((a[0] == 'x' || a[0] == 'X') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0')
{
xx = 1;
} else {
addReply(c,shared.syntaxerr);
return;
}
/* Check if keys exist / don't exist, and flag the transaction if
* at least one key fails the test according to NX / XX option. */
for (j = 2; j < c->argc; j++) {
robj *o = lookupKeyRead(c->db,c->argv[j]);
if ((o != NULL && nx) || (o == NULL && xx))
c->flags |= REDIS_DIRTY_CAS;
}
addReply(c,shared.ok);
}
+15 -18
View File
@@ -797,7 +797,8 @@ void freeClientsInAsyncFreeQueue(void) {
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
redisClient *c = privdata;
int nwritten = 0, totwritten = 0, objlen;
ssize_t nwritten = 0, totwritten = 0;
size_t objlen;
size_t objmem;
robj *o;
REDIS_NOTUSED(el);
@@ -812,7 +813,7 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
/* If the buffer was sent, set bufpos to zero to continue with
* the remainder of the reply. */
if (c->sentlen == c->bufpos) {
if ((int)c->sentlen == c->bufpos) {
c->bufpos = 0;
c->sentlen = 0;
}
@@ -1105,18 +1106,19 @@ int processMultibulkBuffer(redisClient *c) {
}
void processInputBuffer(redisClient *c) {
server.current_client = c;
/* Keep processing while there is something in the input buffer */
while(sdslen(c->querybuf)) {
/* Return if clients are paused. */
if (!(c->flags & REDIS_SLAVE) && clientsArePaused()) return;
if (!(c->flags & REDIS_SLAVE) && clientsArePaused()) break;
/* Immediately abort if the client is in the middle of something. */
if (c->flags & REDIS_BLOCKED) return;
if (c->flags & REDIS_BLOCKED) break;
/* REDIS_CLOSE_AFTER_REPLY closes the connection once the reply is
* written to the client. Make sure to not let the reply grow after
* this flag has been set (i.e. don't process more commands). */
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;
if (c->flags & REDIS_CLOSE_AFTER_REPLY) break;
/* Determine request type when unknown. */
if (!c->reqtype) {
@@ -1144,6 +1146,7 @@ void processInputBuffer(redisClient *c) {
resetClient(c);
}
}
server.current_client = NULL;
}
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
@@ -1153,7 +1156,6 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
REDIS_NOTUSED(el);
REDIS_NOTUSED(mask);
server.current_client = c;
readlen = REDIS_IOBUF_LEN;
/* If this is a multi bulk request, and we are processing a bulk reply
* that is large enough, try to maximize the probability that the query
@@ -1175,7 +1177,7 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
nread = read(fd, c->querybuf+qblen, readlen);
if (nread == -1) {
if (errno == EAGAIN) {
nread = 0;
return;
} else {
redisLog(REDIS_VERBOSE, "Reading from client: %s",strerror(errno));
freeClient(c);
@@ -1186,15 +1188,11 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
freeClient(c);
return;
}
if (nread) {
sdsIncrLen(c->querybuf,nread);
c->lastinteraction = server.unixtime;
if (c->flags & REDIS_MASTER) c->reploff += nread;
server.stat_net_input_bytes += nread;
} else {
server.current_client = NULL;
return;
}
sdsIncrLen(c->querybuf,nread);
c->lastinteraction = server.unixtime;
if (c->flags & REDIS_MASTER) c->reploff += nread;
server.stat_net_input_bytes += nread;
if (sdslen(c->querybuf) > server.client_max_querybuf_len) {
sds ci = catClientInfoString(sdsempty(),c), bytes = sdsempty();
@@ -1206,7 +1204,6 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
processInputBuffer(c);
server.current_client = NULL;
}
void getClientsMaxBuffers(unsigned long *longest_output_list,
@@ -1621,7 +1618,7 @@ int checkClientOutputBufferLimits(redisClient *c) {
* called from contexts where the client can't be freed safely, i.e. from the
* lower level functions pushing data inside the client output buffers. */
void asyncCloseClientOnOutputBufferLimitReached(redisClient *c) {
redisAssert(c->reply_bytes < ULONG_MAX-(1024*64));
redisAssert(c->reply_bytes < SIZE_MAX-(1024*64));
if (c->reply_bytes == 0 || c->flags & REDIS_CLOSE_ASAP) return;
if (checkClientOutputBufferLimits(c)) {
sds client = catClientInfoString(sdsempty(),c);
+7 -13
View File
@@ -180,11 +180,10 @@ robj *dupStringObject(robj *o) {
}
}
robj *createListObject(void) {
list *l = listCreate();
robj *createQuicklistObject(void) {
quicklist *l = quicklistCreate();
robj *o = createObject(REDIS_LIST,l);
listSetFreeMethod(l,decrRefCountVoid);
o->encoding = REDIS_ENCODING_LINKEDLIST;
o->encoding = REDIS_ENCODING_QUICKLIST;
return o;
}
@@ -242,11 +241,8 @@ void freeStringObject(robj *o) {
void freeListObject(robj *o) {
switch (o->encoding) {
case REDIS_ENCODING_LINKEDLIST:
listRelease((list*) o->ptr);
break;
case REDIS_ENCODING_ZIPLIST:
zfree(o->ptr);
case REDIS_ENCODING_QUICKLIST:
quicklistRelease(o->ptr);
break;
default:
redisPanic("Unknown list encoding type");
@@ -533,9 +529,7 @@ size_t stringObjectLen(robj *o) {
if (sdsEncodedObject(o)) {
return sdslen(o->ptr);
} else {
char buf[32];
return ll2string(buf,32,(long)o->ptr);
return sdigits10((long)o->ptr);
}
}
@@ -678,7 +672,7 @@ char *strEncoding(int encoding) {
case REDIS_ENCODING_RAW: return "raw";
case REDIS_ENCODING_INT: return "int";
case REDIS_ENCODING_HT: return "hashtable";
case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
case REDIS_ENCODING_QUICKLIST: return "quicklist";
case REDIS_ENCODING_ZIPLIST: return "ziplist";
case REDIS_ENCODING_INTSET: return "intset";
case REDIS_ENCODING_SKIPLIST: return "skiplist";
+2650
View File
File diff suppressed because it is too large Load Diff
+169
View File
@@ -0,0 +1,169 @@
/* quicklist.h - A generic doubly linked quicklist implementation
*
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this quicklist of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this quicklist of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __QUICKLIST_H__
#define __QUICKLIST_H__
/* Node, quicklist, and Iterator are the only data structures used currently. */
/* quicklistNode is a 32 byte struct describing a ziplist for a quicklist.
* We use bit fields keep the quicklistNode at 32 bytes.
* count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually < 32k).
* encoding: 2 bits, RAW=1, LZF=2.
* container: 2 bits, NONE=1, ZIPLIST=2.
* recompress: 1 bit, bool, true if node is temporarry decompressed for usage.
* attempted_compress: 1 bit, boolean, used for verifying during testing.
* extra: 12 bits, free for future use; pads out the remainder of 32 bits */
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *zl;
unsigned int sz; /* ziplist size in bytes */
unsigned int count : 16; /* count of items in ziplist */
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
unsigned int recompress : 1; /* was this node previous compressed? */
unsigned int attempted_compress : 1; /* node can't compress; too small */
unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
/* quicklistLZF is a 4+N byte struct holding 'sz' followed by 'compressed'.
* 'sz' is byte length of 'compressed' field.
* 'compressed' is LZF data with total (compressed) length 'sz'
* NOTE: uncompressed length is stored in quicklistNode->sz.
* When quicklistNode->zl is compressed, node->zl points to a quicklistLZF */
typedef struct quicklistLZF {
unsigned int sz; /* LZF size in bytes*/
char compressed[];
} quicklistLZF;
/* quicklist is a 32 byte struct (on 64-bit systems) describing a quicklist.
* 'count' is the number of total entries.
* 'len' is the number of quicklist nodes.
* 'compress' is: -1 if compression disabled, otherwise it's the number
* of quicklistNodes to leave uncompressed at ends of quicklist.
* 'fill' is the user-requested (or default) fill factor. */
typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long count; /* total count of all entries in all ziplists */
unsigned int len; /* number of quicklistNodes */
int fill : 16; /* fill factor for individual nodes */
unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
} quicklist;
typedef struct quicklistIter {
const quicklist *quicklist;
quicklistNode *current;
unsigned char *zi;
long offset; /* offset in current ziplist */
int direction;
} quicklistIter;
typedef struct quicklistEntry {
const quicklist *quicklist;
quicklistNode *node;
unsigned char *zi;
unsigned char *value;
unsigned int sz;
long long longval;
int offset;
} quicklistEntry;
#define QUICKLIST_HEAD 0
#define QUICKLIST_TAIL -1
/* quicklist node encodings */
#define QUICKLIST_NODE_ENCODING_RAW 1
#define QUICKLIST_NODE_ENCODING_LZF 2
/* quicklist compression disable */
#define QUICKLIST_NOCOMPRESS 0
/* quicklist container formats */
#define QUICKLIST_NODE_CONTAINER_NONE 1
#define QUICKLIST_NODE_CONTAINER_ZIPLIST 2
#define quicklistNodeIsCompressed(node) \
((node)->encoding == QUICKLIST_NODE_ENCODING_LZF)
/* Prototypes */
quicklist *quicklistCreate(void);
quicklist *quicklistNew(int fill, int compress);
void quicklistSetCompressDepth(quicklist *quicklist, int depth);
void quicklistSetFill(quicklist *quicklist, int fill);
void quicklistSetOptions(quicklist *quicklist, int fill, int depth);
void quicklistRelease(quicklist *quicklist);
int quicklistPushHead(quicklist *quicklist, void *value, const size_t sz);
int quicklistPushTail(quicklist *quicklist, void *value, const size_t sz);
void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
int where);
void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl);
quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
unsigned char *zl);
quicklist *quicklistCreateFromZiplist(int fill, int compress,
unsigned char *zl);
void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *node,
void *value, const size_t sz);
void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *node,
void *value, const size_t sz);
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry);
int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
int sz);
int quicklistDelRange(quicklist *quicklist, const long start, const long stop);
quicklistIter *quicklistGetIterator(const quicklist *quicklist, int direction);
quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
int direction, const long long idx);
int quicklistNext(quicklistIter *iter, quicklistEntry *node);
void quicklistReleaseIterator(quicklistIter *iter);
quicklist *quicklistDup(quicklist *orig);
int quicklistIndex(const quicklist *quicklist, const long long index,
quicklistEntry *entry);
void quicklistRewind(quicklist *quicklist, quicklistIter *li);
void quicklistRewindTail(quicklist *quicklist, quicklistIter *li);
void quicklistRotate(quicklist *quicklist);
int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
unsigned int *sz, long long *sval,
void *(*saver)(unsigned char *data, unsigned int sz));
int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
unsigned int *sz, long long *slong);
unsigned int quicklistCount(quicklist *ql);
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
size_t quicklistGetLzf(const quicklistNode *node, void **data);
#ifdef REDIS_TEST
int quicklistTest(int argc, char *argv[]);
#endif
/* Directions for iterators */
#define AL_START_HEAD 0
#define AL_START_TAIL 1
#endif /* __QUICKLIST_H__ */
+271 -120
View File
@@ -40,6 +40,20 @@
#include <arpa/inet.h>
#include <sys/stat.h>
#define RDB_LOAD_NONE 0
#define RDB_LOAD_ENC (1<<0)
#define RDB_LOAD_PLAIN (1<<1)
#define rdbExitReportCorruptRDB(reason) rdbCheckThenExit(reason, __LINE__);
void rdbCheckThenExit(char *reason, int where) {
redisLog(REDIS_WARNING, "Corrupt RDB detected at rdb.c:%d (%s). "
"Running 'redis-check-rdb %s'",
where, reason, server.rdb_filename);
redis_check_rdb(server.rdb_filename);
exit(1);
}
static int rdbWriteRaw(rio *rdb, void *p, size_t len) {
if (rdb && rioWrite(rdb,p,len) == 0)
return -1;
@@ -161,9 +175,11 @@ int rdbEncodeInteger(long long value, unsigned char *enc) {
}
/* Loads an integer-encoded object with the specified encoding type "enctype".
* If the "encode" argument is set the function may return an integer-encoded
* string object, otherwise it always returns a raw string object. */
robj *rdbLoadIntegerObject(rio *rdb, int enctype, int encode) {
* The returned value changes according to the flags, see
* rdbGenerincLoadStringObject() for more info. */
void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags) {
int plain = flags & RDB_LOAD_PLAIN;
int encode = flags & RDB_LOAD_ENC;
unsigned char enc[4];
long long val;
@@ -182,12 +198,19 @@ robj *rdbLoadIntegerObject(rio *rdb, int enctype, int encode) {
val = (int32_t)v;
} else {
val = 0; /* anti-warning */
redisPanic("Unknown RDB integer encoding type");
rdbExitReportCorruptRDB("Unknown RDB integer encoding type");
}
if (encode)
if (plain) {
char buf[REDIS_LONGSTR_SIZE], *p;
int len = ll2string(buf,sizeof(buf),val);
p = zmalloc(len);
memcpy(p,buf,len);
return p;
} else if (encode) {
return createStringObjectFromLongLong(val);
else
} else {
return createObject(REDIS_STRING,sdsfromlonglong(val));
}
}
/* String objects in the form "2391" "-100" without any space and with a
@@ -209,10 +232,33 @@ int rdbTryIntegerEncoding(char *s, size_t len, unsigned char *enc) {
return rdbEncodeInteger(value,enc);
}
int rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
size_t comprlen, outlen;
ssize_t rdbSaveLzfBlob(rio *rdb, void *data, size_t compress_len,
size_t original_len) {
unsigned char byte;
int n, nwritten = 0;
ssize_t n, nwritten = 0;
/* Data compressed! Let's save it on disk */
byte = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_LZF;
if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr;
nwritten += n;
if ((n = rdbSaveLen(rdb,compress_len)) == -1) goto writeerr;
nwritten += n;
if ((n = rdbSaveLen(rdb,original_len)) == -1) goto writeerr;
nwritten += n;
if ((n = rdbWriteRaw(rdb,data,compress_len)) == -1) goto writeerr;
nwritten += n;
return nwritten;
writeerr:
return -1;
}
ssize_t rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
size_t comprlen, outlen;
void *out;
/* We require at least four bytes compression for this to be worth it */
@@ -224,29 +270,16 @@ int rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
zfree(out);
return 0;
}
/* Data compressed! Let's save it on disk */
byte = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_LZF;
if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr;
nwritten += n;
if ((n = rdbSaveLen(rdb,comprlen)) == -1) goto writeerr;
nwritten += n;
if ((n = rdbSaveLen(rdb,len)) == -1) goto writeerr;
nwritten += n;
if ((n = rdbWriteRaw(rdb,out,comprlen)) == -1) goto writeerr;
nwritten += n;
ssize_t nwritten = rdbSaveLzfBlob(rdb, out, comprlen, len);
zfree(out);
return nwritten;
writeerr:
zfree(out);
return -1;
}
robj *rdbLoadLzfStringObject(rio *rdb) {
/* Load an LZF compressed string in RDB format. The returned value
* changes according to 'flags'. For more info check the
* rdbGenericLoadStringObject() function. */
void *rdbLoadLzfStringObject(rio *rdb, int flags) {
int plain = flags & RDB_LOAD_PLAIN;
unsigned int len, clen;
unsigned char *c = NULL;
sds val = NULL;
@@ -254,22 +287,37 @@ robj *rdbLoadLzfStringObject(rio *rdb) {
if ((clen = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR) return NULL;
if ((len = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR) return NULL;
if ((c = zmalloc(clen)) == NULL) goto err;
if ((val = sdsnewlen(NULL,len)) == NULL) goto err;
/* Allocate our target according to the uncompressed size. */
if (plain) {
val = zmalloc(len);
} else {
if ((val = sdsnewlen(NULL,len)) == NULL) goto err;
}
/* Load the compressed representation and uncompress it to target. */
if (rioRead(rdb,c,clen) == 0) goto err;
if (lzf_decompress(c,clen,val,len) == 0) goto err;
zfree(c);
return createObject(REDIS_STRING,val);
if (plain)
return val;
else
return createObject(REDIS_STRING,val);
err:
zfree(c);
sdsfree(val);
if (plain)
zfree(val);
else
sdsfree(val);
return NULL;
}
/* Save a string object as [len][data] on disk. If the object is a string
* representation of an integer value we try to save it in a special form */
int rdbSaveRawString(rio *rdb, unsigned char *s, size_t len) {
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len) {
int enclen;
int n, nwritten = 0;
ssize_t n, nwritten = 0;
/* Try integer encoding */
if (len <= 11) {
@@ -300,9 +348,9 @@ int rdbSaveRawString(rio *rdb, unsigned char *s, size_t len) {
}
/* Save a long long value as either an encoded string or a string. */
int rdbSaveLongLongAsStringObject(rio *rdb, long long value) {
ssize_t rdbSaveLongLongAsStringObject(rio *rdb, long long value) {
unsigned char buf[32];
int n, nwritten = 0;
ssize_t n, nwritten = 0;
int enclen = rdbEncodeInteger(value,buf);
if (enclen > 0) {
return rdbWriteRaw(rdb,buf,enclen);
@@ -330,10 +378,21 @@ int rdbSaveStringObject(rio *rdb, robj *obj) {
}
}
robj *rdbGenericLoadStringObject(rio *rdb, int encode) {
/* Load a string object from an RDB file according to flags:
*
* RDB_LOAD_NONE (no flags): load an RDB object, unencoded.
* RDB_LOAD_ENC: If the returned type is a Redis object, try to
* encode it in a special way to be more memory
* efficient. When this flag is passed the function
* no longer guarantees that obj->ptr is an SDS string.
* RDB_LOAD_PLAIN: Return a plain string allocated with zmalloc()
* instead of a Redis object.
*/
void *rdbGenericLoadStringObject(rio *rdb, int flags) {
int encode = flags & RDB_LOAD_ENC;
int plain = flags & RDB_LOAD_PLAIN;
int isencoded;
uint32_t len;
robj *o;
len = rdbLoadLen(rdb,&isencoded);
if (isencoded) {
@@ -341,30 +400,39 @@ robj *rdbGenericLoadStringObject(rio *rdb, int encode) {
case REDIS_RDB_ENC_INT8:
case REDIS_RDB_ENC_INT16:
case REDIS_RDB_ENC_INT32:
return rdbLoadIntegerObject(rdb,len,encode);
return rdbLoadIntegerObject(rdb,len,flags);
case REDIS_RDB_ENC_LZF:
return rdbLoadLzfStringObject(rdb);
return rdbLoadLzfStringObject(rdb,flags);
default:
redisPanic("Unknown RDB encoding type");
rdbExitReportCorruptRDB("Unknown RDB encoding type");
}
}
if (len == REDIS_RDB_LENERR) return NULL;
o = encode ? createStringObject(NULL,len) :
createRawStringObject(NULL,len);
if (len && rioRead(rdb,o->ptr,len) == 0) {
decrRefCount(o);
return NULL;
if (!plain) {
robj *o = encode ? createStringObject(NULL,len) :
createRawStringObject(NULL,len);
if (len && rioRead(rdb,o->ptr,len) == 0) {
decrRefCount(o);
return NULL;
}
return o;
} else {
void *buf = zmalloc(len);
if (len && rioRead(rdb,buf,len) == 0) {
zfree(buf);
return NULL;
}
return buf;
}
return o;
}
robj *rdbLoadStringObject(rio *rdb) {
return rdbGenericLoadStringObject(rdb,0);
return rdbGenericLoadStringObject(rdb,RDB_LOAD_NONE);
}
robj *rdbLoadEncodedStringObject(rio *rdb) {
return rdbGenericLoadStringObject(rdb,1);
return rdbGenericLoadStringObject(rdb,RDB_LOAD_ENC);
}
/* Save a double value. Doubles are saved as strings prefixed by an unsigned
@@ -433,10 +501,8 @@ int rdbSaveObjectType(rio *rdb, robj *o) {
case REDIS_STRING:
return rdbSaveType(rdb,REDIS_RDB_TYPE_STRING);
case REDIS_LIST:
if (o->encoding == REDIS_ENCODING_ZIPLIST)
return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST_ZIPLIST);
else if (o->encoding == REDIS_ENCODING_LINKEDLIST)
return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST);
if (o->encoding == REDIS_ENCODING_QUICKLIST)
return rdbSaveType(rdb,REDIS_RDB_TYPE_LIST_QUICKLIST);
else
redisPanic("Unknown list encoding");
case REDIS_SET:
@@ -476,8 +542,8 @@ int rdbLoadObjectType(rio *rdb) {
}
/* Save a Redis object. Returns -1 on error, number of bytes written on success. */
int rdbSaveObject(rio *rdb, robj *o) {
int n, nwritten = 0;
ssize_t rdbSaveObject(rio *rdb, robj *o) {
ssize_t n = 0, nwritten = 0;
if (o->type == REDIS_STRING) {
/* Save a string value */
@@ -485,25 +551,24 @@ int rdbSaveObject(rio *rdb, robj *o) {
nwritten += n;
} else if (o->type == REDIS_LIST) {
/* Save a list value */
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
size_t l = ziplistBlobLen((unsigned char*)o->ptr);
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
quicklist *ql = o->ptr;
quicklistNode *node = ql->head;
if ((n = rdbSaveRawString(rdb,o->ptr,l)) == -1) return -1;
nwritten += n;
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
list *list = o->ptr;
listIter li;
listNode *ln;
if ((n = rdbSaveLen(rdb,listLength(list))) == -1) return -1;
if ((n = rdbSaveLen(rdb,ql->len)) == -1) return -1;
nwritten += n;
listRewind(list,&li);
while((ln = listNext(&li))) {
robj *eleobj = listNodeValue(ln);
if ((n = rdbSaveStringObject(rdb,eleobj)) == -1) return -1;
nwritten += n;
}
do {
if (quicklistNodeIsCompressed(node)) {
void *data;
size_t compress_len = quicklistGetLzf(node, &data);
if ((n = rdbSaveLzfBlob(rdb,data,compress_len,node->sz)) == -1) return -1;
nwritten += n;
} else {
if ((n = rdbSaveRawString(rdb,node->zl,node->sz)) == -1) return -1;
nwritten += n;
}
} while ((node = node->next));
} else {
redisPanic("Unknown list encoding");
}
@@ -599,8 +664,8 @@ int rdbSaveObject(rio *rdb, robj *o) {
* the rdbSaveObject() function. Currently we use a trick to get
* this length with very little changes to the code. In the future
* we could switch to a faster solution. */
off_t rdbSavedObjectLen(robj *o) {
int len = rdbSaveObject(NULL,o);
size_t rdbSavedObjectLen(robj *o) {
ssize_t len = rdbSaveObject(NULL,o);
redisAssertWithInfo(NULL,o,len != -1);
return len;
}
@@ -627,6 +692,39 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
return 1;
}
/* Save an AUX field. */
int rdbSaveAuxField(rio *rdb, void *key, size_t keylen, void *val, size_t vallen) {
if (rdbSaveType(rdb,REDIS_RDB_OPCODE_AUX) == -1) return -1;
if (rdbSaveRawString(rdb,key,keylen) == -1) return -1;
if (rdbSaveRawString(rdb,val,vallen) == -1) return -1;
return 1;
}
/* Wrapper for rdbSaveAuxField() used when key/val length can be obtained
* with strlen(). */
int rdbSaveAuxFieldStrStr(rio *rdb, char *key, char *val) {
return rdbSaveAuxField(rdb,key,strlen(key),val,strlen(val));
}
/* Wrapper for strlen(key) + integer type (up to long long range). */
int rdbSaveAuxFieldStrInt(rio *rdb, char *key, long long val) {
char buf[REDIS_LONGSTR_SIZE];
int vlen = ll2string(buf,sizeof(buf),val);
return rdbSaveAuxField(rdb,key,strlen(key),buf,vlen);
}
/* Save a few default AUX fields with information about the RDB generated. */
int rdbSaveInfoAuxFields(rio *rdb) {
int redis_bits = (sizeof(void*) == 8) ? 64 : 32;
/* Add a few fields about the state when the RDB was created. */
if (rdbSaveAuxFieldStrStr(rdb,"redis-ver",REDIS_VERSION) == -1) return -1;
if (rdbSaveAuxFieldStrInt(rdb,"redis-bits",redis_bits) == -1) return -1;
if (rdbSaveAuxFieldStrInt(rdb,"ctime",time(NULL)) == -1) return -1;
if (rdbSaveAuxFieldStrInt(rdb,"used-mem",zmalloc_used_memory()) == -1) return -1;
return 1;
}
/* Produces a dump of the database in RDB format sending it to the specified
* Redis I/O channel. On success REDIS_OK is returned, otherwise REDIS_ERR
* is returned and part of the output, or all the output, can be
@@ -647,6 +745,7 @@ int rdbSaveRio(rio *rdb, int *error) {
rdb->update_cksum = rioGenericUpdateChecksum;
snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION);
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
if (rdbSaveInfoAuxFields(rdb) == -1) goto werr;
for (j = 0; j < server.dbnum; j++) {
redisDb *db = server.db+j;
@@ -659,6 +758,21 @@ int rdbSaveRio(rio *rdb, int *error) {
if (rdbSaveType(rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr;
if (rdbSaveLen(rdb,j) == -1) goto werr;
/* Write the RESIZE DB opcode. We trim the size to UINT32_MAX, which
* is currently the largest type we are able to represent in RDB sizes.
* However this does not limit the actual size of the DB to load since
* these sizes are just hints to resize the hash tables. */
uint32_t db_size, expires_size;
db_size = (dictSize(db->dict) <= UINT32_MAX) ?
dictSize(db->dict) :
UINT32_MAX;
expires_size = (dictSize(db->dict) <= UINT32_MAX) ?
dictSize(db->expires) :
UINT32_MAX;
if (rdbSaveType(rdb,REDIS_RDB_OPCODE_RESIZEDB) == -1) goto werr;
if (rdbSaveLen(rdb,db_size) == -1) goto werr;
if (rdbSaveLen(rdb,expires_size) == -1) goto werr;
/* Iterate this DB writing every entry */
while((de = dictNext(di)) != NULL) {
sds keystr = dictGetKey(de);
@@ -819,7 +933,7 @@ void rdbRemoveTempFile(pid_t childpid) {
/* Load a Redis object of the specified type from the specified file.
* On success a newly allocated object is returned, otherwise NULL. */
robj *rdbLoadObject(int rdbtype, rio *rdb) {
robj *o, *ele, *dec;
robj *o = NULL, *ele, *dec;
size_t len;
unsigned int i;
@@ -831,33 +945,18 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
/* Read list value */
if ((len = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR) return NULL;
/* Use a real list when there are too many entries */
if (len > server.list_max_ziplist_entries) {
o = createListObject();
} else {
o = createZiplistObject();
}
o = createQuicklistObject();
quicklistSetOptions(o->ptr, server.list_max_ziplist_size,
server.list_compress_depth);
/* Load every single element of the list */
while(len--) {
if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
/* If we are using a ziplist and the value is too big, convert
* the object to a real list. */
if (o->encoding == REDIS_ENCODING_ZIPLIST &&
sdsEncodedObject(ele) &&
sdslen(ele->ptr) > server.list_max_ziplist_value)
listTypeConvert(o,REDIS_ENCODING_LINKEDLIST);
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
dec = getDecodedObject(ele);
o->ptr = ziplistPush(o->ptr,dec->ptr,sdslen(dec->ptr),REDIS_TAIL);
decrRefCount(dec);
decrRefCount(ele);
} else {
ele = tryObjectEncoding(ele);
listAddNodeTail(o->ptr,ele);
}
dec = getDecodedObject(ele);
size_t len = sdslen(dec->ptr);
quicklistPushTail(o->ptr, dec->ptr, len);
decrRefCount(dec);
decrRefCount(ele);
}
} else if (rdbtype == REDIS_RDB_TYPE_SET) {
/* Read list/set value */
@@ -989,25 +1088,33 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
/* Add pair to hash table */
ret = dictAdd((dict*)o->ptr, field, value);
redisAssert(ret == DICT_OK);
if (ret == DICT_ERR) {
rdbExitReportCorruptRDB("Duplicate keys detected");
}
}
/* All pairs should be read by now */
redisAssert(len == 0);
} else if (rdbtype == REDIS_RDB_TYPE_LIST_QUICKLIST) {
if ((len = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR) return NULL;
o = createQuicklistObject();
quicklistSetOptions(o->ptr, server.list_max_ziplist_size,
server.list_compress_depth);
while (len--) {
unsigned char *zl = rdbGenericLoadStringObject(rdb,RDB_LOAD_PLAIN);
if (zl == NULL) return NULL;
quicklistAppendZiplist(o->ptr, zl);
}
} else if (rdbtype == REDIS_RDB_TYPE_HASH_ZIPMAP ||
rdbtype == REDIS_RDB_TYPE_LIST_ZIPLIST ||
rdbtype == REDIS_RDB_TYPE_SET_INTSET ||
rdbtype == REDIS_RDB_TYPE_ZSET_ZIPLIST ||
rdbtype == REDIS_RDB_TYPE_HASH_ZIPLIST)
{
robj *aux = rdbLoadStringObject(rdb);
if (aux == NULL) return NULL;
o = createObject(REDIS_STRING,NULL); /* string is just placeholder */
o->ptr = zmalloc(sdslen(aux->ptr));
memcpy(o->ptr,aux->ptr,sdslen(aux->ptr));
decrRefCount(aux);
unsigned char *encoded = rdbGenericLoadStringObject(rdb,RDB_LOAD_PLAIN);
if (encoded == NULL) return NULL;
o = createObject(REDIS_STRING,encoded); /* Obj type fixed below. */
/* Fix the object encoding, and make sure to convert the encoded
* data type into the base type if accordingly to the current
@@ -1048,8 +1155,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
case REDIS_RDB_TYPE_LIST_ZIPLIST:
o->type = REDIS_LIST;
o->encoding = REDIS_ENCODING_ZIPLIST;
if (ziplistLen(o->ptr) > server.list_max_ziplist_entries)
listTypeConvert(o,REDIS_ENCODING_LINKEDLIST);
listTypeConvert(o,REDIS_ENCODING_QUICKLIST);
break;
case REDIS_RDB_TYPE_SET_INTSET:
o->type = REDIS_SET;
@@ -1070,11 +1176,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
hashTypeConvert(o, REDIS_ENCODING_HT);
break;
default:
redisPanic("Unknown encoding");
rdbExitReportCorruptRDB("Unknown encoding");
break;
}
} else {
redisPanic("Unknown object type");
rdbExitReportCorruptRDB("Unknown object type");
}
return o;
}
@@ -1163,7 +1269,12 @@ int rdbLoad(char *filename) {
/* Read type. */
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
/* Handle special types. */
if (type == REDIS_RDB_OPCODE_EXPIRETIME) {
/* EXPIRETIME: load an expire associated with the next key
* to load. Note that after loading an expire we need to
* load the actual type, and continue. */
if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr;
/* We read the time so we need to read the object type again. */
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
@@ -1171,27 +1282,67 @@ int rdbLoad(char *filename) {
* into milliseconds. */
expiretime *= 1000;
} else if (type == REDIS_RDB_OPCODE_EXPIRETIME_MS) {
/* Milliseconds precision expire times introduced with RDB
* version 3. */
/* EXPIRETIME_MS: milliseconds precision expire times introduced
* with RDB v3. Like EXPIRETIME but no with more precision. */
if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr;
/* We read the time so we need to read the object type again. */
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
}
if (type == REDIS_RDB_OPCODE_EOF)
} else if (type == REDIS_RDB_OPCODE_EOF) {
/* EOF: End of file, exit the main loop. */
break;
/* Handle SELECT DB opcode as a special case */
if (type == REDIS_RDB_OPCODE_SELECTDB) {
} else if (type == REDIS_RDB_OPCODE_SELECTDB) {
/* SELECTDB: Select the specified database. */
if ((dbid = rdbLoadLen(&rdb,NULL)) == REDIS_RDB_LENERR)
goto eoferr;
if (dbid >= (unsigned)server.dbnum) {
redisLog(REDIS_WARNING,"FATAL: Data file was created with a Redis server configured to handle more than %d databases. Exiting\n", server.dbnum);
redisLog(REDIS_WARNING,
"FATAL: Data file was created with a Redis "
"server configured to handle more than %d "
"databases. Exiting\n", server.dbnum);
exit(1);
}
db = server.db+dbid;
continue;
continue; /* Read type again. */
} else if (type == REDIS_RDB_OPCODE_RESIZEDB) {
/* RESIZEDB: Hint about the size of the keys in the currently
* selected data base, in order to avoid useless rehashing. */
uint32_t db_size, expires_size;
if ((db_size = rdbLoadLen(&rdb,NULL)) == REDIS_RDB_LENERR)
goto eoferr;
if ((expires_size = rdbLoadLen(&rdb,NULL)) == REDIS_RDB_LENERR)
goto eoferr;
dictExpand(db->dict,db_size);
dictExpand(db->expires,expires_size);
continue; /* Read type again. */
} else if (type == REDIS_RDB_OPCODE_AUX) {
/* AUX: generic string-string fields. Use to add state to RDB
* which is backward compatible. Implementations of RDB loading
* are requierd to skip AUX fields they don't understand.
*
* An AUX field is composed of two strings: key and value. */
robj *auxkey, *auxval;
if ((auxkey = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
if ((auxval = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
if (((char*)auxkey->ptr)[0] == '%') {
/* All the fields with a name staring with '%' are considered
* information fields and are logged at startup with a log
* level of NOTICE. */
redisLog(REDIS_NOTICE,"RDB '%s': %s",
(char*)auxkey->ptr,
(char*)auxval->ptr);
} else {
/* We ignore fields we don't understand, as by AUX field
* contract. */
redisLog(REDIS_DEBUG,"Unrecognized RDB AUX field: '%s'",
(char*)auxkey->ptr);
}
decrRefCount(auxkey);
decrRefCount(auxval);
continue; /* Read type again. */
}
/* Read key */
if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr;
/* Read value */
@@ -1224,7 +1375,7 @@ int rdbLoad(char *filename) {
redisLog(REDIS_WARNING,"RDB file was saved with checksum disabled: no check performed.");
} else if (cksum != expected) {
redisLog(REDIS_WARNING,"Wrong RDB checksum. Aborting now.");
exit(1);
rdbExitReportCorruptRDB("RDB CRC error");
}
}
@@ -1234,7 +1385,7 @@ int rdbLoad(char *filename) {
eoferr: /* unexpected end of file is handled here with a fatal exit */
redisLog(REDIS_WARNING,"Short read or OOM loading DB. Unrecoverable error, aborting now.");
exit(1);
rdbExitReportCorruptRDB("Unexpected EOF reading RDB file");
return REDIS_ERR; /* Just to avoid warning */
}
+9 -5
View File
@@ -38,7 +38,7 @@
/* The current RDB version. When the format changes in a way that is no longer
* backward compatible this number gets incremented. */
#define REDIS_RDB_VERSION 6
#define REDIS_RDB_VERSION 7
/* Defines related to the dump file format. To store 32 bits lengths for short
* keys requires a lot of space, so we check the most significant 2 bits of
@@ -74,6 +74,7 @@
#define REDIS_RDB_TYPE_SET 2
#define REDIS_RDB_TYPE_ZSET 3
#define REDIS_RDB_TYPE_HASH 4
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
/* Object types for encoded objects. */
#define REDIS_RDB_TYPE_HASH_ZIPMAP 9
@@ -81,11 +82,15 @@
#define REDIS_RDB_TYPE_SET_INTSET 11
#define REDIS_RDB_TYPE_ZSET_ZIPLIST 12
#define REDIS_RDB_TYPE_HASH_ZIPLIST 13
#define REDIS_RDB_TYPE_LIST_QUICKLIST 14
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
/* Test if a type is an object type. */
#define rdbIsObjectType(t) ((t >= 0 && t <= 4) || (t >= 9 && t <= 13))
#define rdbIsObjectType(t) ((t >= 0 && t <= 4) || (t >= 9 && t <= 14))
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
#define REDIS_RDB_OPCODE_AUX 250
#define REDIS_RDB_OPCODE_RESIZEDB 251
#define REDIS_RDB_OPCODE_EXPIRETIME_MS 252
#define REDIS_RDB_OPCODE_EXPIRETIME 253
#define REDIS_RDB_OPCODE_SELECTDB 254
@@ -104,9 +109,8 @@ int rdbSaveBackground(char *filename);
int rdbSaveToSlavesSockets(void);
void rdbRemoveTempFile(pid_t childpid);
int rdbSave(char *filename);
int rdbSaveObject(rio *rdb, robj *o);
off_t rdbSavedObjectLen(robj *o);
off_t rdbSavedObjectPages(robj *o);
ssize_t rdbSaveObject(rio *rdb, robj *o);
size_t rdbSavedObjectLen(robj *o);
robj *rdbLoadObject(int type, rio *rdb);
void backgroundSaveDoneHandler(int exitcode, int bysignal);
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime, long long now);
+14 -2
View File
@@ -86,7 +86,7 @@ typedef struct _client {
char **randptr; /* Pointers to :rand: strings inside the command buf */
size_t randlen; /* Number of pointers in client->randptr */
size_t randfree; /* Number of unused pointers in client->randptr */
unsigned int written; /* Bytes of 'obuf' already written */
size_t written; /* Bytes of 'obuf' already written */
long long start; /* Start time of a request */
long long latency; /* Request latency */
int pending; /* Number of pending requests (replies to consume) */
@@ -266,7 +266,7 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
if (sdslen(c->obuf) > c->written) {
void *ptr = c->obuf+c->written;
int nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written);
ssize_t nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written);
if (nwritten == -1) {
if (errno != EPIPE)
fprintf(stderr, "Writing to socket: %s\n", strerror(errno));
@@ -738,12 +738,24 @@ int main(int argc, const char **argv) {
free(cmd);
}
if (test_is_selected("rpush")) {
len = redisFormatCommand(&cmd,"RPUSH mylist %s",data);
benchmark("RPUSH",cmd,len);
free(cmd);
}
if (test_is_selected("lpop")) {
len = redisFormatCommand(&cmd,"LPOP mylist");
benchmark("LPOP",cmd,len);
free(cmd);
}
if (test_is_selected("rpop")) {
len = redisFormatCommand(&cmd,"RPOP mylist");
benchmark("RPOP",cmd,len);
free(cmd);
}
if (test_is_selected("sadd")) {
len = redisFormatCommand(&cmd,
"SADD myset element:__rand_int__");
+96 -157
View File
@@ -29,74 +29,19 @@
*/
#include "redis.h"
#include "rdb.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <limits.h>
#include "lzf.h"
#include "crc64.h"
/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
#define REDIS_HASH_ZIPMAP 9
#define REDIS_LIST_ZIPLIST 10
#define REDIS_SET_INTSET 11
#define REDIS_ZSET_ZIPLIST 12
#define REDIS_HASH_ZIPLIST 13
/* Objects encoding. Some kind of objects like Strings and Hashes can be
* internally represented in multiple ways. The 'encoding' field of the object
* is set to one of this fields for this object. */
#define REDIS_ENCODING_RAW 0 /* Raw representation */
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
#define REDIS_ENCODING_ZIPMAP 2 /* Encoded as zipmap */
#define REDIS_ENCODING_HT 3 /* Encoded as a hash table */
/* Object types only used for dumping to disk */
#define REDIS_EXPIRETIME_MS 252
#define REDIS_EXPIRETIME 253
#define REDIS_SELECTDB 254
#define REDIS_EOF 255
/* Defines related to the dump file format. To store 32 bits lengths for short
* keys requires a lot of space, so we check the most significant 2 bits of
* the first byte to interpreter the length:
*
* 00|000000 => if the two MSB are 00 the len is the 6 bits of this byte
* 01|000000 00000000 => 01, the len is 14 byes, 6 bits + 8 bits of next byte
* 10|000000 [32 bit integer] => if it's 01, a full 32 bit len will follow
* 11|000000 this means: specially encoded object will follow. The six bits
* number specify the kind of object that follows.
* See the REDIS_RDB_ENC_* defines.
*
* Lengths up to 63 are stored using a single byte, most DB keys, and may
* values, will fit inside. */
#define REDIS_RDB_6BITLEN 0
#define REDIS_RDB_14BITLEN 1
#define REDIS_RDB_32BITLEN 2
#define REDIS_RDB_ENCVAL 3
#define REDIS_RDB_LENERR UINT_MAX
/* When a length of a string object stored on disk has the first two bits
* set, the remaining two bits specify a special encoding for the object
* accordingly to the following defines: */
#define REDIS_RDB_ENC_INT8 0 /* 8 bit signed integer */
#define REDIS_RDB_ENC_INT16 1 /* 16 bit signed integer */
#define REDIS_RDB_ENC_INT32 2 /* 32 bit signed integer */
#define REDIS_RDB_ENC_LZF 3 /* string compressed with FASTLZ */
#define ERROR(...) { \
printf(__VA_ARGS__); \
redisLog(REDIS_WARNING, __VA_ARGS__); \
exit(1); \
}
@@ -133,28 +78,23 @@ typedef struct {
char success;
} entry;
/* Global vars that are actually used as constants. The following double
* values are used for double on-disk serialization, and are initialized
* at runtime to avoid strange compiler optimizations. */
static double R_Zero, R_PosInf, R_NegInf, R_Nan;
#define MAX_TYPES_NUM 256
#define MAX_TYPE_NAME_LEN 16
/* store string types for output */
static char types[MAX_TYPES_NUM][MAX_TYPE_NAME_LEN];
/* Return true if 't' is a valid object type. */
int checkType(unsigned char t) {
static int rdbCheckType(unsigned char t) {
/* In case a new object type is added, update the following
* condition as necessary. */
return
(t >= REDIS_HASH_ZIPMAP && t <= REDIS_HASH_ZIPLIST) ||
t <= REDIS_HASH ||
t >= REDIS_EXPIRETIME_MS;
(t >= REDIS_RDB_TYPE_HASH_ZIPMAP && t <= REDIS_RDB_TYPE_HASH_ZIPLIST) ||
t <= REDIS_RDB_TYPE_HASH ||
t >= REDIS_RDB_OPCODE_EXPIRETIME_MS;
}
/* when number of bytes to read is negative, do a peek */
int readBytes(void *target, long num) {
static int readBytes(void *target, long num) {
char peek = (num < 0) ? 1 : 0;
num = (num < 0) ? -num : num;
@@ -173,28 +113,28 @@ int processHeader(void) {
int dump_version;
if (!readBytes(buf, 9)) {
ERROR("Cannot read header\n");
ERROR("Cannot read header");
}
/* expect the first 5 bytes to equal REDIS */
if (memcmp(buf,"REDIS",5) != 0) {
ERROR("Wrong signature in header\n");
ERROR("Wrong signature in header");
}
dump_version = (int)strtol(buf + 5, NULL, 10);
if (dump_version < 1 || dump_version > 6) {
ERROR("Unknown RDB format version: %d\n", dump_version);
ERROR("Unknown RDB format version: %d", dump_version);
}
return dump_version;
}
int loadType(entry *e) {
static int loadType(entry *e) {
uint32_t offset = CURR_OFFSET;
/* this byte needs to qualify as type */
unsigned char t;
if (readBytes(&t, 1)) {
if (checkType(t)) {
if (rdbCheckType(t)) {
e->type = t;
return 1;
} else {
@@ -208,18 +148,18 @@ int loadType(entry *e) {
return 0;
}
int peekType() {
static int peekType() {
unsigned char t;
if (readBytes(&t, -1) && (checkType(t)))
if (readBytes(&t, -1) && (rdbCheckType(t)))
return t;
return -1;
}
/* discard time, just consume the bytes */
int processTime(int type) {
static int processTime(int type) {
uint32_t offset = CURR_OFFSET;
unsigned char t[8];
int timelen = (type == REDIS_EXPIRETIME_MS) ? 8 : 4;
int timelen = (type == REDIS_RDB_OPCODE_EXPIRETIME_MS) ? 8 : 4;
if (readBytes(t,timelen)) {
return 1;
@@ -231,7 +171,7 @@ int processTime(int type) {
return 0;
}
uint32_t loadLength(int *isencoded) {
static uint32_t loadLength(int *isencoded) {
unsigned char buf[2];
uint32_t len;
int type;
@@ -257,7 +197,7 @@ uint32_t loadLength(int *isencoded) {
}
}
char *loadIntegerObject(int enctype) {
static char *loadIntegerObject(int enctype) {
uint32_t offset = CURR_OFFSET;
unsigned char enc[4];
long long val;
@@ -284,36 +224,36 @@ char *loadIntegerObject(int enctype) {
/* convert val into string */
char *buf;
buf = malloc(sizeof(char) * 128);
buf = zmalloc(sizeof(char) * 128);
sprintf(buf, "%lld", val);
return buf;
}
char* loadLzfStringObject() {
static char* loadLzfStringObject() {
unsigned int slen, clen;
char *c, *s;
if ((clen = loadLength(NULL)) == REDIS_RDB_LENERR) return NULL;
if ((slen = loadLength(NULL)) == REDIS_RDB_LENERR) return NULL;
c = malloc(clen);
c = zmalloc(clen);
if (!readBytes(c, clen)) {
free(c);
zfree(c);
return NULL;
}
s = malloc(slen+1);
s = zmalloc(slen+1);
if (lzf_decompress(c,clen,s,slen) == 0) {
free(c); free(s);
zfree(c); zfree(s);
return NULL;
}
free(c);
zfree(c);
return s;
}
/* returns NULL when not processable, char* when valid */
char* loadStringObject() {
static char* loadStringObject() {
uint32_t offset = CURR_OFFSET;
int isencoded;
uint32_t len;
@@ -336,48 +276,48 @@ char* loadStringObject() {
if (len == REDIS_RDB_LENERR) return NULL;
char *buf = malloc(sizeof(char) * (len+1));
char *buf = zmalloc(sizeof(char) * (len+1));
if (buf == NULL) return NULL;
buf[len] = '\0';
if (!readBytes(buf, len)) {
free(buf);
zfree(buf);
return NULL;
}
return buf;
}
int processStringObject(char** store) {
static int processStringObject(char** store) {
unsigned long offset = CURR_OFFSET;
char *key = loadStringObject();
if (key == NULL) {
SHIFT_ERROR(offset, "Error reading string object");
free(key);
zfree(key);
return 0;
}
if (store != NULL) {
*store = key;
} else {
free(key);
zfree(key);
}
return 1;
}
double* loadDoubleValue() {
static double* loadDoubleValue() {
char buf[256];
unsigned char len;
double* val;
if (!readBytes(&len,1)) return NULL;
val = malloc(sizeof(double));
val = zmalloc(sizeof(double));
switch(len) {
case 255: *val = R_NegInf; return val;
case 254: *val = R_PosInf; return val;
case 253: *val = R_Nan; return val;
default:
if (!readBytes(buf, len)) {
free(val);
zfree(val);
return NULL;
}
buf[len] = '\0';
@@ -386,24 +326,24 @@ double* loadDoubleValue() {
}
}
int processDoubleValue(double** store) {
static int processDoubleValue(double** store) {
unsigned long offset = CURR_OFFSET;
double *val = loadDoubleValue();
if (val == NULL) {
SHIFT_ERROR(offset, "Error reading double value");
free(val);
zfree(val);
return 0;
}
if (store != NULL) {
*store = val;
} else {
free(val);
zfree(val);
}
return 1;
}
int loadPair(entry *e) {
static int loadPair(entry *e) {
uint32_t offset = CURR_OFFSET;
uint32_t i;
@@ -417,10 +357,10 @@ int loadPair(entry *e) {
}
uint32_t length = 0;
if (e->type == REDIS_LIST ||
e->type == REDIS_SET ||
e->type == REDIS_ZSET ||
e->type == REDIS_HASH) {
if (e->type == REDIS_RDB_TYPE_LIST ||
e->type == REDIS_RDB_TYPE_SET ||
e->type == REDIS_RDB_TYPE_ZSET ||
e->type == REDIS_RDB_TYPE_HASH) {
if ((length = loadLength(NULL)) == REDIS_RDB_LENERR) {
SHIFT_ERROR(offset, "Error reading %s length", types[e->type]);
return 0;
@@ -428,19 +368,19 @@ int loadPair(entry *e) {
}
switch(e->type) {
case REDIS_STRING:
case REDIS_HASH_ZIPMAP:
case REDIS_LIST_ZIPLIST:
case REDIS_SET_INTSET:
case REDIS_ZSET_ZIPLIST:
case REDIS_HASH_ZIPLIST:
case REDIS_RDB_TYPE_STRING:
case REDIS_RDB_TYPE_HASH_ZIPMAP:
case REDIS_RDB_TYPE_LIST_ZIPLIST:
case REDIS_RDB_TYPE_SET_INTSET:
case REDIS_RDB_TYPE_ZSET_ZIPLIST:
case REDIS_RDB_TYPE_HASH_ZIPLIST:
if (!processStringObject(NULL)) {
SHIFT_ERROR(offset, "Error reading entry value");
return 0;
}
break;
case REDIS_LIST:
case REDIS_SET:
case REDIS_RDB_TYPE_LIST:
case REDIS_RDB_TYPE_SET:
for (i = 0; i < length; i++) {
offset = CURR_OFFSET;
if (!processStringObject(NULL)) {
@@ -449,7 +389,7 @@ int loadPair(entry *e) {
}
}
break;
case REDIS_ZSET:
case REDIS_RDB_TYPE_ZSET:
for (i = 0; i < length; i++) {
offset = CURR_OFFSET;
if (!processStringObject(NULL)) {
@@ -463,7 +403,7 @@ int loadPair(entry *e) {
}
}
break;
case REDIS_HASH:
case REDIS_RDB_TYPE_HASH:
for (i = 0; i < length; i++) {
offset = CURR_OFFSET;
if (!processStringObject(NULL)) {
@@ -486,7 +426,7 @@ int loadPair(entry *e) {
return 1;
}
entry loadEntry() {
static entry loadEntry() {
entry e = { NULL, -1, 0 };
uint32_t length, offset[4];
@@ -499,7 +439,7 @@ entry loadEntry() {
}
offset[1] = CURR_OFFSET;
if (e.type == REDIS_SELECTDB) {
if (e.type == REDIS_RDB_OPCODE_SELECTDB) {
if ((length = loadLength(NULL)) == REDIS_RDB_LENERR) {
SHIFT_ERROR(offset[1], "Error reading database number");
return e;
@@ -508,7 +448,7 @@ entry loadEntry() {
SHIFT_ERROR(offset[1], "Database number out of range (%d)", length);
return e;
}
} else if (e.type == REDIS_EOF) {
} else if (e.type == REDIS_RDB_OPCODE_EOF) {
if (positions[level].offset < positions[level].size) {
SHIFT_ERROR(offset[0], "Unexpected EOF");
} else {
@@ -517,8 +457,8 @@ entry loadEntry() {
return e;
} else {
/* optionally consume expire */
if (e.type == REDIS_EXPIRETIME ||
e.type == REDIS_EXPIRETIME_MS) {
if (e.type == REDIS_RDB_OPCODE_EXPIRETIME ||
e.type == REDIS_RDB_OPCODE_EXPIRETIME_MS) {
if (!processTime(e.type)) return e;
if (!loadType(&e)) return e;
}
@@ -544,31 +484,31 @@ entry loadEntry() {
return e;
}
void printCentered(int indent, int width, char* body) {
static void printCentered(int indent, int width, char* body) {
char head[256], tail[256];
memset(head, '\0', 256);
memset(tail, '\0', 256);
memset(head, '=', indent);
memset(tail, '=', width - 2 - indent - strlen(body));
printf("%s %s %s\n", head, body, tail);
redisLog(REDIS_WARNING, "%s %s %s", head, body, tail);
}
void printValid(uint64_t ops, uint64_t bytes) {
static void printValid(uint64_t ops, uint64_t bytes) {
char body[80];
sprintf(body, "Processed %llu valid opcodes (in %llu bytes)",
(unsigned long long) ops, (unsigned long long) bytes);
printCentered(4, 80, body);
}
void printSkipped(uint64_t bytes, uint64_t offset) {
static void printSkipped(uint64_t bytes, uint64_t offset) {
char body[80];
sprintf(body, "Skipped %llu bytes (resuming at 0x%08llx)",
(unsigned long long) bytes, (unsigned long long) offset);
printCentered(4, 80, body);
}
void printErrorStack(entry *e) {
static void printErrorStack(entry *e) {
unsigned int i;
char body[64];
@@ -598,20 +538,20 @@ void printErrorStack(entry *e) {
/* display error stack */
for (i = 0; i < errors.level; i++) {
printf("0x%08lx - %s\n",
redisLog(REDIS_WARNING, "0x%08lx - %s",
(unsigned long) errors.offset[i], errors.error[i]);
}
}
void process(void) {
uint64_t num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
entry entry;
entry entry = { NULL, -1, 0 };
int dump_version = processHeader();
/* Exclude the final checksum for RDB >= 5. Will be checked at the end. */
if (dump_version >= 5) {
if (positions[0].size < 8) {
printf("RDB version >= 5 but no room for checksum.\n");
redisLog(REDIS_WARNING, "RDB version >= 5 but no room for checksum.");
exit(1);
}
positions[0].size -= 8;
@@ -660,7 +600,7 @@ void process(void) {
/* advance position */
positions[0] = positions[1];
}
free(entry.key);
zfree(entry.key);
}
/* because there is another potential error,
@@ -668,7 +608,7 @@ void process(void) {
printValid(num_valid_ops, num_valid_bytes);
/* expect an eof */
if (entry.type != REDIS_EOF) {
if (entry.type != REDIS_RDB_OPCODE_EOF) {
/* last byte should be EOF, add error */
errors.level = 0;
SHIFT_ERROR(positions[0].offset, "Expected EOF, got %s", types[entry.type]);
@@ -696,47 +636,40 @@ void process(void) {
if (crc != crc2) {
SHIFT_ERROR(positions[0].offset, "RDB CRC64 does not match.");
} else {
printf("CRC64 checksum is OK\n");
redisLog(REDIS_WARNING, "CRC64 checksum is OK");
}
}
/* print summary on errors */
if (num_errors) {
printf("\n");
printf("Total unprocessable opcodes: %llu\n",
redisLog(REDIS_WARNING, "Total unprocessable opcodes: %llu",
(unsigned long long) num_errors);
}
}
int main(int argc, char **argv) {
/* expect the first argument to be the dump file */
if (argc <= 1) {
printf("Usage: %s <dump.rdb>\n", argv[0]);
exit(0);
}
int redis_check_rdb(char *rdbfilename) {
int fd;
off_t size;
struct stat stat;
void *data;
fd = open(argv[1], O_RDONLY);
fd = open(rdbfilename, O_RDONLY);
if (fd < 1) {
ERROR("Cannot open file: %s\n", argv[1]);
ERROR("Cannot open file: %s", rdbfilename);
}
if (fstat(fd, &stat) == -1) {
ERROR("Cannot stat: %s\n", argv[1]);
ERROR("Cannot stat: %s", rdbfilename);
} else {
size = stat.st_size;
}
if (sizeof(size_t) == sizeof(int32_t) && size >= INT_MAX) {
ERROR("Cannot check dump files >2GB on a 32-bit platform\n");
ERROR("Cannot check dump files >2GB on a 32-bit platform");
}
data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
ERROR("Cannot mmap: %s\n", argv[1]);
ERROR("Cannot mmap: %s", rdbfilename);
}
/* Initialize static vars */
@@ -746,22 +679,16 @@ int main(int argc, char **argv) {
errors.level = 0;
/* Object types */
sprintf(types[REDIS_STRING], "STRING");
sprintf(types[REDIS_LIST], "LIST");
sprintf(types[REDIS_SET], "SET");
sprintf(types[REDIS_ZSET], "ZSET");
sprintf(types[REDIS_HASH], "HASH");
sprintf(types[REDIS_RDB_TYPE_STRING], "STRING");
sprintf(types[REDIS_RDB_TYPE_LIST], "LIST");
sprintf(types[REDIS_RDB_TYPE_SET], "SET");
sprintf(types[REDIS_RDB_TYPE_ZSET], "ZSET");
sprintf(types[REDIS_RDB_TYPE_HASH], "HASH");
/* Object types only used for dumping to disk */
sprintf(types[REDIS_EXPIRETIME], "EXPIRETIME");
sprintf(types[REDIS_SELECTDB], "SELECTDB");
sprintf(types[REDIS_EOF], "EOF");
/* Double constants initialization */
R_Zero = 0.0;
R_PosInf = 1.0/R_Zero;
R_NegInf = -1.0/R_Zero;
R_Nan = R_Zero/R_Zero;
sprintf(types[REDIS_RDB_OPCODE_EXPIRETIME], "EXPIRETIME");
sprintf(types[REDIS_RDB_OPCODE_SELECTDB], "SELECTDB");
sprintf(types[REDIS_RDB_OPCODE_EOF], "EOF");
process();
@@ -769,3 +696,15 @@ int main(int argc, char **argv) {
close(fd);
return 0;
}
/* RDB check main: called form redis.c when Redis is executed with the
* redis-check-rdb alias. */
int redis_check_rdb_main(char **argv, int argc) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <rdb-file-name>\n", argv[0]);
exit(1);
}
redisLog(REDIS_WARNING, "Checking RDB file %s", argv[1]);
exit(redis_check_rdb(argv[1]));
return 0;
}
+291 -9
View File
@@ -44,6 +44,7 @@
#include <assert.h>
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include "hiredis.h"
#include "sds.h"
@@ -63,6 +64,17 @@
#define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE"
#define REDIS_CLI_HISTFILE_DEFAULT ".rediscli_history"
/* --latency-dist palettes. */
int spectrum_palette_color_size = 19;
int spectrum_palette_color[] = {0,233,234,235,237,239,241,243,245,247,144,143,142,184,226,214,208,202,196};
int spectrum_palette_mono_size = 13;
int spectrum_palette_mono[] = {0,233,234,235,237,239,241,243,245,247,249,251,253};
/* The actual palette in use. */
int *spectrum_palette;
int spectrum_palette_size;
static redisContext *context;
static struct config {
char *hostip;
@@ -76,7 +88,10 @@ static struct config {
int monitor_mode;
int pubsub_mode;
int latency_mode;
int latency_dist_mode;
int latency_history;
int lru_test_mode;
long long lru_test_sample_size;
int cluster_mode;
int cluster_reissue_command;
int slave_mode;
@@ -518,7 +533,7 @@ static sds cliFormatReplyCSV(redisReply *r) {
out = sdscatrepr(out,r->str,r->len);
break;
case REDIS_REPLY_NIL:
out = sdscat(out,"NIL\n");
out = sdscat(out,"NIL");
break;
case REDIS_REPLY_ARRAY:
for (i = 0; i < r->elements; i++) {
@@ -700,16 +715,17 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
return REDIS_OK;
}
/* Send the INFO command, reconnecting the link if needed. */
static redisReply *reconnectingInfo(void) {
redisContext *c = context;
/* Send a command reconnecting the link if needed. */
static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ...) {
redisReply *reply = NULL;
int tries = 0;
va_list ap;
assert(!c->err);
while(reply == NULL) {
while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
printf("Reconnecting (%d)...\r", ++tries);
printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
printf("Reconnecting... %d\r", ++tries);
fflush(stdout);
redisFree(c);
@@ -717,12 +733,15 @@ static redisReply *reconnectingInfo(void) {
usleep(1000000);
}
reply = redisCommand(c,"INFO");
va_start(ap,fmt);
reply = redisvCommand(c,fmt,ap);
va_end(ap);
if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
fprintf(stderr, "Error: %s\n", c->errstr);
exit(1);
} else if (tries > 0) {
printf("\n");
printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
}
}
@@ -770,9 +789,17 @@ static int parseOptions(int argc, char **argv) {
config.output = OUTPUT_CSV;
} else if (!strcmp(argv[i],"--latency")) {
config.latency_mode = 1;
} else if (!strcmp(argv[i],"--latency-dist")) {
config.latency_dist_mode = 1;
} else if (!strcmp(argv[i],"--mono")) {
spectrum_palette = spectrum_palette_mono;
spectrum_palette_size = spectrum_palette_mono_size;
} else if (!strcmp(argv[i],"--latency-history")) {
config.latency_mode = 1;
config.latency_history = 1;
} else if (!strcmp(argv[i],"--lru-test") && !lastarg) {
config.lru_test_mode = 1;
config.lru_test_sample_size = strtoll(argv[++i],NULL,10);
} else if (!strcmp(argv[i],"--slave")) {
config.slave_mode = 1;
} else if (!strcmp(argv[i],"--stat")) {
@@ -862,6 +889,9 @@ static void usage(void) {
" --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"
" --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n"
" Default time interval is 1 sec. Change it using -i.\n"
" --lru-test <keys> Simulate a cache workload with an 80-20 distribution.\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"
@@ -1077,7 +1107,7 @@ static void latencyMode(void) {
if (!context) exit(1);
while(1) {
start = mstime();
reply = redisCommand(context,"PING");
reply = reconnectingRedisCommand(context,"PING");
if (reply == NULL) {
fprintf(stderr,"\nI/O error\n");
exit(1);
@@ -1107,6 +1137,148 @@ static void latencyMode(void) {
}
}
/*------------------------------------------------------------------------------
* Latency distribution mode -- requires 256 colors xterm
*--------------------------------------------------------------------------- */
#define LATENCY_DIST_DEFAULT_INTERVAL 1000 /* milliseconds. */
/* Structure to store samples distribution. */
struct distsamples {
long long max; /* Max latency to fit into this interval (usec). */
long long count; /* Number of samples in this interval. */
int character; /* Associated character in visualization. */
};
/* Helper function for latencyDistMode(). Performs the spectrum visualization
* of the collected samples targeting an xterm 256 terminal.
*
* Takes an array of distsamples structures, ordered from smaller to bigger
* 'max' value. Last sample max must be 0, to mean that it olds all the
* samples greater than the previous one, and is also the stop sentinel.
*
* "tot' is the total number of samples in the different buckets, so it
* is the SUM(samples[i].conut) for i to 0 up to the max sample.
*
* As a side effect the function sets all the buckets count to 0. */
void showLatencyDistSamples(struct distsamples *samples, long long tot) {
int j;
/* We convert samples into a index inside the palette
* proportional to the percentage a given bucket represents.
* This way intensity of the different parts of the spectrum
* don't change relative to the number of requests, which avoids to
* pollute the visualization with non-latency related info. */
printf("\033[38;5;0m"); /* Set foreground color to black. */
for (j = 0; ; j++) {
int coloridx =
ceil((float) samples[j].count / tot * (spectrum_palette_size-1));
int color = spectrum_palette[coloridx];
printf("\033[48;5;%dm%c", (int)color, samples[j].character);
samples[j].count = 0;
if (samples[j].max == 0) break; /* Last sample. */
}
printf("\033[0m\n");
fflush(stdout);
}
/* Show the legend: different buckets values and colors meaning, so
* that the spectrum is more easily readable. */
void showLatencyDistLegend(void) {
int j;
printf("---------------------------------------------\n");
printf(". - * # .01 .125 .25 .5 milliseconds\n");
printf("1,2,3,...,9 from 1 to 9 milliseconds\n");
printf("A,B,C,D,E 10,20,30,40,50 milliseconds\n");
printf("F,G,H,I,J .1,.2,.3,.4,.5 seconds\n");
printf("K,L,M,N,O,P,Q,? 1,2,4,8,16,30,60,>60 seconds\n");
printf("From 0 to 100%%: ");
for (j = 0; j < spectrum_palette_size; j++) {
printf("\033[48;5;%dm ", spectrum_palette[j]);
}
printf("\033[0m\n");
printf("---------------------------------------------\n");
}
static void latencyDistMode(void) {
redisReply *reply;
long long start, latency, count = 0;
long long history_interval =
config.interval ? config.interval/1000 :
LATENCY_DIST_DEFAULT_INTERVAL;
long long history_start = ustime();
int j, outputs = 0;
struct distsamples samples[] = {
/* We use a mostly logarithmic scale, with certain linear intervals
* which are more interesting than others, like 1-10 milliseconds
* range. */
{10,0,'.'}, /* 0.01 ms */
{125,0,'-'}, /* 0.125 ms */
{250,0,'*'}, /* 0.25 ms */
{500,0,'#'}, /* 0.5 ms */
{1000,0,'1'}, /* 1 ms */
{2000,0,'2'}, /* 2 ms */
{3000,0,'3'}, /* 3 ms */
{4000,0,'4'}, /* 4 ms */
{5000,0,'5'}, /* 5 ms */
{6000,0,'6'}, /* 6 ms */
{7000,0,'7'}, /* 7 ms */
{8000,0,'8'}, /* 8 ms */
{9000,0,'9'}, /* 9 ms */
{10000,0,'A'}, /* 10 ms */
{20000,0,'B'}, /* 20 ms */
{30000,0,'C'}, /* 30 ms */
{40000,0,'D'}, /* 40 ms */
{50000,0,'E'}, /* 50 ms */
{100000,0,'F'}, /* 0.1 s */
{200000,0,'G'}, /* 0.2 s */
{300000,0,'H'}, /* 0.3 s */
{400000,0,'I'}, /* 0.4 s */
{500000,0,'J'}, /* 0.5 s */
{1000000,0,'K'}, /* 1 s */
{2000000,0,'L'}, /* 2 s */
{4000000,0,'M'}, /* 4 s */
{8000000,0,'N'}, /* 8 s */
{16000000,0,'O'}, /* 16 s */
{30000000,0,'P'}, /* 30 s */
{60000000,0,'Q'}, /* 1 minute */
{0,0,'?'}, /* > 1 minute */
};
if (!context) exit(1);
while(1) {
start = ustime();
reply = reconnectingRedisCommand(context,"PING");
if (reply == NULL) {
fprintf(stderr,"\nI/O error\n");
exit(1);
}
latency = ustime()-start;
freeReplyObject(reply);
count++;
/* Populate the relevant bucket. */
for (j = 0; ; j++) {
if (samples[j].max == 0 || latency <= samples[j].max) {
samples[j].count++;
break;
}
}
/* From time to time show the spectrum. */
if (count && (ustime()-history_start)/1000 > history_interval) {
if ((outputs++ % 20) == 0)
showLatencyDistLegend();
showLatencyDistSamples(samples,count);
history_start = ustime();
count = 0;
}
usleep(LATENCY_SAMPLE_RATE * 1000);
}
}
/*------------------------------------------------------------------------------
* Slave mode
*--------------------------------------------------------------------------- */
@@ -1726,7 +1898,7 @@ static void statMode(void) {
char buf[64];
int j;
reply = reconnectingInfo();
reply = reconnectingRedisCommand(context,"INFO");
if (reply->type == REDIS_REPLY_ERROR) {
printf("ERROR: %s\n", reply->str);
exit(1);
@@ -1780,6 +1952,7 @@ static void statMode(void) {
/* Children */
aux = getLongInfoField(reply->str,"bgsave_in_progress");
aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1;
aux |= getLongInfoField(reply->str,"loading") << 2;
switch(aux) {
case 0: break;
case 1:
@@ -1791,6 +1964,9 @@ static void statMode(void) {
case 3:
printf("SAVE+AOF");
break;
case 4:
printf("LOAD");
break;
}
printf("\n");
@@ -1832,6 +2008,94 @@ static void scanMode(void) {
exit(0);
}
/*------------------------------------------------------------------------------
* LRU test mode
*--------------------------------------------------------------------------- */
/* Return an integer from min to max (both inclusive) using a power-law
* distribution, depending on the value of alpha: the greater the alpha
* the more bias towards lower values.
*
* With alpha = 6.2 the output follows the 80-20 rule where 20% of
* the returned numbers will account for 80% of the frequency. */
long long powerLawRand(long long min, long long max, double alpha) {
double pl, r;
max += 1;
r = ((double)rand()) / RAND_MAX;
pl = pow(
((pow(max,alpha+1) - pow(min,alpha+1))*r + pow(min,alpha+1)),
(1.0/(alpha+1)));
return (max-1-(long long)pl)+min;
}
/* Generates a key name among a set of lru_test_sample_size keys, using
* an 80-20 distribution. */
void LRUTestGenKey(char *buf, size_t buflen) {
snprintf(buf, buflen, "lru:%lld\n",
powerLawRand(1, config.lru_test_sample_size, 6.2));
}
#define LRU_CYCLE_PERIOD 1000 /* 1000 milliseconds. */
#define LRU_CYCLE_PIPELINE_SIZE 250
static void LRUTestMode(void) {
redisReply *reply;
char key[128];
long long start_cycle;
int j;
srand(time(NULL)^getpid());
while(1) {
/* Perform cycles of 1 second with 50% writes and 50% reads.
* We use pipelining batching writes / reads N times per cycle in order
* to fill the target instance easily. */
start_cycle = mstime();
long long hits = 0, misses = 0;
while(mstime() - start_cycle < 1000) {
/* Write cycle. */
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
LRUTestGenKey(key,sizeof(key));
redisAppendCommand(context, "SET %s val",key);
}
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++)
redisGetReply(context, (void**)&reply);
/* Read cycle. */
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
LRUTestGenKey(key,sizeof(key));
redisAppendCommand(context, "GET %s",key);
}
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
if (redisGetReply(context, (void**)&reply) == REDIS_OK) {
switch(reply->type) {
case REDIS_REPLY_ERROR:
printf("%s\n", reply->str);
break;
case REDIS_REPLY_NIL:
misses++;
break;
default:
hits++;
break;
}
}
}
if (context->err) {
fprintf(stderr,"I/O error during LRU test\n");
exit(1);
}
}
/* Print stats. */
printf(
"%lld Gets/sec | Hits: %lld (%.2f%%) | Misses: %lld (%.2f%%)\n",
hits+misses,
hits, (double)hits/(hits+misses)*100,
misses, (double)misses/(hits+misses)*100);
}
exit(0);
}
/*------------------------------------------------------------------------------
* Intrisic latency mode.
*
@@ -1923,7 +2187,10 @@ int main(int argc, char **argv) {
config.monitor_mode = 0;
config.pubsub_mode = 0;
config.latency_mode = 0;
config.latency_dist_mode = 0;
config.latency_history = 0;
config.lru_test_mode = 0;
config.lru_test_sample_size = 0;
config.cluster_mode = 0;
config.slave_mode = 0;
config.getrdb_mode = 0;
@@ -1940,6 +2207,9 @@ int main(int argc, char **argv) {
config.eval = NULL;
config.last_cmd_type = -1;
spectrum_palette = spectrum_palette_color;
spectrum_palette_size = spectrum_palette_color_size;
if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL))
config.output = OUTPUT_RAW;
else
@@ -1957,6 +2227,12 @@ int main(int argc, char **argv) {
latencyMode();
}
/* Latency distribution mode */
if (config.latency_dist_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
latencyDistMode();
}
/* Slave mode */
if (config.slave_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
@@ -1994,6 +2270,12 @@ int main(int argc, char **argv) {
scanMode();
}
/* LRU test mode */
if (config.lru_test_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
LRUTestMode();
}
/* Intrinsic latency mode */
if (config.intrinsic_latency_mode) intrinsicLatencyMode();
+17 -8
View File
@@ -72,7 +72,7 @@ class ClusterNode
@friends
end
def slots
def slots
@info[:slots]
end
@@ -154,7 +154,7 @@ class ClusterNode
end
} if slots
@dirty = false
@r.cluster("info").split("\n").each{|e|
@r.cluster("info").split("\n").each{|e|
k,v=e.split(":")
k = k.to_sym
v.chop!
@@ -213,7 +213,7 @@ class ClusterNode
#
# Note: this could be easily written without side effects,
# we use 'slots' just to split the computation into steps.
# First step: we want an increasing array of integers
# for instance: [1,2,3,4,5,8,9,20,21,22,23,24,25,30]
slots = @info[:slots].keys.sort
@@ -273,7 +273,7 @@ class ClusterNode
def info
@info
end
def is_dirty?
@dirty
end
@@ -563,8 +563,17 @@ class RedisTrib
# Take one node from each IP until we run out of nodes
# across every IP.
ips.each do |ip,nodes|
stop = nodes.empty? and next
interleaved.push nodes.shift
if nodes.empty?
# if this IP has no remaining nodes, check for termination
if interleaved.length == nodes_count
# stop when 'interleaved' has accumulated all nodes
stop = true
next
end
else
# else, move one node from this IP to 'interleaved'
interleaved.push nodes.shift
end
end
end
@@ -780,7 +789,7 @@ class RedisTrib
# Move slots between source and target nodes using MIGRATE.
#
# Options:
# Options:
# :verbose -- Print a dot for every moved key.
# :fix -- We are moving in the context of a fix. Use REPLACE.
# :cold -- Move keys without opening / reconfiguring the nodes.
@@ -1223,7 +1232,7 @@ end
#################################################################################
# Libraries
#
#
# We try to don't depend on external libs since this is a critical part
# of Redis Cluster.
#################################################################################
+149 -51
View File
@@ -202,6 +202,7 @@ struct redisCommand redisCommandTable[] = {
{"hincrbyfloat",hincrbyfloatCommand,4,"wmF",0,NULL,1,1,1,0,0},
{"hdel",hdelCommand,-3,"wF",0,NULL,1,1,1,0,0},
{"hlen",hlenCommand,2,"rF",0,NULL,1,1,1,0,0},
{"hstrlen",hstrlenCommand,3,"rF",0,NULL,1,1,1,0,0},
{"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0},
{"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0},
{"hgetall",hgetallCommand,2,"r",0,NULL,1,1,1,0,0},
@@ -260,6 +261,7 @@ struct redisCommand redisCommandTable[] = {
{"pubsub",pubsubCommand,-2,"pltrR",0,NULL,0,0,0,0,0},
{"watch",watchCommand,-2,"rsF",0,NULL,1,-1,1,0,0},
{"unwatch",unwatchCommand,1,"rsF",0,NULL,0,0,0,0,0},
{"if",ifCommand,-2,"rsF",0,NULL,2,-1,1,0,0},
{"cluster",clusterCommand,-2,"ar",0,NULL,0,0,0,0,0},
{"restore",restoreCommand,-4,"wm",0,NULL,1,1,1,0,0},
{"restore-asking",restoreCommand,-4,"wmk",0,NULL,1,1,1,0,0},
@@ -1416,6 +1418,7 @@ void initServerConfig(void) {
server.syslog_facility = LOG_LOCAL0;
server.daemonize = REDIS_DEFAULT_DAEMONIZE;
server.supervised = 0;
server.supervised_mode = REDIS_SUPERVISED_NONE;
server.aof_state = REDIS_AOF_OFF;
server.aof_fsync = REDIS_DEFAULT_AOF_FSYNC;
server.aof_no_fsync_on_rewrite = REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE;
@@ -1433,7 +1436,7 @@ void initServerConfig(void) {
server.aof_flush_postponed_start = 0;
server.aof_rewrite_incremental_fsync = REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
server.aof_load_truncated = REDIS_DEFAULT_AOF_LOAD_TRUNCATED;
server.pidfile = zstrdup(REDIS_DEFAULT_PID_FILE);
server.pidfile = NULL;
server.rdb_filename = zstrdup(REDIS_DEFAULT_RDB_FILENAME);
server.aof_filename = zstrdup(REDIS_DEFAULT_AOF_FILENAME);
server.requirepass = NULL;
@@ -1449,8 +1452,8 @@ void initServerConfig(void) {
server.maxmemory_samples = REDIS_DEFAULT_MAXMEMORY_SAMPLES;
server.hash_max_ziplist_entries = REDIS_HASH_MAX_ZIPLIST_ENTRIES;
server.hash_max_ziplist_value = REDIS_HASH_MAX_ZIPLIST_VALUE;
server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES;
server.list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE;
server.list_max_ziplist_size = REDIS_LIST_MAX_ZIPLIST_SIZE;
server.list_compress_depth = REDIS_LIST_COMPRESS_DEPTH;
server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
server.zset_max_ziplist_entries = REDIS_ZSET_MAX_ZIPLIST_ENTRIES;
server.zset_max_ziplist_value = REDIS_ZSET_MAX_ZIPLIST_VALUE;
@@ -1528,6 +1531,7 @@ void initServerConfig(void) {
server.lpushCommand = lookupCommandByCString("lpush");
server.lpopCommand = lookupCommandByCString("lpop");
server.rpopCommand = lookupCommandByCString("rpop");
server.sremCommand = lookupCommandByCString("srem");
/* Slow log */
server.slowlog_log_slower_than = REDIS_SLOWLOG_LOG_SLOWER_THAN;
@@ -2000,6 +2004,9 @@ struct redisCommand *lookupCommandOrOriginal(sds name) {
* + REDIS_PROPAGATE_NONE (no propagation of command at all)
* + REDIS_PROPAGATE_AOF (propagate into the AOF file if is enabled)
* + REDIS_PROPAGATE_REPL (propagate into the replication link)
*
* This should not be used inside commands implementation. Use instead
* alsoPropagate(), preventCommandPropagation(), forceCommandPropagation().
*/
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
int flags)
@@ -2011,11 +2018,31 @@ void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
}
/* Used inside commands to schedule the propagation of additional commands
* after the current command is propagated to AOF / Replication. */
* after the current command is propagated to AOF / Replication.
*
* 'cmd' must be a pointer to the Redis command to replicate, dbid is the
* database ID the command should be propagated into.
* Arguments of the command to propagte are passed as an array of redis
* objects pointers of len 'argc', using the 'argv' vector.
*
* The function does not take a reference to the passed 'argv' vector,
* so it is up to the caller to release the passed argv (but it is usually
* stack allocated). The function autoamtically increments ref count of
* passed objects, so the caller does not need to. */
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
int target)
{
redisOpArrayAppend(&server.also_propagate,cmd,dbid,argv,argc,target);
robj **argvcopy;
int j;
if (server.loading) return; /* No propagation during loading. */
argvcopy = zmalloc(sizeof(robj*)*argc);
for (j = 0; j < argc; j++) {
argvcopy[j] = argv[j];
incrRefCount(argv[j]);
}
redisOpArrayAppend(&server.also_propagate,cmd,dbid,argvcopy,argc,target);
}
/* It is possible to call the function forceCommandPropagation() inside a
@@ -2026,6 +2053,13 @@ void forceCommandPropagation(redisClient *c, int flags) {
if (flags & REDIS_PROPAGATE_AOF) c->flags |= REDIS_FORCE_AOF;
}
/* Avoid that the executed command is propagated at all. This way we
* are free to just propagate what we want using the alsoPropagate()
* API. */
void preventCommandPropagation(redisClient *c) {
c->flags |= REDIS_PREVENT_PROP;
}
/* Call() is the core of Redis execution of a command */
void call(redisClient *c, int flags) {
long long dirty, start, duration;
@@ -2079,7 +2113,7 @@ void call(redisClient *c, int flags) {
}
/* Propagate the command into the AOF and replication link */
if (flags & REDIS_CALL_PROPAGATE) {
if (flags & REDIS_CALL_PROPAGATE && (c->flags & REDIS_PREVENT_PROP) == 0) {
int flags = REDIS_PROPAGATE_NONE;
if (c->flags & REDIS_FORCE_REPL) flags |= REDIS_PROPAGATE_REPL;
@@ -2090,20 +2124,24 @@ void call(redisClient *c, int flags) {
propagate(c->cmd,c->db->id,c->argv,c->argc,flags);
}
/* Restore the old FORCE_AOF/REPL flags, since call can be executed
/* Restore the old replication flags, since call can be executed
* recursively. */
c->flags &= ~(REDIS_FORCE_AOF|REDIS_FORCE_REPL);
c->flags |= client_old_flags & (REDIS_FORCE_AOF|REDIS_FORCE_REPL);
c->flags &= ~(REDIS_FORCE_AOF|REDIS_FORCE_REPL|REDIS_PREVENT_PROP);
c->flags |= client_old_flags &
(REDIS_FORCE_AOF|REDIS_FORCE_REPL|REDIS_PREVENT_PROP);
/* Handle the alsoPropagate() API to handle commands that want to propagate
* multiple separated commands. */
* multiple separated commands. Note that alsoPropagate() is not affected
* by REDIS_PREVENT_PROP flag. */
if (server.also_propagate.numops) {
int j;
redisOp *rop;
for (j = 0; j < server.also_propagate.numops; j++) {
rop = &server.also_propagate.ops[j];
propagate(rop->cmd, rop->dbid, rop->argv, rop->argc, rop->target);
if (flags & REDIS_CALL_PROPAGATE) {
for (j = 0; j < server.also_propagate.numops; j++) {
rop = &server.also_propagate.ops[j];
propagate(rop->cmd,rop->dbid,rop->argv,rop->argc,rop->target);
}
}
redisOpArrayFree(&server.also_propagate);
}
@@ -2299,7 +2337,7 @@ int processCommand(redisClient *c) {
/* Exec the command */
if (c->flags & REDIS_MULTI &&
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
c->cmd->proc != execCommand && c->cmd->proc != ifCommand &&
c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
{
queueMultiCommand(c);
@@ -2374,7 +2412,7 @@ int prepareForShutdown(int flags) {
return REDIS_ERR;
}
}
if (server.daemonize) {
if (server.daemonize || server.pidfile) {
redisLog(REDIS_NOTICE,"Removing the pid file.");
unlink(server.pidfile);
}
@@ -2700,9 +2738,13 @@ sds genRedisInfoString(char *section) {
char hmem[64];
char peak_hmem[64];
char total_system_hmem[64];
char used_memory_lua_hmem[64];
char used_memory_rss_hmem[64];
char maxmemory_hmem[64];
size_t zmalloc_used = zmalloc_used_memory();
size_t total_system_mem = server.system_memory_size;
char *evict_policy = maxmemoryToString();
long long memory_lua = (long long)lua_gc(server.lua,LUA_GCCOUNT,0)*1024;
/* Peak memory is updated from time to time by serverCron() so it
* may happen that the instantaneous value is slightly bigger than
@@ -2714,6 +2756,9 @@ sds genRedisInfoString(char *section) {
bytesToHuman(hmem,zmalloc_used);
bytesToHuman(peak_hmem,server.stat_peak_memory);
bytesToHuman(total_system_hmem,total_system_mem);
bytesToHuman(used_memory_lua_hmem,memory_lua);
bytesToHuman(used_memory_rss_hmem,server.resident_set_size);
bytesToHuman(maxmemory_hmem,server.maxmemory);
if (sections++) info = sdscat(info,"\r\n");
info = sdscatprintf(info,
@@ -2721,25 +2766,33 @@ sds genRedisInfoString(char *section) {
"used_memory:%zu\r\n"
"used_memory_human:%s\r\n"
"used_memory_rss:%zu\r\n"
"used_memory_rss_human:%s\r\n"
"used_memory_peak:%zu\r\n"
"used_memory_peak_human:%s\r\n"
"total_system_memory:%lu\r\n"
"total_system_memory_human:%s\r\n"
"used_memory_lua:%lld\r\n"
"used_memory_lua_human:%s\r\n"
"maxmemory:%lld\r\n"
"maxmemory_human:%s\r\n"
"maxmemory_policy:%s\r\n"
"mem_fragmentation_ratio:%.2f\r\n"
"mem_allocator:%s\r\n"
"maxmemory_policy:%s\r\n",
"mem_allocator:%s\r\n",
zmalloc_used,
hmem,
server.resident_set_size,
used_memory_rss_hmem,
server.stat_peak_memory,
peak_hmem,
(unsigned long)total_system_mem,
total_system_hmem,
((long long)lua_gc(server.lua,LUA_GCCOUNT,0))*1024LL,
memory_lua,
used_memory_lua_hmem,
server.maxmemory,
maxmemory_hmem,
evict_policy,
zmalloc_get_fragmentation_ratio(server.resident_set_size),
ZMALLOC_LIB,
evict_policy
ZMALLOC_LIB
);
}
@@ -3146,13 +3199,7 @@ void evictionPoolPopulate(dict *sampledict, dict *keydict, struct evictionPoolEn
samples = zmalloc(sizeof(samples[0])*server.maxmemory_samples);
}
#if 1 /* Use bulk get by default. */
count = dictGetRandomKeys(sampledict,samples,server.maxmemory_samples);
#else
count = server.maxmemory_samples;
for (j = 0; j < count; j++) samples[j] = dictGetRandomKey(sampledict);
#endif
count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples);
for (j = 0; j < count; j++) {
unsigned long long idle;
sds key;
@@ -3207,7 +3254,7 @@ void evictionPoolPopulate(dict *sampledict, dict *keydict, struct evictionPoolEn
int freeMemoryIfNeeded(void) {
size_t mem_used, mem_tofree, mem_freed;
int slaves = listLength(server.slaves);
mstime_t latency;
mstime_t latency, eviction_latency;
/* Remove the size of slaves output buffers and AOF buffer from the
* count of used memory. */
@@ -3338,7 +3385,11 @@ int freeMemoryIfNeeded(void) {
* AOF and Output buffer memory will be freed eventually so
* we only care about memory used by the key space. */
delta = (long long) zmalloc_used_memory();
latencyStartMonitor(eviction_latency);
dbDelete(db,keyobj);
latencyEndMonitor(eviction_latency);
latencyAddSampleIfNeeded("eviction-del",eviction_latency);
latencyRemoveNestedEvent(latency,eviction_latency);
delta -= (long long) zmalloc_used_memory();
mem_freed += delta;
server.stat_evictedkeys++;
@@ -3393,6 +3444,10 @@ void linuxMemoryWarnings(void) {
#endif /* __linux__ */
void createPidFile(void) {
/* If pidfile requested, but no pidfile defined, use
* default pidfile path */
if (!server.pidfile) server.pidfile = zstrdup(REDIS_DEFAULT_PID_FILE);
/* Try to write the pid file in a best-effort way. */
FILE *fp = fopen(server.pidfile,"w");
if (fp) {
@@ -3587,8 +3642,23 @@ void redisSetProcTitle(char *title) {
/*
* Check whether systemd or upstart have been used to start redis.
*/
int redisIsSupervised(void) {
int redisSupervisedUpstart(void) {
const char *upstart_job = getenv("UPSTART_JOB");
if (!upstart_job) {
redisLog(REDIS_WARNING,
"upstart supervision requested, but UPSTART_JOB not found");
return 0;
}
redisLog(REDIS_NOTICE, "supervised by upstart, will stop to signal readyness");
raise(SIGSTOP);
unsetenv("UPSTART_JOB");
return 1;
}
int redisSupervisedSystemd(void) {
const char *notify_socket = getenv("NOTIFY_SOCKET");
int fd = 1;
struct sockaddr_un su;
@@ -3596,31 +3666,24 @@ int redisIsSupervised(void) {
struct msghdr hdr;
int sendto_flags = 0;
if (upstart_job == NULL && notify_socket == NULL)
if (!notify_socket) {
redisLog(REDIS_WARNING,
"systemd supervision requested, but NOTIFY_SOCKET not found");
return 0;
if (upstart_job != NULL) {
redisLog(REDIS_NOTICE, "supervised by upstart, will stop to signal readyness");
raise(SIGSTOP);
unsetenv("UPSTART_JOB");
return 1;
}
/*
* If we got here, we're supervised by systemd.
*/
if ((strchr("@/", notify_socket[0])) == NULL ||
strlen(notify_socket) < 2)
if ((strchr("@/", notify_socket[0])) == NULL || strlen(notify_socket) < 2) {
return 0;
}
redisLog(REDIS_NOTICE, "supervised by systemd, will signal readyness");
if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
redisLog(REDIS_WARNING, "cannot contact systemd socket %s", notify_socket);
if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
redisLog(REDIS_WARNING,
"Can't connect to systemd socket %s", notify_socket);
return 0;
}
bzero(&su, sizeof(su));
memset(&su, 0, sizeof(su));
su.sun_family = AF_UNIX;
strncpy (su.sun_path, notify_socket, sizeof(su.sun_path) -1);
su.sun_path[sizeof(su.sun_path) - 1] = '\0';
@@ -3628,11 +3691,11 @@ int redisIsSupervised(void) {
if (notify_socket[0] == '@')
su.sun_path[0] = '\0';
bzero(&iov, sizeof(iov));
memset(&iov, 0, sizeof(iov));
iov.iov_base = "READY=1";
iov.iov_len = strlen("READY=1");
bzero(&hdr, sizeof(hdr));
memset(&hdr, 0, sizeof(hdr));
hdr.msg_name = &su;
hdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) +
strlen(notify_socket);
@@ -3644,7 +3707,7 @@ int redisIsSupervised(void) {
sendto_flags |= MSG_NOSIGNAL;
#endif
if (sendmsg(fd, &hdr, sendto_flags) < 0) {
redisLog(REDIS_WARNING, "Cannot send notification to systemd");
redisLog(REDIS_WARNING, "Can't send notification to systemd");
close(fd);
return 0;
}
@@ -3652,6 +3715,26 @@ int redisIsSupervised(void) {
return 1;
}
int redisIsSupervised(int mode) {
if (mode == REDIS_SUPERVISED_AUTODETECT) {
const char *upstart_job = getenv("UPSTART_JOB");
const char *notify_socket = getenv("NOTIFY_SOCKET");
if (upstart_job) {
redisSupervisedUpstart();
} else if (notify_socket) {
redisSupervisedSystemd();
}
} else if (mode == REDIS_SUPERVISED_UPSTART) {
return redisSupervisedUpstart();
} else if (mode == REDIS_SUPERVISED_SYSTEMD) {
return redisSupervisedSystemd();
}
return 0;
}
int main(int argc, char **argv) {
struct timeval tv;
@@ -3659,6 +3742,8 @@ int main(int argc, char **argv) {
if (argc == 3 && !strcasecmp(argv[1], "test")) {
if (!strcasecmp(argv[2], "ziplist")) {
return ziplistTest(argc, argv);
} else if (!strcasecmp(argv[2], "quicklist")) {
quicklistTest(argc, argv);
} else if (!strcasecmp(argv[2], "intset")) {
return intsetTest(argc, argv);
} else if (!strcasecmp(argv[2], "zipmap")) {
@@ -3700,6 +3785,12 @@ int main(int argc, char **argv) {
initSentinel();
}
/* Check if we need to start in redis-check-rdb mode. We just execute
* the program main. However the program is part of the Redis executable
* so that we can easily execute an RDB check on loading errors. */
if (strstr(argv[0],"redis-check-rdb") != NULL)
exit(redis_check_rdb_main(argv,argc));
if (argc >= 2) {
int j = 1; /* First option to parse in argv[] */
sds options = sdsempty();
@@ -3731,6 +3822,11 @@ int main(int argc, char **argv) {
while(j != argc) {
if (argv[j][0] == '-' && argv[j][1] == '-') {
/* Option name */
if (!strcmp(argv[j], "--check-rdb")) {
/* Argument has no options, need to skip for parsing. */
j++;
continue;
}
if (sdslen(options)) options = sdscat(options,"\n");
options = sdscat(options,argv[j]+2);
options = sdscat(options," ");
@@ -3756,10 +3852,12 @@ int main(int argc, char **argv) {
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");
}
server.supervised = redisIsSupervised();
if (server.daemonize && server.supervised == 0) daemonize();
server.supervised = redisIsSupervised(server.supervised_mode);
int background = server.daemonize && !server.supervised;
if (background) daemonize();
initServer();
if (server.daemonize && server.supervised == 0) createPidFile();
if (background || server.pidfile) createPidFile();
redisSetProcTitle(argv[0]);
redisAsciiArt();
+36 -20
View File
@@ -32,10 +32,7 @@
#include "fmacros.h"
#include "config.h"
#if defined(__sun)
#include "solarisfixes.h"
#endif
#include <stdio.h>
#include <stdlib.h>
@@ -65,6 +62,7 @@ typedef long long mstime_t; /* millisecond time type. */
#include "util.h" /* Misc functions useful in many places */
#include "latency.h" /* Latency monitor API */
#include "sparkline.h" /* ASII graphs API */
#include "quicklist.h"
/* Following includes allow test functions to be called from Redis main() */
#include "zipmap.h"
@@ -133,7 +131,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1
#define REDIS_DEFAULT_MIN_SLAVES_TO_WRITE 0
#define REDIS_DEFAULT_MIN_SLAVES_MAX_LAG 10
#define REDIS_IP_STR_LEN INET6_ADDRSTRLEN
#define REDIS_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */
#define REDIS_PEER_ID_LEN (REDIS_IP_STR_LEN+32) /* Must be enough for ip:port */
#define REDIS_BINDADDR_MAX 16
#define REDIS_MIN_RESERVED_FDS 32
@@ -204,6 +202,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
#define REDIS_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
/* Defines related to the dump file format. To store 32 bits lengths for short
* keys requires a lot of space, so we check the most significant 2 bits of
@@ -258,6 +257,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define REDIS_PRE_PSYNC (1<<16) /* Instance don't understand PSYNC. */
#define REDIS_READONLY (1<<17) /* Cluster client is in read-only state. */
#define REDIS_PUBSUB (1<<18) /* Client is in Pub/Sub mode. */
#define REDIS_PREVENT_PROP (1<<19) /* Don't propagate to AOF / Slaves. */
/* Client block type (btype field in client structure)
* if REDIS_BLOCKED flag is set. */
@@ -314,6 +314,12 @@ typedef long long mstime_t; /* millisecond time type. */
#define REDIS_LOG_RAW (1<<10) /* Modifier to log without timestamp */
#define REDIS_DEFAULT_VERBOSITY REDIS_NOTICE
/* Supervision options */
#define REDIS_SUPERVISED_NONE 0
#define REDIS_SUPERVISED_AUTODETECT 1
#define REDIS_SUPERVISED_SYSTEMD 2
#define REDIS_SUPERVISED_UPSTART 3
/* Anti-warning macro... */
#define REDIS_NOTUSED(V) ((void) V)
@@ -329,12 +335,14 @@ typedef long long mstime_t; /* millisecond time type. */
/* Zip structure related defaults */
#define REDIS_HASH_MAX_ZIPLIST_ENTRIES 512
#define REDIS_HASH_MAX_ZIPLIST_VALUE 64
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 512
#define REDIS_LIST_MAX_ZIPLIST_VALUE 64
#define REDIS_SET_MAX_INTSET_ENTRIES 512
#define REDIS_ZSET_MAX_ZIPLIST_ENTRIES 128
#define REDIS_ZSET_MAX_ZIPLIST_VALUE 64
/* List defaults */
#define REDIS_LIST_MAX_ZIPLIST_SIZE -2
#define REDIS_LIST_COMPRESS_DEPTH 0
/* HyperLogLog defines */
#define REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES 3000
@@ -535,8 +543,8 @@ typedef struct redisClient {
int multibulklen; /* number of multi bulk arguments left to read */
long bulklen; /* length of bulk argument in multi bulk request */
list *reply;
unsigned long reply_bytes; /* Tot bytes of objects in reply list */
int sentlen; /* Amount of bytes already sent in the current
unsigned long long reply_bytes; /* Tot bytes of objects in reply list */
size_t sentlen; /* Amount of bytes already sent in the current
buffer or object being sent. */
time_t ctime; /* Client creation time */
time_t lastinteraction; /* time of the last interaction, used for timeout */
@@ -546,8 +554,8 @@ typedef struct redisClient {
int replstate; /* replication state if this is a slave */
int repl_put_online_on_ack; /* Install slave write handler on ACK. */
int repldbfd; /* replication DB file descriptor */
off_t repldboff; /* replication DB file offset */
off_t repldbsize; /* replication DB file size */
off_t repldboff; /* replication DB file offset */
off_t repldbsize; /* replication DB file size */
sds replpreamble; /* replication DB preamble. */
long long reploff; /* replication offset if this is our master */
long long repl_ack_off; /* replication ack offset, if this is a slave */
@@ -701,7 +709,7 @@ struct redisServer {
off_t loading_process_events_interval_bytes;
/* Fast pointers to often looked up command */
struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand,
*rpopCommand;
*rpopCommand, *sremCommand;
/* Fields used only for stats */
time_t stat_starttime; /* Server start time */
long long stat_numcommands; /* Number of processed commands */
@@ -739,7 +747,8 @@ struct redisServer {
int active_expire_enabled; /* Can be disabled for testing purposes. */
size_t client_max_querybuf_len; /* Limit for client query buffer length */
int dbnum; /* Total number of configured DBs */
int supervised; /* True if supervised by upstart or systemd */
int supervised; /* 1 if supervised, 0 otherwise. */
int supervised_mode; /* See REDIS_SUPERVISED_* */
int daemonize; /* True if running as a daemon */
clientBufferLimitsConfig client_obuf_limits[REDIS_CLIENT_TYPE_COUNT];
/* AOF persistence */
@@ -869,12 +878,14 @@ struct redisServer {
/* Zip structure config, see redis.conf for more information */
size_t hash_max_ziplist_entries;
size_t hash_max_ziplist_value;
size_t list_max_ziplist_entries;
size_t list_max_ziplist_value;
size_t set_max_intset_entries;
size_t zset_max_ziplist_entries;
size_t zset_max_ziplist_value;
size_t hll_sparse_max_bytes;
/* List parameters */
int list_max_ziplist_size;
int list_compress_depth;
/* time cache */
time_t unixtime; /* Unix time sampled every cron cycle. */
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
/* Pubsub */
@@ -964,15 +975,13 @@ typedef struct {
robj *subject;
unsigned char encoding;
unsigned char direction; /* Iteration direction */
unsigned char *zi;
listNode *ln;
quicklistIter *iter;
} listTypeIterator;
/* Structure for an entry while iterating over a list. */
typedef struct {
listTypeIterator *li;
unsigned char *zi; /* Entry in ziplist */
listNode *ln; /* Entry in linked list */
quicklistEntry entry; /* Entry in quicklist */
} listTypeEntry;
/* Structure to hold set iteration abstraction. */
@@ -1099,7 +1108,7 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry);
robj *listTypeGet(listTypeEntry *entry);
void listTypeInsert(listTypeEntry *entry, robj *value, int where);
int listTypeEqual(listTypeEntry *entry, robj *o);
void listTypeDelete(listTypeEntry *entry);
void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
void listTypeConvert(robj *subject, int enc);
void unblockClientWaitingData(redisClient *c);
void handleClientsBlockedOnLists(void);
@@ -1137,7 +1146,7 @@ robj *getDecodedObject(robj *o);
size_t stringObjectLen(robj *o);
robj *createStringObjectFromLongLong(long long value);
robj *createStringObjectFromLongDouble(long double value, int humanfriendly);
robj *createListObject(void);
robj *createQuicklistObject(void);
robj *createZiplistObject(void);
robj *createSetObject(void);
robj *createIntsetObject(void);
@@ -1244,6 +1253,7 @@ void call(redisClient *c, int flags);
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
void forceCommandPropagation(redisClient *c, int flags);
void preventCommandPropagation(redisClient *c);
int prepareForShutdown();
#ifdef __GNUC__
void redisLog(int level, const char *fmt, ...)
@@ -1372,6 +1382,10 @@ void sentinelTimer(void);
char *sentinelHandleConfiguration(char **argv, int argc);
void sentinelIsRunning(void);
/* redis-check-rdb */
int redis_check_rdb(char *rdbfilename);
int redis_check_rdb_main(char **argv, int argc);
/* Scripting */
void scriptingInit(void);
@@ -1502,6 +1516,7 @@ void hmsetCommand(redisClient *c);
void hmgetCommand(redisClient *c);
void hdelCommand(redisClient *c);
void hlenCommand(redisClient *c);
void hstrlenCommand(redisClient *c);
void zremrangebyrankCommand(redisClient *c);
void zunionstoreCommand(redisClient *c);
void zinterstoreCommand(redisClient *c);
@@ -1546,6 +1561,7 @@ void pfcountCommand(redisClient *c);
void pfmergeCommand(redisClient *c);
void pfdebugCommand(redisClient *c);
void latencyCommand(redisClient *c);
void ifCommand(redisClient *c);
#if defined(__GNUC__)
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
+18 -11
View File
@@ -854,6 +854,23 @@ void replicationEmptyDbCallback(void *privdata) {
replicationSendNewlineToMaster();
}
/* Once we have a link with the master and the synchroniziation was
* performed, this function materializes the master client we store
* at server.master, starting from the specified file descriptor. */
void replicationCreateMasterClient(int fd) {
server.master = createClient(fd);
server.master->flags |= REDIS_MASTER;
server.master->authenticated = 1;
server.repl_state = REDIS_REPL_CONNECTED;
server.master->reploff = server.repl_master_initial_offset;
memcpy(server.master->replrunid, server.repl_master_runid,
sizeof(server.repl_master_runid));
/* If master offset is set to -1, this master is old and is not
* PSYNC capable, so we flag it accordingly. */
if (server.master->reploff == -1)
server.master->flags |= REDIS_PRE_PSYNC;
}
/* Asynchronously read the SYNC payload we receive from a master */
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
@@ -1017,17 +1034,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Final setup of the connected slave <- master link */
zfree(server.repl_transfer_tmpfile);
close(server.repl_transfer_fd);
server.master = createClient(server.repl_transfer_s);
server.master->flags |= REDIS_MASTER;
server.master->authenticated = 1;
server.repl_state = REDIS_REPL_CONNECTED;
server.master->reploff = server.repl_master_initial_offset;
memcpy(server.master->replrunid, server.repl_master_runid,
sizeof(server.repl_master_runid));
/* If master offset is set to -1, this master is old and is not
* PSYNC capable, so we flag it accordingly. */
if (server.master->reploff == -1)
server.master->flags |= REDIS_PRE_PSYNC;
replicationCreateMasterClient(server.repl_transfer_s);
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
/* Restart the AOF subsystem now that we finished the sync. This
* will trigger an AOF rewrite, and when done will start appending
+71 -1
View File
@@ -30,6 +30,7 @@
#include "redis.h"
#include "sha1.h"
#include "rand.h"
#include "cluster.h"
#include <lua.h>
#include <lauxlib.h>
@@ -213,11 +214,27 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
static int argv_size = 0;
static robj *cached_objects[LUA_CMD_OBJCACHE_SIZE];
static size_t cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
static int inuse = 0; /* Recursive calls detection. */
/* By using Lua debug hooks it is possible to trigger a recursive call
* to luaRedisGenericCommand(), which normally should never happen.
* To make this function reentrant is futile and makes it slower, but
* we should at least detect such a misuse, and abort. */
if (inuse) {
char *recursion_warning =
"luaRedisGenericCommand() recursive call detected. "
"Are you doing funny stuff with Lua debug hooks?";
redisLog(REDIS_WARNING,"%s",recursion_warning);
luaPushError(lua,recursion_warning);
return 1;
}
inuse++;
/* Require at least one argument */
if (argc == 0) {
luaPushError(lua,
"Please specify at least one argument for redis.call()");
inuse--;
return 1;
}
@@ -272,6 +289,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
}
luaPushError(lua,
"Lua redis() command arguments must be strings or integers");
inuse--;
return 1;
}
@@ -291,6 +309,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
luaPushError(lua,"Unknown Redis command called from Lua script");
goto cleanup;
}
c->cmd = cmd;
/* There are commands that are not allowed inside scripts. */
if (cmd->flags & REDIS_CMD_NOSCRIPT) {
@@ -337,8 +356,23 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
if (cmd->flags & REDIS_CMD_RANDOM) server.lua_random_dirty = 1;
if (cmd->flags & REDIS_CMD_WRITE) server.lua_write_dirty = 1;
/* If this is a Redis Cluster node, we need to make sure Lua is not
* trying to access non-local keys. */
if (server.cluster_enabled) {
/* Duplicate relevant flags in the lua client. */
c->flags &= ~(REDIS_READONLY|REDIS_ASKING);
c->flags |= server.lua_caller->flags & (REDIS_READONLY|REDIS_ASKING);
if (getNodeByQuery(c,c->cmd,c->argv,c->argc,NULL,NULL) !=
server.cluster->myself)
{
luaPushError(lua,
"Lua script attempted to access a non local key in a "
"cluster node");
goto cleanup;
}
}
/* Run the command */
c->cmd = cmd;
call(c,REDIS_CALL_SLOWLOG | REDIS_CALL_STATS);
/* Convert the result of the Redis command into a suitable Lua type.
@@ -409,8 +443,10 @@ cleanup:
* return the plain error. */
lua_pushstring(lua,"err");
lua_gettable(lua,-2);
inuse--;
return lua_error(lua);
}
inuse--;
return 1;
}
@@ -422,6 +458,35 @@ int luaRedisPCallCommand(lua_State *lua) {
return luaRedisGenericCommand(lua,0);
}
int luaExistsCommand(lua_State *lua) {
int argc = lua_gettop(lua);
redisClient *c = server.lua_client;
char *obj_s;
size_t obj_len;
robj *key, *val;
/* Require at least one argument */
if (argc != 1) {
luaPushError(lua,
"redis.exists() needs exactly one argument");
return 1;
}
obj_s = (char*)lua_tolstring(lua,1,&obj_len);
if (obj_s == NULL) {
luaPushError(lua,
"redis.exists() argument must be a string");
return 1;
}
key = createStringObject(obj_s,obj_len);
val = lookupKeyRead(c->db,key);
decrRefCount(key);
lua_pushnumber(lua,val != NULL);
return 1;
}
/* This adds redis.sha1hex(string) to Lua scripts using the same hashing
* function used for sha1ing lua scripts. */
int luaRedisSha1hexCommand(lua_State *lua) {
@@ -628,6 +693,11 @@ void scriptingInit(void) {
lua_pushcfunction(lua,luaRedisPCallCommand);
lua_settable(lua,-3);
/* redis.exists */
lua_pushstring(lua,"exists");
lua_pushcfunction(lua,luaExistsCommand);
lua_settable(lua,-3);
/* redis.log and log levels. */
lua_pushstring(lua,"log");
lua_pushcfunction(lua,luaLogCommand);
+19 -7
View File
@@ -2741,6 +2741,12 @@ void sentinelCommand(redisClient *c) {
!= REDIS_OK) return;
if (getLongFromObjectOrReply(c,c->argv[4],&port,"Invalid port")
!= REDIS_OK) return;
if (quorum <= 0) {
addReplyError(c, "Quorum must be 1 or greater.");
return;
}
/* Make sure the IP field is actually a valid IP before passing it
* to createSentinelRedisInstance(), otherwise we may trigger a
* DNS lookup at runtime. */
@@ -2856,24 +2862,30 @@ numargserr:
/* SENTINEL INFO [section] */
void sentinelInfoCommand(redisClient *c) {
char *section = c->argc == 2 ? c->argv[1]->ptr : "default";
sds info = sdsempty();
int defsections = !strcasecmp(section,"default");
int sections = 0;
if (c->argc > 2) {
addReply(c,shared.syntaxerr);
return;
}
if (!strcasecmp(section,"server") || defsections) {
int defsections = 0, allsections = 0;
char *section = c->argc == 2 ? c->argv[1]->ptr : NULL;
if (section) {
allsections = !strcasecmp(section,"all");
defsections = !strcasecmp(section,"default");
} else {
defsections = 1;
}
int sections = 0;
sds info = sdsempty();
if (defsections || allsections || !strcasecmp(section,"server")) {
if (sections++) info = sdscat(info,"\r\n");
sds serversection = genRedisInfoString("server");
info = sdscatlen(info,serversection,sdslen(serversection));
sdsfree(serversection);
}
if (!strcasecmp(section,"sentinel") || defsections) {
if (defsections || allsections || !strcasecmp(section,"sentinel")) {
dictIterator *di;
dictEntry *de;
int master_id = 0;
-2
View File
@@ -24,9 +24,7 @@ A million repetitions of "a"
#include <stdio.h>
#include <string.h>
#include <sys/types.h> /* for u_int*_t */
#if defined(__sun)
#include "solarisfixes.h"
#endif
#include "sha1.h"
#include "config.h"
+4
View File
@@ -28,6 +28,8 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
#if defined(__sun)
#if defined(__GNUC__)
#include <math.h>
#undef isnan
@@ -48,3 +50,5 @@
#define u_int uint
#define u_int32_t uint32_t
#endif /* __GNUC__ */
#endif /* __sun */
+37 -15
View File
@@ -220,7 +220,7 @@ void sortCommand(redisClient *c) {
if (sortval)
incrRefCount(sortval);
else
sortval = createListObject();
sortval = createQuicklistObject();
/* The SORT command has an SQL-alike syntax, parse it */
while(j < c->argc) {
@@ -322,17 +322,17 @@ void sortCommand(redisClient *c) {
}
if (end >= vectorlen) end = vectorlen-1;
/* Optimization:
/* Whenever possible, we load elements into the output array in a more
* direct way. This is possible if:
*
* 1) if the object to sort is a sorted set.
* 1) The object to sort is a sorted set or a list (internally sorted).
* 2) There is nothing to sort as dontsort is true (BY <constant string>).
* 3) We have a LIMIT option that actually reduces the number of elements
* to fetch.
*
* In this case to load all the objects in the vector is a huge waste of
* resources. We just allocate a vector that is big enough for the selected
* range length, and make sure to load just this part in the vector. */
if (sortval->type == REDIS_ZSET &&
* In this special case, if we have a LIMIT option that actually reduces
* the number of elements to fetch, we also optimize to just load the
* range we are interested in and allocating a vector that is big enough
* for the selected range length. */
if ((sortval->type == REDIS_ZSET || sortval->type == REDIS_LIST) &&
dontsort &&
(start != 0 || end != vectorlen-1))
{
@@ -343,7 +343,32 @@ void sortCommand(redisClient *c) {
vector = zmalloc(sizeof(redisSortObject)*vectorlen);
j = 0;
if (sortval->type == REDIS_LIST) {
if (sortval->type == REDIS_LIST && dontsort) {
/* Special handling for a list, if 'dontsort' is true.
* This makes sure we return elements in the list original
* ordering, accordingly to DESC / ASC options.
*
* Note that in this case we also handle LIMIT here in a direct
* way, just getting the required range, as an optimization. */
if (end >= start) {
listTypeIterator *li;
listTypeEntry entry;
li = listTypeInitIterator(sortval,
desc ? (long)(listTypeLength(sortval) - start - 1) : start,
desc ? REDIS_HEAD : REDIS_TAIL);
while(j < vectorlen && listTypeNext(li,&entry)) {
vector[j].obj = listTypeGet(&entry);
vector[j].u.score = 0;
vector[j].u.cmpobj = NULL;
j++;
}
listTypeReleaseIterator(li);
/* Fix start/end: output code is not aware of this optimization. */
end -= start;
start = 0;
}
} else if (sortval->type == REDIS_LIST) {
listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL);
listTypeEntry entry;
while(listTypeNext(li,&entry)) {
@@ -399,10 +424,7 @@ void sortCommand(redisClient *c) {
j++;
ln = desc ? ln->backward : ln->level[0].forward;
}
/* The code producing the output does not know that in the case of
* sorted set, 'dontsort', and LIMIT, we are able to get just the
* range, already sorted, so we need to adjust "start" and "end"
* to make sure start is set to 0. */
/* Fix start/end: output code is not aware of this optimization. */
end -= start;
start = 0;
} else if (sortval->type == REDIS_ZSET) {
@@ -509,7 +531,7 @@ void sortCommand(redisClient *c) {
}
}
} else {
robj *sobj = createZiplistObject();
robj *sobj = createQuicklistObject();
/* STORE option specified, set the sorting result as a List object */
for (j = start; j <= end; j++) {
+32 -1
View File
@@ -130,7 +130,6 @@ robj *hashTypeGetObject(robj *o, robj *field) {
value = createStringObjectFromLongLong(vll);
}
}
} else if (o->encoding == REDIS_ENCODING_HT) {
robj *aux;
@@ -144,6 +143,29 @@ robj *hashTypeGetObject(robj *o, robj *field) {
return value;
}
/* Higher level function using hashTypeGet*() to return the length of the
* object associated with the requested field, or 0 if the field does not
* exist. */
size_t hashTypeGetValueLength(robj *o, robj *field) {
size_t len = 0;
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *vstr = NULL;
unsigned int vlen = UINT_MAX;
long long vll = LLONG_MAX;
if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0)
len = vstr ? vlen : sdigits10(vll);
} else if (o->encoding == REDIS_ENCODING_HT) {
robj *aux;
if (hashTypeGetFromHashTable(o, field, &aux) == 0)
len = stringObjectLen(aux);
} else {
redisPanic("Unknown hash encoding");
}
return len;
}
/* Test if the specified field exists in the given hash. Returns 1 if the field
* exists, and 0 when it doesn't. */
int hashTypeExists(robj *o, robj *field) {
@@ -679,12 +701,21 @@ void hdelCommand(redisClient *c) {
void hlenCommand(redisClient *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
addReplyLongLong(c,hashTypeLength(o));
}
void hstrlenCommand(redisClient *c) {
robj *o;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
addReplyLongLong(c,hashTypeGetValueLength(o,c->argv[2]));
}
static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) {
if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *vstr = NULL;
+98 -260
View File
@@ -33,75 +33,37 @@
* List API
*----------------------------------------------------------------------------*/
/* Check the argument length to see if it requires us to convert the ziplist
* to a real list. Only check raw-encoded objects because integer encoded
* objects are never too long. */
void listTypeTryConversion(robj *subject, robj *value) {
if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;
if (sdsEncodedObject(value) &&
sdslen(value->ptr) > server.list_max_ziplist_value)
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
}
/* The function pushes an element to the specified list object 'subject',
* at head or tail position as specified by 'where'.
*
* There is no need for the caller to increment the refcount of 'value' as
* the function takes care of it if needed. */
void listTypePush(robj *subject, robj *value, int where) {
/* Check if we need to convert the ziplist */
listTypeTryConversion(subject,value);
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
ziplistLen(subject->ptr) >= server.list_max_ziplist_entries)
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
int pos = (where == REDIS_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
value = getDecodedObject(value);
subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
size_t len = sdslen(value->ptr);
quicklistPush(subject->ptr, value->ptr, len, pos);
decrRefCount(value);
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
if (where == REDIS_HEAD) {
listAddNodeHead(subject->ptr,value);
} else {
listAddNodeTail(subject->ptr,value);
}
incrRefCount(value);
} else {
redisPanic("Unknown list encoding");
}
}
void *listPopSaver(unsigned char *data, unsigned int sz) {
return createStringObject((char*)data,sz);
}
robj *listTypePop(robj *subject, int where) {
long long vlong;
robj *value = NULL;
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p;
unsigned char *vstr;
unsigned int vlen;
long long vlong;
int pos = (where == REDIS_HEAD) ? 0 : -1;
p = ziplistIndex(subject->ptr,pos);
if (ziplistGet(p,&vstr,&vlen,&vlong)) {
if (vstr) {
value = createStringObject((char*)vstr,vlen);
} else {
int ql_where = where == REDIS_HEAD ? QUICKLIST_HEAD : QUICKLIST_TAIL;
if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
if (quicklistPopCustom(subject->ptr, ql_where, (unsigned char **)&value,
NULL, &vlong, listPopSaver)) {
if (!value)
value = createStringObjectFromLongLong(vlong);
}
/* We only need to delete an element when it exists */
subject->ptr = ziplistDelete(subject->ptr,&p);
}
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
list *list = subject->ptr;
listNode *ln;
if (where == REDIS_HEAD) {
ln = listFirst(list);
} else {
ln = listLast(list);
}
if (ln != NULL) {
value = listNodeValue(ln);
incrRefCount(value);
listDelNode(list,ln);
}
} else {
redisPanic("Unknown list encoding");
@@ -110,25 +72,28 @@ robj *listTypePop(robj *subject, int where) {
}
unsigned long listTypeLength(robj *subject) {
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
return ziplistLen(subject->ptr);
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
return listLength((list*)subject->ptr);
if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
return quicklistCount(subject->ptr);
} else {
redisPanic("Unknown list encoding");
}
}
/* Initialize an iterator at the specified index. */
listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char direction) {
listTypeIterator *listTypeInitIterator(robj *subject, long index,
unsigned char direction) {
listTypeIterator *li = zmalloc(sizeof(listTypeIterator));
li->subject = subject;
li->encoding = subject->encoding;
li->direction = direction;
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
li->zi = ziplistIndex(subject->ptr,index);
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
li->ln = listIndex(subject->ptr,index);
li->iter = NULL;
/* REDIS_HEAD means start at TAIL and move *towards* head.
* REDIS_TAIL means start at HEAD and move *towards tail. */
int iter_direction =
direction == REDIS_HEAD ? AL_START_TAIL : AL_START_HEAD;
if (li->encoding == REDIS_ENCODING_QUICKLIST) {
li->iter = quicklistGetIteratorAtIdx(li->subject->ptr,
iter_direction, index);
} else {
redisPanic("Unknown list encoding");
}
@@ -137,6 +102,7 @@ listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char
/* Clean up the iterator. */
void listTypeReleaseIterator(listTypeIterator *li) {
zfree(li->iter);
zfree(li);
}
@@ -148,24 +114,8 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) {
redisAssert(li->subject->encoding == li->encoding);
entry->li = li;
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
entry->zi = li->zi;
if (entry->zi != NULL) {
if (li->direction == REDIS_TAIL)
li->zi = ziplistNext(li->subject->ptr,li->zi);
else
li->zi = ziplistPrev(li->subject->ptr,li->zi);
return 1;
}
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
entry->ln = li->ln;
if (entry->ln != NULL) {
if (li->direction == REDIS_TAIL)
li->ln = li->ln->next;
else
li->ln = li->ln->prev;
return 1;
}
if (li->encoding == REDIS_ENCODING_QUICKLIST) {
return quicklistNext(li->iter, &entry->entry);
} else {
redisPanic("Unknown list encoding");
}
@@ -174,24 +124,14 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) {
/* Return entry or NULL at the current position of the iterator. */
robj *listTypeGet(listTypeEntry *entry) {
listTypeIterator *li = entry->li;
robj *value = NULL;
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *vstr;
unsigned int vlen;
long long vlong;
redisAssert(entry->zi != NULL);
if (ziplistGet(entry->zi,&vstr,&vlen,&vlong)) {
if (vstr) {
value = createStringObject((char*)vstr,vlen);
} else {
value = createStringObjectFromLongLong(vlong);
}
if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
if (entry->entry.value) {
value = createStringObject((char *)entry->entry.value,
entry->entry.sz);
} else {
value = createStringObjectFromLongLong(entry->entry.longval);
}
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
redisAssert(entry->ln != NULL);
value = listNodeValue(entry->ln);
incrRefCount(value);
} else {
redisPanic("Unknown list encoding");
}
@@ -199,30 +139,18 @@ robj *listTypeGet(listTypeEntry *entry) {
}
void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
robj *subject = entry->li->subject;
if (entry->li->encoding == REDIS_ENCODING_ZIPLIST) {
if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
value = getDecodedObject(value);
sds str = value->ptr;
size_t len = sdslen(str);
if (where == REDIS_TAIL) {
unsigned char *next = ziplistNext(subject->ptr,entry->zi);
/* When we insert after the current element, but the current element
* is the tail of the list, we need to do a push. */
if (next == NULL) {
subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),REDIS_TAIL);
} else {
subject->ptr = ziplistInsert(subject->ptr,next,value->ptr,sdslen(value->ptr));
}
} else {
subject->ptr = ziplistInsert(subject->ptr,entry->zi,value->ptr,sdslen(value->ptr));
quicklistInsertAfter((quicklist *)entry->entry.quicklist,
&entry->entry, str, len);
} else if (where == REDIS_HEAD) {
quicklistInsertBefore((quicklist *)entry->entry.quicklist,
&entry->entry, str, len);
}
decrRefCount(value);
} else if (entry->li->encoding == REDIS_ENCODING_LINKEDLIST) {
if (where == REDIS_TAIL) {
listInsertNode(subject->ptr,entry->ln,value,AL_START_TAIL);
} else {
listInsertNode(subject->ptr,entry->ln,value,AL_START_HEAD);
}
incrRefCount(value);
} else {
redisPanic("Unknown list encoding");
}
@@ -230,59 +158,33 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
/* Compare the given object with the entry at the current position. */
int listTypeEqual(listTypeEntry *entry, robj *o) {
listTypeIterator *li = entry->li;
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
redisAssertWithInfo(NULL,o,sdsEncodedObject(o));
return ziplistCompare(entry->zi,o->ptr,sdslen(o->ptr));
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
return equalStringObjects(o,listNodeValue(entry->ln));
return quicklistCompare(entry->entry.zi,o->ptr,sdslen(o->ptr));
} else {
redisPanic("Unknown list encoding");
}
}
/* Delete the element pointed to. */
void listTypeDelete(listTypeEntry *entry) {
listTypeIterator *li = entry->li;
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p = entry->zi;
li->subject->ptr = ziplistDelete(li->subject->ptr,&p);
/* Update position of the iterator depending on the direction */
if (li->direction == REDIS_TAIL)
li->zi = p;
else
li->zi = ziplistPrev(li->subject->ptr,p);
} else if (entry->li->encoding == REDIS_ENCODING_LINKEDLIST) {
listNode *next;
if (li->direction == REDIS_TAIL)
next = entry->ln->next;
else
next = entry->ln->prev;
listDelNode(li->subject->ptr,entry->ln);
li->ln = next;
void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry) {
if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
quicklistDelEntry(iter->iter, &entry->entry);
} else {
redisPanic("Unknown list encoding");
}
}
/* Create a quicklist from a single ziplist */
void listTypeConvert(robj *subject, int enc) {
listTypeIterator *li;
listTypeEntry entry;
redisAssertWithInfo(NULL,subject,subject->type == REDIS_LIST);
redisAssertWithInfo(NULL,subject,subject->type==REDIS_LIST);
redisAssertWithInfo(NULL,subject,subject->encoding==REDIS_ENCODING_ZIPLIST);
if (enc == REDIS_ENCODING_LINKEDLIST) {
list *l = listCreate();
listSetFreeMethod(l,decrRefCountVoid);
/* listTypeGet returns a robj with incremented refcount */
li = listTypeInitIterator(subject,0,REDIS_TAIL);
while (listTypeNext(li,&entry)) listAddNodeTail(l,listTypeGet(&entry));
listTypeReleaseIterator(li);
subject->encoding = REDIS_ENCODING_LINKEDLIST;
zfree(subject->ptr);
subject->ptr = l;
if (enc == REDIS_ENCODING_QUICKLIST) {
size_t zlen = server.list_max_ziplist_size;
int depth = server.list_compress_depth;
subject->ptr = quicklistCreateFromZiplist(zlen, depth, subject->ptr);
subject->encoding = REDIS_ENCODING_QUICKLIST;
} else {
redisPanic("Unsupported list conversion");
}
@@ -304,7 +206,9 @@ void pushGenericCommand(redisClient *c, int where) {
for (j = 2; j < c->argc; j++) {
c->argv[j] = tryObjectEncoding(c->argv[j]);
if (!lobj) {
lobj = createZiplistObject();
lobj = createQuicklistObject();
quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
server.list_compress_depth);
dbAdd(c->db,c->argv[1],lobj);
}
listTypePush(lobj,c->argv[j],where);
@@ -338,13 +242,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
checkType(c,subject,REDIS_LIST)) return;
if (refval != NULL) {
/* We're not sure if this value can be inserted yet, but we cannot
* convert the list inside the iterator. We don't want to loop over
* the list twice (once to see if the value can be inserted and once
* to do the actual insert), so we assume this value can be inserted
* and convert the ziplist to a regular list if necessary. */
listTypeTryConversion(subject,val);
/* Seek refval from head to tail */
iter = listTypeInitIterator(subject,0,REDIS_TAIL);
while (listTypeNext(iter,&entry)) {
@@ -357,10 +254,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
listTypeReleaseIterator(iter);
if (inserted) {
/* Check if the length exceeds the ziplist length threshold. */
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"linsert",
c->argv[1],c->db->id);
@@ -418,31 +311,19 @@ void lindexCommand(redisClient *c) {
if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK))
return;
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p;
unsigned char *vstr;
unsigned int vlen;
long long vlong;
p = ziplistIndex(o->ptr,index);
if (ziplistGet(p,&vstr,&vlen,&vlong)) {
if (vstr) {
value = createStringObject((char*)vstr,vlen);
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
quicklistEntry entry;
if (quicklistIndex(o->ptr, index, &entry)) {
if (entry.value) {
value = createStringObject((char*)entry.value,entry.sz);
} else {
value = createStringObjectFromLongLong(vlong);
value = createStringObjectFromLongLong(entry.longval);
}
addReplyBulk(c,value);
decrRefCount(value);
} else {
addReply(c,shared.nullbulk);
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
listNode *ln = listIndex(o->ptr,index);
if (ln != NULL) {
value = listNodeValue(ln);
addReplyBulk(c,value);
} else {
addReply(c,shared.nullbulk);
}
} else {
redisPanic("Unknown list encoding");
}
@@ -452,35 +333,18 @@ void lsetCommand(redisClient *c) {
robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
if (o == NULL || checkType(c,o,REDIS_LIST)) return;
long index;
robj *value = (c->argv[3] = tryObjectEncoding(c->argv[3]));
robj *value = c->argv[3];
if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK))
return;
listTypeTryConversion(o,value);
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p, *zl = o->ptr;
p = ziplistIndex(zl,index);
if (p == NULL) {
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
quicklist *ql = o->ptr;
int replaced = quicklistReplaceAtIndex(ql, index,
value->ptr, sdslen(value->ptr));
if (!replaced) {
addReply(c,shared.outofrangeerr);
} else {
o->ptr = ziplistDelete(o->ptr,&p);
value = getDecodedObject(value);
o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr));
decrRefCount(value);
addReply(c,shared.ok);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
server.dirty++;
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
listNode *ln = listIndex(o->ptr,index);
if (ln == NULL) {
addReply(c,shared.outofrangeerr);
} else {
decrRefCount((robj*)listNodeValue(ln));
listNodeValue(ln) = value;
incrRefCount(value);
addReply(c,shared.ok);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
@@ -549,43 +413,28 @@ void lrangeCommand(redisClient *c) {
/* Return the result in form of a multi-bulk reply */
addReplyMultiBulkLen(c,rangelen);
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p = ziplistIndex(o->ptr,start);
unsigned char *vstr;
unsigned int vlen;
long long vlong;
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
listTypeIterator *iter = listTypeInitIterator(o, start, REDIS_TAIL);
while(rangelen--) {
ziplistGet(p,&vstr,&vlen,&vlong);
if (vstr) {
addReplyBulkCBuffer(c,vstr,vlen);
listTypeEntry entry;
listTypeNext(iter, &entry);
quicklistEntry *qe = &entry.entry;
if (qe->value) {
addReplyBulkCBuffer(c,qe->value,qe->sz);
} else {
addReplyBulkLongLong(c,vlong);
addReplyBulkLongLong(c,qe->longval);
}
p = ziplistNext(o->ptr,p);
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
listNode *ln;
/* If we are nearest to the end of the list, reach the element
* starting from tail and going backward, as it is faster. */
if (start > llen/2) start -= llen;
ln = listIndex(o->ptr,start);
while(rangelen--) {
addReplyBulk(c,ln->value);
ln = ln->next;
}
listTypeReleaseIterator(iter);
} else {
redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!");
redisPanic("List encoding is not QUICKLIST!");
}
}
void ltrimCommand(redisClient *c) {
robj *o;
long start, end, llen, j, ltrim, rtrim;
list *list;
listNode *ln;
long start, end, llen, ltrim, rtrim;
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
@@ -612,19 +461,9 @@ void ltrimCommand(redisClient *c) {
}
/* Remove list elements to perform the trim */
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
o->ptr = ziplistDeleteRange(o->ptr,0,ltrim);
o->ptr = ziplistDeleteRange(o->ptr,-rtrim,rtrim);
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
list = o->ptr;
for (j = 0; j < ltrim; j++) {
ln = listFirst(list);
listDelNode(list,ln);
}
for (j = 0; j < rtrim; j++) {
ln = listLast(list);
listDelNode(list,ln);
}
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
quicklistDelRange(o->ptr,0,ltrim);
quicklistDelRange(o->ptr,-rtrim,rtrim);
} else {
redisPanic("Unknown list encoding");
}
@@ -641,10 +480,9 @@ void ltrimCommand(redisClient *c) {
void lremCommand(redisClient *c) {
robj *subject, *obj;
obj = c->argv[3] = tryObjectEncoding(c->argv[3]);
obj = c->argv[3];
long toremove;
long removed = 0;
listTypeEntry entry;
if ((getLongFromObjectOrReply(c, c->argv[2], &toremove, NULL) != REDIS_OK))
return;
@@ -652,10 +490,6 @@ void lremCommand(redisClient *c) {
subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero);
if (subject == NULL || checkType(c,subject,REDIS_LIST)) return;
/* Make sure obj is raw when we're dealing with a ziplist */
if (subject->encoding == REDIS_ENCODING_ZIPLIST)
obj = getDecodedObject(obj);
listTypeIterator *li;
if (toremove < 0) {
toremove = -toremove;
@@ -664,9 +498,10 @@ void lremCommand(redisClient *c) {
li = listTypeInitIterator(subject,0,REDIS_TAIL);
}
listTypeEntry entry;
while (listTypeNext(li,&entry)) {
if (listTypeEqual(&entry,obj)) {
listTypeDelete(&entry);
listTypeDelete(li, &entry);
server.dirty++;
removed++;
if (toremove && removed == toremove) break;
@@ -674,11 +509,10 @@ void lremCommand(redisClient *c) {
}
listTypeReleaseIterator(li);
/* Clean up raw encoded object */
if (subject->encoding == REDIS_ENCODING_ZIPLIST)
decrRefCount(obj);
if (listTypeLength(subject) == 0) {
dbDelete(c->db,c->argv[1]);
}
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
addReplyLongLong(c,removed);
if (removed) signalModifiedKey(c->db,c->argv[1]);
}
@@ -702,7 +536,9 @@ void lremCommand(redisClient *c) {
void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
/* Create the list if the key does not exist */
if (!dstobj) {
dstobj = createZiplistObject();
dstobj = createQuicklistObject();
quicklistSetOptions(dstobj->ptr, server.list_max_ziplist_size,
server.list_compress_depth);
dbAdd(c->db,dstkey,dstobj);
}
signalModifiedKey(c->db,dstkey);
@@ -1010,7 +846,9 @@ void handleClientsBlockedOnLists(void) {
}
}
if (listTypeLength(o) == 0) dbDelete(rl->db,rl->key);
if (listTypeLength(o) == 0) {
dbDelete(rl->db,rl->key);
}
/* We don't call signalModifiedKey() as it was already called
* when an element was pushed on the list. */
}
+105 -162
View File
@@ -45,6 +45,11 @@ robj *setTypeCreate(robj *value) {
return createSetObject();
}
/* Add the specified value into a set. The function takes care of incrementing
* the reference count of the object if needed in order to retain a copy.
*
* If the value was already member of the set, nothing is done and 0 is
* returned, otherwise the new element is added and 1 is returned. */
int setTypeAdd(robj *subject, robj *value) {
long long llval;
if (subject->encoding == REDIS_ENCODING_HT) {
@@ -207,106 +212,6 @@ int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele) {
return setobj->encoding;
}
/* Return a number of random elements from a non empty set.
*
* This is a version of setTypeRandomElement() that is modified in order to
* return multiple entries, using dictGetRandomKeys() and intsetRandomMembers().
*
* The elements are stored into 'aux_set' which should be of a set type.
*
* The function returns the number of items stored into 'aux_set', that may
* be less than 'count' if the hash table has less than 'count' elements
* inside.
*
* Note that this function is not suitable when you need a good distribution
* of the returned items, but only when you need to "sample" a given number
* of continuous elements to run some kind of algorithm or to produce
* statistics. However the function is much faster than setTypeRandomElement()
* at producing N elements, and the elements are guaranteed to be non
* repeating.
*/
unsigned long setTypeRandomElements(robj *set, unsigned long count,
robj *aux_set) {
unsigned long set_size;
unsigned long elements_to_return = count;
unsigned long elements_copied = 0;
unsigned long current_element = 0;
/* Like all setType* functions, we assume good behavior on part of the
* caller, so no extra parameter checks are made. */
/* If the number of elements in the the set is less than the count
* requested, just return all of them. */
set_size = setTypeSize(set);
if (set_size < count) {
elements_to_return = set_size;
}
/* TODO: It is definitely faster adding items to the set by directly
* handling the Dict or intset inside it, avoiding the constant encoding
* checks inside setTypeAdd(). However, We don't want to touch the set
* internals in non setType* functions. So, we just call setTypeAdd()
* multiple times, but this isn't an optimal solution.
* Another option would be to create a bulk-add function:
* setTypeAddBulk(). */
if (set->encoding == REDIS_ENCODING_HT) {
/* Allocate result array */
dictEntry **random_elements =
zmalloc(sizeof(dictEntry*) * elements_to_return);
/* Get the random elements */
elements_copied =
dictGetRandomKeys(set->ptr, random_elements, elements_to_return);
redisAssert(elements_copied == elements_to_return);
/* Put them into the set */
for (current_element = 0; current_element < elements_copied;
current_element++) {
/* We get the key and duplicate it, as we know it is a string */
setTypeAdd(aux_set,
dictGetKey(random_elements[current_element]));
}
zfree(random_elements);
} else if (set->encoding == REDIS_ENCODING_INTSET) {
/* Allocate result array */
int64_t *random_elements =
zmalloc(sizeof(int64_t) * elements_to_return);
robj* element_as_str = NULL;
elements_copied =
intsetRandomMembers((intset*) set->ptr,
random_elements,
elements_to_return);
redisAssert(elements_copied == elements_to_return);
/* Put them into the set */
for (current_element = 0; current_element < elements_copied;
current_element++) {
element_as_str = createStringObjectFromLongLong(
random_elements[current_element]);
/* Put the values in the set */
setTypeAdd(aux_set,
element_as_str);
decrRefCount(element_as_str);
}
zfree(random_elements);
} else {
redisPanic("Unknown set encoding");
}
/* We have a set with random elements. Return the actual elements in
the aux_set. */
return elements_copied;
}
unsigned long setTypeSize(robj *subject) {
if (subject->encoding == REDIS_ENCODING_HT) {
return dictSize((dict*)subject->ptr);
@@ -480,15 +385,18 @@ void scardCommand(redisClient *c) {
addReplyLongLong(c,setTypeSize(o));
}
/* handle the "SPOP key <count>" variant. The normal version of the
/* Handle the "SPOP key <count>" variant. The normal version of the
* command is handled by the spopCommand() function itself. */
/* How many times bigger should be the set compared to the remaining size
* for us to use the "create new set" strategy? Read later in the
* implementation for more info. */
#define SPOP_MOVE_STRATEGY_MUL 5
void spopWithCountCommand(redisClient *c) {
long l;
unsigned long count, size;
unsigned long elements_returned;
robj *set, *aux, *aux_set;
int64_t llele;
robj *set;
/* Get the count argument */
if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != REDIS_OK) return;
@@ -511,18 +419,16 @@ void spopWithCountCommand(redisClient *c) {
return;
}
/* Get the size of the set. It is always > 0, as empty sets get
* deleted. */
size = setTypeSize(set);
/* Generate an SPOP keyspace notification */
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id);
server.dirty += count;
/* CASE 1:
* The number of requested elements is greater than or equal to
* the number of elements inside the set: simply return the whole set. */
if (count >= size) {
/* We just return the entire set */
sunionDiffGenericCommand(c,c->argv+1,1,NULL,REDIS_OP_UNION);
@@ -530,73 +436,110 @@ void spopWithCountCommand(redisClient *c) {
dbDelete(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
/* Replicate/AOF this command as an SREM operation */
aux = createStringObject("DEL",3);
rewriteClientCommandVector(c,2,aux,c->argv[1]);
decrRefCount(aux);
/* Propagate this command as an DEL operation */
rewriteClientCommandVector(c,2,shared.del,c->argv[1]);
signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
return;
}
/* CASE 2:
* The number of requested elements is less than the number
* of elements inside the set. */
/* Case 2 and 3 require to replicate SPOP as a set of SERM commands.
* Prepare our replication argument vector. Also send the array length
* which is common to both the code paths. */
robj *propargv[3];
propargv[0] = createStringObject("SREM",4);
propargv[1] = c->argv[1];
addReplyMultiBulkLen(c,count);
/* We need an auxiliary set. Optimistically, we create a set using an
* Intset internally. */
aux = createStringObjectFromLongLong(0);
aux_set = setTypeCreate(aux);
decrRefCount(aux);
/* Common iteration vars. */
robj *objele;
int encoding;
int64_t llele;
unsigned long remaining = size-count; /* Elements left after SPOP. */
/* Get the count requested of random elements from the set into our
* auxiliary set. */
elements_returned = setTypeRandomElements(set, count, aux_set);
redisAssert(elements_returned == count);
{
setTypeIterator *si;
robj *objele;
int element_encoding;
addReplyMultiBulkLen(c, elements_returned);
/* Replicate/AOF this command as an SREM operation */
aux = createStringObject("SREM",4);
si = setTypeInitIterator(aux_set);
while ((element_encoding = setTypeNext(si, &objele, &llele)) != -1) {
if (element_encoding == REDIS_ENCODING_HT) {
addReplyBulk(c, objele);
/* Replicate/AOF this command as an SREM commands */
rewriteClientCommandVector(c, 3, aux, c->argv[1], objele);
setTypeRemove(set, objele);
}
else if (element_encoding == REDIS_ENCODING_INTSET) {
/* TODO: setTypeRemove() forces us to convert all of the ints
* to string... isn't there a nicer way to do this? */
/* If we are here, the number of requested elements is less than the
* number of elements inside the set. Also we are sure that count < size.
* Use two different strategies.
*
* CASE 2: The number of elements to return is small compared to the
* set size. We can just extract random elements and return them to
* the set. */
if (remaining*SPOP_MOVE_STRATEGY_MUL > count) {
while(count--) {
encoding = setTypeRandomElement(set,&objele,&llele);
if (encoding == REDIS_ENCODING_INTSET) {
objele = createStringObjectFromLongLong(llele);
addReplyBulk(c, objele);
/* Replicate/AOF this command as an SREM commands */
rewriteClientCommandVector(c, 3, aux, c->argv[1], objele);
setTypeRemove(set, objele);
/* We created it, we kill it. */
decrRefCount(objele);
} else {
incrRefCount(objele);
}
else {
redisPanic("Unknown set encoding");
/* Return the element to the client and remove from the set. */
addReplyBulk(c,objele);
setTypeRemove(set,objele);
/* Replicate/AOF this command as an SREM operation */
propargv[2] = objele;
alsoPropagate(server.sremCommand,c->db->id,propargv,3,
REDIS_PROPAGATE_AOF|REDIS_PROPAGATE_REPL);
decrRefCount(objele);
}
} else {
/* CASE 3: The number of elements to return is very big, approaching
* the size of the set itself. After some time extracting random elements
* from such a set becomes computationally expensive, so we use
* a different strategy, we extract random elements that we don't
* want to return (the elements that will remain part of the set),
* creating a new set as we do this (that will be stored as the original
* set). Then we return the elements left in the original set and
* release it. */
robj *newset = NULL;
/* Create a new set with just the remaining elements. */
while(remaining--) {
encoding = setTypeRandomElement(set,&objele,&llele);
if (encoding == REDIS_ENCODING_INTSET) {
objele = createStringObjectFromLongLong(llele);
} else {
incrRefCount(objele);
}
if (!newset) newset = setTypeCreate(objele);
setTypeAdd(newset,objele);
setTypeRemove(set,objele);
decrRefCount(objele);
}
/* Assign the new set as the key value. */
incrRefCount(set); /* Protect the old set value. */
dbOverwrite(c->db,c->argv[1],newset);
/* Tranfer the old set to the client and release it. */
setTypeIterator *si;
si = setTypeInitIterator(set);
while((encoding = setTypeNext(si,&objele,&llele)) != -1) {
if (encoding == REDIS_ENCODING_INTSET) {
objele = createStringObjectFromLongLong(llele);
} else {
incrRefCount(objele);
}
addReplyBulk(c,objele);
/* Replicate/AOF this command as an SREM operation */
propargv[2] = objele;
alsoPropagate(server.sremCommand,c->db->id,propargv,3,
REDIS_PROPAGATE_AOF|REDIS_PROPAGATE_REPL);
decrRefCount(objele);
}
setTypeReleaseIterator(si);
decrRefCount(aux);
decrRefCount(set);
}
/* Free the auxiliary set - we need it no more. */
decrRefCount(aux_set);
/* Don't propagate the command itself even if we incremented the
* dirty counter. We don't want to propagate an SPOP command since
* we propagated the command as a set of SREMs operations using
* the alsoPropagate() API. */
decrRefCount(propargv[0]);
preventCommandPropagation(c);
}
void spopCommand(redisClient *c) {
+17 -5
View File
@@ -61,6 +61,8 @@ static int checkStringLength(redisClient *c, long long size) {
#define REDIS_SET_NO_FLAGS 0
#define REDIS_SET_NX (1<<0) /* Set if key not exists. */
#define REDIS_SET_XX (1<<1) /* Set if key exists. */
#define REDIS_SET_EX (1<<2) /* Set if time in seconds is given */
#define REDIS_SET_PX (1<<3) /* Set if time in ms in given */
void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
long long milliseconds = 0; /* initialized to avoid any harmness warning */
@@ -102,18 +104,28 @@ void setCommand(redisClient *c) {
robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
if ((a[0] == 'n' || a[0] == 'N') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
!(flags & REDIS_SET_XX))
{
flags |= REDIS_SET_NX;
} else if ((a[0] == 'x' || a[0] == 'X') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
!(flags & REDIS_SET_NX))
{
flags |= REDIS_SET_XX;
} else if ((a[0] == 'e' || a[0] == 'E') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
!(flags & REDIS_SET_PX) && next)
{
flags |= REDIS_SET_EX;
unit = UNIT_SECONDS;
expire = next;
j++;
} else if ((a[0] == 'p' || a[0] == 'P') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
!(flags & REDIS_SET_EX) && next)
{
flags |= REDIS_SET_PX;
unit = UNIT_MILLISECONDS;
expire = next;
j++;
@@ -194,7 +206,7 @@ void setrangeCommand(redisClient *c) {
if (checkStringLength(c,offset+sdslen(value)) != REDIS_OK)
return;
o = createObject(REDIS_STRING,sdsempty());
o = createObject(REDIS_STRING,sdsnewlen(NULL, offset+sdslen(value)));
dbAdd(c->db,c->argv[1],o);
} else {
size_t olen;
+70 -13
View File
@@ -38,8 +38,10 @@
#include <sys/time.h>
#include <float.h>
#include <stdint.h>
#include <errno.h>
#include "util.h"
#include "sha1.h"
/* Glob-style pattern matching. */
int stringmatchlen(const char *pattern, int patternLen,
@@ -169,11 +171,12 @@ int stringmatch(const char *pattern, const char *string, int nocase) {
}
/* Convert a string representing an amount of memory into the number of
* bytes, so for instance memtoll("1Gi") will return 1073741824 that is
* bytes, so for instance memtoll("1Gb") will return 1073741824 that is
* (1024*1024*1024).
*
* On parsing error, if *err is not NULL, it's set to 1, otherwise it's
* set to 0 */
* set to 0. On error the function return value is 0, regardless of the
* fact 'err' is NULL or not. */
long long memtoll(const char *p, int *err) {
const char *u;
char buf[128];
@@ -182,6 +185,7 @@ long long memtoll(const char *p, int *err) {
unsigned int digits;
if (err) *err = 0;
/* Search the first non digit character. */
u = p;
if (*u == '-') u++;
@@ -202,16 +206,26 @@ long long memtoll(const char *p, int *err) {
mul = 1024L*1024*1024;
} else {
if (err) *err = 1;
mul = 1;
return 0;
}
/* Copy the digits into a buffer, we'll use strtoll() to convert
* the digit (without the unit) into a number. */
digits = u-p;
if (digits >= sizeof(buf)) {
if (err) *err = 1;
return LLONG_MAX;
return 0;
}
memcpy(buf,p,digits);
buf[digits] = '\0';
val = strtoll(buf,NULL,10);
char *endptr;
errno = 0;
val = strtoll(buf,&endptr,10);
if ((val == 0 && errno == EINVAL) || *endptr != '\0') {
if (err) *err = 1;
return 0;
}
return val*mul;
}
@@ -237,6 +251,18 @@ uint32_t digits10(uint64_t v) {
return 12 + digits10(v / 1000000000000UL);
}
/* Like digits10() but for signed values. */
uint32_t sdigits10(int64_t v) {
if (v < 0) {
/* Abs value of LLONG_MIN requires special handling. */
uint64_t uv = (v != LLONG_MIN) ?
(uint64_t)-v : ((uint64_t) LLONG_MAX)+1;
return digits10(uv)+1; /* +1 for the minus. */
} else {
return digits10(v);
}
}
/* Convert a long long into a string. Returns the number of
* characters needed to represent the number.
* If the buffer is not big enough to store the string, 0 is returned.
@@ -428,11 +454,44 @@ int d2string(char *buf, size_t len, double value) {
* having run_id == A, and you reconnect and it has run_id == B, you can be
* sure that it is either a different instance or it was restarted. */
void getRandomHexChars(char *p, unsigned int len) {
FILE *fp = fopen("/dev/urandom","r");
char *charset = "0123456789abcdef";
unsigned int j;
if (fp == NULL || fread(p,len,1,fp) == 0) {
/* Global state. */
static int seed_initialized = 0;
static unsigned char seed[20]; /* The SHA1 seed, from /dev/urandom. */
static uint64_t counter = 0; /* The counter we hash with the seed. */
if (!seed_initialized) {
/* Initialize a seed and use SHA1 in counter mode, where we hash
* the same seed with a progressive counter. For the goals of this
* function we just need non-colliding strings, there are no
* cryptographic security needs. */
FILE *fp = fopen("/dev/urandom","r");
if (fp && fread(seed,sizeof(seed),1,fp) == 1)
seed_initialized = 1;
if (fp) fclose(fp);
}
if (seed_initialized) {
while(len) {
unsigned char digest[20];
SHA1_CTX ctx;
unsigned int copylen = len > 20 ? 20 : len;
SHA1Init(&ctx);
SHA1Update(&ctx, seed, sizeof(seed));
SHA1Update(&ctx, (unsigned char*)&counter,sizeof(counter));
SHA1Final(digest, &ctx);
counter++;
memcpy(p,digest,copylen);
/* Convert to hex digits. */
for (j = 0; j < copylen; j++) p[j] = charset[p[j] & 0x0F];
len -= copylen;
p += copylen;
}
} else {
/* If we can't read from /dev/urandom, do some reasonable effort
* in order to create some entropy, since this function is used to
* generate run_id and cluster instance IDs */
@@ -459,14 +518,12 @@ void getRandomHexChars(char *p, unsigned int len) {
x += sizeof(pid);
}
/* Finally xor it with rand() output, that was already seeded with
* time() at startup. */
for (j = 0; j < len; j++)
* time() at startup, and convert to hex digits. */
for (j = 0; j < len; j++) {
p[j] ^= rand();
p[j] = charset[p[j] & 0x0F];
}
}
/* Turn it into hex digits taking just 4 bits out of 8 for every byte. */
for (j = 0; j < len; j++)
p[j] = charset[p[j] & 0x0F];
if (fp) fclose(fp);
}
/* Given the filename, return the absolute path as an SDS string, or NULL
+3
View File
@@ -30,11 +30,14 @@
#ifndef __REDIS_UTIL_H
#define __REDIS_UTIL_H
#include <stdint.h>
#include "sds.h"
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
int stringmatch(const char *p, const char *s, int nocase);
long long memtoll(const char *p, int *err);
uint32_t digits10(uint64_t v);
uint32_t sdigits10(int64_t v);
int ll2string(char *s, size_t len, long long value);
int string2ll(const char *s, size_t slen, long long *value);
int string2l(const char *s, size_t slen, long *value);
+1 -1
View File
@@ -1 +1 @@
#define REDIS_VERSION "2.9.999"
#define REDIS_VERSION "3.1.999"
+218
View File
@@ -143,6 +143,7 @@
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
#define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t))
#define ZIPLIST_END_SIZE (sizeof(uint8_t))
#define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE)
#define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
#define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
@@ -176,6 +177,8 @@ typedef struct zlentry {
if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; \
} while(0)
void ziplistRepr(unsigned char *zl);
/* Return bytes needed to store integer encoded by 'encoding' */
static unsigned int zipIntSize(unsigned char encoding) {
switch(encoding) {
@@ -670,6 +673,121 @@ static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsig
return zl;
}
/* Merge ziplists 'first' and 'second' by appending 'second' to 'first'.
*
* NOTE: The larger ziplist is reallocated to contain the new merged ziplist.
* Either 'first' or 'second' can be used for the result. The parameter not
* used will be free'd and set to NULL.
*
* After calling this function, the input parameters are no longer valid since
* they are changed and free'd in-place.
*
* The result ziplist is the contents of 'first' followed by 'second'.
*
* On failure: returns NULL if the merge is impossible.
* On success: returns the merged ziplist (which is expanded version of either
* 'first' or 'second', also frees the other unused input ziplist, and sets the
* input ziplist argument equal to newly reallocated ziplist return value. */
unsigned char *ziplistMerge(unsigned char **first, unsigned char **second) {
/* If any params are null, we can't merge, so NULL. */
if (first == NULL || *first == NULL || second == NULL || *second == NULL)
return NULL;
/* Can't merge same list into itself. */
if (*first == *second)
return NULL;
size_t first_bytes = intrev32ifbe(ZIPLIST_BYTES(*first));
size_t first_len = intrev16ifbe(ZIPLIST_LENGTH(*first));
size_t second_bytes = intrev32ifbe(ZIPLIST_BYTES(*second));
size_t second_len = intrev16ifbe(ZIPLIST_LENGTH(*second));
int append;
unsigned char *source, *target;
size_t target_bytes, source_bytes;
/* Pick the largest ziplist so we can resize easily in-place.
* We must also track if we are now appending or prepending to
* the target ziplist. */
if (first_len >= second_len) {
/* retain first, append second to first. */
target = *first;
target_bytes = first_bytes;
source = *second;
source_bytes = second_bytes;
append = 1;
} else {
/* else, retain second, prepend first to second. */
target = *second;
target_bytes = second_bytes;
source = *first;
source_bytes = first_bytes;
append = 0;
}
/* Calculate final bytes (subtract one pair of metadata) */
size_t zlbytes = first_bytes + second_bytes -
ZIPLIST_HEADER_SIZE - ZIPLIST_END_SIZE;
size_t zllength = first_len + second_len;
/* Combined zl length should be limited within UINT16_MAX */
zllength = zllength < UINT16_MAX ? zllength : UINT16_MAX;
/* Save offset positions before we start ripping memory apart. */
size_t first_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*first));
size_t second_offset = intrev32ifbe(ZIPLIST_TAIL_OFFSET(*second));
/* Extend target to new zlbytes then append or prepend source. */
target = zrealloc(target, zlbytes);
if (append) {
/* append == appending to target */
/* Copy source after target (copying over original [END]):
* [TARGET - END, SOURCE - HEADER] */
memcpy(target + target_bytes - ZIPLIST_END_SIZE,
source + ZIPLIST_HEADER_SIZE,
source_bytes - ZIPLIST_HEADER_SIZE);
} else {
/* !append == prepending to target */
/* Move target *contents* exactly size of (source - [END]),
* then copy source into vacataed space (source - [END]):
* [SOURCE - END, TARGET - HEADER] */
memmove(target + source_bytes - ZIPLIST_END_SIZE,
target + ZIPLIST_HEADER_SIZE,
target_bytes - ZIPLIST_HEADER_SIZE);
memcpy(target, source, source_bytes - ZIPLIST_END_SIZE);
}
/* Update header metadata. */
ZIPLIST_BYTES(target) = intrev32ifbe(zlbytes);
ZIPLIST_LENGTH(target) = intrev16ifbe(zllength);
/* New tail offset is:
* + N bytes of first ziplist
* - 1 byte for [END] of first ziplist
* + M bytes for the offset of the original tail of the second ziplist
* - J bytes for HEADER because second_offset keeps no header. */
ZIPLIST_TAIL_OFFSET(target) = intrev32ifbe(
(first_bytes - ZIPLIST_END_SIZE) +
(second_offset - ZIPLIST_HEADER_SIZE));
/* __ziplistCascadeUpdate just fixes the prev length values until it finds a
* correct prev length value (then it assumes the rest of the list is okay).
* We tell CascadeUpdate to start at the first ziplist's tail element to fix
* the merge seam. */
target = __ziplistCascadeUpdate(target, target+first_offset);
/* Now free and NULL out what we didn't realloc */
if (append) {
zfree(*second);
*second = NULL;
*first = target;
} else {
zfree(*first);
*first = NULL;
*second = target;
}
return target;
}
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where) {
unsigned char *p;
p = (where == ZIPLIST_HEAD) ? ZIPLIST_ENTRY_HEAD(zl) : ZIPLIST_ENTRY_END(zl);
@@ -1104,6 +1222,8 @@ int ziplistTest(int argc, char **argv) {
zl = createIntList();
ziplistRepr(zl);
zfree(zl);
zl = createList();
ziplistRepr(zl);
@@ -1119,6 +1239,8 @@ int ziplistTest(int argc, char **argv) {
zl = pop(zl,ZIPLIST_TAIL);
ziplistRepr(zl);
zfree(zl);
printf("Get element at index 3:\n");
{
zl = createList();
@@ -1134,6 +1256,7 @@ int ziplistTest(int argc, char **argv) {
printf("%lld\n", value);
}
printf("\n");
zfree(zl);
}
printf("Get element at index 4 (out of range):\n");
@@ -1147,6 +1270,7 @@ int ziplistTest(int argc, char **argv) {
return 1;
}
printf("\n");
zfree(zl);
}
printf("Get element at index -1 (last element):\n");
@@ -1164,6 +1288,7 @@ int ziplistTest(int argc, char **argv) {
printf("%lld\n", value);
}
printf("\n");
zfree(zl);
}
printf("Get element at index -4 (first element):\n");
@@ -1181,6 +1306,7 @@ int ziplistTest(int argc, char **argv) {
printf("%lld\n", value);
}
printf("\n");
zfree(zl);
}
printf("Get element at index -5 (reverse out of range):\n");
@@ -1194,6 +1320,7 @@ int ziplistTest(int argc, char **argv) {
return 1;
}
printf("\n");
zfree(zl);
}
printf("Iterate list from 0 to end:\n");
@@ -1211,6 +1338,7 @@ int ziplistTest(int argc, char **argv) {
printf("\n");
}
printf("\n");
zfree(zl);
}
printf("Iterate list from 1 to end:\n");
@@ -1228,6 +1356,7 @@ int ziplistTest(int argc, char **argv) {
printf("\n");
}
printf("\n");
zfree(zl);
}
printf("Iterate list from 2 to end:\n");
@@ -1245,6 +1374,7 @@ int ziplistTest(int argc, char **argv) {
printf("\n");
}
printf("\n");
zfree(zl);
}
printf("Iterate starting out of range:\n");
@@ -1257,6 +1387,7 @@ int ziplistTest(int argc, char **argv) {
printf("ERROR\n");
}
printf("\n");
zfree(zl);
}
printf("Iterate from back to front:\n");
@@ -1274,6 +1405,7 @@ int ziplistTest(int argc, char **argv) {
printf("\n");
}
printf("\n");
zfree(zl);
}
printf("Iterate from back to front, deleting all items:\n");
@@ -1292,6 +1424,7 @@ int ziplistTest(int argc, char **argv) {
printf("\n");
}
printf("\n");
zfree(zl);
}
printf("Delete inclusive range 0,0:\n");
@@ -1299,6 +1432,7 @@ int ziplistTest(int argc, char **argv) {
zl = createList();
zl = ziplistDeleteRange(zl, 0, 1);
ziplistRepr(zl);
zfree(zl);
}
printf("Delete inclusive range 0,1:\n");
@@ -1306,6 +1440,7 @@ int ziplistTest(int argc, char **argv) {
zl = createList();
zl = ziplistDeleteRange(zl, 0, 2);
ziplistRepr(zl);
zfree(zl);
}
printf("Delete inclusive range 1,2:\n");
@@ -1313,6 +1448,7 @@ int ziplistTest(int argc, char **argv) {
zl = createList();
zl = ziplistDeleteRange(zl, 1, 2);
ziplistRepr(zl);
zfree(zl);
}
printf("Delete with start index out of range:\n");
@@ -1320,6 +1456,7 @@ int ziplistTest(int argc, char **argv) {
zl = createList();
zl = ziplistDeleteRange(zl, 5, 1);
ziplistRepr(zl);
zfree(zl);
}
printf("Delete with num overflow:\n");
@@ -1327,6 +1464,7 @@ int ziplistTest(int argc, char **argv) {
zl = createList();
zl = ziplistDeleteRange(zl, 1, 5);
ziplistRepr(zl);
zfree(zl);
}
printf("Delete foo while iterating:\n");
@@ -1351,6 +1489,7 @@ int ziplistTest(int argc, char **argv) {
}
printf("\n");
ziplistRepr(zl);
zfree(zl);
}
printf("Regression test for >255 byte strings:\n");
@@ -1370,6 +1509,7 @@ int ziplistTest(int argc, char **argv) {
assert(ziplistGet(p,&entry,&elen,&value));
assert(strncmp(v2,(char*)entry,elen) == 0);
printf("SUCCESS\n\n");
zfree(zl);
}
printf("Regression test deleting next to last entries:\n");
@@ -1408,6 +1548,7 @@ int ziplistTest(int argc, char **argv) {
assert(e[1].prevrawlensize == 5);
printf("SUCCESS\n\n");
zfree(zl);
}
printf("Create long list and check indices:\n");
@@ -1429,6 +1570,7 @@ int ziplistTest(int argc, char **argv) {
assert(999-i == value);
}
printf("SUCCESS\n\n");
zfree(zl);
}
printf("Compare strings with ziplist entries:\n");
@@ -1454,6 +1596,82 @@ int ziplistTest(int argc, char **argv) {
return 1;
}
printf("SUCCESS\n\n");
zfree(zl);
}
printf("Merge test:\n");
{
/* create list gives us: [hello, foo, quux, 1024] */
zl = createList();
unsigned char *zl2 = createList();
unsigned char *zl3 = ziplistNew();
unsigned char *zl4 = ziplistNew();
if (ziplistMerge(&zl4, &zl4)) {
printf("ERROR: Allowed merging of one ziplist into itself.\n");
return 1;
}
/* Merge two empty ziplists, get empty result back. */
zl4 = ziplistMerge(&zl3, &zl4);
ziplistRepr(zl4);
if (ziplistLen(zl4)) {
printf("ERROR: Merging two empty ziplists created entries.\n");
return 1;
}
zfree(zl4);
zl2 = ziplistMerge(&zl, &zl2);
/* merge gives us: [hello, foo, quux, 1024, hello, foo, quux, 1024] */
ziplistRepr(zl2);
if (ziplistLen(zl2) != 8) {
printf("ERROR: Merged length not 8, but: %u\n", ziplistLen(zl2));
return 1;
}
p = ziplistIndex(zl2,0);
if (!ziplistCompare(p,(unsigned char*)"hello",5)) {
printf("ERROR: not \"hello\"\n");
return 1;
}
if (ziplistCompare(p,(unsigned char*)"hella",5)) {
printf("ERROR: \"hella\"\n");
return 1;
}
p = ziplistIndex(zl2,3);
if (!ziplistCompare(p,(unsigned char*)"1024",4)) {
printf("ERROR: not \"1024\"\n");
return 1;
}
if (ziplistCompare(p,(unsigned char*)"1025",4)) {
printf("ERROR: \"1025\"\n");
return 1;
}
p = ziplistIndex(zl2,4);
if (!ziplistCompare(p,(unsigned char*)"hello",5)) {
printf("ERROR: not \"hello\"\n");
return 1;
}
if (ziplistCompare(p,(unsigned char*)"hella",5)) {
printf("ERROR: \"hella\"\n");
return 1;
}
p = ziplistIndex(zl2,7);
if (!ziplistCompare(p,(unsigned char*)"1024",4)) {
printf("ERROR: not \"1024\"\n");
return 1;
}
if (ziplistCompare(p,(unsigned char*)"1025",4)) {
printf("ERROR: \"1025\"\n");
return 1;
}
printf("SUCCESS\n\n");
zfree(zl);
}
printf("Stress with random payloads of different encoding:\n");
+1
View File
@@ -32,6 +32,7 @@
#define ZIPLIST_TAIL 1
unsigned char *ziplistNew(void);
unsigned char *ziplistMerge(unsigned char **first, unsigned char **second);
unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int slen, int where);
unsigned char *ziplistIndex(unsigned char *zl, int index);
unsigned char *ziplistNext(unsigned char *zl, unsigned char *p);
+1
View File
@@ -21,6 +21,7 @@ proc main {} {
if {[catch main e]} {
puts $::errorInfo
if {$::pause_on_error} pause_on_error
cleanup
exit 1
}
+11 -2
View File
@@ -66,9 +66,18 @@ test "Cluster consistency during live resharding" {
}
# Write random data to random list.
set key "key:[randomInt $numkeys]"
set listid [randomInt $numkeys]
set key "key:$listid"
set ele [randomValue]
$cluster rpush $key $ele
# We write both with Lua scripts and with plain commands.
# This way we are able to stress Lua -> Redis command invocation
# as well, that has tests to prevent Lua to write into wrong
# hash slots.
if {$listid % 2} {
$cluster rpush $key $ele
} else {
$cluster eval {redis.call("rpush",KEYS[1],ARGV[1])} 1 $key $ele
}
lappend content($key) $ele
if {($j % 1000) == 0} {
+8 -1
View File
@@ -27,10 +27,17 @@ test "Cluster nodes are reachable" {
test "Cluster nodes hard reset" {
foreach_redis_id id {
if {$::valgrind} {
set node_timeout 10000
} else {
set node_timeout 3000
}
catch {R $id flushall} ; # May fail for readonly slaves.
R $id MULTI
R $id cluster reset hard
R $id cluster set-config-epoch [expr {$id+1}]
R $id config set cluster-node-timeout 3000
R $id EXEC
R $id config set cluster-node-timeout $node_timeout
R $id config set cluster-slave-validity-factor 10
R $id config rewrite
}
+41 -14
View File
@@ -16,6 +16,7 @@ source ../support/server.tcl
source ../support/test.tcl
set ::verbose 0
set ::valgrind 0
set ::pause_on_error 0
set ::simulate_error 0
set ::sentinel_instances {}
@@ -32,6 +33,25 @@ if {[catch {cd tmp}]} {
exit 1
}
# Execute the specified instance of the server specified by 'type', using
# the provided configuration file. Returns the PID of the process.
proc exec_instance {type cfgfile} {
if {$type eq "redis"} {
set prgname redis-server
} elseif {$type eq "sentinel"} {
set prgname redis-sentinel
} else {
error "Unknown instance type."
}
if {$::valgrind} {
set pid [exec valgrind --track-origins=yes --suppressions=../../../src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full ../../../src/${prgname} $cfgfile &]
} else {
set pid [exec ../../../src/${prgname} $cfgfile &]
}
return $pid
}
# Spawn a redis or sentinel instance, depending on 'type'.
proc spawn_instance {type base_port count {conf {}}} {
for {set j 0} {$j < $count} {incr j} {
@@ -58,14 +78,7 @@ proc spawn_instance {type base_port count {conf {}}} {
close $cfg
# Finally exec it and remember the pid for later cleanup.
if {$type eq "redis"} {
set prgname redis-server
} elseif {$type eq "sentinel"} {
set prgname redis-sentinel
} else {
error "Unknown instance type."
}
set pid [exec ../../../src/${prgname} $cfgfile &]
set pid [exec_instance $type $cfgfile]
lappend ::pids $pid
# Check availability
@@ -98,6 +111,7 @@ proc cleanup {} {
proc abort_sentinel_test msg {
puts "WARNING: Aborting the test."
puts ">>>>>>>> $msg"
if {$::pause_on_error} pause_on_error
cleanup
exit 1
}
@@ -113,6 +127,8 @@ proc parse_options {} {
set ::pause_on_error 1
} elseif {$opt eq "--fail"} {
set ::simulate_error 1
} elseif {$opt eq {--valgrind}} {
set ::valgrind 1
} elseif {$opt eq "--help"} {
puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests."
puts "\nOptions:"
@@ -360,15 +376,31 @@ proc get_instance_id_by_port {type port} {
# The instance can be restarted with restart-instance.
proc kill_instance {type id} {
set pid [get_instance_attrib $type $id pid]
set port [get_instance_attrib $type $id port]
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]
# Wait for the port it was using to be available again, so that's not
# an issue to start a new server ASAP with the same port.
set retry 10
while {[incr retry -1]} {
set port_is_free [catch {set s [socket 127.0.01 $port]}]
if {$port_is_free} break
catch {close $s}
after 1000
}
if {$retry == 0} {
error "Port $port does not return available after killing instance."
}
}
# Return true of the instance of the specified type/id is killed.
@@ -385,12 +417,7 @@ proc restart_instance {type id} {
# 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 pid [exec_instance $type $cfgfile]
set_instance_attrib $type $id pid $pid
lappend ::pids $pid
+1 -1
View File
@@ -204,7 +204,7 @@ tags {"aof"} {
}
}
## Test that SPOP with <count> (that modifies the client's argc/argv) is correctly free'd
## Uses the alsoPropagate() API.
create_aof {
append_to_aof [formatCommand sadd set foo]
append_to_aof [formatCommand sadd set bar]
+24
View File
@@ -0,0 +1,24 @@
set server_path [tmpdir server.log]
set system_name [string tolower [exec uname -s]]
if {$system_name eq {linux} || $system_name eq {darwin}} {
start_server [list overrides [list dir $server_path]] {
test "Server is able to generate a stack trace on selected systems" {
r config set watchdog-period 200
r debug sleep 1
set pattern "*debugCommand*"
set retry 10
while {$retry} {
set result [exec tail -100 < [srv 0 stdout]]
if {[string match $pattern $result]} {
break
}
incr retry -1
after 1000
}
if {$retry == 0} {
error "assertion:expected stack trace not found into log file"
}
}
}
}
+1 -1
View File
@@ -90,7 +90,7 @@ start_server_and_kill_it [list "dir" $server_path] {
test {Server should not start if RDB is corrupted} {
wait_for_condition 50 100 {
[string match {*RDB checksum*} \
[exec tail -n1 < [dict get $srv stdout]]]
[exec tail -n10 < [dict get $srv stdout]]]
} else {
fail "Server started even if RDB was corrupted!"
}
+19
View File
@@ -132,5 +132,24 @@ start_server {tags {"repl"}} {
}
assert {[$master dbsize] > 0}
}
test {Replication of SPOP command -- alsoPropagate() API} {
$master del myset
set size [randomInt 100]
set content {}
for {set j 0} {$j < $size} {incr j} {
lappend content [randomValue]
}
$master sadd myset {*}$content
set count [randomInt 100]
set result [$master spop myset $count]
wait_for_condition 50 100 {
[$master debug digest] eq [$slave debug digest]
} else {
fail "SPOP replication inconsistency"
}
}
}
}
+2
View File
@@ -226,6 +226,8 @@ proc ::redis_cluster::get_keys_from_command {cmd argv} {
# Special handling for other commands
switch -exact $cmd {
mget {return $argv}
eval {return [lrange $argv 2 1+[lindex $argv 1]]}
evalsha {return [lrange $argv 2 1+[lindex $argv 1]]}
}
# All the remaining commands are not handled.
+1 -1
View File
@@ -207,7 +207,7 @@ proc start_server {options {code undefined}} {
set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
if {$::valgrind} {
set pid [exec valgrind --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/redis-server $config_file > $stdout 2> $stderr &]
set pid [exec valgrind --track-origins=yes --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/redis-server $config_file > $stdout 2> $stderr &]
} else {
set pid [exec src/redis-server $config_file > $stdout 2> $stderr &]
}
+5 -2
View File
@@ -19,9 +19,12 @@ proc assert_match {pattern value} {
}
}
proc assert_equal {expected value} {
proc assert_equal {expected value {detail ""}} {
if {$expected ne $value} {
error "assertion:Expected '$value' to be equal to '$expected'"
if {$detail ne ""} {
set detail " (detail: $detail)"
}
error "assertion:Expected '$value' to be equal to '$expected'$detail"
}
}
+4 -1
View File
@@ -16,8 +16,10 @@ set ::all_tests {
unit/dump
unit/auth
unit/protocol
unit/basic
unit/keyspace
unit/scan
unit/type/string
unit/type/incr
unit/type/list
unit/type/list-2
unit/type/list-3
@@ -38,6 +40,7 @@ set ::all_tests {
integration/aof
integration/rdb
integration/convert-zipmap-hash-on-load
integration/logging
unit/pubsub
unit/slowlog
unit/scripting
+2 -2
View File
@@ -77,10 +77,10 @@ start_server {tags {"aofrw"}} {
}
foreach d {string int} {
foreach e {ziplist linkedlist} {
foreach e {quicklist} {
test "AOF rewrite of list with $e encoding, $d data" {
r flushall
if {$e eq {ziplist}} {set len 10} else {set len 1000}
set len 1000
for {set j 0} {$j < $len} {incr j} {
if {$d eq {string}} {
set data [randstring 0 16 alpha]
+2 -2
View File
@@ -157,7 +157,7 @@ start_server {tags {"dump"}} {
test {MIGRATE can correctly transfer large values} {
set first [srv 0 client]
r del key
for {set j 0} {$j < 5000} {incr j} {
for {set j 0} {$j < 40000} {incr j} {
r rpush key 1 2 3 4 5 6 7 8 9 10
r rpush key "item 1" "item 2" "item 3" "item 4" "item 5" \
"item 6" "item 7" "item 8" "item 9" "item 10"
@@ -175,7 +175,7 @@ start_server {tags {"dump"}} {
assert {[$first exists key] == 0}
assert {[$second exists key] == 1}
assert {[$second ttl key] == -1}
assert {[$second llen key] == 5000*20}
assert {[$second llen key] == 40000*20}
}
}
+249
View File
@@ -0,0 +1,249 @@
start_server {tags {"keyspace"}} {
test {DEL against a single item} {
r set x foo
assert {[r get x] eq "foo"}
r del x
r get x
} {}
test {Vararg DEL} {
r set foo1 a
r set foo2 b
r set foo3 c
list [r del foo1 foo2 foo3 foo4] [r mget foo1 foo2 foo3]
} {3 {{} {} {}}}
test {KEYS with pattern} {
foreach key {key_x key_y key_z foo_a foo_b foo_c} {
r set $key hello
}
lsort [r keys foo*]
} {foo_a foo_b foo_c}
test {KEYS to get all keys} {
lsort [r keys *]
} {foo_a foo_b foo_c key_x key_y key_z}
test {DBSIZE} {
r dbsize
} {6}
test {DEL all keys} {
foreach key [r keys *] {r del $key}
r dbsize
} {0}
test "DEL against expired key" {
r debug set-active-expire 0
r setex keyExpire 1 valExpire
after 1100
assert_equal 0 [r del keyExpire]
r debug set-active-expire 1
}
test {EXISTS} {
set res {}
r set newkey test
append res [r exists newkey]
r del newkey
append res [r exists newkey]
} {10}
test {Zero length value in key. SET/GET/EXISTS} {
r set emptykey {}
set res [r get emptykey]
append res [r exists emptykey]
r del emptykey
append res [r exists emptykey]
} {10}
test {Commands pipelining} {
set fd [r channel]
puts -nonewline $fd "SET k1 xyzk\r\nGET k1\r\nPING\r\n"
flush $fd
set res {}
append res [string match OK* [r read]]
append res [r read]
append res [string match PONG* [r read]]
format $res
} {1xyzk1}
test {Non existing command} {
catch {r foobaredcommand} err
string match ERR* $err
} {1}
test {RENAME basic usage} {
r set mykey hello
r rename mykey mykey1
r rename mykey1 mykey2
r get mykey2
} {hello}
test {RENAME source key should no longer exist} {
r exists mykey
} {0}
test {RENAME against already existing key} {
r set mykey a
r set mykey2 b
r rename mykey2 mykey
set res [r get mykey]
append res [r exists mykey2]
} {b0}
test {RENAMENX basic usage} {
r del mykey
r del mykey2
r set mykey foobar
r renamenx mykey mykey2
set res [r get mykey2]
append res [r exists mykey]
} {foobar0}
test {RENAMENX against already existing key} {
r set mykey foo
r set mykey2 bar
r renamenx mykey mykey2
} {0}
test {RENAMENX against already existing key (2)} {
set res [r get mykey]
append res [r get mykey2]
} {foobar}
test {RENAME against non existing source key} {
catch {r rename nokey foobar} err
format $err
} {ERR*}
test {RENAME where source and dest key are the same (existing)} {
r set mykey foo
r rename mykey mykey
} {OK}
test {RENAMENX where source and dest key are the same (existing)} {
r set mykey foo
r renamenx mykey mykey
} {0}
test {RENAME where source and dest key are the same (non existing)} {
r del mykey
catch {r rename mykey mykey} err
format $err
} {ERR*}
test {RENAME with volatile key, should move the TTL as well} {
r del mykey mykey2
r set mykey foo
r expire mykey 100
assert {[r ttl mykey] > 95 && [r ttl mykey] <= 100}
r rename mykey mykey2
assert {[r ttl mykey2] > 95 && [r ttl mykey2] <= 100}
}
test {RENAME with volatile key, should not inherit TTL of target key} {
r del mykey mykey2
r set mykey foo
r set mykey2 bar
r expire mykey2 100
assert {[r ttl mykey] == -1 && [r ttl mykey2] > 0}
r rename mykey mykey2
r ttl mykey2
} {-1}
test {DEL all keys again (DB 0)} {
foreach key [r keys *] {
r del $key
}
r dbsize
} {0}
test {DEL all keys again (DB 1)} {
r select 10
foreach key [r keys *] {
r del $key
}
set res [r dbsize]
r select 9
format $res
} {0}
test {MOVE basic usage} {
r set mykey foobar
r move mykey 10
set res {}
lappend res [r exists mykey]
lappend res [r dbsize]
r select 10
lappend res [r get mykey]
lappend res [r dbsize]
r select 9
format $res
} [list 0 0 foobar 1]
test {MOVE against key existing in the target DB} {
r set mykey hello
r move mykey 10
} {0}
test {MOVE against non-integer DB (#1428)} {
r set mykey hello
catch {r move mykey notanumber} e
set e
} {*ERR*index out of range}
test {SET/GET keys in different DBs} {
r set a hello
r set b world
r select 10
r set a foo
r set b bared
r select 9
set res {}
lappend res [r get a]
lappend res [r get b]
r select 10
lappend res [r get a]
lappend res [r get b]
r select 9
format $res
} {hello world foo bared}
test {RANDOMKEY} {
r flushdb
r set foo x
r set bar y
set foo_seen 0
set bar_seen 0
for {set i 0} {$i < 100} {incr i} {
set rkey [r randomkey]
if {$rkey eq {foo}} {
set foo_seen 1
}
if {$rkey eq {bar}} {
set bar_seen 1
}
}
list $foo_seen $bar_seen
} {1 1}
test {RANDOMKEY against empty DB} {
r flushdb
r randomkey
} {}
test {RANDOMKEY regression 1} {
r flushdb
r set x 10
r del x
r randomkey
} {}
test {KEYS * two times with long key, Github issue #1208} {
r flushdb
r set dlskeriewrioeuwqoirueioqwrueoqwrueqw test
r keys *
r keys *
} {dlskeriewrioeuwqoirueioqwrueoqwrueqw}
}
+6 -1
View File
@@ -1,15 +1,20 @@
proc test_memory_efficiency {range} {
r flushall
set rd [redis_deferring_client]
set base_mem [s used_memory]
set written 0
for {set j 0} {$j < 10000} {incr j} {
set key key:$j
set val [string repeat A [expr {int(rand()*$range)}]]
r set $key $val
$rd set $key $val
incr written [string length $key]
incr written [string length $val]
incr written 2 ;# A separator is the minimum to store key-value data.
}
for {set j 0} {$j < 10000} {incr j} {
$rd read ; # Discard replies
}
set current_mem [s used_memory]
set used [expr {$current_mem-$base_mem}]
set efficiency [expr {double($written)/$used}]
+6 -7
View File
@@ -1,8 +1,7 @@
start_server {
tags {"sort"}
overrides {
"list-max-ziplist-value" 16
"list-max-ziplist-entries" 32
"list-max-ziplist-size" 32
"set-max-intset-entries" 32
}
} {
@@ -36,9 +35,9 @@ start_server {
}
foreach {num cmd enc title} {
16 lpush ziplist "Ziplist"
1000 lpush linkedlist "Linked list"
10000 lpush linkedlist "Big Linked list"
16 lpush quicklist "Old Ziplist"
1000 lpush quicklist "Old Linked list"
10000 lpush quicklist "Old Big Linked list"
16 sadd intset "Intset"
1000 sadd hashtable "Hash table"
10000 sadd hashtable "Big Hash table"
@@ -85,14 +84,14 @@ start_server {
r sort tosort BY weight_* store sort-res
assert_equal $result [r lrange sort-res 0 -1]
assert_equal 16 [r llen sort-res]
assert_encoding ziplist sort-res
assert_encoding quicklist sort-res
}
test "SORT BY hash field STORE" {
r sort tosort BY wobj_*->weight store sort-res
assert_equal $result [r lrange sort-res 0 -1]
assert_equal 16 [r llen sort-res]
assert_encoding ziplist sort-res
assert_encoding quicklist sort-res
}
test "SORT extracts STORE correctly" {
+45
View File
@@ -390,6 +390,51 @@ start_server {tags {"hash"}} {
lappend rv [string match "ERR*not*float*" $bigerr]
} {1 1}
test {HSTRLEN against the small hash} {
set err {}
foreach k [array names smallhash *] {
if {[string length $smallhash($k)] ne [r hstrlen smallhash $k]} {
set err "[string length $smallhash($k)] != [r hstrlen smallhash $k]"
break
}
}
set _ $err
} {}
test {HSTRLEN against the big hash} {
set err {}
foreach k [array names bighash *] {
if {[string length $bighash($k)] ne [r hstrlen bighash $k]} {
set err "[string length $bighash($k)] != [r hstrlen bighash $k]"
break
}
}
set _ $err
} {}
test {HSTRLEN against non existing field} {
set rv {}
lappend rv [r hstrlen smallhash __123123123__]
lappend rv [r hstrlen bighash __123123123__]
set _ $rv
} {0 0}
test {HSTRLEN corner cases} {
set vals {
-9223372036854775808 9223372036854775807 9223372036854775808
{} 0 -1 x
}
foreach v $vals {
r hmset smallhash field $v
r hmset bighash field $v
set len1 [string length $v]
set len2 [r hstrlen smallhash field]
set len3 [r hstrlen bighash field]
assert {$len1 == $len2}
assert {$len2 == $len3}
}
}
test {Hash ziplist regression test for large keys} {
r hset hash kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk a
r hset hash kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk b
+147
View File
@@ -0,0 +1,147 @@
start_server {tags {"incr"}} {
test {INCR against non existing key} {
set res {}
append res [r incr novar]
append res [r get novar]
} {11}
test {INCR against key created by incr itself} {
r incr novar
} {2}
test {INCR against key originally set with SET} {
r set novar 100
r incr novar
} {101}
test {INCR over 32bit value} {
r set novar 17179869184
r incr novar
} {17179869185}
test {INCRBY over 32bit value with over 32bit increment} {
r set novar 17179869184
r incrby novar 17179869184
} {34359738368}
test {INCR fails against key with spaces (left)} {
r set novar " 11"
catch {r incr novar} err
format $err
} {ERR*}
test {INCR fails against key with spaces (right)} {
r set novar "11 "
catch {r incr novar} err
format $err
} {ERR*}
test {INCR fails against key with spaces (both)} {
r set novar " 11 "
catch {r incr novar} err
format $err
} {ERR*}
test {INCR fails against a key holding a list} {
r rpush mylist 1
catch {r incr mylist} err
r rpop mylist
format $err
} {WRONGTYPE*}
test {DECRBY over 32bit value with over 32bit increment, negative res} {
r set novar 17179869184
r decrby novar 17179869185
} {-1}
test {INCR uses shared objects in the 0-9999 range} {
r set foo -1
r incr foo
assert {[r object refcount foo] > 1}
r set foo 9998
r incr foo
assert {[r object refcount foo] > 1}
r incr foo
assert {[r object refcount foo] == 1}
}
test {INCR can modify objects in-place} {
r set foo 20000
r incr foo
assert {[r object refcount foo] == 1}
set old [lindex [split [r debug object foo]] 1]
r incr foo
set new [lindex [split [r debug object foo]] 1]
assert {[string range $old 0 2] eq "at:"}
assert {[string range $new 0 2] eq "at:"}
assert {$old eq $new}
}
test {INCRBYFLOAT against non existing key} {
r del novar
list [roundFloat [r incrbyfloat novar 1]] \
[roundFloat [r get novar]] \
[roundFloat [r incrbyfloat novar 0.25]] \
[roundFloat [r get novar]]
} {1 1 1.25 1.25}
test {INCRBYFLOAT against key originally set with SET} {
r set novar 1.5
roundFloat [r incrbyfloat novar 1.5]
} {3}
test {INCRBYFLOAT over 32bit value} {
r set novar 17179869184
r incrbyfloat novar 1.5
} {17179869185.5}
test {INCRBYFLOAT over 32bit value with over 32bit increment} {
r set novar 17179869184
r incrbyfloat novar 17179869184
} {34359738368}
test {INCRBYFLOAT fails against key with spaces (left)} {
set err {}
r set novar " 11"
catch {r incrbyfloat novar 1.0} err
format $err
} {ERR*valid*}
test {INCRBYFLOAT fails against key with spaces (right)} {
set err {}
r set novar "11 "
catch {r incrbyfloat novar 1.0} err
format $err
} {ERR*valid*}
test {INCRBYFLOAT fails against key with spaces (both)} {
set err {}
r set novar " 11 "
catch {r incrbyfloat novar 1.0} err
format $err
} {ERR*valid*}
test {INCRBYFLOAT fails against a key holding a list} {
r del mylist
set err {}
r rpush mylist 1
catch {r incrbyfloat mylist 1.0} err
r del mylist
format $err
} {WRONGTYPE*}
test {INCRBYFLOAT does not allow NaN or Infinity} {
r set foo 0
set err {}
catch {r incrbyfloat foo +inf} err
set err
# p.s. no way I can force NaN to test it from the API because
# there is no way to increment / decrement by infinity nor to
# perform divisions.
} {ERR*would produce*}
test {INCRBYFLOAT decrement} {
r set foo 1
roundFloat [r incrbyfloat foo -1.1]
} {-0.1}
}
+6 -3
View File
@@ -1,8 +1,7 @@
start_server {
tags {"list"}
overrides {
"list-max-ziplist-value" 16
"list-max-ziplist-entries" 256
"list-max-ziplist-size" 4
}
} {
source "tests/unit/type/list-common.tcl"
@@ -28,14 +27,18 @@ start_server {
for {set i 0} {$i < 1000} {incr i} {
set min [expr {int(rand()*$startlen)}]
set max [expr {$min+int(rand()*$startlen)}]
set before_len [llength $mylist]
set before_len_r [r llen mylist]
set mylist [lrange $mylist $min $max]
r ltrim mylist $min $max
assert_equal $mylist [r lrange mylist 0 -1]
assert_equal $mylist [r lrange mylist 0 -1] "failed trim"
set starting [r llen mylist]
for {set j [r llen mylist]} {$j < $startlen} {incr j} {
set str [randomInt 9223372036854775807]
r rpush mylist $str
lappend mylist $str
assert_equal $mylist [r lrange mylist 0 -1] "failed append match"
}
}
}
+1 -2
View File
@@ -1,8 +1,7 @@
start_server {
tags {list ziplist}
overrides {
"list-max-ziplist-value" 200000
"list-max-ziplist-entries" 256
"list-max-ziplist-size" 16
}
} {
test {Explicit regression for a list bug} {
+54 -115
View File
@@ -1,25 +1,24 @@
start_server {
tags {"list"}
overrides {
"list-max-ziplist-value" 16
"list-max-ziplist-entries" 256
"list-max-ziplist-size" 5
}
} {
source "tests/unit/type/list-common.tcl"
test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - ziplist} {
# first lpush then rpush
assert_equal 1 [r lpush myziplist1 a]
assert_equal 2 [r rpush myziplist1 b]
assert_equal 3 [r rpush myziplist1 c]
assert_equal 1 [r lpush myziplist1 aa]
assert_equal 2 [r rpush myziplist1 bb]
assert_equal 3 [r rpush myziplist1 cc]
assert_equal 3 [r llen myziplist1]
assert_equal a [r lindex myziplist1 0]
assert_equal b [r lindex myziplist1 1]
assert_equal c [r lindex myziplist1 2]
assert_equal aa [r lindex myziplist1 0]
assert_equal bb [r lindex myziplist1 1]
assert_equal cc [r lindex myziplist1 2]
assert_equal {} [r lindex myziplist2 3]
assert_equal c [r rpop myziplist1]
assert_equal a [r lpop myziplist1]
assert_encoding ziplist myziplist1
assert_equal cc [r rpop myziplist1]
assert_equal aa [r lpop myziplist1]
assert_encoding quicklist myziplist1
# first rpush then lpush
assert_equal 1 [r rpush myziplist2 a]
@@ -32,13 +31,13 @@ start_server {
assert_equal {} [r lindex myziplist2 3]
assert_equal a [r rpop myziplist2]
assert_equal c [r lpop myziplist2]
assert_encoding ziplist myziplist2
assert_encoding quicklist myziplist2
}
test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - regular list} {
# first lpush then rpush
assert_equal 1 [r lpush mylist1 $largevalue(linkedlist)]
assert_encoding linkedlist mylist1
assert_encoding quicklist mylist1
assert_equal 2 [r rpush mylist1 b]
assert_equal 3 [r rpush mylist1 c]
assert_equal 3 [r llen mylist1]
@@ -51,7 +50,7 @@ start_server {
# first rpush then lpush
assert_equal 1 [r rpush mylist2 $largevalue(linkedlist)]
assert_encoding linkedlist mylist2
assert_encoding quicklist mylist2
assert_equal 2 [r lpush mylist2 b]
assert_equal 3 [r lpush mylist2 c]
assert_equal 3 [r llen mylist2]
@@ -74,34 +73,22 @@ start_server {
assert_equal {d c b a 0 1 2 3} [r lrange mylist 0 -1]
}
test {DEL a list - ziplist} {
assert_equal 1 [r del myziplist2]
assert_equal 0 [r exists myziplist2]
assert_equal 0 [r llen myziplist2]
}
test {DEL a list - regular list} {
test {DEL a list} {
assert_equal 1 [r del mylist2]
assert_equal 0 [r exists mylist2]
assert_equal 0 [r llen mylist2]
}
proc create_ziplist {key entries} {
proc create_list {key entries} {
r del $key
foreach entry $entries { r rpush $key $entry }
assert_encoding ziplist $key
}
proc create_linkedlist {key entries} {
r del $key
foreach entry $entries { r rpush $key $entry }
assert_encoding linkedlist $key
assert_encoding quicklist $key
}
foreach {type large} [array get largevalue] {
test "BLPOP, BRPOP: single existing list - $type" {
set rd [redis_deferring_client]
create_$type blist "a b $large c d"
create_list blist "a b $large c d"
$rd blpop blist 1
assert_equal {blist a} [$rd read]
@@ -116,8 +103,8 @@ start_server {
test "BLPOP, BRPOP: multiple existing lists - $type" {
set rd [redis_deferring_client]
create_$type blist1 "a $large c"
create_$type blist2 "d $large f"
create_list blist1 "a $large c"
create_list blist2 "d $large f"
$rd blpop blist1 blist2 1
assert_equal {blist1 a} [$rd read]
@@ -137,7 +124,7 @@ start_server {
test "BLPOP, BRPOP: second list has an entry - $type" {
set rd [redis_deferring_client]
r del blist1
create_$type blist2 "d $large f"
create_list blist2 "d $large f"
$rd blpop blist1 blist2 1
assert_equal {blist2 d} [$rd read]
@@ -151,7 +138,7 @@ start_server {
r del target
set rd [redis_deferring_client]
create_$type blist "a b $large c d"
create_list blist "a b $large c d"
$rd brpoplpush blist target 1
assert_equal d [$rd read]
@@ -517,28 +504,28 @@ start_server {
foreach {type large} [array get largevalue] {
test "LPUSHX, RPUSHX - $type" {
create_$type xlist "$large c"
create_list xlist "$large c"
assert_equal 3 [r rpushx xlist d]
assert_equal 4 [r lpushx xlist a]
assert_equal "a $large c d" [r lrange xlist 0 -1]
}
test "LINSERT - $type" {
create_$type xlist "a $large c d"
assert_equal 5 [r linsert xlist before c zz]
assert_equal "a $large zz c d" [r lrange xlist 0 10]
assert_equal 6 [r linsert xlist after c yy]
assert_equal "a $large zz c yy d" [r lrange xlist 0 10]
assert_equal 7 [r linsert xlist after d dd]
assert_equal -1 [r linsert xlist after bad ddd]
assert_equal "a $large zz c yy d dd" [r lrange xlist 0 10]
assert_equal 8 [r linsert xlist before a aa]
assert_equal -1 [r linsert xlist before bad aaa]
assert_equal "aa a $large zz c yy d dd" [r lrange xlist 0 10]
create_list xlist "a $large c d"
assert_equal 5 [r linsert xlist before c zz] "before c"
assert_equal "a $large zz c d" [r lrange xlist 0 10] "lrangeA"
assert_equal 6 [r linsert xlist after c yy] "after c"
assert_equal "a $large zz c yy d" [r lrange xlist 0 10] "lrangeB"
assert_equal 7 [r linsert xlist after d dd] "after d"
assert_equal -1 [r linsert xlist after bad ddd] "after bad"
assert_equal "a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeC"
assert_equal 8 [r linsert xlist before a aa] "before a"
assert_equal -1 [r linsert xlist before bad aaa] "before bad"
assert_equal "aa a $large zz c yy d dd" [r lrange xlist 0 10] "lrangeD"
# check inserting integer encoded value
assert_equal 9 [r linsert xlist before aa 42]
assert_equal 42 [r lrange xlist 0 0]
assert_equal 9 [r linsert xlist before aa 42] "before aa"
assert_equal 42 [r lrange xlist 0 0] "lrangeE"
}
}
@@ -547,55 +534,7 @@ start_server {
set e
} {*ERR*syntax*error*}
test {LPUSHX, RPUSHX convert from ziplist to list} {
set large $largevalue(linkedlist)
# convert when a large value is pushed
create_ziplist xlist a
assert_equal 2 [r rpushx xlist $large]
assert_encoding linkedlist xlist
create_ziplist xlist a
assert_equal 2 [r lpushx xlist $large]
assert_encoding linkedlist xlist
# convert when the length threshold is exceeded
create_ziplist xlist [lrepeat 256 a]
assert_equal 257 [r rpushx xlist b]
assert_encoding linkedlist xlist
create_ziplist xlist [lrepeat 256 a]
assert_equal 257 [r lpushx xlist b]
assert_encoding linkedlist xlist
}
test {LINSERT convert from ziplist to list} {
set large $largevalue(linkedlist)
# convert when a large value is inserted
create_ziplist xlist a
assert_equal 2 [r linsert xlist before a $large]
assert_encoding linkedlist xlist
create_ziplist xlist a
assert_equal 2 [r linsert xlist after a $large]
assert_encoding linkedlist xlist
# convert when the length threshold is exceeded
create_ziplist xlist [lrepeat 256 a]
assert_equal 257 [r linsert xlist before a a]
assert_encoding linkedlist xlist
create_ziplist xlist [lrepeat 256 a]
assert_equal 257 [r linsert xlist after a a]
assert_encoding linkedlist xlist
# don't convert when the value could not be inserted
create_ziplist xlist [lrepeat 256 a]
assert_equal -1 [r linsert xlist before foo a]
assert_encoding ziplist xlist
create_ziplist xlist [lrepeat 256 a]
assert_equal -1 [r linsert xlist after foo a]
assert_encoding ziplist xlist
}
foreach {type num} {ziplist 250 linkedlist 500} {
foreach {type num} {quicklist 250 quicklist 500} {
proc check_numbered_list_consistency {key} {
set len [r llen $key]
for {set i 0} {$i < $len} {incr i} {
@@ -664,16 +603,16 @@ start_server {
foreach {type large} [array get largevalue] {
test "RPOPLPUSH base case - $type" {
r del mylist1 mylist2
create_$type mylist1 "a $large c d"
create_list mylist1 "a $large c d"
assert_equal d [r rpoplpush mylist1 mylist2]
assert_equal c [r rpoplpush mylist1 mylist2]
assert_equal "a $large" [r lrange mylist1 0 -1]
assert_equal "c d" [r lrange mylist2 0 -1]
assert_encoding ziplist mylist2
assert_encoding quicklist mylist2
}
test "RPOPLPUSH with the same list as src and dst - $type" {
create_$type mylist "a $large c"
create_list mylist "a $large c"
assert_equal "a $large c" [r lrange mylist 0 -1]
assert_equal c [r rpoplpush mylist mylist]
assert_equal "c a $large" [r lrange mylist 0 -1]
@@ -681,8 +620,8 @@ start_server {
foreach {othertype otherlarge} [array get largevalue] {
test "RPOPLPUSH with $type source and existing target $othertype" {
create_$type srclist "a b c $large"
create_$othertype dstlist "$otherlarge"
create_list srclist "a b c $large"
create_list dstlist "$otherlarge"
assert_equal $large [r rpoplpush srclist dstlist]
assert_equal c [r rpoplpush srclist dstlist]
assert_equal "a b" [r lrange srclist 0 -1]
@@ -691,7 +630,7 @@ start_server {
# When we rpoplpush'ed a large value, dstlist should be
# converted to the same encoding as srclist.
if {$type eq "linkedlist"} {
assert_encoding linkedlist dstlist
assert_encoding quicklist dstlist
}
}
}
@@ -713,7 +652,7 @@ start_server {
}
test {RPOPLPUSH against non list dst key} {
create_ziplist srclist {a b c d}
create_list srclist {a b c d}
r set dstlist x
assert_error WRONGTYPE* {r rpoplpush srclist dstlist}
assert_type string dstlist
@@ -727,7 +666,7 @@ start_server {
foreach {type large} [array get largevalue] {
test "Basic LPOP/RPOP - $type" {
create_$type mylist "$large 1 2"
create_list mylist "$large 1 2"
assert_equal $large [r lpop mylist]
assert_equal 2 [r rpop mylist]
assert_equal 1 [r lpop mylist]
@@ -745,7 +684,7 @@ start_server {
assert_error WRONGTYPE* {r rpop notalist}
}
foreach {type num} {ziplist 250 linkedlist 500} {
foreach {type num} {quicklist 250 quicklist 500} {
test "Mass RPOP/LPOP - $type" {
r del mylist
set sum1 0
@@ -765,24 +704,24 @@ start_server {
foreach {type large} [array get largevalue] {
test "LRANGE basics - $type" {
create_$type mylist "$large 1 2 3 4 5 6 7 8 9"
create_list mylist "$large 1 2 3 4 5 6 7 8 9"
assert_equal {1 2 3 4 5 6 7 8} [r lrange mylist 1 -2]
assert_equal {7 8 9} [r lrange mylist -3 -1]
assert_equal {4} [r lrange mylist 4 4]
}
test "LRANGE inverted indexes - $type" {
create_$type mylist "$large 1 2 3 4 5 6 7 8 9"
create_list mylist "$large 1 2 3 4 5 6 7 8 9"
assert_equal {} [r lrange mylist 6 2]
}
test "LRANGE out of range indexes including the full list - $type" {
create_$type mylist "$large 1 2 3"
create_list mylist "$large 1 2 3"
assert_equal "$large 1 2 3" [r lrange mylist -1000 1000]
}
test "LRANGE out of range negative end index - $type" {
create_$type mylist "$large 1 2 3"
create_list mylist "$large 1 2 3"
assert_equal $large [r lrange mylist 0 -4]
assert_equal {} [r lrange mylist 0 -5]
}
@@ -796,7 +735,7 @@ start_server {
proc trim_list {type min max} {
upvar 1 large large
r del mylist
create_$type mylist "1 2 3 4 $large"
create_list mylist "1 2 3 4 $large"
r ltrim mylist $min $max
r lrange mylist 0 -1
}
@@ -825,7 +764,7 @@ start_server {
foreach {type large} [array get largevalue] {
test "LSET - $type" {
create_$type mylist "99 98 $large 96 95"
create_list mylist "99 98 $large 96 95"
r lset mylist 1 foo
r lset mylist -1 bar
assert_equal "99 foo $large 96 bar" [r lrange mylist 0 -1]
@@ -847,7 +786,7 @@ start_server {
foreach {type e} [array get largevalue] {
test "LREM remove all the occurrences - $type" {
create_$type mylist "$e foo bar foobar foobared zap bar test foo"
create_list mylist "$e foo bar foobar foobared zap bar test foo"
assert_equal 2 [r lrem mylist 0 bar]
assert_equal "$e foo foobar foobared zap test foo" [r lrange mylist 0 -1]
}
@@ -863,7 +802,7 @@ start_server {
}
test "LREM starting from tail with negative count - $type" {
create_$type mylist "$e foo bar foobar foobared zap bar test foo foo"
create_list mylist "$e foo bar foobar foobared zap bar test foo foo"
assert_equal 1 [r lrem mylist -1 bar]
assert_equal "$e foo bar foobar foobared zap test foo foo" [r lrange mylist 0 -1]
}
@@ -874,7 +813,7 @@ start_server {
}
test "LREM deleting objects that may be int encoded - $type" {
create_$type myotherlist "$e 1 2 3"
create_list myotherlist "$e 1 2 3"
assert_equal 1 [r lrem myotherlist 1 2]
assert_equal 3 [r llen myotherlist]
}
+27
View File
@@ -346,6 +346,33 @@ start_server {
r spop nonexisting_key 100
} {}
test "SPOP new implementation: code path #1" {
set content {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20}
create_set myset $content
set res [r spop myset 30]
assert {[lsort $content] eq [lsort $res]}
}
test "SPOP new implementation: code path #2" {
set content {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20}
create_set myset $content
set res [r spop myset 2]
assert {[llength $res] == 2}
assert {[r scard myset] == 18}
set union [concat [r smembers myset] $res]
assert {[lsort $union] eq [lsort $content]}
}
test "SPOP new implementation: code path #3" {
set content {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20}
create_set myset $content
set res [r spop myset 18]
assert {[llength $res] == 18}
assert {[r scard myset] == 2}
set union [concat [r smembers myset] $res]
assert {[lsort $union] eq [lsort $content]}
}
test "SRANDMEMBER with <count> against non existing key" {
r srandmember nonexisting_key 100
} {}
@@ -1,9 +1,4 @@
start_server {tags {"basic"}} {
test {DEL all keys to start with a clean DB} {
foreach key [r keys *] {r del $key}
r dbsize
} {0}
start_server {tags {"string"}} {
test {SET and GET an item} {
r set x foobar
r get x
@@ -14,38 +9,6 @@ start_server {tags {"basic"}} {
r get x
} {}
test {DEL against a single item} {
r del x
r get x
} {}
test {Vararg DEL} {
r set foo1 a
r set foo2 b
r set foo3 c
list [r del foo1 foo2 foo3 foo4] [r mget foo1 foo2 foo3]
} {3 {{} {} {}}}
test {KEYS with pattern} {
foreach key {key_x key_y key_z foo_a foo_b foo_c} {
r set $key hello
}
lsort [r keys foo*]
} {foo_a foo_b foo_c}
test {KEYS to get all keys} {
lsort [r keys *]
} {foo_a foo_b foo_c key_x key_y key_z}
test {DBSIZE} {
r dbsize
} {6}
test {DEL all keys} {
foreach key [r keys *] {r del $key}
r dbsize
} {0}
test {Very big payload in GET/SET} {
set buf [string repeat "abcd" 1000000]
r set foo $buf
@@ -75,6 +38,7 @@ start_server {tags {"basic"}} {
} {}
test {SET 10000 numeric keys and access all them in reverse order} {
r flushdb
set err {}
for {set x 0} {$x < 10000} {incr x} {
r set $x $x
@@ -90,157 +54,11 @@ start_server {tags {"basic"}} {
set _ $err
} {}
test {DBSIZE should be 10101 now} {
test {DBSIZE should be 10000 now} {
r dbsize
} {10101}
} {10000}
}
test {INCR against non existing key} {
set res {}
append res [r incr novar]
append res [r get novar]
} {11}
test {INCR against key created by incr itself} {
r incr novar
} {2}
test {INCR against key originally set with SET} {
r set novar 100
r incr novar
} {101}
test {INCR over 32bit value} {
r set novar 17179869184
r incr novar
} {17179869185}
test {INCRBY over 32bit value with over 32bit increment} {
r set novar 17179869184
r incrby novar 17179869184
} {34359738368}
test {INCR fails against key with spaces (left)} {
r set novar " 11"
catch {r incr novar} err
format $err
} {ERR*}
test {INCR fails against key with spaces (right)} {
r set novar "11 "
catch {r incr novar} err
format $err
} {ERR*}
test {INCR fails against key with spaces (both)} {
r set novar " 11 "
catch {r incr novar} err
format $err
} {ERR*}
test {INCR fails against a key holding a list} {
r rpush mylist 1
catch {r incr mylist} err
r rpop mylist
format $err
} {WRONGTYPE*}
test {DECRBY over 32bit value with over 32bit increment, negative res} {
r set novar 17179869184
r decrby novar 17179869185
} {-1}
test {INCR uses shared objects in the 0-9999 range} {
r set foo -1
r incr foo
assert {[r object refcount foo] > 1}
r set foo 9998
r incr foo
assert {[r object refcount foo] > 1}
r incr foo
assert {[r object refcount foo] == 1}
}
test {INCR can modify objects in-place} {
r set foo 20000
r incr foo
assert {[r object refcount foo] == 1}
set old [lindex [split [r debug object foo]] 1]
r incr foo
set new [lindex [split [r debug object foo]] 1]
assert {[string range $old 0 2] eq "at:"}
assert {[string range $new 0 2] eq "at:"}
assert {$old eq $new}
}
test {INCRBYFLOAT against non existing key} {
r del novar
list [roundFloat [r incrbyfloat novar 1]] \
[roundFloat [r get novar]] \
[roundFloat [r incrbyfloat novar 0.25]] \
[roundFloat [r get novar]]
} {1 1 1.25 1.25}
test {INCRBYFLOAT against key originally set with SET} {
r set novar 1.5
roundFloat [r incrbyfloat novar 1.5]
} {3}
test {INCRBYFLOAT over 32bit value} {
r set novar 17179869184
r incrbyfloat novar 1.5
} {17179869185.5}
test {INCRBYFLOAT over 32bit value with over 32bit increment} {
r set novar 17179869184
r incrbyfloat novar 17179869184
} {34359738368}
test {INCRBYFLOAT fails against key with spaces (left)} {
set err {}
r set novar " 11"
catch {r incrbyfloat novar 1.0} err
format $err
} {ERR*valid*}
test {INCRBYFLOAT fails against key with spaces (right)} {
set err {}
r set novar "11 "
catch {r incrbyfloat novar 1.0} err
format $err
} {ERR*valid*}
test {INCRBYFLOAT fails against key with spaces (both)} {
set err {}
r set novar " 11 "
catch {r incrbyfloat novar 1.0} err
format $err
} {ERR*valid*}
test {INCRBYFLOAT fails against a key holding a list} {
r del mylist
set err {}
r rpush mylist 1
catch {r incrbyfloat mylist 1.0} err
r del mylist
format $err
} {WRONGTYPE*}
test {INCRBYFLOAT does not allow NaN or Infinity} {
r set foo 0
set err {}
catch {r incrbyfloat foo +inf} err
set err
# p.s. no way I can force NaN to test it from the API because
# there is no way to increment / decrement by infinity nor to
# perform divisions.
} {ERR*would produce*}
test {INCRBYFLOAT decrement} {
r set foo 1
roundFloat [r incrbyfloat foo -1.1]
} {-0.1}
test "SETNX target key missing" {
r del novar
assert_equal 1 [r setnx novar foobared]
@@ -284,172 +102,6 @@ start_server {tags {"basic"}} {
assert_equal 20 [r get x]
}
test "DEL against expired key" {
r debug set-active-expire 0
r setex keyExpire 1 valExpire
after 1100
assert_equal 0 [r del keyExpire]
r debug set-active-expire 1
}
test {EXISTS} {
set res {}
r set newkey test
append res [r exists newkey]
r del newkey
append res [r exists newkey]
} {10}
test {Zero length value in key. SET/GET/EXISTS} {
r set emptykey {}
set res [r get emptykey]
append res [r exists emptykey]
r del emptykey
append res [r exists emptykey]
} {10}
test {Commands pipelining} {
set fd [r channel]
puts -nonewline $fd "SET k1 xyzk\r\nGET k1\r\nPING\r\n"
flush $fd
set res {}
append res [string match OK* [r read]]
append res [r read]
append res [string match PONG* [r read]]
format $res
} {1xyzk1}
test {Non existing command} {
catch {r foobaredcommand} err
string match ERR* $err
} {1}
test {RENAME basic usage} {
r set mykey hello
r rename mykey mykey1
r rename mykey1 mykey2
r get mykey2
} {hello}
test {RENAME source key should no longer exist} {
r exists mykey
} {0}
test {RENAME against already existing key} {
r set mykey a
r set mykey2 b
r rename mykey2 mykey
set res [r get mykey]
append res [r exists mykey2]
} {b0}
test {RENAMENX basic usage} {
r del mykey
r del mykey2
r set mykey foobar
r renamenx mykey mykey2
set res [r get mykey2]
append res [r exists mykey]
} {foobar0}
test {RENAMENX against already existing key} {
r set mykey foo
r set mykey2 bar
r renamenx mykey mykey2
} {0}
test {RENAMENX against already existing key (2)} {
set res [r get mykey]
append res [r get mykey2]
} {foobar}
test {RENAME against non existing source key} {
catch {r rename nokey foobar} err
format $err
} {ERR*}
test {RENAME where source and dest key is the same} {
catch {r rename mykey mykey} err
format $err
} {ERR*}
test {RENAME with volatile key, should move the TTL as well} {
r del mykey mykey2
r set mykey foo
r expire mykey 100
assert {[r ttl mykey] > 95 && [r ttl mykey] <= 100}
r rename mykey mykey2
assert {[r ttl mykey2] > 95 && [r ttl mykey2] <= 100}
}
test {RENAME with volatile key, should not inherit TTL of target key} {
r del mykey mykey2
r set mykey foo
r set mykey2 bar
r expire mykey2 100
assert {[r ttl mykey] == -1 && [r ttl mykey2] > 0}
r rename mykey mykey2
r ttl mykey2
} {-1}
test {DEL all keys again (DB 0)} {
foreach key [r keys *] {
r del $key
}
r dbsize
} {0}
test {DEL all keys again (DB 1)} {
r select 10
foreach key [r keys *] {
r del $key
}
set res [r dbsize]
r select 9
format $res
} {0}
test {MOVE basic usage} {
r set mykey foobar
r move mykey 10
set res {}
lappend res [r exists mykey]
lappend res [r dbsize]
r select 10
lappend res [r get mykey]
lappend res [r dbsize]
r select 9
format $res
} [list 0 0 foobar 1]
test {MOVE against key existing in the target DB} {
r set mykey hello
r move mykey 10
} {0}
test {MOVE against non-integer DB (#1428)} {
r set mykey hello
catch {r move mykey notanumber} e
set e
} {*ERR*index out of range}
test {SET/GET keys in different DBs} {
r set a hello
r set b world
r select 10
r set a foo
r set b bared
r select 9
set res {}
lappend res [r get a]
lappend res [r get b]
r select 10
lappend res [r get a]
lappend res [r get b]
r select 9
format $res
} {hello world foo bared}
test {MGET} {
r flushdb
r set foo BAR
@@ -467,37 +119,8 @@ start_server {tags {"basic"}} {
r mget foo baazz bar myset
} {BAR {} FOO {}}
test {RANDOMKEY} {
r flushdb
r set foo x
r set bar y
set foo_seen 0
set bar_seen 0
for {set i 0} {$i < 100} {incr i} {
set rkey [r randomkey]
if {$rkey eq {foo}} {
set foo_seen 1
}
if {$rkey eq {bar}} {
set bar_seen 1
}
}
list $foo_seen $bar_seen
} {1 1}
test {RANDOMKEY against empty DB} {
r flushdb
r randomkey
} {}
test {RANDOMKEY regression 1} {
r flushdb
r set x 10
r del x
r randomkey
} {}
test {GETSET (set new value)} {
r del foo
list [r getset foo xyz] [r get foo]
} {{} xyz}
@@ -792,13 +415,6 @@ start_server {tags {"basic"}} {
assert {$ttl <= 10 && $ttl > 5}
}
test {KEYS * two times with long key, Github issue #1208} {
r flushdb
r set dlskeriewrioeuwqoirueioqwrueoqwrueqw test
r keys *
r keys *
} {dlskeriewrioeuwqoirueioqwrueoqwrueqw}
test {GETRANGE with huge ranges, Github issue #1844} {
r set foo bar
r getrange foo 0 4294967297
+50
View File
@@ -0,0 +1,50 @@
# This simple script is used in order to estimate the average PFAIL->FAIL
# state switch after a failure.
set ::sleep_time 10 ; # How much to sleep to trigger PFAIL.
set ::fail_port 30016 ; # Node to put in sleep.
set ::other_port 30001 ; # Node to use to monitor the flag switch.
proc avg vector {
set sum 0.0
foreach x $vector {
set sum [expr {$sum+$x}]
}
expr {$sum/[llength $vector]}
}
set samples {}
while 1 {
exec redis-cli -p $::fail_port debug sleep $::sleep_time > /dev/null &
# Wait for fail? to appear.
while 1 {
set output [exec redis-cli -p $::other_port cluster nodes]
if {[string match {*fail\?*} $output]} break
after 100
}
puts "FAIL?"
set start [clock milliseconds]
# Wait for fail? to disappear.
while 1 {
set output [exec redis-cli -p $::other_port cluster nodes]
if {![string match {*fail\?*} $output]} break
after 100
}
puts "FAIL"
set now [clock milliseconds]
set elapsed [expr {$now-$start}]
puts $elapsed
lappend samples $elapsed
puts "AVG([llength $samples]): [avg $samples]"
# Wait for the instance to be available again.
exec redis-cli -p $::fail_port ping
# Wait for the fail flag to be cleared.
after 2000
}
+1
View File
@@ -0,0 +1 @@
config.sh
+27
View File
@@ -0,0 +1,27 @@
Create-custer is a small script used to easily start a big number of Redis
instances configured to run in cluster mode. Its main goal is to allow manual
testing in a condition which is not easy to replicate with the Redis cluster
unit tests, for example when a lot of instances are needed in order to trigger
a give bug.
The tool can also be used just to easily create a number of instances in a
Redis Cluster in order to experiment a bit with the system.
USAGE
---
To create a cluster, follow this steps:
1. Edit create-cluster and change the start / end port, depending on the
number of instances you want to create.
2. Use "./create-cluster start" in order to run the instances.
3. Use "./create-cluster create" in order to execute redis-trib create, so that
an actual Redis cluster will be created.
4. Now you are ready to play with the cluster. AOF files and logs for each instances are created in the current directory.
In order to stop a cluster:
1. Use "./craete-cluster stop" to stop all the instances. After you stopped the instances you can use "./create-cluster start" to restart them if you change ideas.
2. Use "./create-cluster clean" to remove all the AOF / log files to restat with a clean environment.
Use the command "./create-cluster help" to get the full list of features.
+95
View File
@@ -0,0 +1,95 @@
#!/bin/bash
# Settings
PORT=30000
TIMEOUT=2000
NODES=6
REPLICAS=1
# You may want to put the above config parameters into config.sh in order to
# override the defaults without modifying this script.
if [ -a config.sh ]
then
source "config.sh"
fi
# Computed vars
ENDPORT=$((PORT+NODES))
if [ "$1" == "start" ]
then
while [ $((PORT < ENDPORT)) != "0" ]; do
PORT=$((PORT+1))
echo "Starting $PORT"
../../src/redis-server --port $PORT --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes
done
exit 0
fi
if [ "$1" == "create" ]
then
HOSTS=""
while [ $((PORT < ENDPORT)) != "0" ]; do
PORT=$((PORT+1))
HOSTS="$HOSTS 127.0.0.1:$PORT"
done
../../src/redis-trib.rb create --replicas $REPLICAS $HOSTS
exit 0
fi
if [ "$1" == "stop" ]
then
while [ $((PORT < ENDPORT)) != "0" ]; do
PORT=$((PORT+1))
echo "Stopping $PORT"
redis-cli -p $PORT shutdown nosave
done
exit 0
fi
if [ "$1" == "watch" ]
then
PORT=$((PORT+1))
while [ 1 ]; do
clear
date
redis-cli -p $PORT cluster nodes | head -30
sleep 1
done
exit 0
fi
if [ "$1" == "tail" ]
then
INSTANCE=$2
PORT=$((PORT+INSTANCE))
tail -f ${PORT}.log
exit 0
fi
if [ "$1" == "call" ]
then
while [ $((PORT < ENDPORT)) != "0" ]; do
PORT=$((PORT+1))
../../src/redis-cli -p $PORT $2 $3 $4 $5 $6 $7 $8 $9
done
exit 0
fi
if [ "$1" == "clean" ]
then
rm -rf *.log
rm -rf appendonly*.aof
rm -rf dump*.rdb
rm -rf nodes*.conf
exit 0
fi
echo "Usage: $0 [start|create|stop|watch|tail|clean]"
echo "start -- Launch Redis Cluster instances."
echo "create -- Create a cluster using redis-trib create."
echo "stop -- Stop Redis Cluster instances."
echo "watch -- Show CLUSTER NODES output (first 30 lines) of first node."
echo "tail <id> -- Run tail -f of instance at base port + ID."
echo "clean -- Remove all instances data, logs, configs."
+13
View File
@@ -0,0 +1,13 @@
Hash table implementation related utilities.
rehashing.c
---
Visually show buckets in the two hash tables between rehashings. Also stress
test getRandomKeys() implementation, that may actually disappear from
Redis soon, however visualizaiton some code is reusable in new bugs
investigation.
Compile with:
cc -I ../../src/ rehashing.c ../../src/zmalloc.c ../../src/dict.c -o rehashing_test
+142
View File
@@ -0,0 +1,142 @@
#include "redis.h"
#include "dict.h"
void _redisAssert(char *x, char *y, int l) {
printf("ASSERT: %s %s %d\n",x,y,l);
exit(1);
}
unsigned int dictKeyHash(const void *keyp) {
unsigned long key = (unsigned long)keyp;
key = dictGenHashFunction(&key,sizeof(key));
key += ~(key << 15);
key ^= (key >> 10);
key += (key << 3);
key ^= (key >> 6);
key += ~(key << 11);
key ^= (key >> 16);
return key;
}
int dictKeyCompare(void *privdata, const void *key1, const void *key2) {
unsigned long k1 = (unsigned long)key1;
unsigned long k2 = (unsigned long)key2;
return k1 == k2;
}
dictType dictTypeTest = {
dictKeyHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictKeyCompare, /* key compare */
NULL, /* key destructor */
NULL /* val destructor */
};
void showBuckets(dictht ht) {
if (ht.table == NULL) {
printf("NULL\n");
} else {
int j;
for (j = 0; j < ht.size; j++) {
printf("%c", ht.table[j] ? '1' : '0');
}
printf("\n");
}
}
void show(dict *d) {
int j;
if (d->rehashidx != -1) {
printf("rhidx: ");
for (j = 0; j < d->rehashidx; j++)
printf(".");
printf("|\n");
}
printf("ht[0]: ");
showBuckets(d->ht[0]);
printf("ht[1]: ");
showBuckets(d->ht[1]);
printf("\n");
}
int sortPointers(const void *a, const void *b) {
unsigned long la, lb;
la = (long) (*((dictEntry**)a));
lb = (long) (*((dictEntry**)b));
return la-lb;
}
void stressGetKeys(dict *d, int times, int *perfect_run, int *approx_run) {
int j;
dictEntry **des = zmalloc(sizeof(dictEntry*)*dictSize(d));
for (j = 0; j < times; j++) {
int requested = rand() % (dictSize(d)+1);
int returned = dictGetSomeKeys(d, des, requested);
int dup = 0;
qsort(des,returned,sizeof(dictEntry*),sortPointers);
if (returned > 1) {
int i;
for (i = 0; i < returned-1; i++) {
if (des[i] == des[i+1]) dup++;
}
}
if (requested == returned && dup == 0) {
(*perfect_run)++;
} else {
(*approx_run)++;
printf("Requested, returned, duplicated: %d %d %d\n",
requested, returned, dup);
}
}
zfree(des);
}
#define MAX1 120
#define MAX2 1000
int main(void) {
dict *d = dictCreate(&dictTypeTest,NULL);
unsigned long i;
srand(time(NULL));
for (i = 0; i < MAX1; i++) {
dictAdd(d,(void*)i,NULL);
show(d);
}
printf("Size: %d\n", (int)dictSize(d));
for (i = 0; i < MAX1; i++) {
dictDelete(d,(void*)i);
dictResize(d);
show(d);
}
dictRelease(d);
d = dictCreate(&dictTypeTest,NULL);
printf("Stress testing dictGetSomeKeys\n");
int perfect_run = 0, approx_run = 0;
for (i = 0; i < MAX2; i++) {
dictAdd(d,(void*)i,NULL);
stressGetKeys(d,100,&perfect_run,&approx_run);
}
for (i = 0; i < MAX2; i++) {
dictDelete(d,(void*)i);
dictResize(d);
stressGetKeys(d,100,&perfect_run,&approx_run);
}
printf("dictGetSomeKey, %d perfect runs, %d approximated runs\n",
perfect_run, approx_run);
dictRelease(d);
printf("TEST PASSED!\n");
return 0;
}