Compare commits

..
105 Commits
Author SHA1 Message Date
antirez 7ca8fbabe2 Redis 3.2.0 2016-05-06 09:11:36 +02:00
antirez 746e1bebb4 Cluster: don't check scripts key slots during AOF loading. 2016-05-05 23:42:46 +02:00
antirez 3907b05928 redis-cli: remove debugging message. 2016-05-05 18:05:43 +02:00
antirez f01a271458 Revert "Fix commandCommand arity"
This reverts commit 1189a4eae6.

Actually this is wrong, the command can be called without args at all.
2016-05-05 17:35:51 +02:00
Ruben Bridgewater 1189a4eae6 Fix commandCommand arity 2016-05-05 17:20:24 +02:00
Daniel Shih d9dc0d777b Fix a possible race condition of sdown detection if the
connection to master/slave/sentinel decames disconnected just after the last PONG and before the next PING.
2016-05-05 17:17:30 +02:00
Jan-Erik Rediger 13bd702844 Fix nanosecond conversion
1 microsecond = 1000 nanoseconds
1e3 = 1000
10e3 = 10000
2016-05-05 16:21:41 +02:00
antirez f6b7df3aec redis-cli: don't free historyfile, is used later. 2016-05-05 13:57:57 +02:00
antirez 97ce72fa2d Cluster test 12: reshard back just a few slots to speedup the test. 2016-05-05 13:45:20 +02:00
antirez 708f486c77 Cluster: make getNodeByQuery() responsible of -CLUSTERDOWN errors.
This fixes a bug introduced by d827dbf, and makes the code consistent
with the logic of always allowing, while the cluster is down, commands
that don't target any key.

As a side effect the code is also simpler now.
2016-05-05 11:35:47 +02:00
Itamar Haber 1fc2ed61be Fixes a typo 2016-05-05 10:15:35 +02:00
David Cavar 787d5ab9be Reverse redirect address parse
Fix issue in case the redirect address is in ipv6 format. Parse from behind to extract last part of the response which represents actual port.
2016-05-05 10:13:23 +02:00
antirez 0fda06225f Bind both IPv4 and IPv6 or exit with an error by default.
Thanks to @tushar2708 for the PR. I applied a slightly different fix.
Thanks to @cespare for reporting.

Close #3024
Close #3020
2016-05-05 10:05:10 +02:00
antirez 65707fa6b5 Quick fix to avoid false positive in replica migration test. 2016-05-05 09:46:48 +02:00
Chris Thunes 1f3ed652e2 Ensure slots are rechecked on EXEC.
Fixes #2515.
2016-05-05 09:34:48 +02:00
therealbill 3a48106789 fix for #3187
I've renamed maxmemoryToString to evictPolicyToString since that is
more accurate (and easier to mentally connect with the correct data), as
well as updated the function to user server.maxmemory_policy rather than
server.maxmemory. Now with a default config it is actually returning
the correct policy rather than volatile-lru.
2016-05-05 09:03:06 +02:00
Seth Bergman bba53d7fc7 Fixed typo in README.md 2016-05-05 08:57:48 +02:00
Ryosuke Hasebe ab75814898 fix variable 2016-05-05 08:56:54 +02:00
Ryosuke Hasebe 0fcf896ac1 fix check_open_slots 2016-05-05 08:56:54 +02:00
antirez b8a63635e3 Minor redis-cli wording change in --help output. 2016-05-04 22:34:17 +02:00
antirez ae95de9331 Allow CONFIG GET during loading.
Thanks to @oranagra for the idea of allowing CONFIG GET during loading.
2016-05-04 15:49:02 +02:00
antirez 3ff8f57ef3 Command "r" flag removed from commands not accessing the key space.
Thanks to @oranagra for the hint about misplaced 'r' flags.
2016-05-04 15:48:47 +02:00
antirez 07b852d24e DEBUG command self documentation. 2016-05-04 12:46:12 +02:00
Oran Agra cfc08b65b0 various cleanups and minor fixes 2016-05-04 09:16:09 +02:00
Oran Agra 6710c8dc15 dict.c minor optimization 2016-05-04 09:14:06 +02:00
Oran Agra be5c086982 networking.c minor optimization 2016-05-04 09:14:02 +02:00
Oran Agra 3ede6c7a50 add DEBUG JEMALLC PURGE and JEMALLOC INFO cleanup 2016-05-04 09:13:58 +02:00
Oran Agra e2fab184ef fix small issues in redis 3.2 2016-05-04 09:13:52 +02:00
Oran Agra 9d57ceec08 additional fix to issue #2948 2016-05-04 09:12:57 +02:00
antirez 7ba2cf5f8b Reply with error on negative geo radius.
Thanks to @tidwall for reporting.
Close #3194.
2016-05-04 09:01:14 +02:00
antirez 38cf2bd257 Cluster regression test for #3043.
The test works but is very slow so far, since it involves resharding
1/5 of all the cluster slots from master 0 to the other 4 masters and
back into the original master.
2016-05-02 18:36:13 +02:00
antirez 7b618823c1 New masters with slots are now targets of migration if others are.
This fixes issue #3043.

Before this fix, after a complete resharding of a master slots
to other nodes, the master remains empty and the slaves migrate away
to other masters with non-zero nodes. However the old master now empty,
is no longer considered a target for migration, because the system has
no way to tell it had slaves in the past.

This fix leaves the algorithm used in the past untouched, but adds a
new rule. When a new or old master which is empty and without slaves,
are assigend with their first slot, if other masters in the cluster have
slaves, they are automatically considered to be targets for replicas
migration.
2016-05-02 16:41:56 +02:00
antirez 1101b515c7 Test ZINCRBY return value. 2016-05-02 08:57:30 +02:00
antirez bbf93108ef redis-cli preferences and rc file support. 2016-05-02 08:42:35 +02:00
antirez 3fd3fca0dd redis-cli hints. 2016-05-02 08:42:32 +02:00
antirez 34354473ff Linenoise updated again (hints support). 2016-05-02 08:42:28 +02:00
antirez 58229cd766 Linenoise updated.
As a side effect, cat commands.txt | redis-cli now is able to handle
lines more than 4096 bytes.
2016-05-02 08:42:25 +02:00
antirez 29645f1f44 ae.c: Fix delay until next timer event.
This fix was written by Anthony LaTorre.
The old code mis-calculated the amount of time to wait till next event.
2016-05-02 08:42:22 +02:00
antirez aa79c1f1c1 ae.c: comment to explain why we have a useless maxId check. 2016-05-02 08:42:17 +02:00
antirez 0b69c98656 Fix ae.c to avoid timers infinite loop.
This fix was suggested by Anthony LaTorre, that provided also a good
test case that was used to verify the fix.

The problem with the old implementation is that, the time returned by
a timer event (that is the time after it want to run again) is added
to the event *start time*. So if the event takes, in order to run, more
than the time it says it want to be scheduled again for running, an
infinite loop is triggered.
2016-05-02 08:42:14 +02:00
antirez 258857cd62 BITFIELD: overflow wrap behavior fuzz tester. 2016-05-02 08:42:08 +02:00
antirez 35d05d6dc5 BITFIELD basic unit tests. 2016-05-02 08:42:04 +02:00
antirez 6cf83feffa BITFIELD: Farest bit set is offset+bits-1. Off by one error fixed. 2016-05-02 08:42:00 +02:00
antirez b4d65c9501 BITFIELD: overflow fuzzy testing. 2016-05-02 08:41:57 +02:00
antirez d75e0fdbe6 Fix typo in bitops.tcl comment. 2016-05-02 08:41:53 +02:00
antirez 5f3ef73ed4 More BITFIELD fixes. Overflow conditional simplified.
See issue #3114.
2016-05-02 08:41:47 +02:00
Sun He 5b9aa50262 bitops/bitfield: fix length, overflow condition and *sign 2016-05-02 08:41:43 +02:00
antirez ba9154d7e7 Fix INFO commandstats reporting when argv is rewritten.
We want to report the original command in the stats, for example GEOADD,
even when what is actually executed is the ZADD implementation.
2016-05-02 08:41:39 +02:00
antirez 76b22e3728 BITFIELD: refactoring & fix of retval on FAIL. 2016-05-02 08:41:30 +02:00
antirez c333a9e2d7 BITFIELD: Fix #<index> form parsing. 2016-05-02 08:41:26 +02:00
antirez f84871cb5d BITFIELD: Support #<index> offsets form. 2016-05-02 08:41:21 +02:00
antirez 761a772871 BITFIELD command initial implementation.
The new bitfield command is an extension to the Redis bit operations,
where not just single bit operations are performed, but the array of
bits composing a string, can be addressed at random, not aligned
offsets, with any width unsigned and signed integers like u8, s5, u10
(up to 64 bit signed integers and 63 bit unsigned integers).

The BITFIELD command supports subcommands that can SET, GET, or INCRBY
those arbitrary bit counters, with multiple overflow semantics.

Trivial and credits:

A similar command was imagined a few times in the past, but for
some reason looked a bit far fetched or not well specified.
Finally the command was proposed again in a clear form by
Yoav Steinberg from Redis Labs, that proposed a set of commands on
arbitrary sized integers stored at bit offsets.

Starting from this proposal I wrote an initial specification of a single
command with sub-commands similar to what Yoav envisioned, using short
names for types definitions, and adding control on the overflow.

This commit is the resulting implementation.

Examples:

    BITFIELD mykey OVERFLOW wrap INCRBY i2 10 -1 GET i2 10
2016-05-02 08:41:17 +02:00
Itamar Haber 4b89ea3a95 Eliminates engineers near the equator & prime meridian 2016-02-19 12:08:49 +01:00
Itamar Haber 0f46f9bd42 Fixes a typo in a comment 2016-02-19 12:08:49 +01:00
Itamar Haber 9de844603c Adjusts accuracy for GEODIST 2016-02-19 12:08:49 +01:00
antirez 79e553a58d addReplyHumanLongDouble() API added.
Send a long double or double as a bulk reply, in a human friendly
format.
2016-02-18 22:18:05 +01:00
antirez 158d1e2fbf Fix GEORADIUS STORE/DIST refcount after backport to 3.2. 2016-02-18 15:33:02 +01:00
antirez 123cd88286 GEOADD STORE/STOREDIST tests. 2016-02-18 15:30:22 +01:00
antirez 0b6daf5a6b New options for GEORADIUS: STORE and STOREDIST.
Related to issue #3019.
2016-02-18 15:30:13 +01:00
antirez f7af1beaab Include full paths on RDB/AOF files errors.
Close #3086.
2016-02-15 16:15:29 +01:00
antirez 0b4a628fc7 Remove Lua state reference from buffers in lua_cmsgpack. 2016-02-10 09:16:45 +01:00
yoav@monfort.co.il 43509f6864 cmsgpack: pass correct osize values to lua allocator, update correct buf free space in cmsgpack 2016-02-10 09:16:45 +01:00
Itamar Haber cd9f7c6976 Fixes a typo 2016-02-05 15:54:12 +01:00
Itamar Haber bf30f5a739 Adds keyspace notifications for lrem 2016-02-05 15:54:12 +01:00
antirez 0c7c7631d2 Typo ASII -> ASCII fixed in comment. 2016-01-29 12:08:16 +01:00
antirez 0f3fb0097a Cluster: include node IDs in SLOTS output.
CLUSTER SLOTS now includes IDs in the nodes description associated with
a given slot range. Certain client libraries implementations need a way
to reference a node in an unique way, so they were relying on CLUSTER
NODES, that is not a stable API and may change frequently depending on
Redis Cluster future requirements.
2016-01-29 12:02:23 +01:00
antirez 99eb062314 Changelog of 3.2.0 RC2/RC3 split into two sections. 2016-01-28 12:47:46 +01:00
antirez d5dab73127 Redis 3.1.103 (Redis 3.2.0 RC3). 2016-01-28 12:43:30 +01:00
antirez 6bd409d0c8 Sentinel: improve handling of known Sentinel instances.
1. Bug #3035 is fixed (NULL pointer access). This was happening with the
   folling set of conditions:

* For some reason one of the Sentinels, let's call it Sentinel_A, changed ID (reconfigured from scratch), but is as the same address at which it used to be.

* Sentinel_A performs a failover and/or has a newer configuration compared to another Sentinel, that we call, Sentinel_B.

* Sentinel_B receives an HELLO message from Sentinel_A, where the address and/or ID is mismatched, but it is reporting a newer configuration for the master they are both monitoring.

2. Sentinels now must have an ID otherwise they are not loaded nor persisted in the configuration. This allows to have conflicting Sentinels with the same address since now the master->sentinels dictionary is indexed by Sentinel ID.

3. The code now detects if a Sentinel is annoucing itself with an IP/port pair already busy (of another Sentinel). The old Sentinel that had the same port/pair is set as having port 0, that means, the address is invalid. We may discover the right address later via HELLO messages.
2016-01-27 16:35:15 +01:00
antirez b81fb9cdbc Use a smoother running average for avg_ttl in INFO.
Reported here:
https://www.reddit.com/r/redis/comments/42r0i0/avg_ttl_varies_a_lot/
2016-01-26 15:29:45 +01:00
antirez c0acccc3d3 Cluster: mismatch sender ID log put back at DEBUG level. 2016-01-26 14:21:23 +01:00
antirez 7f97d75ef6 Cluster: fix missing ntohs() call to access gossip section port. 2016-01-26 14:20:08 +01:00
antirez bd998b7d46 Better address udpate strategy when processing gossip sections.
The change covers the case where:

1. There is a node we can't reach (in fail or pfail state).
2. We see a different address for this node, in the gossip section sent
to us by a node that, instead, is able to talk with the node we cannot
talk to.

In this case it's a good bet to switch to the address reported by this
node, since there was an address switch and it is able to talk with the
node and we are not.

However previosuly this was done in a dangerous way, by initiating an
handshake. The handshake, using the MEET packet, forces the receiver to
join our cluster, and this is not a good idea. If the node in question
really just switched address, but is the same node, it already knows about
us, so we just need to perform an address update and a reconnection.

So with this commit instead we just update the address of the node,
release the node link if any, and attempt to reconnect in the next
clusterCron() cycle.

The commit also improves debugging messages printed by Cluster during
address or ID switches.
2016-01-26 14:20:08 +01:00
antirez 73e4e40e50 Fix memory leak in masterauth config option loading. 2016-01-26 14:20:08 +01:00
Itamar Haber 27be786c57 Removes an extra space in protected mode message 2016-01-26 14:20:08 +01:00
antirez 8a8fe56c3b Redis 3.1.102 (Redis 3.2.0 RC2). 2016-01-25 15:52:12 +01:00
antirez 33c4da4a08 Minor MIGRATE refactoring.
Centralize cleanup of newargv in a single place.
Add more comments to help a bit following a complex function.

Related to issue #3016.
2016-01-25 15:20:26 +01:00
antirez 5a5e323181 More variadic MIGRATE fixes.
Another leak was fixed in the case of syntax error by restructuring the
allocation strategy for the two dynamic vectors.

We also make sure to always close the cached socket on I/O errors so that
all the I/O errors are handled the same, even if we had a previously
queued error of a different kind from the destination server.

Thanks to Kevin McGehee. Related to issue #3016.
2016-01-25 15:20:22 +01:00
antirez 77837a91d5 Various fixes to MIGRATE with multiple keys.
In issue #3016 Kevin McGehee identified multiple very serious issues in
the new implementation of MIGRATE. This commit attempts to restructure
the code in oder to avoid mistakes, an analysis of the new
implementation is in progress in order to check for possible edge cases.
2016-01-25 15:20:19 +01:00
antirez c476dbad96 Test: Handle LOADING in restart_instance. 2016-01-25 15:20:16 +01:00
antirez 3148b52285 Detect and show crashes on Sentinel/Cluster tests. 2016-01-25 15:20:12 +01:00
antirez c9889461aa Cluster: fix setting nodes slaveof pointer to NULL on node release.
With this commit we preserve the list of nodes that have .slaveof set
to the node, even when the node is turned into a slave, and make sure to
fix the .slaveof pointers to NULL when a node is freed from memory,
regardless of the fact it's a slave or a master.

Basically we try to remember the logical master in the current
configuration even if the logical master advertised it as a slave
already. However we still remember the associations, so that when a node
is freed we can fix them.

This should fix issue #3002.
2016-01-25 15:20:08 +01:00
antirez 08db70928b Cluster: clarify node->slave may be NULL. 2016-01-25 15:20:05 +01:00
antirez bcc556eb5b Cluster: fix rebalancing to always empty nodes.
Because of rounding error even with weight=0 sometimes a node was left
with an assigned slot.

Close #3001.
2016-01-25 15:20:01 +01:00
antirez 557e7c3af3 Cluster: redis-trib move_to_slot: don't send SETSLOT to slaves. 2016-01-25 15:19:57 +01:00
antirez 18f62b127b Cluster: fix redis-trib reference of variable in warning. 2016-01-25 15:19:54 +01:00
antirez ace53e899a CLUSTER BUMPEPOCH initial implementation fixed. 2016-01-25 15:19:50 +01:00
antirez eb2d95a47c Cluster: implement redis-trib fix when slot is open without owners.
Still work to do.
2016-01-25 15:19:47 +01:00
antirez 874ad0cbdf Cluster: implement redis-trib fix for uncovered slots. 2016-01-25 15:19:47 +01:00
antirez 90a79bc1b6 Cluster: CLUSTER BUMPEPOCH introduced to help redis-trib fix.
Sometimes during "fixes" we have to setup a new configuration and assign
slots to nodes. With BUMPEPOCH we can make sure the new configuration of
the node will win if there are conflicting configurations (for example
another node is *also* claiming the same slot because the cluster is
totally messed up).
2016-01-25 15:19:35 +01:00
antirez 6ce536db49 Cluster: don't allow CLUSTER SETSLOT with slaves. 2016-01-25 15:19:32 +01:00
antirez a7ec6d1ffa Cluster: check packets length before accessing far fields. 2016-01-19 13:18:05 +01:00
antirez 5fd61c9558 Scripting: handle trailing comments.
This fix, provided by Paul Kulchenko (@pkulchenko), allows the Lua
scripting engine to evaluate statements with a trailing comment like the
following one:

    EVAL "print() --comment" 0

Lua can't parse the above if the string does not end with a newline, so
now a final newline is always added automatically. This does not change
the SHA1 of scripts since the SHA1 is computed on the body we pass to
EVAL, without the other code we add to register the function.

Close #2951.
2016-01-08 15:45:13 +01:00
antirez d975baa35b Allow MIGRATE to always be called on local keys for open slots.
Extend the MIGRATE extra freedom to be able to be called in the context
of the local slot, anytime there is a slot open in one or the other
direction (importing or migrating). This is useful for redis-trib to fix
the cluster when it has in an odd state.

Thix fix allows "redis-trib fix" to make its work in certain cases where
previously an error was reported.
2016-01-08 15:35:32 +01:00
antirez 515bbdfcdd Fix typos & grammar in clusterBumpConfigEpochWithoutConsensus() comment. 2016-01-08 15:35:28 +01:00
antirez 6cbd559679 Lua debugger: support direct calls to SCRIPT DEBUG in redis-cli.
Previously it was possible to activate a debugging session only using
the --ldb option in redis-cli. Now calling SCRIPT DEBUG can also
activate the debugging mode without putting the redis-cli in a
desynchronized state.

Related to #2952.
2016-01-08 15:35:23 +01:00
antirez 8cc1a49edf Lua debugger: fix crash printing nested or deep objects.
Example of offending code:

> script debug yes
OK
> eval "local a = {1} a[1] = a\nprint(a)" 0
1) * Stopped at 1, stop reason = step over
2) -> 1   local a = {1} a[1] = a
> next
1) * Stopped at 2, stop reason = step over
2) -> 2   print(a)
> print

... server crash ...

Close #2955.
2016-01-08 15:35:18 +01:00
antirez d256abe9c0 Another typo in protected mode error message. 2016-01-08 15:35:14 +01:00
antirez 068461521f Fix protected mode error message typo. 2016-01-08 15:35:09 +01:00
antirez 273c49e726 New security feature: Redis protected mode.
An exposed Redis instance on the internet can be cause of serious
issues. Since Redis, by default, binds to all the interfaces, it is easy
to forget an instance without any protection layer, for error.

Protected mode try to address this feature in a soft way, providing a
layer of protection, but giving clues to Redis users about why the
server is not accepting connections.

When protected mode is enabeld (the default), and if there are no
minumum hints about the fact the server is properly configured (no
"bind" directive is used in order to restrict the server to certain
interfaces, nor a password is set), clients connecting from external
intefaces are refused with an error explaining what to do in order to
fix the issue.

