Compare commits

..
262 Commits
Author SHA1 Message Date
antirez 4834969032 Transactions: Use CMD_CLAL_NOQUEUE now that call() handles +QUEUED. 2018-10-09 11:02:03 +02:00
antirez 71c37605d0 Transactions: move the QUEUED logic inside call(). (Work in progress)
As a side effect this makes things like #5201 fixed automatically.
The problem with handling it in ProcessQueryBuffer() or similar is that
a client that is handled just via the call() interface will misbehave,
and call()-ing MULTI and later some command will see just the other
command executed like if the client was not in a transaction.

For now this has worked well enough but probably it makes more sense if
we can handle that case on call().

WARNING: this comment is not enough and will introduce bugs. It's a WIP.
2018-10-08 19:13:09 +02:00
antirez 3e78344d87 Refactoring of XADD / XTRIM MAXLEN rewriting.
See #5141.
2018-10-08 12:05:22 +02:00
Salvatore SanfilippoandGitHub e5f1de1448 Merge pull request #5141 from soloestoy/fix-xtrim-inconsistency
Fix XTRIM and XADD with MAXLEN inconsistency
2018-10-08 12:00:00 +02:00
Salvatore SanfilippoandGitHub 8507c3f26e Merge pull request #5422 from ziyasal/patch-1
Fix typo in the comment of raxSeekGreatest function
2018-10-08 11:34:42 +02:00
O(ʒ)andGitHub 534c3bc21a Fix typo 2018-10-07 13:37:58 +02:00
antirez 3d07ed983e Fix typo in replicationCron() comment. 2018-10-05 18:30:45 +02:00
antirez ce8b6ede34 Fix typo in design comment of bio.c. 2018-10-05 18:29:23 +02:00
antirez 68c0e6e331 xclaimCommand(): fix comment typos. 2018-10-04 17:34:06 +02:00
antirez 32e0d2376f streamAppendItem(): Update the radix tree pointer only if changed. 2018-10-02 19:45:33 +02:00
antirez 9d4f51e1ff Listpack: optionally force reallocation on inserts.
This is useful in order to spot bugs where we fail
at updating the pointer returned by the insertion
function. Normally often the same pointer is returned,
making it harder than needed to spot bugs.

Related to #5210.
2018-10-02 19:37:36 +02:00
antirez 73a78adf13 Fix printf type mismatch in genRedisInfoString(). 2018-10-02 16:26:30 +02:00
antirez c7c3b23787 streamIteratorRemoveEntry(): set back lp only if pointer changed.
Most of the times the pointer will remain the same since integers of
similar size don't take more space in listpacks.

Related to #5210.
2018-10-02 16:20:40 +02:00
Salvatore SanfilippoandGitHub 9fe7cd8f14 Merge pull request #5210 from soloestoy/raxinsert-in-xdel
Streams: update listpack with new pointer in XDEL
2018-10-02 16:18:55 +02:00
Salvatore SanfilippoandGitHub 2b09884faf Merge pull request #5392 from soloestoy/bugfix-brpoplpush
bugfix: replace lastcmd with cmd when rewrite BRPOPLPUSH as RPOPLPUSH
2018-10-02 16:09:22 +02:00
Salvatore SanfilippoandGitHub e19a06d2dd Merge pull request #5242 from oranagra/script_mem
script cache memory in INFO and MEMORY includes both script code and overheads
2018-10-02 16:03:05 +02:00
Salvatore SanfilippoandGitHub fe43406929 Merge pull request #5397 from bmerry/fix-bad-zmalloc-size
Fix invalid use of sdsZmallocSize on an embedded string
2018-10-01 13:30:42 +02:00
Salvatore SanfilippoandGitHub 9ce638695c Merge pull request #5398 from bmerry/fix-zrealloc-accounting
Fix incorrect memory usage accounting in zrealloc
2018-10-01 13:28:53 +02:00
Salvatore SanfilippoandGitHub cd2ee8b113 Merge pull request #5396 from oranagra/cmdstats_exec
fix #5024 - commandstats for multi-exec were logged as EXEC.
2018-10-01 13:24:49 +02:00
Salvatore SanfilippoandGitHub 1da93f85cc Merge pull request #5400 from halaei/fix-dict-get-on-not-found
fix dict get on not found
2018-10-01 13:22:33 +02:00
Hamid Alaei 86fb7b20bf fix dict get on not found 2018-10-01 13:57:25 +03:30
antirez 9e0e5ccbf4 Fix XINFO comment for consistency. 2018-10-01 11:38:58 +02:00
Bruce Merry 8fd1031b10 Fix incorrect memory usage accounting in zrealloc
When HAVE_MALLOC_SIZE is false, each call to zrealloc causes used_memory
to increase by PREFIX_SIZE more than it should, due to mis-matched
accounting between the original zmalloc (which includes PREFIX size in
its increment) and zrealloc (which misses it from its decrement).

I've also supplied a command-line test to easily demonstrate the
problem. It's not wired into the test framework, because I don't know
TCL so I'm not sure how to automate it.
2018-09-30 11:49:03 +02:00
Oran Agra f03aed3ca9 fix #5024 - commandstats for multi-exec were logged as EXEC.
this was broken a while back by ba9154d7e7
the purpose of which was to fix commandstats for GEOADD
2018-09-30 12:43:11 +03:00
Bruce Merry ecc48369ce Fix invalid use of sdsZmallocSize on an embedded string
sdsZmallocSize assumes a dynamically allocated SDS. When given a string
object created by createEmbeddedStringObject, it calls zmalloc_size on a
pointer that isn't the one returned by zmalloc
2018-09-30 11:32:48 +02:00
antirez 08c26591e3 Try to avoid issues with GCC pragmas and older compilers.
See issue #5394.
2018-09-28 13:00:44 +02:00
zhaozhao.zz fc9b4e7958 bugfix: replace lastcmd with cmd when rewrite BRPOPLPUSH as RPOPLPUSH
There are two problems if we use lastcmd:

1. BRPOPLPUSH cannot be rewrited as RPOPLPUSH in multi/exec
    In mulit/exec context, the lastcmd is exec.
2. Redis will crash when execute RPOPLPUSH loading from AOF
    In fakeClient, the lastcmd is NULL.
2018-09-28 12:06:48 +08:00
antirez 5aa347b377 Modules: hellodict example WIP #3: KEYRANGE. 2018-09-27 17:01:35 +02:00
antirez 0aecb135c9 Modules: Modules: dictionary API WIP #13: Compare API exported. 2018-09-27 11:46:22 +02:00
antirez 80bde9844b Modules: Modules: dictionary API WIP #12: DictCompare API. 2018-09-27 11:44:25 +02:00
antirez 7af83a0c11 Modules: Modules: dictionary API WIP #11: DictCompareC API. 2018-09-27 11:44:16 +02:00
antirez 20f047965c Modules: hellodict example WIP #1: GET command. 2018-09-27 11:22:43 +02:00
antirez 880ca07719 Modules: hellodict example WIP #1: SET command. 2018-09-26 17:52:12 +02:00
antirez bbe18eacda Modules: remove useless defines in hellotimer.c 2018-09-26 17:42:00 +02:00
antirez 6c3bfb00fc Modules: fix top comment of hellotimer.c 2018-09-26 17:41:08 +02:00
antirez bec4bfaa0b Merge branch 'unstable' of github.com:/antirez/redis into unstable 2018-09-26 13:39:16 +02:00
antirez 1e585d01de Modules: dictionary API WIP #10: export API to modules. 2018-09-26 13:39:01 +02:00
Salvatore SanfilippoandGitHub 1d6711a764 Merge pull request #5373 from gkorland/patch-5
add missing argument to function doc
2018-09-25 17:18:50 +02:00
Salvatore SanfilippoandGitHub cf7475b9b8 Merge pull request #5377 from Chupaka/patch-1
Fix typo in comment
2018-09-25 17:15:34 +02:00
antirez 3ff82790e1 Modules: dictionary API WIP #9: iterator returning string object. 2018-09-25 16:49:46 +02:00
antirez fb1d5717de Modules: dictionary API WIP #8: Iterator next/prev. 2018-09-25 16:25:46 +02:00
antirez 58ac1f8bbe Modules: dictionary API WIP #7: don't store the context.
Storing the context is useless, because we can't really reuse that
later. For instance in the API RM_DictNext() that returns a
RedisModuleString for the next key iterated, the user should pass the
new context, because we may run the keys of the dictionary in a
different context of the one where the dictionary was created. Also the
dictionary may be created without a context, but we may still demand
automatic memory management for the returned strings while iterating.
2018-09-25 12:58:16 +02:00
antirez b6c794acf6 Modules: dictionary API WIP #6: implement automatic memory management. 2018-09-25 12:45:08 +02:00
antirez 448d696549 Modules: dictionary API work in progress #5: rename API for consistency.
By using the "C" suffix for functions getting pointer/len, we can do the
same in the future for other modules APIs that need a variant with
pointer/len and that are now accepting a RedisModuleString.
2018-09-25 12:31:46 +02:00
antirez c7e0c410d6 Modules: change RedisModuleString API to allow NULL context.
The burden of having to always create RedisModuleString objects within a
module context was too much, especially now that we have threaded
operations and modules are doing more interesting things. The context in
the string API is currently only used for automatic memory managemnet,
so now the API was modified so that the user can opt-out this feature by
passing a NULL context.
2018-09-24 17:20:00 +02:00
antirez 3968550135 Modules: dictionary API work in progress #4: reseek API. 2018-09-24 16:43:47 +02:00
Pavel SkuratovichandGitHub 3a27b3d0d8 Fix typo in comment 2018-09-24 16:14:36 +03:00
antirez 14b2f7b033 Modules: dictionary API work in progress #3: Iterator creation. 2018-09-24 11:44:49 +02:00
antirez bb64c7d8b2 Modules: dictionary API work in progress #2: Del API. 2018-09-24 11:16:58 +02:00
antirez c5e0bc1070 Modules: dictionary API work in progress #1. 2018-09-21 17:54:09 +02:00
Guy KorlandandGitHub 8b87876094 add missing argument to function doc 2018-09-21 02:46:31 +03:00
antirez 0d6f11f4d1 Module cluster flags: use RM_SetClusterFlags() in the example. 2018-09-19 16:17:20 +02:00
antirez 3213e8de92 Module cluster flags: add RM_SetClusterFlags() API. 2018-09-19 12:02:37 +02:00
antirez 777cc5b987 Module cluster flags: add hooks for NO_FAILOVER flag. 2018-09-19 11:43:37 +02:00
antirez c202ba2b35 Module cluster flags: add hooks for NO_REDIRECTION flag. 2018-09-19 11:31:22 +02:00
antirez 744fe7f348 Module cluster flags: initial vars / defines added. 2018-09-19 11:20:52 +02:00
antirez 7cdf272d46 Modules: rename the reused static client to something more general. 2018-09-18 13:22:05 +02:00
antirez 9df1f73e4c Modules: associate a fake client to timer context callback. 2018-09-18 13:19:33 +02:00
antirez bf18044082 Modules: associate a fake client to cluster message context callback.
Fixes #5354.
2018-09-18 13:15:40 +02:00
artixandantirez 81c4be42c1 Cluster Manager: clusterManagerFixOpenSlot now counts node's keys in slot
if node is neither migrating nor importing.
2018-09-17 16:26:46 +02:00
antirez 12fd92bf67 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2018-09-17 16:24:52 +02:00
antirez 9f43264f86 Revert "fix repeat argument issue and reduce unnessary loop times for redis-cli."
Reverts commit 9505dd2011
since the commit introduced the very serious bug issue #5286.
2018-09-17 16:24:07 +02:00
Salvatore SanfilippoandGitHub 54871412c2 Merge pull request #5348 from gkorland/patch-2
No need to return "OK"
2018-09-17 15:42:50 +02:00
Salvatore SanfilippoandGitHub f39bb4107c Merge pull request #5352 from gkorland/patch-3
typo fix
2018-09-17 15:41:24 +02:00
antirez 394b974920 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2018-09-17 15:35:07 +02:00
antirez 378218d746 Sentinel: document how to undo a renamed command. 2018-09-17 15:29:04 +02:00
Salvatore SanfilippoandGitHub bd33014f90 Merge pull request #5357 from gkorland/patch-4
RedisModule_HashSet call must end with NULL
2018-09-17 15:20:10 +02:00
Guy KorlandandGitHub 3176f8e955 Merge pull request #1 from gkorland/patch-5
Fix few typos
2018-09-17 14:15:39 +03:00
Guy KorlandandGitHub 3b0f008615 Fix few typos 2018-09-17 14:13:46 +03:00
Guy KorlandandGitHub 44f9e0d7c7 RedisModule_HashSet call must end with NULL
Extended the RedisModule_HashSet doc to mark that each call must end with NULL
2018-09-17 13:54:56 +03:00
Guy KorlandandGitHub b2fc998ad6 typo fix 2018-09-16 15:21:21 +03:00
antirez c560ade831 LOLWUT: split the command from version-specific implementations. 2018-09-14 12:35:59 +02:00
antirez 411f4b4c12 Slave removal: add a few forgotten aliases for CONFIG SET. 2018-09-13 21:43:15 +02:00
antirez 9849cf2df5 LOLWUT: add Redis version in the output.
This creates an incentive to run the command and as a side effect
experience the art piece inside.
2018-09-13 21:36:02 +02:00
Guy KorlandandGitHub 62015d4fb0 No need to return "OK"
No need to return "+OK" in this case since the result is an Array of all the nodes
2018-09-13 15:42:17 +03:00
antirez d23cae5f06 LOLWUT: Ness -> Nees. 2018-09-13 09:03:36 +02:00
antirez ffbf8f4b8e LOLWUT: Limit maximum CPU effort. 2018-09-13 08:31:55 +02:00
antirez 5c9a7b891f LOLWUT: change padding conditional to a more direct one. 2018-09-13 08:29:55 +02:00
antirez 02817d8b3d Merge branch 'unstable' of github.com:/antirez/redis into unstable 2018-09-13 08:08:56 +02:00
antirez 4dc37c240e LOLWUT: fix crash when col < 2.
Close #5345.
2018-09-13 08:08:30 +02:00
Salvatore SanfilippoandGitHub b278d352c5 Merge pull request #5343 from slobo/patch-1
Fix spelling descrive -> describe
2018-09-12 21:30:27 +02:00
Slobodan MiškovićandGitHub 30377f154f Fix spelling descrive -> describe 2018-09-12 12:10:07 -07:00
antirez 690560e8db LOLWUT: fix structure typo in comment. 2018-09-12 17:53:00 +02:00
antirez cf0abff567 LOLWUT: Fix license copyright year. 2018-09-12 12:40:40 +02:00
antirez 77bdfb5339 LOLWUT: increase the translation factor. 2018-09-12 12:28:28 +02:00
antirez 0dd8fd062d LOLWUT: change default size to fit a normal terminal better. 2018-09-12 12:26:33 +02:00
antirez 43385c4375 LOLWUT: wrap it into a proper command. 2018-09-12 11:34:10 +02:00
antirez eac2a79cf5 LOLWUT: draw Schotter by Georg Nees. 2018-09-12 11:16:07 +02:00
antirez 2ead41e05b LOLWUT: draw rotated squares using trivial trigonometry. 2018-09-12 10:36:02 +02:00
antirez a974531d1a LOLWUT: draw lines using Bresenham algorithm. 2018-09-11 21:15:43 +02:00
antirez 7777379814 LOLWUT: Rendering of the virtual canvas to a string. 2018-09-11 17:32:57 +02:00
antirez 096c74c886 LOLWUT: show the output verbatim in redis-cli. 2018-09-11 16:29:31 +02:00
antirez 2dbf2047ac LOLWUT: canvas structure and BSD license on top. 2018-09-11 16:09:24 +02:00
antirez 65ce839d09 LOLWUT: Emit Braille unicode according to pixel pattern. 2018-09-11 16:04:25 +02:00
Jakub Vranaandantirez cb51bb4320 Slave removal: capitalize Replica 2018-09-11 15:32:28 +02:00
antirez 4cf8fdbbd3 Slave removal: remove slave from integration tests descriptions. 2018-09-11 15:32:28 +02:00
antirez 52e7192e4a Slave removal: remove slave from top-level tests descriptions. 2018-09-11 15:32:28 +02:00
antirez 0c1d28e2d0 Slave removal: remove slave from object.c. 2018-09-11 15:32:28 +02:00
antirez 3841236074 Slave removal: remove slave from the README. 2018-09-11 15:32:28 +02:00
antirez db146de086 Slave removal: server.c logs fixed. 2018-09-11 15:32:28 +02:00
antirez f74c7394d7 Slave removal: remove slave from sentinel.conf when possible.
All the occurrences translated, but the ones referring to SLAVEOF
must be intact because that describe the actual Sentinel -> Redis
protocol. In theory we could send REPLICAOF to Redis instances, but
actually this would prevent Sentinel to be compatible with older Redis
instances.
2018-09-11 15:32:28 +02:00
antirez ca6aed02f8 Slave removal: replace very few things in Sentinel.
SENTINEL REPLICAS was added as an alias, in the configuration rewriting
now it uses known-replica, however all the rest is basically at API
level of logged events and messages having to do with the protocol, so
there is very little to do here compared to the Redis core itself, to
preserve compatibility.
2018-09-11 15:32:28 +02:00
antirez 1051d93837 Slave removal: scripting.c logs and other stuff fixed. 2018-09-11 15:32:28 +02:00
antirez 61b7a176ef Slave removal: replication.c logs fixed. 2018-09-11 15:32:28 +02:00
antirez cff5f36d94 Slave removal: networking.c logs fixed. 2018-09-11 15:32:28 +02:00
antirez 05e8db24ed Slave removal: blocked.c logs fixed. 2018-09-11 15:32:28 +02:00
antirez c9bab6f302 Slave removal: Make obvious in redis.conf what a replica is. 2018-09-11 15:32:28 +02:00
antirez 8296e5c846 Slave removal: slave mode -> replica mode text in redis-cli. 2018-09-11 15:32:28 +02:00
antirez a67181f88a Slave removal: fix typo of replicaof. 2018-09-11 15:32:28 +02:00
antirez 6f3d357d8f Slave removal: slave -> replica in redis.conf and output buffer option. 2018-09-11 15:32:28 +02:00
antirez 6f58486139 Slave removal: config.c converted + config rewriting hacks.
Aliases added for all the commands mentioning slave. Moreover CONFIG
REWRITE will use the new names, and will be able to reuse the old lines
mentioning the old options.
2018-09-11 15:32:28 +02:00
antirez a9419e2386 Slave removal: Convert cluster.c log messages and command names. 2018-09-11 15:32:28 +02:00
antirez f579b77449 Slave removal: redis-cli --slave -> --replica.
--slave alias remains but is undocumented, just for backward
compatibiltiy.
2018-09-11 15:32:28 +02:00
antirez ef2c7a5bbb Slave removal: SLAVEOF -> REPLICAOF. SLAVEOF is now an alias. 2018-09-11 15:32:28 +02:00
Salvatore SanfilippoandGitHub 14629278e3 Merge pull request #5307 from mesbahamin/use_geohash_defines_in_check
Use geohash limit defines in constraint check
2018-09-07 12:31:27 +02:00
Salvatore SanfilippoandGitHub 83b8eaa638 Merge pull request #5239 from jeffreylovitz/cli-help-fix
CLI Help text loop verifies arg count
2018-09-07 12:28:22 +02:00
Salvatore SanfilippoandGitHub a62b8410ef Merge pull request #5322 from youjiali1995/fix-sentinel-randomize
sentinel: fix randomized sentinelTimer.
2018-09-07 12:10:35 +02:00
Salvatore SanfilippoandGitHub aba57a1bb7 Merge pull request #5324 from youjiali1995/fix-bio
bio: fix bioWaitStepOfType.
2018-09-07 12:06:02 +02:00
Salvatore SanfilippoandGitHub 596749e75f Merge pull request #5331 from Weiliang-Li/patch-redis-cli
fix usage typo in redis-cli
2018-09-06 17:41:31 +02:00
Weiliang Li db74d71eb3 fix usage typo in redis-cli 2018-09-06 13:40:05 +08:00
antirez 6bd0d342dd Fix scripting tests now that we default to commands repl. 2018-09-05 19:54:54 +02:00
antirez 7895835df6 Use commands (effects) replication by default in scripts.
See issue #5250 and issue #5292 for more info.
2018-09-05 19:33:56 +02:00
antirez 7e11825ef4 Safer script stop condition on OOM.
Here the idea is that we do not want freeMemoryIfNeeded() to propagate a
DEL command before the script and change what happens in the script
execution once it reaches the slave. For example see this potential
issue (in the words of @soloestoy):

On master, we run the following script:

    if redis.call('get','key')
    then
        redis.call('set','xxx','yyy')
    end
    redis.call('set','c','d')

Then when redis attempts to execute redis.call('set','xxx','yyy'), we call freeMemoryIfNeeded(), and the key may get deleted, and because redis.call('set','xxx','yyy') has already been executed on master, this script will be replicated to slave.

But the slave received "DEL key" before the script, and will ignore maxmemory, so after that master has xxx and c, slave has only one key c.

Note that this patch (and other related work) was authored collaboratively in
issue #5250 with the help of @soloestoy and @oranagra.

Related to issue #5250.
2018-09-05 15:48:08 +02:00
antirez 092e4de613 Propagate read-only scripts as SCRIPT LOAD.
See issue #5250 and the new comments added to the code in this commit
for details.
2018-09-05 15:44:33 +02:00
antirez 51b627d916 Don't perform eviction when re-entering the event loop.
Related to #5250.
2018-09-05 13:10:07 +02:00
youjiali1995 c328834832 bio: fix bioWaitStepOfType. 2018-09-05 16:51:13 +08:00
youjiali1995 a8322f44b3 sentinel: fix randomized sentinelTimer. 2018-09-05 10:32:18 +08:00
antirez 4e5e0d3719 Clarify why remaining may be zero in readQueryFromClient().
See #5304.
2018-09-04 13:29:27 +02:00
antirez ff57b8d550 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2018-09-04 13:26:06 +02:00
Salvatore SanfilippoandGitHub 2ef829d65c Merge pull request #5304 from soloestoy/fix-unexpected-readlen
networking: fix unexpected negative or zero readlen
2018-09-04 13:25:28 +02:00
Sascha Rolandandantirez c1e9186f06 #5299 Fix blocking XREAD for streams that ran dry
The conclusion, that a xread request can be answered syncronously in
case that the stream's last_id is larger than the passed last-received-id
parameter, assumes, that there must be entries present, which could be
returned immediately.
This assumption fails for empty streams that actually contained some
entries which got removed by xdel, ... .

As result, the client is answered synchronously with an empty result,
instead of blocking for new entries to arrive.
An additional check for a non-empty stream is required.
2018-09-04 13:13:36 +02:00
Salvatore SanfilippoandGitHub e4298d11e2 Merge pull request #5321 from maya-rv/patch-1
Fix typo
2018-09-04 12:58:04 +02:00
Salvatore SanfilippoandGitHub d60c17cbb3 Merge pull request #5315 from soloestoy/optimize-parsing-large-bulk
networking: optimize parsing large bulk greater than 32k
2018-09-04 12:49:50 +02:00
maya-rvandGitHub 227965221a Fix typo 2018-09-04 13:32:02 +03:00
antirez 6c001bfc0d Unblocked clients API refactoring. See #4418. 2018-09-03 18:39:18 +02:00
Salvatore SanfilippoandGitHub 2b689ad641 Merge pull request #4418 from soloestoy/fix-multiple-unblock
fix multiple unblock for clientsArePaused()
2018-09-03 18:31:02 +02:00
antirez 3e7349fdaf Make pending buffer processing safe for CLIENT_MASTER client.
Related to #5305.
2018-09-03 18:17:31 +02:00
zhaozhao.zz 247d2a734b networking: optimize parsing large bulk greater than 32k
If we are going to read a large object from network
try to make it likely that it will start at c->querybuf
boundary so that we can optimize object creation
avoiding a large copy of data.

But only when the data we have not parsed is less than
or equal to ll+2. If the data length is greater than
ll+2, trimming querybuf is just a waste of time, because
at this time the querybuf contains not only our bulk.

It's easy to reproduce the that:

Time1: call `client pause 10000` on slave.

Time2: redis-benchmark -t set -r 10000 -d 33000 -n 10000.

Then slave hung after 10 seconds.
2018-09-04 00:02:25 +08:00
zhaozhao.zz 2290c4bee1 if master is already unblocked, do not unblock it twice 2018-09-03 14:36:48 +08:00
zhaozhao.zz e3dfd8c811 fix multiple unblock for clientsArePaused() 2018-09-03 14:26:14 +08:00
Amin Mesbah a036c64c01 Use geohash limit defines in constraint check
Slight robustness improvement, especially if the limit values are
changed, as was suggested in antires/redis#4291 [1].

[1] https://github.com/antirez/redis/pull/4291
2018-09-02 00:06:20 -07:00
antirez febe102bf6 Test: processing of master stream in slave -BUSY state.
See #5297.
2018-08-31 16:45:02 +02:00
antirez 7fa493912e After slave Lua script leaves busy state, re-process the master buffer.
Technically speaking we don't really need to put the master client in
the clients that need to be processed, since in practice the PING
commands from the master will take care, however it is conceptually more
sane to do so.
2018-08-31 16:45:02 +02:00
antirez 9ab91b8c6c While the slave is busy, just accumulate master input.
Processing command from the master while the slave is in busy state is
not correct, however we cannot, also, just reply -BUSY to the
replication stream commands from the master. The correct solution is to
stop processing data from the master, but just accumulate the stream
into the buffers and resume the processing later.

Related to #5297.
2018-08-31 16:45:02 +02:00
antirez 83af8ef1fd Allow scripts to timeout even if from the master instance.
However the master scripts will be impossible to kill.

Related to #5297.
2018-08-31 16:45:02 +02:00
antirez f5b29c6444 Allow scripts to timeout on slaves as well.
See reasoning in #5297.
2018-08-31 16:45:01 +02:00
zhaozhao.zz dce7cefb7c networking: fix unexpected negative or zero readlen
To avoid copying buffers to create a large Redis Object which
exceeding PROTO_IOBUF_LEN 32KB, we just read the remaining data
we need, which may less than PROTO_IOBUF_LEN. But the remaining
len may be zero, if the bulklen+2 equals sdslen(c->querybuf),
in client pause context.

For example:

Time1:

python
>>> import os, socket
>>> server="127.0.0.1"
>>> port=6379
>>> data1="*3\r\n$3\r\nset\r\n$1\r\na\r\n$33000\r\n"
>>> data2="".join("x" for _ in range(33000)) + "\r\n"
>>> data3="\n\n"
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> s.settimeout(10)
>>> s.connect((server, port))
>>> s.send(data1)
28

Time2:

redis-cli client pause 10000

Time3:

>>> s.send(data2)
33002
>>> s.send(data3)
2
>>> s.send(data3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
socket.error: [Errno 104] Connection reset by peer

To fix that, we should check if remaining is greater than zero.
2018-08-31 20:02:09 +08:00
Salvatore SanfilippoandGitHub d05f5c8f64 Merge pull request #5268 from 0xtonyxia/fix-latency-cmd-comments2
Revise the comments of latency command.
2018-08-29 16:19:13 +02:00
Salvatore SanfilippoandGitHub 476eea95da Merge pull request #4216 from lamby/did-not-received-typos
Correct "did not received" -> "did not receive" typos/grammar.
2018-08-29 16:18:11 +02:00
Salvatore SanfilippoandGitHub bd9259d12d Merge pull request #5282 from soloestoy/remove-duplicate-bind-in-sentinel.conf
remove duplicate bind in sentinel.conf
2018-08-29 16:13:42 +02:00
Salvatore SanfilippoandGitHub ed5cc77ce0 Merge pull request #5296 from soloestoy/command-script-flag2
Supplement to PR #4835, just take info/memory/command as random commands
2018-08-29 12:26:01 +02:00
zhaozhao.zz 7d39c149c4 Supplement to PR #4835, just take info/memory/command as random commands 2018-08-29 18:23:05 +08:00
Salvatore SanfilippoandGitHub 0e21efdb1c Merge pull request #4835 from soloestoy/command-script-flag
some commands' flags should be set correctly, issue #4834
2018-08-29 12:13:50 +02:00
zhaozhao.zz 32844178ac some commands' flags should be set correctly, issue #4834 2018-08-29 18:07:01 +08:00
Salvatore SanfilippoandGitHub 5b4bec9d33 Merge pull request #5265 from oranagra/stabilize_tests
Fix unstable tests on slow machines.
2018-08-27 13:19:31 +02:00
antirez 5941022b52 Document slave-ignore-maxmemory in redis.conf. 2018-08-27 12:34:29 +02:00
antirez e245a2046a Make slave-ignore-maxmemory configurable. 2018-08-27 12:27:17 +02:00
antirez 067647a783 Introduce repl_slave_ignore_maxmemory flag internally.
Note: this breaks backward compatibility with Redis 4, since now slaves
by default are exact copies of masters and do not try to evict keys
independently.
2018-08-27 12:20:27 +02:00
antirez abf52c7cf4 Better variable meaning in processCommand(). 2018-08-27 12:17:34 +02:00
antirez 66b5afdaa4 Re-apply rebased #2358. 2018-08-27 12:17:14 +02:00
antirez c241f51607 Fix build errors caused by #2358. 2018-08-27 12:15:55 +02:00
zhaozhao.zz e711e6f614 remove duplicate bind in sentinel.conf 2018-08-27 12:07:24 +08:00
Salvatore SanfilippoandGitHub 19880ab851 Merge pull request #5248 from soloestoy/rewrite-brpoplpush
rewrite BRPOPLPUSH as RPOPLPUSH to propagate
2018-08-26 16:31:24 +02:00
Salvatore SanfilippoandGitHub 80e1695652 Merge pull request #5244 from soloestoy/optimize-pipeline
pipeline: do not sdsrange querybuf unless all commands processed
2018-08-26 16:30:49 +02:00
Chris Lamb 132be8aed5 Correct "did not received" -> "did not receive" typos/grammar. 2018-08-26 14:45:39 +02:00
Salvatore SanfilippoandGitHub 46d89a9abb Merge pull request #2992 from lamby/source-date-epoch
Use SOURCE_DATE_EPOCH over unreproducible uname + date calls.
2018-08-26 11:25:44 +02:00
Salvatore SanfilippoandGitHub 590717782f Merge pull request #2358 from lamby/config-set-maxmemory-grammar
Tidy grammar in CONFIG SET maxmemory warning.
2018-08-26 11:23:41 +02:00
Salvatore SanfilippoandGitHub 8d519a85f4 Merge pull request #2292 from lamby/sentinel-conf-defaults
Make some defaults explicit in the sentinel.conf for package maintainers
2018-08-26 11:19:51 +02:00
Chris LambandGitHub f63e81c202 Merge branch 'unstable' into config-set-maxmemory-grammar 2018-08-25 21:49:29 +02:00
zhaozhao.zz f2ad89a314 networking: make setProtocolError simple and clear
Function setProtocolError just records proctocol error
details in server log, set client as CLIENT_CLOSE_AFTER_REPLY.
It doesn't care about querybuf sdsrange, because we
will do it after procotol parsing.
2018-08-23 12:21:28 +08:00
dejun.xdj 1ab64d405e Revise the comments of latency command. 2018-08-22 18:07:02 +08:00
Oran Agra c8452ab005 Fix unstable tests on slow machines.
Few tests had borderline thresholds that were adjusted.

The slave buffers test had two issues, preventing the slave buffer from growing:
1) the slave didn't necessarily go to sleep on time, or woke up too early,
   now using SIGSTOP to make sure it goes to sleep exactly when we want.