Clients connecting from the IPv4 and IPv6 lookback interfaces are still
accepted normally, similarly Unix domain socket connections are not
restricted in any way.
2016-01-08 15:35:06 +01:00
antirez b18e42b23e Cluster: don't send -ASK to MIGRATE.
For non existing keys, we don't want to send -ASK redirections to
MIGRATE, since when moving slots from the migrating node to the
importing node, we want just to ignore keys that are no longer there.
They may be expired or deleted between the GETKEYSINSLOT call and the
MIGRATE call. Otherwise this causes an error during migrations with
redis-trib (or equivalent cluster management tools).
2016-01-06 12:18:55 +01:00
antirez 2ebfd0f7c5 Cluster test: do leaks detection with OSX leaks utility. 2016-01-02 13:25:20 +01:00
antirez 13a70eb684 redis-trib: Remove duplicated key in hash initialization. 2016-01-02 13:25:20 +01:00
antirez e4f1994bd1 Cluster/Sentinel test: report ability to run via valgrind. 2015-12-29 15:27:52 +01:00
antirez bb3ed7e2d0 Changelog for 3.2 release: more details and credits. 2015-12-23 16:06:30 +01:00
45 changed files with 3117 additions and 452 deletions
+901 -2
View File
@@ -10,6 +10,892 @@ HIGH: There is a critical bug that may affect a subset of users. Upgrade!
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
--------------------------------------------------------------------------------
================================================================================
Redis 3.2.0 Released Fri May 06 08:47:10 CEST 2016
================================================================================
Upgrade urgency HIGH: 3.2-RC users should upgrade ASAP to this more
stable release of Redis.
Hello, this is Redis 3.2.0 stable! And with the new release there is a new
format for the changelog. Now it is composed of a "letter" like the one
you are reading, followed by all the commits composing the release for people
interested in the details.
There are a few changes between the latest release candidate of Redis 3.2
and the final 3.2.0 release. The main ones are the obvious bug fixes you
can find detailed below, also a few new things:
Critical bugs fixed:
* There was a problem in the way a cluster instance loaded the AOF that
could cause data written via scripts to be lost during reshardings.
New things:
* There is a new very powerful BITFIELD command. Check the documentation
here: http://redis.io/commands/BITFIELD
* CONFIG GET is allowed during the loading of the dataset.
* The DEBUG command have new features and can show an help with DEBUG HELP.
* redis-cli show hits about the commands arguments to the right.
* GEORADIUS got a STORE / STOREDIST option to store the result into a target
key (as as orted set) instead of reporting it to the user.
* Redis Cluster replicas migration now works in a slightly different way. In
the past a slave could migrate only to a master that used to have slaves
in the past (and if there was still trace of this information). Now instead
if a new slave gets at least a slot, and at least one other master in the
cluster has a slave, then the new master is considered a valid target for
replica migration. So if it will be orphaned and there is a spare slave
it will get one.
* CLUSTER SLOTS output now includes the node ID (in a backward compatible
manner).
* A lot of bug fixes you can read in the commits details below, with the
authors that kindly provided many useful patches. Thanks.
As usually what "stable" means is that we are confident the release is mature,
however do your tests before putting it into production in your environemnt
as it's fresh meat anyway.
Cheers,
Salvatore
+-------------------------------------------------------------------------------
| Cluster: don't check scripts key slots during AOF loading.
| By antirez, 2016-05-05 23:37:08 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/746e1bebb4baf876c1e8cd2fe2d468a1c41e5dc3
src/scripting.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
+-------------------------------------------------------------------------------
| redis-cli: remove debugging message.
| By antirez, 2016-05-05 18:05:37 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/3907b059280898c6340b53982b3b65957535da7c
src/redis-cli.c | 1 -
1 file changed, 1 deletion(-)
+-------------------------------------------------------------------------------
| Revert "Fix commandCommand arity"
| By antirez, 2016-05-05 17:35:33 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/f01a2714582a9a916b41e6cb90e42474a104925d
This reverts commit 1189a4eae6d009fc0da8d50fd542ba1391542165.
Actually this is wrong, the command can be called without args at all.
src/server.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Fix commandCommand arity
| By Ruben Bridgewater, 2016-01-04 11:39:45 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/1189a4eae6d009fc0da8d50fd542ba1391542165
src/server.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Fix a possible race condition of sdown detection if the connection to master/slave/sentinel decames disconnected just after the last PONG and before the next PING.
| By Daniel Shih, 2016-01-12 17:06:47 +0800
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/d9dc0d777b775f2fe4f23f42e1b4130f4de1ed72
src/sentinel.c | 2 ++
1 file changed, 2 insertions(+)
+-------------------------------------------------------------------------------
| Fix nanosecond conversion
| By Jan-Erik Rediger, 2016-01-13 10:22:29 -0700
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/13bd702844eda7bccc7c672b6ff77f53e21ab841
1 microsecond = 1000 nanoseconds
1e3 = 1000
10e3 = 10000
src/redis-cli.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| redis-cli: don't free historyfile, is used later.
| By antirez, 2016-05-05 13:57:57 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/f6b7df3aec1d8fddf3121c4315fea4bf45cc4cfe
src/redis-cli.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Cluster test 12: reshard back just a few slots to speedup the test.
| By antirez, 2016-05-05 11:49:27 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/97ce72fa2d348f914fc40ccf0a32008c7362b817
tests/cluster/tests/12-replica-migration-2.tcl | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
+-------------------------------------------------------------------------------
| Cluster: make getNodeByQuery() responsible of -CLUSTERDOWN errors.
| By antirez, 2016-05-05 11:33:43 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/708f486c77b455fa7ae1e87cf31278474eb9d432
This fixes a bug introduced by d827dbf, and makes the code consistent
with the logic of always allowing, while the cluster is down, commands
that don't target any key.
As a side effect the code is also simpler now.
src/cluster.c | 13 +++++++++++--
src/server.c | 26 ++++++++++----------------
2 files changed, 21 insertions(+), 18 deletions(-)
+-------------------------------------------------------------------------------
| Fixes a typo
| By Itamar Haber, 2016-01-28 21:47:18 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/1fc2ed61bec2f2bf266b48e566a197c5214b4350
src/cluster.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Reverse redirect address parse
| By David Cavar, 2016-02-09 15:04:42 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/787d5ab9be45bf4feb6cfa8a227c185c93aaadae
Fix issue in case the redirect address is in ipv6 format. Parse from behind to extract last part of the response which represents actual port.
src/redis-cli.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Bind both IPv4 and IPv6 or exit with an error by default.
| By antirez, 2016-05-05 10:02:42 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/0fda06225f19b18a370d63ac650a8bb61020235d
Thanks to @tushar2708 for the PR. I applied a slightly different fix.
Thanks to @cespare for reporting.
Close #3024
Close #3020
src/server.c | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
+-------------------------------------------------------------------------------
| Quick fix to avoid false positive in replica migration test.
| By antirez, 2016-05-05 09:45:31 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/65707fa6b5bb38b5818af57ab93b9b1da47d708a
tests/cluster/tests/12-replica-migration-2.tcl | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Ensure slots are rechecked on EXEC.
| By Chris Thunes, 2016-03-11 15:22:36 -0500
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/1f3ed652e2a6cfcbbd5300a31f5771f2ce842e98
Fixes #2515.
src/server.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
+-------------------------------------------------------------------------------
| fix for #3187
| By therealbill, 2016-04-22 10:43:48 -0500
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/3a4810678904af013901a896ab0bb3c71856644c
I've renamed maxmemoryToString to evictPolicyToString since that is
more accurate (and easier to mentally connect with the correct data), as
well as updated the function to user server.maxmemory_policy rather than
server.maxmemory. Now with a default config it is actually returning
the correct policy rather than volatile-lru.
src/config.c | 4 ++--
src/server.c | 2 +-
src/server.h | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
+-------------------------------------------------------------------------------
| Fixed typo in README.md
| By Seth Bergman, 2016-04-30 11:58:48 -0500
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/bba53d7fc7a759b53d72a7c3be17a00abe32039e
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| fix variable
| By Ryosuke Hasebe, 2016-03-30 23:09:36 +0900
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/ab7581489871eebcf0253915acc26efb27f5df23
src/redis-trib.rb | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
+-------------------------------------------------------------------------------
| fix check_open_slots
| By Ryosuke Hasebe, 2016-03-30 21:56:22 +0900
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/0fcf896ac14c6e5c57e3b79e836dd8bf7dad94b5
src/redis-trib.rb | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Minor redis-cli wording change in --help output.
| By antirez, 2016-05-04 22:34:17 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/b8a63635e39ce312b2c5664f2c2a59184849b8fb
src/redis-cli.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Allow CONFIG GET during loading.
| By antirez, 2016-05-04 15:45:38 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/ae95de93312d6bb2c8c365c31f2a3fd0b6d2c100
Thanks to @oranagra for the idea of allowing CONFIG GET during loading.
src/config.c | 6 ++++++
src/server.c | 2 +-
2 files changed, 7 insertions(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Command "r" flag removed from commands not accessing the key space.
| By antirez, 2016-05-04 15:42:14 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/3ff8f57ef32e3b1ddd407b65d474f610881bef34
Thanks to @oranagra for the hint about misplaced 'r' flags.
src/server.c | 70 ++++++++++++++++++++++++++++++------------------------------
1 file changed, 35 insertions(+), 35 deletions(-)
+-------------------------------------------------------------------------------
| DEBUG command self documentation.
| By antirez, 2016-05-04 12:45:55 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/07b852d24ec8d7cb47044b096baf0af1410c69ed
src/debug.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++++-
src/server.c | 2 +-
2 files changed, 49 insertions(+), 2 deletions(-)
+-------------------------------------------------------------------------------
| various cleanups and minor fixes
| By Oran Agra, 2016-04-25 16:49:57 +0300
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/cfc08b65b04cf972ebeadf3be9f39f2acd7672b3
src/adlist.c | 18 ++++++------------
src/ae_epoll.c | 6 ++----
src/aof.c | 5 ++++-
src/db.c | 2 +-
src/rdb.c | 6 ++++--
src/rio.c | 5 +++--
src/rio.h | 2 ++
src/server.h | 3 +--
tests/support/test.tcl | 6 ------
tests/unit/bitops.tcl | 2 +-
tests/unit/other.tcl | 1 +
tests/unit/scripting.tcl | 27 ++++++++++++++-------------
12 files changed, 39 insertions(+), 44 deletions(-)
+-------------------------------------------------------------------------------
| dict.c minor optimization
| By Oran Agra, 2016-04-25 16:48:25 +0300
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/6710c8dc15680b756de26d1d45df110987b951b3
src/dict.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
+-------------------------------------------------------------------------------
| networking.c minor optimization
| By Oran Agra, 2016-04-25 16:48:09 +0300
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/be5c0869821b16f18104d9ea62ab42bfa0615816
src/networking.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
+-------------------------------------------------------------------------------
| add DEBUG JEMALLC PURGE and JEMALLOC INFO cleanup
| By Oran Agra, 2016-04-25 16:47:42 +0300
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/3ede6c7a506bcff3c7d2147c8af140dd4102a36e
src/debug.c | 17 ++++++++++++++++-
src/redis-cli.c | 2 +-
2 files changed, 17 insertions(+), 2 deletions(-)
+-------------------------------------------------------------------------------
| fix small issues in redis 3.2
| By Oran Agra, 2016-04-25 14:19:28 +0300
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/e2fab184efd01ffa0d510739f4d819a30b1d6787
src/config.c | 2 ++
src/rdb.c | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| additional fix to issue #2948
| By Oran Agra, 2016-04-25 14:18:40 +0300
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/9d57ceec08687545c2a63a2c18c28e2bcaf74664
src/networking.c | 3 +++
1 file changed, 3 insertions(+)
+-------------------------------------------------------------------------------
| Reply with error on negative geo radius.
| By antirez, 2016-05-04 08:59:56 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/7ba2cf5f8bea9cd125e773de147e3ea9b19ee2b0
Thanks to @tidwall for reporting.
Close #3194.
src/geo.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Cluster regression test for #3043.
| By antirez, 2016-05-02 18:36:09 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/38cf2bd257d09b360be6adf450d59592a359696d
The test works but is very slow so far, since it involves resharding
1/5 of all the cluster slots from master 0 to the other 4 masters and
back into the original master.
tests/cluster/tests/12-replica-migration-2.tcl | 61 ++++++++++++++++++++++++++
1 file changed, 61 insertions(+)
+-------------------------------------------------------------------------------
| New masters with slots are now targets of migration if others are.
| By antirez, 2016-05-02 16:41:56 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/7b618823c189c24313effbccda7022d167ea419c
This fixes issue #3043.
Before this fix, after a complete resharding of a master slots
to other nodes, the master remains empty and the slaves migrate away
to other masters with non-zero nodes. However the old master now empty,
is no longer considered a target for migration, because the system has
no way to tell it had slaves in the past.
This fix leaves the algorithm used in the past untouched, but adds a
new rule. When a new or old master which is empty and without slaves,
are assigend with their first slot, if other masters in the cluster have
slaves, they are automatically considered to be targets for replicas
migration.
src/cluster.c | 36 +++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Test ZINCRBY return value.
| By antirez, 2016-05-02 08:57:30 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/1101b515c73dcb0ff4a868ba38a91dcebb5b85cc
tests/unit/type/zset.tcl | 6 ++++++
1 file changed, 6 insertions(+)
+-------------------------------------------------------------------------------
| redis-cli preferences and rc file support.
| By antirez, 2016-04-13 13:58:30 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/bbf93108ef2a7ac7bd53e26c96bf838f7c5e72df
src/redis-cli.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 85 insertions(+), 17 deletions(-)
+-------------------------------------------------------------------------------
| redis-cli hints.
| By antirez, 2016-04-13 12:34:14 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/3fd3fca0dd38cb21c900e4ba98e3d41f333335aa
deps/linenoise/linenoise.c | 8 +++++++
deps/linenoise/linenoise.h | 2 +-
src/redis-cli.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 62 insertions(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Linenoise updated again (hints support).
| By antirez, 2016-04-12 23:39:18 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/34354473ffe8d60b80acdbc8c16b194f8bbd70a0
deps/linenoise/README.markdown | 176 ++++++++++++++++++++++++++++++++++++++++-
deps/linenoise/example.c | 10 +++
deps/linenoise/linenoise.c | 55 ++++++++++++-
deps/linenoise/linenoise.h | 15 +++-
4 files changed, 248 insertions(+), 8 deletions(-)
+-------------------------------------------------------------------------------
| Linenoise updated.
| By antirez, 2016-04-06 13:37:27 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/58229cd766fa02f399267310b5b488aac2c55a6a
As a side effect, cat commands.txt | redis-cli now is able to handle
lines more than 4096 bytes.
deps/linenoise/linenoise.c | 60 ++++++++++++++++++++++++++++++++++------------
1 file changed, 45 insertions(+), 15 deletions(-)
+-------------------------------------------------------------------------------
| ae.c: Fix delay until next timer event.
| By antirez, 2016-04-04 14:08:16 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/29645f1f44926ac2662ed768fe1e369775a30b66
This fix was written by Anthony LaTorre.
The old code mis-calculated the amount of time to wait till next event.
src/ae.c | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
+-------------------------------------------------------------------------------
| ae.c: comment to explain why we have a useless maxId check.
| By antirez, 2016-04-04 12:23:10 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/aa79c1f1c1d288e4cc2c0f4689ad01488678bf9d
src/ae.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Fix ae.c to avoid timers infinite loop.
| By antirez, 2016-01-08 15:05:14 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/0b69c9865619b54b75576e67aaafe7b018734bec
This fix was suggested by Anthony LaTorre, that provided also a good
test case that was used to verify the fix.
The problem with the old implementation is that, the time returned by
a timer event (that is the time after it want to run again) is added
to the event *start time*. So if the event takes, in order to run, more
than the time it says it want to be scheduled again for running, an
infinite loop is triggered.
src/ae.c | 51 ++++++++++++++++++++++-----------------------------
src/ae.h | 3 +++
src/anet.h | 2 ++
src/redis-trib.rb | 2 +-
4 files changed, 28 insertions(+), 30 deletions(-)
+-------------------------------------------------------------------------------
| BITFIELD: overflow wrap behavior fuzz tester.
| By antirez, 2016-03-02 17:22:42 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/258857cd62e0eb70da32742651a55213fbc28a53
tests/unit/bitfield.tcl | 51 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
+-------------------------------------------------------------------------------
| BITFIELD basic unit tests.
| By antirez, 2016-03-02 16:31:02 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/35d05d6dc5d7fb810e732f9de7b05c6b14933fb6
tests/unit/bitfield.tcl | 86 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 85 insertions(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| BITFIELD: Farest bit set is offset+bits-1. Off by one error fixed.
| By antirez, 2016-03-02 16:20:28 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/6cf83feffad7f7b7693a207a9d63278354769ece
src/bitops.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
+-------------------------------------------------------------------------------
| BITFIELD: overflow fuzzy testing.
| By antirez, 2016-03-02 15:15:18 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/b4d65c9501be01531eb8fbeb4ba2b444e35d6494
tests/test_helper.tcl | 1 +
tests/unit/bitfield.tcl | 52 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 53 insertions(+)
+-------------------------------------------------------------------------------
| Fix typo in bitops.tcl comment.
| By antirez, 2016-03-02 15:14:53 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/d75e0fdbe6ecf3074b18c4dd1e8b71a474254ccc
tests/unit/bitops.tcl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| More BITFIELD fixes. Overflow conditional simplified.
| By antirez, 2016-03-02 15:13:45 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/5f3ef73ed41be6e051daad9fdb7efa91b4ca682d
See issue #3114.
src/bitops.c | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
+-------------------------------------------------------------------------------
| bitops/bitfield: fix length, overflow condition and *sign
| By Sun He, 2016-03-02 18:11:30 +0800
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/5b9aa5026204fb6c8b0098d4e47c2e001cf06151
src/bitops.c | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
+-------------------------------------------------------------------------------
| Fix INFO commandstats reporting when argv is rewritten.
| By antirez, 2016-03-02 08:51:27 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/ba9154d7e7bf959b002533384319a1e90545447b
We want to report the original command in the stats, for example GEOADD,
even when what is actually executed is the ZADD implementation.
src/scripting.c | 2 +-
src/server.c | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
+-------------------------------------------------------------------------------
| BITFIELD: refactoring & fix of retval on FAIL.
| By antirez, 2016-02-29 09:08:46 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/76b22e3728e37fe11582b71802bf98a2be09623f
src/bitops.c | 32 ++++++++++++++++++++++++--------
1 file changed, 24 insertions(+), 8 deletions(-)
+-------------------------------------------------------------------------------
| BITFIELD: Fix #<index> form parsing.
| By antirez, 2016-02-26 15:53:29 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/c333a9e2d7873190b22aaee8835a188bc55b60dc
src/bitops.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
+-------------------------------------------------------------------------------
| BITFIELD: Support #<index> offsets form.
| By antirez, 2016-02-26 15:16:24 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/f84871cb5dd3bb3c04bfd7ac53b6e06b246f511f
src/bitops.c | 29 +++++++++++++++++++++++------
1 file changed, 23 insertions(+), 6 deletions(-)
+-------------------------------------------------------------------------------
| BITFIELD command initial implementation.
| By antirez, 2016-02-25 23:31:45 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/761a77287107ce53dbf4755f1435cc9660b6c86d
The new bitfield command is an extension to the Redis bit operations,
where not just single bit operations are performed, but the array of
bits composing a string, can be addressed at random, not aligned
offsets, with any width unsigned and signed integers like u8, s5, u10
(up to 64 bit signed integers and 63 bit unsigned integers).
The BITFIELD command supports subcommands that can SET, GET, or INCRBY
those arbitrary bit counters, with multiple overflow semantics.
Trivial and credits:
A similar command was imagined a few times in the past, but for
some reason looked a bit far fetched or not well specified.
Finally the command was proposed again in a clear form by
Yoav Steinberg from Redis Labs, that proposed a set of commands on
arbitrary sized integers stored at bit offsets.
Starting from this proposal I wrote an initial specification of a single
command with sub-commands similar to what Yoav envisioned, using short
names for types definitions, and adding control on the overflow.
This commit is the resulting implementation.
Examples:
BITFIELD mykey OVERFLOW wrap INCRBY i2 10 -1 GET i2 10
src/bitops.c | 506 +++++++++++++++++++++++++++++++++++++++++++++++++++++++----
src/server.c | 1 +
src/server.h | 1 +
3 files changed, 476 insertions(+), 32 deletions(-)
+-------------------------------------------------------------------------------
| Eliminates engineers near the equator & prime meridian
| By Itamar Haber, 2016-02-18 15:11:30 -0800
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/4b89ea3a95ef72353e14e0eb5cbf293bf2dc8b48
src/geo.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
+-------------------------------------------------------------------------------
| Fixes a typo in a comment
| By Itamar Haber, 2016-02-18 15:01:34 -0800
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/0f46f9bd423a146f96e1993aca6221e1305f9a28
src/geo.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Adjusts accuracy for GEODIST
| By Itamar Haber, 2016-02-18 15:00:39 -0800
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/9de844603c5203ae6ff6e7882a3dcb47a9f79a85
src/geo.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| addReplyHumanLongDouble() API added.
| By antirez, 2016-02-18 22:08:47 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/79e553a58d7f814fd08482b118561fcf9854b1df
Send a long double or double as a bulk reply, in a human friendly
format.
src/networking.c | 9 +++++++++
src/server.h | 1 +
2 files changed, 10 insertions(+)
+-------------------------------------------------------------------------------
| Fix GEORADIUS STORE/DIST refcount after backport to 3.2.
| By antirez, 2016-02-18 15:33:02 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/158d1e2fbf6badb881a4d78623fd1bffde34cc49
src/geo.c | 2 ++
1 file changed, 2 insertions(+)
+-------------------------------------------------------------------------------
| GEOADD STORE/STOREDIST tests.
| By antirez, 2016-02-18 14:42:42 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/123cd88286127cc20684bf0457cb51107a67cfa2
tests/unit/geo.tcl | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
+-------------------------------------------------------------------------------
| New options for GEORADIUS: STORE and STOREDIST.
| By antirez, 2016-02-18 10:24:11 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/0b6daf5a6b27447f03c76387b8b707ce0ab7b08d
Related to issue #3019.
src/geo.c | 143 +++++++++++++++++++++++++++++++++++++++++------------------
src/server.c | 4 +-
src/server.h | 1 +
src/t_zset.c | 18 +++++---
4 files changed, 117 insertions(+), 49 deletions(-)
+-------------------------------------------------------------------------------
| Include full paths on RDB/AOF files errors.
| By antirez, 2016-02-15 16:14:56 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/f7af1beaab938b7e319d1b91d7736f043b7ef682
Close #3086.
src/aof.c | 17 +++++++++++++++--
src/rdb.c | 19 +++++++++++++++++--
2 files changed, 32 insertions(+), 4 deletions(-)
+-------------------------------------------------------------------------------
| Remove Lua state reference from buffers in lua_cmsgpack.
| By antirez, 2016-02-10 09:12:17 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/0b4a628fc716ab6b88c9823b143de174601a790d
deps/lua/src/lua_cmsgpack.c | 53 +++++++++++++++++++++------------------------
1 file changed, 25 insertions(+), 28 deletions(-)
+-------------------------------------------------------------------------------
| cmsgpack: pass correct osize values to lua allocator, update correct buf free space in cmsgpack
| By yoav@monfort.co.il, 2016-02-07 20:24:28 +0200
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/43509f686452fba68966aa89b87d1be065b50e17
deps/lua/src/lua_cmsgpack.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
+-------------------------------------------------------------------------------
| Fixes a typo
| By Itamar Haber, 2016-02-03 11:04:09 -0800
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/cd9f7c6976f81c0e9ffae9e34b31eda19384a7dd
src/server.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
+-------------------------------------------------------------------------------
| Adds keyspace notifications for lrem
| By Itamar Haber, 2016-02-02 09:58:19 -0800
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/bf30f5a73922e5111d6a39da7351e22357213134
src/t_list.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Typo ASII -> ASCII fixed in comment.
| By antirez, 2016-01-29 12:08:10 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/0c7c7631d206964b8bf1dae3db286a3acda4edad
src/server.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
+-------------------------------------------------------------------------------
| Cluster: include node IDs in SLOTS output.
| By antirez, 2016-01-29 12:00:38 +0100
+--------------------------------------------------------------------------------
https://github.com/antirez/redis/commit/0f3fb0097ae70e36ee65468b79f0be4160934bb4
CLUSTER SLOTS now includes IDs in the nodes description associated with
a given slot range. Certain client libraries implementations need a way
to reference a node in an unique way, so they were relying on CLUSTER
NODES, that is not a stable API and may change frequently depending on
Redis Cluster future requirements.
src/cluster.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
~~~~~~~~~~~~~~~~~~~~~~~~~~ OLD STYLE CHANGELOG ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--[ Redis 3.2.0 RC3 (version 3.1.103) ] Release date: 28 jan 2016
Upgrade urgency MODERATE: this release fixes a non trivial Sentinel crash.
* [FIX] avg_ttl reporting in INFO improved. (Salvatore Sanfilippo)
* [FIX] Sentinel: improve handling of known Sentinel instances.
(Salvatore Sanfilippo, thanks to Paweł Kowalczykf or the original
crash report).
* [FIX] Redis Cluster address update (via gossip section) processing improved
to avoid initiating inwanted handshakes.
--[ Redis 3.2.0 RC2 (version 3.1.102) ] Release date: 25 jan 2016
Upgrade urgency MODERATE: this release fixes important Redis Cluster bugs.
* [FIX] Many fixes to MIGRATE multiple keys implementation. The command
could handle errors in a faulty way leading to crashes or other
unexpected behaviors. MIGRATE command refactoring.
(The analysis of the faulty conditions was conducted by
Kevin McGehee. The fix was developed by Salvatore Sanfilippo)
* [FIX] A Redis Cluster node crash was fixed because of wrong handling of
node->slaveof pointers.
(Reported by JackyWoo, fixed by Salvatore Sanfilippo)
* [FIX] Fix redis-trib rebalance when nodes need to be left empty because
the specified weight is zero.
(Reported by Shahar Mor, fixed by Salvatore Sanfilippo)
* [FIX] MIGRATE: Never send -ASK redirections for MIGRATE when there are
open slots. Redis-trib and other cluster management utility must
always be free to move keys between nodes about open slots, in order
to reshard, fix the cluster configuration, and so forth.
(Salvatore Sanfilippo)
* [FIX] Lua debugger crash when printing too deeply nested objects.
(Reported by Paul Kulchenko, fixed by Salvatore Sanfilippo)
* [FIX] Redis-cli implementation of Lua debugging now allows to use the
SCRIPT DEBUG command directly, switching to debugging mode as needed.
(Reported by Paul Kulchenko, fixed by Salvatore Sanfilippo)
* [FIX] Redis-trib is now able to fix more errors. A new CLUSTER subcommand
called BUMPEPOCH was introduced in order to support new modes
for the "fix" subcommand. (Salvatore Sanfilippo)
* [NEW] Redis proctected mode: this feature improves Redis security and makes
harder to run Redis in a configuration that is unsecure because no
firewalling was used in order to protect Redis from external accesses.
See this Reddit post for more info: https://www.reddit.com/r/redis/comments/3zv85m/new_security_feature_redis_protected_mode/ (Salvatore Sanfilippo)
* [NEW] Cluster/Sentinel tests now use OSX leak to perform leak detection
at the end of every unit. (Salvatore Sanfilippo)
* [NEW] Detect and show server crashes during Cluster/Sentinel tests.
(Salvatore Sanfilippo)
* [NEW] More reliable Cluster/Sentinel test becuase of timing errors and
-LOADING errors. (Salvatore Sanfilippo)
--[ Redis 3.2.0 RC1 (version 3.1.101) ] Release date: 23 dec 2015
This is the first release candidate of Redis 3.2. The changelog above shows
@@ -23,6 +909,9 @@ General changes:
* [NEW] Lua scripts "effect replication". Makes possible to write scripts
with side effects, use of random commands, and so forth.
(Salvatore Sanfilippo)
* [NEW] Lua scripts selective replication. Makes possible to replicate to
slaves and AOF only selected parts of a script. (Design by
Yossi Gottlieb and Salvatore Sanfilippo, implemented by Salvatore)
* [NEW] Geo indexing support via GEOADD, GEORADIUS and other commands.
See http://redis.io/commands/geoadd for more information.
(Initially implemented in a fork of Redis called "Ardb".
@@ -32,6 +921,8 @@ General changes:
and unified the duplicated code with t_zset.c)
* [NEW] Lua debugger. A complete stepping, remote debugger for Lua scripts.
Video here: https://www.youtube.com/watch?v=IMvRfStaoyM
(Salvatore Sanfilippo with many feedbacks and testing from
Itamar Haber)
* [NEW] SDS improvements for speed and maximum string length.
This makes Redis more memory efficient in different use cases.
(Design and implementation by Oran Agra, some additional work
@@ -72,8 +963,16 @@ General changes:
Redis Cluster changes:
* All the Redis Cluster changes in 3.2 were backported to 3.0, so there is
technically nothing new for now in this release.
All the Redis Cluster changes in 3.2 were backported to 3.0, so there is
technically nothing new for now in this release. The most important things
are:
* Cluster rebalancing.
* A pipelined MIGRATE command which is 10x faster and makes resharding
and rebalancing faster.
* Improved replicas migration.
* As a side effect of quicklists encoding (see above items), moving big
lists between nodes is now a lot faster.
Redis Sentinel changes:
+1 -1
View File
@@ -48,7 +48,7 @@ Fixing build problems with dependencies or cached build options
Redis has some dependencies which are included into the `deps` directory.
`make` does not rebuild dependencies automatically, even if something in the
source code of dependencies is changes.
source code of dependencies is changed.
When you update the source code with `git pull` or when code inside the
dependencies tree is modified in any other way, make sure to use the following
+174 -2
View File
@@ -6,6 +6,7 @@ MongoDB, and Android.
* Single and multi line editing mode with the usual key bindings implemented.
* History handling.
* Completion.
* Hints (suggestions at the right of the prompt as you type).
* About 1,100 lines of BSD license source code.
* Only uses a subset of VT100 escapes (ANSI.SYS compatible).
@@ -20,7 +21,7 @@ So what usually happens is either:
The result is a pollution of binaries without line editing support.
So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporing line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to linenoise if not.
So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporing line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not.
## Terminals, in 2010.
@@ -41,12 +42,183 @@ The library is currently about 1100 lines of code. In order to use it in your pr
* IBM AIX 6.1
* FreeBSD xterm ($TERM = xterm)
* ANSI.SYS
* Emacs comint mode ($TERM = dumb)
Please test it everywhere you can and report back!
## Let's push this forward!
Patches should be provided in the respect of linenoise sensibility for small
Patches should be provided in the respect of Linenoise sensibility for small
easy to understand code.
Send feedbacks to antirez at gmail
# The API
Linenoise is very easy to use, and reading the example shipped with the
library should get you up to speed ASAP. Here is a list of API calls
and how to use them.
char *linenoise(const char *prompt);
This is the main Linenoise call: it shows the user a prompt with line editing
and history capabilities. The prompt you specify is used as a prompt, that is,
it will be printed to the left of the cursor. The library returns a buffer
with the line composed by the user, or NULL on end of file or when there
is an out of memory condition.
When a tty is detected (the user is actually typing into a terminal session)
the maximum editable line length is `LINENOISE_MAX_LINE`. When instead the
standard input is not a tty, which happens every time you redirect a file
to a program, or use it in an Unix pipeline, there are no limits to the
length of the line that can be returned.
The returned line should be freed with the `free()` standard system call.
However sometimes it could happen that your program uses a different dynamic
allocation library, so you may also used `linenoiseFree` to make sure the
line is freed with the same allocator it was created.
The canonical loop used by a program using Linenoise will be something like
this:
while((line = linenoise("hello> ")) != NULL) {
printf("You wrote: %s\n", line);
linenoiseFree(line); /* Or just free(line) if you use libc malloc. */
}
## Single line VS multi line editing
By default, Linenoise uses single line editing, that is, a single row on the
screen will be used, and as the user types more, the text will scroll towards
left to make room. This works if your program is one where the user is
unlikely to write a lot of text, otherwise multi line editing, where multiple
screens rows are used, can be a lot more comfortable.
In order to enable multi line editing use the following API call:
linenoiseSetMultiLine(1);
You can disable it using `0` as argument.
## History
Linenoise supporst history, so that the user does not have to retype
again and again the same things, but can use the down and up arrows in order
to search and re-edit already inserted lines of text.
The followings are the history API calls:
int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(const char *filename);
int linenoiseHistoryLoad(const char *filename);
Use `linenoiseHistoryAdd` every time you want to add a new element
to the top of the history (it will be the first the user will see when
using the up arrow).
Note that for history to work, you have to set a length for the history
(which is zero by default, so history will be disabled if you don't set
a proper one). This is accomplished using the `linenoiseHistorySetMaxLen`
function.
Linenoise has direct support for persisting the history into an history
file. The functions `linenoiseHistorySave` and `linenoiseHistoryLoad` do
just that. Both functions return -1 on error and 0 on success.
## Completion
Linenoise supports completion, which is the ability to complete the user
input when she or he presses the `<TAB>` key.
In order to use completion, you need to register a completion callback, which
is called every time the user presses `<TAB>`. Your callback will return a
list of items that are completions for the current string.
The following is an example of registering a completion callback:
linenoiseSetCompletionCallback(completion);
The completion must be a function returning `void` and getting as input
a `const char` pointer, which is the line the user has typed so far, and
a `linenoiseCompletions` object pointer, which is used as argument of
`linenoiseAddCompletion` in order to add completions inside the callback.
An example will make it more clear:
void completion(const char *buf, linenoiseCompletions *lc) {
if (buf[0] == 'h') {
linenoiseAddCompletion(lc,"hello");
linenoiseAddCompletion(lc,"hello there");
}
}
Basically in your completion callback, you inspect the input, and return
a list of items that are good completions by using `linenoiseAddCompletion`.
If you want to test the completion feature, compile the example program
with `make`, run it, type `h` and press `<TAB>`.
## Hints
Linenoise has a feature called *hints* which is very useful when you
use Linenoise in order to implement a REPL (Read Eval Print Loop) for
a program that accepts commands and arguments, but may also be useful in
other conditions.
The feature shows, on the right of the cursor, as the user types, hints that
may be useful. The hints can be displayed using a different color compared
to the color the user is typing, and can also be bold.
For example as the user starts to type `"git remote add"`, with hints it's
possible to show on the right of the prompt a string `<name> <url>`.
The feature works similarly to the history feature, using a callback.
To register the callback we use:
linenoiseSetHintsCallback(hints);
The callback itself is implemented like this:
char *hints(const char *buf, int *color, int *bold) {
if (!strcasecmp(buf,"git remote add")) {
*color = 35;
*bold = 0;
return " <name> <url>";
}
return NULL;
}
The callback function returns the string that should be displayed or NULL
if no hint is available for the text the user currently typed. The returned
string will be trimmed as needed depending on the number of columns available
on the screen.
It is possible to return a string allocated in dynamic way, by also registering
a function to deallocate the hint string once used:
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
The free hint callback will just receive the pointer and free the string
as needed (depending on how the hits callback allocated it).
As you can see in the example above, a `color` (in xterm color terminal codes)
can be provided together with a `bold` attribute. If no color is set, the
current terminal foreground color is used. If no bold attribute is set,
non-bold text is printed.
Color codes are:
red = 31
green = 32
yellow = 33
blue = 34
magenta = 35
cyan = 36
white = 37;
## Screen handling
Sometimes you may want to clear the screen as a result of something the
user typed. You can do this by calling the following function:
void linenoiseClearScreen(void);
+10
View File
@@ -11,6 +11,15 @@ void completion(const char *buf, linenoiseCompletions *lc) {
}
}
char *hints(const char *buf, int *color, int *bold) {
if (!strcasecmp(buf,"hello")) {
*color = 35;
*bold = 0;
return " World";
}
return NULL;
}
int main(int argc, char **argv) {
char *line;
char *prgname = argv[0];
@@ -34,6 +43,7 @@ int main(int argc, char **argv) {
/* Set the completion callback. This will be called every time the
* user uses the <tab> key. */
linenoiseSetCompletionCallback(completion);
linenoiseSetHintsCallback(hints);
/* Load history from file. The history file is just a plain text file
* where entries are separated by newlines. */
+107 -18
View File
@@ -10,7 +10,7 @@
*
* ------------------------------------------------------------------------
*
* Copyright (c) 2010-2013, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
@@ -120,6 +120,8 @@
#define LINENOISE_MAX_LINE 4096
static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
static linenoiseCompletionCallback *completionCallback = NULL;
static linenoiseHintsCallback *hintsCallback = NULL;
static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
static struct termios orig_termios; /* In order to restore at exit.*/
static int rawmode = 0; /* For atexit() function to check if restore is needed*/
@@ -407,6 +409,18 @@ void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
completionCallback = fn;
}
/* Register a hits function to be called to show hits to the user at the
* right of the prompt. */
void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) {
hintsCallback = fn;
}
/* Register a function to free the hints returned by the hints callback
* registered with linenoiseSetHintsCallback(). */
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) {
freeHintsCallback = fn;
}
/* This function is used by the callback function registered by the user
* in order to add completion options given the input string when the
* user typed <tab>. See the example.c source code for a very easy to
@@ -456,6 +470,30 @@ static void abFree(struct abuf *ab) {
free(ab->b);
}
/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
* to the right of the prompt. */
void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
char seq[64];
if (hintsCallback && plen+l->len < l->cols) {
int color = -1, bold = 0;
char *hint = hintsCallback(l->buf,&color,&bold);
if (hint) {
int hintlen = strlen(hint);
int hintmaxlen = l->cols-(plen+l->len);
if (hintlen > hintmaxlen) hintlen = hintmaxlen;
if (bold == 1 && color == -1) color = 37;
if (color != -1 || bold != 0)
snprintf(seq,64,"\033[%d;%d;49m",bold,color);
abAppend(ab,seq,strlen(seq));
abAppend(ab,hint,hintlen);
if (color != -1 || bold != 0)
abAppend(ab,"\033[0m",4);
/* Call the function to free the hint returned. */
if (freeHintsCallback) freeHintsCallback(hint);
}
}
}
/* Single line low level line refresh.
*
* Rewrite the currently edited line accordingly to the buffer content,
@@ -485,6 +523,8 @@ static void refreshSingleLine(struct linenoiseState *l) {
/* Write the prompt and the current buffer content */
abAppend(&ab,l->prompt,strlen(l->prompt));
abAppend(&ab,buf,len);
/* Show hits if any. */
refreshShowHints(&ab,l,plen);
/* Erase to right */
snprintf(seq,64,"\x1b[0K");
abAppend(&ab,seq,strlen(seq));
@@ -538,6 +578,9 @@ static void refreshMultiLine(struct linenoiseState *l) {
abAppend(&ab,l->prompt,strlen(l->prompt));
abAppend(&ab,l->buf,l->len);
/* Show hits if any. */
refreshShowHints(&ab,l,plen);
/* If we are at the very end of the screen with our prompt, we need to
* emit a newline and move the prompt to the first column. */
if (l->pos &&
@@ -598,7 +641,7 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) {
l->pos++;
l->len++;
l->buf[l->len] = '\0';
if ((!mlmode && l->plen+l->len < l->cols) /* || mlmode */) {
if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) {
/* Avoid a full update of the line in the
* trivial case. */
if (write(l->ofd,&c,1) == -1) return -1;
@@ -772,6 +815,14 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
history_len--;
free(history[history_len]);
if (mlmode) linenoiseEditMoveEnd(&l);
if (hintsCallback) {
/* Force a refresh without hints to leave the previous
* line as the user typed it after a newline. */
linenoiseHintsCallback *hc = hintsCallback;
hintsCallback = NULL;
refreshLine(&l);
hintsCallback = hc;
}
return (int)l.len;
case CTRL_C: /* ctrl-c */
errno = EAGAIN;
@@ -934,24 +985,50 @@ static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
errno = EINVAL;
return -1;
}
if (!isatty(STDIN_FILENO)) {
/* Not a tty: read from file / pipe. */
if (fgets(buf, buflen, stdin) == NULL) return -1;
count = strlen(buf);
if (count && buf[count-1] == '\n') {
count--;
buf[count] = '\0';
}
} else {
/* Interactive editing. */
if (enableRawMode(STDIN_FILENO) == -1) return -1;
count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt);
disableRawMode(STDIN_FILENO);
printf("\n");
}
if (enableRawMode(STDIN_FILENO) == -1) return -1;
count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt);
disableRawMode(STDIN_FILENO);
printf("\n");
return count;
}
/* This function is called when linenoise() is called with the standard
* input file descriptor not attached to a TTY. So for example when the
* program using linenoise is called in pipe or with a file redirected
* to its standard input. In this case, we want to be able to return the
* line regardless of its length (by default we are limited to 4k). */
static char *linenoiseNoTTY(void) {
char *line = NULL;
size_t len = 0, maxlen = 0;
while(1) {
if (len == maxlen) {
if (maxlen == 0) maxlen = 16;
maxlen *= 2;
char *oldval = line;
line = realloc(line,maxlen);
if (line == NULL) {
if (oldval) free(oldval);
return NULL;
}
}
int c = fgetc(stdin);
if (c == EOF || c == '\n') {
if (c == EOF && len == 0) {
free(line);
return NULL;
} else {
line[len] = '\0';
return line;
}
} else {
line[len] = c;
len++;
}
}
}
/* The high level function that is the main API of the linenoise library.
* This function checks if the terminal has basic capabilities, just checking
* for a blacklist of stupid terminals, and later either calls the line
@@ -961,7 +1038,11 @@ char *linenoise(const char *prompt) {
char buf[LINENOISE_MAX_LINE];
int count;
if (isUnsupportedTerm()) {
if (!isatty(STDIN_FILENO)) {
/* Not a tty: read from file / pipe. In this mode we don't want any
* limit to the line size, so we call a function to handle that. */
return linenoiseNoTTY();
} else if (isUnsupportedTerm()) {
size_t len;
printf("%s",prompt);
@@ -980,6 +1061,14 @@ char *linenoise(const char *prompt) {
}
}
/* This is just a wrapper the user may want to call in order to make sure
* the linenoise returned buffer is freed with the same allocator it was
* created with. Useful when the main program is using an alternative
* allocator. */
void linenoiseFree(void *ptr) {
free(ptr);
}
/* ================================ History ================================= */
/* Free the history, but does not reset it. Only used when we have to
+11 -4
View File
@@ -1,12 +1,14 @@
/* linenoise.h -- guerrilla line editing library against the idea that a
* line editing lib needs to be 20,000 lines of C code.
/* linenoise.h -- VERSION 1.0
*
* Guerrilla line editing library against the idea that a line editing lib
* needs to be 20,000 lines of C code.
*
* See linenoise.c for more information.
*
* ------------------------------------------------------------------------
*
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
* Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
@@ -47,10 +49,15 @@ typedef struct linenoiseCompletions {
} linenoiseCompletions;
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
typedef void(linenoiseFreeHintsCallback)(void *);
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
void linenoiseSetHintsCallback(linenoiseHintsCallback *);
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
char *linenoise(const char *prompt);
void linenoiseFree(void *ptr);
int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(const char *filename);
+27 -30
View File
@@ -91,7 +91,6 @@ void memrevifle(void *ptr, size_t len) {
* behavior. */
typedef struct mp_buf {
lua_State *L;
unsigned char *b;
size_t len, free;
} mp_buf;
@@ -111,27 +110,26 @@ mp_buf *mp_buf_new(lua_State *L) {
/* Old size = 0; new size = sizeof(*buf) */
buf = (mp_buf*)mp_realloc(L, NULL, 0, sizeof(*buf));
buf->L = L;
buf->b = NULL;
buf->len = buf->free = 0;
return buf;
}
void mp_buf_append(mp_buf *buf, const unsigned char *s, size_t len) {
void mp_buf_append(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
if (buf->free < len) {
size_t newlen = buf->len+len;
size_t newsize = (buf->len+len)*2;
buf->b = (unsigned char*)mp_realloc(buf->L, buf->b, buf->len, newlen*2);
buf->free = newlen;
buf->b = (unsigned char*)mp_realloc(L, buf->b, buf->len + buf->free, newsize);
buf->free = newsize - buf->len;
}
memcpy(buf->b+buf->len,s,len);
buf->len += len;
buf->free -= len;
}
void mp_buf_free(mp_buf *buf) {
mp_realloc(buf->L, buf->b, buf->len, 0); /* realloc to 0 = free */
mp_realloc(buf->L, buf, sizeof(*buf), 0);
void mp_buf_free(lua_State *L, mp_buf *buf) {
mp_realloc(L, buf->b, buf->len + buf->free, 0); /* realloc to 0 = free */
mp_realloc(L, buf, sizeof(*buf), 0);
}
/* ---------------------------- String cursor ----------------------------------
@@ -173,7 +171,7 @@ void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) {
/* ------------------------- Low level MP encoding -------------------------- */
void mp_encode_bytes(mp_buf *buf, const unsigned char *s, size_t len) {
void mp_encode_bytes(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
unsigned char hdr[5];
int hdrlen;
@@ -197,12 +195,12 @@ void mp_encode_bytes(mp_buf *buf, const unsigned char *s, size_t len) {
hdr[4] = len&0xff;
hdrlen = 5;
}
mp_buf_append(buf,hdr,hdrlen);
mp_buf_append(buf,s,len);
mp_buf_append(L,buf,hdr,hdrlen);
mp_buf_append(L,buf,s,len);
}
/* we assume IEEE 754 internal format for single and double precision floats. */
void mp_encode_double(mp_buf *buf, double d) {
void mp_encode_double(lua_State *L, mp_buf *buf, double d) {
unsigned char b[9];
float f = d;
@@ -211,16 +209,16 @@ void mp_encode_double(mp_buf *buf, double d) {
b[0] = 0xca; /* float IEEE 754 */
memcpy(b+1,&f,4);
memrevifle(b+1,4);
mp_buf_append(buf,b,5);
mp_buf_append(L,buf,b,5);
} else if (sizeof(d) == 8) {
b[0] = 0xcb; /* double IEEE 754 */
memcpy(b+1,&d,8);
memrevifle(b+1,8);
mp_buf_append(buf,b,9);
mp_buf_append(L,buf,b,9);
}
}
void mp_encode_int(mp_buf *buf, int64_t n) {
void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) {
unsigned char b[9];
int enclen;
@@ -289,10 +287,10 @@ void mp_encode_int(mp_buf *buf, int64_t n) {
enclen = 9;
}
}
mp_buf_append(buf,b,enclen);
mp_buf_append(L,buf,b,enclen);
}
void mp_encode_array(mp_buf *buf, int64_t n) {
void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) {
unsigned char b[5];
int enclen;
@@ -312,10 +310,10 @@ void mp_encode_array(mp_buf *buf, int64_t n) {
b[4] = n & 0xff;
enclen = 5;
}
mp_buf_append(buf,b,enclen);
mp_buf_append(L,buf,b,enclen);
}
void mp_encode_map(mp_buf *buf, int64_t n) {
void mp_encode_map(lua_State *L, mp_buf *buf, int64_t n) {
unsigned char b[5];
int enclen;
@@ -335,7 +333,7 @@ void mp_encode_map(mp_buf *buf, int64_t n) {
b[4] = n & 0xff;
enclen = 5;
}
mp_buf_append(buf,b,enclen);
mp_buf_append(L,buf,b,enclen);
}
/* --------------------------- Lua types encoding --------------------------- */
@@ -345,12 +343,12 @@ void mp_encode_lua_string(lua_State *L, mp_buf *buf) {
const char *s;
s = lua_tolstring(L,-1,&len);
mp_encode_bytes(buf,(const unsigned char*)s,len);
mp_encode_bytes(L,buf,(const unsigned char*)s,len);
}
void mp_encode_lua_bool(lua_State *L, mp_buf *buf) {
unsigned char b = lua_toboolean(L,-1) ? 0xc3 : 0xc2;
mp_buf_append(buf,&b,1);
mp_buf_append(L,buf,&b,1);
}
/* Lua 5.3 has a built in 64-bit integer type */
@@ -360,7 +358,7 @@ void mp_encode_lua_integer(lua_State *L, mp_buf *buf) {
#else
lua_Integer i = lua_tointeger(L,-1);
#endif
mp_encode_int(buf, (int64_t)i);
mp_encode_int(L, buf, (int64_t)i);
}
/* Lua 5.2 and lower only has 64-bit doubles, so we need to
@@ -372,7 +370,7 @@ void mp_encode_lua_number(lua_State *L, mp_buf *buf) {
if (IS_INT64_EQUIVALENT(n)) {
mp_encode_lua_integer(L, buf);
} else {
mp_encode_double(buf,(double)n);
mp_encode_double(L,buf,(double)n);
}
}
@@ -386,7 +384,7 @@ void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) {
size_t len = lua_rawlen(L,-1), j;
#endif
mp_encode_array(buf,len);
mp_encode_array(L,buf,len);
for (j = 1; j <= len; j++) {
lua_pushnumber(L,j);
lua_gettable(L,-2);
@@ -409,7 +407,7 @@ void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) {
}
/* Step two: actually encoding of the map. */
mp_encode_map(buf,len);
mp_encode_map(L,buf,len);
lua_pushnil(L);
while(lua_next(L,-2)) {
/* Stack: ... key value */
@@ -474,10 +472,9 @@ void mp_encode_lua_table(lua_State *L, mp_buf *buf, int level) {
void mp_encode_lua_null(lua_State *L, mp_buf *buf) {
unsigned char b[1];
(void)L;
b[0] = 0xc0;
mp_buf_append(buf,b,1);
mp_buf_append(L,buf,b,1);
}
void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level) {
@@ -534,7 +531,7 @@ int mp_pack(lua_State *L) {
buf->free += buf->len;
buf->len = 0;
}
mp_buf_free(buf);
mp_buf_free(L, buf);
/* Concatenate all nargs buffers together */
lua_concat(L, nargs);
+19
View File
@@ -60,6 +60,25 @@
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bind 127.0.0.1
# Protected mode is a layer of security protection, in order to avoid that
# Redis instances left open on the internet are accessed and exploited.
#
# When protected mode is on and if:
#
# 1) The server is not binding explicitly to a set of addresses using the
# "bind" directive.
# 2) No password is configured.
#
# The server only accepts connections from clients connecting from the
# IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain
# sockets.
#
# By default protected mode is enabled. You should disable it only if
# you are sure you want clients from other hosts to connect to Redis
# even if no authentication is configured, nor a specific set of interfaces
# are explicitly listed using the "bind" directive.
protected-mode yes
# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6379
+6 -12
View File
@@ -242,7 +242,7 @@ listNode *listNext(listIter *iter)
list *listDup(list *orig)
{
list *copy;
listIter *iter;
listIter iter;
listNode *node;
if ((copy = listCreate()) == NULL)
@@ -250,26 +250,23 @@ list *listDup(list *orig)
copy->dup = orig->dup;
copy->free = orig->free;
copy->match = orig->match;
iter = listGetIterator(orig, AL_START_HEAD);
while((node = listNext(iter)) != NULL) {
listRewind(orig, &iter);
while((node = listNext(&iter)) != NULL) {
void *value;
if (copy->dup) {
value = copy->dup(node->value);
if (value == NULL) {
listRelease(copy);
listReleaseIterator(iter);
return NULL;
}
} else
value = node->value;
if (listAddNodeTail(copy, value) == NULL) {
listRelease(copy);
listReleaseIterator(iter);
return NULL;
}
}
listReleaseIterator(iter);
return copy;
}
@@ -284,24 +281,21 @@ list *listDup(list *orig)
* NULL is returned. */
listNode *listSearchKey(list *list, void *key)
{
listIter *iter;
listIter iter;
listNode *node;
iter = listGetIterator(list, AL_START_HEAD);
while((node = listNext(iter)) != NULL) {
listRewind(list, &iter);
while((node = listNext(&iter)) != NULL) {
if (list->match) {
if (list->match(node->value, key)) {
listReleaseIterator(iter);
return node;
}
} else {
if (key == node->value) {
listReleaseIterator(iter);
return node;
}
}
}
listReleaseIterator(iter);
return NULL;
}
+38 -38
View File
@@ -221,21 +221,12 @@ long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
{
aeTimeEvent *te, *prev = NULL;
te = eventLoop->timeEventHead;
aeTimeEvent *te = eventLoop->timeEventHead;
while(te) {
if (te->id == id) {
if (prev == NULL)
eventLoop->timeEventHead = te->next;
else
prev->next = te->next;
if (te->finalizerProc)
te->finalizerProc(eventLoop, te->clientData);
zfree(te);
te->id = AE_DELETED_EVENT_ID;
return AE_OK;
}
prev = te;
te = te->next;
}
return AE_ERR; /* NO event with the specified ID found */
@@ -270,7 +261,7 @@ static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
/* Process time events */
static int processTimeEvents(aeEventLoop *eventLoop) {
int processed = 0;
aeTimeEvent *te;
aeTimeEvent *te, *prev;
long long maxId;
time_t now = time(NULL);
@@ -291,12 +282,32 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
}
eventLoop->lastTime = now;
prev = NULL;
te = eventLoop->timeEventHead;
maxId = eventLoop->timeEventNextId-1;
while(te) {
long now_sec, now_ms;
long long id;
/* Remove events scheduled for deletion. */
if (te->id == AE_DELETED_EVENT_ID) {
aeTimeEvent *next = te->next;
if (prev == NULL)
eventLoop->timeEventHead = te->next;
else
prev->next = te->next;
if (te->finalizerProc)
te->finalizerProc(eventLoop, te->clientData);
zfree(te);
te = next;
continue;
}
/* Make sure we don't process time events created by time events in
* this iteration. Note that this check is currently useless: we always
* add new timers on the head, however if we change the implementation
* detail, this check may be useful again: we keep it here for future
* defense. */
if (te->id > maxId) {
te = te->next;
continue;
@@ -310,28 +321,14 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
id = te->id;
retval = te->timeProc(eventLoop, id, te->clientData);
processed++;
/* After an event is processed our time event list may
* no longer be the same, so we restart from head.
* Still we make sure to don't process events registered
* by event handlers itself in order to don't loop forever.
* To do so we saved the max ID we want to handle.
*
* FUTURE OPTIMIZATIONS:
* Note that this is NOT great algorithmically. Redis uses
* a single time event so it's not a problem but the right
* way to do this is to add the new elements on head, and
* to flag deleted elements in a special way for later
* deletion (putting references to the nodes to delete into
* another linked list). */
if (retval != AE_NOMORE) {
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
} else {
aeDeleteTimeEvent(eventLoop, id);
te->id = AE_DELETED_EVENT_ID;
}
te = eventLoop->timeEventHead;
} else {
te = te->next;
}
prev = te;
te = te->next;
}
return processed;
}
@@ -371,19 +368,22 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
if (shortest) {
long now_sec, now_ms;
/* Calculate the time missing for the nearest
* timer to fire. */
aeGetTime(&now_sec, &now_ms);
tvp = &tv;
tvp->tv_sec = shortest->when_sec - now_sec;
if (shortest->when_ms < now_ms) {
tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
tvp->tv_sec --;
/* How many milliseconds we need to wait for the next
* time event to fire? */
long long ms =
(shortest->when_sec - now_sec)*1000 +
shortest->when_ms - now_ms;
if (ms > 0) {
tvp->tv_sec = ms/1000;
tvp->tv_usec = (ms % 1000)*1000;
} else {
tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
tvp->tv_sec = 0;
tvp->tv_usec = 0;
}
if (tvp->tv_sec < 0) tvp->tv_sec = 0;
if (tvp->tv_usec < 0) tvp->tv_usec = 0;
} else {
/* If we have to check for events but need to return
* ASAP because of AE_DONT_WAIT we need to set the timeout
+3
View File
@@ -33,6 +33,8 @@
#ifndef __AE_H__
#define __AE_H__
#include <time.h>
#define AE_OK 0
#define AE_ERR -1
@@ -46,6 +48,7 @@
#define AE_DONT_WAIT 4
#define AE_NOMORE -1
#define AE_DELETED_EVENT_ID -1
/* Macros */
#define AE_NOTUSED(V) ((void) V)
+2 -4
View File
@@ -72,7 +72,7 @@ static void aeApiFree(aeEventLoop *eventLoop) {
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
aeApiState *state = eventLoop->apidata;
struct epoll_event ee;
struct epoll_event ee = {0}; /* avoid valgrind warning */
/* If the fd was already monitored for some event, we need a MOD
* operation. Otherwise we need an ADD operation. */
int op = eventLoop->events[fd].mask == AE_NONE ?
@@ -82,7 +82,6 @@ static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
mask |= eventLoop->events[fd].mask; /* Merge old events */
if (mask & AE_READABLE) ee.events |= EPOLLIN;
if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
ee.data.u64 = 0; /* avoid valgrind warning */
ee.data.fd = fd;
if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;
return 0;
@@ -90,13 +89,12 @@ static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) {
aeApiState *state = eventLoop->apidata;
struct epoll_event ee;
struct epoll_event ee = {0}; /* avoid valgrind warning */
int mask = eventLoop->events[fd].mask & (~delmask);
ee.events = 0;
if (mask & AE_READABLE) ee.events |= EPOLLIN;
if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
ee.data.u64 = 0; /* avoid valgrind warning */
ee.data.fd = fd;
if (mask != AE_NONE) {
epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee);
+2
View File
@@ -31,6 +31,8 @@
#ifndef ANET_H
#define ANET_H
#include <sys/types.h>
#define ANET_OK 0
#define ANET_ERR -1
#define ANET_ERR_LEN 256
+19 -3
View File
@@ -38,6 +38,7 @@
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/param.h>
void aofUpdateCurrentSize(void);
void aofClosePipes(void);
@@ -234,11 +235,20 @@ void stopAppendOnly(void) {
/* Called when the user switches from "appendonly no" to "appendonly yes"
* at runtime using the CONFIG command. */
int startAppendOnly(void) {
char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
server.aof_last_fsync = server.unixtime;
server.aof_fd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);
serverAssert(server.aof_state == AOF_OFF);
if (server.aof_fd == -1) {
serverLog(LL_WARNING,"Redis needs to enable the AOF but can't open the append only file: %s",strerror(errno));
char *cwdp = getcwd(cwd,MAXPATHLEN);
serverLog(LL_WARNING,
"Redis needs to enable the AOF but can't open the "
"append only file %s (in server root dir %s): %s",
server.aof_filename,
cwdp ? cwdp : "unknown",
strerror(errno));
return C_ERR;
}
if (rewriteAppendOnlyFileBackground() == C_ERR) {
@@ -711,6 +721,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
if (!feof(fp)) {
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
serverLog(LL_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
exit(1);
}
@@ -740,10 +751,12 @@ uxeof: /* Unexpected AOF end of file. */
}
}
}
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
serverLog(LL_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.");
exit(1);
fmterr: /* Format error. */
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
serverLog(LL_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>");
exit(1);
}
@@ -753,7 +766,7 @@ fmterr: /* Format error. */
* ------------------------------------------------------------------------- */
/* Delegate writing an object to writing a bulk string or bulk long long.
* This is not placed in rio.c since that adds the redis.h dependency. */
* This is not placed in rio.c since that adds the server.h dependency. */
int rioWriteBulkObject(rio *r, robj *obj) {
/* Avoid using getDecodedObject to help copy-on-write (we are often
* in a child process when this function is called). */
@@ -1417,7 +1430,10 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
latencyStartMonitor(latency);
if (rename(tmpfile,server.aof_filename) == -1) {
serverLog(LL_WARNING,
"Error trying to rename the temporary AOF file: %s", strerror(errno));
"Error trying to rename the temporary AOF file %s into %s: %s",
tmpfile,
server.aof_filename,
strerror(errno));
close(newfd);
if (oldfd != -1) close(oldfd);
goto cleanup;
+511 -34
View File
@@ -34,27 +34,6 @@
* Helpers and low level bit functions.
* -------------------------------------------------------------------------- */
/* This helper function used by GETBIT / SETBIT parses the bit offset argument
* making sure an error is returned if it is negative or if it overflows
* Redis 512 MB limit for the string value. */
static int getBitOffsetFromArgument(client *c, robj *o, size_t *offset) {
long long loffset;
char *err = "bit offset is not an integer or out of range";
if (getLongLongFromObjectOrReply(c,o,&loffset,err) != C_OK)
return C_ERR;
/* Limit offset to 512MB in bytes */
if ((loffset < 0) || ((unsigned long long)loffset >> 3) >= (512*1024*1024))
{
addReplyError(c,err);
return C_ERR;
}
*offset = (size_t)loffset;
return C_OK;
}
/* Count number of bits set in the binary array pointed by 's' and long
* 'count' bytes. The implementation of this function is required to
* work with a input string length up to 512 MB. */
@@ -199,6 +178,202 @@ long redisBitpos(void *s, unsigned long count, int bit) {
return 0; /* Just to avoid warnings. */
}
/* The following set.*Bitfield and get.*Bitfield functions implement setting
* and getting arbitrary size (up to 64 bits) signed and unsigned integers
* at arbitrary positions into a bitmap.
*
* The representation considers the bitmap as having the bit number 0 to be
* the most significant bit of the first byte, and so forth, so for example
* setting a 5 bits unsigned integer to value 23 at offset 7 into a bitmap
* previously set to all zeroes, will produce the following representation:
*
* +--------+--------+
* |00000001|01110000|
* +--------+--------+
*
* When offsets and integer sizes are aligned to bytes boundaries, this is the
* same as big endian, however when such alignment does not exist, its important
* to also understand how the bits inside a byte are ordered.
*
* Note that this format follows the same convention as SETBIT and related
* commands.
*/
void setUnsignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits, uint64_t value) {
uint64_t byte, bit, byteval, bitval, j;
for (j = 0; j < bits; j++) {
bitval = (value & ((uint64_t)1<<(bits-1-j))) != 0;
byte = offset >> 3;
bit = 7 - (offset & 0x7);
byteval = p[byte];
byteval &= ~(1 << bit);
byteval |= bitval << bit;
p[byte] = byteval & 0xff;
offset++;
}
}
void setSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits, int64_t value) {
uint64_t uv;
if (value >= 0)
uv = value;
else
uv = UINT64_MAX + value + 1;
setUnsignedBitfield(p,offset,bits,uv);
}
uint64_t getUnsignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) {
uint64_t byte, bit, byteval, bitval, j, value = 0;
for (j = 0; j < bits; j++) {
byte = offset >> 3;
bit = 7 - (offset & 0x7);
byteval = p[byte];
bitval = (byteval >> bit) & 1;
value = (value<<1) | bitval;
offset++;
}
return value;
}
int64_t getSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) {
int64_t value = getUnsignedBitfield(p,offset,bits);
/* If the top significant bit is 1, propagate it to all the
* higher bits for two complement representation of signed
* integers. */
if (value & ((uint64_t)1 << (bits-1)))
value |= ((uint64_t)-1) << bits;
return value;
}
/* The following two functions detect overflow of a value in the context
* of storing it as an unsigned or signed integer with the specified
* number of bits. The functions both take the value and a possible increment.
* If no overflow could happen and the value+increment fit inside the limits,
* then zero is returned, otherwise in case of overflow, 1 is returned,
* otherwise in case of underflow, -1 is returned.
*
* When non-zero is returned (oferflow or underflow), if not NULL, *limit is
* set to the value the operation should result when an overflow happens,
* depending on the specified overflow semantics:
*
* For BFOVERFLOW_SAT if 1 is returned, *limit it is set maximum value that
* you can store in that integer. when -1 is returned, *limit is set to the
* minimum value that an integer of that size can represent.
*
* For BFOVERFLOW_WRAP *limit is set by performing the operation in order to
* "wrap" around towards zero for unsigned integers, or towards the most
* negative number that is possible to represent for signed integers. */
#define BFOVERFLOW_WRAP 0
#define BFOVERFLOW_SAT 1
#define BFOVERFLOW_FAIL 2 /* Used by the BITFIELD command implementation. */
int checkUnsignedBitfieldOverflow(uint64_t value, int64_t incr, uint64_t bits, int owtype, uint64_t *limit) {
uint64_t max = (bits == 64) ? UINT64_MAX : (((uint64_t)1<<bits)-1);
int64_t maxincr = max-value;
int64_t minincr = -value;
if (value > max || (incr > 0 && incr > maxincr)) {
if (limit) {
if (owtype == BFOVERFLOW_WRAP) {
goto handle_wrap;
} else if (owtype == BFOVERFLOW_SAT) {
*limit = max;
}
}
return 1;
} else if (incr < 0 && incr < minincr) {
if (limit) {
if (owtype == BFOVERFLOW_WRAP) {
goto handle_wrap;
} else if (owtype == BFOVERFLOW_SAT) {
*limit = 0;
}
}
return -1;
}
return 0;
handle_wrap:
{
uint64_t mask = ((int64_t)-1) << bits;
uint64_t res = value+incr;
res &= ~mask;
*limit = res;
}
return 1;
}
int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int owtype, int64_t *limit) {
int64_t max = (bits == 64) ? INT64_MAX : (((int64_t)1<<(bits-1))-1);
int64_t min = (-max)-1;
/* Note that maxincr and minincr could overflow, but we use the values
* only after checking 'value' range, so when we use it no overflow
* happens. */
int64_t maxincr = max-value;
int64_t minincr = min-value;
if (value > max || (bits != 64 && incr > maxincr) || (value >= 0 && incr > 0 && incr > maxincr))
{
if (limit) {
if (owtype == BFOVERFLOW_WRAP) {
goto handle_wrap;
} else if (owtype == BFOVERFLOW_SAT) {
*limit = max;
}
}
return 1;
} else if (value < min || (bits != 64 && incr < minincr) || (value < 0 && incr < 0 && incr < minincr)) {
if (limit) {
if (owtype == BFOVERFLOW_WRAP) {
goto handle_wrap;
} else if (owtype == BFOVERFLOW_SAT) {
*limit = min;
}
}
return -1;
}
return 0;
handle_wrap:
{
uint64_t mask = ((int64_t)-1) << bits;
uint64_t msb = (uint64_t)1 << (bits-1);
uint64_t a = value, b = incr, c;
c = a+b; /* Perform addition as unsigned so that's defined. */
/* If the sign bit is set, propagate to all the higher order
* bits, to cap the negative value. If it's clear, mask to
* the positive integer limit. */
if (c & msb) {
c |= mask;
} else {
c &= ~mask;
}
*limit = c;
}
return 1;
}
/* Debugging function. Just show bits in the specified bitmap. Not used
* but here for not having to rewrite it when debugging is needed. */
void printBits(unsigned char *p, unsigned long count) {
unsigned long j, i, byte;
for (j = 0; j < count; j++) {
byte = p[j];
for (i = 0x80; i > 0; i /= 2)
printf("%c", (byte & i) ? '1' : '0');
printf("|");
}
printf("\n");
}
/* -----------------------------------------------------------------------------
* Bits related string commands: GETBIT, SETBIT, BITCOUNT, BITOP.
* -------------------------------------------------------------------------- */
@@ -208,16 +383,109 @@ long redisBitpos(void *s, unsigned long count, int bit) {
#define BITOP_XOR 2
#define BITOP_NOT 3
#define BITFIELDOP_GET 0
#define BITFIELDOP_SET 1
#define BITFIELDOP_INCRBY 2
/* This helper function used by GETBIT / SETBIT parses the bit offset argument
* making sure an error is returned if it is negative or if it overflows
* Redis 512 MB limit for the string value.
*
* If the 'hash' argument is true, and 'bits is positive, then the command
* will also parse bit offsets prefixed by "#". In such a case the offset
* is multiplied by 'bits'. This is useful for the BITFIELD command. */
int getBitOffsetFromArgument(client *c, robj *o, size_t *offset, int hash, int bits) {
long long loffset;
char *err = "bit offset is not an integer or out of range";
char *p = o->ptr;
size_t plen = sdslen(p);
int usehash = 0;
/* Handle #<offset> form. */
if (p[0] == '#' && hash && bits > 0) usehash = 1;
if (string2ll(p+usehash,plen-usehash,&loffset) == 0) {
addReplyError(c,err);
return C_ERR;
}
/* Adjust the offset by 'bits' for #<offset> form. */
if (usehash) loffset *= bits;
/* Limit offset to 512MB in bytes */
if ((loffset < 0) || ((unsigned long long)loffset >> 3) >= (512*1024*1024))
{
addReplyError(c,err);
return C_ERR;
}
*offset = (size_t)loffset;
return C_OK;
}
/* This helper function for BITFIELD parses a bitfield type in the form
* <sign><bits> where sign is 'u' or 'i' for unsigned and signed, and
* the bits is a value between 1 and 64. However 64 bits unsigned integers
* are reported as an error because of current limitations of Redis protocol
* to return unsigned integer values greater than INT64_MAX.
*
* On error C_ERR is returned and an error is sent to the client. */
int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
char *p = o->ptr;
char *err = "Invalid bitfield type. Use something like i16 u8. Note that u64 is not supported but i64 is.";
long long llbits;
if (p[0] == 'i') {
*sign = 1;
} else if (p[0] == 'u') {
*sign = 0;
} else {
addReplyError(c,err);
return C_ERR;
}
if ((string2ll(p+1,strlen(p+1),&llbits)) == 0 ||
llbits < 1 ||
(*sign == 1 && llbits > 64) ||
(*sign == 0 && llbits > 63))
{
addReplyError(c,err);
return C_ERR;
}
*bits = llbits;
return C_OK;
}
/* This is an helper function for commands implementations that need to write
* bits to a string object. The command creates or pad with zeroes the string
* so that the 'maxbit' bit can be addressed. The object is finally
* returned. Otherwise if the key holds a wrong type NULL is returned and
* an error is sent to the client. */
robj *lookupStringForBitCommand(client *c, size_t maxbit) {
size_t byte = maxbit >> 3;
robj *o = lookupKeyWrite(c->db,c->argv[1]);
if (o == NULL) {
o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1));
dbAdd(c->db,c->argv[1],o);
} else {
if (checkType(c,o,OBJ_STRING)) return NULL;
o = dbUnshareStringValue(c->db,c->argv[1],o);
o->ptr = sdsgrowzero(o->ptr,byte+1);
}
return o;
}
/* SETBIT key offset bitvalue */
void setbitCommand(client *c) {
robj *o;
char *err = "bit is not an integer or out of range";
size_t bitoffset;
int byte, bit;
ssize_t byte, bit;
int byteval, bitval;
long on;
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != C_OK)
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0) != C_OK)
return;
if (getLongFromObjectOrReply(c,c->argv[3],&on,err) != C_OK)
@@ -229,18 +497,10 @@ void setbitCommand(client *c) {
return;
}
byte = bitoffset >> 3;
o = lookupKeyWrite(c->db,c->argv[1]);
if (o == NULL) {
o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1));
dbAdd(c->db,c->argv[1],o);
} else {
if (checkType(c,o,OBJ_STRING)) return;
o = dbUnshareStringValue(c->db,c->argv[1],o);
o->ptr = sdsgrowzero(o->ptr,byte+1);
}
if ((o = lookupStringForBitCommand(c,bitoffset)) == NULL) return;
/* Get current values */
byte = bitoffset >> 3;
byteval = ((uint8_t*)o->ptr)[byte];
bit = 7 - (bitoffset & 0x7);
bitval = byteval & (1 << bit);
@@ -263,7 +523,7 @@ void getbitCommand(client *c) {
size_t byte, bit;
size_t bitval = 0;
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != C_OK)
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset,0,0) != C_OK)
return;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
@@ -596,3 +856,220 @@ void bitposCommand(client *c) {
addReplyLongLong(c,pos);
}
}
/* BITFIELD key subcommmand-1 arg ... subcommand-2 arg ... subcommand-N ...
*
* Supported subcommands:
*
* GET <type> <offset>
* SET <type> <offset> <value>
* INCRBY <type> <offset> <increment>
* OVERFLOW [WRAP|SAT|FAIL]
*/
struct bitfieldOp {
uint64_t offset; /* Bitfield offset. */
int64_t i64; /* Increment amount (INCRBY) or SET value */
int opcode; /* Operation id. */
int owtype; /* Overflow type to use. */
int bits; /* Integer bitfield bits width. */
int sign; /* True if signed, otherwise unsigned op. */
};
void bitfieldCommand(client *c) {
robj *o;
size_t bitoffset;
int j, numops = 0, changes = 0;
struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
int owtype = BFOVERFLOW_WRAP; /* Overflow type. */
for (j = 2; j < c->argc; j++) {
int remargs = c->argc-j-1; /* Remaining args other than current. */
char *subcmd = c->argv[j]->ptr; /* Current command name. */
int opcode; /* Current operation code. */
long long i64 = 0; /* Signed SET value. */
int sign = 0; /* Signed or unsigned type? */
int bits = 0; /* Bitfield width in bits. */
if (!strcasecmp(subcmd,"get") && remargs >= 2)
opcode = BITFIELDOP_GET;
else if (!strcasecmp(subcmd,"set") && remargs >= 3)
opcode = BITFIELDOP_SET;
else if (!strcasecmp(subcmd,"incrby") && remargs >= 3)
opcode = BITFIELDOP_INCRBY;
else if (!strcasecmp(subcmd,"overflow") && remargs >= 1) {
char *owtypename = c->argv[j+1]->ptr;
j++;
if (!strcasecmp(owtypename,"wrap"))
owtype = BFOVERFLOW_WRAP;
else if (!strcasecmp(owtypename,"sat"))
owtype = BFOVERFLOW_SAT;
else if (!strcasecmp(owtypename,"fail"))
owtype = BFOVERFLOW_FAIL;
else {
addReplyError(c,"Invalid OVERFLOW type specified");
zfree(ops);
return;
}
continue;
} else {
addReply(c,shared.syntaxerr);
zfree(ops);
return;
}
/* Get the type and offset arguments, common to all the ops. */
if (getBitfieldTypeFromArgument(c,c->argv[j+1],&sign,&bits) != C_OK) {
zfree(ops);
return;
}
if (getBitOffsetFromArgument(c,c->argv[j+2],&bitoffset,1,bits) != C_OK){
zfree(ops);
return;
}
/* INCRBY and SET require another argument. */
if (opcode != BITFIELDOP_GET) {
if (getLongLongFromObjectOrReply(c,c->argv[j+3],&i64,NULL) != C_OK){
zfree(ops);
return;
}
}
/* Populate the array of operations we'll process. */
ops = zrealloc(ops,sizeof(*ops)*(numops+1));
ops[numops].offset = bitoffset;
ops[numops].i64 = i64;
ops[numops].opcode = opcode;
ops[numops].owtype = owtype;
ops[numops].bits = bits;
ops[numops].sign = sign;
numops++;
j += 3 - (opcode == BITFIELDOP_GET);
}
addReplyMultiBulkLen(c,numops);
/* Actually process the operations. */
for (j = 0; j < numops; j++) {
struct bitfieldOp *thisop = ops+j;
/* Execute the operation. */
if (thisop->opcode == BITFIELDOP_SET ||
thisop->opcode == BITFIELDOP_INCRBY)
{
/* SET and INCRBY: We handle both with the same code path
* for simplicity. SET return value is the previous value so
* we need fetch & store as well. */
/* Lookup by making room up to the farest bit reached by
* this operation. */
if ((o = lookupStringForBitCommand(c,
thisop->offset + (thisop->bits-1))) == NULL) return;
/* We need two different but very similar code paths for signed
* and unsigned operations, since the set of functions to get/set
* the integers and the used variables types are different. */
if (thisop->sign) {
int64_t oldval, newval, wrapped, retval;
int overflow;
oldval = getSignedBitfield(o->ptr,thisop->offset,
thisop->bits);
if (thisop->opcode == BITFIELDOP_INCRBY) {
newval = oldval + thisop->i64;
overflow = checkSignedBitfieldOverflow(oldval,
thisop->i64,thisop->bits,thisop->owtype,&wrapped);
if (overflow) newval = wrapped;
retval = newval;
} else {
newval = thisop->i64;
overflow = checkSignedBitfieldOverflow(newval,
0,thisop->bits,thisop->owtype,&wrapped);
if (overflow) newval = wrapped;
retval = oldval;
}
/* On overflow of type is "FAIL", don't write and return
* NULL to signal the condition. */
if (!(overflow && thisop->owtype == BFOVERFLOW_FAIL)) {
addReplyLongLong(c,retval);
setSignedBitfield(o->ptr,thisop->offset,
thisop->bits,newval);
} else {
addReply(c,shared.nullbulk);
}
} else {
uint64_t oldval, newval, wrapped, retval;
int overflow;
oldval = getUnsignedBitfield(o->ptr,thisop->offset,
thisop->bits);
if (thisop->opcode == BITFIELDOP_INCRBY) {
newval = oldval + thisop->i64;
overflow = checkUnsignedBitfieldOverflow(oldval,
thisop->i64,thisop->bits,thisop->owtype,&wrapped);
if (overflow) newval = wrapped;
retval = newval;
} else {
newval = thisop->i64;
overflow = checkUnsignedBitfieldOverflow(newval,
0,thisop->bits,thisop->owtype,&wrapped);
if (overflow) newval = wrapped;
retval = oldval;
}
/* On overflow of type is "FAIL", don't write and return
* NULL to signal the condition. */
if (!(overflow && thisop->owtype == BFOVERFLOW_FAIL)) {
addReplyLongLong(c,retval);
setUnsignedBitfield(o->ptr,thisop->offset,
thisop->bits,newval);
} else {
addReply(c,shared.nullbulk);
}
}
changes++;
} else {
/* GET */
o = lookupKeyRead(c->db,c->argv[1]);
size_t olen = (o == NULL) ? 0 : sdslen(o->ptr);
unsigned char buf[9];
/* For GET we use a trick: before executing the operation
* copy up to 9 bytes to a local buffer, so that we can easily
* execute up to 64 bit operations that are at actual string
* object boundaries. */
memset(buf,0,9);
unsigned char *src = o ? o->ptr : NULL;
int i;
size_t byte = thisop->offset >> 3;
for (i = 0; i < 9; i++) {
if (src == NULL || i+byte >= olen) break;
buf[i] = src[i+byte];
}
/* Now operate on the copied buffer which is guaranteed
* to be zero-padded. */
if (thisop->sign) {
int64_t val = getSignedBitfield(buf,thisop->offset-(byte*8),
thisop->bits);
addReplyLongLong(c,val);
} else {
uint64_t val = getUnsignedBitfield(buf,thisop->offset-(byte*8),
thisop->bits);
addReplyLongLong(c,val);
}
}
}
if (changes) {
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
server.dirty += changes;
}
zfree(ops);
}
+160 -54
View File
@@ -531,6 +531,7 @@ void clusterReset(int hard) {
sdsfree(oldname);
getRandomHexChars(myself->name, CLUSTER_NAMELEN);
clusterAddNode(myself);
serverLog(LL_NOTICE,"Node hard reset, now I'm %.40s", myself->name);
}
/* Make sure to persist the new config and update the state. */
@@ -806,12 +807,6 @@ int clusterNodeAddSlave(clusterNode *master, clusterNode *slave) {
return C_OK;
}
void clusterNodeResetSlaves(clusterNode *n) {
zfree(n->slaves);
n->numslaves = 0;
n->slaves = NULL;
}
int clusterCountNonFailingSlaves(clusterNode *n) {
int j, okslaves = 0;
@@ -825,12 +820,10 @@ void freeClusterNode(clusterNode *n) {
sds nodename;
int j;
/* If the node is a master with associated slaves, we have to set
/* If the node has associated slaves, we have to set
* all the slaves->slaveof fields to NULL (unknown). */
if (nodeIsMaster(n)) {
for (j = 0; j < n->numslaves; j++)
n->slaves[j]->slaveof = NULL;
}
for (j = 0; j < n->numslaves; j++)
n->slaves[j]->slaveof = NULL;
/* Remove this node from the list of slaves of its master. */
if (nodeIsSlave(n) && n->slaveof) clusterNodeRemoveSlave(n->slaveof,n);
@@ -948,7 +941,7 @@ uint64_t clusterGetMaxEpoch(void) {
/* If this node epoch is zero or is not already the greatest across the
* cluster (from the POV of the local configuration), this function will:
*
* 1) Generate a new config epoch increment the current epoch.
* 1) Generate a new config epoch, incrementing the current epoch.
* 2) Assign the new epoch to this node, WITHOUT any consensus.
* 3) Persist the configuration on disk before sending packets with the
* new configuration.
@@ -963,16 +956,16 @@ uint64_t clusterGetMaxEpoch(void) {
* cases:
*
* 1) When slots are closed after importing. Otherwise resharding would be
* too exansive.
* too expensive.
* 2) When CLUSTER FAILOVER is called with options that force a slave to
* failover its master even if there is not master majority able to
* create a new configuration epoch.
*
* Redis Cluster does not explode using this function, even in the case of
* Redis Cluster will not explode using this function, even in the case of
* a collision between this node and another node, generating the same
* configuration epoch unilaterally, because the config epoch conflict
* resolution algorithm will eventually move colliding nodes to different
* config epochs. However usign this function may violate the "last failover
* config epochs. However using this function may violate the "last failover
* wins" rule, so should only be used with care. */
int clusterBumpConfigEpochWithoutConsensus(void) {
uint64_t maxEpoch = clusterGetMaxEpoch();
@@ -1338,14 +1331,19 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
}
/* If we already know this node, but it is not reachable, and
* we see a different address in the gossip section, start an
* handshake with the (possibly) new address: this will result
* into a node address update if the handshake will be
* successful. */
* we see a different address in the gossip section of a node that
* can talk with this other node, update the address, disconnect
* the old link if any, so that we'll attempt to connect with the
* new address. */
if (node->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL) &&
!(flags & CLUSTER_NODE_NOADDR) &&
!(flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) &&
(strcasecmp(node->ip,g->ip) || node->port != ntohs(g->port)))
{
clusterStartHandshake(g->ip,ntohs(g->port));
if (node->link) freeClusterLink(node->link);
memcpy(node->ip,g->ip,NET_IP_STR_LEN);
node->port = ntohs(g->port);
node->flags &= ~CLUSTER_NODE_NOADDR;
}
} else {
/* If it's not in NOADDR state and we don't have it, we
@@ -1544,9 +1542,6 @@ int clusterProcessPacket(clusterLink *link) {
clusterMsg *hdr = (clusterMsg*) link->rcvbuf;
uint32_t totlen = ntohl(hdr->totlen);
uint16_t type = ntohs(hdr->type);
uint16_t flags = ntohs(hdr->flags);
uint64_t senderCurrentEpoch = 0, senderConfigEpoch = 0;
clusterNode *sender;
server.cluster->stats_bus_messages_received++;
serverLog(LL_DEBUG,"--- Processing packet of type %d, %lu bytes",
@@ -1554,9 +1549,17 @@ int clusterProcessPacket(clusterLink *link) {
/* Perform sanity checks */
if (totlen < 16) return 1; /* At least signature, version, totlen, count. */
if (ntohs(hdr->ver) != CLUSTER_PROTO_VER)
return 1; /* Can't handle versions other than the current one.*/
if (totlen > sdslen(link->rcvbuf)) return 1;
if (ntohs(hdr->ver) != CLUSTER_PROTO_VER) {
/* Can't handle messages of different versions. */
return 1;
}
uint16_t flags = ntohs(hdr->flags);
uint64_t senderCurrentEpoch = 0, senderConfigEpoch = 0;
clusterNode *sender;
if (type == CLUSTERMSG_TYPE_PING || type == CLUSTERMSG_TYPE_PONG ||
type == CLUSTERMSG_TYPE_MEET)
{
@@ -1718,7 +1721,10 @@ int clusterProcessPacket(clusterLink *link) {
/* If the reply has a non matching node ID we
* disconnect this node and set it as not having an associated
* address. */
serverLog(LL_DEBUG,"PONG contains mismatching sender ID");
serverLog(LL_DEBUG,"PONG contains mismatching sender ID. About node %.40s added %d ms ago, having flags %d",
link->node->name,
(int)(mstime()-(link->node->ctime)),
link->node->flags);
link->node->flags |= CLUSTER_NODE_NOADDR;
link->node->ip[0] = '\0';
link->node->port = 0;
@@ -1775,9 +1781,6 @@ int clusterProcessPacket(clusterLink *link) {
CLUSTER_NODE_MIGRATE_TO);
sender->flags |= CLUSTER_NODE_SLAVE;
/* Remove the list of slaves from the node. */
if (sender->numslaves) clusterNodeResetSlaves(sender);
/* Update config and state. */
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
CLUSTER_TODO_UPDATE_STATE);
@@ -3353,11 +3356,45 @@ void bitmapClearBit(unsigned char *bitmap, int pos) {
bitmap[byte] &= ~(1<<bit);
}
/* Return non-zero if there is at least one master with slaves in the cluster.
* Otherwise zero is returned. Used by clusterNodeSetSlotBit() to set the
* MIGRATE_TO flag the when a master gets the first slot. */
int clusterMastersHaveSlaves(void) {
dictIterator *di = dictGetSafeIterator(server.cluster->nodes);
dictEntry *de;
int slaves = 0;
while((de = dictNext(di)) != NULL) {
clusterNode *node = dictGetVal(de);
if (nodeIsSlave(node)) continue;
slaves += node->numslaves;
}
dictReleaseIterator(di);
return slaves != 0;
}
/* Set the slot bit and return the old value. */
int clusterNodeSetSlotBit(clusterNode *n, int slot) {
int old = bitmapTestBit(n->slots,slot);
bitmapSetBit(n->slots,slot);
if (!old) n->numslots++;
if (!old) {
n->numslots++;
/* When a master gets its first slot, even if it has no slaves,
* it gets flagged with MIGRATE_TO, that is, the master is a valid
* target for replicas migration, if and only if at least one of
* the other masters has slaves right now.
*
* Normally masters are valid targerts of replica migration if:
* 1. The used to have slaves (but no longer have).
* 2. They are slaves failing over a master that used to have slaves.
*
* However new masters with slots assigned are considered valid
* migration tagets if the rest of the cluster is not a slave-less.
*
* See https://github.com/antirez/redis/issues/3043 for more info. */
if (n->numslots == 1 && clusterMastersHaveSlaves())
n->flags |= CLUSTER_NODE_MIGRATE_TO;
}
return old;
}
@@ -3771,8 +3808,10 @@ void clusterReplyMultiBulkSlots(client *c) {
* 2) end slot
* 3) 1) master IP
* 2) master port
* 3) node ID
* 4) 1) replica IP
* 2) replica port
* 3) node ID
* ... continued until done
*/
@@ -3813,18 +3852,20 @@ void clusterReplyMultiBulkSlots(client *c) {
start = -1;
/* First node reply position is always the master */
addReplyMultiBulkLen(c, 2);
addReplyMultiBulkLen(c, 3);
addReplyBulkCString(c, node->ip);
addReplyLongLong(c, node->port);
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
/* Remaining nodes in reply are replicas for slot range */
for (i = 0; i < node->numslaves; i++) {
/* This loop is copy/pasted from clusterGenNodeDescription()
* with modifications for per-slot node aggregation */
if (nodeFailed(node->slaves[i])) continue;
addReplyMultiBulkLen(c, 2);
addReplyMultiBulkLen(c, 3);
addReplyBulkCString(c, node->slaves[i]->ip);
addReplyLongLong(c, node->slaves[i]->port);
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
nested_elements++;
}
setDeferredMultiBulkLength(c, nested_replylen, nested_elements);
@@ -3940,6 +3981,11 @@ void clusterCommand(client *c) {
int slot;
clusterNode *n;
if (nodeIsSlave(myself)) {
addReplyError(c,"Please use SETSLOT only with masters.");
return;
}
if ((slot = getSlotOrReply(c,c->argv[2])) == -1) return;
if (!strcasecmp(c->argv[3]->ptr,"migrating") && c->argc == 5) {
@@ -4024,6 +4070,13 @@ void clusterCommand(client *c) {
}
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_UPDATE_STATE);
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"bumpepoch") && c->argc == 2) {
/* CLUSTER BUMPEPOCH */
int retval = clusterBumpConfigEpochWithoutConsensus();
sds reply = sdscatprintf(sdsempty(),"+%s %llu\r\n",
(retval == C_OK) ? "BUMPED" : "STILL",
(unsigned long long) myself->configEpoch);
addReplySds(c,reply);
} else if (!strcasecmp(c->argv[1]->ptr,"info") && c->argc == 2) {
/* CLUSTER INFO */
char *statestr[] = {"ok","fail","needhelp"};
@@ -4600,10 +4653,11 @@ void migrateCommand(client *c) {
long timeout;
long dbid;
long long ttl, expireat;
robj **ov = zmalloc(sizeof(robj*)); /* Objects to migrate. */
robj **kv = zmalloc(sizeof(robj*)); /* Key names. */
robj **ov = NULL; /* Objects to migrate. */
robj **kv = NULL; /* Key names. */
robj **newargv = NULL; /* Used to rewrite the command as DEL ... keys ... */
rio cmd, payload;
int retry_num = 0;
int may_retry = 1;
int write_error = 0;
/* To support the KEYS option we need the following additional state. */
@@ -4626,13 +4680,10 @@ void migrateCommand(client *c) {
addReplyError(c,
"When using MIGRATE KEYS option, the key argument"
" must be set to the empty string");
zfree(ov); zfree(kv);
return;
}
first_key = j+1;
num_keys = c->argc - j - 1;
ov = zrealloc(ov,sizeof(robj*)*num_keys);
kv = zrealloc(kv,sizeof(robj*)*num_keys);
break; /* All the remaining args are keys. */
} else {
addReply(c,shared.syntaxerr);
@@ -4641,10 +4692,11 @@ void migrateCommand(client *c) {
}
/* Sanity check */
if (getLongFromObjectOrReply(c,c->argv[5],&timeout,NULL) != C_OK)
return;
if (getLongFromObjectOrReply(c,c->argv[4],&dbid,NULL) != C_OK)
if (getLongFromObjectOrReply(c,c->argv[5],&timeout,NULL) != C_OK ||
getLongFromObjectOrReply(c,c->argv[4],&dbid,NULL) != C_OK)
{
return;
}
if (timeout <= 0) timeout = 1000;
/* Check if the keys are here. If at least one key is to migrate, do it
@@ -4652,7 +4704,10 @@ void migrateCommand(client *c) {
* the caller there was nothing to migrate. We don't return an error in
* this case, since often this is due to a normal condition like the key
* expiring in the meantime. */
ov = zrealloc(ov,sizeof(robj*)*num_keys);
kv = zrealloc(kv,sizeof(robj*)*num_keys);
int oi = 0;
for (j = 0; j < num_keys; j++) {
if ((ov[oi] = lookupKeyRead(c->db,c->argv[first_key+j])) != NULL) {
kv[oi] = c->argv[first_key+j];
@@ -4671,7 +4726,10 @@ try_again:
/* Connect */
cs = migrateGetSocket(c,c->argv[1],c->argv[2],timeout);
if (cs == NULL) return; /* error sent to the client by migrateGetSocket() */
if (cs == NULL) {
zfree(ov); zfree(kv);
return; /* error sent to the client by migrateGetSocket() */
}
rioInitWithBuffer(&cmd,sdsempty());
@@ -4742,14 +4800,16 @@ try_again:
/* Read the RESTORE replies. */
int error_from_target = 0;
int socket_error = 0;
int del_idx = 1; /* Index of the key argument for the replicated DEL op. */
robj **newargv = NULL;
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
for (j = 0; j < num_keys; j++) {
if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0)
goto socket_err;
if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0) {
socket_error = 1;
break;
}
if ((select && buf1[0] == '-') || buf2[0] == '-') {
/* On error assume that last_dbid is no longer valid. */
if (!error_from_target) {
@@ -4772,36 +4832,70 @@ try_again:
}
}
/* On socket error, if we want to retry, do it now before rewriting the
* command vector. We only retry if we are sure nothing was processed
* and we failed to read the first reply (j == 0 test). */
if (!error_from_target && socket_error && j == 0 && may_retry &&
errno != ETIMEDOUT)
{
goto socket_err; /* A retry is guaranteed because of tested conditions.*/
}
if (!copy) {
/* Translate MIGRATE as DEL for replication/AOF. */
if (del_idx > 1) {
newargv[0] = createStringObject("DEL",3);
/* Note that the following call takes ownership of newargv. */
replaceClientCommandVector(c,del_idx,newargv);
} else {
/* No key transfer acknowledged, no need to rewrite as DEL. */
zfree(newargv);
}
newargv = NULL; /* Make it safe to call zfree() on it in the future. */
}
/* If we are here and a socket error happened, we don't want to retry.
* Just signal the problem to the client, but only do it if we don't
* already queued a different error reported by the destination server. */
if (!error_from_target && socket_error) {
may_retry = 0;
goto socket_err;
}
if (!error_from_target) {
/* Update the last_dbid in migrateCachedSocket and reply +OK. */
/* Success! Update the last_dbid in migrateCachedSocket, so that we can
* avoid SELECT the next time if the target DB is the same. Reply +OK. */
cs->last_dbid = dbid;
addReply(c,shared.ok);
} else {
/* On error we already sent it in the for loop above. */
/* On error we already sent it in the for loop above, and set
* the curretly selected socket to -1 to force SELECT the next time. */
}
sdsfree(cmd.io.buffer.ptr);
zfree(ov); zfree(kv);
zfree(ov); zfree(kv); zfree(newargv);
if (socket_error) migrateCloseSocket(c->argv[1],c->argv[2]);
return;
/* On socket errors we try to close the cached socket and try again.
* It is very common for the cached socket to get closed, if just reopening
* it works it's a shame to notify the error to the caller. */
socket_err:
/* Cleanup we want to perform in both the retry and no retry case.
* Note: Closing the migrate socket will also force SELECT next time. */
sdsfree(cmd.io.buffer.ptr);
migrateCloseSocket(c->argv[1],c->argv[2]);
if (errno != ETIMEDOUT && retry_num++ == 0) goto try_again;
zfree(newargv);
newargv = NULL; /* This will get reallocated on retry. */
/* Retry only if it's not a timeout and we never attempted a retry
* (or the code jumping here did not set may_retry to zero). */
if (errno != ETIMEDOUT && may_retry) {
may_retry = 0;
goto try_again;
}
/* Cleanup we want to do if no retry is attempted. */
zfree(ov); zfree(kv);
addReplySds(c,
sdscatprintf(sdsempty(),
@@ -4866,14 +4960,17 @@ void readwriteCommand(client *c) {
* CLUSTER_REDIR_CROSS_SLOT if the request contains multiple keys that
* don't belong to the same hash slot.
*
* CLUSTER_REDIR_UNSTABLE if the request contains mutliple keys
* CLUSTER_REDIR_UNSTABLE if the request contains multiple keys
* belonging to the same slot, but the slot is not stable (in migration or
* importing state, likely because a resharding is in progress).
*
* CLUSTER_REDIR_DOWN_UNBOUND if the request addresses a slot which is
* not bound to any node. In this case the cluster global state should be
* already "down" but it is fragile to rely on the update of the global state,
* so we also handle it here. */
* so we also handle it here.
*
* CLUSTER_REDIR_DOWN_STATE if the cluster is down but the user attempts to
* execute a command that addresses one or more keys. */
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *error_code) {
clusterNode *n = NULL;
robj *firstkey = NULL;
@@ -4980,14 +5077,23 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
}
/* No key at all in command? then we can serve the request
* without redirections or errors. */
* without redirections or errors in all the cases. */
if (n == NULL) return myself;
/* Cluster is globally down but we got keys? We can't serve the request. */
if (server.cluster->state != CLUSTER_OK) {
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
return NULL;
}
/* Return the hashslot by reference. */
if (hashslot) *hashslot = slot;
/* This request is about a slot we are migrating into another instance?
* Then if we have all the keys. */
/* MIGRATE always works in the context of the local node if the slot
* is open (migrating or importing state). We need to be able to freely
* move keys among instances in this case. */
if ((migrating_slot || importing_slot) && cmd->proc == migrateCommand)
return myself;
/* If we don't have all the keys and we are migrating the slot, send
* an ASK redirection. */
+4 -1
View File
@@ -88,7 +88,10 @@ typedef struct clusterNode {
int numslots; /* Number of slots handled by this node */
int numslaves; /* Number of slave nodes, if this is a master */
struct clusterNode **slaves; /* pointers to slave nodes */
struct clusterNode *slaveof; /* pointer to the master node */
struct clusterNode *slaveof; /* pointer to the master node. Note that it
may be NULL even if the node is a slave
if we don't have the master node in our
tables. */
mstime_t ping_sent; /* Unix time we sent latest ping */
mstime_t pong_received; /* Unix time we received the pong */
mstime_t fail_time; /* Unix time when FAIL flag was set */
+19 -2
View File
@@ -126,8 +126,8 @@ const char *configEnumGetNameOrUnknown(configEnum *ce, int val) {
}
/* Used for INFO generation. */
const char *maxmemoryToString(void) {
return configEnumGetNameOrUnknown(maxmemory_policy_enum,server.maxmemory);
const char *evictPolicyToString(void) {
return configEnumGetNameOrUnknown(maxmemory_policy_enum,server.maxmemory_policy);
}
/*-----------------------------------------------------------------------------
@@ -196,6 +196,10 @@ void loadServerConfigFromString(char *config) {
if (server.tcpkeepalive < 0) {
err = "Invalid tcp-keepalive value"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"protected-mode") && argc == 2) {
if ((server.protected_mode = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"port") && argc == 2) {
server.port = atoi(argv[1]);
if (server.port < 0 || server.port > 65535) {
@@ -349,6 +353,7 @@ void loadServerConfigFromString(char *config) {
goto loaderr;
}
} else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
zfree(server.masterauth);
server.masterauth = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) {
if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
@@ -873,8 +878,12 @@ void configSetCommand(client *c) {
"slave-read-only",server.repl_slave_ro) {
} config_set_bool_field(
"activerehashing",server.activerehashing) {
} config_set_bool_field(
"protected-mode",server.protected_mode) {
} config_set_bool_field(
"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err) {
} config_set_bool_field(
"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite) {
/* Numerical fields.
* config_set_numerical_field(name,var,min,max) */
@@ -1105,6 +1114,7 @@ void configGetCommand(client *c) {
config_get_bool_field("rdbcompression", server.rdb_compression);
config_get_bool_field("rdbchecksum", server.rdb_checksum);
config_get_bool_field("activerehashing", server.activerehashing);
config_get_bool_field("protected-mode", server.protected_mode);
config_get_bool_field("repl-disable-tcp-nodelay",
server.repl_disable_tcp_nodelay);
config_get_bool_field("repl-diskless-sync",
@@ -1815,6 +1825,7 @@ int rewriteConfig(char *path) {
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,OBJ_ZSET_MAX_ZIPLIST_VALUE);
rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",server.hll_sparse_max_bytes,CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES);
rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,CONFIG_DEFAULT_ACTIVE_REHASHING);
rewriteConfigYesNoOption(state,"protected-mode",server.protected_mode,CONFIG_DEFAULT_PROTECTED_MODE);
rewriteConfigClientoutputbufferlimitOption(state);
rewriteConfigNumericalOption(state,"hz",server.hz,CONFIG_DEFAULT_HZ);
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
@@ -1844,6 +1855,12 @@ int rewriteConfig(char *path) {
*----------------------------------------------------------------------------*/
void configCommand(client *c) {
/* Only allow CONFIG GET while loading. */
if (server.loading && strcasecmp(c->argv[1]->ptr,"get")) {
addReplyError(c,"Only CONFIG GET is allowed during loading");
return;
}
if (!strcasecmp(c->argv[1]->ptr,"set")) {
if (c->argc != 4) goto badarity;
configSetCommand(c);
+1 -1
View File
@@ -119,7 +119,7 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
sds copy = sdsdup(key->ptr);
int retval = dictAdd(db->dict, copy, val);
serverAssertWithInfo(NULL,key,retval == C_OK);
serverAssertWithInfo(NULL,key,retval == DICT_OK);
if (val->type == OBJ_LIST) signalListAsReady(db, key);
if (server.cluster_enabled) slotToKeyAdd(key);
}
+64 -2
View File
@@ -252,14 +252,63 @@ void computeDatasetDigest(unsigned char *final) {
}
}
#if defined(USE_JEMALLOC)
void inputCatSds(void *result, const char *str) {
/* result is actually a (sds *), so re-cast it here */
sds *info = (sds *)result;
*info = sdscat(*info, str);
}
#endif
void debugCommand(client *c) {
if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
if (c->argc == 1) {
addReplyError(c,"You must specify a subcommand for DEBUG. Try DEBUG HELP for info.");
return;
}
if (!strcasecmp(c->argv[1]->ptr,"help")) {
void *blenp = addDeferredMultiBulkLength(c);
int blen = 0;
blen++; addReplyStatus(c,
"DEBUG <subcommand> arg arg ... arg. Subcommands:");
blen++; addReplyStatus(c,
"segfault -- Crash the server with sigsegv.");
blen++; addReplyStatus(c,
"restart -- Graceful restart: save config, db, restart.");
blen++; addReplyStatus(c,
"crash-and-recovery <milliseconds> -- Hard crash and restart after <milliseconds> delay.");
blen++; addReplyStatus(c,
"assert -- Crash by assertion failed.");
blen++; addReplyStatus(c,
"reload -- Save the RDB on disk and reload it back in memory.");
blen++; addReplyStatus(c,
"loadaof -- Flush the AOF buffers on disk and reload the AOF in memory.");
blen++; addReplyStatus(c,
"object <key> -- Show low level info about key and associated value.");
blen++; addReplyStatus(c,
"sdslen <key> -- Show low level SDS string info representing key and value.");
blen++; addReplyStatus(c,
"populate <count> [prefix] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.");
blen++; addReplyStatus(c,
"digest -- Outputs an hex signature representing the current DB content.");
blen++; addReplyStatus(c,
"sleep <seconds> -- Stop the server for <seconds>. Decimals allowed.");
blen++; addReplyStatus(c,
"set-active-expire (0|1) -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.");
blen++; addReplyStatus(c,
"lua-always-replicate-commands (0|1) -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.");
blen++; addReplyStatus(c,
"error <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.");
blen++; addReplyStatus(c,
"structsize -- Return the size of different Redis core C structures.");
blen++; addReplyStatus(c,
"htstats <dbid> -- Return hash table statistics of the specified Redis database.");
blen++; addReplyStatus(c,
"jemalloc info -- Show internal jemalloc statistics.");
blen++; addReplyStatus(c,
"jemalloc purge -- Force jemalloc to release unused memory.");
setDeferredMultiBulkLength(c,blenp,blen);
} else if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
*((char*)-1) = 'x';
} else if (!strcasecmp(c->argv[1]->ptr,"restart") ||
!strcasecmp(c->argv[1]->ptr,"crash-and-recover"))
@@ -399,6 +448,7 @@ void debugCommand(client *c) {
snprintf(buf,sizeof(buf),"value:%lu",j);
val = createStringObject(buf,strlen(buf));
dbAdd(c->db,key,val);
signalModifiedKey(c->db,key);
decrRefCount(key);
}
addReply(c,shared.ok);
@@ -476,8 +526,20 @@ void debugCommand(client *c) {
sds info = sdsempty();
je_malloc_stats_print(inputCatSds, &info, NULL);
addReplyBulkSds(c, info);
} else if (!strcasecmp(c->argv[2]->ptr, "purge")) {
char tmp[32];
unsigned narenas = 0;
size_t sz = sizeof(unsigned);
if (!je_mallctl("arenas.narenas", &narenas, &sz, NULL, 0)) {
sprintf(tmp, "arena.%d.purge", narenas);
if (!je_mallctl(tmp, NULL, 0, NULL, 0)) {
addReply(c, shared.ok);
return;
}
}
addReplyError(c, "Error purging dirty pages");
} else {
addReplyErrorFormat(c, "Valid jemalloc debug fields: info");
addReplyErrorFormat(c, "Valid jemalloc debug fields: info, purge");
}
#else
addReplyErrorFormat(c, "jemalloc support not available");
+4 -4
View File
@@ -424,7 +424,7 @@ static int dictGenericDelete(dict *d, const void *key, int nofree)
he = d->ht[table].table[idx];
prevHe = NULL;
while(he) {
if (dictCompareKeys(d, key, he->key)) {
if (key==he->key || dictCompareKeys(d, key, he->key)) {
/* Unlink the element from the list */
if (prevHe)
prevHe->next = he->next;
@@ -494,14 +494,14 @@ dictEntry *dictFind(dict *d, const void *key)
dictEntry *he;
unsigned int h, idx, table;
if (d->ht[0].size == 0) return NULL; /* We don't have a table at all */
if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
if (dictIsRehashing(d)) _dictRehashStep(d);
h = dictHashKey(d, key);
for (table = 0; table <= 1; table++) {
idx = h & d->ht[table].sizemask;
he = d->ht[table].table[idx];
while(he) {
if (dictCompareKeys(d, key, he->key))
if (key==he->key || dictCompareKeys(d, key, he->key))
return he;
he = he->next;
}
@@ -978,7 +978,7 @@ static int _dictKeyIndex(dict *d, const void *key)
/* Search if this slot does not already contain the given key */
he = d->ht[table].table[idx];
while(he) {
if (dictCompareKeys(d, key, he->key))
if (key==he->key || dictCompareKeys(d, key, he->key))
return -1;
he = he->next;
}
+111 -47
View File
@@ -157,13 +157,16 @@ double extractDistanceOrReply(client *c, robj **argv,
}
double to_meters = extractUnitOrReply(c,argv[1]);
if (to_meters < 0) return -1;
if (to_meters < 0) {
addReplyError(c,"radius cannot be negative");
return -1;
}
if (conversion) *conversion = to_meters;
return distance * to_meters;
}
/* The defailt addReplyDouble has too much accuracy. We use this
/* The default addReplyDouble has too much accuracy. We use this
* for returning location distances. "5.2145 meters away" is nicer
* than "5.2144992818115 meters away." We provide 4 digits after the dot
* so that the returned value is decently accurate even when the unit is
@@ -427,10 +430,12 @@ void geoaddCommand(client *c) {
#define RADIUS_MEMBER 2
/* GEORADIUS key x y radius unit [WITHDIST] [WITHHASH] [WITHCOORD] [ASC|DESC]
* [COUNT count]
* [COUNT count] [STORE key] [STOREDIST key]
* GEORADIUSBYMEMBER key member radius unit ... options ... */
void georadiusGeneric(client *c, int type) {
robj *key = c->argv[1];
robj *storekey = NULL;
int storedist = 0; /* 0 for STORE, 1 for STOREDIST. */
/* Look up the requested zset */
robj *zobj = NULL;
@@ -491,6 +496,14 @@ void georadiusGeneric(client *c, int type) {
return;
}
i++;
} else if (!strcasecmp(arg, "store") && (i+1) < remaining) {
storekey = c->argv[base_args+i+1];
storedist = 0;
i++;
} else if (!strcasecmp(arg, "storedist") && (i+1) < remaining) {
storekey = c->argv[base_args+i+1];
storedist = 1;
i++;
} else {
addReply(c, shared.syntaxerr);
return;
@@ -498,6 +511,14 @@ void georadiusGeneric(client *c, int type) {
}
}
/* Trap options not compatible with STORE and STOREDIST. */
if (storekey && (withdist || withhash || withcoords)) {
addReplyError(c,
"STORE option in GEORADIUS is not compatible with "
"WITHDIST, WITHHASH and WITHCOORDS options");
return;
}
/* COUNT without ordering does not make much sense, force ASC
* ordering if COUNT was specified but no sorting was requested. */
if (count != 0 && sort == SORT_NONE) sort = SORT_ASC;
@@ -511,33 +532,17 @@ void georadiusGeneric(client *c, int type) {
membersOfAllNeighbors(zobj, georadius, xy[0], xy[1], radius_meters, ga);
/* If no matching results, the user gets an empty reply. */
if (ga->used == 0) {
if (ga->used == 0 && storekey == NULL) {
addReply(c, shared.emptymultibulk);
geoArrayFree(ga);
return;
}
long result_length = ga->used;
long returned_items = (count == 0 || result_length < count) ?
result_length : count;
long option_length = 0;
/* Our options are self-contained nested multibulk replies, so we
* only need to track how many of those nested replies we return. */
if (withdist)
option_length++;
if (withcoords)
option_length++;
if (withhash)
option_length++;
/* The multibulk len we send is exactly result_length. The result is either
* all strings of just zset members *or* a nested multi-bulk reply
* containing the zset member string _and_ all the additional options the
* user enabled for this request. */
addReplyMultiBulkLen(c, (count == 0 || result_length < count) ?
result_length : count);
/* Process [optional] requested sorting */
if (sort == SORT_ASC) {
qsort(ga->array, result_length, sizeof(geoPoint), sort_gp_asc);
@@ -545,35 +550,94 @@ void georadiusGeneric(client *c, int type) {
qsort(ga->array, result_length, sizeof(geoPoint), sort_gp_desc);
}
/* Finally send results back to the caller */
int i;
for (i = 0; i < result_length; i++) {
geoPoint *gp = ga->array+i;
gp->dist /= conversion; /* Fix according to unit. */
/* If we have options in option_length, return each sub-result
* as a nested multi-bulk. Add 1 to account for result value itself. */
if (option_length)
addReplyMultiBulkLen(c, option_length + 1);
addReplyBulkSds(c,gp->member);
gp->member = NULL;
if (storekey == NULL) {
/* No target key, return results to user. */
/* Our options are self-contained nested multibulk replies, so we
* only need to track how many of those nested replies we return. */
if (withdist)
addReplyDoubleDistance(c, gp->dist);
option_length++;
if (withcoords)
option_length++;
if (withhash)
addReplyLongLong(c, gp->score);
option_length++;
if (withcoords) {
addReplyMultiBulkLen(c, 2);
addReplyDouble(c, gp->longitude);
addReplyDouble(c, gp->latitude);
/* The multibulk len we send is exactly result_length. The result is
* either all strings of just zset members *or* a nested multi-bulk
* reply containing the zset member string _and_ all the additional
* options the user enabled for this request. */
addReplyMultiBulkLen(c, returned_items);
/* Finally send results back to the caller */
int i;
for (i = 0; i < returned_items; i++) {
geoPoint *gp = ga->array+i;
gp->dist /= conversion; /* Fix according to unit. */
/* If we have options in option_length, return each sub-result
* as a nested multi-bulk. Add 1 to account for result value
* itself. */
if (option_length)
addReplyMultiBulkLen(c, option_length + 1);
addReplyBulkSds(c,gp->member);
gp->member = NULL;
if (withdist)
addReplyDoubleDistance(c, gp->dist);
if (withhash)
addReplyLongLong(c, gp->score);
if (withcoords) {
addReplyMultiBulkLen(c, 2);
addReplyHumanLongDouble(c, gp->longitude);
addReplyHumanLongDouble(c, gp->latitude);
}
}
} else {
/* Target key, create a sorted set with the results. */
robj *zobj;
zset *zs;
int i;
size_t maxelelen = 0;
if (returned_items) {
zobj = createZsetObject();
zs = zobj->ptr;
}
/* Stop if COUNT was specified and we already provided the
* specified number of elements. */
if (count != 0 && count == i+1) break;
for (i = 0; i < returned_items; i++) {
zskiplistNode *znode;
geoPoint *gp = ga->array+i;
gp->dist /= conversion; /* Fix according to unit. */
double score = storedist ? gp->dist : gp->score;
size_t elelen = sdslen(gp->member);
robj *ele = createObject(OBJ_STRING,gp->member);
if (maxelelen < elelen) maxelelen = elelen;
incrRefCount(ele); /* Set refcount to 2 since we reference the
object both in the skiplist and dict. */
znode = zslInsert(zs->zsl,score,ele);
serverAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK);
gp->member = NULL;
}
if (returned_items) {
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
setKey(c->db,storekey,zobj);
decrRefCount(zobj);
notifyKeyspaceEvent(NOTIFY_LIST,"georadiusstore",storekey,
c->db->id);
server.dirty += returned_items;
} else if (dbDelete(c->db,storekey)) {
signalModifiedKey(c->db,storekey);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",storekey,c->db->id);
server.dirty++;
}
addReplyLongLong(c, returned_items);
}
geoArrayFree(ga);
}
@@ -670,8 +734,8 @@ void geoposCommand(client *c) {
continue;
}
addReplyMultiBulkLen(c,2);
addReplyDouble(c,xy[0]);
addReplyDouble(c,xy[1]);
addReplyHumanLongDouble(c,xy[0]);
addReplyHumanLongDouble(c,xy[1]);
}
}
}
@@ -711,6 +775,6 @@ void geodistCommand(client *c) {
if (!decodeGeohash(score1,xyxy) || !decodeGeohash(score2,xyxy+2))
addReply(c,shared.nullbulk);
else
addReplyDouble(c,
addReplyDoubleDistance(c,
geohashGetDistance(xyxy[0],xyxy[1],xyxy[2],xyxy[3]) / to_meter);
}
+62 -9
View File
@@ -479,6 +479,15 @@ void addReplyDouble(client *c, double d) {
}
}
/* Add a long double as a bulk reply, but uses a human readable formatting
* of the double instead of exposing the crude behavior of doubles to the
* dear user. */
void addReplyHumanLongDouble(client *c, long double d) {
robj *o = createStringObjectFromLongDouble(d,1);
addReplyBulk(c,o);
decrRefCount(o);
}
/* Add a long long as integer reply or bulk len / multi bulk count.
* Basically this is used to output <prefix><long long><crlf>. */
void addReplyLongLongWithPrefix(client *c, long long ll, char prefix) {
@@ -603,7 +612,7 @@ int clientHasPendingReplies(client *c) {
}
#define MAX_ACCEPTS_PER_CALL 1000
static void acceptCommonHandler(int fd, int flags) {
static void acceptCommonHandler(int fd, int flags, char *ip) {
client *c;
if ((c = createClient(fd)) == NULL) {
serverLog(LL_WARNING,
@@ -627,6 +636,48 @@ static void acceptCommonHandler(int fd, int flags) {
freeClient(c);
return;
}
/* If the server is running in protected mode (the default) and there
* is no password set, nor a specific interface is bound, we don't accept
* requests from non loopback interfaces. Instead we try to explain the
* user what to do to fix it if needed. */
if (server.protected_mode &&
server.bindaddr_count == 0 &&
server.requirepass == NULL &&
!(flags & CLIENT_UNIX_SOCKET) &&
ip != NULL)
{
if (strcmp(ip,"127.0.0.1") && strcmp(ip,"::1")) {
char *err =
"-DENIED Redis is running in protected mode because protected "
"mode is enabled, no bind address was specified, no "
"authentication password is requested to clients. In this mode "
"connections are only accepted from the loopback interface. "
"If you want to connect from external computers to Redis you "
"may adopt one of the following solutions: "
"1) Just disable protected mode sending the command "
"'CONFIG SET protected-mode no' from the loopback interface "
"by connecting to Redis from the same host the server is "
"running, however MAKE SURE Redis is not publicly accessible "
"from internet if you do so. Use CONFIG REWRITE to make this "
"change permanent. "
"2) Alternatively you can just disable the protected mode by "
"editing the Redis configuration file, and setting the protected "
"mode option to 'no', and then restarting the server. "
"3) If you started the server manually just for testing, restart "
"it with the '--protected-mode no' option. "
"4) Setup a bind address or an authentication password. "
"NOTE: You only need to do one of the above things in order for "
"the server to start accepting connections from the outside.\r\n";
if (write(c->fd,err,strlen(err)) == -1) {
/* Nothing to do, Just to avoid the warning... */
}
server.stat_rejected_conn++;
freeClient(c);
return;
}
}
server.stat_numconnections++;
c->flags |= flags;
}
@@ -647,7 +698,7 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
acceptCommonHandler(cfd,0);
acceptCommonHandler(cfd,0,cip);
}
}
@@ -666,7 +717,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
serverLog(LL_VERBOSE,"Accepted connection to %s", server.unixsocket);
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET);
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET,NULL);
}
}
@@ -1180,10 +1231,10 @@ int processMultibulkBuffer(client *c) {
{
c->argv[c->argc++] = createObject(OBJ_STRING,c->querybuf);
sdsIncrLen(c->querybuf,-2); /* remove CRLF */
c->querybuf = sdsempty();
/* Assume that if we saw a fat argument we'll see another one
* likely... */
c->querybuf = sdsMakeRoomFor(c->querybuf,c->bulklen+2);
c->querybuf = sdsnewlen(NULL,c->bulklen+2);
sdsclear(c->querybuf);
pos = 0;
} else {
c->argv[c->argc++] =
@@ -1244,6 +1295,9 @@ void processInputBuffer(client *c) {
/* Only reset the client when the command was executed. */
if (processCommand(c) == C_OK)
resetClient(c);
/* 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) break;
}
}
server.current_client = NULL;
@@ -1416,9 +1470,8 @@ sds getAllClientsInfoString(void) {
listNode *ln;
listIter li;
client *client;
sds o = sdsempty();
o = sdsMakeRoomFor(o,200*listLength(server.clients));
sds o = sdsnewlen(NULL,200*listLength(server.clients));
sdsclear(o);
listRewind(server.clients,&li);
while ((ln = listNext(&li)) != NULL) {
client = listNodeValue(ln);
@@ -1858,7 +1911,7 @@ int clientsArePaused(void) {
* and so forth.
*
* It calls the event loop in order to process a few events. Specifically we
* try to call the event loop for times as long as we receive acknowledge that
* try to call the event loop 4 times as long as we receive acknowledge that
* some event was processed, in order to go forward with the accept, read,
* write, close sequence needed to serve a client.
*
+22 -5
View File
@@ -39,6 +39,7 @@
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/param.h>
#define RDB_LOAD_NONE 0
#define RDB_LOAD_ENC (1<<0)
@@ -386,7 +387,8 @@ int rdbSaveStringObject(rio *rdb, robj *obj) {
* efficient. When this flag is passed the function
* no longer guarantees that obj->ptr is an SDS string.
* RDB_LOAD_PLAIN: Return a plain string allocated with zmalloc()
* instead of a Redis object.
* instead of a Redis object with an sds in it.
* RDB_LOAD_SDS: Return an SDS string instead of a Redis object.
*/
void *rdbGenericLoadStringObject(rio *rdb, int flags) {
int encode = flags & RDB_LOAD_ENC;
@@ -766,7 +768,7 @@ int rdbSaveRio(rio *rdb, int *error) {
db_size = (dictSize(db->dict) <= UINT32_MAX) ?
dictSize(db->dict) :
UINT32_MAX;
expires_size = (dictSize(db->dict) <= UINT32_MAX) ?
expires_size = (dictSize(db->expires) <= UINT32_MAX) ?
dictSize(db->expires) :
UINT32_MAX;
if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;
@@ -832,6 +834,7 @@ werr: /* Write error. */
/* Save the DB on disk. Return C_ERR on error, C_OK on success. */
int rdbSave(char *filename) {
char tmpfile[256];
char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
FILE *fp;
rio rdb;
int error = 0;
@@ -839,7 +842,12 @@ int rdbSave(char *filename) {
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
fp = fopen(tmpfile,"w");
if (!fp) {
serverLog(LL_WARNING, "Failed opening .rdb for saving: %s",
char *cwdp = getcwd(cwd,MAXPATHLEN);
serverLog(LL_WARNING,
"Failed opening the RDB file %s (in server root dir %s) "
"for saving: %s",
filename,
cwdp ? cwdp : "unknown",
strerror(errno));
return C_ERR;
}
@@ -858,10 +866,18 @@ int rdbSave(char *filename) {
/* Use RENAME to make sure the DB file is changed atomically only
* if the generate DB file is ok. */
if (rename(tmpfile,filename) == -1) {
serverLog(LL_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));
char *cwdp = getcwd(cwd,MAXPATHLEN);
serverLog(LL_WARNING,
"Error moving temp DB file %s on the final "
"destination %s (in server root dir %s): %s",
tmpfile,
filename,
cwdp ? cwdp : "unknown",
strerror(errno));
unlink(tmpfile);
return C_ERR;
}
serverLog(LL_NOTICE,"DB saved on disk");
server.dirty = 0;
server.lastsave = time(NULL);
@@ -1572,7 +1588,7 @@ int rdbSaveToSlavesSockets(void) {
clientids[numfds] = slave->id;
fds[numfds++] = slave->fd;
replicationSetupSlaveForFullResync(slave,getPsyncInitialOffset());
/* Put the socket in non-blocking mode to simplify RDB transfer.
/* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the children returns (since duped socket
* will share the O_NONBLOCK attribute with the parent). */
anetBlock(NULL,slave->fd);
@@ -1646,6 +1662,7 @@ int rdbSaveToSlavesSockets(void) {
zfree(msg);
}
zfree(clientids);
rioFreeFdset(&slave_sockets);
exitFromChild((retval == C_OK) ? 0 : 1);
} else {
/* Parent */
+163 -21
View File
@@ -63,6 +63,8 @@
#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
#define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE"
#define REDIS_CLI_HISTFILE_DEFAULT ".rediscli_history"
#define REDIS_CLI_RCFILE_ENV "REDISCLI_RCFILE"
#define REDIS_CLI_RCFILE_DEFAULT ".redisclirc"
/* --latency-dist palettes. */
int spectrum_palette_color_size = 19;
@@ -114,9 +116,15 @@ static struct config {
int eval_ldb;
int eval_ldb_sync; /* Ask for synchronous mode of the Lua debugger. */
int eval_ldb_end; /* Lua debugging session ended. */
int enable_ldb_on_eval; /* Handle manual SCRIPT DEBUG + EVAL commands. */
int last_cmd_type;
} config;
/* User preferences. */
static struct pref {
int hints;
} pref;
static volatile sig_atomic_t force_cancel_loop = 0;
static void usage(void);
static void slaveMode(void);
@@ -158,28 +166,35 @@ static void cliRefreshPrompt(void) {
snprintf(config.prompt+len,sizeof(config.prompt)-len,"> ");
}
static sds getHistoryPath() {
/* Return the name of the dotfile for the specified 'dotfilename'.
* Normally it just concatenates user $HOME to the file specified
* in 'dotfilename'. However if the environment varialbe 'envoverride'
* is set, its value is taken as the path.
*
* The function returns NULL (if the file is /dev/null or cannot be
* obtained for some error), or an SDS string that must be freed by
* the user. */
static sds getDotfilePath(char *envoverride, char *dotfilename) {
char *path = NULL;
sds historyPath = NULL;
sds dotPath = NULL;
/* check the env for a histfile override */
path = getenv(REDIS_CLI_HISTFILE_ENV);
/* Check the env for a dotfile override. */
path = getenv(envoverride);
if (path != NULL && *path != '\0') {
if (!strcmp("/dev/null", path)) {
return NULL;
}
/* if the env is set, return it */
historyPath = sdscatprintf(sdsempty(), "%s", path);
/* If the env is set, return it. */
dotPath = sdsnew(path);
} else {
char *home = getenv("HOME");
if (home != NULL && *home != '\0') {
/* otherwise, return the default */
historyPath = sdscatprintf(sdsempty(), "%s/%s", home, REDIS_CLI_HISTFILE_DEFAULT);
/* If no override is set use $HOME/<dotfilename>. */
dotPath = sdscatprintf(sdsempty(), "%s/%s", home, dotfilename);
}
}
return historyPath;
return dotPath;
}
/*------------------------------------------------------------------------------
@@ -258,11 +273,17 @@ static void cliOutputCommandHelp(struct commandHelp *help, int group) {
static void cliOutputGenericHelp(void) {
sds version = cliVersion();
printf(
"redis-cli %s\r\n"
"Type: \"help @<group>\" to get a list of commands in <group>\r\n"
" \"help <command>\" for help on <command>\r\n"
" \"help <tab>\" to get a list of possible help topics\r\n"
" \"quit\" to exit\r\n",
"redis-cli %s\n"
"To get help about Redis commands type:\n"
" \"help @<group>\" to get a list of commands in <group>\n"
" \"help <command>\" for help on <command>\n"
" \"help <tab>\" to get a list of possible help topics\n"
" \"quit\" to exit\n"
"\n"
"To set redis-cli perferences:\n"
" \":set hints\" enable online hints\n"
" \":set nohints\" disable online hints\n"
"Set your preferences in ~/.redisclirc\n",
version
);
sdsfree(version);
@@ -313,6 +334,7 @@ static void cliOutputHelp(int argc, char **argv) {
printf("\r\n");
}
/* Linenoise completion callback. */
static void completionCallback(const char *buf, linenoiseCompletions *lc) {
size_t startpos = 0;
int mask;
@@ -341,6 +363,58 @@ static void completionCallback(const char *buf, linenoiseCompletions *lc) {
}
}
/* Linenoise hints callback. */
static char *hintsCallback(const char *buf, int *color, int *bold) {
if (!pref.hints) return NULL;
int i, argc, buflen = strlen(buf);
sds *argv = sdssplitargs(buf,&argc);
int endspace = buflen && isspace(buf[buflen-1]);
/* Check if the argument list is empty and return ASAP. */
if (argc == 0) {
sdsfreesplitres(argv,argc);
return NULL;
}
for (i = 0; i < helpEntriesLen; i++) {
if (!(helpEntries[i].type & CLI_HELP_COMMAND)) continue;
if (strcasecmp(argv[0],helpEntries[i].full) == 0)
{
*color = 90;
*bold = 0;
sds hint = sdsnew(helpEntries[i].org->params);
/* Remove arguments from the returned hint to show only the
* ones the user did not yet typed. */
int toremove = argc-1;
while(toremove > 0 && sdslen(hint)) {
if (hint[0] == '[') break;
if (hint[0] == ' ') toremove--;
sdsrange(hint,1,-1);
}
/* Add an initial space if needed. */
if (!endspace) {
sds newhint = sdsnewlen(" ",1);
newhint = sdscatsds(newhint,hint);
sdsfree(hint);
hint = newhint;
}
sdsfreesplitres(argv,argc);
return hint;
}
}
sdsfreesplitres(argv,argc);
return NULL;
}
static void freeHintsCallback(void *ptr) {
sdsfree(ptr);
}
/*------------------------------------------------------------------------------
* Networking / parsing
*--------------------------------------------------------------------------- */
@@ -551,6 +625,7 @@ static sds cliFormatReplyRaw(redisReply *r) {
/* Detect the end of a debugging session. */
if (strstr(r->str,"<endsession>") == r->str) {
config.enable_ldb_on_eval = 0;
config.eval_ldb = 0;
config.eval_ldb_end = 1; /* Signal the caller session ended. */
config.output = OUTPUT_STANDARD;
@@ -663,7 +738,7 @@ static int cliReadReply(int output_raw_strings) {
p = strchr(s+1,' '); /* MOVED[S]3999[P]127.0.0.1:6381 */
*p = '\0';
slot = atoi(s+1);
s = strchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */
s = strrchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */
*s = '\0';
sdsfree(config.hostip);
config.hostip = sdsnew(p+1);
@@ -712,7 +787,7 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
output_raw = 0;
if (!strcasecmp(command,"info") ||
(argc >= 2 && !strcasecmp(command,"debug") &&
(!strcasecmp(argv[1],"jemalloc") ||
((!strcasecmp(argv[1],"jemalloc") && !strcasecmp(argv[2],"info")) ||
!strcasecmp(argv[1],"htstats"))) ||
(argc == 2 && !strcasecmp(command,"cluster") &&
(!strcasecmp(argv[1],"nodes") ||
@@ -734,6 +809,24 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
if (!strcasecmp(command,"sync") ||
!strcasecmp(command,"psync")) config.slave_mode = 1;
/* When the user manually calls SCRIPT DEBUG, setup the activation of
* debugging mode on the next eval if needed. */
if (argc == 3 && !strcasecmp(argv[0],"script") &&
!strcasecmp(argv[1],"debug"))
{
if (!strcasecmp(argv[2],"yes") || !strcasecmp(argv[2],"sync")) {
config.enable_ldb_on_eval = 1;
} else {
config.enable_ldb_on_eval = 0;
}
}
/* Actually activate LDB on EVAL if needed. */
if (!strcasecmp(command,"eval") && config.enable_ldb_on_eval) {
config.eval_ldb = 1;
config.output = OUTPUT_RAW;
}
/* Setup argument length */
argvlen = malloc(argc*sizeof(size_t));
for (j = 0; j < argc; j++)
@@ -1003,7 +1096,8 @@ static void usage(void) {
" (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n"
"\n"
"When no command is given, redis-cli starts in interactive mode.\n"
"Type \"help\" in interactive mode for information on available commands.\n"
"Type \"help\" in interactive mode for information on available commands\n"
"and settings.\n"
"\n",
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
sdsfree(version);
@@ -1070,6 +1164,45 @@ static sds *cliSplitArgs(char *line, int *argc) {
}
}
/* Set the CLI perferences. This function is invoked when an interactive
* ":command" is called, or when reading ~/.redisclirc file, in order to
* set user preferences. */
void cliSetPreferences(char **argv, int argc, int interactive) {
if (!strcasecmp(argv[0],":set") && argc >= 2) {
if (!strcasecmp(argv[1],"hints")) pref.hints = 1;
else if (!strcasecmp(argv[1],"nohints")) pref.hints = 0;
else {
printf("%sunknown redis-cli preference '%s'\n",
interactive ? "" : ".redisclirc: ",
argv[1]);
}
} else {
printf("%sunknown redis-cli internal command '%s'\n",
interactive ? "" : ".redisclirc: ",
argv[0]);
}
}
/* Load the ~/.redisclirc file if any. */
void cliLoadPreferences(void) {
sds rcfile = getDotfilePath(REDIS_CLI_RCFILE_ENV,REDIS_CLI_RCFILE_DEFAULT);
if (rcfile == NULL) return;
FILE *fp = fopen(rcfile,"r");
char buf[1024];
if (fp) {
while(fgets(buf,sizeof(buf),fp) != NULL) {
sds *argv;
int argc;
argv = sdssplitargs(buf,&argc);
if (argc > 0) cliSetPreferences(argv,argc,0);
sdsfreesplitres(argv,argc);
}
}
sdsfree(rcfile);
}
static void repl(void) {
sds historyfile = NULL;
int history = 0;
@@ -1080,14 +1213,17 @@ static void repl(void) {
config.interactive = 1;
linenoiseSetMultiLine(1);
linenoiseSetCompletionCallback(completionCallback);
linenoiseSetHintsCallback(hintsCallback);
linenoiseSetFreeHintsCallback(freeHintsCallback);
/* Only use history when stdin is a tty. */
/* Only use history and load the rc file when stdin is a tty. */
if (isatty(fileno(stdin))) {
historyfile = getHistoryPath();
historyfile = getDotfilePath(REDIS_CLI_HISTFILE_ENV,REDIS_CLI_HISTFILE_DEFAULT);
if (historyfile != NULL) {
history = 1;
linenoiseHistoryLoad(historyfile);
}
cliLoadPreferences();
}
cliRefreshPrompt();
@@ -1106,6 +1242,9 @@ static void repl(void) {
strcasecmp(argv[0],"exit") == 0)
{
exit(0);
} else if (argv[0][0] == ':') {
cliSetPreferences(argv,argc,1);
continue;
} else if (strcasecmp(argv[0],"restart") == 0) {
if (config.eval) {
config.eval_ldb = 1;
@@ -2331,7 +2470,7 @@ static void intrinsicLatencyMode(void) {
}
double avg_us = (double)run_time/runs;
double avg_ns = avg_us * 10e3;
double avg_ns = avg_us * 1e3;
if (force_cancel_loop || end > test_end) {
printf("\n%lld total runs "
"(avg latency: "
@@ -2383,8 +2522,11 @@ int main(int argc, char **argv) {
config.eval_ldb = 0;
config.eval_ldb_end = 0;
config.eval_ldb_sync = 0;
config.enable_ldb_on_eval = 0;
config.last_cmd_type = -1;
pref.hints = 1;
spectrum_palette = spectrum_palette_color;
spectrum_palette_size = spectrum_palette_color_size;
+116 -29
View File
@@ -407,7 +407,8 @@ class RedisTrib
cluster_error \
"[WARNING] Node #{n} has slots in migrating state (#{n.info[:migrating].keys.join(",")})."
open_slots += n.info[:migrating].keys
elsif n.info[:importing].size > 0
end
if n.info[:importing].size > 0
cluster_error \
"[WARNING] Node #{n} has slots in importing state (#{n.info[:importing].keys.join(",")})."
open_slots += n.info[:importing].keys
@@ -425,6 +426,7 @@ class RedisTrib
def nodes_with_keys_in_slot(slot)
nodes = []
@nodes.each{|n|
next if n.has_flag?("slave")
nodes << n if n.r.cluster("getkeysinslot",slot,1).length > 0
}
nodes
@@ -443,7 +445,7 @@ class RedisTrib
not_covered.each{|slot|
nodes = nodes_with_keys_in_slot(slot)
slots[slot] = nodes
xputs "Slot #{slot} has keys in #{nodes.length} nodes: #{nodes.join}"
xputs "Slot #{slot} has keys in #{nodes.length} nodes: #{nodes.join(", ")}"
}
none = slots.select {|k,v| v.length == 0}
@@ -479,26 +481,50 @@ class RedisTrib
xputs multi.keys.join(",")
yes_or_die "Fix these slots by moving keys into a single node?"
multi.each{|slot,nodes|
xputs ">>> Covering slot #{slot} moving keys to #{nodes[0]}"
# TODO
# 1) Set all nodes as "MIGRATING" for this slot, so that we
# can access keys in the hash slot using ASKING.
# 2) Move everything to node[0]
# 3) Clear MIGRATING from nodes, and ADDSLOTS the slot to
# node[0].
raise "TODO: Work in progress"
target = get_node_with_most_keys_in_slot(nodes,slot)
xputs ">>> Covering slot #{slot} moving keys to #{target}"
target.r.cluster('addslots',slot)
target.r.cluster('setslot',slot,'stable')
nodes.each{|src|
next if src == target
# Set the source node in 'importing' state (even if we will
# actually migrate keys away) in order to avoid receiving
# redirections for MIGRATE.
src.r.cluster('setslot',slot,'importing',target.info[:name])
move_slot(src,target,slot,:dots=>true,:fix=>true,:cold=>true)
src.r.cluster('setslot',slot,'stable')
}
}
end
end
# Return the owner of the specified slot
def get_slot_owner(slot)
def get_slot_owners(slot)
owners = []
@nodes.each{|n|
next if n.has_flag?("slave")
n.slots.each{|s,_|
return n if s == slot
owners << n if s == slot
}
}
nil
owners
end
# Return the node, among 'nodes' with the greatest number of keys
# in the specified slot.
def get_node_with_most_keys_in_slot(nodes,slot)
best = nil
best_numkeys = 0
@nodes.each{|n|
next if n.has_flag?("slave")
numkeys = n.r.cluster("countkeysinslot",slot)
if numkeys > best_numkeys || best == nil
best = n
best_numkeys = numkeys
end
}
return best
end
# Slot 'slot' was found to be in importing or migrating state in one or
@@ -509,16 +535,8 @@ class RedisTrib
# Try to obtain the current slot owner, according to the current
# nodes configuration.
owner = get_slot_owner(slot)
# 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
xputs "*** Fix me, some work to do here."
# Select owner...
# Use ADDSLOTS to assign the slot.
exit 1
end
owners = get_slot_owners(slot)
owner = owners[0] if owners.length == 1
migrating = []
importing = []
@@ -536,6 +554,53 @@ class RedisTrib
puts "Set as migrating in: #{migrating.join(",")}"
puts "Set as importing in: #{importing.join(",")}"
# 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
xputs ">>> Nobody claims ownership, selecting an owner..."
owner = get_node_with_most_keys_in_slot(@nodes,slot)
# If we still don't have an owner, we can't fix it.
if !owner
xputs "[ERR] Can't select a slot owner. Impossible to fix."
exit 1
end
# Use ADDSLOTS to assign the slot.
puts "*** Configuring #{owner} as the slot owner"
owner.r.cluster("setslot",slot,"stable")
owner.r.cluster("addslots",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.
owner.r.cluster("bumpepoch")
# Remove the owner from the list of migrating/importing
# nodes.
migrating.delete(owner)
importing.delete(owner)
end
# If there are multiple owners of the slot, we need to fix it
# so that a single node is the owner and all the other nodes
# are in importing state. Later the fix can be handled by one
# of the base cases above.
#
# Note that this case also covers multiple nodes having the slot
# in migrating state, since migrating is a valid state only for
# slot owners.
if owners.length > 1
owner = get_node_with_most_keys_in_slot(owners,slot)
owners.each{|n|
next if n == owner
n.r.cluster('delslots',slot)
n.r.cluster('setslot',slot,'importing',owner.info[:name])
importing.delete(n) # Avoid duplciates
importing << n
}
owner.r.cluster('bumpepoch')
end
# Case 1: The slot is in migrating state in one slot, and in
# importing state in 1 slot. That's trivial to address.
if migrating.length == 1 && importing.length == 1
@@ -873,11 +938,11 @@ class RedisTrib
source.r.client.call(["migrate",target.info[:host],target.info[:port],"",0,@timeout,:keys,*keys])
rescue => e
if o[:fix] && e.to_s =~ /BUSYKEY/
xputs "*** Target key #{key} exists. Replacing it for FIX."
xputs "*** Target key exists. Replacing it for FIX."
source.r.client.call(["migrate",target.info[:host],target.info[:port],"",0,@timeout,:replace,:keys,*keys])
else
puts ""
xputs "[ERR] #{e}"
xputs "[ERR] Calling MIGRATE: #{e}"
exit 1
end
end
@@ -889,6 +954,7 @@ class RedisTrib
# Set the new node as the owner of the slot in all the known nodes.
if !o[:cold]
@nodes.each{|n|
next if n.has_flag?("slave")
n.r.cluster("setslot",slot,"node",target.info[:name])
}
end
@@ -979,7 +1045,6 @@ class RedisTrib
over_threshold = true
end
end
puts "#{n} balance is #{n.info[:balance]} slots" if $verbose
threshold_reached = true if over_threshold
end
}
@@ -988,15 +1053,37 @@ class RedisTrib
return
end
# Sort nodes by their slots balance.
# Only consider nodes we want to change
sn = @nodes.select{|n|
n.has_flag?("master") && n.info[:w]
}.sort{|a,b|
}
# Because of rounding, it is possible that the balance of all nodes
# summed does not give 0. Make sure that nodes that have to provide
# slots are always matched by nodes receiving slots.
total_balance = sn.map{|x| x.info[:balance]}.reduce{|a,b| a+b}
while total_balance > 0
sn.each{|n|
if n.info[:balance] < 0 && total_balance > 0
n.info[:balance] -= 1
total_balance -= 1
end
}
end
# Sort nodes by their slots balance.
sn = sn.sort{|a,b|
a.info[:balance] <=> b.info[:balance]
}
xputs ">>> Rebalancing across #{nodes_involved} nodes. Total weight = #{total_weight}"
if $verbose
sn.each{|n|
puts "#{n} balance is #{n.info[:balance]} slots"
}
end
# Now we have at the start of the 'sn' array nodes that should get
# slots, at the end nodes that must give slots.
# We take two indexes, one at the start, and one at the end,
@@ -1570,7 +1657,7 @@ ALLOWED_OPTIONS={
"add-node" => {"slave" => false, "master-id" => true},
"import" => {"from" => :required, "copy" => false, "replace" => false},
"reshard" => {"from" => true, "to" => true, "slots" => true, "yes" => false, "timeout" => true, "pipeline" => true},
"rebalance" => {"weight" => [], "auto-weights" => false, "threshold" => RebalanceDefaultThreshold, "use-empty-masters" => false, "timeout" => true, "simulate" => false, "pipeline" => true, "threshold" => true},
"rebalance" => {"weight" => [], "auto-weights" => false, "use-empty-masters" => false, "timeout" => true, "simulate" => false, "pipeline" => true, "threshold" => true},
"fix" => {"timeout" => MigrateDefaultTimeout},
}
+3 -2
View File
@@ -163,7 +163,7 @@ void rioInitWithFile(rio *r, FILE *fp) {
* The function returns success as long as we are able to correctly write
* to at least one file descriptor.
*
* When buf is NULL adn len is 0, the function performs a flush operation
* When buf is NULL and len is 0, the function performs a flush operation
* if there is some pending buffer, so this function is also used in order
* to implement rioFdsetFlush(). */
static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
@@ -176,7 +176,7 @@ static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
* a given size, we actually write to the sockets. */
if (len) {
r->io.fdset.buf = sdscatlen(r->io.fdset.buf,buf,len);
len = 0; /* Prevent entering the while belove if we don't flush. */
len = 0; /* Prevent entering the while below if we don't flush. */
if (sdslen(r->io.fdset.buf) > PROTO_IOBUF_LEN) doflush = 1;
}
@@ -276,6 +276,7 @@ void rioInitWithFdset(rio *r, int *fds, int numfds) {
r->io.fdset.buf = sdsempty();
}
/* release the rio stream. */
void rioFreeFdset(rio *r) {
zfree(r->io.fdset.fds);
zfree(r->io.fdset.state);
+2
View File
@@ -128,6 +128,8 @@ void rioInitWithFile(rio *r, FILE *fp);
void rioInitWithBuffer(rio *r, sds s);
void rioInitWithFdset(rio *r, int *fds, int numfds);
void rioFreeFdset(rio *r);
size_t rioWriteBulkCount(rio *r, char prefix, int count);
size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
size_t rioWriteBulkLongLong(rio *r, long long l);
+20 -8
View File
@@ -463,7 +463,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
luaPushError(lua,"Unknown Redis command called from Lua script");
goto cleanup;
}
c->cmd = cmd;
c->cmd = c->lastcmd = cmd;
/* There are commands that are not allowed inside scripts. */
if (cmd->flags & CMD_NOSCRIPT) {
@@ -512,8 +512,10 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
/* If this is a Redis Cluster node, we need to make sure Lua is not
* trying to access non-local keys, with the exception of commands
* received from our master. */
if (server.cluster_enabled && !(server.lua_caller->flags & CLIENT_MASTER)) {
* received from our master or when loading the AOF back in memory. */
if (server.cluster_enabled && !server.loading &&
!(server.lua_caller->flags & CLIENT_MASTER))
{
/* Duplicate relevant flags in the lua client. */
c->flags &= ~(CLIENT_READONLY|CLIENT_ASKING);
c->flags |= server.lua_caller->flags & (CLIENT_READONLY|CLIENT_ASKING);
@@ -1145,7 +1147,7 @@ int luaCreateFunction(client *c, lua_State *lua, char *funcname, robj *body) {
funcdef = sdscatlen(funcdef,funcname,42);
funcdef = sdscatlen(funcdef,"() ",3);
funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr));
funcdef = sdscatlen(funcdef," end",4);
funcdef = sdscatlen(funcdef,"\nend",4);
if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) {
addReplyErrorFormat(c,"Error compiling script (new function): %s\n",
@@ -1846,9 +1848,13 @@ void ldbList(int around, int context) {
*
* The element is not automatically removed from the stack, nor it is
* converted to a different type. */
sds ldbCatStackValue(sds s, lua_State *lua, int idx) {
#define LDB_MAX_VALUES_DEPTH (LUA_MINSTACK/2)
sds ldbCatStackValueRec(sds s, lua_State *lua, int idx, int level) {
int t = lua_type(lua,idx);
if (level++ == LDB_MAX_VALUES_DEPTH)
return sdscat(s,"<max recursion level reached! Nested table?>");
switch(t) {
case LUA_TSTRING:
{
@@ -1883,13 +1889,13 @@ sds ldbCatStackValue(sds s, lua_State *lua, int idx) {
lua_tonumber(lua,-2) != expected_index)) is_array = 0;
/* Stack now: table, key, value */
/* Array repr. */
repr1 = ldbCatStackValue(repr1,lua,-1);
repr1 = ldbCatStackValueRec(repr1,lua,-1,level);
repr1 = sdscatlen(repr1,"; ",2);
/* Full repr. */
repr2 = sdscatlen(repr2,"[",1);
repr2 = ldbCatStackValue(repr2,lua,-2);
repr2 = ldbCatStackValueRec(repr2,lua,-2,level);
repr2 = sdscatlen(repr2,"]=",2);
repr2 = ldbCatStackValue(repr2,lua,-1);
repr2 = ldbCatStackValueRec(repr2,lua,-1,level);
repr2 = sdscatlen(repr2,"; ",2);
lua_pop(lua,1); /* Stack: table, key. Ready for next iteration. */
expected_index++;
@@ -1926,6 +1932,12 @@ sds ldbCatStackValue(sds s, lua_State *lua, int idx) {
return s;
}
/* Higher level wrapper for ldbCatStackValueRec() that just uses an initial
* recursion level of '0'. */
sds ldbCatStackValue(sds s, lua_State *lua, int idx) {
return ldbCatStackValueRec(s,lua,idx,0);
}
/* Produce a debugger log entry representing the value of the Lua object
* currently on the top of the stack. The element is ot popped nor modified.
* Check ldbCatStackValue() for the actual implementation. */
+36 -19
View File
@@ -518,7 +518,7 @@ sentinelAddr *createSentinelAddr(char *hostname, int port) {
char ip[NET_IP_STR_LEN];
sentinelAddr *sa;
if (port <= 0 || port > 65535) {
if (port < 0 || port > 65535) {
errno = EINVAL;
return NULL;
}
@@ -1132,7 +1132,8 @@ void sentinelDisconnectCallback(const redisAsyncContext *c, int status) {
* createSentinelAddr() function.
*
* The function may also fail and return NULL with errno set to EBUSY if
* a master or slave with the same name already exists. */
* a master with the same name, a slave with the same address, or a sentinel
* with the same ID already exists. */
sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *hostname, int port, int quorum, sentinelRedisInstance *master) {
sentinelRedisInstance *ri;
@@ -1147,8 +1148,8 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
addr = createSentinelAddr(hostname,port);
if (addr == NULL) return NULL;
/* For slaves and sentinel we use ip:port as name. */
if (flags & (SRI_SLAVE|SRI_SENTINEL)) {
/* For slaves use ip:port as name. */
if (flags & SRI_SLAVE) {
anetFormatAddr(slavename, sizeof(slavename), hostname, port);
name = slavename;
}
@@ -1657,15 +1658,15 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
(argc == 4 || argc == 5)) {
sentinelRedisInstance *si;
/* known-sentinel <name> <ip> <port> [runid] */
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
if ((si = createSentinelRedisInstance(NULL,SRI_SENTINEL,argv[2],
atoi(argv[3]), ri->quorum, ri)) == NULL)
{
return "Wrong hostname or port for sentinel.";
}
if (argc == 5) {
if (argc == 5) { /* Ignore the old form without runid. */
/* known-sentinel <name> <ip> <port> [runid] */
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
if ((si = createSentinelRedisInstance(argv[4],SRI_SENTINEL,argv[2],
atoi(argv[3]), ri->quorum, ri)) == NULL)
{
return "Wrong hostname or port for sentinel.";
}
si->runid = sdsnew(argv[4]);
sentinelTryConnectionSharing(si);
}
@@ -1796,11 +1797,10 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
di2 = dictGetIterator(master->sentinels);
while((de = dictNext(di2)) != NULL) {
ri = dictGetVal(de);
if (ri->runid == NULL) continue;
line = sdscatprintf(sdsempty(),
"sentinel known-sentinel %s %s %d%s%s",
master->name, ri->addr->ip, ri->addr->port,
ri->runid ? " " : "",
ri->runid ? ri->runid : "");
"sentinel known-sentinel %s %s %d %s",
master->name, ri->addr->ip, ri->addr->port, ri->runid);
rewriteConfigRewriteLine(state,"sentinel",line,1);
}
dictReleaseIterator(di2);
@@ -1895,6 +1895,7 @@ void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char
* one of the two links (commands and pub/sub) is missing. */
void sentinelReconnectInstance(sentinelRedisInstance *ri) {
if (ri->link->disconnected == 0) return;
if (ri->addr->port == 0) return; /* port == 0 means invalid address. */
instanceLink *link = ri->link;
mstime_t now = mstime();
@@ -2340,11 +2341,25 @@ void sentinelProcessHelloMessage(char *hello, int hello_len) {
if (removed) {
sentinelEvent(LL_NOTICE,"+sentinel-address-switch",master,
"%@ ip %s port %d for %s", token[0],port,token[2]);
} else {
/* Check if there is another Sentinel with the same address this
* new one is reporting. What we do if this happens is to set its
* port to 0, to signal the address is invalid. We'll update it
* later if we get an HELLO message. */
sentinelRedisInstance *other =
getSentinelRedisInstanceByAddrAndRunID(
master->sentinels, token[0],port,NULL);
if (other) {
sentinelEvent(LL_NOTICE,"+sentinel-invalid-addr",other,"%@");
other->addr->port = 0; /* It means: invalid address. */
sentinelUpdateSentinelAddressInAllMasters(other);
}
}
/* Add the new sentinel. */
si = createSentinelRedisInstance(NULL,SRI_SENTINEL,
si = createSentinelRedisInstance(token[2],SRI_SENTINEL,
token[0],port,master->quorum,master);
if (si) {
if (!removed) sentinelEvent(LL_NOTICE,"+sentinel",si,"%@");
/* The runid is NULL after a new instance creation and
@@ -2366,7 +2381,7 @@ void sentinelProcessHelloMessage(char *hello, int hello_len) {
}
/* Update master info if received configuration is newer. */
if (master->config_epoch < master_config_epoch) {
if (si && master->config_epoch < master_config_epoch) {
master->config_epoch = master_config_epoch;
if (master_port != master->addr->port ||
strcmp(master->addr->ip, token[5]))
@@ -3372,6 +3387,8 @@ void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
if (ri->link->act_ping_time)
elapsed = mstime() - ri->link->act_ping_time;
else if (ri->link->disconnected)
elapsed = mstime() - ri->link->last_avail_time;
/* Check if we are in need for a reconnection of one of the
* links, because we are detecting low activity.
+75 -68
View File
@@ -134,6 +134,7 @@ struct redisCommand redisCommandTable[] = {
{"exists",existsCommand,-2,"rF",0,NULL,1,-1,1,0,0},
{"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},
{"getbit",getbitCommand,3,"rF",0,NULL,1,1,1,0,0},
{"bitfield",bitfieldCommand,-2,"wm",0,NULL,1,1,1,0,0},
{"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0},
{"getrange",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
{"substr",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
@@ -215,7 +216,7 @@ struct redisCommand redisCommandTable[] = {
{"mset",msetCommand,-3,"wm",0,NULL,1,-1,2,0,0},
{"msetnx",msetnxCommand,-3,"wm",0,NULL,1,-1,2,0,0},
{"randomkey",randomkeyCommand,1,"rR",0,NULL,0,0,0,0,0},
{"select",selectCommand,2,"rlF",0,NULL,0,0,0,0,0},
{"select",selectCommand,2,"lF",0,NULL,0,0,0,0,0},
{"move",moveCommand,3,"wF",0,NULL,1,1,1,0,0},
{"rename",renameCommand,3,"w",0,NULL,1,2,1,0,0},
{"renamenx",renamenxCommand,3,"wF",0,NULL,1,2,1,0,0},
@@ -226,73 +227,73 @@ struct redisCommand redisCommandTable[] = {
{"keys",keysCommand,2,"rS",0,NULL,0,0,0,0,0},
{"scan",scanCommand,-2,"rR",0,NULL,0,0,0,0,0},
{"dbsize",dbsizeCommand,1,"rF",0,NULL,0,0,0,0,0},
{"auth",authCommand,2,"rsltF",0,NULL,0,0,0,0,0},
{"ping",pingCommand,-1,"rtF",0,NULL,0,0,0,0,0},
{"echo",echoCommand,2,"rF",0,NULL,0,0,0,0,0},
{"save",saveCommand,1,"ars",0,NULL,0,0,0,0,0},
{"bgsave",bgsaveCommand,1,"ar",0,NULL,0,0,0,0,0},
{"bgrewriteaof",bgrewriteaofCommand,1,"ar",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"arlt",0,NULL,0,0,0,0,0},
{"lastsave",lastsaveCommand,1,"rRF",0,NULL,0,0,0,0,0},
{"auth",authCommand,2,"sltF",0,NULL,0,0,0,0,0},
{"ping",pingCommand,-1,"tF",0,NULL,0,0,0,0,0},
{"echo",echoCommand,2,"F",0,NULL,0,0,0,0,0},
{"save",saveCommand,1,"as",0,NULL,0,0,0,0,0},
{"bgsave",bgsaveCommand,1,"a",0,NULL,0,0,0,0,0},
{"bgrewriteaof",bgrewriteaofCommand,1,"a",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"alt",0,NULL,0,0,0,0,0},
{"lastsave",lastsaveCommand,1,"RF",0,NULL,0,0,0,0,0},
{"type",typeCommand,2,"rF",0,NULL,1,1,1,0,0},
{"multi",multiCommand,1,"rsF",0,NULL,0,0,0,0,0},
{"multi",multiCommand,1,"sF",0,NULL,0,0,0,0,0},
{"exec",execCommand,1,"sM",0,NULL,0,0,0,0,0},
{"discard",discardCommand,1,"rsF",0,NULL,0,0,0,0,0},
{"discard",discardCommand,1,"sF",0,NULL,0,0,0,0,0},
{"sync",syncCommand,1,"ars",0,NULL,0,0,0,0,0},
{"psync",syncCommand,3,"ars",0,NULL,0,0,0,0,0},
{"replconf",replconfCommand,-1,"arslt",0,NULL,0,0,0,0,0},
{"replconf",replconfCommand,-1,"aslt",0,NULL,0,0,0,0,0},
{"flushdb",flushdbCommand,1,"w",0,NULL,0,0,0,0,0},
{"flushall",flushallCommand,1,"w",0,NULL,0,0,0,0,0},
{"sort",sortCommand,-2,"wm",0,sortGetKeys,1,1,1,0,0},
{"info",infoCommand,-1,"rlt",0,NULL,0,0,0,0,0},
{"monitor",monitorCommand,1,"ars",0,NULL,0,0,0,0,0},
{"info",infoCommand,-1,"lt",0,NULL,0,0,0,0,0},
{"monitor",monitorCommand,1,"as",0,NULL,0,0,0,0,0},
{"ttl",ttlCommand,2,"rF",0,NULL,1,1,1,0,0},
{"pttl",pttlCommand,2,"rF",0,NULL,1,1,1,0,0},
{"persist",persistCommand,2,"wF",0,NULL,1,1,1,0,0},
{"slaveof",slaveofCommand,3,"ast",0,NULL,0,0,0,0,0},
{"role",roleCommand,1,"lst",0,NULL,0,0,0,0,0},
{"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0},
{"config",configCommand,-2,"art",0,NULL,0,0,0,0,0},
{"subscribe",subscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0},
{"unsubscribe",unsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0},
{"psubscribe",psubscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0},
{"punsubscribe",punsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0},
{"publish",publishCommand,3,"pltrF",0,NULL,0,0,0,0,0},
{"pubsub",pubsubCommand,-2,"pltrR",0,NULL,0,0,0,0,0},
{"watch",watchCommand,-2,"rsF",0,NULL,1,-1,1,0,0},
{"unwatch",unwatchCommand,1,"rsF",0,NULL,0,0,0,0,0},
{"cluster",clusterCommand,-2,"ar",0,NULL,0,0,0,0,0},
{"debug",debugCommand,-1,"as",0,NULL,0,0,0,0,0},
{"config",configCommand,-2,"lat",0,NULL,0,0,0,0,0},
{"subscribe",subscribeCommand,-2,"pslt",0,NULL,0,0,0,0,0},
{"unsubscribe",unsubscribeCommand,-1,"pslt",0,NULL,0,0,0,0,0},
{"psubscribe",psubscribeCommand,-2,"pslt",0,NULL,0,0,0,0,0},
{"punsubscribe",punsubscribeCommand,-1,"pslt",0,NULL,0,0,0,0,0},
{"publish",publishCommand,3,"pltF",0,NULL,0,0,0,0,0},
{"pubsub",pubsubCommand,-2,"pltR",0,NULL,0,0,0,0,0},
{"watch",watchCommand,-2,"sF",0,NULL,1,-1,1,0,0},
{"unwatch",unwatchCommand,1,"sF",0,NULL,0,0,0,0,0},
{"cluster",clusterCommand,-2,"a",0,NULL,0,0,0,0,0},
{"restore",restoreCommand,-4,"wm",0,NULL,1,1,1,0,0},
{"restore-asking",restoreCommand,-4,"wmk",0,NULL,1,1,1,0,0},
{"migrate",migrateCommand,-6,"w",0,migrateGetKeys,0,0,0,0,0},
{"asking",askingCommand,1,"r",0,NULL,0,0,0,0,0},
{"readonly",readonlyCommand,1,"rF",0,NULL,0,0,0,0,0},
{"readwrite",readwriteCommand,1,"rF",0,NULL,0,0,0,0,0},
{"asking",askingCommand,1,"F",0,NULL,0,0,0,0,0},
{"readonly",readonlyCommand,1,"F",0,NULL,0,0,0,0,0},
{"readwrite",readwriteCommand,1,"F",0,NULL,0,0,0,0,0},
{"dump",dumpCommand,2,"r",0,NULL,1,1,1,0,0},
{"object",objectCommand,3,"r",0,NULL,2,2,2,0,0},
{"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
{"client",clientCommand,-2,"as",0,NULL,0,0,0,0,0},
{"eval",evalCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
{"evalsha",evalShaCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
{"slowlog",slowlogCommand,-2,"r",0,NULL,0,0,0,0,0},
{"script",scriptCommand,-2,"rs",0,NULL,0,0,0,0,0},
{"time",timeCommand,1,"rRF",0,NULL,0,0,0,0,0},
{"slowlog",slowlogCommand,-2,"a",0,NULL,0,0,0,0,0},
{"script",scriptCommand,-2,"s",0,NULL,0,0,0,0,0},
{"time",timeCommand,1,"RF",0,NULL,0,0,0,0,0},
{"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
{"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0},
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0},
{"wait",waitCommand,3,"rs",0,NULL,0,0,0,0,0},
{"command",commandCommand,0,"rlt",0,NULL,0,0,0,0,0},
{"wait",waitCommand,3,"s",0,NULL,0,0,0,0,0},
{"command",commandCommand,0,"lt",0,NULL,0,0,0,0,0},
{"geoadd",geoaddCommand,-5,"wm",0,NULL,1,1,1,0,0},
{"georadius",georadiusCommand,-6,"r",0,NULL,1,1,1,0,0},
{"georadiusbymember",georadiusByMemberCommand,-5,"r",0,NULL,1,1,1,0,0},
{"georadius",georadiusCommand,-6,"w",0,NULL,1,1,1,0,0},
{"georadiusbymember",georadiusByMemberCommand,-5,"w",0,NULL,1,1,1,0,0},
{"geohash",geohashCommand,-2,"r",0,NULL,1,1,1,0,0},
{"geopos",geoposCommand,-2,"r",0,NULL,1,1,1,0,0},
{"geodist",geodistCommand,-4,"r",0,NULL,1,1,1,0,0},
{"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0},
{"pfselftest",pfselftestCommand,1,"a",0,NULL,0,0,0,0,0},
{"pfadd",pfaddCommand,-2,"wmF",0,NULL,1,1,1,0,0},
{"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},
{"latency",latencyCommand,-2,"arslt",0,NULL,0,0,0,0,0}
{"latency",latencyCommand,-2,"aslt",0,NULL,0,0,0,0,0}
};
struct evictionPoolEntry *evictionPoolAlloc(void);
@@ -849,18 +850,22 @@ void activeExpireCycle(int type) {
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
ttl = dictGetSignedIntegerVal(de)-now;
if (activeExpireCycleTryExpire(db,de,now)) expired++;
if (ttl < 0) ttl = 0;
ttl_sum += ttl;
ttl_samples++;
if (ttl > 0) {
/* We want the average TTL of keys yet not expired. */
ttl_sum += ttl;
ttl_samples++;
}
}
/* Update the average TTL stats for this database. */
if (ttl_samples) {
long long avg_ttl = ttl_sum/ttl_samples;
/* Do a simple running average with a few samples.
* We just use the current estimate with a weight of 2%
* and the previous estimate with a weight of 98%. */
if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
/* Smooth the value averaging with the previous one. */
db->avg_ttl = (db->avg_ttl+avg_ttl)/2;
db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
}
/* We can't block forever here even if there are many keys to
@@ -1440,6 +1445,7 @@ void initServerConfig(void) {
server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM;
server.ipfd_count = 0;
server.sofd = -1;
server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE;
server.dbnum = CONFIG_DEFAULT_DBNUM;
server.verbosity = CONFIG_DEFAULT_VERBOSITY;
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
@@ -1758,17 +1764,19 @@ int listenToPort(int port, int *fds, int *count) {
if (fds[*count] != ANET_ERR) {
anetNonBlock(NULL,fds[*count]);
(*count)++;
/* Bind the IPv4 address as well. */
fds[*count] = anetTcpServer(server.neterr,port,NULL,
server.tcp_backlog);
if (fds[*count] != ANET_ERR) {
anetNonBlock(NULL,fds[*count]);
(*count)++;
}
}
fds[*count] = anetTcpServer(server.neterr,port,NULL,
server.tcp_backlog);
if (fds[*count] != ANET_ERR) {
anetNonBlock(NULL,fds[*count]);
(*count)++;
}
/* Exit the loop if we were able to bind * on IPv4 or IPv6,
/* Exit the loop if we were able to bind * on IPv4 and IPv6,
* otherwise fds[*count] will be ANET_ERR and we'll print an
* error and return to the caller with an error. */
if (*count) break;
if (*count == 2) break;
} else if (strchr(server.bindaddr[j],':')) {
/* Bind IPv6 address. */
fds[*count] = anetTcp6Server(server.neterr,port,server.bindaddr[j],
@@ -2239,8 +2247,8 @@ void call(client *c, int flags) {
slowlogPushEntryIfNeeded(c->argv,c->argc,duration);
}
if (flags & CMD_CALL_STATS) {
c->cmd->microseconds += duration;
c->cmd->calls++;
c->lastcmd->microseconds += duration;
c->lastcmd->calls++;
}
/* Propagate the command into the AOF and replication link */
@@ -2354,22 +2362,21 @@ int processCommand(client *c) {
!(c->flags & CLIENT_MASTER) &&
!(c->flags & CLIENT_LUA &&
server.lua_caller->flags & CLIENT_MASTER) &&
!(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0))
!(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0 &&
c->cmd->proc != execCommand))
{
int hashslot;
if (server.cluster->state != CLUSTER_OK) {
flagTransaction(c);
clusterRedirectClient(c,NULL,0,CLUSTER_REDIR_DOWN_STATE);
return C_OK;
} else {
int error_code;
clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,&hashslot,&error_code);
if (n == NULL || n != server.cluster->myself) {
int error_code;
clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,
&hashslot,&error_code);
if (n == NULL || n != server.cluster->myself) {
if (c->cmd->proc == execCommand) {
discardTransaction(c);
} else {
flagTransaction(c);
clusterRedirectClient(c,n,hashslot,error_code);
return C_OK;
}
clusterRedirectClient(c,n,hashslot,error_code);
return C_OK;
}
}
@@ -2906,7 +2913,7 @@ sds genRedisInfoString(char *section) {
char maxmemory_hmem[64];
size_t zmalloc_used = zmalloc_used_memory();
size_t total_system_mem = server.system_memory_size;
const char *evict_policy = maxmemoryToString();
const char *evict_policy = evictPolicyToString();
long long memory_lua = (long long)lua_gc(server.lua,LUA_GCCOUNT,0)*1024;
/* Peak memory is updated from time to time by serverCron() so it
@@ -3815,7 +3822,7 @@ int redisSupervisedUpstart(void) {
return 0;
}
serverLog(LL_NOTICE, "supervised by upstart, will stop to signal readyness");
serverLog(LL_NOTICE, "supervised by upstart, will stop to signal readiness");
raise(SIGSTOP);
unsetenv("UPSTART_JOB");
return 1;
@@ -3839,7 +3846,7 @@ int redisSupervisedSystemd(void) {
return 0;
}
serverLog(LL_NOTICE, "supervised by systemd, will signal readyness");
serverLog(LL_NOTICE, "supervised by systemd, will signal readiness");
if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
serverLog(LL_WARNING,
"Can't connect to systemd socket %s", notify_socket);
+8 -4
View File
@@ -61,7 +61,7 @@ typedef long long mstime_t; /* millisecond time type. */
#include "version.h" /* Version macro */
#include "util.h" /* Misc functions useful in many places */
#include "latency.h" /* Latency monitor API */
#include "sparkline.h" /* ASII graphs API */
#include "sparkline.h" /* ASCII graphs API */
#include "quicklist.h"
/* Following includes allow test functions to be called from Redis main() */
@@ -111,6 +111,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_DEFAULT_DAEMONIZE 0
#define CONFIG_DEFAULT_UNIX_SOCKET_PERM 0
#define CONFIG_DEFAULT_TCP_KEEPALIVE 0
#define CONFIG_DEFAULT_PROTECTED_MODE 1
#define CONFIG_DEFAULT_LOGFILE ""
#define CONFIG_DEFAULT_SYSLOG_ENABLED 0
#define CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR 1
@@ -478,7 +479,7 @@ typedef struct redisObject {
_var.type = OBJ_STRING; \
_var.encoding = OBJ_ENCODING_RAW; \
_var.ptr = _ptr; \
} while(0);
} while(0)
/* To improve the quality of the LRU approximation we take a set of keys
* that are good candidate for eviction across freeMemoryIfNeeded() calls.
@@ -737,6 +738,7 @@ struct redisServer {
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
dict *migrate_cached_sockets;/* MIGRATE cached sockets */
uint64_t next_client_id; /* Next client unique ID. Incremental. */
int protected_mode; /* Don't accept external connections. */
/* RDB / AOF loading information */
int loading; /* We are loading data from disk if true */
off_t loading_total_bytes;
@@ -1102,13 +1104,13 @@ void addReplyBulkSds(client *c, sds s);
void addReplyError(client *c, const char *err);
void addReplyStatus(client *c, const char *status);
void addReplyDouble(client *c, double d);
void addReplyHumanLongDouble(client *c, long double d);
void addReplyLongLong(client *c, long long ll);
void addReplyMultiBulkLen(client *c, long length);
void copyClientOutputBuffer(client *dst, client *src);
void *dupClientReplyValue(void *o);
void getClientsMaxBuffers(unsigned long *longest_output_list,
unsigned long *biggest_input_buffer);
void formatPeerId(char *peerid, size_t peerid_len, char *ip, int port);
char *getClientPeerId(client *client);
sds catClientInfoString(sds s, client *client);
sds getAllClientsInfoString(void);
@@ -1288,6 +1290,7 @@ void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
unsigned int zsetLength(robj *zobj);
void zsetConvert(robj *zobj, int encoding);
void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen);
int zsetScore(robj *zobj, robj *member, double *score);
unsigned long zslGetRank(zskiplist *zsl, double score, robj *o);
@@ -1325,7 +1328,7 @@ void closeListeningSockets(int unlink_unix_socket);
void updateCachedTime(void);
void resetServerStats(void);
unsigned int getLRUClock(void);
const char *maxmemoryToString(void);
const char *evictPolicyToString(void);
#define RESTART_SERVER_NONE 0
#define RESTART_SERVER_GRACEFULLY (1<<0) /* Do proper shutdown. */
@@ -1476,6 +1479,7 @@ void delCommand(client *c);
void existsCommand(client *c);
void setbitCommand(client *c);
void getbitCommand(client *c);
void bitfieldCommand(client *c);
void setrangeCommand(client *c);
void getrangeCommand(client *c);
void incrCommand(client *c);
+6 -1
View File
@@ -509,12 +509,17 @@ void lremCommand(client *c) {
}
listTypeReleaseIterator(li);
if (removed) {
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"lrem",c->argv[1],c->db->id);
}
if (listTypeLength(subject) == 0) {
dbDelete(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
}
addReplyLongLong(c,removed);
if (removed) signalModifiedKey(c->db,c->argv[1]);
}
/* This is the semantic of this command:
+13 -5
View File
@@ -1166,6 +1166,18 @@ void zsetConvert(robj *zobj, int encoding) {
}
}
/* Convert the sorted set object into a ziplist if it is not already a ziplist
* and if the number of elements and the maximum element size is within the
* expected ranges. */
void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen) {
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) return;
zset *zset = zobj->ptr;
if (zset->zsl->length <= server.zset_max_ziplist_entries &&
maxelelen <= server.zset_max_ziplist_value)
zsetConvert(zobj,OBJ_ENCODING_ZIPLIST);
}
/* Return (by reference) the score of the specified member of the sorted set
* storing it into *score. If the element does not exist C_ERR is returned
* otherwise C_OK is returned and *score is correctly populated.
@@ -2142,11 +2154,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
server.dirty++;
}
if (dstzset->zsl->length) {
/* Convert to ziplist when in limits. */
if (dstzset->zsl->length <= server.zset_max_ziplist_entries &&
maxelelen <= server.zset_max_ziplist_value)
zsetConvert(dstobj,OBJ_ENCODING_ZIPLIST);
zsetConvertToZiplistIfNeeded(dstobj,maxelelen);
dbAdd(c->db,dstkey,dstobj);
addReplyLongLong(c,zsetLength(dstobj));
if (!touched) signalModifiedKey(c->db,dstkey);
+1 -1
View File
@@ -1 +1 @@
#define REDIS_VERSION "3.1.101"
#define REDIS_VERSION "3.2.0"
@@ -0,0 +1,64 @@
# Replica migration test #2.
#
# Check that the status of master that can be targeted by replica migration
# is acquired again, after being getting slots again, in a cluster where the
# other masters have slaves.
source "../tests/includes/init-tests.tcl"
# Create a cluster with 5 master and 15 slaves, to make sure there are no
# empty masters and make rebalancing simpler to handle during the test.
test "Create a 5 nodes cluster" {
create_cluster 5 15
}
test "Cluster is up" {
assert_cluster_state ok
}
test "Each master should have at least two replicas attached" {
foreach_redis_id id {
if {$id < 5} {
wait_for_condition 1000 50 {
[llength [lindex [R 0 role] 2]] >= 2
} else {
fail "Master #$id does not have 2 slaves as expected"
}
}
}
}
set master0_id [dict get [get_myself 0] id]
test "Resharding all the master #0 slots away from it" {
set output [exec \
../../../src/redis-trib.rb rebalance \
--weight ${master0_id}=0 \
127.0.0.1:[get_instance_attrib redis 0 port] >@ stdout]
}
test "Master #0 should lose its replicas" {
wait_for_condition 1000 50 {
[llength [lindex [R 0 role] 2]] == 0
} else {
fail "Master #0 still has replicas"
}
}
test "Resharding back some slot to master #0" {
# Wait for the cluster config to propagate before attempting a
# new resharding.
after 10000
set output [exec \
../../../src/redis-trib.rb rebalance \
--weight ${master0_id}=.01 \
--use-empty-masters \
127.0.0.1:[get_instance_attrib redis 0 port] >@ stdout]
}
test "Master #0 should re-acquire one or more replicas" {
wait_for_condition 1000 50 {
[llength [lindex [R 0 role] 2]] >= 1
} else {
fail "Master #0 has no has replicas"
}
}
+63
View File
@@ -99,8 +99,25 @@ proc spawn_instance {type base_port count {conf {}}} {
}
}
proc log_crashes {} {
set start_pattern {*REDIS BUG REPORT START*}
set logs [glob */log.txt]
foreach log $logs {
set fd [open $log]
set found 0
while {[gets $fd line] >= 0} {
if {[string match $start_pattern $line]} {
puts "\n*** Crash report found in $log ***"
set found 1
}
if {$found} {puts $line}
}
}
}
proc cleanup {} {
puts "Cleaning up..."
log_crashes
foreach pid $::pids {
catch {exec kill -9 $pid}
}
@@ -110,6 +127,7 @@ proc cleanup {} {
}
proc abort_sentinel_test msg {
incr ::failed
puts "WARNING: Aborting the test."
puts ">>>>>>>> $msg"
if {$::pause_on_error} pause_on_error
@@ -136,6 +154,7 @@ proc parse_options {} {
puts "--single <pattern> Only runs tests specified by pattern."
puts "--pause-on-error Pause for manual inspection on error."
puts "--fail Simulate a test failure."
puts "--valgrind Run with valgrind."
puts "--help Shows this help."
exit 0
} else {
@@ -248,6 +267,37 @@ proc test {descr code} {
}
}
# Check memory leaks when running on OSX using the "leaks" utility.
proc check_leaks instance_types {
if {[string match {*Darwin*} [exec uname -a]]} {
puts -nonewline "Testing for memory leaks..."; flush stdout
foreach type $instance_types {
foreach_instance_id [set ::${type}_instances] id {
if {[instance_is_killed $type $id]} continue
set pid [get_instance_attrib $type $id pid]
set output {0 leaks}
catch {exec leaks $pid} output
if {[string match {*process does not exist*} $output] ||
[string match {*cannot examine*} $output]} {
# In a few tests we kill the server process.
set output "0 leaks"
} else {
puts -nonewline "$type/$pid "
flush stdout
}
if {![string match {*0 leaks*} $output]} {
puts [colorstr red "=== MEMORY LEAK DETECTED ==="]
puts "Instance type $type, ID $id:"
puts $output
puts "==="
incr ::failed
}
}
}
puts ""
}
}
# Execute all the units inside the 'tests' directory.
proc run_tests {} {
set tests [lsort [glob ../tests/*]]
@@ -258,6 +308,7 @@ proc run_tests {} {
if {[file isdirectory $test]} continue
puts [colorstr yellow "Testing unit: [lindex [file split $test] end]"]
source $test
check_leaks {redis sentinel}
}
}
@@ -444,5 +495,17 @@ proc restart_instance {type id} {
set link [redis 127.0.0.1 $port]
$link reconnect 1
set_instance_attrib $type $id link $link
# Make sure the instance is not loading the dataset when this
# function returns.
while 1 {
catch {[$link ping]} retval
if {[string match {*LOADING*} $retval]} {
after 100
continue
} else {
break
}
}
}
-6
View File
@@ -37,13 +37,7 @@ proc assert_error {pattern code} {
}
proc assert_encoding {enc key} {
# Swapped out values don't have an encoding, so make sure that
# the value is swapped in before checking the encoding.
set dbg [r debug object $key]
while {[string match "* swapped at:*" $dbg]} {
r debug swapin $key
set dbg [r debug object $key]
}
assert_match "* encoding:$enc *" $dbg
}
+1
View File
@@ -49,6 +49,7 @@ set ::all_tests {
unit/limits
unit/obuf-limits
unit/bitops
unit/bitfield
unit/memefficiency
unit/hyperloglog
}
+187
View File
@@ -0,0 +1,187 @@
start_server {tags {"bitops"}} {
test {BITFIELD signed SET and GET basics} {
r del bits
set results {}
lappend results [r bitfield bits set i8 0 -100]
lappend results [r bitfield bits set i8 0 101]
lappend results [r bitfield bits get i8 0]
set results
} {0 -100 101}
test {BITFIELD unsigned SET and GET basics} {
r del bits
set results {}
lappend results [r bitfield bits set u8 0 255]
lappend results [r bitfield bits set u8 0 100]
lappend results [r bitfield bits get u8 0]
set results
} {0 255 100}
test {BITFIELD #<idx> form} {
r del bits
set results {}
r bitfield bits set u8 #0 65
r bitfield bits set u8 #1 66
r bitfield bits set u8 #2 67
r get bits
} {ABC}
test {BITFIELD basic INCRBY form} {
r del bits
set results {}
r bitfield bits set u8 #0 10
lappend results [r bitfield bits incrby u8 #0 100]
lappend results [r bitfield bits incrby u8 #0 100]
set results
} {110 210}
test {BITFIELD chaining of multiple commands} {
r del bits
set results {}
r bitfield bits set u8 #0 10
lappend results [r bitfield bits incrby u8 #0 100 incrby u8 #0 100]
set results
} {{110 210}}
test {BITFIELD unsigned overflow wrap} {
r del bits
set results {}
r bitfield bits set u8 #0 100
lappend results [r bitfield bits overflow wrap incrby u8 #0 257]
lappend results [r bitfield bits get u8 #0]
lappend results [r bitfield bits overflow wrap incrby u8 #0 255]
lappend results [r bitfield bits get u8 #0]
} {101 101 100 100}
test {BITFIELD unsigned overflow sat} {
r del bits
set results {}
r bitfield bits set u8 #0 100
lappend results [r bitfield bits overflow sat incrby u8 #0 257]
lappend results [r bitfield bits get u8 #0]
lappend results [r bitfield bits overflow sat incrby u8 #0 -255]
lappend results [r bitfield bits get u8 #0]
} {255 255 0 0}
test {BITFIELD signed overflow wrap} {
r del bits
set results {}
r bitfield bits set i8 #0 100
lappend results [r bitfield bits overflow wrap incrby i8 #0 257]
lappend results [r bitfield bits get i8 #0]
lappend results [r bitfield bits overflow wrap incrby i8 #0 255]
lappend results [r bitfield bits get i8 #0]
} {101 101 100 100}
test {BITFIELD signed overflow sat} {
r del bits
set results {}
r bitfield bits set u8 #0 100
lappend results [r bitfield bits overflow sat incrby i8 #0 257]
lappend results [r bitfield bits get i8 #0]
lappend results [r bitfield bits overflow sat incrby i8 #0 -255]
lappend results [r bitfield bits get i8 #0]
} {127 127 -128 -128}
test {BITFIELD overflow detection fuzzing} {
for {set j 0} {$j < 1000} {incr j} {
set bits [expr {[randomInt 64]+1}]
set sign [randomInt 2]
set range [expr {2**$bits}]
if {$bits == 64} {set sign 1} ; # u64 is not supported by BITFIELD.
if {$sign} {
set min [expr {-($range/2)}]
set type "i$bits"
} else {
set min 0
set type "u$bits"
}
set max [expr {$min+$range-1}]
# Compare Tcl vs Redis
set range2 [expr {$range*2}]
set value [expr {($min*2)+[randomInt $range2]}]
set increment [expr {($min*2)+[randomInt $range2]}]
if {$value > 9223372036854775807} {
set value 9223372036854775807
}
if {$value < -9223372036854775808} {
set value -9223372036854775808
}
if {$increment > 9223372036854775807} {
set increment 9223372036854775807
}
if {$increment < -9223372036854775808} {
set increment -9223372036854775808
}
set overflow 0
if {$value > $max || $value < $min} {set overflow 1}
if {($value + $increment) > $max} {set overflow 1}
if {($value + $increment) < $min} {set overflow 1}
r del bits
set res1 [r bitfield bits overflow fail set $type 0 $value]
set res2 [r bitfield bits overflow fail incrby $type 0 $increment]
if {$overflow && [lindex $res1 0] ne {} &&
[lindex $res2 0] ne {}} {
fail "OW not detected where needed: $type $value+$increment"
}
if {!$overflow && ([lindex $res1 0] eq {} ||
[lindex $res2 0] eq {})} {
fail "OW detected where NOT needed: $type $value+$increment"
}
}
}
test {BITFIELD overflow wrap fuzzing} {
for {set j 0} {$j < 1000} {incr j} {
set bits [expr {[randomInt 64]+1}]
set sign [randomInt 2]
set range [expr {2**$bits}]
if {$bits == 64} {set sign 1} ; # u64 is not supported by BITFIELD.
if {$sign} {
set min [expr {-($range/2)}]
set type "i$bits"
} else {
set min 0
set type "u$bits"
}
set max [expr {$min+$range-1}]
# Compare Tcl vs Redis
set range2 [expr {$range*2}]
set value [expr {($min*2)+[randomInt $range2]}]
set increment [expr {($min*2)+[randomInt $range2]}]
if {$value > 9223372036854775807} {
set value 9223372036854775807
}
if {$value < -9223372036854775808} {
set value -9223372036854775808
}
if {$increment > 9223372036854775807} {
set increment 9223372036854775807
}
if {$increment < -9223372036854775808} {
set increment -9223372036854775808
}
r del bits
r bitfield bits overflow wrap set $type 0 $value
r bitfield bits overflow wrap incrby $type 0 $increment
set res [lindex [r bitfield bits get $type 0] 0]
set expected 0
if {$sign} {incr expected [expr {$max+1}]}
incr expected $value
incr expected $increment
set expected [expr {$expected % $range}]
if {$sign} {incr expected $min}
if {$res != $expected} {
fail "WRAP error: $type $value+$increment = $res, should be $expected"
}
}
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
# Compare Redis commadns against Tcl implementations of the same commands.
# Compare Redis commands against Tcl implementations of the same commands.
proc count_bits s {
binary scan $s b* bits
string length [regsub -all {0} $bits {}]
@@ -88,7 +88,7 @@ start_server {tags {"bitops"}} {
} {ERR*syntax*}
test {BITCOUNT regression test for github issue #582} {
r del str
r del foo
r setbit foo 0 1
if {[catch {r bitcount foo 0 4294967296} e]} {
assert_match {*ERR*out of range*} $e
+54
View File
@@ -128,6 +128,60 @@ start_server {tags {"geo"}} {
assert {$m eq {}}
}
test {GEORADIUS STORE option: syntax error} {
r del points
r geoadd points 13.361389 38.115556 "Palermo" \
15.087269 37.502669 "Catania"
catch {r georadius points 13.361389 38.115556 50 km store} e
set e
} {*ERR*syntax*}
test {GEORANGE STORE option: incompatible options} {
r del points
r geoadd points 13.361389 38.115556 "Palermo" \
15.087269 37.502669 "Catania"
catch {r georadius points 13.361389 38.115556 50 km store points2 withdist} e
assert_match {*ERR*} $e
catch {r georadius points 13.361389 38.115556 50 km store points2 withhash} e
assert_match {*ERR*} $e
catch {r georadius points 13.361389 38.115556 50 km store points2 withcoords} e
assert_match {*ERR*} $e
}
test {GEORANGE STORE option: plain usage} {
r del points
r geoadd points 13.361389 38.115556 "Palermo" \
15.087269 37.502669 "Catania"
r georadius points 13.361389 38.115556 500 km store points2
assert_equal [r zrange points 0 -1] [r zrange points2 0 -1]
}
test {GEORANGE STOREDIST option: plain usage} {
r del points
r geoadd points 13.361389 38.115556 "Palermo" \
15.087269 37.502669 "Catania"
r georadius points 13.361389 38.115556 500 km storedist points2
set res [r zrange points2 0 -1 withscores]
assert {[lindex $res 1] < 1}
assert {[lindex $res 3] > 166}
assert {[lindex $res 3] < 167}
}
test {GEORANGE STOREDIST option: COUNT ASC and DESC} {
r del points
r geoadd points 13.361389 38.115556 "Palermo" \
15.087269 37.502669 "Catania"
r georadius points 13.361389 38.115556 500 km storedist points2 asc count 1
assert {[r zcard points2] == 1}
set res [r zrange points2 0 -1 withscores]
assert {[lindex $res 0] eq "Palermo"}
r georadius points 13.361389 38.115556 500 km storedist points2 desc count 1
assert {[r zcard points2] == 1}
set res [r zrange points2 0 -1 withscores]
assert {[lindex $res 0] eq "Catania"}
}
test {GEOADD + GEORANGE randomized test} {
set attempt 10
while {[incr attempt -1]} {
+1
View File
@@ -194,6 +194,7 @@ start_server {tags {"other"}} {
}
test {APPEND basics} {
r del foo
list [r append foo bar] [r get foo] \
[r append foo 100] [r get foo]
} {3 bar 6 bar100}
+18 -13
View File
@@ -62,18 +62,19 @@ start_server {tags {"scripting"}} {
} {NOSCRIPT*}
test {EVAL - Redis integer -> Lua type conversion} {
r set x 0
r eval {
local foo = redis.pcall('incr','x')
local foo = redis.pcall('incr',KEYS[1])
return {type(foo),foo}
} 0
} 1 x
} {number 1}
test {EVAL - Redis bulk -> Lua type conversion} {
r set mykey myval
r eval {
local foo = redis.pcall('get','mykey')
local foo = redis.pcall('get',KEYS[1])
return {type(foo),foo}
} 0
} 1 mykey
} {string myval}
test {EVAL - Redis multi bulk -> Lua type conversion} {
@@ -82,39 +83,39 @@ start_server {tags {"scripting"}} {
r rpush mylist b
r rpush mylist c
r eval {
local foo = redis.pcall('lrange','mylist',0,-1)
local foo = redis.pcall('lrange',KEYS[1],0,-1)
return {type(foo),foo[1],foo[2],foo[3],# foo}
} 0
} 1 mylist
} {table a b c 3}
test {EVAL - Redis status reply -> Lua type conversion} {
r eval {
local foo = redis.pcall('set','mykey','myval')
local foo = redis.pcall('set',KEYS[1],'myval')
return {type(foo),foo['ok']}
} 0
} 1 mykey
} {table OK}
test {EVAL - Redis error reply -> Lua type conversion} {
r set mykey myval
r eval {
local foo = redis.pcall('incr','mykey')
local foo = redis.pcall('incr',KEYS[1])
return {type(foo),foo['err']}
} 0
} 1 mykey
} {table {ERR value is not an integer or out of range}}
test {EVAL - Redis nil bulk reply -> Lua type conversion} {
r del mykey
r eval {
local foo = redis.pcall('get','mykey')
local foo = redis.pcall('get',KEYS[1])
return {type(foo),foo == false}
} 0
} 1 mykey
} {boolean 1}
test {EVAL - Is the Lua client using the currently selected DB?} {
r set mykey "this is DB 9"
r select 10
r set mykey "this is DB 10"
r eval {return redis.pcall('get','mykey')} 0
r eval {return redis.pcall('get',KEYS[1])} 1 mykey
} {this is DB 10}
test {EVAL - SELECT inside Lua should not affect the caller} {
@@ -272,6 +273,10 @@ start_server {tags {"scripting"}} {
} 0
} {}
test {EVAL - Able to parse trailing comments} {
r eval {return 'hello' --trailing comment} 0
} {hello}
test {SCRIPTING FLUSH - is able to clear the scripts cache?} {
r set mykey myval
set v [r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey]
+6
View File
@@ -290,6 +290,12 @@ start_server {tags {"zset"}} {
assert_equal 6 [r zscore zset bar]
}
test "ZINCRBY return value" {
r del ztmp
set retval [r zincrby ztmp 1.0 x]
assert {$retval == 1.0}
}
proc create_default_zset {} {
create_zset zset {-inf a 1 b 2 c 3 d 4 e 5 f +inf g}
}