2) the master disconnected the slave on timeout
2018-08-21 11:46:07 +03:00
zhaozhao.zz 9a65f9cd3e block: format code 2018-08-14 20:59:32 +08:00
zhaozhao.zz 8a1219d93b block: rewrite BRPOPLPUSH as RPOPLPUSH to propagate 2018-08-14 20:58:58 +08:00
zhaozhao.zz ef2a95c461 networking: just move qb_pos instead of sdsrange in processInlineBuffer 2018-08-14 14:50:37 +08:00
zhaozhao.zz e623bd22ba networking: just return C_OK if multibulk processing saw a <= 0 length. 2018-08-14 13:55:30 +08:00
zhaozhao.zz b89302c462 adjust qbuf to 26 in test case for client list 2018-08-14 00:57:22 +08:00
zhaozhao.zz 14c4ddb5a6 pipeline: do not sdsrange querybuf unless all commands processed
This is an optimization for processing pipeline, we discussed a
problem in issue #5229: clients may be paused if we apply `CLIENT
PAUSE` command, and then querybuf may grow too large, the cost of
memmove in sdsrange after parsing a completed command will be
horrible. The optimization is that parsing all commands in queyrbuf
, after that we can just call sdsrange only once.
2018-08-14 00:43:42 +08:00
Oran Agra f4b27ae222 script cache memory in INFO and MEMORY includes both script code and overheads 2018-08-13 17:36:54 +03:00
Jeffrey Lovitz bd01334da1 CLI Help text loop verifies arg count 2018-08-12 12:47:01 -04:00
zhaozhao.zz a3a1460525 Streams: update listpack with new pointer in XDEL 2018-08-04 01:06:53 +08:00
Salvatore SanfilippoandGitHub 39c70e728b Merge pull request #5146 from 0xtonyxia/fix-xclaim-id-parse
Streams: ID of xclaim command should start from the sixth argument.
2018-08-03 13:45:27 +02:00
Salvatore SanfilippoandGitHub 7b5e7f3ed0 Merge pull request #5151 from shenlongxing/fix-stream
Fix stream command paras
2018-08-03 13:39:32 +02:00
shenlongxing fe56c67405 Fix stream command paras 2018-08-03 19:01:15 +08:00
antirez 0ce8323c0d Fix AOF comment to report the current behavior.
Realted to #5201.
2018-08-03 12:46:06 +02:00
antirez 3d56311f0c Test suite: add --loop option.
Very useful with --stop in order to catch heisenbugs.
2018-08-02 19:07:17 +02:00
antirez 26897c03e2 Test suite: new --stop option.
It pauses the test execution once the first failure is found.
2018-08-02 18:49:49 +02:00
antirez d506334b67 Test: new sorted set skiplist order consistency.
This should be able to find new bugs and regressions about the new
sorted set update function when ZADD is used to update an element
already existing.

The test is able to find the bug fixed at 2f282aee immediately.
2018-08-02 14:15:53 +02:00
antirez 2f282aee0b Fix zslUpdateScore() edge case.
When the element new score is the same of prev/next node, the
lexicographical order kicks in, so we can safely update the node in
place only when the new score is strictly between the adjacent nodes
but never equal to one of them.

Technically speaking we could do extra checks to make sure that even if the
score is the same as one of the adjacent nodes, we can still update on
place, but this rarely happens, so probably not a good deal to make it
more complex.

Related to #5179.
2018-08-01 19:04:53 +02:00
antirez 29226a3919 More commenting of zslUpdateScore(). 2018-08-01 18:54:15 +02:00
antirez 12ff0c0d79 Explain what's the point of zslUpdateScore() in top comment. 2018-08-01 18:53:09 +02:00
antirez e3e94ec35c Remove old commented zslUpdateScore() from source. 2018-08-01 18:50:49 +02:00
antirez 0b800c4332 Optimize zslUpdateScore() as asked in #5179. 2018-08-01 18:50:31 +02:00
antirez 6dd0d6f7dd zsetAdd() refactored adding zslUpdateScore(). 2018-08-01 18:27:56 +02:00
zhaozhao.zz 60acac4cd0 Streams: add test cases for XADD/XTRIM maxlen 2018-08-01 10:34:29 +08:00
zhaozhao.zz 9042d1c249 Streams: propagate specified MAXLEN instead of approximated
Slaves and rebooting redis may have different radix tree struct,
by different stream* config options. So propagating approximated
MAXLEN to AOF/slaves may lead to date inconsistency.
2018-08-01 10:34:12 +08:00
zhaozhao.zz 14d6318b32 Streams: reset approx_maxlen in every maxlen loop 2018-08-01 10:31:27 +08:00
zhaozhao.zz da6b7516f1 Streams: XTRIM will return an error if MAXLEN with a count < 0 2018-08-01 10:31:27 +08:00
zhaozhao.zz 20c6a7fe2c Streams: propagate original MAXLEN argument in XADD context
If we rewrite the MAXLEN argument as zero when no trimming
was performed, date between master and slave and aof will
be inconsistent, because `xtrim maxlen 0` means delete all
entries in stream.
2018-08-01 10:31:27 +08:00
antirez 2f2987ffc5 Streams IDs parsing refactoring.
Related to #5184.
2018-07-31 18:08:52 +02:00
Salvatore SanfilippoandGitHub 187fa78637 Merge pull request #5184 from 0xtonyxia/streams-id-opt
Streams: rearrange the usage of '-' and '+' IDs in stream commands.
2018-07-31 17:58:13 +02:00
antirez 7a30be1237 Minor improvements to PR #5187. 2018-07-31 17:30:12 +02:00
Salvatore SanfilippoandGitHub 7b41701eb8 Merge pull request #5187 from oranagra/testsuite_improvements
Testsuite improvements
2018-07-31 17:20:09 +02:00
antirez e245ed9a44 Cluster cron announce IP minor refactoring. 2018-07-31 17:13:03 +02:00
Salvatore SanfilippoandGitHub 093adb7ef9 Merge pull request #5191 from shenlongxing/fix-memory-leak
Fix cluster-announce-ip memory leak
2018-07-31 17:11:16 +02:00
antirez b0392e75ec Tranfer -> transfer typo fixed. 2018-07-31 16:41:33 +02:00
Salvatore SanfilippoandGitHub 92b39a0abf Merge pull request #5189 from soloestoy/refactor-dbOverwrite
refactor dbOverwrite to make lazyfree work
2018-07-31 16:40:35 +02:00
antirez db693be00d Refactoring: replace low-level checks with writeCommandsDeniedByDiskError(). 2018-07-31 13:16:43 +02:00
antirez 11dd3f4b03 Fix writeCommandsDeniedByDiskError() inverted return value. 2018-07-31 13:14:24 +02:00
antirez 0e49938b68 Better top comment for writeCommandsDeniedByDiskError(). 2018-07-31 13:10:37 +02:00
antirez 5401fe7fb9 Introduce writeCommandsDeniedByDiskError(). 2018-07-31 13:09:38 +02:00
shenlongxing 35ca670060 Fix cluster-announce-ip memory leak 2018-07-31 16:01:44 +08:00
zhaozhao.zz fddeeae724 refactor dbOverwrite to make lazyfree work 2018-07-31 12:07:57 +08:00
Oran Agra 7829287691 test suite conveniency improvements
* allowing --single to be repeated
* adding --only so that only a specific test inside a unit can be run
* adding --skiptill useful to resume a test that crashed passed the problematic unit.
  useful together with --clients 1
* adding --skipfile to use a file containing list of tests names to skip
* printing the names of the tests that are skiped by skipfile or denytags
* adding --config to add config file options from command line
2018-07-30 19:13:15 +03:00
Salvatore SanfilippoandGitHub ac3c012a7f Merge pull request #5153 from trevor211/fixLuaScript
Consider aof write error as well as rdb in lua script.
2018-07-30 18:10:06 +02:00
Salvatore Sanfilippo 4ebb4d54c6 Merge pull request #5168 from rpv-tomsk/issue-5033
INFO CPU: higher precision of reported values
2018-07-30 18:06:54 +02:00
Pavel Rochnyackandantirez 1e394b73b7 INFO CPU: higher precision of reported values
Closes: #5033
2018-07-30 18:06:22 +02:00
Salvatore SanfilippoandGitHub 1465755e38 Merge pull request #5183 from gkorland/patch-1
Few typo fixes
2018-07-30 17:59:12 +02:00
antirez 3c19ae941d Add year in log.
User: "is there a reason why redis server logs  are missing the year in
the "date time"?"

Me: "I guess I did not imagine it would be stable enough to run for
several years".
2018-07-30 17:42:30 +02:00
dejun.xdj 6491717c88 Streams: rearrange the usage of '-' and '+' IDs in stream commands. 2018-07-30 21:33:01 +08:00
dejun.xdj d6f5ec6f09 Streams: add mmid_supp argument in streamParseIDOrReply().
If 'mmid_supp' is set to 0, "-" and "+" will be
treated as an invalid ID.
2018-07-30 21:32:07 +08:00
Guy KorlandandGitHub acaa18f1d1 Few typo fixes 2018-07-30 16:18:56 +03:00
antirez 15be174cf1 Document dynamic-hz in the example redis.conf. 2018-07-30 13:50:15 +02:00
antirez be28050ac0 Make dynamic hz actually configurable. 2018-07-30 13:44:52 +02:00
antirez c426d85c4c Control dynamic HZ via server configuration. 2018-07-30 13:37:30 +02:00
antirez 4e9c30a6ca Merge branch 'dynamic-hz' into unstable 2018-07-30 13:31:23 +02:00
Oran Agra 4a30adde31 add DEBUG LOG, to to assist test suite debugging 2018-07-30 12:23:51 +03:00
antirez fd174cca23 Remove useless conditional from emptyDb().
Related to #4852.
2018-07-25 16:34:57 +02:00
antirez 1c2352f0dd Make emptyDb() change introduced in #4852 simpler to read. 2018-07-25 16:32:52 +02:00
Salvatore SanfilippoandGitHub fe408ad23f Merge pull request #4852 from soloestoy/optimize-flushall
optimize flushdb, avoid useless loops
2018-07-25 16:30:11 +02:00
zhaozhao.zz dd071ff6c9 optimize flushdb, avoid useless loops 2018-07-25 18:13:34 +08:00
Salvatore SanfilippoandGitHub 73cbc33a0a Merge pull request #5133 from soloestoy/fix-xdel-memory-leak
Streams: fix xdel memory leak
2018-07-24 18:57:05 +02:00
zhaozhao.zz eb0e5fe4e3 Streams: fix xdel memory leak 2018-07-25 00:06:27 +08:00
antirez 0bdeb861f4 Example the magic +1 in migrateCommand().
Related to #5154.
2018-07-24 17:31:43 +02:00
antirez 53d46fa712 Make changes of PR #5154 hopefully simpler. 2018-07-24 17:27:43 +02:00
Salvatore SanfilippoandGitHub e52a036ac6 Merge pull request #5154 from trevor211/optimizeMigrate
Do not migrate already expired keys.
2018-07-24 17:24:37 +02:00
antirez 8513f31be0 Streams: refactoring of next entry seek in the iterator.
After #5161 the code could be made a bit more obvious for newcomers.
2018-07-24 11:07:27 +02:00
Salvatore SanfilippoandGitHub 41a886605a Merge pull request #5161 from soloestoy/optimize-stream-iterator
Streams: skip master fileds only when we are going forward in streamIteratorGetID
2018-07-24 10:59:55 +02:00
Salvatore SanfilippoandGitHub dfc82fca54 Merge pull request #5163 from oranagra/fix_slave_buffer_test
fix slave buffer test suite false positives
2018-07-24 10:28:48 +02:00
antirez f4ac796c34 string2ll(): better commenting. 2018-07-24 10:27:20 +02:00
Oran Agra d4ae76d1a6 fix slave buffer test suite false positives
it looks like on slow machines we're getting:
[err]: slave buffer are counted correctly in tests/unit/maxmemory.tcl
Expected condition '$slave_buf > 2*1024*1024' to be true (16914 > 2*1024*1024)

this is a result of the slave waking up too early and eating the
slave buffer before the traffic and the test ends.
2018-07-24 11:24:27 +03:00
antirez b9a8e56439 Merge branch 'removeRedundantCheck' of https://github.com/dsomeshwar/redis into unstable 2018-07-24 10:24:24 +02:00
antirez b28cbe90e1 Restore string2ll() to original version.
See PR #5157.
2018-07-24 10:22:12 +02:00
antirez 1a8d7cd9c2 Merge branch 'unstable' of github.com:/antirez/redis into unstable 2018-07-24 00:21:49 +02:00
antirez e8f5d21940 string2ll(): remove duplicated check for special case.
Related to #5157. The PR author correctly indentified that the check was
duplicated, but removing the second one introduces a bug that was fixed
in the past (hence the duplication). Instead we can remove the first
instance of the check without issues.
2018-07-24 00:20:20 +02:00
antirez 1daee8c748 string2ll(): test for NULL pointer in all the cases. 2018-07-24 00:18:39 +02:00
Salvatore SanfilippoandGitHub ab1d5fe11a Merge pull request #5160 from oranagra/fix_zmalloc_usable
fix recursion typo in zmalloc_usable
2018-07-24 00:07:59 +02:00
antirez 9a375e5408 Change 42 to 1000 as warning level for cached scripts.
Related to #4883.
2018-07-23 18:44:58 +02:00
Salvatore SanfilippoandGitHub 445a2a2b1b Merge pull request #4883 from itamarhaber/lua_scripts-in-info-memory
Adds memory information about the scripts' cache to INFO
2018-07-23 18:43:05 +02:00
antirez b65ddfb16a Dynamic HZ: adapt cron frequency to number of clients. 2018-07-23 14:21:04 +02:00
antirez e6ea603ad3 Dynamic HZ: separate hz from the configured hz.
This way we can remember what the user configured HZ is, but change the
actual HZ dynamically if needed in the dynamic HZ feature
implementation.
2018-07-23 14:13:58 +02:00
Itamar Haber 993716c351 Adds Lua overheads to MEMORY STATS, smartens the MEMORY DOCTOR 2018-07-22 21:16:00 +03:00
zhaozhao.zz 9674ede85a Streams: skip master fileds only when we are going forward in streamIteratorGetID 2018-07-22 17:52:09 +08:00
Oran Agra 780815dd6e fix recursion typo in zmalloc_usable 2018-07-22 10:17:35 +03:00
dsomeshwar bb5b8b3a6f removing redundant check 2018-07-21 23:42:08 +05:30
WuYunlong 4017a11144 Do not migrate already expired keys. 2018-07-21 10:00:32 +08:00
WuYunlong dd760bd5e6 Consider aof write error as well as rdb in lua script. 2018-07-21 08:48:51 +08:00
dejun.xdj de5ca516ae Streams: ID of xclaim command starts from the sixth argument. 2018-07-19 20:48:08 +08:00
Itamar Haber 49890c8ee9 Adds memory information about the script's cache to INFO
Implementation notes: as INFO is "already broken", I didn't want to break it further. Instead of computing the server.lua_script dict size on every call, I'm keeping a running sum of the body's length and dict overheads.

This implementation is naive as it **does not** take into consideration dict rehashing, but that inaccuracy pays off in speed ;)

Demo time:

```bash
$ redis-cli info memory | grep "script"
used_memory_scripts:96
used_memory_scripts_human:96B
number_of_cached_scripts:0
$ redis-cli eval "" 0 ; redis-cli info memory | grep "script"
(nil)
used_memory_scripts:120
used_memory_scripts_human:120B
number_of_cached_scripts:1
$ redis-cli script flush ; redis-cli info memory | grep "script"
OK
used_memory_scripts:96
used_memory_scripts_human:96B
number_of_cached_scripts:0
$ redis-cli eval "return('Hello, Script Cache :)')" 0 ; redis-cli info memory | grep "script"
"Hello, Script Cache :)"
used_memory_scripts:152
used_memory_scripts_human:152B
number_of_cached_scripts:1
$ redis-cli eval "return redis.sha1hex(\"return('Hello, Script Cache :)')\")" 0 ; redis-cli info memory | grep "script"
"1be72729d43da5114929c1260a749073732dc822"
used_memory_scripts:232
used_memory_scripts_human:232B
number_of_cached_scripts:2
✔ 19:03:54 redis [lua_scripts-in-info-memory L ✚…⚑] $ redis-cli evalsha 1be72729d43da5114929c1260a749073732dc822 0
"Hello, Script Cache :)"
```
2018-04-30 19:33:01 +03:00
Chris Lamb 82ae4f30ed Use SOURCE_DATE_EPOCH over unreproducible uname + date calls.
See <https://reproducible-builds.org/specs/source-date-epoch/> for more
details.

Signed-off-by: Chris Lamb <chris@chris-lamb.co.uk>
2016-04-29 16:45:53 +01:00
Chris Lamb eaeba1b2c8 Tidy grammar in CONFIG SET maxmemory warning.
Signed-off-by: Chris Lamb <chris@chris-lamb.co.uk>
2015-02-04 18:37:34 +00:00
Chris Lamb 3447062d5e Make some defaults explicit in the sentinel.conf for package maintainers
This may look a little pointless (and it is a complete no-op change here)
but as package maintainers need to modify these lines to actually
daemonize (etc. etc) but it's far preferable if the diff is restricted to
actually changing just that bit, not adding docs, etc. The less diff the
better, in general.

Signed-off-by: Chris Lamb <chris@chris-lamb.co.uk>
2015-01-16 09:03:00 +00:00
46 changed files with 585 additions and 4576 deletions
+11 -2642
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -216,7 +216,7 @@ Inside the root are the following important directories:
* `src`: contains the Redis implementation, written in C.
* `tests`: contains the unit tests, implemented in Tcl.
* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `antirez/redis`. An exception to this rule is `deps/geohash-int` which is the low level geocoding library used by Redis: it originated from a different project, but at this point it diverged so much that it is developed as a separated entity directly inside the Redis repository.
* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `anitrez/redis`. An exception to this rule is `deps/geohash-int` which is the low level geocoding library used by Redis: it originated from a different project, but at this point it diverged so much that it is developed as a separated entity directly inside the Redis repository.
There are a few more directories but they are not very important for our goals
here. We'll focus mostly on `src`, where the Redis implementation is contained,
@@ -227,7 +227,7 @@ of complexity incrementally.
Note: lately Redis was refactored quite a bit. Function names and file
names have been changed, so you may find that this documentation reflects the
`unstable` branch more closely. For instance in Redis 3.0 the `server.c`
and `server.h` files were named `redis.c` and `redis.h`. However the overall
and `server.h` files were named to `redis.c` and `redis.h`. However the overall
structure is the same. Keep in mind that all the new developments and pull
requests should be performed against the `unstable` branch.
+1 -1
View File
@@ -625,7 +625,7 @@ replica-priority 100
# memory to never hit a real out-of-memory condition before the master hits
# the configured maxmemory setting.
#
# replica-ignore-maxmemory yes
# replica-ingore-maxmemory yes
############################# LAZY FREEING ####################################
+1 -21
View File
@@ -21,11 +21,6 @@ NODEPS:=clean distclean
# Default settings
STD=-std=c99 -pedantic -DREDIS_STATIC=''
ifneq (,$(findstring clang,$(CC)))
ifneq (,$(findstring FreeBSD,$(uname_S)))
STD+=-Wno-c11-extensions
endif
endif
WARN=-Wall -W -Wno-missing-field-initializers
OPT=$(OPTIMIZATION)
@@ -46,10 +41,6 @@ endif
# To get ARM stack traces if Redis crashes we need a special C flag.
ifneq (,$(filter aarch64 armv,$(uname_M)))
CFLAGS+=-funwind-tables
else
ifneq (,$(findstring armv,$(uname_M)))
CFLAGS+=-funwind-tables
endif
endif
# Backwards compatibility for selecting an allocator
@@ -102,20 +93,10 @@ else
ifeq ($(uname_S),OpenBSD)
# OpenBSD
FINAL_LIBS+= -lpthread
ifeq ($(USE_BACKTRACE),yes)
FINAL_CFLAGS+= -DUSE_BACKTRACE -I/usr/local/include
FINAL_LDFLAGS+= -L/usr/local/lib
FINAL_LIBS+= -lexecinfo
endif
else
ifeq ($(uname_S),FreeBSD)
# FreeBSD
FINAL_LIBS+= -lpthread -lexecinfo
else
ifeq ($(uname_S),DragonFly)
# FreeBSD
FINAL_LIBS+= -lpthread -lexecinfo
FINAL_LIBS+= -lpthread
else
# All the other OSes (notably Linux)
FINAL_LDFLAGS+= -rdynamic
@@ -125,7 +106,6 @@ endif
endif
endif
endif
endif
# Include paths to dependencies
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
+18 -58
View File
@@ -677,7 +677,6 @@ int loadAppendOnlyFile(char *filename) {
int old_aof_state = server.aof_state;
long loops = 0;
off_t valid_up_to = 0; /* Offset of latest well-formed command loaded. */
off_t valid_before_multi = 0; /* Offset before MULTI command loaded. */
if (fp == NULL) {
serverLog(LL_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno));
@@ -778,28 +777,16 @@ int loadAppendOnlyFile(char *filename) {
/* Command lookup */
cmd = lookupCommand(argv[0]->ptr);
if (!cmd) {
serverLog(LL_WARNING,
"Unknown command '%s' reading the append only file",
(char*)argv[0]->ptr);
serverLog(LL_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);
exit(1);
}
if (cmd == server.multiCommand) valid_before_multi = valid_up_to;
/* Run the command in the context of a fake client */
fakeClient->cmd = cmd;
if (fakeClient->flags & CLIENT_MULTI &&
fakeClient->cmd->proc != execCommand)
{
queueMultiCommand(fakeClient);
} else {
cmd->proc(fakeClient);
}
cmd->proc(fakeClient);
/* The fake client should not have a reply */
serverAssert(fakeClient->bufpos == 0 &&
listLength(fakeClient->reply) == 0);
serverAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
/* The fake client should never get blocked */
serverAssert((fakeClient->flags & CLIENT_BLOCKED) == 0);
@@ -814,12 +801,7 @@ int loadAppendOnlyFile(char *filename) {
* If the client is in the middle of a MULTI/EXEC, handle it as it was
* a short read, even if technically the protocol is correct: we want
* to remove the unprocessed tail and continue. */
if (fakeClient->flags & CLIENT_MULTI) {
serverLog(LL_WARNING,
"Revert incomplete MULTI/EXEC transaction in AOF file");
valid_up_to = valid_before_multi;
goto uxeof;
}
if (fakeClient->flags & CLIENT_MULTI) goto uxeof;
loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
fclose(fp);
@@ -1139,47 +1121,25 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
streamID id;
int64_t numfields;
if (s->length) {
/* Reconstruct the stream data using XADD commands. */
while(streamIteratorGetID(&si,&id,&numfields)) {
/* Emit a two elements array for each item. The first is
* the ID, the second is an array of field-value pairs. */
/* Reconstruct the stream data using XADD commands. */
while(streamIteratorGetID(&si,&id,&numfields)) {
/* Emit a two elements array for each item. The first is
* the ID, the second is an array of field-value pairs. */
/* Emit the XADD <key> <id> ...fields... command. */
if (rioWriteBulkCount(r,'*',3+numfields*2) == 0) return 0;
if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
if (rioWriteBulkStreamID(r,&id) == 0) return 0;
while(numfields--) {
unsigned char *field, *value;
int64_t field_len, value_len;
streamIteratorGetField(&si,&field,&value,&field_len,&value_len);
if (rioWriteBulkString(r,(char*)field,field_len) == 0) return 0;
if (rioWriteBulkString(r,(char*)value,value_len) == 0) return 0;
}
}
} else {
/* Use the XADD MAXLEN 0 trick to generate an empty stream if
* the key we are serializing is an empty string, which is possible
* for the Stream type. */
if (rioWriteBulkCount(r,'*',7) == 0) return 0;
/* Emit the XADD <key> <id> ...fields... command. */
if (rioWriteBulkCount(r,'*',3+numfields*2) == 0) return 0;
if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
if (rioWriteBulkString(r,"MAXLEN",6) == 0) return 0;
if (rioWriteBulkString(r,"0",1) == 0) return 0;
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
if (rioWriteBulkString(r,"x",1) == 0) return 0;
if (rioWriteBulkString(r,"y",1) == 0) return 0;
if (rioWriteBulkStreamID(r,&id) == 0) return 0;
while(numfields--) {
unsigned char *field, *value;
int64_t field_len, value_len;
streamIteratorGetField(&si,&field,&value,&field_len,&value_len);
if (rioWriteBulkString(r,(char*)field,field_len) == 0) return 0;
if (rioWriteBulkString(r,(char*)value,value_len) == 0) return 0;
}
}
/* Append XSETID after XADD, make sure lastid is correct,
* in case of XDEL lastid. */
if (rioWriteBulkCount(r,'*',3) == 0) return 0;
if (rioWriteBulkString(r,"XSETID",6) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
/* Create all the stream consumer groups. */
if (s->cgroups) {
raxIterator ri;
+1 -1
View File
@@ -1,7 +1,7 @@
/* This file implements atomic counters using __atomic or __sync macros if
* available, otherwise synchronizing different threads using a mutex.
*
* The exported interface is composed of three macros:
* The exported interaface is composed of three macros:
*
* atomicIncr(var,count) -- Increment the atomic counter
* atomicGetIncr(var,oldvalue_var,count) -- Get and increment the atomic counter
+6 -14
View File
@@ -5164,10 +5164,10 @@ try_again:
serverAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,dbid));
}
int non_expired = 0; /* Number of keys that we'll find non expired.
Note that serializing large keys may take some time
so certain keys that were found non expired by the
lookupKey() function, may be expired later. */
int expired = 0; /* Number of keys that we'll find already expired.
Note that serializing large keys may take some time
so certain keys that were found non expired by the
lookupKey() function, may be expired later. */
/* Create RESTORE payload and generate the protocol to call the command. */
for (j = 0; j < num_keys; j++) {
@@ -5177,16 +5177,11 @@ try_again:
if (expireat != -1) {
ttl = expireat-mstime();
if (ttl < 0) {
expired++;
continue;
}
if (ttl < 1) ttl = 1;
}
/* Relocate valid (non expired) keys into the array in successive
* positions to remove holes created by the keys that were present
* in the first lookup but are now expired after the second lookup. */
kv[non_expired++] = kv[j];
serverAssertWithInfo(c,NULL,
rioWriteBulkCount(&cmd,'*',replace ? 5 : 4));
@@ -5214,9 +5209,6 @@ try_again:
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"REPLACE",7));
}
/* Fix the actual number of keys we are migrating. */
num_keys = non_expired;
/* Transfer the query to the other node in 64K chunks. */
errno = 0;
{
@@ -5258,7 +5250,7 @@ try_again:
* command name itself. */
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
for (j = 0; j < num_keys; j++) {
for (j = 0; j < num_keys-expired; j++) {
if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0) {
socket_error = 1;
break;
+3 -3
View File
@@ -120,7 +120,7 @@ const char *configEnumGetName(configEnum *ce, int val) {
return NULL;
}
/* Wrapper for configEnumGetName() returning "unknown" instead of NULL if
/* Wrapper for configEnumGetName() returning "unknown" insetad of NULL if
* there is no match. */
const char *configEnumGetNameOrUnknown(configEnum *ce, int val) {
const char *name = configEnumGetName(ce,val);
@@ -684,7 +684,7 @@ void loadServerConfigFromString(char *config) {
goto loaderr;
}
} else if ((!strcasecmp(argv[0],"cluster-slave-no-failover") ||
!strcasecmp(argv[0],"cluster-replica-no-failover")) &&
!strcasecmp(argv[0],"cluster-replica-no-failiver")) &&
argc == 2)
{
server.cluster_slave_no_failover = yesnotoi(argv[1]);
@@ -1248,7 +1248,7 @@ void configSetCommand(client *c) {
if (server.maxmemory < zmalloc_used_memory()) {
serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.");
}
freeMemoryIfNeededAndSafe();
freeMemoryIfNeeded();
}
} config_set_memory_field(
"proto-max-bulk-len",server.proto_max_bulk_len) {
+1 -3
View File
@@ -62,9 +62,7 @@
#endif
/* Test for backtrace() */
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__)) || \
defined(__FreeBSD__) || (defined(__OpenBSD__) && defined(USE_BACKTRACE))\
|| defined(__DragonFly__)
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__))
#define HAVE_BACKTRACE 1
#endif
+22 -32
View File
@@ -38,8 +38,6 @@
* C-level DB API
*----------------------------------------------------------------------------*/
int keyIsExpired(redisDb *db, robj *key);
/* Update LFU when an object is accessed.
* Firstly, decrement the counter if the decrement time is reached.
* Then logarithmically increment the counter, and update the access time. */
@@ -104,10 +102,7 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
/* 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 safe
* to return NULL ASAP. */
if (server.masterhost == NULL) {
server.stat_keyspace_misses++;
return NULL;
}
if (server.masterhost == NULL) return NULL;
/* However if we are in the context of a slave, expireIfNeeded() will
* not really try to expire the key, it only returns information
@@ -126,7 +121,6 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
server.current_client->cmd &&
server.current_client->cmd->flags & CMD_READONLY)
{
server.stat_keyspace_misses++;
return NULL;
}
}
@@ -212,7 +206,7 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
* 2) clients WATCHing for the destination key notified.
* 3) The expire time of the key is reset (the key is made persistent).
*
* All the new keys in the database should be created via this interface. */
* All the new keys in the database should be craeted via this interface. */
void setKey(redisDb *db, robj *key, robj *val) {
if (lookupKeyWrite(db,key) == NULL) {
dbAdd(db,key,val);
@@ -549,7 +543,7 @@ void keysCommand(client *c) {
if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
keyobj = createStringObject(key,sdslen(key));
if (!keyIsExpired(c->db,keyobj)) {
if (expireIfNeeded(c->db,keyobj) == 0) {
addReplyBulk(c,keyobj);
numkeys++;
}
@@ -1126,25 +1120,6 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
decrRefCount(argv[1]);
}
/* Check if the key is expired. */
int keyIsExpired(redisDb *db, robj *key) {
mstime_t when = getExpire(db,key);
if (when < 0) return 0; /* No expire for this key */
/* Don't expire anything while loading. It will be done later. */
if (server.loading) return 0;
/* If we are in the context of a Lua script, we pretend that time is
* blocked to when the Lua script started. This way a key can expire
* only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / AOF consistent.
* See issue #1525 on Github for more information. */
mstime_t now = server.lua_caller ? server.lua_time_start : mstime();
return now > when;
}
/* This function is called when we are going to perform some operation
* in a given key, but such key may be already logically expired even if
* it still exists in the database. The main way this function is called
@@ -1165,17 +1140,32 @@ int keyIsExpired(redisDb *db, robj *key) {
* The return value of the function is 0 if the key is still valid,
* otherwise the function returns 1 if the key is expired. */
int expireIfNeeded(redisDb *db, robj *key) {
if (!keyIsExpired(db,key)) return 0;
mstime_t when = getExpire(db,key);
mstime_t now;
/* If we are running in the context of a slave, instead of
* evicting the expired key from the database, we return ASAP:
if (when < 0) return 0; /* No expire for this key */
/* Don't expire anything while loading. It will be done later. */
if (server.loading) return 0;
/* If we are in the context of a Lua script, we pretend that time is
* blocked to when the Lua script started. This way a key can expire
* only the first time it is accessed and not in the middle of the
* script execution, making propagation to slaves / AOF consistent.
* See issue #1525 on Github for more information. */
now = server.lua_caller ? server.lua_time_start : mstime();
/* If we are running in the context of a slave, return ASAP:
* the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
*
* Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
if (server.masterhost != NULL) return 1;
if (server.masterhost != NULL) return now > when;
/* Return when this key has not expired */
if (now <= when) return 0;
/* Delete the key */
server.stat_expiredkeys++;
+137 -333
View File
@@ -37,11 +37,7 @@
#ifdef HAVE_BACKTRACE
#include <execinfo.h>
#ifndef __OpenBSD__
#include <ucontext.h>
#else
typedef ucontext_t sigcontext_t;
#endif
#include <fcntl.h>
#include "bio.h"
#include <unistd.h>
@@ -74,7 +70,7 @@ void xorDigest(unsigned char *digest, void *ptr, size_t len) {
digest[j] ^= hash[j];
}
void xorStringObjectDigest(unsigned char *digest, robj *o) {
void xorObjectDigest(unsigned char *digest, robj *o) {
o = getDecodedObject(o);
xorDigest(digest,o->ptr,sdslen(o->ptr));
decrRefCount(o);
@@ -104,151 +100,12 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len) {
SHA1Final(digest,&ctx);
}
void mixStringObjectDigest(unsigned char *digest, robj *o) {
void mixObjectDigest(unsigned char *digest, robj *o) {
o = getDecodedObject(o);
mixDigest(digest,o->ptr,sdslen(o->ptr));
decrRefCount(o);
}
/* This function computes the digest of a data structure stored in the
* object 'o'. It is the core of the DEBUG DIGEST command: when taking the
* digest of a whole dataset, we take the digest of the key and the value
* pair, and xor all those together.
*
* Note that this function does not reset the initial 'digest' passed, it
* will continue mixing this object digest to anything that was already
* present. */
void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o) {
uint32_t aux = htonl(o->type);
mixDigest(digest,&aux,sizeof(aux));
long long expiretime = getExpire(db,keyobj);
char buf[128];
/* Save the key and associated value */
if (o->type == OBJ_STRING) {
mixStringObjectDigest(digest,o);
} else if (o->type == OBJ_LIST) {
listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL);
listTypeEntry entry;
while(listTypeNext(li,&entry)) {
robj *eleobj = listTypeGet(&entry);
mixStringObjectDigest(digest,eleobj);
decrRefCount(eleobj);
}
listTypeReleaseIterator(li);
} else if (o->type == OBJ_SET) {
setTypeIterator *si = setTypeInitIterator(o);
sds sdsele;
while((sdsele = setTypeNextObject(si)) != NULL) {
xorDigest(digest,sdsele,sdslen(sdsele));
sdsfree(sdsele);
}
setTypeReleaseIterator(si);
} else if (o->type == OBJ_ZSET) {
unsigned char eledigest[20];
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
unsigned char *zl = o->ptr;
unsigned char *eptr, *sptr;
unsigned char *vstr;
unsigned int vlen;
long long vll;
double score;
eptr = ziplistIndex(zl,0);
serverAssert(eptr != NULL);
sptr = ziplistNext(zl,eptr);
serverAssert(sptr != NULL);
while (eptr != NULL) {
serverAssert(ziplistGet(eptr,&vstr,&vlen,&vll));
score = zzlGetScore(sptr);
memset(eledigest,0,20);
if (vstr != NULL) {
mixDigest(eledigest,vstr,vlen);
} else {
ll2string(buf,sizeof(buf),vll);
mixDigest(eledigest,buf,strlen(buf));
}
snprintf(buf,sizeof(buf),"%.17g",score);
mixDigest(eledigest,buf,strlen(buf));
xorDigest(digest,eledigest,20);
zzlNext(zl,&eptr,&sptr);
}
} else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
zset *zs = o->ptr;
dictIterator *di = dictGetIterator(zs->dict);
dictEntry *de;
while((de = dictNext(di)) != NULL) {
sds sdsele = dictGetKey(de);
double *score = dictGetVal(de);
snprintf(buf,sizeof(buf),"%.17g",*score);
memset(eledigest,0,20);
mixDigest(eledigest,sdsele,sdslen(sdsele));
mixDigest(eledigest,buf,strlen(buf));
xorDigest(digest,eledigest,20);
}
dictReleaseIterator(di);
} else {
serverPanic("Unknown sorted set encoding");
}
} else if (o->type == OBJ_HASH) {
hashTypeIterator *hi = hashTypeInitIterator(o);
while (hashTypeNext(hi) != C_ERR) {
unsigned char eledigest[20];
sds sdsele;
memset(eledigest,0,20);
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
mixDigest(eledigest,sdsele,sdslen(sdsele));
sdsfree(sdsele);
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
mixDigest(eledigest,sdsele,sdslen(sdsele));
sdsfree(sdsele);
xorDigest(digest,eledigest,20);
}
hashTypeReleaseIterator(hi);
} else if (o->type == OBJ_STREAM) {
streamIterator si;
streamIteratorStart(&si,o->ptr,NULL,NULL,0);
streamID id;
int64_t numfields;
while(streamIteratorGetID(&si,&id,&numfields)) {
sds itemid = sdscatfmt(sdsempty(),"%U.%U",id.ms,id.seq);
mixDigest(digest,itemid,sdslen(itemid));
sdsfree(itemid);
while(numfields--) {
unsigned char *field, *value;
int64_t field_len, value_len;
streamIteratorGetField(&si,&field,&value,
&field_len,&value_len);
mixDigest(digest,field,field_len);
mixDigest(digest,value,value_len);
}
}
streamIteratorStop(&si);
} else if (o->type == OBJ_MODULE) {
RedisModuleDigest md;
moduleValue *mv = o->ptr;
moduleType *mt = mv->type;
moduleInitDigestContext(md);
if (mt->digest) {
mt->digest(&md,mv->value);
xorDigest(digest,md.x,sizeof(md.x));
}
} else {
serverPanic("Unknown object type");
}
/* If the key has an expire, add it to the mix */
if (expiretime != -1) xorDigest(digest,"!!expire!!",10);
}
/* Compute the dataset digest. Since keys, sets elements, hashes elements
* are not ordered, we use a trick: every aggregate digest is the xor
* of the digests of their elements. This way the order will not change
@@ -257,6 +114,7 @@ void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o)
* a different digest. */
void computeDatasetDigest(unsigned char *final) {
unsigned char digest[20];
char buf[128];
dictIterator *di = NULL;
dictEntry *de;
int j;
@@ -279,6 +137,7 @@ void computeDatasetDigest(unsigned char *final) {
while((de = dictNext(di)) != NULL) {
sds key;
robj *keyobj, *o;
long long expiretime;
memset(digest,0,20); /* This key-val digest */
key = dictGetKey(de);
@@ -287,8 +146,134 @@ void computeDatasetDigest(unsigned char *final) {
mixDigest(digest,key,sdslen(key));
o = dictGetVal(de);
xorObjectDigest(db,keyobj,digest,o);
aux = htonl(o->type);
mixDigest(digest,&aux,sizeof(aux));
expiretime = getExpire(db,keyobj);
/* Save the key and associated value */
if (o->type == OBJ_STRING) {
mixObjectDigest(digest,o);
} else if (o->type == OBJ_LIST) {
listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL);
listTypeEntry entry;
while(listTypeNext(li,&entry)) {
robj *eleobj = listTypeGet(&entry);
mixObjectDigest(digest,eleobj);
decrRefCount(eleobj);
}
listTypeReleaseIterator(li);
} else if (o->type == OBJ_SET) {
setTypeIterator *si = setTypeInitIterator(o);
sds sdsele;
while((sdsele = setTypeNextObject(si)) != NULL) {
xorDigest(digest,sdsele,sdslen(sdsele));
sdsfree(sdsele);
}
setTypeReleaseIterator(si);
} else if (o->type == OBJ_ZSET) {
unsigned char eledigest[20];
if (o->encoding == OBJ_ENCODING_ZIPLIST) {
unsigned char *zl = o->ptr;
unsigned char *eptr, *sptr;
unsigned char *vstr;
unsigned int vlen;
long long vll;
double score;
eptr = ziplistIndex(zl,0);
serverAssert(eptr != NULL);
sptr = ziplistNext(zl,eptr);
serverAssert(sptr != NULL);
while (eptr != NULL) {
serverAssert(ziplistGet(eptr,&vstr,&vlen,&vll));
score = zzlGetScore(sptr);
memset(eledigest,0,20);
if (vstr != NULL) {
mixDigest(eledigest,vstr,vlen);
} else {
ll2string(buf,sizeof(buf),vll);
mixDigest(eledigest,buf,strlen(buf));
}
snprintf(buf,sizeof(buf),"%.17g",score);
mixDigest(eledigest,buf,strlen(buf));
xorDigest(digest,eledigest,20);
zzlNext(zl,&eptr,&sptr);
}
} else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
zset *zs = o->ptr;
dictIterator *di = dictGetIterator(zs->dict);
dictEntry *de;
while((de = dictNext(di)) != NULL) {
sds sdsele = dictGetKey(de);
double *score = dictGetVal(de);
snprintf(buf,sizeof(buf),"%.17g",*score);
memset(eledigest,0,20);
mixDigest(eledigest,sdsele,sdslen(sdsele));
mixDigest(eledigest,buf,strlen(buf));
xorDigest(digest,eledigest,20);
}
dictReleaseIterator(di);
} else {
serverPanic("Unknown sorted set encoding");
}
} else if (o->type == OBJ_HASH) {
hashTypeIterator *hi = hashTypeInitIterator(o);
while (hashTypeNext(hi) != C_ERR) {
unsigned char eledigest[20];
sds sdsele;
memset(eledigest,0,20);
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
mixDigest(eledigest,sdsele,sdslen(sdsele));
sdsfree(sdsele);
sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
mixDigest(eledigest,sdsele,sdslen(sdsele));
sdsfree(sdsele);
xorDigest(digest,eledigest,20);
}
hashTypeReleaseIterator(hi);
} else if (o->type == OBJ_STREAM) {
streamIterator si;
streamIteratorStart(&si,o->ptr,NULL,NULL,0);
streamID id;
int64_t numfields;
while(streamIteratorGetID(&si,&id,&numfields)) {
sds itemid = sdscatfmt(sdsempty(),"%U.%U",id.ms,id.seq);
mixDigest(digest,itemid,sdslen(itemid));
sdsfree(itemid);
while(numfields--) {
unsigned char *field, *value;
int64_t field_len, value_len;
streamIteratorGetField(&si,&field,&value,
&field_len,&value_len);
mixDigest(digest,field,field_len);
mixDigest(digest,value,value_len);
}
}
streamIteratorStop(&si);
} else if (o->type == OBJ_MODULE) {
RedisModuleDigest md;
moduleValue *mv = o->ptr;
moduleType *mt = mv->type;
moduleInitDigestContext(md);
if (mt->digest) {
mt->digest(&md,mv->value);
xorDigest(digest,md.x,sizeof(md.x));
}
} else {
serverPanic("Unknown object type");
}
/* If the key has an expire, add it to the mix */
if (expiretime != -1) xorDigest(digest,"!!expire!!",10);
/* We can finally xor the key-val digest to the final digest */
xorDigest(final,digest,20);
decrRefCount(keyobj);
@@ -304,7 +289,6 @@ void debugCommand(client *c) {
"CHANGE-REPL-ID -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.",
"CRASH-AND-RECOVER <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
"DIGEST -- Output a hex signature representing the current DB content.",
"DIGEST-VALUE <key-1> ... <key-N>-- Output a hex signature of the values of all the specified keys.",
"ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
"LOG <message> -- write message to the server log.",
"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.",
@@ -322,7 +306,6 @@ void debugCommand(client *c) {
"SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.",
"STRUCTSIZE -- Return the size of different Redis core C structures.",
"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
NULL
};
addReplyHelp(c, help);
@@ -349,6 +332,7 @@ NULL
zfree(ptr);
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"assert")) {
if (c->argc >= 3) c->argv[2] = tryObjectEncoding(c->argv[2]);
serverAssertWithInfo(c,c->argv[0],1 == 2);
} else if (!strcasecmp(c->argv[1]->ptr,"log") && c->argc == 3) {
serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)c->argv[2]->ptr);
@@ -361,10 +345,7 @@ NULL
return;
}
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
protectClient(c);
int ret = rdbLoad(server.rdb_filename,NULL);
unprotectClient(c);
if (ret != C_OK) {
if (rdbLoad(server.rdb_filename,NULL) != C_OK) {
addReplyError(c,"Error trying to load the RDB dump");
return;
}
@@ -373,10 +354,7 @@ NULL
} else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) {
if (server.aof_state != AOF_OFF) flushAppendOnlyFile(1);
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
protectClient(c);
int ret = loadAppendOnlyFile(server.aof_filename);
unprotectClient(c);
if (ret != C_OK) {
if (loadAppendOnlyFile(server.aof_filename) != C_OK) {
addReply(c,shared.err);
return;
}
@@ -507,28 +485,15 @@ NULL
}
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) {
/* DEBUG DIGEST (form without keys specified) */
unsigned char digest[20];
sds d = sdsempty();
int j;
computeDatasetDigest(digest);
for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]);
for (j = 0; j < 20; j++)
d = sdscatprintf(d, "%02x",digest[j]);
addReplyStatus(c,d);
sdsfree(d);
} else if (!strcasecmp(c->argv[1]->ptr,"digest-value") && c->argc >= 2) {
/* DEBUG DIGEST-VALUE key key key ... key. */
addReplyMultiBulkLen(c,c->argc-2);
for (int j = 2; j < c->argc; j++) {
unsigned char digest[20];
memset(digest,0,20); /* Start with a clean result */
robj *o = lookupKeyReadWithFlags(c->db,c->argv[j],LOOKUP_NOTOUCH);
if (o) xorObjectDigest(c->db,c->argv[j],digest,o);
sds d = sdsempty();
for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]);
addReplyStatus(c,d);
sdsfree(d);
}
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
double dtime = strtod(c->argv[2]->ptr,NULL);
long long utime = dtime*1000000;
@@ -620,10 +585,6 @@ NULL
changeReplicationId();
clearReplicationId2();
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"stringmatch-test") && c->argc == 2)
{
stringmatchlen_fuzz_test();
addReplyStatus(c,"Apparently Redis did not crash: test passed");
} else {
addReplySubcommandSyntaxError(c);
return;
@@ -762,22 +723,6 @@ static void *getMcontextEip(ucontext_t *uc) {
#elif defined(__aarch64__) /* Linux AArch64 */
return (void*) uc->uc_mcontext.pc;
#endif
#elif defined(__FreeBSD__)
/* FreeBSD */
#if defined(__i386__)
return (void*) uc->uc_mcontext.mc_eip;
#elif defined(__x86_64__)
return (void*) uc->uc_mcontext.mc_rip;
#endif
#elif defined(__OpenBSD__)
/* OpenBSD */
#if defined(__i386__)
return (void*) uc->sc_eip;
#elif defined(__x86_64__)
return (void*) uc->sc_rip;
#endif
#elif defined(__DragonFly__)
return (void*) uc->uc_mcontext.mc_rip;
#else
return NULL;
#endif
@@ -919,145 +864,6 @@ void logRegisters(ucontext_t *uc) {
);
logStackContent((void**)uc->uc_mcontext.gregs[15]);
#endif
#elif defined(__FreeBSD__)
#if defined(__x86_64__)
serverLog(LL_WARNING,
"\n"
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
(unsigned long) uc->uc_mcontext.mc_rax,
(unsigned long) uc->uc_mcontext.mc_rbx,
(unsigned long) uc->uc_mcontext.mc_rcx,
(unsigned long) uc->uc_mcontext.mc_rdx,
(unsigned long) uc->uc_mcontext.mc_rdi,
(unsigned long) uc->uc_mcontext.mc_rsi,
(unsigned long) uc->uc_mcontext.mc_rbp,
(unsigned long) uc->uc_mcontext.mc_rsp,
(unsigned long) uc->uc_mcontext.mc_r8,
(unsigned long) uc->uc_mcontext.mc_r9,
(unsigned long) uc->uc_mcontext.mc_r10,
(unsigned long) uc->uc_mcontext.mc_r11,
(unsigned long) uc->uc_mcontext.mc_r12,
(unsigned long) uc->uc_mcontext.mc_r13,
(unsigned long) uc->uc_mcontext.mc_r14,
(unsigned long) uc->uc_mcontext.mc_r15,
(unsigned long) uc->uc_mcontext.mc_rip,
(unsigned long) uc->uc_mcontext.mc_rflags,
(unsigned long) uc->uc_mcontext.mc_cs
);
logStackContent((void**)uc->uc_mcontext.mc_rsp);
#elif defined(__i386__)
serverLog(LL_WARNING,
"\n"
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
"SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
"DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
(unsigned long) uc->uc_mcontext.mc_eax,
(unsigned long) uc->uc_mcontext.mc_ebx,
(unsigned long) uc->uc_mcontext.mc_ebx,
(unsigned long) uc->uc_mcontext.mc_edx,
(unsigned long) uc->uc_mcontext.mc_edi,
(unsigned long) uc->uc_mcontext.mc_esi,
(unsigned long) uc->uc_mcontext.mc_ebp,
(unsigned long) uc->uc_mcontext.mc_esp,
(unsigned long) uc->uc_mcontext.mc_ss,
(unsigned long) uc->uc_mcontext.mc_eflags,
(unsigned long) uc->uc_mcontext.mc_eip,
(unsigned long) uc->uc_mcontext.mc_cs,
(unsigned long) uc->uc_mcontext.mc_es,
(unsigned long) uc->uc_mcontext.mc_fs,
(unsigned long) uc->uc_mcontext.mc_gs
);
logStackContent((void**)uc->uc_mcontext.mc_esp);
#endif
#elif defined(__OpenBSD__)
#if defined(__x86_64__)
serverLog(LL_WARNING,
"\n"
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
(unsigned long) uc->sc_rax,
(unsigned long) uc->sc_rbx,
(unsigned long) uc->sc_rcx,
(unsigned long) uc->sc_rdx,
(unsigned long) uc->sc_rdi,
(unsigned long) uc->sc_rsi,
(unsigned long) uc->sc_rbp,
(unsigned long) uc->sc_rsp,
(unsigned long) uc->sc_r8,
(unsigned long) uc->sc_r9,
(unsigned long) uc->sc_r10,
(unsigned long) uc->sc_r11,
(unsigned long) uc->sc_r12,
(unsigned long) uc->sc_r13,
(unsigned long) uc->sc_r14,
(unsigned long) uc->sc_r15,
(unsigned long) uc->sc_rip,
(unsigned long) uc->sc_rflags,
(unsigned long) uc->sc_cs
);
logStackContent((void**)uc->sc_rsp);
#elif defined(__i386__)
serverLog(LL_WARNING,
"\n"
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
"SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
"DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
(unsigned long) uc->sc_eax,
(unsigned long) uc->sc_ebx,
(unsigned long) uc->sc_ebx,
(unsigned long) uc->sc_edx,
(unsigned long) uc->sc_edi,
(unsigned long) uc->sc_esi,
(unsigned long) uc->sc_ebp,
(unsigned long) uc->sc_esp,
(unsigned long) uc->sc_ss,
(unsigned long) uc->sc_eflags,
(unsigned long) uc->sc_eip,
(unsigned long) uc->sc_cs,
(unsigned long) uc->sc_es,
(unsigned long) uc->sc_fs,
(unsigned long) uc->sc_gs
);
logStackContent((void**)uc->sc_esp);
#endif
#elif defined(__DragonFly__)
serverLog(LL_WARNING,
"\n"
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
(unsigned long) uc->uc_mcontext.mc_rax,
(unsigned long) uc->uc_mcontext.mc_rbx,
(unsigned long) uc->uc_mcontext.mc_rcx,
(unsigned long) uc->uc_mcontext.mc_rdx,
(unsigned long) uc->uc_mcontext.mc_rdi,
(unsigned long) uc->uc_mcontext.mc_rsi,
(unsigned long) uc->uc_mcontext.mc_rbp,
(unsigned long) uc->uc_mcontext.mc_rsp,
(unsigned long) uc->uc_mcontext.mc_r8,
(unsigned long) uc->uc_mcontext.mc_r9,
(unsigned long) uc->uc_mcontext.mc_r10,
(unsigned long) uc->uc_mcontext.mc_r11,
(unsigned long) uc->uc_mcontext.mc_r12,
(unsigned long) uc->uc_mcontext.mc_r13,
(unsigned long) uc->uc_mcontext.mc_r14,
(unsigned long) uc->uc_mcontext.mc_r15,
(unsigned long) uc->uc_mcontext.mc_rip,
(unsigned long) uc->uc_mcontext.mc_rflags,
(unsigned long) uc->uc_mcontext.mc_cs
);
logStackContent((void**)uc->uc_mcontext.mc_rsp);
#else
serverLog(LL_WARNING,
" Dumping of registers not supported for this OS/arch");
@@ -1377,8 +1183,6 @@ void serverLogHexDump(int level, char *descr, void *value, size_t len) {
void watchdogSignalHandler(int sig, siginfo_t *info, void *secret) {
#ifdef HAVE_BACKTRACE
ucontext_t *uc = (ucontext_t*) secret;
#else
(void)secret;
#endif
UNUSED(info);
UNUSED(sig);
+3 -14
View File
@@ -364,7 +364,7 @@ size_t freeMemoryGetNotCountedMemory(void) {
}
}
if (server.aof_state != AOF_OFF) {
overhead += sdsalloc(server.aof_buf)+aofRewriteBufferSize();
overhead += sdslen(server.aof_buf)+aofRewriteBufferSize();
}
return overhead;
}
@@ -444,8 +444,8 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
* Otehrwise if we are over the memory limit, but not enough memory
* was freed to return back under the limit, the function returns C_ERR. */
int freeMemoryIfNeeded(void) {
/* By default replicas should ignore maxmemory
* and just be masters exact copies. */
/* By default slaves should ignore maxmemory and just be masters excat
* copies. */
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
size_t mem_reported, mem_tofree, mem_freed;
@@ -622,14 +622,3 @@ cant_free:
return C_ERR;
}
/* This is a wrapper for freeMemoryIfNeeded() that only really calls the
* function if right now there are the conditions to do so safely:
*
* - There must be no script in timeout condition.
* - Nor we are loading data right now.
*
*/
int freeMemoryIfNeededAndSafe(void) {
if (server.lua_timedout || server.loading) return C_OK;
return freeMemoryIfNeeded();
}
+7 -57
View File
@@ -98,11 +98,6 @@ struct commandHelp {
"Get the current connection name",
9,
"2.6.9" },
{ "CLIENT ID",
"-",
"Returns the client ID for the current connection",
9,
"5.0.0" },
{ "CLIENT KILL",
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [ADDR ip:port] [SKIPME yes/no]",
"Kill the connection of a client",
@@ -128,11 +123,6 @@ struct commandHelp {
"Set the current connection name",
9,
"2.6.9" },
{ "CLIENT UNBLOCK",
"client-id [TIMEOUT|ERROR]",
"Unblock a client blocked in a blocking command from a different connection",
9,
"5.0.0" },
{ "CLUSTER ADDSLOTS",
"slot [slot ...]",
"Assign new hash slots to receiving node",
@@ -155,7 +145,7 @@ struct commandHelp {
"3.0.0" },
{ "CLUSTER FAILOVER",
"[FORCE|TAKEOVER]",
"Forces a replica to perform a manual failover of its master.",
"Forces a slave to perform a manual failover of its master.",
12,
"3.0.0" },
{ "CLUSTER FORGET",
@@ -188,14 +178,9 @@ struct commandHelp {
"Get Cluster config for the node",
12,
"3.0.0" },
{ "CLUSTER REPLICAS",
"node-id",
"List replica nodes of the specified master node",
12,
"5.0.0" },
{ "CLUSTER REPLICATE",
"node-id",
"Reconfigure a node as a replica of the specified master node",
"Reconfigure a node as a slave of the specified master node",
12,
"3.0.0" },
{ "CLUSTER RESET",
@@ -220,7 +205,7 @@ struct commandHelp {
"3.0.0" },
{ "CLUSTER SLAVES",
"node-id",
"List replica nodes of the specified master node",
"List slave nodes of the specified master node",
12,
"3.0.0" },
{ "CLUSTER SLOTS",
@@ -705,12 +690,12 @@ struct commandHelp {
"1.0.0" },
{ "READONLY",
"-",
"Enables read queries for a connection to a cluster replica node",
"Enables read queries for a connection to a cluster slave node",
12,
"3.0.0" },
{ "READWRITE",
"-",
"Disables read queries for a connection to a cluster replica node",
"Disables read queries for a connection to a cluster slave node",
12,
"3.0.0" },
{ "RENAME",
@@ -723,11 +708,6 @@ struct commandHelp {
"Rename a key, only if the new key does not exist",
0,
"1.0.0" },
{ "REPLICAOF",
"host port",
"Make the server a replica of another instance, or promote it as master.",
9,
"5.0.0" },
{ "RESTORE",
"key ttl serialized-value [REPLACE]",
"Create a key using the provided serialized value, previously obtained using DUMP.",
@@ -865,7 +845,7 @@ struct commandHelp {
"1.0.0" },
{ "SLAVEOF",
"host port",
"Make the server a replica of another instance, or promote it as master. Deprecated starting with Redis 5. Use REPLICAOF instead.",
"Make the server a slave of another instance, or promote it as master",
9,
"1.0.0" },
{ "SLOWLOG",
@@ -974,7 +954,7 @@ struct commandHelp {
7,
"2.2.0" },
{ "WAIT",
"numreplicas timeout",
"numslaves timeout",
"Wait for the synchronous replication of all the write commands sent in the context of the current connection",
0,
"3.0.0" },
@@ -983,36 +963,11 @@ struct commandHelp {
"Watch the given keys to determine execution of the MULTI/EXEC block",
7,
"2.2.0" },
{ "XACK",
"key group ID [ID ...]",
"Marks a pending message as correctly processed, effectively removing it from the pending entries list of the consumer group. Return value of the command is the number of messages successfully acknowledged, that is, the IDs we were actually able to resolve in the PEL.",
14,
"5.0.0" },
{ "XADD",
"key ID field string [field string ...]",
"Appends a new entry to a stream",
14,
"5.0.0" },
{ "XCLAIM",
"key group consumer min-idle-time ID [ID ...] [IDLE ms] [TIME ms-unix-time] [RETRYCOUNT count] [force] [justid]",
"Changes (or acquires) ownership of a message in a consumer group, as if the message was delivered to the specified consumer.",
14,
"5.0.0" },
{ "XDEL",
"key ID [ID ...]",
"Removes the specified entries from the stream. Returns the number of items actually deleted, that may be different from the number of IDs passed in case certain IDs do not exist.",
14,
"5.0.0" },
{ "XGROUP",
"[CREATE key groupname id-or-$] [SETID key id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]",
"Create, destroy, and manage consumer groups.",
14,
"5.0.0" },
{ "XINFO",
"[CONSUMERS key groupname] [GROUPS key] [STREAM key] [HELP]",
"Get information on streams and consumer groups",
14,
"5.0.0" },
{ "XLEN",
"key",
"Return the number of entires in a stream",
@@ -1043,11 +998,6 @@ struct commandHelp {
"Return a range of elements in a stream, with IDs matching the specified IDs interval, in reverse order (from greater to smaller IDs) compared to XRANGE",
14,
"5.0.0" },
{ "XTRIM",
"key MAXLEN [~] count",
"Trims the stream to (approximately if '~' is passed) a certain size",
14,
"5.0.0" },
{ "ZADD",
"key [NX|XX] [CH] [INCR] score member [score member ...]",
"Add one or more members to a sorted set, or update its score if it already exists",
+1 -1
View File
@@ -123,7 +123,7 @@ static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
} else {
/* Check for the case where we know we cannot find the value,
* but do know the insert position. */
if (value > _intsetGet(is,max)) {
if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {
if (pos) *pos = intrev32ifbe(is->length);
return 0;
} else if (value < _intsetGet(is,0)) {
+2 -14
View File
@@ -562,21 +562,11 @@ sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
*
* LATENCY HISTORY: return time-latency samples for the specified event.
* LATENCY LATEST: return the latest latency for all the events classes.
* LATENCY DOCTOR: returns a human readable analysis of instance latency.
* LATENCY DOCTOR: returns an human readable analysis of instance latency.
* LATENCY GRAPH: provide an ASCII graph of the latency of the specified event.
* LATENCY RESET: reset data of a specified event or all the data if no event provided.
*/
void latencyCommand(client *c) {
const char *help[] = {
"DOCTOR -- Returns a human readable latency analysis report.",
"GRAPH <event> -- Returns an ASCII latency graph for the event class.",
"HISTORY <event> -- Returns time-latency samples for the event class.",
"LATEST -- Returns the latest latency samples for all events.",
"RESET [event ...] -- Resets latency data of one or more event classes.",
" (default: reset all data for all event classes)",
"HELP -- Prints this help.",
NULL
};
struct latencyTimeSeries *ts;
if (!strcasecmp(c->argv[1]->ptr,"history") && c->argc == 3) {
@@ -621,10 +611,8 @@ NULL
resets += latencyResetEvent(c->argv[j]->ptr);
addReplyLongLong(c,resets);
}
} else if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc >= 2) {
addReplyHelp(c, help);
} else {
addReplySubcommandSyntaxError(c);
addReply(c,shared.syntaxerr);
}
return;
+1 -1
View File
@@ -274,7 +274,7 @@ void lolwut5Command(client *c) {
lwCanvas *canvas = lwDrawSchotter(cols,squares_per_row,squares_per_col);
sds rendered = lwRenderCanvas(canvas);
rendered = sdscat(rendered,
"\nGeorg Nees - schotter, plotter on paper, 1968. Redis ver. ");
"\nGeorg nees - schotter, plotter on paper, 1968. Redis ver. ");
rendered = sdscat(rendered,REDIS_VERSION);
rendered = sdscatlen(rendered,"\n",1);
addReplyBulkSds(c,rendered);
+1 -3
View File
@@ -4221,7 +4221,6 @@ typedef struct RedisModuleTimer {
RedisModule *module; /* Module reference. */
RedisModuleTimerProc callback; /* The callback to invoke on expire. */
void *data; /* Private data for the callback. */
int dbid; /* Database number selected by the original client. */
} RedisModuleTimer;
/* This is the timer handler that is called by the main event loop. We schedule
@@ -4248,7 +4247,7 @@ int moduleTimerHandler(struct aeEventLoop *eventLoop, long long id, void *client
ctx.module = timer->module;
ctx.client = moduleFreeContextReusedClient;
selectDb(ctx.client, timer->dbid);
selectDb(ctx.client, 0);
timer->callback(&ctx,timer->data);
moduleFreeContext(&ctx);
raxRemove(Timers,(unsigned char*)ri.key,ri.key_len,NULL);
@@ -4273,7 +4272,6 @@ RedisModuleTimerID RM_CreateTimer(RedisModuleCtx *ctx, mstime_t period, RedisMod
timer->module = ctx->module;
timer->callback = callback;
timer->data = data;
timer->dbid = ctx->client->db->id;
uint64_t expiretime = ustime()+period*1000;
uint64_t key;
+1 -18
View File
@@ -35,7 +35,6 @@
void initClientMultiState(client *c) {
c->mstate.commands = NULL;
c->mstate.count = 0;
c->mstate.cmd_flags = 0;
}
/* Release all the resources associated with MULTI/EXEC state */
@@ -68,7 +67,6 @@ void queueMultiCommand(client *c) {
for (j = 0; j < c->argc; j++)
incrRefCount(mc->argv[j]);
c->mstate.count++;
c->mstate.cmd_flags |= c->cmd->flags;
}
void discardTransaction(client *c) {
@@ -139,21 +137,6 @@ void execCommand(client *c) {
goto handle_monitor;
}
/* If there are write commands inside the transaction, and this is a read
* only slave, we want to send an error. This happens when the transaction
* was initiated when the instance was a master or a writable replica and
* then the configuration changed (for example instance was turned into
* a replica). */
if (!server.loading && server.masterhost && server.repl_slave_ro &&
!(c->flags & CLIENT_MASTER) && c->mstate.cmd_flags & CMD_WRITE)
{
addReplyError(c,
"Transaction contains write commands but instance "
"is now a read-only slave. EXEC aborted.");
discardTransaction(c);
goto handle_monitor;
}
/* Exec all the queued commands */
unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
orig_argv = c->argv;
@@ -175,7 +158,7 @@ void execCommand(client *c) {
must_propagate = 1;
}
call(c,server.loading ? CMD_CALL_NONE : CMD_CALL_FULL);
call(c,CMD_CALL_FULL|CMD_CALL_NOQUEUE);
/* Commands may alter argc/argv, restore mstate. */
c->mstate.commands[j].argc = c->argc;
+26 -71
View File
@@ -160,32 +160,6 @@ client *createClient(int fd) {
return c;
}
/* This funciton puts the client in the queue of clients that should write
* their output buffers to the socket. Note that it does not *yet* install
* the write handler, to start clients are put in a queue of clients that need
* to write, so we try to do that before returning in the event loop (see the
* handleClientsWithPendingWrites() function).
* If we fail and there is more data to write, compared to what the socket
* buffers can hold, then we'll really install the handler. */
void clientInstallWriteHandler(client *c) {
/* Schedule the client to write the output buffers to the socket only
* if not already done and, for slaves, if the slave can actually receive
* writes at this stage. */
if (!(c->flags & CLIENT_PENDING_WRITE) &&
(c->replstate == REPL_STATE_NONE ||
(c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)))
{
/* Here instead of installing the write handler, we just flag the
* client and put it into a list of clients that have something
* to write to the socket. This way before re-entering the event
* loop, we can try to directly write to the client sockets avoiding
* a system call. We'll only really install the write handler if
* we'll not be able to write the whole reply at once. */
c->flags |= CLIENT_PENDING_WRITE;
listAddNodeHead(server.clients_pending_write,c);
}
}
/* This function is called every time we are going to transmit new data
* to the client. The behavior is the following:
*
@@ -223,9 +197,24 @@ int prepareClientToWrite(client *c) {
if (c->fd <= 0) return C_ERR; /* Fake client for AOF loading. */
/* Schedule the client to write the output buffers to the socket, unless
* it should already be setup to do so (it has already pending data). */
if (!clientHasPendingReplies(c)) clientInstallWriteHandler(c);
/* Schedule the client to write the output buffers to the socket only
* if not already done (there were no pending writes already and the client
* was yet not flagged), and, for slaves, if the slave can actually
* receive writes at this stage. */
if (!clientHasPendingReplies(c) &&
!(c->flags & CLIENT_PENDING_WRITE) &&
(c->replstate == REPL_STATE_NONE ||
(c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)))
{
/* Here instead of installing the write handler, we just flag the
* client and put it into a list of clients that have something
* to write to the socket. This way before re-entering the event
* loop, we can try to directly write to the client sockets avoiding
* a system call. We'll only really install the write handler if
* we'll not be able to write the whole reply at once. */
c->flags |= CLIENT_PENDING_WRITE;
listAddNodeHead(server.clients_pending_write,c);
}
/* Authorize the caller to queue in the output buffer of this client. */
return C_OK;
@@ -365,13 +354,19 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
* Where the master must propagate the first change even if the second
* will produce an error. However it is useful to log such events since
* they are rare and may hint at errors in a script or a bug in Redis. */
if (c->flags & (CLIENT_MASTER|CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) {
if (c->flags & (CLIENT_MASTER|CLIENT_SLAVE)) {
char* to = c->flags & CLIENT_MASTER? "master": "replica";
char* from = c->flags & CLIENT_MASTER? "replica": "master";
char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
"to its %s: '%s' after processing the command "
"'%s'", from, to, s, cmdname);
/* Here we want to panic because when a master is sending an
* error to some slave in the context of replication, this can
* only create some kind of offset or data desynchronization. Better
* to catch it ASAP and crash instead of continuing. */
if (c->flags & CLIENT_SLAVE)
serverPanic("Continuing is unsafe: replication protocol violation.");
}
}
@@ -823,13 +818,6 @@ void unlinkClient(client *c) {
void freeClient(client *c) {
listNode *ln;
/* If a client is protected, yet we need to free it right now, make sure
* to at least use asynchronous freeing. */
if (c->flags & CLIENT_PROTECTED) {
freeClientAsync(c);
return;
}
/* If it is our master that's beging disconnected we should make sure
* to cache the state to try a partial resynchronization later.
*
@@ -1066,10 +1054,6 @@ int handleClientsWithPendingWrites(void) {
c->flags &= ~CLIENT_PENDING_WRITE;
listDelNode(server.clients_pending_write,ln);
/* If a client is protected, don't do anything,
* that may trigger write error or recreate handler. */
if (c->flags & CLIENT_PROTECTED) continue;
/* Try to write buffers to the client socket. */
if (writeToClient(c->fd,c,0) == C_ERR) continue;
@@ -1121,34 +1105,6 @@ void resetClient(client *c) {
}
}
/* This funciton is used when we want to re-enter the event loop but there
* is the risk that the client we are dealing with will be freed in some
* way. This happens for instance in:
*
* * DEBUG RELOAD and similar.
* * When a Lua script is in -BUSY state.
*
* So the function will protect the client by doing two things:
*
* 1) It removes the file events. This way it is not possible that an
* error is signaled on the socket, freeing the client.
* 2) Moreover it makes sure that if the client is freed in a different code
* path, it is not really released, but only marked for later release. */
void protectClient(client *c) {
c->flags |= CLIENT_PROTECTED;
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
}
/* This will undo the client protection done by protectClient() */
void unprotectClient(client *c) {
if (c->flags & CLIENT_PROTECTED) {
c->flags &= ~CLIENT_PROTECTED;
aeCreateFileEvent(server.el,c->fd,AE_READABLE,readQueryFromClient,c);
if (clientHasPendingReplies(c)) clientInstallWriteHandler(c);
}
}
/* Like processMultibulkBuffer(), but for the inline protocol instead of RESP,
* this function consumes the client query buffer and creates a command ready
* to be executed inside the client structure. Returns C_OK if the command
@@ -1464,7 +1420,7 @@ void processInputBuffer(client *c) {
}
/* Trim to pos */
if (server.current_client != NULL && c->qb_pos) {
if (c->qb_pos) {
sdsrange(c->querybuf,c->qb_pos,-1);
c->qb_pos = 0;
}
@@ -2103,7 +2059,6 @@ int checkClientOutputBufferLimits(client *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(client *c) {
if (c->fd == -1) return; /* It is unsafe to free fake clients. */
serverAssert(c->reply_bytes < SIZE_MAX-(1024*64));
if (c->reply_bytes == 0 || c->flags & CLIENT_CLOSE_ASAP) return;
if (checkClientOutputBufferLimits(c)) {
+22 -21
View File
@@ -185,7 +185,7 @@ robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {
/* Duplicate a string object, with the guarantee that the returned object
* has the same encoding as the original one.
*
* This function also guarantees that duplicating a small integer object
* This function also guarantees that duplicating a small integere object
* (or a string object that contains a representation of a small integer)
* will always result in a fresh object that is unshared (refcount == 1).
*
@@ -1011,7 +1011,7 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
mem = 0;
if (server.aof_state != AOF_OFF) {
mem += sdsalloc(server.aof_buf);
mem += sdslen(server.aof_buf);
mem += aofRewriteBufferSize();
}
mh->aof_buffer = mem;
@@ -1285,18 +1285,9 @@ NULL
*
* Usage: MEMORY usage <key> */
void memoryCommand(client *c) {
if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
const char *help[] = {
"DOCTOR - Return memory problems reports.",
"MALLOC-STATS -- Return internal statistics report from the memory allocator.",
"PURGE -- Attempt to purge dirty pages for reclamation by the allocator.",
"STATS -- Return information about the memory usage of the server.",
"USAGE <key> [SAMPLES <count>] -- Return memory in bytes used by <key> and its value. Nested values are sampled up to <count> times (default: 5).",
NULL
};
addReplyHelp(c, help);
} else if (!strcasecmp(c->argv[1]->ptr,"usage") && c->argc >= 3) {
dictEntry *de;
robj *o;
if (!strcasecmp(c->argv[1]->ptr,"usage") && c->argc >= 3) {
long long samples = OBJ_COMPUTE_SIZE_DEF_SAMPLES;
for (int j = 3; j < c->argc; j++) {
if (!strcasecmp(c->argv[j]->ptr,"samples") &&
@@ -1315,12 +1306,10 @@ NULL
return;
}
}
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
addReply(c, shared.nullbulk);
return;
}
size_t usage = objectComputeSize(dictGetVal(de),samples);
usage += sdsAllocSize(dictGetKey(de));
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
size_t usage = objectComputeSize(o,samples);
usage += sdsAllocSize(c->argv[2]->ptr);
usage += sizeof(dictEntry);
addReplyLongLong(c,usage);
} else if (!strcasecmp(c->argv[1]->ptr,"stats") && c->argc == 2) {
@@ -1445,7 +1434,19 @@ NULL
addReply(c, shared.ok);
/* Nothing to do for other allocators. */
#endif
} else if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
addReplyMultiBulkLen(c,5);
addReplyBulkCString(c,
"MEMORY DOCTOR - Outputs memory problems report");
addReplyBulkCString(c,
"MEMORY USAGE <key> [SAMPLES <count>] - Estimate memory usage of key");
addReplyBulkCString(c,
"MEMORY STATS - Show memory usage details");
addReplyBulkCString(c,
"MEMORY PURGE - Ask the allocator to release memory");
addReplyBulkCString(c,
"MEMORY MALLOC-STATS - Show allocator internal stats");
} else {
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try MEMORY HELP", (char*)c->argv[1]->ptr);
addReplyError(c,"Syntax error. Try MEMORY HELP");
}
}
+1 -1
View File
@@ -40,7 +40,7 @@
* 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: 10 bits, free for future use; pads out the remainder of 32 bits */
* extra: 12 bits, free for future use; pads out the remainder of 32 bits */
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
+66 -199
View File
@@ -1,6 +1,6 @@
/* Rax -- A radix tree implementation.
*
* Copyright (c) 2017-2018, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2017, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -51,18 +51,14 @@ void *raxNotFound = (void*)"rax-not-found-pointer";
void raxDebugShowNode(const char *msg, raxNode *n);
/* Turn debugging messages on/off by compiling with RAX_DEBUG_MSG macro on.
* When RAX_DEBUG_MSG is defined by default Rax operations will emit a lot
* of debugging info to the standard output, however you can still turn
* debugging on/off in order to enable it only when you suspect there is an
* operation causing a bug using the function raxSetDebugMsg(). */
#ifdef RAX_DEBUG_MSG
/* Turn debugging messages on/off. */
#if 0
#define debugf(...) \
if (raxDebugMsg) { \
do { \
printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__); \
printf(__VA_ARGS__); \
fflush(stdout); \
}
} while (0);
#define debugnode(msg,n) raxDebugShowNode(msg,n)
#else
@@ -70,16 +66,6 @@ void raxDebugShowNode(const char *msg, raxNode *n);
#define debugnode(msg,n)
#endif
/* By default log debug info if RAX_DEBUG_MSG is defined. */
static int raxDebugMsg = 1;
/* When debug messages are enabled, turn them on/off dynamically. By
* default they are enabled. Set the state to 0 to disable, and 1 to
* re-enable. */
void raxSetDebugMsg(int onoff) {
raxDebugMsg = onoff;
}
/* ------------------------- raxStack functions --------------------------
* The raxStack is a simple stack of pointers that is capable of switching
* from using a stack-allocated array to dynamic heap once a given number of
@@ -148,43 +134,12 @@ static inline void raxStackFree(raxStack *ts) {
* Radix tree implementation
* --------------------------------------------------------------------------*/
/* Return the padding needed in the characters section of a node having size
* 'nodesize'. The padding is needed to store the child pointers to aligned
* addresses. Note that we add 4 to the node size because the node has a four
* bytes header. */
#define raxPadding(nodesize) ((sizeof(void*)-((nodesize+4) % sizeof(void*))) & (sizeof(void*)-1))
/* Return the pointer to the last child pointer in a node. For the compressed
* nodes this is the only child pointer. */
#define raxNodeLastChildPtr(n) ((raxNode**) ( \
((char*)(n)) + \
raxNodeCurrentLength(n) - \
sizeof(raxNode*) - \
(((n)->iskey && !(n)->isnull) ? sizeof(void*) : 0) \
))
/* Return the pointer to the first child pointer. */
#define raxNodeFirstChildPtr(n) ((raxNode**) ( \
(n)->data + \
(n)->size + \
raxPadding((n)->size)))
/* Return the current total size of the node. Note that the second line
* computes the padding after the string of characters, needed in order to
* save pointers to aligned addresses. */
#define raxNodeCurrentLength(n) ( \
sizeof(raxNode)+(n)->size+ \
raxPadding((n)->size)+ \
((n)->iscompr ? sizeof(raxNode*) : sizeof(raxNode*)*(n)->size)+ \
(((n)->iskey && !(n)->isnull)*sizeof(void*)) \
)
/* Allocate a new non compressed node with the specified number of children.
* If datafiled is true, the allocation is made large enough to hold the
* associated data pointer.
* Returns the new node pointer. On out of memory NULL is returned. */
raxNode *raxNewNode(size_t children, int datafield) {
size_t nodesize = sizeof(raxNode)+children+raxPadding(children)+
size_t nodesize = sizeof(raxNode)+children+
sizeof(raxNode*)*children;
if (datafield) nodesize += sizeof(void*);
raxNode *node = rax_malloc(nodesize);
@@ -212,6 +167,13 @@ rax *raxNew(void) {
}
}
/* Return the current total size of the node. */
#define raxNodeCurrentLength(n) ( \
sizeof(raxNode)+(n)->size+ \
((n)->iscompr ? sizeof(raxNode*) : sizeof(raxNode*)*(n)->size)+ \
(((n)->iskey && !(n)->isnull)*sizeof(void*)) \
)
/* realloc the node to make room for auxiliary data in order
* to store an item in that node. On out of memory NULL is returned. */
raxNode *raxReallocForData(raxNode *n, void *data) {
@@ -254,17 +216,18 @@ void *raxGetData(raxNode *n) {
raxNode *raxAddChild(raxNode *n, unsigned char c, raxNode **childptr, raxNode ***parentlink) {
assert(n->iscompr == 0);
size_t curlen = raxNodeCurrentLength(n);
n->size++;
size_t newlen = raxNodeCurrentLength(n);
n->size--; /* For now restore the orignal size. We'll update it only on
success at the end. */
size_t curlen = sizeof(raxNode)+
n->size+
sizeof(raxNode*)*n->size;
size_t newlen;
/* Alloc the new child we will link to 'n'. */
raxNode *child = raxNewNode(0,0);
if (child == NULL) return NULL;
/* Make space in the original node. */
if (n->iskey) curlen += sizeof(void*);
newlen = curlen+sizeof(raxNode*)+1; /* Add 1 char and 1 pointer. */
raxNode *newn = rax_realloc(n,newlen);
if (newn == NULL) {
rax_free(child);
@@ -272,34 +235,14 @@ raxNode *raxAddChild(raxNode *n, unsigned char c, raxNode **childptr, raxNode **
}
n = newn;
/* After the reallocation, we have up to 8/16 (depending on the system
* pointer size, and the required node padding) bytes at the end, that is,
* the additional char in the 'data' section, plus one pointer to the new
* child, plus the padding needed in order to store addresses into aligned
* locations.
/* After the reallocation, we have 5/9 (depending on the system
* pointer size) bytes at the end, that is, the additional char
* in the 'data' section, plus one pointer to the new child:
*
* So if we start with the following node, having "abde" edges.
*
* Note:
* - We assume 4 bytes pointer for simplicity.
* - Each space below corresponds to one byte
*
* [HDR*][abde][Aptr][Bptr][Dptr][Eptr]|AUXP|
*
* After the reallocation we need: 1 byte for the new edge character
* plus 4 bytes for a new child pointer (assuming 32 bit machine).
* However after adding 1 byte to the edge char, the header + the edge
* characters are no longer aligned, so we also need 3 bytes of padding.
* In total the reallocation will add 1+4+3 bytes = 8 bytes:
*
* (Blank bytes are represented by ".")
*
* [HDR*][abde][Aptr][Bptr][Dptr][Eptr]|AUXP|[....][....]
* [numc][abx][ap][bp][xp]|auxp|.....
*
* Let's find where to insert the new child in order to make sure
* it is inserted in-place lexicographically. Assuming we are adding
* a child "c" in our case pos will be = 2 after the end of the following
* loop. */
* it is inserted in-place lexicographically. */
int pos;
for (pos = 0; pos < n->size; pos++) {
if (n->data[pos] > c) break;
@@ -309,81 +252,55 @@ raxNode *raxAddChild(raxNode *n, unsigned char c, raxNode **childptr, raxNode **
* so that we can mess with the other data without overwriting it.
* We will obtain something like that:
*
* [HDR*][abde][Aptr][Bptr][Dptr][Eptr][....][....]|AUXP|
*/
unsigned char *src, *dst;
* [numc][abx][ap][bp][xp].....|auxp| */
unsigned char *src;
if (n->iskey && !n->isnull) {
src = ((unsigned char*)n+curlen-sizeof(void*));
dst = ((unsigned char*)n+newlen-sizeof(void*));
memmove(dst,src,sizeof(void*));
src = n->data+n->size+sizeof(raxNode*)*n->size;
memmove(src+1+sizeof(raxNode*),src,sizeof(void*));
}
/* Compute the "shift", that is, how many bytes we need to move the
* pointers section forward because of the addition of the new child
* byte in the string section. Note that if we had no padding, that
* would be always "1", since we are adding a single byte in the string
* section of the node (where now there is "abde" basically).
*
* However we have padding, so it could be zero, or up to 8.
*
* Another way to think at the shift is, how many bytes we need to
* move child pointers forward *other than* the obvious sizeof(void*)
* needed for the additional pointer itself. */
size_t shift = newlen - curlen - sizeof(void*);
/* We said we are adding a node with edge 'c'. The insertion
* point is between 'b' and 'd', so the 'pos' variable value is
* the index of the first child pointer that we need to move forward
* to make space for our new pointer.
*
/* Now imagine we are adding a node with edge 'c'. The insertion
* point is between 'b' and 'x', so the 'pos' variable value is
* To start, move all the child pointers after the insertion point
* of shift+sizeof(pointer) bytes on the right, to obtain:
* of 1+sizeof(pointer) bytes on the right, to obtain:
*
* [HDR*][abde][Aptr][Bptr][....][....][Dptr][Eptr]|AUXP|
*/
src = n->data+n->size+
raxPadding(n->size)+
sizeof(raxNode*)*pos;
memmove(src+shift+sizeof(raxNode*),src,sizeof(raxNode*)*(n->size-pos));
/* Move the pointers to the left of the insertion position as well. Often
* we don't need to do anything if there was already some padding to use. In
* that case the final destination of the pointers will be the same, however
* in our example there was no pre-existing padding, so we added one byte
* plus thre bytes of padding. After the next memmove() things will look
* like thata:
*
* [HDR*][abde][....][Aptr][Bptr][....][Dptr][Eptr]|AUXP|
*/
if (shift) {
src = (unsigned char*) raxNodeFirstChildPtr(n);
memmove(src+shift,src,sizeof(raxNode*)*pos);
}
* [numc][abx][ap][bp].....[xp]|auxp| */
src = n->data+n->size+sizeof(raxNode*)*pos;
memmove(src+1+sizeof(raxNode*),src,sizeof(raxNode*)*(n->size-pos));
/* Now make the space for the additional char in the data section,
* but also move the pointers before the insertion point to the right
* by shift bytes, in order to obtain the following:
* but also move the pointers before the insertion point in the right
* by 1 byte, in order to obtain the following:
*
* [HDR*][ab.d][e...][Aptr][Bptr][....][Dptr][Eptr]|AUXP|
*/
* [numc][ab.x][ap][bp]....[xp]|auxp| */
src = n->data+pos;
memmove(src+1,src,n->size-pos);
memmove(src+1,src,n->size-pos+sizeof(raxNode*)*pos);
/* We can now set the character and its child node pointer to get:
*
* [HDR*][abcd][e...][Aptr][Bptr][....][Dptr][Eptr]|AUXP|
* [HDR*][abcd][e...][Aptr][Bptr][Cptr][Dptr][Eptr]|AUXP|
*/
* [numc][abcx][ap][bp][cp]....|auxp|
* [numc][abcx][ap][bp][cp][xp]|auxp| */
n->data[pos] = c;
n->size++;
src = (unsigned char*) raxNodeFirstChildPtr(n);
raxNode **childfield = (raxNode**)(src+sizeof(raxNode*)*pos);
raxNode **childfield = (raxNode**)(n->data+n->size+sizeof(raxNode*)*pos);
memcpy(childfield,&child,sizeof(child));
*childptr = child;
*parentlink = childfield;
return n;
}
/* Return the pointer to the last child pointer in a node. For the compressed
* nodes this is the only child pointer. */
#define raxNodeLastChildPtr(n) ((raxNode**) ( \
((char*)(n)) + \
raxNodeCurrentLength(n) - \
sizeof(raxNode*) - \
(((n)->iskey && !(n)->isnull) ? sizeof(void*) : 0) \
))
/* Return the pointer to the first child pointer. */
#define raxNodeFirstChildPtr(n) ((raxNode**)((n)->data+(n)->size))
/* Turn the node 'n', that must be a node without any children, into a
* compressed node representing a set of nodes linked one after the other
* and having exactly one child each. The node can be a key or not: this
@@ -404,7 +321,7 @@ raxNode *raxCompressNode(raxNode *n, unsigned char *s, size_t len, raxNode **chi
if (*child == NULL) return NULL;
/* Make space in the parent node. */
newsize = sizeof(raxNode)+len+raxPadding(len)+sizeof(raxNode*);
newsize = sizeof(raxNode)+len+sizeof(raxNode*);
if (n->iskey) {
data = raxGetData(n); /* To restore it later. */
if (!n->isnull) newsize += sizeof(void*);
@@ -702,14 +619,13 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
raxNode *postfix = NULL;
if (trimmedlen) {
nodesize = sizeof(raxNode)+trimmedlen+raxPadding(trimmedlen)+
sizeof(raxNode*);
nodesize = sizeof(raxNode)+trimmedlen+sizeof(raxNode*);
if (h->iskey && !h->isnull) nodesize += sizeof(void*);
trimmed = rax_malloc(nodesize);
}
if (postfixlen) {
nodesize = sizeof(raxNode)+postfixlen+raxPadding(postfixlen)+
nodesize = sizeof(raxNode)+postfixlen+
sizeof(raxNode*);
postfix = rax_malloc(nodesize);
}
@@ -785,12 +701,11 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
/* Allocate postfix & trimmed nodes ASAP to fail for OOM gracefully. */
size_t postfixlen = h->size - j;
size_t nodesize = sizeof(raxNode)+postfixlen+raxPadding(postfixlen)+
sizeof(raxNode*);
size_t nodesize = sizeof(raxNode)+postfixlen+sizeof(raxNode*);
if (data != NULL) nodesize += sizeof(void*);
raxNode *postfix = rax_malloc(nodesize);
nodesize = sizeof(raxNode)+j+raxPadding(j)+sizeof(raxNode*);
nodesize = sizeof(raxNode)+j+sizeof(raxNode*);
if (h->iskey && !h->isnull) nodesize += sizeof(void*);
raxNode *trimmed = rax_malloc(nodesize);
@@ -960,7 +875,7 @@ raxNode *raxRemoveChild(raxNode *parent, raxNode *child) {
return parent;
}
/* Otherwise we need to scan for the child pointer and memmove()
/* Otherwise we need to scan for the children pointer and memmove()
* accordingly.
*
* 1. To start we seek the first element in both the children
@@ -985,21 +900,13 @@ raxNode *raxRemoveChild(raxNode *parent, raxNode *child) {
debugf("raxRemoveChild tail len: %d\n", taillen);
memmove(e,e+1,taillen);
/* Compute the shift, that is the amount of bytes we should move our
* child pointers to the left, since the removal of one edge character
* and the corresponding padding change, may change the layout.
* We just check if in the old version of the node there was at the
* end just a single byte and all padding: in that case removing one char
* will remove a whole sizeof(void*) word. */
size_t shift = ((parent->size+4) % sizeof(void*)) == 1 ? sizeof(void*) : 0;
/* Since we have one data byte less, also child pointers start one byte
* before now. */
memmove(((char*)cp)-1,cp,(parent->size-taillen-1)*sizeof(raxNode**));
/* Move the children pointers before the deletion point. */
if (shift)
memmove(((char*)cp)-shift,cp,(parent->size-taillen-1)*sizeof(raxNode**));
/* Move the remaining "tail" pointers at the right position as well. */
/* Move the remaining "tail" pointer at the right position as well. */
size_t valuelen = (parent->iskey && !parent->isnull) ? sizeof(void*) : 0;
memmove(((char*)c)-shift,c+1,taillen*sizeof(raxNode**)+valuelen);
memmove(((char*)c)-1,c+1,taillen*sizeof(raxNode**)+valuelen);
/* 4. Update size. */
parent->size--;
@@ -1165,7 +1072,7 @@ int raxRemove(rax *rax, unsigned char *s, size_t len, void **old) {
if (nodes > 1) {
/* If we can compress, create the new node and populate it. */
size_t nodesize =
sizeof(raxNode)+comprsize+raxPadding(comprsize)+sizeof(raxNode*);
sizeof(raxNode)+comprsize+sizeof(raxNode*);
raxNode *new = rax_malloc(nodesize);
/* An out of memory here just means we cannot optimize this
* node, but the tree is left in a consistent state. */
@@ -1406,7 +1313,7 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
}
}
/* Seek the grestest key in the subtree at the current node. Return 0 on
/* Seek the greatest key in the subtree at the current node. Return 0 on
* out of memory, otherwise 1. This is an helper function for different
* iteration functions below. */
int raxSeekGreatest(raxIterator *it) {
@@ -1886,7 +1793,6 @@ void raxShow(rax *rax) {
/* Used by debugnode() macro to show info about a given node. */
void raxDebugShowNode(const char *msg, raxNode *n) {
if (raxDebugMsg == 0) return;
printf("%s: %p [%.*s] key:%d size:%d children:",
msg, (void*)n, (int)n->size, (char*)n->data, n->iskey, n->size);
int numcld = n->iscompr ? 1 : n->size;
@@ -1901,43 +1807,4 @@ void raxDebugShowNode(const char *msg, raxNode *n) {
fflush(stdout);
}
/* Touch all the nodes of a tree returning a check sum. This is useful
* in order to make Valgrind detect if there is something wrong while
* reading the data structure.
*
* This function was used in order to identify Rax bugs after a big refactoring
* using this technique:
*
* 1. The rax-test is executed using Valgrind, adding a printf() so that for
* the fuzz tester we see what iteration in the loop we are in.
* 2. After every modification of the radix tree made by the fuzz tester
* in rax-test.c, we add a call to raxTouch().
* 3. Now as soon as an operation will corrupt the tree, raxTouch() will
* detect it (via Valgrind) immediately. We can add more calls to narrow
* the state.
* 4. At this point a good idea is to enable Rax debugging messages immediately
* before the moment the tree is corrupted, to see what happens.
*/
unsigned long raxTouch(raxNode *n) {
debugf("Touching %p\n", (void*)n);
unsigned long sum = 0;
if (n->iskey) {
sum += (unsigned long)raxGetData(n);
}
int numchildren = n->iscompr ? 1 : n->size;
raxNode **cp = raxNodeFirstChildPtr(n);
int count = 0;
for (int i = 0; i < numchildren; i++) {
if (numchildren > 1) {
sum += (long)n->data[i];
}
raxNode *child;
memcpy(&child,cp,sizeof(child));
if (child == (void*)0x65d1760) count++;
if (count > 1) exit(1);
sum += raxTouch(child);
cp++;
}
return sum;
}
+3 -35
View File
@@ -1,33 +1,3 @@
/* Rax -- A radix tree implementation.
*
* Copyright (c) 2017-2018, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef RAX_H
#define RAX_H
@@ -107,16 +77,16 @@ typedef struct raxNode {
* Note how the character is not stored in the children but in the
* edge of the parents:
*
* [header iscompr=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?)
* [header strlen=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?)
*
* if node is compressed (iscompr bit is 1) the node has 1 children.
* if node is compressed (strlen != 0) the node has 1 children.
* In that case the 'size' bytes of the string stored immediately at
* the start of the data section, represent a sequence of successive
* nodes linked one after the other, for which only the last one in
* the sequence is actually represented as a node, and pointed to by
* the current compressed node.
*
* [header iscompr=1][xyz][z-ptr](value-ptr?)
* [header strlen=3][xyz][z-ptr](value-ptr?)
*
* Both compressed and not compressed nodes can represent a key
* with associated data in the radix tree at any level (not just terminal
@@ -206,8 +176,6 @@ void raxStop(raxIterator *it);
int raxEOF(raxIterator *it);
void raxShow(rax *rax);
uint64_t raxSize(rax *rax);
unsigned long raxTouch(raxNode *n);
void raxSetDebugMsg(int onoff);
/* Internal API. May be used by the node callback in order to access rax nodes
* in a low level way, so this function is exported as well. */
-3
View File
@@ -1645,9 +1645,6 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
* node: the entries inside the listpack itself are delta-encoded
* relatively to this ID. */
sds nodekey = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL);
if (nodekey == NULL) {
rdbExitReportCorruptRDB("Stream master ID loading failed: invalid encoding or I/O error.");
}
if (sdslen(nodekey) != sizeof(streamID)) {
rdbExitReportCorruptRDB("Stream node key entry is not the "
"size of a stream ID");
+106 -452
View File
@@ -67,7 +67,6 @@
#define REDIS_CLI_HISTFILE_DEFAULT ".rediscli_history"
#define REDIS_CLI_RCFILE_ENV "REDISCLI_RCFILE"
#define REDIS_CLI_RCFILE_DEFAULT ".redisclirc"
#define REDIS_CLI_AUTH_ENV "REDISCLI_AUTH"
#define CLUSTER_MANAGER_SLOTS 16384
#define CLUSTER_MANAGER_MIGRATE_TIMEOUT 60000
@@ -117,7 +116,6 @@
#define CLUSTER_MANAGER_CMD_FLAG_REPLACE 1 << 6
#define CLUSTER_MANAGER_CMD_FLAG_COPY 1 << 7
#define CLUSTER_MANAGER_CMD_FLAG_COLOR 1 << 8
#define CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS 1 << 9
#define CLUSTER_MANAGER_OPT_GETFRIENDS 1 << 0
#define CLUSTER_MANAGER_OPT_COLD 1 << 1
@@ -1379,9 +1377,6 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i],"--cluster-use-empty-masters")) {
config.cluster_manager_command.flags |=
CLUSTER_MANAGER_CMD_FLAG_EMPTYMASTER;
} else if (!strcmp(argv[i],"--cluster-search-multiple-owners")) {
config.cluster_manager_command.flags |=
CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
} else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
sds version = cliVersion();
printf("redis-cli %s\n", version);
@@ -1424,14 +1419,6 @@ static int parseOptions(int argc, char **argv) {
return i;
}
static void parseEnv() {
/* Set auth from env, but do not overwrite CLI arguments if passed */
char *auth = getenv(REDIS_CLI_AUTH_ENV);
if (auth != NULL && config.auth == NULL) {
config.auth = auth;
}
}
static sds readArgFromStdin(void) {
char buf[1024];
sds arg = sdsempty();
@@ -1459,9 +1446,6 @@ static void usage(void) {
" -p <port> Server port (default: 6379).\n"
" -s <socket> Server socket (overrides hostname and port).\n"
" -a <password> Password to use when connecting to the server.\n"
" You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
" variable to pass this password more safely\n"
" (if both are used, this argument takes predecence).\n"
" -u <uri> Server URI.\n"
" -r <repeat> Execute specified command N times.\n"
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
@@ -1850,7 +1834,7 @@ static int evalMode(int argc, char **argv) {
if (eval_ldb) {
if (!config.eval_ldb) {
/* If the debugging session ended immediately, there was an
* error compiling the script. Show it and they don't enter
* error compiling the script. Show it and don't enter
* the REPL at all. */
printf("Eval debugging session can't start:\n");
cliReadReply(0);
@@ -1933,7 +1917,6 @@ static dictType clusterManagerDictType = {
};
typedef int clusterManagerCommandProc(int argc, char **argv);
typedef int (*clusterManagerOnReplyError)(redisReply *reply, int bulk_idx);
/* Cluster Manager helper functions */
@@ -1995,17 +1978,14 @@ typedef struct clusterManagerCommandDef {
clusterManagerCommandDef clusterManagerCommands[] = {
{"create", clusterManagerCommandCreate, -2, "host1:port1 ... hostN:portN",
"replicas <arg>"},
{"check", clusterManagerCommandCheck, -1, "host:port",
"search-multiple-owners"},
{"check", clusterManagerCommandCheck, -1, "host:port", NULL},
{"info", clusterManagerCommandInfo, -1, "host:port", NULL},
{"fix", clusterManagerCommandFix, -1, "host:port",
"search-multiple-owners"},
{"fix", clusterManagerCommandFix, -1, "host:port", NULL},
{"reshard", clusterManagerCommandReshard, -1, "host:port",
"from <arg>,to <arg>,slots <arg>,yes,timeout <arg>,pipeline <arg>,"
"replace"},
"from <arg>,to <arg>,slots <arg>,yes,timeout <arg>,pipeline <arg>"},
{"rebalance", clusterManagerCommandRebalance, -1, "host:port",
"weight <node1=w1...nodeN=wN>,use-empty-masters,"
"timeout <arg>,simulate,pipeline <arg>,threshold <arg>,replace"},
"timeout <arg>,simulate,pipeline <arg>,threshold <arg>"},
{"add-node", clusterManagerCommandAddNode, 2,
"new_host:new_port existing_host:existing_port", "slave,master-id <arg>"},
{"del-node", clusterManagerCommandDeleteNode, 2, "host:port node_id",NULL},
@@ -2196,44 +2176,6 @@ static int clusterManagerCheckRedisReply(clusterManagerNode *n,
return 1;
}
/* Execute MULTI command on a cluster node. */
static int clusterManagerStartTransaction(clusterManagerNode *node) {
redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "MULTI");
int success = clusterManagerCheckRedisReply(node, reply, NULL);
if (reply) freeReplyObject(reply);
return success;
}
/* Execute EXEC command on a cluster node. */
static int clusterManagerExecTransaction(clusterManagerNode *node,
clusterManagerOnReplyError onerror)
{
redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "EXEC");
int success = clusterManagerCheckRedisReply(node, reply, NULL);
if (success) {
if (reply->type != REDIS_REPLY_ARRAY) {
success = 0;
goto cleanup;
}
size_t i;
for (i = 0; i < reply->elements; i++) {
redisReply *r = reply->element[i];
char *err = NULL;
success = clusterManagerCheckRedisReply(node, r, &err);
if (!success && onerror) success = onerror(r, i);
if (err) {
if (!success)
CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
zfree(err);
}
if (!success) break;
}
}
cleanup:
if (reply) freeReplyObject(reply);
return success;
}
static int clusterManagerNodeConnect(clusterManagerNode *node) {
if (node->context) redisFree(node->context);
node->context = redisConnect(node->ip, node->port);
@@ -2784,7 +2726,8 @@ static int clusterManagerSetSlot(clusterManagerNode *node1,
if (err != NULL) {
*err = zmalloc((reply->len + 1) * sizeof(char));
strcpy(*err, reply->str);
} else CLUSTER_MANAGER_PRINT_REPLY_ERROR(node1, reply->str);
CLUSTER_MANAGER_PRINT_REPLY_ERROR(node1, err);
}
goto cleanup;
}
cleanup:
@@ -2792,84 +2735,6 @@ cleanup:
return success;
}
static int clusterManagerClearSlotStatus(clusterManagerNode *node, int slot) {
redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
"CLUSTER SETSLOT %d %s", slot, "STABLE");
int success = clusterManagerCheckRedisReply(node, reply, NULL);
if (reply) freeReplyObject(reply);
return success;
}
static int clusterManagerDelSlot(clusterManagerNode *node, int slot,
int ignore_unassigned_err)
{
redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
"CLUSTER DELSLOTS %d", slot);
char *err = NULL;
int success = clusterManagerCheckRedisReply(node, reply, &err);
if (!success && reply && reply->type == REDIS_REPLY_ERROR &&
ignore_unassigned_err &&
strstr(reply->str, "already unassigned") != NULL) success = 1;
if (!success && err != NULL) {
CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
zfree(err);
}
if (reply) freeReplyObject(reply);
return success;
}
static int clusterManagerAddSlot(clusterManagerNode *node, int slot) {
redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
"CLUSTER ADDSLOTS %d", slot);
int success = clusterManagerCheckRedisReply(node, reply, NULL);
if (reply) freeReplyObject(reply);
return success;
}
static signed int clusterManagerCountKeysInSlot(clusterManagerNode *node,
int slot)
{
redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
"CLUSTER COUNTKEYSINSLOT %d", slot);
int count = -1;
int success = clusterManagerCheckRedisReply(node, reply, NULL);
if (success && reply->type == REDIS_REPLY_INTEGER) count = reply->integer;
if (reply) freeReplyObject(reply);
return count;
}
static int clusterManagerBumpEpoch(clusterManagerNode *node) {
redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER BUMPEPOCH");
int success = clusterManagerCheckRedisReply(node, reply, NULL);
if (reply) freeReplyObject(reply);
return success;
}
static int clusterManagerIgnoreUnassignedErr(redisReply *reply, int bulk_idx) {
if (bulk_idx == 0 && reply) {
if (reply->type == REDIS_REPLY_ERROR)
return strstr(reply->str, "already unassigned") != NULL;
}
return 0;
}
static int clusterManagerSetSlotOwner(clusterManagerNode *owner,
int slot,
int do_clear)
{
int success = clusterManagerStartTransaction(owner);
if (!success) return 0;
/* Ensure the slot is not already assigned. */
clusterManagerDelSlot(owner, slot, 1);
/* Add the slot and bump epoch. */
clusterManagerAddSlot(owner, slot);
if (do_clear) clusterManagerClearSlotStatus(owner, slot);
clusterManagerBumpEpoch(owner);
success = clusterManagerExecTransaction(owner,
clusterManagerIgnoreUnassignedErr);
return success;
}
/* Migrate keys taken from reply->elements. It returns the reply from the
* MIGRATE command, or NULL if something goes wrong. If the argument 'dots'
* is not NULL, a dot will be printed for every migrated key. */
@@ -2950,8 +2815,8 @@ static int clusterManagerMigrateKeysInSlot(clusterManagerNode *source,
char **err)
{
int success = 1;
int replace_existing_keys = (config.cluster_manager_command.flags &
(CLUSTER_MANAGER_CMD_FLAG_FIX | CLUSTER_MANAGER_CMD_FLAG_REPLACE));
int do_fix = (config.cluster_manager_command.flags &
CLUSTER_MANAGER_CMD_FLAG_FIX);
while (1) {
char *dots = NULL;
redisReply *reply = NULL, *migrate_reply = NULL;
@@ -2965,7 +2830,7 @@ static int clusterManagerMigrateKeysInSlot(clusterManagerNode *source,
if (err != NULL) {
*err = zmalloc((reply->len + 1) * sizeof(char));
strcpy(*err, reply->str);
CLUSTER_MANAGER_PRINT_REPLY_ERROR(source, *err);
CLUSTER_MANAGER_PRINT_REPLY_ERROR(source, err);
}
goto next;
}
@@ -2982,23 +2847,15 @@ static int clusterManagerMigrateKeysInSlot(clusterManagerNode *source,
dots);
if (migrate_reply == NULL) goto next;
if (migrate_reply->type == REDIS_REPLY_ERROR) {
int is_busy = strstr(migrate_reply->str, "BUSYKEY") != NULL;
int not_served = strstr(migrate_reply->str, "slot not served") != NULL;
if (replace_existing_keys && (is_busy || not_served)) {
/* If the key already exists, try to migrate keys
* adding REPLACE option.
* If the key's slot is not served, try to assign slot
* to the target node. */
if (not_served)
clusterManagerSetSlot(source, target, slot, "node", NULL);
if (do_fix && strstr(migrate_reply->str, "BUSYKEY")) {
clusterManagerLogWarn("*** Target key exists. "
"Replacing it for FIX.\n");
freeReplyObject(migrate_reply);
/* Try to migrate keys adding REPLACE option. */
migrate_reply = clusterManagerMigrateKeysInReply(source,
target,
reply,
is_busy,
timeout,
1, timeout,
NULL);
success = (migrate_reply != NULL &&
migrate_reply->type != REDIS_REPLY_ERROR);
@@ -3084,7 +2941,7 @@ static int clusterManagerMoveSlot(clusterManagerNode *source,
if (err != NULL) {
*err = zmalloc((r->len + 1) * sizeof(char));
strcpy(*err, r->str);
CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, *err);
CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, err);
}
}
freeReplyObject(r);
@@ -3444,8 +3301,8 @@ static sds clusterManagerGetConfigSignature(clusterManagerNode *node) {
nodename = token;
tot_size = (p - token);
name_len = tot_size++; // Make room for ':' in tot_size
}
if (++i == 8) break;
} else if (i == 8) break;
i++;
}
if (i != 8) continue;
if (nodename == NULL) continue;
@@ -3484,7 +3341,7 @@ static sds clusterManagerGetConfigSignature(clusterManagerNode *node) {
char *sp = cfg + name_len;
*(sp++) = ':';
for (i = 0; i < c; i++) {
if (i > 0) *(sp++) = ',';
if (i > 0) *(sp++) = '|';
int slen = strlen(slots[i]);
memcpy(sp, slots[i], slen);
sp += slen;
@@ -3642,34 +3499,6 @@ static clusterManagerNode *clusterManagerNodeWithLeastReplicas() {
return node;
}
/* This fucntion returns a random master node, return NULL if none */
static clusterManagerNode *clusterManagerNodeMasterRandom() {
int master_count = 0;
int idx;
listIter li;
listNode *ln;
listRewind(cluster_manager.nodes, &li);
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
master_count++;
}
srand(time(NULL));
idx = rand() % master_count;
listRewind(cluster_manager.nodes, &li);
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
if (!idx--) {
return n;
}
}
/* Can not be reached */
return NULL;
}
static int clusterManagerFixSlotsCoverage(char *all_slots) {
int i, fixed = 0;
list *none = NULL, *single = NULL, *multi = NULL;
@@ -3742,22 +3571,26 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
"across the cluster:\n");
clusterManagerPrintSlotsList(none);
if (confirmWithYes("Fix these slots by covering with a random node?")){
srand(time(NULL));
listIter li;
listNode *ln;
listRewind(none, &li);
while ((ln = listNext(&li)) != NULL) {
sds slot = ln->value;
int s = atoi(slot);
clusterManagerNode *n = clusterManagerNodeMasterRandom();
long idx = (long) (rand() % listLength(cluster_manager.nodes));
listNode *node_n = listIndex(cluster_manager.nodes, idx);
assert(node_n != NULL);
clusterManagerNode *n = node_n->value;
clusterManagerLogInfo(">>> Covering slot %s with %s:%d\n",
slot, n->ip, n->port);
if (!clusterManagerSetSlotOwner(n, s, 0)) {
fixed = -1;
goto cleanup;
}
redisReply *r = CLUSTER_MANAGER_COMMAND(n,
"CLUSTER ADDSLOTS %s", slot);
if (!clusterManagerCheckRedisReply(n, r, NULL)) fixed = -1;
if (r) freeReplyObject(r);
if (fixed < 0) goto cleanup;
/* Since CLUSTER ADDSLOTS succeeded, we also update the slot
* info into the node struct, in order to keep it synced */
n->slots[s] = 1;
n->slots[atoi(slot)] = 1;
fixed++;
}
}
@@ -3765,7 +3598,7 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
/* Handle case "2": keys only in one node. */
if (listLength(single) > 0) {
printf("The following uncovered slots have keys in just one node:\n");
printf("The following uncovered slots have keys in just one node:\n");
clusterManagerPrintSlotsList(single);
if (confirmWithYes("Fix these slots by covering with those nodes?")){
listIter li;
@@ -3773,7 +3606,6 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
listRewind(single, &li);
while ((ln = listNext(&li)) != NULL) {
sds slot = ln->value;
int s = atoi(slot);
dictEntry *entry = dictFind(clusterManagerUncoveredSlots, slot);
assert(entry != NULL);
list *nodes = (list *) dictGetVal(entry);
@@ -3782,10 +3614,11 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
clusterManagerNode *n = fn->value;
clusterManagerLogInfo(">>> Covering slot %s with %s:%d\n",
slot, n->ip, n->port);
if (!clusterManagerSetSlotOwner(n, s, 0)) {
fixed = -1;
goto cleanup;
}
redisReply *r = CLUSTER_MANAGER_COMMAND(n,
"CLUSTER ADDSLOTS %s", slot);
if (!clusterManagerCheckRedisReply(n, r, NULL)) fixed = -1;
if (r) freeReplyObject(r);
if (fixed < 0) goto cleanup;
/* Since CLUSTER ADDSLOTS succeeded, we also update the slot
* info into the node struct, in order to keep it synced */
n->slots[atoi(slot)] = 1;
@@ -3818,10 +3651,16 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
clusterManagerLogInfo(">>> Covering slot %s moving keys "
"to %s:%d\n", slot,
target->ip, target->port);
if (!clusterManagerSetSlotOwner(target, s, 1)) {
fixed = -1;
goto cleanup;
}
redisReply *r = CLUSTER_MANAGER_COMMAND(target,
"CLUSTER ADDSLOTS %s", slot);
if (!clusterManagerCheckRedisReply(target, r, NULL)) fixed = -1;
if (r) freeReplyObject(r);
if (fixed < 0) goto cleanup;
r = CLUSTER_MANAGER_COMMAND(target,
"CLUSTER SETSLOT %s %s", slot, "STABLE");
if (!clusterManagerCheckRedisReply(target, r, NULL)) fixed = -1;
if (r) freeReplyObject(r);
if (fixed < 0) goto cleanup;
/* Since CLUSTER ADDSLOTS succeeded, we also update the slot
* info into the node struct, in order to keep it synced */
target->slots[atoi(slot)] = 1;
@@ -3831,16 +3670,16 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
while ((nln = listNext(&nli)) != NULL) {
clusterManagerNode *src = nln->value;
if (src == target) continue;
/* Assign the slot to target node in the source node. */
if (!clusterManagerSetSlot(src, target, s, "NODE", NULL))
fixed = -1;
if (fixed < 0) goto cleanup;
/* Set the source node in 'importing' state
* (even if we will actually migrate keys away)
* in order to avoid receiving redirections
* for MIGRATE. */
if (!clusterManagerSetSlot(src, target, s,
"IMPORTING", NULL)) fixed = -1;
redisReply *r = CLUSTER_MANAGER_COMMAND(src,
"CLUSTER SETSLOT %s %s %s", slot,
"IMPORTING", target->name);
if (!clusterManagerCheckRedisReply(target, r, NULL))
fixed = -1;
if (r) freeReplyObject(r);
if (fixed < 0) goto cleanup;
int opts = CLUSTER_MANAGER_OPT_VERBOSE |
CLUSTER_MANAGER_OPT_COLD;
@@ -3848,9 +3687,6 @@ static int clusterManagerFixSlotsCoverage(char *all_slots) {
fixed = -1;
goto cleanup;
}
if (!clusterManagerClearSlotStatus(src, s))
fixed = -1;
if (fixed < 0) goto cleanup;
}
fixed++;
}
@@ -3884,22 +3720,11 @@ static int clusterManagerFixOpenSlot(int slot) {
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
if (n->slots[slot]) listAddNodeTail(owners, n);
else {
redisReply *r = CLUSTER_MANAGER_COMMAND(n,
"CLUSTER COUNTKEYSINSLOT %d", slot);
success = clusterManagerCheckRedisReply(n, r, NULL);
if (success && r->integer > 0) {
clusterManagerLogWarn("*** Found keys about slot %d "
"in non-owner node %s:%d!\n", slot,
n->ip, n->port);
listAddNodeTail(owners, n);
}
if (r) freeReplyObject(r);
if (!success) goto cleanup;
if (n->slots[slot]) {
if (owner == NULL) owner = n;
listAddNodeTail(owners, n);
}
}
if (listLength(owners) == 1) owner = listFirst(owners)->value;
listRewind(cluster_manager.nodes, &li);
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
@@ -3910,7 +3735,7 @@ static int clusterManagerFixOpenSlot(int slot) {
sds migrating_slot = n->migrating[i];
if (atoi(migrating_slot) == slot) {
char *sep = (listLength(migrating) == 0 ? "" : ",");
migrating_str = sdscatfmt(migrating_str, "%s%s:%u",
migrating_str = sdscatfmt(migrating_str, "%s%S:%u",
sep, n->ip, n->port);
listAddNodeTail(migrating, n);
is_migrating = 1;
@@ -3923,7 +3748,7 @@ static int clusterManagerFixOpenSlot(int slot) {
sds importing_slot = n->importing[i];
if (atoi(importing_slot) == slot) {
char *sep = (listLength(importing) == 0 ? "" : ",");
importing_str = sdscatfmt(importing_str, "%s%s:%u",
importing_str = sdscatfmt(importing_str, "%s%S:%u",
sep, n->ip, n->port);
listAddNodeTail(importing, n);
is_importing = 1;
@@ -3942,20 +3767,15 @@ static int clusterManagerFixOpenSlot(int slot) {
clusterManagerLogWarn("*** Found keys about slot %d "
"in node %s:%d!\n", slot, n->ip,
n->port);
char *sep = (listLength(importing) == 0 ? "" : ",");
importing_str = sdscatfmt(importing_str, "%s%S:%u",
sep, n->ip, n->port);
listAddNodeTail(importing, n);
}
if (r) freeReplyObject(r);
if (!success) goto cleanup;
}
}
if (sdslen(migrating_str) > 0)
printf("Set as migrating in: %s\n", migrating_str);
if (sdslen(importing_str) > 0)
printf("Set as importing in: %s\n", importing_str);
/* If there is no slot owner, set as owner the node with the biggest
printf("Set as migrating in: %s\n", migrating_str);
printf("Set as importing in: %s\n", importing_str);
/* If there is no slot owner, set as owner the slot with the biggest
* number of keys, among the set of migrating / importing nodes. */
if (owner == NULL) {
clusterManagerLogInfo(">>> Nobody claims ownership, "
@@ -3973,9 +3793,15 @@ static int clusterManagerFixOpenSlot(int slot) {
// Use ADDSLOTS to assign the slot.
clusterManagerLogWarn("*** Configuring %s:%d as the slot owner\n",
owner->ip, owner->port);
success = clusterManagerClearSlotStatus(owner, slot);
redisReply *reply = CLUSTER_MANAGER_COMMAND(owner, "CLUSTER "
"SETSLOT %d %s",
slot, "STABLE");
success = clusterManagerCheckRedisReply(owner, reply, NULL);
if (reply) freeReplyObject(reply);
if (!success) goto cleanup;
success = clusterManagerSetSlotOwner(owner, slot, 0);
reply = CLUSTER_MANAGER_COMMAND(owner, "CLUSTER ADDSLOTS %d", slot);
success = clusterManagerCheckRedisReply(owner, reply, NULL);
if (reply) freeReplyObject(reply);
if (!success) goto cleanup;
/* Since CLUSTER ADDSLOTS succeeded, we also update the slot
* info into the node struct, in order to keep it synced */
@@ -3983,7 +3809,9 @@ static int clusterManagerFixOpenSlot(int slot) {
/* Make sure this information will propagate. Not strictly needed
* since there is no past owner, so all the other nodes will accept
* whatever epoch this node will claim the slot with. */
success = clusterManagerBumpEpoch(owner);
reply = CLUSTER_MANAGER_COMMAND(owner, "CLUSTER BUMPEPOCH");
success = clusterManagerCheckRedisReply(owner, reply, NULL);
if (reply) freeReplyObject(reply);
if (!success) goto cleanup;
/* Remove the owner from the list of migrating/importing
* nodes. */
@@ -3999,38 +3827,32 @@ static int clusterManagerFixOpenSlot(int slot) {
* in migrating state, since migrating is a valid state only for
* slot owners. */
if (listLength(owners) > 1) {
/* Owner cannot be NULL at this point, since if there are more owners,
* the owner has been set in the previous condition (owner == NULL). */
assert(owner != NULL);
owner = clusterManagerGetNodeWithMostKeysInSlot(owners, slot, NULL);
listRewind(owners, &li);
redisReply *reply = NULL;
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
if (n == owner) continue;
success = clusterManagerDelSlot(n, slot, 1);
if (!success) goto cleanup;
n->slots[slot] = 0;
/* Assign the slot to the owner in the node 'n' configuration.' */
success = clusterManagerSetSlot(n, owner, slot, "node", NULL);
reply = CLUSTER_MANAGER_COMMAND(n, "CLUSTER DELSLOT %d", slot);
success = clusterManagerCheckRedisReply(n, reply, NULL);
if (reply) freeReplyObject(reply);
if (!success) goto cleanup;
success = clusterManagerSetSlot(n, owner, slot, "importing", NULL);
if (!success) goto cleanup;
/* Avoid duplicates. */
clusterManagerRemoveNodeFromList(importing, n);
clusterManagerRemoveNodeFromList(importing, n); //Avoid duplicates
listAddNodeTail(importing, n);
/* Ensure that the node is not in the migrating list. */
clusterManagerRemoveNodeFromList(migrating, n);
}
reply = CLUSTER_MANAGER_COMMAND(owner, "CLUSTER BUMPEPOCH");
success = clusterManagerCheckRedisReply(owner, reply, NULL);
if (reply) freeReplyObject(reply);
if (!success) goto cleanup;
}
int move_opts = CLUSTER_MANAGER_OPT_VERBOSE;
/* Case 1: The slot is in migrating state in one node, and in
* importing state in 1 node. That's trivial to address. */
/* Case 1: The slot is in migrating state in one slot, and in
* importing state in 1 slot. That's trivial to address. */
if (listLength(migrating) == 1 && listLength(importing) == 1) {
clusterManagerNode *src = listFirst(migrating)->value;
clusterManagerNode *dst = listFirst(importing)->value;
clusterManagerLogInfo(">>> Case 1: Moving slot %d from "
"%s:%d to %s:%d\n", slot,
src->ip, src->port, dst->ip, dst->port);
move_opts |= CLUSTER_MANAGER_OPT_UPDATE;
success = clusterManagerMoveSlot(src, dst, slot, move_opts, NULL);
}
/* Case 2: There are multiple nodes that claim the slot as importing,
@@ -4038,7 +3860,7 @@ static int clusterManagerFixOpenSlot(int slot) {
* the slot. In this case we just move all the keys to the owner
* according to the configuration. */
else if (listLength(migrating) == 0 && listLength(importing) > 0) {
clusterManagerLogInfo(">>> Case 2: Moving all the %d slot keys to its "
clusterManagerLogInfo(">>> Moving all the %d slot keys to its "
"owner %s:%d\n", slot, owner->ip, owner->port);
move_opts |= CLUSTER_MANAGER_OPT_COLD;
listRewind(importing, &li);
@@ -4049,117 +3871,38 @@ static int clusterManagerFixOpenSlot(int slot) {
if (!success) goto cleanup;
clusterManagerLogInfo(">>> Setting %d as STABLE in "
"%s:%d\n", slot, n->ip, n->port);
success = clusterManagerClearSlotStatus(n, slot);
redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER SETSLOT %d %s",
slot, "STABLE");
success = clusterManagerCheckRedisReply(n, r, NULL);
if (r) freeReplyObject(r);
if (!success) goto cleanup;
}
/* Since the slot has been moved in "cold" mode, ensure that all the
* other nodes update their own configuration about the slot itself. */
listRewind(cluster_manager.nodes, &li);
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
if (n == owner) continue;
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
success = clusterManagerSetSlot(n, owner, slot, "NODE", NULL);
if (!success) goto cleanup;
}
}
/* Case 3: The slot is in migrating state in one node but multiple
* other nodes claim to be in importing state and don't have any key in
* the slot. We search for the importing node having the same ID as
* the destination node of the migrating node.
* In that case we move the slot from the migrating node to this node and
* we close the importing states on all the other importing nodes.
* If no importing node has the same ID as the destination node of the
* migrating node, the slot's state is closed on both the migrating node
* and the importing nodes. */
else if (listLength(migrating) == 1 && listLength(importing) > 1) {
int try_to_fix = 1;
clusterManagerNode *src = listFirst(migrating)->value;
clusterManagerNode *dst = NULL;
sds target_id = NULL;
for (int i = 0; i < src->migrating_count; i += 2) {
sds migrating_slot = src->migrating[i];
if (atoi(migrating_slot) == slot) {
target_id = src->migrating[i + 1];
break;
}
}
assert(target_id != NULL);
listIter li;
listNode *ln;
listRewind(importing, &li);
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
int count = clusterManagerCountKeysInSlot(n, slot);
if (count > 0) {
try_to_fix = 0;
break;
}
if (strcmp(n->name, target_id) == 0) dst = n;
}
if (!try_to_fix) goto unhandled_case;
if (dst != NULL) {
clusterManagerLogInfo(">>> Case 3: Moving slot %d from %s:%d to "
"%s:%d and closing it on all the other "
"importing nodes.\n",
slot, src->ip, src->port,
dst->ip, dst->port);
/* Move the slot to the destination node. */
success = clusterManagerMoveSlot(src, dst, slot, move_opts, NULL);
if (!success) goto cleanup;
/* Close slot on all the other importing nodes. */
listRewind(importing, &li);
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
if (dst == n) continue;
success = clusterManagerClearSlotStatus(n, slot);
if (!success) goto cleanup;
}
} else {
clusterManagerLogInfo(">>> Case 3: Closing slot %d on both "
"migrating and importing nodes.\n", slot);
/* Close the slot on both the migrating node and the importing
* nodes. */
success = clusterManagerClearSlotStatus(src, slot);
if (!success) goto cleanup;
listRewind(importing, &li);
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
success = clusterManagerClearSlotStatus(n, slot);
if (!success) goto cleanup;
}
}
} else {
int try_to_close_slot = (listLength(importing) == 0 &&
listLength(migrating) == 1);
if (try_to_close_slot) {
clusterManagerNode *n = listFirst(migrating)->value;
if (!owner || owner != n) {
redisReply *r = CLUSTER_MANAGER_COMMAND(n,
"CLUSTER GETKEYSINSLOT %d %d", slot, 10);
success = clusterManagerCheckRedisReply(n, r, NULL);
if (r) {
if (success) try_to_close_slot = (r->elements == 0);
freeReplyObject(r);
}
if (!success) goto cleanup;
redisReply *r = CLUSTER_MANAGER_COMMAND(n,
"CLUSTER GETKEYSINSLOT %d %d", slot, 10);
success = clusterManagerCheckRedisReply(n, r, NULL);
if (r) {
if (success) try_to_close_slot = (r->elements == 0);
freeReplyObject(r);
}
if (!success) goto cleanup;
}
/* Case 4: There are no slots claiming to be in importing state, but
* there is a migrating node that actually don't have any key or is the
* slot owner. We can just close the slot, probably a reshard
* interrupted in the middle. */
/* Case 3: There are no slots claiming to be in importing state, but
* there is a migrating node that actually don't have any key. We
* can just close the slot, probably a reshard interrupted in the middle. */
if (try_to_close_slot) {
clusterManagerNode *n = listFirst(migrating)->value;
clusterManagerLogInfo(">>> Case 4: Closing slot %d on %s:%d\n",
slot, n->ip, n->port);
redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER SETSLOT %d %s",
slot, "STABLE");
success = clusterManagerCheckRedisReply(n, r, NULL);
if (r) freeReplyObject(r);
if (!success) goto cleanup;
} else {
unhandled_case:
success = 0;
clusterManagerLogErr("[ERR] Sorry, redis-cli can't fix this slot "
"yet (work in progress). Slot is set as "
@@ -4177,55 +3920,17 @@ cleanup:
return success;
}
static int clusterManagerFixMultipleSlotOwners(int slot, list *owners) {
clusterManagerLogInfo(">>> Fixing multiple owners for slot %d...\n", slot);
int success = 0;
assert(listLength(owners) > 1);
clusterManagerNode *owner = clusterManagerGetNodeWithMostKeysInSlot(owners,
slot,
NULL);
if (!owner) owner = listFirst(owners)->value;
clusterManagerLogInfo(">>> Setting slot %d owner: %s:%d\n",
slot, owner->ip, owner->port);
/* Set the slot owner. */
if (!clusterManagerSetSlotOwner(owner, slot, 0)) return 0;
listIter li;
listNode *ln;
listRewind(cluster_manager.nodes, &li);
/* Update configuration in all the other master nodes by assigning the slot
* itself to the new owner, and by eventually migrating keys if the node
* has keys for the slot. */
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
if (n == owner) continue;
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
int count = clusterManagerCountKeysInSlot(n, slot);
success = (count >= 0);
if (!success) break;
clusterManagerDelSlot(n, slot, 1);
if (!clusterManagerSetSlot(n, owner, slot, "node", NULL)) return 0;
if (count > 0) {
int opts = CLUSTER_MANAGER_OPT_VERBOSE |
CLUSTER_MANAGER_OPT_COLD;
success = clusterManagerMoveSlot(n, owner, slot, opts, NULL);
if (!success) break;
}
}
return success;
}
static int clusterManagerCheckCluster(int quiet) {
listNode *ln = listFirst(cluster_manager.nodes);
if (!ln) return 0;
int result = 1;
int do_fix = config.cluster_manager_command.flags &
CLUSTER_MANAGER_CMD_FLAG_FIX;
clusterManagerNode *node = ln->value;
clusterManagerLogInfo(">>> Performing Cluster Check (using node %s:%d)\n",
node->ip, node->port);
int result = 1, consistent = 0;
int do_fix = config.cluster_manager_command.flags &
CLUSTER_MANAGER_CMD_FLAG_FIX;
if (!quiet) clusterManagerShowNodes();
consistent = clusterManagerIsConfigConsistent();
if (!consistent) {
if (!clusterManagerIsConfigConsistent()) {
sds err = sdsnew("[ERR] Nodes don't agree about configuration!");
clusterManagerOnError(err);
result = 0;
@@ -4233,7 +3938,7 @@ static int clusterManagerCheckCluster(int quiet) {
clusterManagerLogOk("[OK] All nodes agree about slots "
"configuration.\n");
}
/* Check open slots */
// Check open slots
clusterManagerLogInfo(">>> Check for open slots...\n");
listIter li;
listRewind(cluster_manager.nodes, &li);
@@ -4292,7 +3997,7 @@ static int clusterManagerCheckCluster(int quiet) {
clusterManagerLogErr("%s.\n", (char *) errstr);
sdsfree(errstr);
if (do_fix) {
/* Fix open slots. */
// Fix open slots.
dictReleaseIterator(iter);
iter = dictGetIterator(open_slots);
while ((entry = dictNext(iter)) != NULL) {
@@ -4320,58 +4025,12 @@ static int clusterManagerCheckCluster(int quiet) {
result = 0;
if (do_fix/* && result*/) {
dictType dtype = clusterManagerDictType;
dtype.keyDestructor = dictSdsDestructor;
dtype.valDestructor = dictListDestructor;
clusterManagerUncoveredSlots = dictCreate(&dtype, NULL);
int fixed = clusterManagerFixSlotsCoverage(slots);
if (fixed > 0) result = 1;
}
}
int search_multiple_owners = config.cluster_manager_command.flags &
CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
if (search_multiple_owners) {
/* Check whether there are multiple owners, even when slots are
* fully covered and there are no open slots. */
clusterManagerLogInfo(">>> Check for multiple slot owners...\n");
int slot = 0;
for (; slot < CLUSTER_MANAGER_SLOTS; slot++) {
listIter li;
listNode *ln;
listRewind(cluster_manager.nodes, &li);
list *owners = listCreate();
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
if (n->slots[slot]) listAddNodeTail(owners, n);
else {
/* Nodes having keys for the slot will be considered
* owners too. */
int count = clusterManagerCountKeysInSlot(n, slot);
if (count > 0) listAddNodeTail(owners, n);
}
}
if (listLength(owners) > 1) {
result = 0;
clusterManagerLogErr("[WARNING] Slot %d has %d owners:\n",
slot, listLength(owners));
listRewind(owners, &li);
while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value;
clusterManagerLogErr(" %s:%d\n", n->ip, n->port);
}
if (do_fix) {
result = clusterManagerFixMultipleSlotOwners(slot, owners);
if (!result) {
clusterManagerLogErr("Failed to fix multiple owners "
"for slot %d\n", slot);
listRelease(owners);
break;
}
}
}
listRelease(owners);
}
}
return result;
}
@@ -4402,7 +4061,7 @@ static clusterManagerNode *clusterNodeForResharding(char *id,
static list *clusterManagerComputeReshardTable(list *sources, int numslots) {
list *moved = listCreate();
int src_count = listLength(sources), i = 0, tot_slots = 0, j;
clusterManagerNode **sorted = zmalloc(src_count * sizeof(*sorted));
clusterManagerNode **sorted = zmalloc(src_count * sizeof(**sorted));
listIter li;
listNode *ln;
listRewind(sources, &li);
@@ -5441,13 +5100,10 @@ static int clusterManagerCommandSetTimeout(int argc, char **argv) {
n->port);
ok_count++;
continue;
reply_err:;
int need_free = 0;
reply_err:
if (err == NULL) err = "";
else need_free = 1;
clusterManagerLogErr("ERR setting node-timeot for %s:%d: %s\n", n->ip,
n->port, err);
if (need_free) zfree(err);
err_count++;
}
clusterManagerLogInfo(">>> New node timeout set. %d OK, %d ERR.\n",
@@ -5624,7 +5280,7 @@ static int clusterManagerCommandCall(int argc, char **argv) {
if (status != REDIS_OK || reply == NULL )
printf("%s:%d: Failed!\n", n->ip, n->port);
else {
sds formatted_reply = cliFormatReplyRaw(reply);
sds formatted_reply = cliFormatReplyTTY(reply, "");
printf("%s:%d: %s\n", n->ip, n->port, (char *) formatted_reply);
sdsfree(formatted_reply);
}
@@ -7001,8 +6657,6 @@ int main(int argc, char **argv) {
argc -= firstarg;
argv += firstarg;
parseEnv();
/* Cluster Manager mode */
if (CLUSTER_MANAGER_MODE()) {
clusterManagerCommandProc *proc = validateClusterManagerCommand();
-12
View File
@@ -1245,18 +1245,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
if (eof_reached) {
int aof_is_enabled = server.aof_state != AOF_OFF;
/* Ensure background save doesn't overwrite synced data */
if (server.rdb_child_pid != -1) {
serverLog(LL_NOTICE,
"Replica is about to load the RDB file received from the "
"master, but there is a pending RDB child running. "
"Killing process %ld and removing its temp file to avoid "
"any race",
(long) server.rdb_child_pid);
kill(server.rdb_child_pid,SIGUSR1);
rdbRemoveTempFile(server.rdb_child_pid);
}
if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
serverLog(LL_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> REPLICA synchronization: %s", strerror(errno));
cancelReplicationHandshake();
+6 -5
View File
@@ -560,7 +560,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
}
/* Run the command */
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS | CMD_CALL_NOQUEUE;
if (server.lua_replicate_commands) {
/* Set flags according to redis.set_repl() settings. */
if (server.lua_repl & PROPAGATE_AOF)
@@ -1242,7 +1242,7 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
* we need to mask the client executing the script from the event loop.
* If we don't do that the client may disconnect and could no longer be
* here when the EVAL command will return. */
protectClient(server.lua_caller);
aeDeleteFileEvent(server.el, server.lua_caller->fd, AE_READABLE);
}
if (server.lua_timedout) processEventsWhileBlocked();
if (server.lua_kill) {
@@ -1370,9 +1370,10 @@ void evalGenericCommand(client *c, int evalsha) {
if (delhook) lua_sethook(lua,NULL,0,0); /* Disable hook */
if (server.lua_timedout) {
server.lua_timedout = 0;
/* Restore the client that was protected when the script timeout
* was detected. */
unprotectClient(c);
/* Restore the readable handler that was unregistered when the
* script timeout was detected. */
aeCreateFileEvent(server.el,c->fd,AE_READABLE,
readQueryFromClient,c);
if (server.masterhost && server.master)
queueClientForReprocessing(server.master);
}
+1 -1
View File
@@ -695,7 +695,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
* s = sdstrim(s,"Aa. :");
* printf("%s\n", s);
*
* Output will be just "HelloWorld".
* Output will be just "Hello World".
*/
sds sdstrim(sds s, const char *cset) {
char *start, *end, *sp, *ep;
+3 -18
View File
@@ -452,15 +452,13 @@ struct redisCommand sentinelcmds[] = {
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
{"role",sentinelRoleCommand,1,"l",0,NULL,0,0,0,0,0},
{"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0},
{"auth",authCommand,2,"sltF",0,NULL,0,0,0,0,0}
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
};
/* This function overwrites a few normal Redis config default with Sentinel
* specific defaults. */
void initSentinelConfig(void) {
server.port = REDIS_SENTINEL_PORT;
server.protected_mode = 0; /* Sentinel must be exposed. */
}
/* Perform the Sentinel mode initialization. */
@@ -1943,25 +1941,12 @@ werr:
/* Send the AUTH command with the specified master password if needed.
* Note that for slaves the password set for the master is used.
*
* In case this Sentinel requires a password as well, via the "requirepass"
* configuration directive, we assume we should use the local password in
* order to authenticate when connecting with the other Sentinels as well.
* So basically all the Sentinels share the same password and use it to
* authenticate reciprocally.
*
* We don't check at all if the command was successfully transmitted
* to the instance as if it fails Sentinel will detect the instance down,
* will disconnect and reconnect the link and so forth. */
void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
char *auth_pass = NULL;
if (ri->flags & SRI_MASTER) {
auth_pass = ri->auth_pass;
} else if (ri->flags & SRI_SLAVE) {
auth_pass = ri->master->auth_pass;
} else if (ri->flags & SRI_SENTINEL) {
if (server.requirepass) auth_pass = server.requirepass;
}
char *auth_pass = (ri->flags & SRI_MASTER) ? ri->auth_pass :
ri->master->auth_pass;
if (auth_pass) {
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s",
+43 -38
View File
@@ -307,20 +307,19 @@ struct redisCommand redisCommandTable[] = {
{"pfcount",pfcountCommand,-2,"r",0,NULL,1,-1,1,0,0},
{"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0},
{"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0},
{"xadd",xaddCommand,-5,"wmFR",0,NULL,1,1,1,0,0},
{"xadd",xaddCommand,-5,"wmF",0,NULL,1,1,1,0,0},
{"xrange",xrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
{"xrevrange",xrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
{"xlen",xlenCommand,2,"rF",0,NULL,1,1,1,0,0},
{"xread",xreadCommand,-4,"rs",0,xreadGetKeys,1,1,1,0,0},
{"xreadgroup",xreadCommand,-7,"ws",0,xreadGetKeys,1,1,1,0,0},
{"xgroup",xgroupCommand,-2,"wm",0,NULL,2,2,1,0,0},
{"xsetid",xsetidCommand,3,"wmF",0,NULL,1,1,1,0,0},
{"xack",xackCommand,-4,"wF",0,NULL,1,1,1,0,0},
{"xpending",xpendingCommand,-3,"rR",0,NULL,1,1,1,0,0},
{"xclaim",xclaimCommand,-6,"wRF",0,NULL,1,1,1,0,0},
{"xinfo",xinfoCommand,-2,"rR",0,NULL,2,2,1,0,0},
{"xpending",xpendingCommand,-3,"r",0,NULL,1,1,1,0,0},
{"xclaim",xclaimCommand,-6,"wF",0,NULL,1,1,1,0,0},
{"xinfo",xinfoCommand,-2,"r",0,NULL,2,2,1,0,0},
{"xdel",xdelCommand,-3,"wF",0,NULL,1,1,1,0,0},
{"xtrim",xtrimCommand,-2,"wFR",0,NULL,1,1,1,0,0},
{"xtrim",xtrimCommand,-2,"wF",0,NULL,1,1,1,0,0},
{"post",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},
{"host:",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},
{"latency",latencyCommand,-2,"aslt",0,NULL,0,0,0,0,0},
@@ -1526,10 +1525,10 @@ void initServerConfig(void) {
server.runid[CONFIG_RUN_ID_SIZE] = '\0';
changeReplicationId();
clearReplicationId2();
server.timezone = getTimeZone(); /* Initialized by tzset(). */
server.timezone = timezone; /* Initialized by tzset(). */
server.configfile = NULL;
server.executable = NULL;
server.hz = server.config_hz = CONFIG_DEFAULT_HZ;
server.config_hz = CONFIG_DEFAULT_HZ;
server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ;
server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
server.port = CONFIG_DEFAULT_SERVER_PORT;
@@ -1703,7 +1702,6 @@ void initServerConfig(void) {
server.expireCommand = lookupCommandByCString("expire");
server.pexpireCommand = lookupCommandByCString("pexpire");
server.xclaimCommand = lookupCommandByCString("xclaim");
server.xgroupCommand = lookupCommandByCString("xgroup");
/* Slow log */
server.slowlog_log_slower_than = CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN;
@@ -1958,13 +1956,9 @@ int listenToPort(int port, int *fds, int *count) {
}
if (fds[*count] == ANET_ERR) {
serverLog(LL_WARNING,
"Could not create server TCP listening socket %s:%d: %s",
"Creating Server TCP listening socket %s:%d: %s",
server.bindaddr[j] ? server.bindaddr[j] : "*",
port, server.neterr);
if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT ||
errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
errno == EAFNOSUPPORT || errno == EADDRNOTAVAIL)
continue;
return C_ERR;
}
anetNonBlock(NULL,fds[*count]);
@@ -2416,7 +2410,7 @@ void call(client *c, int flags) {
int client_old_flags = c->flags;
struct redisCommand *real_cmd = c->cmd;
/* Sent the command to clients in MONITOR mode, only if the commands are
/* Send the command to clients in MONITOR mode, only if the commands are
* not generated from reading an AOF. */
if (listLength(server.monitors) &&
!server.loading &&
@@ -2425,6 +2419,25 @@ void call(client *c, int flags) {
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
}
/* If the client is in the context of a transaction, reply with
* +QUEUED and just accumulate the command in the client transaction
* commands vector.
*
* Note that CALL_NOQUEUE can override this check and execute the command
* straight away even if the client is in "MULTI" state. This is useful
* every time we want to actually execute commands even if the client is
* in MULTI state, like in scripting.c or in the implementation of EXEC
* itself. */
if (!(flags & CMD_CALL_NOQUEUE) &&
c->flags & CLIENT_MULTI &&
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
{
queueMultiCommand(c);
addReply(c,shared.queued);
return;
}
/* Initialization: clear the flags that must be set by the command on
* demand, and initialize the array for additional commands propagation. */
c->flags &= ~(CLIENT_FORCE_AOF|CLIENT_FORCE_REPL|CLIENT_PREVENT_PROP);
@@ -2607,23 +2620,24 @@ int processCommand(client *c) {
}
/* Handle the maxmemory directive.
*
* First we try to free some memory if possible (if there are volatile
* keys in the dataset). If there are not the only thing we can do
* is returning an error.
*
* Note that we do not want to reclaim memory if we are here re-entering
* the event loop since there is a busy Lua script running in timeout
* condition, to avoid mixing the propagation of scripts with the
* propagation of DELs due to eviction. */
* condition, to avoid mixing the propagation of scripts with the propagation
* of DELs due to eviction. */
if (server.maxmemory && !server.lua_timedout) {
int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR;
int out_of_memory = freeMemoryIfNeeded() == C_ERR;
/* freeMemoryIfNeeded may flush slave output buffers. This may result
* into a slave, that may be the active client, to be freed. */
if (server.current_client == NULL) return C_ERR;
/* It was impossible to free enough memory, and the command the client
* is trying to execute is denied during OOM conditions or the client
* is in MULTI/EXEC context? Error. */
if (out_of_memory &&
(c->cmd->flags & CMD_DENYOOM ||
(c->flags & CLIENT_MULTI && c->cmd->proc != execCommand))) {
* is trying to execute is denied during OOM conditions? Error. */
if ((c->cmd->flags & CMD_DENYOOM) && out_of_memory) {
flagTransaction(c);
addReply(c, shared.oomerr);
return C_OK;
@@ -2719,18 +2733,9 @@ int processCommand(client *c) {
}
/* Exec the command */
if (c->flags & CLIENT_MULTI &&
c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
{
queueMultiCommand(c);
addReply(c,shared.queued);
} else {
call(c,CMD_CALL_FULL);
c->woff = server.master_repl_offset;
if (listLength(server.ready_keys))
handleClientsBlockedOnKeys();
}
call(c,CMD_CALL_FULL);
c->woff = server.master_repl_offset;
if (listLength(server.ready_keys)) handleClientsBlockedOnKeys();
return C_OK;
}
@@ -3243,11 +3248,11 @@ sds genRedisInfoString(char *section) {
"allocator_frag_ratio:%.2f\r\n"
"allocator_frag_bytes:%zu\r\n"
"allocator_rss_ratio:%.2f\r\n"
"allocator_rss_bytes:%zd\r\n"
"allocator_rss_bytes:%zu\r\n"
"rss_overhead_ratio:%.2f\r\n"
"rss_overhead_bytes:%zd\r\n"
"rss_overhead_bytes:%zu\r\n"
"mem_fragmentation_ratio:%.2f\r\n"
"mem_fragmentation_bytes:%zd\r\n"
"mem_fragmentation_bytes:%zu\r\n"
"mem_not_counted_for_evict:%zu\r\n"
"mem_replication_backlog:%zu\r\n"
"mem_clients_slaves:%zu\r\n"
+7 -13
View File
@@ -256,7 +256,6 @@ typedef long long mstime_t; /* millisecond time type. */
#define CLIENT_LUA_DEBUG (1<<25) /* Run EVAL in debug mode. */
#define CLIENT_LUA_DEBUG_SYNC (1<<26) /* EVAL debugging without fork() */
#define CLIENT_MODULE (1<<27) /* Non connected client used by some module. */
#define CLIENT_PROTECTED (1<<28) /* Client should not be freed for now. */
/* Client block type (btype field in client structure)
* if CLIENT_BLOCKED flag is set. */
@@ -417,6 +416,9 @@ typedef long long mstime_t; /* millisecond time type. */
#define CMD_CALL_PROPAGATE_REPL (1<<3)
#define CMD_CALL_PROPAGATE (CMD_CALL_PROPAGATE_AOF|CMD_CALL_PROPAGATE_REPL)
#define CMD_CALL_FULL (CMD_CALL_SLOWLOG | CMD_CALL_STATS | CMD_CALL_PROPAGATE)
/* --- call() special semantics that can be requested by the caller
* --- but are not included in CALL_FULL. */
#define CMD_CALL_NOQUEUE (1<<4)
/* Command propagation flags, see propagate() function */
#define PROPAGATE_NONE 0
@@ -654,9 +656,6 @@ typedef struct multiCmd {
typedef struct multiState {
multiCmd *commands; /* Array of MULTI commands */
int count; /* Total number of MULTI commands */
int cmd_flags; /* The accumulated command flags OR-ed together.
So if at least a command has a given flag, it
will be set in this field. */
int minreplicas; /* MINREPLICAS for synchronous replication */
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
} multiState;
@@ -867,11 +866,11 @@ struct redisMemOverhead {
float dataset_perc;
float peak_perc;
float total_frag;
ssize_t total_frag_bytes;
size_t total_frag_bytes;
float allocator_frag;
ssize_t allocator_frag_bytes;
size_t allocator_frag_bytes;
float allocator_rss;
ssize_t allocator_rss_bytes;
size_t allocator_rss_bytes;
float rss_extra;
size_t rss_extra_bytes;
size_t num_dbs;
@@ -993,8 +992,7 @@ struct redisServer {
struct redisCommand *delCommand, *multiCommand, *lpushCommand,
*lpopCommand, *rpopCommand, *zpopminCommand,
*zpopmaxCommand, *sremCommand, *execCommand,
*expireCommand, *pexpireCommand, *xclaimCommand,
*xgroupCommand;
*expireCommand, *pexpireCommand, *xclaimCommand;
/* Fields used only for stats */
time_t stat_starttime; /* Server start time */
long long stat_numcommands; /* Number of processed commands */
@@ -1478,8 +1476,6 @@ int clientHasPendingReplies(client *c);
void unlinkClient(client *c);
int writeToClient(int fd, client *c, int handler_installed);
void linkClient(client *c);
void protectClient(client *c);
void unprotectClient(client *c);
#ifdef __GNUC__
void addReplyErrorFormat(client *c, const char *fmt, ...)
@@ -1702,7 +1698,6 @@ int zslLexValueLteMax(sds value, zlexrangespec *spec);
int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level);
size_t freeMemoryGetNotCountedMemory();
int freeMemoryIfNeeded(void);
int freeMemoryIfNeededAndSafe(void);
int processCommand(client *c);
void setupSignalHandlers(void);
struct redisCommand *lookupCommand(sds name);
@@ -2115,7 +2110,6 @@ void xrevrangeCommand(client *c);
void xlenCommand(client *c);
void xreadCommand(client *c);
void xgroupCommand(client *c);
void xsetidCommand(client *c);
void xackCommand(client *c);
void xpendingCommand(client *c);
void xclaimCommand(client *c);
+1 -1
View File
@@ -39,7 +39,7 @@
#include <errno.h> /* errno program_invocation_name program_invocation_short_name */
#if !defined(HAVE_SETPROCTITLE)
#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __DragonFly__)
#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
#define HAVE_SETPROCTITLE 1
#else
#define HAVE_SETPROCTITLE 0
+57 -204
View File
@@ -777,8 +777,8 @@ int streamDeleteItem(stream *s, streamID *id) {
* in the standard <ms>-<seq> format, using the simple string protocol
* of REPL. */
void addReplyStreamID(client *c, streamID *id) {
sds replyid = sdscatfmt(sdsempty(),"%U-%U",id->ms,id->seq);
addReplyBulkSds(c,replyid);
sds replyid = sdscatfmt(sdsempty(),"+%U-%U\r\n",id->ms,id->seq);
addReplySds(c,replyid);
}
/* Similar to the above function, but just creates an object, usually useful
@@ -791,18 +791,18 @@ robj *createObjectFromStreamID(streamID *id) {
/* As a result of an explicit XCLAIM or XREADGROUP command, new entries
* are created in the pending list of the stream and consumers. We need
* to propagate this changes in the form of XCLAIM commands. */
void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupname, robj *id, streamNACK *nack) {
void streamPropagateXCLAIM(client *c, robj *key, robj *group, robj *id, streamNACK *nack) {
/* We need to generate an XCLAIM that will work in a idempotent fashion:
*
* XCLAIM <key> <group> <consumer> 0 <id> TIME <milliseconds-unix-time>
* RETRYCOUNT <count> FORCE JUSTID LASTID <id>.
* RETRYCOUNT <count> FORCE JUSTID.
*
* Note that JUSTID is useful in order to avoid that XCLAIM will do
* useless work in the slave side, trying to fetch the stream item. */
robj *argv[14];
robj *argv[12];
argv[0] = createStringObject("XCLAIM",6);
argv[1] = key;
argv[2] = groupname;
argv[2] = group;
argv[3] = createStringObject(nack->consumer->name,sdslen(nack->consumer->name));
argv[4] = createStringObjectFromLongLong(0);
argv[5] = id;
@@ -812,9 +812,7 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
argv[9] = createStringObjectFromLongLong(nack->delivery_count);
argv[10] = createStringObject("FORCE",5);
argv[11] = createStringObject("JUSTID",6);
argv[12] = createStringObject("LASTID",6);
argv[13] = createObjectFromStreamID(&group->last_id);
propagate(server.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
propagate(server.xclaimCommand,c->db->id,argv,12,PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(argv[0]);
decrRefCount(argv[3]);
decrRefCount(argv[4]);
@@ -824,38 +822,13 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
decrRefCount(argv[9]);
decrRefCount(argv[10]);
decrRefCount(argv[11]);
decrRefCount(argv[12]);
decrRefCount(argv[13]);
}
/* We need this when we want to propoagate the new last-id of a consumer group
* that was consumed by XREADGROUP with the NOACK option: in that case we can't
* propagate the last ID just using the XCLAIM LASTID option, so we emit
*
* XGROUP SETID <key> <groupname> <id>
*/
void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupname) {
robj *argv[5];
argv[0] = createStringObject("XGROUP",6);
argv[1] = createStringObject("SETID",5);
argv[2] = key;
argv[3] = groupname;
argv[4] = createObjectFromStreamID(&group->last_id);
propagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
decrRefCount(argv[4]);
}
/* Send the stream items in the specified range to the client 'c'. The range
* the client will receive is between start and end inclusive, if 'count' is
* non zero, no more than 'count' elements are sent.
*
* The 'end' pointer can be NULL to mean that we want all the elements from
* 'start' till the end of the stream. If 'rev' is non zero, elements are
* produced in reversed order from end to start.
*
* The function returns the number of entries emitted.
/* Send the specified range to the client 'c'. The range the client will
* receive is between start and end inclusive, if 'count' is non zero, no more
* than 'count' elemnets are sent. The 'end' pointer can be NULL to mean that
* we want all the elements from 'start' till the end of the stream. If 'rev'
* is non zero, elements are produced in reversed order from end to start.
*
* If group and consumer are not NULL, the function performs additional work:
* 1. It updates the last delivered ID in the group in case we are
@@ -894,21 +867,22 @@ void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupna
#define STREAM_RWR_NOACK (1<<0) /* Do not create entries in the PEL. */
#define STREAM_RWR_RAWENTRIES (1<<1) /* Do not emit protocol for array
boundaries, just the entries. */
#define STREAM_RWR_HISTORY (1<<2) /* Only serve consumer local PEL. */
size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev, streamCG *group, streamConsumer *consumer, int flags, streamPropInfo *spi) {
void *arraylen_ptr = NULL;
size_t arraylen = 0;
streamIterator si;
int64_t numfields;
streamID id;
int propagate_last_id = 0;
/* If the client is asking for some history, we serve it using a
* different function, so that we return entries *solely* from its
* own PEL. This ensures each consumer will always and only see
* the history of messages delivered to it and not yet confirmed
/* If a group was passed, we check if the request is about messages
* never delivered so far (normally this happens when ">" ID is passed).
*
* If instead the client is asking for some history, we serve it
* using a different function, so that we return entries *solely*
* from its own PEL. This ensures each consumer will always and only
* see the history of messages delivered to it and not yet confirmed
* as delivered. */
if (group && (flags & STREAM_RWR_HISTORY)) {
if (group && streamCompareID(start,&group->last_id) <= 0) {
return streamReplyWithRangeFromConsumerPEL(c,s,start,end,count,
consumer);
}
@@ -918,10 +892,8 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
streamIteratorStart(&si,s,start,end,rev);
while(streamIteratorGetID(&si,&id,&numfields)) {
/* Update the group last_id if needed. */
if (group && streamCompareID(&id,&group->last_id) > 0) {
if (group && streamCompareID(&id,&group->last_id) > 0)
group->last_id = id;
propagate_last_id = 1;
}
/* Emit a two elements array for each item. The first is
* the ID, the second is an array of field-value pairs. */
@@ -981,12 +953,9 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
/* Propagate as XCLAIM. */
if (spi) {
robj *idarg = createObjectFromStreamID(&id);
streamPropagateXCLAIM(c,spi->keyname,group,spi->groupname,idarg,nack);
streamPropagateXCLAIM(c,spi->keyname,spi->groupname,idarg,nack);
decrRefCount(idarg);
}
} else {
if (propagate_last_id)
streamPropagateGroupID(c,spi->keyname,group,spi->groupname);
}
arraylen++;
@@ -1025,7 +994,7 @@ size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start
if (end && memcmp(ri.key,end,ri.key_len) > 0) break;
streamID thisid;
streamDecodeID(ri.key,&thisid);
if (streamReplyWithRange(c,s,&thisid,&thisid,1,0,NULL,NULL,
if (streamReplyWithRange(c,s,&thisid,NULL,1,0,NULL,NULL,
STREAM_RWR_RAWENTRIES,NULL) == 0)
{
/* Note that we may have a not acknowledged entry in the PEL
@@ -1262,7 +1231,7 @@ void xrangeGenericCommand(client *c, int rev) {
robj *o;
stream *s;
streamID startid, endid;
long long count = -1;
long long count = 0;
robj *startarg = rev ? c->argv[3] : c->argv[2];
robj *endarg = rev ? c->argv[2] : c->argv[3];
@@ -1289,13 +1258,7 @@ void xrangeGenericCommand(client *c, int rev) {
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|| checkType(c,o,OBJ_STREAM)) return;
s = o->ptr;
if (count == 0) {
addReply(c,shared.nullmultibulk);
} else {
if (count == -1) count = 0;
streamReplyWithRange(c,s,&startid,&endid,count,rev,NULL,NULL,0,NULL);
}
streamReplyWithRange(c,s,&startid,&endid,count,rev,NULL,NULL,0,NULL);
}
/* XRANGE key start end [COUNT <n>] */
@@ -1472,10 +1435,8 @@ void xreadCommand(client *c) {
stream *s = o->ptr;
streamID *gt = ids+i; /* ID must be greater than this. */
int serve_synchronously = 0;
int serve_history = 0; /* True for XREADGROUP with ID != ">". */
/* Check if there are the conditions to serve the client
* synchronously. */
/* Check if there are the conditions to serve the client synchronously. */
if (groups) {
/* If the consumer is blocked on a group, we always serve it
* synchronously (serving its local history) if the ID specified
@@ -1484,7 +1445,6 @@ void xreadCommand(client *c) {
gt->seq != UINT64_MAX)
{
serve_synchronously = 1;
serve_history = 1;
} else {
/* We also want to serve a consumer in a consumer group
* synchronously in case the group top item delivered is smaller
@@ -1520,12 +1480,9 @@ void xreadCommand(client *c) {
if (groups) consumer = streamLookupConsumer(groups[i],
consumername->ptr,1);
streamPropInfo spi = {c->argv[i+streams_arg],groupname};
int flags = 0;
if (noack) flags |= STREAM_RWR_NOACK;
if (serve_history) flags |= STREAM_RWR_HISTORY;
streamReplyWithRange(c,s,&start,NULL,count,0,
groups ? groups[i] : NULL,
consumer, flags, &spi);
consumer, noack, &spi);
if (groups) server.dirty++;
}
}
@@ -1697,14 +1654,13 @@ uint64_t streamDelConsumer(streamCG *cg, sds name) {
* Consumer groups commands
* ----------------------------------------------------------------------- */
/* XGROUP CREATE <key> <groupname> <id or $> [MKSTREAM]
/* XGROUP CREATE <key> <groupname> <id or $>
* XGROUP SETID <key> <groupname> <id or $>
* XGROUP DESTROY <key> <groupname>
* XGROUP DELCONSUMER <key> <groupname> <consumername> */
void xgroupCommand(client *c) {
const char *help[] = {
"CREATE <key> <groupname> <id or $> [opt] -- Create a new consumer group.",
" option MKSTREAM: create the empty stream if it does not exist.",
"CREATE <key> <groupname> <id or $> -- Create a new consumer group.",
"SETID <key> <groupname> <id or $> -- Set the current group ID.",
"DESTROY <key> <groupname> -- Remove the specified group.",
"DELCONSUMER <key> <groupname> <consumer> -- Remove the specified consumer.",
@@ -1715,39 +1671,13 @@ NULL
sds grpname = NULL;
streamCG *cg = NULL;
char *opt = c->argv[1]->ptr; /* Subcommand name. */
int mkstream = 0;
robj *o;
/* CREATE has an MKSTREAM option that creates the stream if it
* does not exist. */
if (c->argc == 6 && !strcasecmp(opt,"CREATE")) {
if (strcasecmp(c->argv[5]->ptr,"MKSTREAM")) {
addReplySubcommandSyntaxError(c);
return;
}
mkstream = 1;
grpname = c->argv[3]->ptr;
}
/* Everything but the "HELP" option requires a key and group name. */
/* Lookup the key now, this is common for all the subcommands but HELP. */
if (c->argc >= 4) {
o = lookupKeyWrite(c->db,c->argv[2]);
if (o) s = o->ptr;
robj *o = lookupKeyWriteOrReply(c,c->argv[2],shared.nokeyerr);
if (o == NULL || checkType(c,o,OBJ_STREAM)) return;
s = o->ptr;
grpname = c->argv[3]->ptr;
}
/* Check for missing key/group. */
if (c->argc >= 4 && !mkstream) {
/* At this point key must exist, or there is an error. */
if (o == NULL) {
addReplyError(c,
"The XGROUP subcommand requires the key to exist. "
"Note that for CREATE you may want to use the MKSTREAM "
"option to create an empty stream automatically.");
return;
}
if (checkType(c,o,OBJ_STREAM)) return;
/* Certain subcommands require the group to exist. */
if ((cg = streamLookupCG(s,grpname)) == NULL &&
@@ -1762,26 +1692,13 @@ NULL
}
/* Dispatch the different subcommands. */
if (!strcasecmp(opt,"CREATE") && (c->argc == 5 || c->argc == 6)) {
if (!strcasecmp(opt,"CREATE") && c->argc == 5) {
streamID id;
if (!strcmp(c->argv[4]->ptr,"$")) {
if (s) {
id = s->last_id;
} else {
id.ms = 0;
id.seq = 0;
}
id = s->last_id;
} else if (streamParseStrictIDOrReply(c,c->argv[4],&id,0) != C_OK) {
return;
}
/* Handle the MKSTREAM option now that the command can no longer fail. */
if (s == NULL && mkstream) {
o = createStreamObject();
dbAdd(c->db,c->argv[2],o);
s = o->ptr;
}
streamCG *cg = streamCreateCG(s,grpname,sdslen(grpname),&id);
if (cg) {
addReply(c,shared.ok);
@@ -1829,40 +1746,6 @@ NULL
}
}
/* XSETID <stream> <groupname> <id>
*
* Set the internal "last ID" of a stream. */
void xsetidCommand(client *c) {
robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
if (o == NULL || checkType(c,o,OBJ_STREAM)) return;
stream *s = o->ptr;
streamID id;
if (streamParseStrictIDOrReply(c,c->argv[2],&id,0) != C_OK) return;
/* If the stream has at least one item, we want to check that the user
* is setting a last ID that is equal or greater than the current top
* item, otherwise the fundamental ID monotonicity assumption is violated. */
if (s->length > 0) {
streamID maxid;
streamIterator si;
streamIteratorStart(&si,s,NULL,NULL,1);
int64_t numfields;
streamIteratorGetID(&si,&maxid,&numfields);
streamIteratorStop(&si);
if (streamCompareID(&id,&maxid) < 0) {
addReplyError(c,"The ID specified in XSETID is smaller than the "
"target stream top item");
return;
}
}
s->last_id = id;
addReply(c,shared.ok);
server.dirty++;
notifyKeyspaceEvent(NOTIFY_STREAM,"xsetid",c->argv[1],c->db->id);
}
/* XACK <key> <group> <id> <id> ... <id>
*
* Acknowledge a message as processed. In practical terms we just check the
@@ -1908,7 +1791,7 @@ void xackCommand(client *c) {
addReplyLongLong(c,acknowledged);
}
/* XPENDING <key> <group> [<start> <stop> <count> [<consumer>]]
/* XPENDING <key> <group> [<start> <stop> <count>] [<consumer>]
*
* If start and stop are omitted, the command just outputs information about
* the amount of pending messages for the key/group pair, together with
@@ -1937,7 +1820,6 @@ void xpendingCommand(client *c) {
if (c->argc >= 6) {
if (getLongLongFromObjectOrReply(c,c->argv[5],&count,NULL) == C_ERR)
return;
if (count < 0) count = 0;
if (streamParseIDOrReply(c,c->argv[3],&startid,0) == C_ERR)
return;
if (streamParseIDOrReply(c,c->argv[4],&endid,UINT64_MAX) == C_ERR)
@@ -2111,14 +1993,6 @@ void xpendingCommand(client *c) {
* Return just an array of IDs of messages successfully claimed,
* without returning the actual message.
*
* 6. LASTID <id>:
* Update the consumer group last ID with the specified ID if the
* current last ID is smaller than the provided one.
* This is used for replication / AOF, so that when we read from a
* consumer group, the XCLAIM that gets propagated to give ownership
* to the consumer, is also used in order to update the group current
* ID.
*
* The command returns an array of messages that the user
* successfully claimed, so that the caller is able to understand
* what messages it is now in charge of. */
@@ -2163,9 +2037,7 @@ void xclaimCommand(client *c) {
/* If we stopped because some IDs cannot be parsed, perhaps they
* are trailing options. */
mstime_t now = mstime();
streamID last_id = {0,0};
int propagate_last_id = 0;
time_t now = mstime();
for (; j < c->argc; j++) {
int moreargs = (c->argc-1) - j; /* Number of additional arguments. */
char *opt = c->argv[j]->ptr;
@@ -2189,20 +2061,12 @@ void xclaimCommand(client *c) {
if (getLongLongFromObjectOrReply(c,c->argv[j],&retrycount,
"Invalid RETRYCOUNT option argument for XCLAIM")
!= C_OK) return;
} else if (!strcasecmp(opt,"LASTID") && moreargs) {
j++;
if (streamParseStrictIDOrReply(c,c->argv[j],&last_id,0) != C_OK) return;
} else {
addReplyErrorFormat(c,"Unrecognized XCLAIM option '%s'",opt);
return;
}
}
if (streamCompareID(&last_id,&group->last_id) > 0) {
group->last_id = last_id;
propagate_last_id = 1;
}
if (deliverytime != -1) {
/* If a delivery time was passed, either with IDLE or TIME, we
* do some sanity check on it, and set the deliverytime to now
@@ -2226,8 +2090,7 @@ void xclaimCommand(client *c) {
for (int j = 5; j <= last_id_arg; j++) {
streamID id;
unsigned char buf[sizeof(streamID)];
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK)
serverPanic("StreamID invalid after check. Should not be possible.");
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
streamEncodeID(buf,&id);
/* Lookup the ID in the group PEL. */
@@ -2257,12 +2120,8 @@ void xclaimCommand(client *c) {
if (nack != raxNotFound) {
/* We need to check if the minimum idle time requested
* by the caller is satisfied by this entry.
*
* Note that the nack could be created by FORCE, in this
* case there was no pre-existing entry and minidle should
* be ignored, but in that case nick->consumer is NULL. */
if (nack->consumer && minidle) {
* by the caller is satisfied by this entry. */
if (minidle) {
mstime_t this_idle = now - nack->delivery_time;
if (this_idle < minidle) continue;
}
@@ -2282,22 +2141,16 @@ void xclaimCommand(client *c) {
if (justid) {
addReplyStreamID(c,&id);
} else {
size_t emitted = streamReplyWithRange(c,o->ptr,&id,&id,1,0,
NULL,NULL,STREAM_RWR_RAWENTRIES,NULL);
if (!emitted) addReply(c,shared.nullbulk);
streamReplyWithRange(c,o->ptr,&id,NULL,1,0,NULL,NULL,
STREAM_RWR_RAWENTRIES,NULL);
}
arraylen++;
/* Propagate this change. */
streamPropagateXCLAIM(c,c->argv[1],group,c->argv[2],c->argv[j],nack);
propagate_last_id = 0; /* Will be propagated by XCLAIM itself. */
streamPropagateXCLAIM(c,c->argv[1],c->argv[3],c->argv[j],nack);
server.dirty++;
}
}
if (propagate_last_id) {
streamPropagateGroupID(c,c->argv[1],group,c->argv[2]);
server.dirty++;
}
setDeferredMultiBulkLength(c,arraylenptr,arraylen);
preventCommandPropagation(c);
}
@@ -2474,11 +2327,11 @@ NULL
if (idle < 0) idle = 0;
addReplyMultiBulkLen(c,6);
addReplyBulkCString(c,"name");
addReplyStatus(c,"name");
addReplyBulkCBuffer(c,consumer->name,sdslen(consumer->name));
addReplyBulkCString(c,"pending");
addReplyStatus(c,"pending");
addReplyLongLong(c,raxSize(consumer->pel));
addReplyBulkCString(c,"idle");
addReplyStatus(c,"idle");
addReplyLongLong(c,idle);
}
raxStop(&ri);
@@ -2496,28 +2349,28 @@ NULL
while(raxNext(&ri)) {
streamCG *cg = ri.data;
addReplyMultiBulkLen(c,8);
addReplyBulkCString(c,"name");
addReplyStatus(c,"name");
addReplyBulkCBuffer(c,ri.key,ri.key_len);
addReplyBulkCString(c,"consumers");
addReplyStatus(c,"consumers");
addReplyLongLong(c,raxSize(cg->consumers));
addReplyBulkCString(c,"pending");
addReplyStatus(c,"pending");
addReplyLongLong(c,raxSize(cg->pel));
addReplyBulkCString(c,"last-delivered-id");
addReplyStatus(c,"last-delivered-id");
addReplyStreamID(c,&cg->last_id);
}
raxStop(&ri);
} else if (!strcasecmp(opt,"STREAM") && c->argc == 3) {
/* XINFO STREAM <key> (or the alias XINFO <key>). */
addReplyMultiBulkLen(c,14);
addReplyBulkCString(c,"length");
addReplyStatus(c,"length");
addReplyLongLong(c,s->length);
addReplyBulkCString(c,"radix-tree-keys");
addReplyStatus(c,"radix-tree-keys");
addReplyLongLong(c,raxSize(s->rax));
addReplyBulkCString(c,"radix-tree-nodes");
addReplyStatus(c,"radix-tree-nodes");
addReplyLongLong(c,s->rax->numnodes);
addReplyBulkCString(c,"groups");
addReplyStatus(c,"groups");
addReplyLongLong(c,s->cgroups ? raxSize(s->cgroups) : 0);
addReplyBulkCString(c,"last-generated-id");
addReplyStatus(c,"last-generated-id");
addReplyStreamID(c,&s->last_id);
/* To emit the first/last entry we us the streamReplyWithRange()
@@ -2526,11 +2379,11 @@ NULL
streamID start, end;
start.ms = start.seq = 0;
end.ms = end.seq = UINT64_MAX;
addReplyBulkCString(c,"first-entry");
addReplyStatus(c,"first-entry");
count = streamReplyWithRange(c,s,&start,&end,1,0,NULL,NULL,
STREAM_RWR_RAWENTRIES,NULL);
if (!count) addReply(c,shared.nullbulk);
addReplyBulkCString(c,"last-entry");
addReplyStatus(c,"last-entry");
count = streamReplyWithRange(c,s,&start,&end,1,1,NULL,NULL,
STREAM_RWR_RAWENTRIES,NULL);
if (!count) addReply(c,shared.nullbulk);
+7 -5
View File
@@ -301,22 +301,24 @@ void mgetCommand(client *c) {
}
void msetGenericCommand(client *c, int nx) {
int j;
int j, busykeys = 0;
if ((c->argc % 2) == 0) {
addReplyError(c,"wrong number of arguments for MSET");
return;
}
/* Handle the NX flag. The MSETNX semantic is to return zero and don't
* set anything if at least one key alerady exists. */
* set nothing at all if at least one already key exists. */
if (nx) {
for (j = 1; j < c->argc; j += 2) {
if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
addReply(c, shared.czero);
return;
busykeys++;
}
}
if (busykeys) {
addReply(c, shared.czero);
return;
}
}
for (j = 1; j < c->argc; j += 2) {
+8 -6
View File
@@ -574,12 +574,12 @@ int zslParseLexRangeItem(robj *item, sds *dest, int *ex) {
switch(c[0]) {
case '+':
if (c[1] != '\0') return C_ERR;
*ex = 1;
*ex = 0;
*dest = shared.maxstring;
return C_OK;
case '-':
if (c[1] != '\0') return C_ERR;
*ex = 1;
*ex = 0;
*dest = shared.minstring;
return C_OK;
case '(':
@@ -652,8 +652,9 @@ int zslIsInLexRange(zskiplist *zsl, zlexrangespec *range) {
zskiplistNode *x;
/* Test for ranges that will always be empty. */
int cmp = sdscmplex(range->min,range->max);
if (cmp > 0 || (cmp == 0 && (range->minex || range->maxex)))
if (sdscmplex(range->min,range->max) > 1 ||
(sdscmp(range->min,range->max) == 0 &&
(range->minex || range->maxex)))
return 0;
x = zsl->tail;
if (x == NULL || !zslLexValueGteMin(x->ele,range))
@@ -926,8 +927,9 @@ int zzlIsInLexRange(unsigned char *zl, zlexrangespec *range) {
unsigned char *p;
/* Test for ranges that will always be empty. */
int cmp = sdscmplex(range->min,range->max);
if (cmp > 0 || (cmp == 0 && (range->minex || range->maxex)))
if (sdscmplex(range->min,range->max) > 1 ||
(sdscmp(range->min,range->max) == 0 &&
(range->minex || range->maxex)))
return 0;
p = ziplistIndex(zl,-2); /* Last element. */
+2 -37
View File
@@ -39,7 +39,6 @@
#include <float.h>
#include <stdint.h>
#include <errno.h>
#include <time.h>
#include "util.h"
#include "sha1.h"
@@ -48,7 +47,7 @@
int stringmatchlen(const char *pattern, int patternLen,
const char *string, int stringLen, int nocase)
{
while(patternLen && stringLen) {
while(patternLen) {
switch(pattern[0]) {
case '*':
while (pattern[1] == '*') {
@@ -171,22 +170,6 @@ int stringmatch(const char *pattern, const char *string, int nocase) {
return stringmatchlen(pattern,strlen(pattern),string,strlen(string),nocase);
}
/* Fuzz stringmatchlen() trying to crash it with bad input. */
int stringmatchlen_fuzz_test(void) {
char str[32];
char pat[32];
int cycles = 10000000;
int total_matches = 0;
while(cycles--) {
int strlen = rand() % sizeof(str);
int patlen = rand() % sizeof(pat);
for (int j = 0; j < strlen; j++) str[j] = rand() % 128;
for (int j = 0; j < patlen; j++) pat[j] = rand() % 128;
total_matches += stringmatchlen(pat, patlen, str, strlen, 0);
}
return total_matches;
}
/* Convert a string representing an amount of memory into the number of
* bytes, so for instance memtoll("1Gb") will return 1073741824 that is
* (1024*1024*1024).
@@ -622,7 +605,7 @@ void getRandomHexChars(char *p, size_t len) {
* already, this will be detected and handled correctly.
*
* The function does not try to normalize everything, but only the obvious
* case of one or more "../" appearing at the start of "filename"
* case of one or more "../" appearning at the start of "filename"
* relative path. */
sds getAbsolutePath(char *filename) {
char cwd[1024];
@@ -669,24 +652,6 @@ sds getAbsolutePath(char *filename) {
return abspath;
}
/*
* Gets the proper timezone in a more portable fashion
* i.e timezone variables are linux specific.
*/
unsigned long getTimeZone(void) {
#ifdef __linux__
return timezone;
#else
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
return tz.tz_minuteswest * 60UL;
#endif
}
/* Return true if the specified path is just a file basename without any
* relative or absolute path. This function just checks that no / or \
* character exists inside the specified path, that's enough in the
-2
View File
@@ -40,7 +40,6 @@
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
int stringmatch(const char *p, const char *s, int nocase);
int stringmatchlen_fuzz_test(void);
long long memtoll(const char *p, int *err);
uint32_t digits10(uint64_t v);
uint32_t sdigits10(int64_t v);
@@ -51,7 +50,6 @@ int string2ld(const char *s, size_t slen, long double *dp);
int d2string(char *buf, size_t len, double value);
int ld2string(char *buf, size_t len, long double value, int humanfriendly);
sds getAbsolutePath(char *filename);
unsigned long getTimeZone(void);
int pathIsBaseName(char *path);
#ifdef REDIS_TEST
+1 -1
View File
@@ -1 +1 @@
#define REDIS_VERSION "5.0.3"
#define REDIS_VERSION "999.999.999"
+1 -1
View File
@@ -4,7 +4,7 @@ set ::valgrind_errors {}
proc start_server_error {config_file error} {
set err {}
append err "Cant' start the Redis server\n"
append err "Can't start the Redis server\n"
append err "CONFIGURATION:"
append err [exec cat $config_file]
append err "\nERROR:"
-8
View File
@@ -91,14 +91,6 @@ proc wait_for_sync r {
}
}
proc wait_for_ofs_sync {r1 r2} {
wait_for_condition 50 100 {
[status $r1 master_repl_offset] eq [status $r2 master_repl_offset]
} else {
fail "replica didn't sync in time"
}
}
# Random integer between 0 and max (excluded).
proc randomInt {max} {
expr {int(rand()*$max)}
+1 -1
View File
@@ -33,7 +33,7 @@ start_server {tags {"dump"}} {
set now [clock milliseconds]
r restore foo [expr $now+3000] $encoded absttl
set ttl [r pttl foo]
assert {$ttl >= 2900 && $ttl <= 3100}
assert {$ttl >= 2990 && $ttl <= 3000}
r get foo
} {bar}
-1
View File
@@ -90,7 +90,6 @@ start_server {tags {"defrag"}} {
test "Active defrag big keys" {
r flushdb
r config resetstat
r config set save "" ;# prevent bgsave from interfereing with save below
r config set activedefrag no
r config set active-defrag-max-scan-fields 1000
r config set active-defrag-threshold-lower 5
-149
View File
@@ -9,17 +9,6 @@ start_server {
set err
} {BUSYGROUP*}
test {XGROUP CREATE: automatic stream creation fails without MKSTREAM} {
r DEL mystream
catch {r XGROUP CREATE mystream mygroup $} err
set err
} {ERR*}
test {XGROUP CREATE: automatic stream creation works with MKSTREAM} {
r DEL mystream
r XGROUP CREATE mystream mygroup $ MKSTREAM
} {OK}
test {XREADGROUP will return only new elements} {
r XADD mystream * a 1
r XADD mystream * b 2
@@ -107,142 +96,4 @@ start_server {
set c [llength [lindex [r xreadgroup group g1 c2 streams events >] 0 1]]
assert {$c == 5}
}
test {XREADGROUP will not report data on empty history. Bug #5577} {
r del events
r xadd events * a 1
r xadd events * b 2
r xadd events * c 3
r xgroup create events mygroup 0
# Current local PEL should be empty
set res [r xpending events mygroup - + 10]
assert {[llength $res] == 0}
# So XREADGROUP should read an empty history as well
set res [r xreadgroup group mygroup myconsumer count 3 streams events 0]
assert {[llength [lindex $res 0 1]] == 0}
# We should fetch all the elements in the stream asking for >
set res [r xreadgroup group mygroup myconsumer count 3 streams events >]
assert {[llength [lindex $res 0 1]] == 3}
# Now the history is populated with three not acked entries
set res [r xreadgroup group mygroup myconsumer count 3 streams events 0]
assert {[llength [lindex $res 0 1]] == 3}
}
test {XREADGROUP history reporting of deleted entries. Bug #5570} {
r del mystream
r XGROUP CREATE mystream mygroup $ MKSTREAM
r XADD mystream 1 field1 A
r XREADGROUP GROUP mygroup myconsumer STREAMS mystream >
r XADD mystream MAXLEN 1 2 field1 B
r XREADGROUP GROUP mygroup myconsumer STREAMS mystream >
# Now we have two pending entries, however one should be deleted
# and one should be ok (we should only see "B")
set res [r XREADGROUP GROUP mygroup myconsumer STREAMS mystream 0-1]
assert {[lindex $res 0 1 0] == {1-0 {}}}
assert {[lindex $res 0 1 1] == {2-0 {field1 B}}}
}
test {XCLAIM can claim PEL items from another consumer} {
# Add 3 items into the stream, and create a consumer group
r del mystream
set id1 [r XADD mystream * a 1]
set id2 [r XADD mystream * b 2]
set id3 [r XADD mystream * c 3]
r XGROUP CREATE mystream mygroup 0
# Client 1 reads item 1 from the stream without acknowledgements.
# Client 2 then claims pending item 1 from the PEL of client 1
set reply [
r XREADGROUP GROUP mygroup client1 count 1 STREAMS mystream >
]
assert {[llength [lindex $reply 0 1 0 1]] == 2}
assert {[lindex $reply 0 1 0 1] eq {a 1}}
r debug sleep 0.2
set reply [
r XCLAIM mystream mygroup client2 10 $id1
]
assert {[llength [lindex $reply 0 1]] == 2}
assert {[lindex $reply 0 1] eq {a 1}}
# Client 1 reads another 2 items from stream
r XREADGROUP GROUP mygroup client1 count 2 STREAMS mystream >
r debug sleep 0.2
# Delete item 2 from the stream. Now client 1 has PEL that contains
# only item 3. Try to use client 2 to claim the deleted item 2
# from the PEL of client 1, this should return nil
r XDEL mystream $id2
set reply [
r XCLAIM mystream mygroup client2 10 $id2
]
assert {[llength $reply] == 1}
assert_equal "" [lindex $reply 0]
# Delete item 3 from the stream. Now client 1 has PEL that is empty.
# Try to use client 2 to claim the deleted item 3 from the PEL
# of client 1, this should return nil
r debug sleep 0.2
r XDEL mystream $id3
set reply [
r XCLAIM mystream mygroup client2 10 $id3
]
assert {[llength $reply] == 1}
assert_equal "" [lindex $reply 0]
}
start_server {} {
set master [srv -1 client]
set master_host [srv -1 host]
set master_port [srv -1 port]
set slave [srv 0 client]
foreach noack {0 1} {
test "Consumer group last ID propagation to slave (NOACK=$noack)" {
$slave slaveof $master_host $master_port
wait_for_condition 50 100 {
[s 0 master_link_status] eq {up}
} else {
fail "Replication not started."
}
$master del stream
$master xadd stream * a 1
$master xadd stream * a 2
$master xadd stream * a 3
$master xgroup create stream mygroup 0
# Consume the first two items on the master
for {set j 0} {$j < 2} {incr j} {
if {$noack} {
set item [$master xreadgroup group mygroup \
myconsumer COUNT 1 NOACK STREAMS stream >]
} else {
set item [$master xreadgroup group mygroup \
myconsumer COUNT 1 STREAMS stream >]
}
set id [lindex $item 0 1 0 0]
if {$noack == 0} {
assert {[$master xack stream mygroup $id] eq "1"}
}
}
wait_for_ofs_sync $master $slave
# Turn slave into master
$slave slaveof no one
set item [$slave xreadgroup group mygroup myconsumer \
COUNT 1 STREAMS stream >]
# The consumed enty should be the third
set myentry [lindex $item 0 1 0 1]
assert {$myentry eq {a 3}}
}
}
}
}
-48
View File
@@ -363,51 +363,3 @@ start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries
assert {[r xlen mystream] == 90}
}
}
start_server {tags {"xsetid"}} {
test {XADD can CREATE an empty stream} {
r XADD mystream MAXLEN 0 * a b
assert {[dict get [r xinfo stream mystream] length] == 0}
}
test {XSETID can set a specific ID} {
r XSETID mystream "200-0"
assert {[dict get [r xinfo stream mystream] last-generated-id] == "200-0"}
}
test {XSETID cannot SETID with smaller ID} {
r XADD mystream * a b
catch {r XSETID mystream "1-1"} err
r XADD mystream MAXLEN 0 * a b
set err
} {ERR*smaller*}
test {XSETID cannot SETID on non-existent key} {
catch {r XSETID stream 1-1} err
set _ $err
} {ERR no such key}
}
start_server {tags {"stream"} overrides {appendonly yes aof-use-rdb-preamble no}} {
test {Empty stream can be rewrite into AOF correctly} {
r XADD mystream MAXLEN 0 * a b
assert {[dict get [r xinfo stream mystream] length] == 0}
r bgrewriteaof
waitForBgrewriteaof r
r debug loadaof
assert {[dict get [r xinfo stream mystream] length] == 0}
}
test {Stream can be rewrite into AOF correctly after XDEL lastid} {
r XSETID mystream 0-0
r XADD mystream 1-1 a b
r XADD mystream 2-2 a b
assert {[dict get [r xinfo stream mystream] length] == 2}
r XDEL mystream 2-2
r bgrewriteaof
waitForBgrewriteaof r
r debug loadaof
assert {[dict get [r xinfo stream mystream] length] == 1}
assert {[dict get [r xinfo stream mystream] last-generated-id] == "2-2"}
}
}
+1 -17
View File
@@ -388,7 +388,7 @@ start_server {tags {"zset"}} {
0 omega}
}
test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZLEXCOUNT basics" {
test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZCOUNT basics" {
create_default_lex_zset
# inclusive range
@@ -416,22 +416,6 @@ start_server {tags {"zset"}} {
assert_equal {} [r zrevrangebylex zset \[elez \[elex]
assert_equal {} [r zrevrangebylex zset (hill (omega]
}
test "ZLEXCOUNT advanced" {
create_default_lex_zset
assert_equal 9 [r zlexcount zset - +]
assert_equal 0 [r zlexcount zset + -]
assert_equal 0 [r zlexcount zset + \[c]
assert_equal 0 [r zlexcount zset \[c -]
assert_equal 8 [r zlexcount zset \[bar +]
assert_equal 5 [r zlexcount zset \[bar \[foo]
assert_equal 4 [r zlexcount zset \[bar (foo]
assert_equal 4 [r zlexcount zset (bar \[foo]
assert_equal 3 [r zlexcount zset (bar (foo]
assert_equal 5 [r zlexcount zset - (foo]
assert_equal 1 [r zlexcount zset (maxstring +]
}
test "ZRANGEBYSLEX with LIMIT" {
create_default_lex_zset
+3 -8
View File
@@ -1,17 +1,12 @@
#!/usr/bin/env tclsh
if {[llength $::argv] != 2 && [llength $::argv] != 3} {
puts "Usage: $::argv0 <branch> <version> \[<num-commits>\]"
if {[llength $::argv] != 2} {
puts "Usage: $::argv0 <branch> <version>"
exit 1
}
set branch [lindex $::argv 0]
set ver [lindex $::argv 1]
if {[llength $::argv] == 3} {
set count [lindex ::$argv 2]
} else {
set count 100
}
set template {
================================================================================
@@ -26,7 +21,7 @@ append template "\n\n"
set date [clock format [clock seconds]]
set template [string map [list %ver% $ver %date% $date] $template]
append template [exec git log $branch~$count..$branch "--format=format:%an in commit %h:%n %s" --shortstat]
append template [exec git log $branch~100..$branch "--format=format:%an in commit %h:%n %s" --shortstat]
#Older, more verbose version.
#