Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d96497961c | ||
|
|
4f38d03254 | ||
|
|
59ff2fe9a4 | ||
|
|
fb0d08e35f | ||
|
|
395e6a5872 | ||
|
|
34a57a5b52 | ||
|
|
967ae8ca67 | ||
|
|
c735116c87 | ||
|
|
8b41e19e47 | ||
|
|
cf7b2c329b | ||
|
|
bac29c8b7c | ||
|
|
4848af1b9e | ||
|
|
172eac504a | ||
|
|
5bf9b53a83 | ||
|
|
40861516ac | ||
|
|
92a7b0102c | ||
|
|
9f2e6ac0ed | ||
|
|
63978d338d | ||
|
|
ed2d988192 | ||
|
|
7da5980848 | ||
|
|
062f60a36e | ||
|
|
4243483d3e | ||
|
|
d785413d86 | ||
|
|
e8a1a169dd | ||
|
|
dc0f13774b | ||
|
|
47911b8630 | ||
|
|
7ff729e192 | ||
|
|
02b9a72bae | ||
|
|
1862239136 | ||
|
|
f8791a158b | ||
|
|
26319d08b5 | ||
|
|
fc78d978de | ||
|
|
6cb7860658 | ||
|
|
611dcb56ee | ||
|
|
889b017403 | ||
|
|
d1369c3d9f | ||
|
|
6818905406 | ||
|
|
ef1cf15c14 | ||
|
|
dee8d84270 | ||
|
|
5576a28977 | ||
|
|
4f8b18f3dd | ||
|
|
c64255990e | ||
|
|
ba3c5494d8 | ||
|
|
684b61505a | ||
|
|
189e865c51 | ||
|
|
b9f8c2a5b0 | ||
|
|
18d16f8592 | ||
|
|
3a00520e5f | ||
|
|
f93d9929d8 | ||
|
|
6589821035 | ||
|
|
48f4f77189 | ||
|
|
b3281e93c3 | ||
|
|
2677c97d39 | ||
|
|
1d426bf53b | ||
|
|
da2dd8991b | ||
|
|
13f84841b5 | ||
|
|
ef3a95fa6f | ||
|
|
665b819eb4 | ||
|
|
bb562a6414 | ||
|
|
e166abad10 | ||
|
|
aec5ea5d07 | ||
|
|
2d24bf2f94 | ||
|
|
e7a61d287e | ||
|
|
78bae8b078 | ||
|
|
bf6c5d960d | ||
|
|
95fc9cc0a6 | ||
|
|
5de5efa364 | ||
|
|
1f2d5941a1 | ||
|
|
60aa3a67e8 | ||
|
|
921aec17b6 | ||
|
|
e174290307 | ||
|
|
a3273dbfa6 | ||
|
|
e9318d92db | ||
|
|
803c5b140d | ||
|
|
74373cd930 | ||
|
|
b5047d9b2f | ||
|
|
34b420dbf3 | ||
|
|
02ac84ddbd | ||
|
|
45bfab8596 | ||
|
|
948fdb5f7d | ||
|
|
fe97d7b1db | ||
|
|
e88517ac30 | ||
|
|
2d89c53d0d | ||
|
|
0fe052efaa | ||
|
|
e1e8b1cd29 | ||
|
|
609a3ec1be | ||
|
|
0b50d1ce9a | ||
|
|
c7b9a57fb2 | ||
|
|
63ad0e8867 | ||
|
|
d5d0b467db | ||
|
|
cf0191dc3c | ||
|
|
45b1b2f7ad | ||
|
|
eb07793b59 | ||
|
|
22ccc6db0f | ||
|
|
f9a8b0eced | ||
|
|
6da090e916 | ||
|
|
8c0b9f778e | ||
|
|
63dc34bd01 | ||
|
|
9ddd0f7796 | ||
|
|
c17a7f6fbc | ||
|
|
bfad381f57 | ||
|
|
742e580f40 | ||
|
|
1b0f244509 | ||
|
|
580f65a50b | ||
|
|
a154908a8f | ||
|
|
da540228d8 | ||
|
|
9e5b70162c | ||
|
|
dbde1d85ea | ||
|
|
560e049947 | ||
|
|
c2b43eac4f | ||
|
|
5e2aa2cafb | ||
|
|
a732d86fe0 | ||
|
|
0f4dbd9a11 | ||
|
|
21159d53f5 | ||
|
|
52f6a1dc16 | ||
|
|
f9c1263f31 | ||
|
|
1b2ac3b206 | ||
|
|
c5f23ca79b | ||
|
|
27abaa238f | ||
|
|
78b175a241 | ||
|
|
14cc0045dc | ||
|
|
023cbc3787 | ||
|
|
b70b9c6027 | ||
|
|
46dc3fbb53 | ||
|
|
c1c3e9b756 | ||
|
|
1f8ad7ae55 | ||
|
|
4468ba2317 | ||
|
|
d8a0fddd51 | ||
|
|
886c9ecb7c | ||
|
|
8e0a9f6027 | ||
|
|
d64a9cf514 | ||
|
|
d9301f05e2 | ||
|
|
6c9897f6cf | ||
|
|
984f6edf0a | ||
|
|
a2b3fff235 | ||
|
|
54b08c86a6 | ||
|
|
6bdcd50b56 | ||
|
|
cac49a9031 | ||
|
|
ed70955510 | ||
|
|
b41f4e5a48 | ||
|
|
21b26915ce | ||
|
|
3ccfb5a4d6 | ||
|
|
9120275dc9 | ||
|
|
de00a5a092 | ||
|
|
2b45ef020c | ||
|
|
52bd3d8a39 | ||
|
|
37d978d9b4 | ||
|
|
41f0f927c9 | ||
|
|
5ab4151d7f | ||
|
|
e95ca66320 | ||
|
|
681c191006 | ||
|
|
d85a09dfc6 | ||
|
|
26a48504cb | ||
|
|
ae3aeca828 | ||
|
|
5a8234d2c9 | ||
|
|
2861cd84f9 | ||
|
|
8e30092f06 | ||
|
|
d288ee655f | ||
|
|
89514f80e9 | ||
|
|
3f006679b1 | ||
|
|
36f026a3a0 | ||
|
|
cc017c5839 | ||
|
|
b6ffa85f11 | ||
|
|
4917a6a8b3 | ||
|
|
ed44a74e72 | ||
|
|
47ff443b53 | ||
|
|
4d9bd53530 | ||
|
|
5bf0997ff8 | ||
|
|
ec0f483de2 | ||
|
|
0dd6241314 | ||
|
|
3b1a41b172 | ||
|
|
9b3b110004 | ||
|
|
b25b0dc5f4 | ||
|
|
2b5fc529a4 | ||
|
|
be407c015c | ||
|
|
1eb9145089 |
+346
-103
@@ -1,9 +1,9 @@
|
||||
Redis 2.8 release notes
|
||||
Redis 2.6 release notes
|
||||
=======================
|
||||
|
||||
** IMPORTANT ** Check the 'Migrating from 2.6 to 2.8' section at the end of
|
||||
this file for information about what changed between 2.6 and
|
||||
2.8 and how this may affect your application.
|
||||
** IMPORTANT ** Check the 'Migrating from 2.4 to 2.6' section at the end of
|
||||
this file for information about what changed between 2.4 and
|
||||
2.6 and how this may affect your application.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Upgrade urgency levels:
|
||||
@@ -14,143 +14,386 @@ HIGH: There is a critical bug that may affect a subset of users. Upgrade!
|
||||
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--[ Redis 2.8.0 ] Release date: 22 Nov 2013
|
||||
--[ Redis 2.6.13 ]
|
||||
|
||||
# UPGRADE URGENCY: LOW, unless you want to upgrade to new Sentinel code.
|
||||
UPGRADE URGENCY: MODERATE, nothing very critical but upgrading is suggested
|
||||
if you experienced:
|
||||
|
||||
* [FIX] Fixed an error in rdbWriteRaw() that should have no practical impact.
|
||||
* [NEW] Log the new master when SLAVEOF command is used.
|
||||
* [NEW] Sentinel code synchronized with the unstable branch, the new Sentinel
|
||||
is a reimplementation that uses more reliable algorithms.
|
||||
1) Strange issues with Lua scripting.
|
||||
2) Not reconfigured reappearing master using Sentinel.
|
||||
3) Server continusly trying to save on save error.
|
||||
|
||||
--[ Redis 2.8 Release Candidate 6 (2.7.106) ] Release date: 6 Nov 2013
|
||||
This version of Redis may also help with AOF and slow / busy
|
||||
disks and latency issues.
|
||||
|
||||
This is the 6th release candidate of Redis 2.8 (official version is 2.7.106).
|
||||
* [FIX] Throttle BGSAVE attempt on saving error.
|
||||
* [FIX] redis-cli: raise error on bad command line switch.
|
||||
* [FIX] Redis/Jemalloc Gitignore were too aggressive.
|
||||
* [FIX] Test: fix RDB test checking file permissions.
|
||||
* [FIX] Sentinel: always redirect on master->slave transition.
|
||||
* [FIX] Lua updated to version 5.1.5. Fixes rare scripting issues.
|
||||
* [NEW] AOF: improved latency figures with slow/busy disks.
|
||||
* [NEW] Sentinel: turn old master into a slave when it comes back.
|
||||
* [NEW] More explicit panic message on out of memory.
|
||||
* [NEW] redis-cli: --latency-history mode implemented.
|
||||
|
||||
# UPGRADE URGENCY: LOW, only new features back ported, no fixes.
|
||||
--[ Redis 2.6.12 ]
|
||||
|
||||
* [NEW] SCAN, SSCAN, HSCAN, ZSCAN commands.
|
||||
UPGRADE URGENCY: MODERATE, nothing very critical but a few non trivial bugs.
|
||||
|
||||
--[ Redis 2.8 Release Candidate 5 (2.7.105) ] Release date: 9 Oct 2013
|
||||
* [BUGFIX] redis-cli --bigkeys: don't crash with empty DB.
|
||||
* [BUGFIX] stop-writes-on-bgsave-error now works in redis.conf
|
||||
* [BUGFIX] Don't crash at startup if RDB is there but can't be opened.
|
||||
* [BUGFIX] Initial value for master_link_down_since_seconds is now huge.
|
||||
* [BUGFIX] Allow SELECT while loading the DB.
|
||||
* [BUGFIX] Don't replicate/AOF an empty MULTI/EXEC if the transaction
|
||||
is empty or containing just read-only commands.
|
||||
* [BUGFIX] EXPIRE should not be able to resurrect keys (see issue #1026).
|
||||
* [IMPROVED] Extended SET back ported from Redis 2.8 / unstable
|
||||
See http://redis.io/commands/set for more information.
|
||||
* [IMPROVED] Test suite improved.
|
||||
|
||||
This is the 5th release candidate of Redis 2.8 (official version is 2.7.105).
|
||||
Important bugs fixed inside.
|
||||
--[ Redis 2.6.11 ]
|
||||
|
||||
# UPGRADE URGENCY: HIGH because of many non critical replication bugs fixed.
|
||||
UPGRADE URGENCY: LOW, however updating is encouraged if you have many instances
|
||||
per server and you want to lower the CPU / energy usage.
|
||||
|
||||
* [FIX] redis-cli: don't crash with --bigkeys when the key no longer exist.
|
||||
* [FIX] Allow AUTH / PING when disconnected from slave and serve-stale-data is no.
|
||||
* [FIX] PSYNC: safer handling of PSYNC requests with offsets in the future.
|
||||
* [FIX] Replication: Fix master timeout detection.
|
||||
* [FIX] Replication: Correctly install the write handler after successful PSYNC.
|
||||
* [BUGFIX] Replication: more strict error checking for master PING reply.
|
||||
* [BUGFIX] redis-cli: use keepalive socket option for improved reliability.
|
||||
* [BUGFIX] Allow AUTH while loading the DB in memory.
|
||||
* [BUGFIX] Don't segfault on unbalanced quotes while parsing config file.
|
||||
* [BUGFIX] Don't segfault if command gets propagated to AOF / replication
|
||||
link as another command name that was renamed in redis.conf
|
||||
* [IMPROVED] serverCron() frequency is now a runtime parameter (was REDIS_HZ).
|
||||
* [IMPROVED] Use a lot less CPU when idle, even with many configured DBs.
|
||||
|
||||
--[ Redis 2.8 Release Candidate 4 (2.7.104) ] Release date: 30 Aug 2013
|
||||
--[ Redis 2.6.10 ]
|
||||
|
||||
This is the fourth release candidate of Redis 2.8 (official version is 2.7.104).
|
||||
Important bugs fixed inside.
|
||||
UPGRADE URGENCY: MODERATE, this release contains many non-critical fixes
|
||||
and many small improvements.
|
||||
|
||||
# UPGRADE URGENCY: HIGH because of the EVAL memory leak.
|
||||
* [BUGFIX] redis-cli --rdb, fixed when the server sends newlines to ping.
|
||||
* [BUGFIX] redis-cli, minor fixes on connection handling, prompt.
|
||||
* [BUGFIX] Slow log: don't log EXEC, just executed commands.
|
||||
* [BUGFIX] On failed shutdown don't try again and again compulsively.
|
||||
* [BUGFIX] Fix build on sunos without backtrace().
|
||||
* [BUGFIX] UNSUBSCRIBE and PUNSUBSCRIBE: always provide a reply (see 742e580)
|
||||
* [BUGFIX] Lua struct library was broken, upgraded.
|
||||
* [BUGFIX] Fix a bug in srandmemberWithCountCommand() with count argument.
|
||||
* [BUGFIX] Test: disable clients timeout to prevent issues on slow systems.
|
||||
* [BUGFIX] Sentinel: don't advertise the promoted slave as master too early.
|
||||
* [IMPROVED] Whitelist SIGUSR1, see http://redis.io/topics/signals.
|
||||
* [IMPROVED] Simpler to understand redis-cli --bigkeys output.
|
||||
* [IMPROVED] Test now works with tclsh > 8.5.
|
||||
* [IMPROVED] Added option to turn of the Nagle algorithm in slave socket.
|
||||
* [IMPROVED] Optionally use SO_KEEPALIVE to detect dead peers.
|
||||
|
||||
* [FIX] Fixed a serious EVAL memory leak in the Lua stack.
|
||||
* [FIX] Fixed server startup when no IPv6 address exists in any interface.
|
||||
* [FIX] Send MISCONFIG error when BGSAVE fails because can't fork.
|
||||
* [FIX] Memory efficiency with large (> a few kbytes) values improved considerably.
|
||||
* [NEW] DEBUG SDSLEN for sds memory debugging.
|
||||
--[ Redis 2.6.9 ]
|
||||
|
||||
--[ Redis 2.8 Release Candidate 3 (2.7.103) ] Release date: 19 Aug 2013
|
||||
UPGRADE URGENCY: MODERATE if you use replication.
|
||||
|
||||
This is the third release candidate of Redis 2.8 (official version is 2.7.103).
|
||||
Important bugs fixed inside.
|
||||
* [BUGFIX] Changing master at runtime (SLAVEOF command) in presence of
|
||||
network problems, or in very rapid succession, could result
|
||||
in non-critical problems (GitHub Issue #828).
|
||||
* [IMPROVED] CLINGET GETNAME and SETNAME to set and query connection names
|
||||
reported by CLIENT LIST. Very useful for debugging of
|
||||
problems.
|
||||
* [IMPROVED] redis-cli is now able to transfer an RDB file from a remote
|
||||
server to a local file using the --rdb <filename> command
|
||||
line option.
|
||||
|
||||
# UPGRADE URGENCY: HIGH
|
||||
--[ Redis 2.6.8 ]
|
||||
|
||||
* [FIX] Improved expired keys collection algorithm. Even under heavy load keys
|
||||
to be expired can't accumulate because of lack of CPU time.
|
||||
* [FIX] Replication speed regression fixed (issue #1238).
|
||||
* [FIX] Fixed an hard to trigger PSYNC bug.
|
||||
* [FIX] Fixed Issue #1240, ZUNIONSTORE could lead to wrong result.
|
||||
* [NEW] Add per-db average TTL information in INFO output.
|
||||
* [NEW] redis-benchmark improvements.
|
||||
* [NEW] dict.c API wrong usage detection.
|
||||
UPGRADE URGENCY: MODERATE if you use Lua scripting. Otherwise LOW.
|
||||
|
||||
--[ Redis 2.8 Release Candidate 2 (2.7.102) ] Release date: 30 Jul 2013
|
||||
* [BUGFIX] Multiple fixes for EVAL (issue #872).
|
||||
* [BUGFIX] Fix overflow in mstime() in redis-cli and benchmark.
|
||||
* [BUGFIX] Fix Linux / PPC64 behavior by correcting endianess detection.
|
||||
* [BUGFIX] Fix NetBSD build by defining _XOPEN_SOURCE appropriately.
|
||||
* [BUGFIX] Added missing license and copyright in a few places.
|
||||
* [BUGFIX] Better error reporting when fd event creation fails.
|
||||
|
||||
This is the second release candidate of Redis 2.8 (official version is 2.7.102).
|
||||
Important bugs fixed inside.
|
||||
--[ Redis 2.6.7 ]
|
||||
|
||||
# UPGRADE URGENCY: HIGH
|
||||
UPGRADE URGENCY: MODERATE (unless you BLPOP using the same key multiple times).
|
||||
|
||||
* [FIX] Fixed a critical replication bug, see issue #1221.
|
||||
* [NEW] The new inline protocol now accepts quoted strings like, for example
|
||||
you can now type in a telnet session: set 'foo bar' "hello world\n".
|
||||
* [BUGFIX] Don't crash if BLPOP & co are called with the same key repeated
|
||||
multiple times (Issue #801).
|
||||
|
||||
--[ Redis 2.8 Release Candidate 1 (2.7.101) ] Release date: 18 Jul 2013
|
||||
--[ Redis 2.6.6 ]
|
||||
|
||||
This is the first release candidate of Redis 2.8 (official version is 2.7.101).
|
||||
UPGRADE URGENCY: CRITICAL if you experienced one more more crashes.
|
||||
MODERATE if Redis is running fine for you.
|
||||
|
||||
The following is a list of improvements in Redis 2.8, compared to Redis 2.6.
|
||||
* [BUGFIX] Jemalloc updated to 3.2.0.
|
||||
|
||||
* [NEW] Slaves are now able to partially resynchronize with the master, so most
|
||||
of the times a full resynchronization with the RDB creation in the master
|
||||
side is not needed when the master-slave link is disconnected for a short
|
||||
amount of time.
|
||||
* [NEW] Experimental IPv6 support.
|
||||
* [NEW] Slaves explicitly ping masters now, a master is able to detect a timed out
|
||||
slave independently.
|
||||
* [NEW] Masters can stop accepting writes if not enough slaves with a given
|
||||
maximum latency are connected.
|
||||
* [NEW] Keyspace changes notifications via Pub/Sub.
|
||||
* [NEW] CONFIG SET maxclients is now available.
|
||||
* [NEW] Ability to bind multiple IP addresses.
|
||||
* [NEW] Set process names so that you can recognize, in the "ps" command output,
|
||||
the listening port of an instance, or if it is a saving child.
|
||||
* [NEW] Automatic memory check on crash.
|
||||
* [NEW] CONFIG REWRITE is able to materialize the changes in the configuration
|
||||
operated using CONFIG SET into the redis.conf file.
|
||||
* [NEW] More NetBSD friendly code base.
|
||||
* [NEW] PUBSUB command for Pub/Sub introspection capabilities.
|
||||
* [NEW] EVALSHA can now be replicated as such, without requiring to be expanded
|
||||
to a full EVAL for the replication link.
|
||||
* [NEW] Better Lua scripts error reporting.
|
||||
* [NEW] SDIFF performance improved.
|
||||
* [FIX] A number of bugfixes.
|
||||
--[ Redis 2.6.5 ]
|
||||
|
||||
Migrating from 2.6 to 2.8
|
||||
UPGRADE URGENCY: MODERATE
|
||||
|
||||
Warning: this release of Redis introduces a different behavior in MULTI/EXEC
|
||||
handling of errors. This was done because the new behavior is safer
|
||||
compared to the old one, and should not break any code targeting
|
||||
Redis 2.6 in a critical way.
|
||||
|
||||
For more information check http://redis.io/topics/transactions
|
||||
|
||||
* [IMPROVED] RDB/AOF childern now log amount of additional memory used
|
||||
because of copy on write.
|
||||
* [BUGFIX] MIGRATE non critical fixes (see commits for details).
|
||||
* [BUGFIX] MULTI/EXEC: now EXEC aborts on errors before EXEC.
|
||||
* [BUGFIX] Fix integer overflow in zunionInterGenericCommand resulting
|
||||
into Z[INTER|UNION][STORE] commands to crash under extremely
|
||||
unlikely conditions (almost impossible in real world).
|
||||
* [BUGFIX] EVALSHA is now case insensitive (and will not crash).
|
||||
|
||||
--[ Redis 2.6.4 ]
|
||||
|
||||
UPGRADE URGENCY: LOW
|
||||
|
||||
* [IMPROVED] BSD license and copyright notice added to every .c and .h file.
|
||||
|
||||
--[ Redis 2.6.2 ]
|
||||
|
||||
UPGRADE URGENCY: LOW
|
||||
|
||||
* [BUGFIX] The compilation fix for RHLE5 in 2.6.1 was broken. Fixed.
|
||||
* [IMPROVED] Linenoise updated, now supports Ctrl+w.
|
||||
|
||||
---[ Redis 2.6.1 ]
|
||||
|
||||
UPGRADE URGENCY: LOW
|
||||
|
||||
* [BUGFIX] Compilation on Linux < 2.6.17 or glibc < 2.6 fixed (RHLE5 & co).
|
||||
|
||||
---[ Redis 2.6.0 ]
|
||||
|
||||
UPGRADE URGENCY: HIGH
|
||||
|
||||
* [BUGFIX] Allow AUTH when server is in -BUSY state because of a slow script.
|
||||
* [BUGFIX] MULTI/EXEC flow now makes sense when observed in MONITOR
|
||||
* [BUGFIX] SCRIPT KILL now uses different error prefixes for different errors.
|
||||
* [BUGFIX] Default memory limit for 32bit archs lowered from 3.5 to 3 GB.
|
||||
* [BUGFIX] redis-check-dump is now compatible with RDB files generated by 2.6.
|
||||
* [IMPROVED] New field in INFO: slave_read_only.
|
||||
|
||||
---[ Redis 2.5.14 (2.6 Release Candidate 8) ]
|
||||
|
||||
* [BUGFIX] Fixed compilation on FreeBSD.
|
||||
* [IMPROVED] SRANDMEMBER <count> that returns multiple random elements.
|
||||
* [IMPROVED] Sentinel backported to 2.6. It will be taken in sync with 2.8.
|
||||
* [IMPROVED] Helper function for scripting to return errors and status replies.
|
||||
* [IMPROVED] SORT by nosort [ASC|DESC] uses sorted set elements ordering.
|
||||
* [BUGFIX] Better resistence to system clock skew.
|
||||
* [IMPROVED] Warn the user when the configured maxmemory seems odd.
|
||||
* [BUGFIX] Hashing function is now murmurhash2 for security purposes.
|
||||
* [IMPROVED] Install script no longer uses a template but redis.conf itself.
|
||||
|
||||
---[ Redis 2.5.13 (2.6 Release Candidate 7) ]
|
||||
|
||||
UPGRADE URGENCY: HIGH
|
||||
|
||||
* [BUGFIX] Theoretical bug in ziplist fixed.
|
||||
* [BUGFIX] Better out of memory handling (Log produced in log file).
|
||||
* [BUGFIX] Incrementally flush RDB file on slave side while performing the
|
||||
first synchronization with the master. This makes Redis less
|
||||
blocking in environments where disk I/O is slow.
|
||||
* [BUGFIX] Don't crash with Lua's redis.call() without arguments.
|
||||
* [BUGFIX] Don't crash after a big number of Lua calls on 32 bit systems
|
||||
because of a failed assertion.
|
||||
* [BUGFIX] Fix SORT behaviour when called from scripting.
|
||||
* [BUGFIX] Adjust slave PING period accordingly to REDIS_HZ define.
|
||||
* [BUGFIX] BITCOUNT: fix crash on overflowing arguments.
|
||||
* [BUGFIX] Return an error when SELECT argument is not an integer.
|
||||
* [BUGFIX] Blocking operations on lists were completely reimplemented for
|
||||
correctness. Now blocking list ops and pushes originated from
|
||||
Lua scripts will play well together and will be replicated
|
||||
and transmitted to the AOF correctly.
|
||||
* [IMPROVED] Send async PING before starting replication to avoid blocking if
|
||||
master allows us to connect but it is actually not able to reply.
|
||||
* [IMPROVED] Support slave-priority for Redis Sentinel.
|
||||
* [IMPROVED] Hiredis library updated.
|
||||
|
||||
---[ Redis 2.5.12 (2.6 Release Candidate 6) ]
|
||||
|
||||
UPGRADE URGENCY: MODERATE.
|
||||
|
||||
* [BUGFIX] Fixed a timing attack on AUTH (Issue #560).
|
||||
* [BUGFIX] Don't assume that "char" is signed.
|
||||
* [BUGFIX] Check that we have connection before enabling pipe mode.
|
||||
* [BUGFIX] Use the optimized version of the function to convert a double to
|
||||
its string representation. Compilation was disabled because of
|
||||
a typo in the #if statement.
|
||||
* [IMPROVED} REPLCONF internal command introduced, now INFO shows slaves with
|
||||
correct port numbers. This makes 2.5.12 Redis Sentinel compatible.
|
||||
* [IMPROVED] Truncate short write from the AOF for a cleaner restart. On short
|
||||
writes (for instance out of space) Redis will now try to remove
|
||||
the half-written data so that the next restart will work without
|
||||
the need for the "redis-check-aof" utility.
|
||||
* [IMPROVED] New in INFO: aof_last_bgrewrite_status
|
||||
* [IMPROVED] Allow Pub/Sub in contexts where other commands are blocked.
|
||||
* [BUGFIX] mark fd as writable when EPOLLERR or EPOLLHUP is returned by
|
||||
epoll_wait.
|
||||
|
||||
---[ Redis 2.5.11 (2.6 Release Candidate 5) ]
|
||||
|
||||
UPGRADE URGENCY: HIGH.
|
||||
|
||||
* [BUGFIX] Fixed Hash corruption when loading an RDB file generated by
|
||||
previous versions of Redis that encoded hashes using
|
||||
a different ziplist encoding format for small integers.
|
||||
All the fileds that are integers in the range 0-255 may not
|
||||
be recognized, or duplicated un updates, causing a crash
|
||||
when the ziplist is converted to a real hash. (Issue #547).
|
||||
* [BUGFIX] Fixed the count of memory used by output buffers in the
|
||||
setDeferredMultiBulkLength() function.
|
||||
|
||||
---[ Redis 2.5.10 (2.6 Release Candidate 4) ]
|
||||
|
||||
UPGRADE URGENCY: HIGH.
|
||||
|
||||
* [BUGFIX] Allow PREFIX to be overwritten on "make install".
|
||||
* [BUGFIX] Run the test with just one client if the computer is slow.
|
||||
* [BUGFIX] Event port support in our event driven libray.
|
||||
* [BUGFIX] Jemalloc updated to 3.0.0. This fixes a possibly AOF rewrite issue.
|
||||
See https://github.com/antirez/redis/issues/504 for info.
|
||||
* [BUGFIX] Fixed issue #516: ZINTERSTORE / ZUNIONSTORE with mixed sets/zsets.
|
||||
* [BUGFIX] Set fd to writable when poll(2) detects POLLERR or POLLHUP event.
|
||||
* [BUGFIX] Fixed RESTORE hash failure (Issue #532).
|
||||
* [IMPROVED] Allow an AOF rewrite buffer > 2GB (Related to issue #504).
|
||||
* [IMPROVED] Server cron function frequency is now configurable (REDIS_HZ).
|
||||
* [IMPROVED] Better, less blocking expired keys collection algorithm.
|
||||
* [FEATURE] New commands: BITOP and BITCOUNT.
|
||||
* [FEATURE] redis-cli --pipe for mass import.
|
||||
|
||||
What's new in Redis 2.5.9 (aka 2.6 Release Candidate 3)
|
||||
=======================================================
|
||||
|
||||
UPGRADE URGENCY: critical, upgrade ASAP.
|
||||
|
||||
* [BUGFIX] Fix for issue #500 (https://github.com/antirez/redis/pull/500).
|
||||
Redis 2.6-RC1 and RC2 may corrupt ziplist-encoded sorted sets
|
||||
produced by Redis 2.4.x.
|
||||
* [BUGFIX] Fixed several bugs in init.d script.
|
||||
* [BUGFIX] syncio.c functions modified for speed and correctness. On osx
|
||||
(and possibly other BSD-based systems) the slave would block on
|
||||
replication to send the SYNC command when the master was not
|
||||
available. This is fixed now, but was not affecting Linux installs.
|
||||
* Now when slave-serve-stale-data is set to yes and the master is down, instead
|
||||
of reporting a generic error Redis replies with -MASTERDOWN.
|
||||
|
||||
What's new in Redis 2.5.8 (aka 2.6 Release Candidate 2)
|
||||
=======================================================
|
||||
|
||||
UPGRADE URGENCY: high for all the users of the KEYS command, otherwise low.
|
||||
|
||||
* [BUGFIX] Fix for KEYS command: if the DB contains keys with expires the KEYS
|
||||
command may return the wrong output, having duplicated or missing
|
||||
keys. See issue #487 and #488 on github for details.
|
||||
|
||||
What's new in Redis 2.5.7 (aka 2.6 Release Candidate 1)
|
||||
=======================================================
|
||||
|
||||
UPGRADE URGENCY: upgrade not recommended because this is an RC release.
|
||||
|
||||
* This is the first release candidate for Redis 2.6. We are not aware of
|
||||
bugs, but part of this code is young and was never tested in production
|
||||
environments, so handle with care.
|
||||
|
||||
An overview of new features and changes in Redis 2.6.x
|
||||
======================================================
|
||||
|
||||
* Server side Lua scripting, see http://redis.io/commands/eval
|
||||
* Virtual Memory removed (was deprecated in 2.4)
|
||||
* Hardcoded limits about max number of clients removed.
|
||||
* AOF low level semantics is generally more sane, and especially when used
|
||||
in slaves.
|
||||
* Milliseconds resolution expires, also added new commands with milliseconds
|
||||
precision (PEXPIRE, PTTL, ...).
|
||||
* Better memory usage for "small" lists, ziplists and hashes when fields or
|
||||
values contain small integers.
|
||||
* Read only slaves.
|
||||
* New bit opeations: BITCOUNT and BITOP commands.
|
||||
* Clients max output buffer soft and hard limits. You can specifiy different
|
||||
limits for different classes of clients (normal,pubsub,slave).
|
||||
* More incremental (less blocking) expired keys collection algorithm, in
|
||||
practical terms this means that Redis is more responsive when a very
|
||||
big number of keys expire about at the same time.
|
||||
* AOF is now able to rewrite aggregate data types using variadic commands,
|
||||
often producing an AOF that is faster to save, load, and is smaller in size.
|
||||
* Every redis.conf directive is now accepted as a command line option for the
|
||||
redis-server binary, with the same name and number of arguments.
|
||||
* Hash table seed randomization for protection against collisions attacks.
|
||||
* Performances improved when writing large objects to Redis.
|
||||
* Integrated memory test, see redis-server --test-memory.
|
||||
* INCRBYFLOAT and HINCRBYFLOAT commands.
|
||||
* New DUMP, RESTORE, MIGRATE commands (back ported from Redis Cluster to 2.6).
|
||||
* CRC64 checksump in RDB files.
|
||||
* Better MONITOR output and behavior (now commands are logged before execution).
|
||||
* "Software Watchdog" feature to debug latency issues.
|
||||
* Significant parts of the core refactored or rewritten. New internal APIs
|
||||
and core changes allowed to develop Redis Cluster on top of the new code,
|
||||
however for 2.6 all the cluster code was removed, and will be released with
|
||||
Redis 3.0 when it is more complete and stable.
|
||||
* Redis ASCII art logo added at startup.
|
||||
* Crash report on memory violation or failed asserts improved significantly
|
||||
to make debugging of hard to catch bugs simpler.
|
||||
* redis-benchmark improvements: ability to run selected tests,
|
||||
CSV output, faster, better help.
|
||||
* redis-cli improvements: --eval for comfortable development of Lua scripts.
|
||||
* SHUTDOWN now supports two optional arguments: "SAVE" and "NOSAVE".
|
||||
* INFO output split into sections, the command is now able to just show
|
||||
pecific sections.
|
||||
* New statistics about how many time a command was called, and how much
|
||||
execution time it used (INFO commandstats).
|
||||
* More predictable SORT behavior in edge cases.
|
||||
* Better support for big endian and *BSD systems.
|
||||
* Build system improved.
|
||||
|
||||
Migrating from 2.4 to 2.6
|
||||
=========================
|
||||
|
||||
Redis 2.6 is mostly a strict subset of 2.8. However there are a few things
|
||||
Redis 2.4 is mostly a strict subset of 2.6. However there are a few things
|
||||
that you should be aware of:
|
||||
|
||||
The following commands changed behavior:
|
||||
* You can't use .rdb and AOF files generated with 2.6 into a 2.4 instance.
|
||||
* 2.6 slaves can be attached to 2.4 masters, but not the contrary, and only
|
||||
for the time needed to perform the version upgrade.
|
||||
|
||||
* SORT with ALPHA now sorts according to local collation locale if no STORE
|
||||
option is used.
|
||||
* ZADD/ZINCRBY are now able to accept a bigger range of values as valid
|
||||
scores, that is, all the values you may end having as a result of
|
||||
calling ZINCRBY multiple times.
|
||||
* Many errors are now prefixed by a more specific error code instead of
|
||||
the generic -ERR, for example -WRONGTYPE, -NOAUTH, ...
|
||||
* PUBLISH called inside Lua scripts is now correctly propagated to slaves.
|
||||
There are also a few API differences, that are unlikely to cause problems,
|
||||
but it is better to keep them in mind:
|
||||
|
||||
* SORT now will refuse to sort in numerical mode elements that can't be parsed
|
||||
as numbers.
|
||||
* EXPIREs now all have millisecond resolution (but this is very unlikely to
|
||||
break code that was not conceived exploting the previous resolution error
|
||||
in some way.)
|
||||
* INFO output is a bit different now, and contains empty lines and comments
|
||||
starting with '#'. All the major clients should be already fixed to work
|
||||
with the new INFO format.
|
||||
* Slaves are only read-only by default (but you can change this easily
|
||||
setting the "slave-read-only" configuration option to "no" editing your
|
||||
redis.conf or using CONFIG SET.
|
||||
|
||||
The following INFO fields were renamed for consistency:
|
||||
|
||||
changes_since_last_save -> rdb_changes_since_last_save
|
||||
bgsave_in_progress -> rdb_bgsave_in_progress
|
||||
last_save_time -> rdb_last_save_time
|
||||
last_bgsave_status -> rdb_last_bgsave_status
|
||||
bgrewriteaof_in_progress -> aof_rewrite_in_progress
|
||||
bgrewriteaof_scheduled -> aof_rewrite_scheduled
|
||||
|
||||
The following redis.conf and CONFIG GET / SET parameters changed:
|
||||
|
||||
* logfile now uses the empty string in order to log to standard output,
|
||||
so 'logfile stdout' is now invalid, use 'logfile ""' instead.
|
||||
|
||||
The following INFO fields changed format in a non-backward compatible way:
|
||||
|
||||
* The list of slaves in INFO is now in field=value format.
|
||||
|
||||
Replication:
|
||||
|
||||
Redis 2.8 can be used as slave for Redis 2.6, but doing this is only
|
||||
a good idea for the short amount of time needed to upgrade your servers.
|
||||
We suggest to update both master and slaves at about the same time.
|
||||
* hash-max-zipmap-entries, now replaced by hash-max-ziplist-entries
|
||||
* hash-max-zipmap-value, now replaced by hash-max-ziplist-value
|
||||
* glueoutputbuf option was now completely removed (was deprecated)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Credits: Where not specified the implementation and design is done by
|
||||
Salvatore Sanfilippo. Thanks to VMware and Pivotal for making all
|
||||
Credits: Where not specified the implementation and design are done by
|
||||
Salvatore Sanfilippo and Pieter Noordhuis. Thanks to VMware for making all
|
||||
this possible. Also many thanks to all the other contributors and the amazing
|
||||
community we have.
|
||||
|
||||
|
||||
@@ -26,20 +26,9 @@ After building Redis is a good idea to test it, using:
|
||||
|
||||
% make test
|
||||
|
||||
Fixing problems building 32 bit binaries
|
||||
---------
|
||||
|
||||
If after building Redis with a 32 bit target you need to rebuild it
|
||||
with a 64 bit target, or the other way around, you need to perform a
|
||||
"make distclean" in the root directory of the Redis distribution.
|
||||
|
||||
In case of build errors when trying to build a 32 bit binary of Redis, try
|
||||
the following steps:
|
||||
|
||||
* Install the packages libc6-dev-i386 (also try g++-multilib).
|
||||
* Try using the following command line instead of "make 32bit":
|
||||
|
||||
make CFLAGS="-m32 -march=native" LDFLAGS="-m32"
|
||||
NOTE: if after building Redis with a 32 bit target you need to rebuild it
|
||||
with a 64 bit target you need to perform a "make clean" in the root
|
||||
directory of the Redis distribution.
|
||||
|
||||
Allocator
|
||||
---------
|
||||
|
||||
Vendored
+2
-2
@@ -54,8 +54,8 @@ linenoise: .make-prerequisites
|
||||
.PHONY: linenoise
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# Make isinf() available
|
||||
LUA_CFLAGS= -D__C99FEATURES__=1
|
||||
# Make isinf() available
|
||||
LUA_CFLAGS= -D__C99FEATURES__=1
|
||||
endif
|
||||
|
||||
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI $(CFLAGS)
|
||||
|
||||
Vendored
+2
-2
@@ -650,7 +650,7 @@ int redisReaderGetReply(redisReader *r, void **reply) {
|
||||
/* Discard part of the buffer when we've consumed at least 1k, to avoid
|
||||
* doing unnecessary calls to memmove() in sds.c. */
|
||||
if (r->pos >= 1024) {
|
||||
sdsrange(r->buf,r->pos,-1);
|
||||
r->buf = sdsrange(r->buf,r->pos,-1);
|
||||
r->pos = 0;
|
||||
r->len = sdslen(r->buf);
|
||||
}
|
||||
@@ -1125,7 +1125,7 @@ int redisBufferWrite(redisContext *c, int *done) {
|
||||
sdsfree(c->obuf);
|
||||
c->obuf = sdsempty();
|
||||
} else {
|
||||
sdsrange(c->obuf,nwritten,-1);
|
||||
c->obuf = sdsrange(c->obuf,nwritten,-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
-10
@@ -215,17 +215,9 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port, struct t
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
/* Try with IPv6 if no IPv4 address was found. We do it in this order since
|
||||
* in a Redis client you can't afford to test if you have IPv6 connectivity
|
||||
* as this would add latency to every connect. Otherwise a more sensible
|
||||
* route could be: Use IPv6 if both addresses are available and there is IPv6
|
||||
* connectivity. */
|
||||
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
|
||||
hints.ai_family = AF_INET6;
|
||||
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
|
||||
|
||||
Vendored
+109
-386
@@ -1,6 +1,6 @@
|
||||
/* SDSLib, A C dynamic strings library
|
||||
*
|
||||
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -32,76 +32,52 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include "sds.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
/* Create a new sds string with the content specified by the 'init' pointer
|
||||
* and 'initlen'.
|
||||
* If NULL is used for 'init' the string is initialized with zero bytes.
|
||||
*
|
||||
* The string is always null-termined (all the sds strings are, always) so
|
||||
* even if you create an sds string with:
|
||||
*
|
||||
* mystring = sdsnewlen("abc",3");
|
||||
*
|
||||
* You can print the string with printf() as there is an implicit \0 at the
|
||||
* end of the string. However the string is binary safe and can contain
|
||||
* \0 characters in the middle, as the length is stored in the sds header. */
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
static void sdsOomAbort(void) {
|
||||
fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n");
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
sds sdsnewlen(const void *init, size_t initlen) {
|
||||
struct sdshdr *sh;
|
||||
|
||||
if (init) {
|
||||
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
|
||||
} else {
|
||||
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
|
||||
}
|
||||
sh = malloc(sizeof(struct sdshdr)+initlen+1);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (sh == NULL) sdsOomAbort();
|
||||
#else
|
||||
if (sh == NULL) return NULL;
|
||||
#endif
|
||||
sh->len = initlen;
|
||||
sh->free = 0;
|
||||
if (initlen && init)
|
||||
memcpy(sh->buf, init, initlen);
|
||||
if (initlen) {
|
||||
if (init) memcpy(sh->buf, init, initlen);
|
||||
else memset(sh->buf,0,initlen);
|
||||
}
|
||||
sh->buf[initlen] = '\0';
|
||||
return (char*)sh->buf;
|
||||
}
|
||||
|
||||
/* Create an empty (zero length) sds string. Even in this case the string
|
||||
* always has an implicit null term. */
|
||||
sds sdsempty(void) {
|
||||
return sdsnewlen("",0);
|
||||
}
|
||||
|
||||
/* Create a new sds string starting from a null termined C string. */
|
||||
sds sdsnew(const char *init) {
|
||||
size_t initlen = (init == NULL) ? 0 : strlen(init);
|
||||
return sdsnewlen(init, initlen);
|
||||
}
|
||||
|
||||
/* Duplicate an sds string. */
|
||||
sds sdsdup(const sds s) {
|
||||
return sdsnewlen(s, sdslen(s));
|
||||
}
|
||||
|
||||
/* Free an sds string. No operation is performed if 's' is NULL. */
|
||||
void sdsfree(sds s) {
|
||||
if (s == NULL) return;
|
||||
zfree(s-sizeof(struct sdshdr));
|
||||
free(s-sizeof(struct sdshdr));
|
||||
}
|
||||
|
||||
/* Set the sds string length to the length as obtained with strlen(), so
|
||||
* considering as content only up to the first null term character.
|
||||
*
|
||||
* This function is useful when the sds string is hacked manually in some
|
||||
* way, like in the following example:
|
||||
*
|
||||
* s = sdsnew("foobar");
|
||||
* s[2] = '\0';
|
||||
* sdsupdatelen(s);
|
||||
* printf("%d\n", sdslen(s));
|
||||
*
|
||||
* The output will be "2", but if we comment out the call to sdsupdatelen()
|
||||
* the output will be "6" as the string was modified but the logical length
|
||||
* remains 6 bytes. */
|
||||
void sdsupdatelen(sds s) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
int reallen = strlen(s);
|
||||
@@ -109,24 +85,7 @@ void sdsupdatelen(sds s) {
|
||||
sh->len = reallen;
|
||||
}
|
||||
|
||||
/* Modify an sds string on-place to make it empty (zero length).
|
||||
* However all the existing buffer is not discarded but set as free space
|
||||
* so that next append operations will not require allocations up to the
|
||||
* number of bytes previously available. */
|
||||
void sdsclear(sds s) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
sh->free += sh->len;
|
||||
sh->len = 0;
|
||||
sh->buf[0] = '\0';
|
||||
}
|
||||
|
||||
/* Enlarge the free space at the end of the sds string so that the caller
|
||||
* is sure that after calling this function can overwrite up to addlen
|
||||
* bytes after the end of the string, plus one more byte for nul term.
|
||||
*
|
||||
* Note: this does not change the *length* of the sds string as returned
|
||||
* by sdslen(), but only the free buffer space we have. */
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
static sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
struct sdshdr *sh, *newsh;
|
||||
size_t free = sdsavail(s);
|
||||
size_t len, newlen;
|
||||
@@ -134,84 +93,20 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
if (free >= addlen) return s;
|
||||
len = sdslen(s);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
newlen = (len+addlen);
|
||||
if (newlen < SDS_MAX_PREALLOC)
|
||||
newlen *= 2;
|
||||
else
|
||||
newlen += SDS_MAX_PREALLOC;
|
||||
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
|
||||
newlen = (len+addlen)*2;
|
||||
newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (newsh == NULL) sdsOomAbort();
|
||||
#else
|
||||
if (newsh == NULL) return NULL;
|
||||
#endif
|
||||
|
||||
newsh->free = newlen - len;
|
||||
return newsh->buf;
|
||||
}
|
||||
|
||||
/* Reallocate the sds string so that it has no free space at the end. The
|
||||
* contained string remains not altered, but next concatenation operations
|
||||
* will require a reallocation.
|
||||
*
|
||||
* After the call, the passed sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call. */
|
||||
sds sdsRemoveFreeSpace(sds s) {
|
||||
struct sdshdr *sh;
|
||||
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
sh = zrealloc(sh, sizeof(struct sdshdr)+sh->len+1);
|
||||
sh->free = 0;
|
||||
return sh->buf;
|
||||
}
|
||||
|
||||
/* Return the total size of the allocation of the specifed sds string,
|
||||
* including:
|
||||
* 1) The sds header before the pointer.
|
||||
* 2) The string.
|
||||
* 3) The free buffer at the end if any.
|
||||
* 4) The implicit null term.
|
||||
*/
|
||||
size_t sdsAllocSize(sds s) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
|
||||
return sizeof(*sh)+sh->len+sh->free+1;
|
||||
}
|
||||
|
||||
/* Increment the sds length and decrements the left free space at the
|
||||
* end of the string according to 'incr'. Also set the null term
|
||||
* in the new end of the string.
|
||||
*
|
||||
* This function is used in order to fix the string length after the
|
||||
* user calls sdsMakeRoomFor(), writes something after the end of
|
||||
* the current string, and finally needs to set the new length.
|
||||
*
|
||||
* Note: it is possible to use a negative increment in order to
|
||||
* right-trim the string.
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the
|
||||
* following schema, to cat bytes coming from the kernel to the end of an
|
||||
* sds string without copying into an intermediate buffer:
|
||||
*
|
||||
* oldlen = sdslen(s);
|
||||
* s = sdsMakeRoomFor(s, BUFFER_SIZE);
|
||||
* nread = read(fd, s+oldlen, BUFFER_SIZE);
|
||||
* ... check for nread <= 0 and handle it ...
|
||||
* sdsIncrLen(s, nread);
|
||||
*/
|
||||
void sdsIncrLen(sds s, int incr) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
|
||||
assert(sh->free >= incr);
|
||||
sh->len += incr;
|
||||
sh->free -= incr;
|
||||
assert(sh->free >= 0);
|
||||
s[sh->len] = '\0';
|
||||
}
|
||||
|
||||
/* Grow the sds to have the specified length. Bytes that were not part of
|
||||
* the original length of the sds will be set to zero.
|
||||
*
|
||||
* if the specified length is smaller than the current length, no operation
|
||||
* is performed. */
|
||||
* the original length of the sds will be set to zero. */
|
||||
sds sdsgrowzero(sds s, size_t len) {
|
||||
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
|
||||
size_t totlen, curlen = sh->len;
|
||||
@@ -229,11 +124,6 @@ sds sdsgrowzero(sds s, size_t len) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
|
||||
* end of the specified sds string 's'.
|
||||
*
|
||||
* After the call, the passed sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call. */
|
||||
sds sdscatlen(sds s, const void *t, size_t len) {
|
||||
struct sdshdr *sh;
|
||||
size_t curlen = sdslen(s);
|
||||
@@ -248,25 +138,11 @@ sds sdscatlen(sds s, const void *t, size_t len) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Append the specified null termianted C string to the sds string 's'.
|
||||
*
|
||||
* After the call, the passed sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call. */
|
||||
sds sdscat(sds s, const char *t) {
|
||||
return sdscatlen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
/* Append the specified sds 't' to the existing sds 's'.
|
||||
*
|
||||
* After the call, the modified sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call. */
|
||||
sds sdscatsds(sds s, const sds t) {
|
||||
return sdscatlen(s, t, sdslen(t));
|
||||
}
|
||||
|
||||
/* Destructively modify the sds string 's' to hold the specified binary
|
||||
* safe string pointed by 't' of length 'len' bytes. */
|
||||
sds sdscpylen(sds s, const char *t, size_t len) {
|
||||
sds sdscpylen(sds s, char *t, size_t len) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t totlen = sh->free+sh->len;
|
||||
|
||||
@@ -283,52 +159,37 @@ sds sdscpylen(sds s, const char *t, size_t len) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Like sdscpylen() but 't' must be a null-termined string so that the length
|
||||
* of the string is obtained with strlen(). */
|
||||
sds sdscpy(sds s, const char *t) {
|
||||
sds sdscpy(sds s, char *t) {
|
||||
return sdscpylen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
/* Like sdscatpritf() but gets va_list instead of being variadic. */
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
va_list cpy;
|
||||
char *buf, *t;
|
||||
size_t buflen = 16;
|
||||
|
||||
while(1) {
|
||||
buf = zmalloc(buflen);
|
||||
buf = malloc(buflen);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (buf == NULL) sdsOomAbort();
|
||||
#else
|
||||
if (buf == NULL) return NULL;
|
||||
#endif
|
||||
buf[buflen-2] = '\0';
|
||||
va_copy(cpy,ap);
|
||||
vsnprintf(buf, buflen, fmt, cpy);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
zfree(buf);
|
||||
free(buf);
|
||||
buflen *= 2;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
t = sdscat(s, buf);
|
||||
zfree(buf);
|
||||
free(buf);
|
||||
return t;
|
||||
}
|
||||
|
||||
/* Append to the sds string 's' a string obtained using printf-alike format
|
||||
* specifier.
|
||||
*
|
||||
* After the call, the modified sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* s = sdsempty("Sum is: ");
|
||||
* s = sdscatprintf(s,"%d+%d = %d",a,b,a+b).
|
||||
*
|
||||
* Often you need to create a string from scratch with the printf-alike
|
||||
* format. When this is the need, just use sdsempty() as the target string:
|
||||
*
|
||||
* s = sdscatprintf(sdsempty(), "... your format ...", args);
|
||||
*/
|
||||
sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
char *t;
|
||||
@@ -338,20 +199,6 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
return t;
|
||||
}
|
||||
|
||||
/* Remove the part of the string from left and from right composed just of
|
||||
* contiguous characters found in 'cset', that is a null terminted C string.
|
||||
*
|
||||
* After the call, the modified sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
|
||||
* s = sdstrim(s,"A. :");
|
||||
* printf("%s\n", s);
|
||||
*
|
||||
* Output will be just "Hello World".
|
||||
*/
|
||||
sds sdstrim(sds s, const char *cset) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
char *start, *end, *sp, *ep;
|
||||
@@ -369,27 +216,11 @@ sds sdstrim(sds s, const char *cset) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Turn the string into a smaller (or equal) string containing only the
|
||||
* substring specified by the 'start' and 'end' indexes.
|
||||
*
|
||||
* start and end can be negative, where -1 means the last character of the
|
||||
* string, -2 the penultimate character, and so forth.
|
||||
*
|
||||
* The interval is inclusive, so the start and end characters will be part
|
||||
* of the resulting string.
|
||||
*
|
||||
* The string is modified in-place.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* s = sdsnew("Hello World");
|
||||
* sdstrim(s,1,-1); => "ello Worl"
|
||||
*/
|
||||
void sdsrange(sds s, int start, int end) {
|
||||
sds sdsrange(sds s, int start, int end) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t newlen, len = sdslen(s);
|
||||
|
||||
if (len == 0) return;
|
||||
if (len == 0) return s;
|
||||
if (start < 0) {
|
||||
start = len+start;
|
||||
if (start < 0) start = 0;
|
||||
@@ -413,34 +244,22 @@ void sdsrange(sds s, int start, int end) {
|
||||
sh->buf[newlen] = 0;
|
||||
sh->free = sh->free+(sh->len-newlen);
|
||||
sh->len = newlen;
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Apply tolower() to every character of the sds string 's'. */
|
||||
void sdstolower(sds s) {
|
||||
int len = sdslen(s), j;
|
||||
|
||||
for (j = 0; j < len; j++) s[j] = tolower(s[j]);
|
||||
}
|
||||
|
||||
/* Apply toupper() to every character of the sds string 's'. */
|
||||
void sdstoupper(sds s) {
|
||||
int len = sdslen(s), j;
|
||||
|
||||
for (j = 0; j < len; j++) s[j] = toupper(s[j]);
|
||||
}
|
||||
|
||||
/* Compare two sds strings s1 and s2 with memcmp().
|
||||
*
|
||||
* Return value:
|
||||
*
|
||||
* 1 if s1 > s2.
|
||||
* -1 if s1 < s2.
|
||||
* 0 if s1 and s2 are exactly the same binary string.
|
||||
*
|
||||
* If two strings share exactly the same prefix, but one of the two has
|
||||
* additional characters, the longer string is considered to be greater than
|
||||
* the smaller one. */
|
||||
int sdscmp(const sds s1, const sds s2) {
|
||||
int sdscmp(sds s1, sds s2) {
|
||||
size_t l1, l2, minlen;
|
||||
int cmp;
|
||||
|
||||
@@ -468,15 +287,14 @@ int sdscmp(const sds s1, const sds s2) {
|
||||
* requires length arguments. sdssplit() is just the
|
||||
* same function but for zero-terminated strings.
|
||||
*/
|
||||
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) {
|
||||
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
|
||||
int elements = 0, slots = 5, start = 0, j;
|
||||
sds *tokens;
|
||||
|
||||
if (seplen < 1 || len < 0) return NULL;
|
||||
|
||||
tokens = zmalloc(sizeof(sds)*slots);
|
||||
if (tokens == NULL) return NULL;
|
||||
|
||||
sds *tokens = malloc(sizeof(sds)*slots);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (tokens == NULL) sdsOomAbort();
|
||||
#endif
|
||||
if (seplen < 1 || len < 0 || tokens == NULL) return NULL;
|
||||
if (len == 0) {
|
||||
*count = 0;
|
||||
return tokens;
|
||||
@@ -487,14 +305,26 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count
|
||||
sds *newtokens;
|
||||
|
||||
slots *= 2;
|
||||
newtokens = zrealloc(tokens,sizeof(sds)*slots);
|
||||
if (newtokens == NULL) goto cleanup;
|
||||
newtokens = realloc(tokens,sizeof(sds)*slots);
|
||||
if (newtokens == NULL) {
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
sdsOomAbort();
|
||||
#else
|
||||
goto cleanup;
|
||||
#endif
|
||||
}
|
||||
tokens = newtokens;
|
||||
}
|
||||
/* search the separator */
|
||||
if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
|
||||
tokens[elements] = sdsnewlen(s+start,j-start);
|
||||
if (tokens[elements] == NULL) goto cleanup;
|
||||
if (tokens[elements] == NULL) {
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
sdsOomAbort();
|
||||
#else
|
||||
goto cleanup;
|
||||
#endif
|
||||
}
|
||||
elements++;
|
||||
start = j+seplen;
|
||||
j = j+seplen-1; /* skip the separator */
|
||||
@@ -502,33 +332,35 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count
|
||||
}
|
||||
/* Add the final element. We are sure there is room in the tokens array. */
|
||||
tokens[elements] = sdsnewlen(s+start,len-start);
|
||||
if (tokens[elements] == NULL) goto cleanup;
|
||||
if (tokens[elements] == NULL) {
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
sdsOomAbort();
|
||||
#else
|
||||
goto cleanup;
|
||||
#endif
|
||||
}
|
||||
elements++;
|
||||
*count = elements;
|
||||
return tokens;
|
||||
|
||||
#ifndef SDS_ABORT_ON_OOM
|
||||
cleanup:
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < elements; i++) sdsfree(tokens[i]);
|
||||
zfree(tokens);
|
||||
*count = 0;
|
||||
free(tokens);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */
|
||||
void sdsfreesplitres(sds *tokens, int count) {
|
||||
if (!tokens) return;
|
||||
while(count--)
|
||||
sdsfree(tokens[count]);
|
||||
zfree(tokens);
|
||||
free(tokens);
|
||||
}
|
||||
|
||||
/* Create an sds string from a long long value. It is much faster than:
|
||||
*
|
||||
* sdscatprintf(sdsempty(),"%lld\n", value);
|
||||
*/
|
||||
sds sdsfromlonglong(long long value) {
|
||||
char buf[32], *p;
|
||||
unsigned long long v;
|
||||
@@ -544,14 +376,10 @@ sds sdsfromlonglong(long long value) {
|
||||
return sdsnewlen(p,32-(p-buf));
|
||||
}
|
||||
|
||||
/* Append to the sds string "s" an escaped string representation where
|
||||
* all the non-printable characters (tested with isprint()) are turned into
|
||||
* escapes in the form "\n\r\a...." or "\x<hex-number>".
|
||||
*
|
||||
* After the call, the modified sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call. */
|
||||
sds sdscatrepr(sds s, const char *p, size_t len) {
|
||||
sds sdscatrepr(sds s, char *p, size_t len) {
|
||||
s = sdscatlen(s,"\"",1);
|
||||
if (s == NULL) return NULL;
|
||||
|
||||
while(len--) {
|
||||
switch(*p) {
|
||||
case '\\':
|
||||
@@ -571,64 +399,27 @@ sds sdscatrepr(sds s, const char *p, size_t len) {
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
if (s == NULL) return NULL;
|
||||
}
|
||||
return sdscatlen(s,"\"",1);
|
||||
}
|
||||
|
||||
/* Helper function for sdssplitargs() that returns non zero if 'c'
|
||||
* is a valid hex digit. */
|
||||
int is_hex_digit(char c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
|
||||
(c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
/* Helper function for sdssplitargs() that converts an hex digit into an
|
||||
* integer from 0 to 15 */
|
||||
int hex_digit_to_int(char c) {
|
||||
switch(c) {
|
||||
case '0': return 0;
|
||||
case '1': return 1;
|
||||
case '2': return 2;
|
||||
case '3': return 3;
|
||||
case '4': return 4;
|
||||
case '5': return 5;
|
||||
case '6': return 6;
|
||||
case '7': return 7;
|
||||
case '8': return 8;
|
||||
case '9': return 9;
|
||||
case 'a': case 'A': return 10;
|
||||
case 'b': case 'B': return 11;
|
||||
case 'c': case 'C': return 12;
|
||||
case 'd': case 'D': return 13;
|
||||
case 'e': case 'E': return 14;
|
||||
case 'f': case 'F': return 15;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Split a line into arguments, where every argument can be in the
|
||||
* following programming-language REPL-alike form:
|
||||
*
|
||||
* foo bar "newline are supported\n" and "\xff\x00otherstuff"
|
||||
*
|
||||
* The number of arguments is stored into *argc, and an array
|
||||
* of sds is returned.
|
||||
*
|
||||
* The caller should free the resulting array of sds strings with
|
||||
* sdsfreesplitres().
|
||||
* of sds is returned. The caller should sdsfree() all the returned
|
||||
* strings and finally free() the array itself.
|
||||
*
|
||||
* Note that sdscatrepr() is able to convert back a string into
|
||||
* a quoted string in the same format sdssplitargs() is able to parse.
|
||||
*
|
||||
* The function returns the allocated tokens on success, even when the
|
||||
* input string is empty, or NULL if the input contains unbalanced
|
||||
* quotes or closed quotes followed by non space characters
|
||||
* as in: "foo"bar or "foo'
|
||||
*/
|
||||
sds *sdssplitargs(const char *line, int *argc) {
|
||||
const char *p = line;
|
||||
sds *sdssplitargs(char *line, int *argc) {
|
||||
char *p = line;
|
||||
char *current = NULL;
|
||||
char **vector = NULL;
|
||||
char **vector = NULL, **_vector = NULL;
|
||||
|
||||
*argc = 0;
|
||||
while(1) {
|
||||
@@ -636,24 +427,17 @@ sds *sdssplitargs(const char *line, int *argc) {
|
||||
while(*p && isspace(*p)) p++;
|
||||
if (*p) {
|
||||
/* get a token */
|
||||
int inq=0; /* set to 1 if we are in "quotes" */
|
||||
int insq=0; /* set to 1 if we are in 'single quotes' */
|
||||
int inq=0; /* set to 1 if we are in "quotes" */
|
||||
int done=0;
|
||||
|
||||
if (current == NULL) current = sdsempty();
|
||||
if (current == NULL) {
|
||||
current = sdsempty();
|
||||
if (current == NULL) goto err;
|
||||
}
|
||||
|
||||
while(!done) {
|
||||
if (inq) {
|
||||
if (*p == '\\' && *(p+1) == 'x' &&
|
||||
is_hex_digit(*(p+2)) &&
|
||||
is_hex_digit(*(p+3)))
|
||||
{
|
||||
unsigned char byte;
|
||||
|
||||
byte = (hex_digit_to_int(*(p+2))*16)+
|
||||
hex_digit_to_int(*(p+3));
|
||||
current = sdscatlen(current,(char*)&byte,1);
|
||||
p += 3;
|
||||
} else if (*p == '\\' && *(p+1)) {
|
||||
if (*p == '\\' && *(p+1)) {
|
||||
char c;
|
||||
|
||||
p++;
|
||||
@@ -667,23 +451,7 @@ sds *sdssplitargs(const char *line, int *argc) {
|
||||
}
|
||||
current = sdscatlen(current,&c,1);
|
||||
} else if (*p == '"') {
|
||||
/* closing quote must be followed by a space or
|
||||
* nothing at all. */
|
||||
if (*(p+1) && !isspace(*(p+1))) goto err;
|
||||
done=1;
|
||||
} else if (!*p) {
|
||||
/* unterminated quotes */
|
||||
goto err;
|
||||
} else {
|
||||
current = sdscatlen(current,p,1);
|
||||
}
|
||||
} else if (insq) {
|
||||
if (*p == '\\' && *(p+1) == '\'') {
|
||||
p++;
|
||||
current = sdscatlen(current,"'",1);
|
||||
} else if (*p == '\'') {
|
||||
/* closing quote must be followed by a space or
|
||||
* nothing at all. */
|
||||
/* closing quote must be followed by a space */
|
||||
if (*(p+1) && !isspace(*(p+1))) goto err;
|
||||
done=1;
|
||||
} else if (!*p) {
|
||||
@@ -704,24 +472,23 @@ sds *sdssplitargs(const char *line, int *argc) {
|
||||
case '"':
|
||||
inq=1;
|
||||
break;
|
||||
case '\'':
|
||||
insq=1;
|
||||
break;
|
||||
default:
|
||||
current = sdscatlen(current,p,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*p) p++;
|
||||
if (current == NULL) goto err;
|
||||
}
|
||||
/* add the token to the vector */
|
||||
vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
|
||||
_vector = realloc(vector,((*argc)+1)*sizeof(char*));
|
||||
if (_vector == NULL) goto err;
|
||||
|
||||
vector = _vector;
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
} else {
|
||||
/* Even on empty input string return something not NULL. */
|
||||
if (vector == NULL) vector = zmalloc(sizeof(void*));
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
@@ -729,55 +496,30 @@ sds *sdssplitargs(const char *line, int *argc) {
|
||||
err:
|
||||
while((*argc)--)
|
||||
sdsfree(vector[*argc]);
|
||||
zfree(vector);
|
||||
if (current) sdsfree(current);
|
||||
*argc = 0;
|
||||
if (vector != NULL) free(vector);
|
||||
if (current != NULL) sdsfree(current);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Modify the string substituting all the occurrences of the set of
|
||||
* characters specified in the 'from' string to the corresponding character
|
||||
* in the 'to' array.
|
||||
*
|
||||
* For instance: sdsmapchars(mystring, "ho", "01", 2)
|
||||
* will have the effect of turning the string "hello" into "0ell1".
|
||||
*
|
||||
* The function returns the sds string pointer, that is always the same
|
||||
* as the input pointer since no resize is needed. */
|
||||
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) {
|
||||
size_t j, i, l = sdslen(s);
|
||||
|
||||
for (j = 0; j < l; j++) {
|
||||
for (i = 0; i < setlen; i++) {
|
||||
if (s[j] == from[i]) {
|
||||
s[j] = to[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Join an array of C strings using the specified separator (also a C string).
|
||||
* Returns the result as an sds string. */
|
||||
sds sdsjoin(char **argv, int argc, char *sep) {
|
||||
sds join = sdsempty();
|
||||
int j;
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
join = sdscat(join, argv[j]);
|
||||
if (j != argc-1) join = sdscat(join,sep);
|
||||
}
|
||||
return join;
|
||||
}
|
||||
|
||||
#ifdef SDS_TEST_MAIN
|
||||
#include <stdio.h>
|
||||
#include "testhelp.h"
|
||||
|
||||
int __failed_tests = 0;
|
||||
int __test_num = 0;
|
||||
#define test_cond(descr,_c) do { \
|
||||
__test_num++; printf("%d - %s: ", __test_num, descr); \
|
||||
if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \
|
||||
} while(0);
|
||||
#define test_report() do { \
|
||||
printf("%d tests, %d passed, %d failed\n", __test_num, \
|
||||
__test_num-__failed_tests, __failed_tests); \
|
||||
if (__failed_tests) { \
|
||||
printf("=== WARNING === We have failed tests here...\n"); \
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
int main(void) {
|
||||
{
|
||||
struct sdshdr *sh;
|
||||
sds x = sdsnew("foo"), y;
|
||||
|
||||
test_cond("Create a string and obtain the length",
|
||||
@@ -857,26 +599,7 @@ int main(void) {
|
||||
x = sdsnew("aar");
|
||||
y = sdsnew("bar");
|
||||
test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
|
||||
|
||||
{
|
||||
int oldfree;
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnew("0");
|
||||
sh = (void*) (x-(sizeof(struct sdshdr)));
|
||||
test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0);
|
||||
x = sdsMakeRoomFor(x,1);
|
||||
sh = (void*) (x-(sizeof(struct sdshdr)));
|
||||
test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0);
|
||||
oldfree = sh->free;
|
||||
x[1] = '1';
|
||||
sdsIncrLen(x,1);
|
||||
test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1');
|
||||
test_cond("sdsIncrLen() -- len", sh->len == 2);
|
||||
test_cond("sdsIncrLen() -- free", sh->free == oldfree-1);
|
||||
}
|
||||
}
|
||||
test_report()
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
Vendored
+8
-20
@@ -31,8 +31,6 @@
|
||||
#ifndef __SDS_H
|
||||
#define __SDS_H
|
||||
|
||||
#define SDS_MAX_PREALLOC (1024*1024)
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
@@ -60,13 +58,12 @@ sds sdsempty(void);
|
||||
size_t sdslen(const sds s);
|
||||
sds sdsdup(const sds s);
|
||||
void sdsfree(sds s);
|
||||
size_t sdsavail(const sds s);
|
||||
size_t sdsavail(sds s);
|
||||
sds sdsgrowzero(sds s, size_t len);
|
||||
sds sdscatlen(sds s, const void *t, size_t len);
|
||||
sds sdscat(sds s, const char *t);
|
||||
sds sdscatsds(sds s, const sds t);
|
||||
sds sdscpylen(sds s, const char *t, size_t len);
|
||||
sds sdscpy(sds s, const char *t);
|
||||
sds sdscpylen(sds s, char *t, size_t len);
|
||||
sds sdscpy(sds s, char *t);
|
||||
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
|
||||
#ifdef __GNUC__
|
||||
@@ -77,24 +74,15 @@ sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
void sdsrange(sds s, int start, int end);
|
||||
sds sdsrange(sds s, int start, int end);
|
||||
void sdsupdatelen(sds s);
|
||||
void sdsclear(sds s);
|
||||
int sdscmp(const sds s1, const sds s2);
|
||||
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
|
||||
int sdscmp(sds s1, sds s2);
|
||||
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count);
|
||||
void sdsfreesplitres(sds *tokens, int count);
|
||||
void sdstolower(sds s);
|
||||
void sdstoupper(sds s);
|
||||
sds sdsfromlonglong(long long value);
|
||||
sds sdscatrepr(sds s, const char *p, size_t len);
|
||||
sds *sdssplitargs(const char *line, int *argc);
|
||||
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
|
||||
sds sdsjoin(char **argv, int argc, char *sep);
|
||||
|
||||
/* Low level functions exposed to the user API */
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen);
|
||||
void sdsIncrLen(sds s, int incr);
|
||||
sds sdsRemoveFreeSpace(sds s);
|
||||
size_t sdsAllocSize(sds s);
|
||||
sds sdscatrepr(sds s, char *p, size_t len);
|
||||
sds *sdssplitargs(char *line, int *argc);
|
||||
|
||||
#endif
|
||||
|
||||
Vendored
-13
@@ -1,13 +0,0 @@
|
||||
/* Drop in replacement for zmalloc.h in order to just use libc malloc without
|
||||
* any wrappering. */
|
||||
|
||||
#ifndef ZMALLOC_H
|
||||
#define ZMALLOC_H
|
||||
|
||||
#define zmalloc malloc
|
||||
#define zrealloc realloc
|
||||
#define zcalloc(x) calloc(x,1)
|
||||
#define zfree free
|
||||
#define zstrdup strdup
|
||||
|
||||
#endif
|
||||
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
||||
# Your platform. See PLATS for possible values.
|
||||
PLAT= none
|
||||
|
||||
CC?= gcc
|
||||
CC= gcc
|
||||
CFLAGS= -O2 -Wall $(MYCFLAGS)
|
||||
AR= ar rcu
|
||||
RANLIB= ranlib
|
||||
|
||||
Vendored
+1
-1
@@ -374,7 +374,7 @@ static int table_is_an_array(lua_State *L) {
|
||||
while(lua_next(L,-2)) {
|
||||
/* Stack: ... key value */
|
||||
lua_pop(L,1); /* Stack: ... key */
|
||||
if (lua_type(L,-1) != LUA_TNUMBER) goto not_array;
|
||||
if (!lua_isnumber(L,-1)) goto not_array;
|
||||
n = lua_tonumber(L,-1);
|
||||
idx = n;
|
||||
if (idx != n || idx < 1) goto not_array;
|
||||
|
||||
+7
-105
@@ -24,14 +24,9 @@ pidfile /var/run/redis.pid
|
||||
# If port 0 is specified Redis will not listen on a TCP socket.
|
||||
port 6379
|
||||
|
||||
# By default Redis listens for connections from all the network interfaces
|
||||
# available on the server. It is possible to listen to just one or multiple
|
||||
# interfaces using the "bind" configuration directive, followed by one or
|
||||
# more IP addresses.
|
||||
# If you want you can bind a single interface, if the bind option is not
|
||||
# specified all the interfaces will listen for incoming connections.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# bind 192.168.1.100 10.0.0.1
|
||||
# bind 127.0.0.1
|
||||
|
||||
# Specify the path for the unix socket that will be used to listen for
|
||||
@@ -68,10 +63,10 @@ tcp-keepalive 0
|
||||
# warning (only very important / critical messages are logged)
|
||||
loglevel notice
|
||||
|
||||
# Specify the log file name. Also the emptry string can be used to force
|
||||
# Specify the log file name. Also 'stdout' can be used to force
|
||||
# Redis to log on the standard output. Note that if you use standard
|
||||
# output for logging but daemonize, logs will be sent to /dev/null
|
||||
logfile ""
|
||||
logfile stdout
|
||||
|
||||
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
|
||||
# and optionally update the other syslog parameters to suit your needs.
|
||||
@@ -208,11 +203,8 @@ slave-read-only yes
|
||||
#
|
||||
# repl-ping-slave-period 10
|
||||
|
||||
# The following option sets the replication timeout for:
|
||||
#
|
||||
# 1) Bulk transfer I/O during SYNC, from the point of view of slave.
|
||||
# 2) Master timeout from the point of view of slaves (data, pings).
|
||||
# 3) Slave timeout from the point of view of masters (REPLCONF ACK pings).
|
||||
# The following option sets a timeout for both Bulk transfer I/O timeout and
|
||||
# master data or ping response timeout. The default value is 60 seconds.
|
||||
#
|
||||
# It is important to make sure that this value is greater than the value
|
||||
# specified for repl-ping-slave-period otherwise a timeout will be detected
|
||||
@@ -235,28 +227,6 @@ slave-read-only yes
|
||||
# be a good idea.
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# Set the replication backlog size. The backlog is a buffer that accumulates
|
||||
# slave data when slaves are disconnected for some time, so that when a slave
|
||||
# wants to reconnect again, often a full resync is not needed, but a partial
|
||||
# resync is enough, just passing the portion of data the slave missed while
|
||||
# disconnected.
|
||||
#
|
||||
# The biggest the replication backlog, the longer the time the slave can be
|
||||
# disconnected and later be able to perform a partial resynchronization.
|
||||
#
|
||||
# The backlog is only allocated once there is at least a slave connected.
|
||||
#
|
||||
# repl-backlog-size 1mb
|
||||
|
||||
# After a master has no longer connected slaves for some time, the backlog
|
||||
# will be freed. The following option configures the amount of seconds that
|
||||
# need to elapse, starting from the time the last slave disconnected, for
|
||||
# the backlog buffer to be freed.
|
||||
#
|
||||
# A value of 0 means to never release the backlog.
|
||||
#
|
||||
# repl-backlog-ttl 3600
|
||||
|
||||
# The slave priority is an integer number published by Redis in the INFO output.
|
||||
# It is used by Redis Sentinel in order to select a slave to promote into a
|
||||
# master if the master is no longer working correctly.
|
||||
@@ -272,28 +242,6 @@ repl-disable-tcp-nodelay no
|
||||
# By default the priority is 100.
|
||||
slave-priority 100
|
||||
|
||||
# It is possible for a master to stop accepting writes if there are less than
|
||||
# N slaves connected, having a lag less or equal than M seconds.
|
||||
#
|
||||
# The N slaves need to be in "online" state.
|
||||
#
|
||||
# The lag in seconds, that must be <= the specified value, is calculated from
|
||||
# the last ping received from the slave, that is usually sent every second.
|
||||
#
|
||||
# This option does not GUARANTEES that N replicas will accept the write, but
|
||||
# will limit the window of exposure for lost writes in case not enough slaves
|
||||
# are available, to the specified number of seconds.
|
||||
#
|
||||
# For example to require at least 3 slaves with a lag <= 10 seconds use:
|
||||
#
|
||||
# min-slaves-to-write 3
|
||||
# min-slaves-max-lag 10
|
||||
#
|
||||
# Setting one or the other to 0 disables the feature.
|
||||
#
|
||||
# By default min-slaves-to-write is set to 0 (feature disabled) and
|
||||
# min-slaves-max-lag is set to 10.
|
||||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
# Require clients to issue AUTH <PASSWORD> before processing any other
|
||||
@@ -531,52 +479,6 @@ slowlog-log-slower-than 10000
|
||||
# You can reclaim memory used by the slow log with SLOWLOG RESET.
|
||||
slowlog-max-len 128
|
||||
|
||||
############################# Event notification ##############################
|
||||
|
||||
# Redis can notify Pub/Sub clients about events happening in the key space.
|
||||
# This feature is documented at http://redis.io/topics/keyspace-events
|
||||
#
|
||||
# For instance if keyspace events notification is enabled, and a client
|
||||
# performs a DEL operation on key "foo" stored in the Database 0, two
|
||||
# messages will be published via Pub/Sub:
|
||||
#
|
||||
# PUBLISH __keyspace@0__:foo del
|
||||
# PUBLISH __keyevent@0__:del foo
|
||||
#
|
||||
# It is possible to select the events that Redis will notify among a set
|
||||
# of classes. Every class is identified by a single character:
|
||||
#
|
||||
# K Keyspace events, published with __keyspace@<db>__ prefix.
|
||||
# E Keyevent events, published with __keyevent@<db>__ prefix.
|
||||
# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
|
||||
# $ String commands
|
||||
# l List commands
|
||||
# s Set commands
|
||||
# h Hash commands
|
||||
# z Sorted set commands
|
||||
# x Expired events (events generated every time a key expires)
|
||||
# e Evicted events (events generated when a key is evicted for maxmemory)
|
||||
# A Alias for g$lshzxe, so that the "AKE" string means all the events.
|
||||
#
|
||||
# The "notify-keyspace-events" takes as argument a string that is composed
|
||||
# by zero or multiple characters. The empty string means that notifications
|
||||
# are disabled at all.
|
||||
#
|
||||
# Example: to enable list and generic events, from the point of view of the
|
||||
# event name, use:
|
||||
#
|
||||
# notify-keyspace-events Elg
|
||||
#
|
||||
# Example 2: to get the stream of the expired keys subscribing to channel
|
||||
# name __keyevent@0__:expired use:
|
||||
#
|
||||
# notify-keyspace-events Ex
|
||||
#
|
||||
# By default all notifications are disabled because most users don't need
|
||||
# this feature and the feature has some overhead. Note that if you don't
|
||||
# specify at least one of K or E, no events will be delivered.
|
||||
notify-keyspace-events ""
|
||||
|
||||
############################### ADVANCED CONFIG ###############################
|
||||
|
||||
# Hashes are encoded using a memory efficient data structure when they have a
|
||||
@@ -665,7 +567,7 @@ client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
# closing connections of clients in timeot, purging expired keys that are
|
||||
# never requested, and so forth.
|
||||
#
|
||||
# Not all tasks are performed with the same frequency, but Redis checks for
|
||||
# Not all tasks are perforemd with the same frequency, but Redis checks for
|
||||
# tasks to perform accordingly to the specified "hz" value.
|
||||
#
|
||||
# By default "hz" is set to 10. Raising the value will use more CPU when
|
||||
|
||||
+31
-27
@@ -9,10 +9,6 @@ port 26379
|
||||
# Tells Sentinel to monitor this slave, and to consider it in O_DOWN
|
||||
# (Objectively Down) state only if at least <quorum> sentinels agree.
|
||||
#
|
||||
# Note that whatever is the ODOWN quorum, a Sentinel will require to
|
||||
# be elected by the majority of the known Sentinels in order to
|
||||
# start a failover, so no failover can be performed in minority.
|
||||
#
|
||||
# Note: master name should not include special characters or spaces.
|
||||
# The valid charset is A-z 0-9 and the three characters ".-_".
|
||||
sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
@@ -46,6 +42,11 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
# Default is 30 seconds.
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
|
||||
# sentinel can-failover <master-name> <yes|no>
|
||||
#
|
||||
# Specify if this Sentinel can start the failover for this master.
|
||||
sentinel can-failover mymaster yes
|
||||
|
||||
# sentinel parallel-syncs <master-name> <numslaves>
|
||||
#
|
||||
# How many slaves we can reconfigure to point to the new slave simultaneously
|
||||
@@ -56,28 +57,19 @@ sentinel parallel-syncs mymaster 1
|
||||
|
||||
# sentinel failover-timeout <master-name> <milliseconds>
|
||||
#
|
||||
# Specifies the failover timeout in milliseconds. It is used in many ways:
|
||||
# Specifies the failover timeout in milliseconds. When this time has elapsed
|
||||
# without any progress in the failover process, it is considered concluded by
|
||||
# the sentinel even if not all the attached slaves were correctly configured
|
||||
# to replicate with the new master (however a "best effort" SLAVEOF command
|
||||
# is sent to all the slaves before).
|
||||
#
|
||||
# - The time needed to re-start a failover after a previous failover was
|
||||
# already tried against the same master by a given Sentinel, is two
|
||||
# times the failover timeout.
|
||||
# Also when 25% of this time has elapsed without any advancement, and there
|
||||
# is a leader switch (the sentinel did not started the failover but is now
|
||||
# elected as leader), the sentinel will continue the failover doing a
|
||||
# "takeover".
|
||||
#
|
||||
# - The time needed for a slave replicating to a wrong master according
|
||||
# to a Sentinel currnet configuration, to be forced to replicate
|
||||
# with the right master, is exactly the failover timeout (counting since
|
||||
# the moment a Sentinel detected the misconfiguration).
|
||||
#
|
||||
# - The time needed to cancel a failover that is already in progress but
|
||||
# did not produced any configuration change (SLAVEOF NO ONE yet not
|
||||
# acknowledged by the promoted slave).
|
||||
#
|
||||
# - The maximum time a failover in progress waits for all the slaves to be
|
||||
# reconfigured as slaves of the new master. However even after this time
|
||||
# the slaves will be reconfigured by the Sentinels anyway, but not with
|
||||
# the exact parallel-syncs progression as specified.
|
||||
#
|
||||
# Default is 3 minutes.
|
||||
sentinel failover-timeout mymaster 180000
|
||||
# Default is 15 minutes.
|
||||
sentinel failover-timeout mymaster 900000
|
||||
|
||||
# SCRIPTS EXECUTION
|
||||
#
|
||||
@@ -122,20 +114,32 @@ sentinel failover-timeout mymaster 180000
|
||||
#
|
||||
# sentinel client-reconfig-script <master-name> <script-path>
|
||||
#
|
||||
# When the master changed because of a failover a script can be called in
|
||||
# When the failover starts, ends, or is aborted, a script can be called in
|
||||
# order to perform application-specific tasks to notify the clients that the
|
||||
# configuration has changed and the master is at a different address.
|
||||
#
|
||||
# The script is called in the following cases:
|
||||
#
|
||||
# Failover started (a slave is already promoted)
|
||||
# Failover finished (all the additional slaves already reconfigured)
|
||||
# Failover aborted (in that case the script was previously called when the
|
||||
# failover started, and now gets called again with swapped
|
||||
# addresses).
|
||||
#
|
||||
# The following arguments are passed to the script:
|
||||
#
|
||||
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
|
||||
#
|
||||
# <state> is currently always "failover"
|
||||
# <state> is "start", "end" or "abort"
|
||||
# <role> is either "leader" or "observer"
|
||||
#
|
||||
# The arguments from-ip, from-port, to-ip, to-port are used to communicate
|
||||
# the old address of the master and the new address of the elected slave
|
||||
# (now a master).
|
||||
# (now a master) in the case state is "start" or "end".
|
||||
#
|
||||
# For abort instead the "from" is the address of the promoted slave and
|
||||
# "to" is the address of the original master address, since the failover
|
||||
# was aborted.
|
||||
#
|
||||
# This script should be resistant to multiple invocations.
|
||||
#
|
||||
|
||||
+39
-45
@@ -18,76 +18,72 @@ OPTIMIZATION?=-O2
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua
|
||||
|
||||
# Default settings
|
||||
STD=-std=c99 -pedantic
|
||||
WARN=-Wall
|
||||
OPT=$(OPTIMIZATION)
|
||||
|
||||
PREFIX?=/usr/local
|
||||
INSTALL_BIN=$(PREFIX)/bin
|
||||
INSTALL=install
|
||||
STD= -std=c99 -pedantic
|
||||
WARN= -Wall
|
||||
OPT= $(OPTIMIZATION)
|
||||
|
||||
# Default allocator
|
||||
ifeq ($(uname_S),Linux)
|
||||
MALLOC=jemalloc
|
||||
MALLOC=jemalloc
|
||||
else
|
||||
MALLOC=libc
|
||||
MALLOC=libc
|
||||
endif
|
||||
|
||||
# Backwards compatibility for selecting an allocator
|
||||
ifeq ($(USE_TCMALLOC),yes)
|
||||
MALLOC=tcmalloc
|
||||
MALLOC=tcmalloc
|
||||
endif
|
||||
|
||||
ifeq ($(USE_TCMALLOC_MINIMAL),yes)
|
||||
MALLOC=tcmalloc_minimal
|
||||
MALLOC=tcmalloc_minimal
|
||||
endif
|
||||
|
||||
ifeq ($(USE_JEMALLOC),yes)
|
||||
MALLOC=jemalloc
|
||||
MALLOC=jemalloc
|
||||
endif
|
||||
|
||||
# Override default settings if possible
|
||||
-include .make-settings
|
||||
|
||||
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LIBS=-lm
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
INSTALL=cp -pf
|
||||
FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lpthread
|
||||
else ifeq ($(uname_S),Darwin)
|
||||
|
||||
FINAL_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS) -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_LDFLAGS= $(LDFLAGS) $(REDIS_LDFLAGS) -g -ggdb
|
||||
FINAL_LIBS= -ldl -lnsl -lsocket -lm -lpthread
|
||||
DEBUG= -g -ggdb
|
||||
else
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
FINAL_LIBS+= -pthread
|
||||
FINAL_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_LDFLAGS= $(LDFLAGS) $(REDIS_LDFLAGS) -g -rdynamic -ggdb
|
||||
FINAL_LIBS= -lm -pthread
|
||||
DEBUG= -g -rdynamic -ggdb
|
||||
endif
|
||||
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
|
||||
|
||||
ifeq ($(MALLOC),tcmalloc)
|
||||
FINAL_CFLAGS+= -DUSE_TCMALLOC
|
||||
FINAL_LIBS+= -ltcmalloc
|
||||
FINAL_CFLAGS+= -DUSE_TCMALLOC
|
||||
FINAL_LIBS+= -ltcmalloc
|
||||
endif
|
||||
|
||||
ifeq ($(MALLOC),tcmalloc_minimal)
|
||||
FINAL_CFLAGS+= -DUSE_TCMALLOC
|
||||
FINAL_LIBS+= -ltcmalloc_minimal
|
||||
FINAL_CFLAGS+= -DUSE_TCMALLOC
|
||||
FINAL_LIBS+= -ltcmalloc_minimal
|
||||
endif
|
||||
|
||||
ifeq ($(MALLOC),jemalloc)
|
||||
DEPENDENCY_TARGETS+= jemalloc
|
||||
FINAL_CFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include
|
||||
FINAL_LIBS+= ../deps/jemalloc/lib/libjemalloc.a -ldl
|
||||
DEPENDENCY_TARGETS+= jemalloc
|
||||
FINAL_CFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include
|
||||
FINAL_LIBS+= ../deps/jemalloc/lib/libjemalloc.a -ldl
|
||||
endif
|
||||
|
||||
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
|
||||
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
|
||||
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
|
||||
|
||||
PREFIX?=/usr/local
|
||||
INSTALL_BIN= $(PREFIX)/bin
|
||||
INSTALL= cp -pf
|
||||
|
||||
CCCOLOR="\033[34m"
|
||||
LINKCOLOR="\033[34;1m"
|
||||
SRCCOLOR="\033[33m"
|
||||
@@ -101,17 +97,17 @@ QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(EN
|
||||
QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2;
|
||||
endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o migrate.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o redis-benchmark.o
|
||||
REDIS_CHECK_DUMP_NAME=redis-check-dump
|
||||
REDIS_CHECK_DUMP_OBJ=redis-check-dump.o lzf_c.o lzf_d.o crc64.o
|
||||
REDIS_CHECK_AOF_NAME=redis-check-aof
|
||||
REDIS_CHECK_AOF_OBJ=redis-check-aof.o
|
||||
REDIS_SERVER_NAME= redis-server
|
||||
REDIS_SENTINEL_NAME= redis-sentinel
|
||||
REDIS_SERVER_OBJ= adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o migrate.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o
|
||||
REDIS_CLI_NAME= redis-cli
|
||||
REDIS_CLI_OBJ= anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o
|
||||
REDIS_BENCHMARK_NAME= redis-benchmark
|
||||
REDIS_BENCHMARK_OBJ= ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o redis-benchmark.o
|
||||
REDIS_CHECK_DUMP_NAME= redis-check-dump
|
||||
REDIS_CHECK_DUMP_OBJ= redis-check-dump.o lzf_c.o lzf_d.o crc64.o
|
||||
REDIS_CHECK_AOF_NAME= redis-check-aof
|
||||
REDIS_CHECK_AOF_OBJ= redis-check-aof.o
|
||||
|
||||
all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
@echo ""
|
||||
@@ -200,8 +196,6 @@ distclean: clean
|
||||
test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
@(cd ..; ./runtest)
|
||||
|
||||
check: test
|
||||
|
||||
lcov:
|
||||
$(MAKE) gcov
|
||||
@(set -e; cd ..; ./runtest --clients 1)
|
||||
@@ -232,7 +226,7 @@ src/help.h:
|
||||
@../utils/generate-command-help.rb > help.h
|
||||
|
||||
install: all
|
||||
@mkdir -p $(INSTALL_BIN)
|
||||
mkdir -p $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
|
||||
|
||||
+5
-9
@@ -23,13 +23,13 @@ db.o: db.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
debug.o: debug.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h crc64.h bio.h
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h
|
||||
dict.o: dict.c fmacros.h dict.h zmalloc.h
|
||||
endianconv.o: endianconv.c
|
||||
intset.o: intset.c intset.h zmalloc.h endianconv.h config.h
|
||||
lzf_c.o: lzf_c.c lzfP.h
|
||||
lzf_d.o: lzf_d.c lzfP.h
|
||||
memtest.o: memtest.c config.h
|
||||
memtest.o: memtest.c
|
||||
migrate.o: migrate.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h endianconv.h
|
||||
@@ -40,9 +40,6 @@ networking.o: networking.c redis.h fmacros.h config.h \
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
|
||||
rio.h
|
||||
notify.o: notify.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
object.o: object.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
@@ -58,19 +55,19 @@ rdb.o: rdb.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h \
|
||||
../deps/hiredis/hiredis.h sds.h adlist.h zmalloc.h
|
||||
redis-check-aof.o: redis-check-aof.c fmacros.h config.h
|
||||
redis-check-dump.o: redis-check-dump.c lzf.h crc64.h
|
||||
redis-check-dump.o: redis-check-dump.c lzf.h
|
||||
redis-cli.o: redis-cli.c fmacros.h version.h ../deps/hiredis/hiredis.h \
|
||||
sds.h zmalloc.h ../deps/linenoise/linenoise.h help.h anet.h ae.h
|
||||
redis.o: redis.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h slowlog.h bio.h \
|
||||
asciilogo.h
|
||||
release.o: release.c release.h version.h crc64.h
|
||||
release.o: release.c release.h
|
||||
replication.o: replication.c redis.h fmacros.h config.h \
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
|
||||
rio.h
|
||||
rio.o: rio.c fmacros.h rio.h sds.h util.h crc64.h
|
||||
rio.o: rio.c fmacros.h rio.h sds.h util.h
|
||||
scripting.o: scripting.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h rand.h \
|
||||
@@ -82,7 +79,6 @@ sentinel.o: sentinel.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h \
|
||||
../deps/hiredis/hiredis.h ../deps/hiredis/async.h \
|
||||
../deps/hiredis/hiredis.h
|
||||
setproctitle.o: setproctitle.c
|
||||
sha1.o: sha1.c sha1.h config.h
|
||||
slowlog.o: slowlog.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
|
||||
@@ -91,36 +91,6 @@ err:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Return the current set size. */
|
||||
int aeGetSetSize(aeEventLoop *eventLoop) {
|
||||
return eventLoop->setsize;
|
||||
}
|
||||
|
||||
/* Resize the maximum set size of the event loop.
|
||||
* If the requested set size is smaller than the current set size, but
|
||||
* there is already a file descriptor in use that is >= the requested
|
||||
* set size minus one, AE_ERR is returned and the operation is not
|
||||
* performed at all.
|
||||
*
|
||||
* Otherwise AE_OK is returned and the operation is successful. */
|
||||
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize) {
|
||||
int i;
|
||||
|
||||
if (setsize == eventLoop->setsize) return AE_OK;
|
||||
if (eventLoop->maxfd >= setsize) return AE_ERR;
|
||||
if (aeApiResize(eventLoop,setsize) == -1) return AE_ERR;
|
||||
|
||||
eventLoop->events = zrealloc(eventLoop->events,sizeof(aeFileEvent)*setsize);
|
||||
eventLoop->fired = zrealloc(eventLoop->fired,sizeof(aeFiredEvent)*setsize);
|
||||
eventLoop->setsize = setsize;
|
||||
|
||||
/* Make sure that if we created new slots, they are initialized with
|
||||
* an AE_NONE mask. */
|
||||
for (i = eventLoop->maxfd+1; i < setsize; i++)
|
||||
eventLoop->events[i].mask = AE_NONE;
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
void aeDeleteEventLoop(aeEventLoop *eventLoop) {
|
||||
aeApiFree(eventLoop);
|
||||
zfree(eventLoop->events);
|
||||
|
||||
@@ -114,7 +114,5 @@ int aeWait(int fd, int mask, long long milliseconds);
|
||||
void aeMain(aeEventLoop *eventLoop);
|
||||
char *aeGetApiName(void);
|
||||
void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep);
|
||||
int aeGetSetSize(aeEventLoop *eventLoop);
|
||||
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -55,13 +55,6 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
|
||||
aeApiState *state = eventLoop->apidata;
|
||||
|
||||
state->events = zrealloc(state->events, sizeof(struct epoll_event)*setsize);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void aeApiFree(aeEventLoop *eventLoop) {
|
||||
aeApiState *state = eventLoop->apidata;
|
||||
|
||||
|
||||
@@ -94,11 +94,6 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
|
||||
/* Nothing to resize here. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void aeApiFree(aeEventLoop *eventLoop) {
|
||||
aeApiState *state = eventLoop->apidata;
|
||||
|
||||
|
||||
+1
-7
@@ -54,16 +54,10 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
return -1;
|
||||
}
|
||||
eventLoop->apidata = state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
|
||||
aeApiState *state = eventLoop->apidata;
|
||||
|
||||
state->events = zrealloc(state->events, sizeof(struct kevent)*setsize);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void aeApiFree(aeEventLoop *eventLoop) {
|
||||
aeApiState *state = eventLoop->apidata;
|
||||
|
||||
|
||||
@@ -48,12 +48,6 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
|
||||
/* Just ensure we have enough room in the fd_set type. */
|
||||
if (setsize >= FD_SETSIZE) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void aeApiFree(aeEventLoop *eventLoop) {
|
||||
zfree(eventLoop->apidata);
|
||||
}
|
||||
|
||||
+70
-150
@@ -163,44 +163,27 @@ int anetTcpKeepAlive(char *err, int fd)
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len)
|
||||
int anetResolve(char *err, char *host, char *ipbuf)
|
||||
{
|
||||
struct addrinfo hints, *info;
|
||||
int rv;
|
||||
struct sockaddr_in sa;
|
||||
|
||||
memset(&hints,0,sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM; /* specify socktype to avoid dups */
|
||||
sa.sin_family = AF_INET;
|
||||
if (inet_aton(host, &sa.sin_addr) == 0) {
|
||||
struct hostent *he;
|
||||
|
||||
if ((rv = getaddrinfo(host, NULL, &hints, &info)) != 0) {
|
||||
anetSetError(err, "%s", gai_strerror(rv));
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (info->ai_family == AF_INET) {
|
||||
struct sockaddr_in *sa = (struct sockaddr_in *)info->ai_addr;
|
||||
inet_ntop(AF_INET, &(sa->sin_addr), ipbuf, ipbuf_len);
|
||||
} else {
|
||||
struct sockaddr_in6 *sa = (struct sockaddr_in6 *)info->ai_addr;
|
||||
inet_ntop(AF_INET6, &(sa->sin6_addr), ipbuf, ipbuf_len);
|
||||
}
|
||||
|
||||
freeaddrinfo(info);
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
static int anetSetReuseAddr(char *err, int fd) {
|
||||
int yes = 1;
|
||||
/* Make sure connection-intensive things like the redis benckmark
|
||||
* will be able to close/open sockets a zillion of times */
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_REUSEADDR: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
he = gethostbyname(host);
|
||||
if (he == NULL) {
|
||||
anetSetError(err, "can't resolve: %s", host);
|
||||
return ANET_ERR;
|
||||
}
|
||||
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
|
||||
}
|
||||
strcpy(ipbuf,inet_ntoa(sa.sin_addr));
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
static int anetCreateSocket(char *err, int domain) {
|
||||
int s;
|
||||
int s, on = 1;
|
||||
if ((s = socket(domain, SOCK_STREAM, 0)) == -1) {
|
||||
anetSetError(err, "creating socket: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
@@ -208,8 +191,8 @@ static int anetCreateSocket(char *err, int domain) {
|
||||
|
||||
/* Make sure connection-intensive things like the redis benchmark
|
||||
* will be able to close/open sockets a zillion of times */
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) {
|
||||
close(s);
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_REUSEADDR: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return s;
|
||||
@@ -219,45 +202,38 @@ static int anetCreateSocket(char *err, int domain) {
|
||||
#define ANET_CONNECT_NONBLOCK 1
|
||||
static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
|
||||
{
|
||||
int s, rv;
|
||||
char _port[6]; /* strlen("65535"); */
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
int s;
|
||||
struct sockaddr_in sa;
|
||||
|
||||
snprintf(_port,6,"%d",port);
|
||||
memset(&hints,0,sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
if ((s = anetCreateSocket(err,AF_INET)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
|
||||
anetSetError(err, "%s", gai_strerror(rv));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(port);
|
||||
if (inet_aton(addr, &sa.sin_addr) == 0) {
|
||||
struct hostent *he;
|
||||
|
||||
he = gethostbyname(addr);
|
||||
if (he == NULL) {
|
||||
anetSetError(err, "can't resolve: %s", addr);
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
|
||||
}
|
||||
if (flags & ANET_CONNECT_NONBLOCK) {
|
||||
if (anetNonBlock(err,s) != ANET_OK)
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS &&
|
||||
flags & ANET_CONNECT_NONBLOCK)
|
||||
return s;
|
||||
|
||||
anetSetError(err, "connect: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
|
||||
continue;
|
||||
|
||||
/* if we set err then goto cleanup, otherwise next */
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
|
||||
if (flags & ANET_CONNECT_NONBLOCK && anetNonBlock(err,s) != ANET_OK)
|
||||
goto error;
|
||||
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
|
||||
if (errno == EINPROGRESS && flags & ANET_CONNECT_NONBLOCK) goto end;
|
||||
close(s);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* break with the socket */
|
||||
goto end;
|
||||
}
|
||||
if (p == NULL) {
|
||||
anetSetError(err, "creating socket: %s", strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
s = ANET_ERR;
|
||||
end:
|
||||
freeaddrinfo(servinfo);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -355,63 +331,28 @@ static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) {
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
static int anetV6Only(char *err, int s) {
|
||||
int yes = 1;
|
||||
if (setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,&yes,sizeof(yes)) == -1) {
|
||||
anetSetError(err, "setsockopt: %s", strerror(errno));
|
||||
int anetTcpServer(char *err, int port, char *bindaddr)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_in sa;
|
||||
|
||||
if ((s = anetCreateSocket(err,AF_INET)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(port);
|
||||
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (bindaddr && inet_aton(bindaddr, &sa.sin_addr) == 0) {
|
||||
anetSetError(err, "invalid bind address");
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af)
|
||||
{
|
||||
int s, rv;
|
||||
char _port[6]; /* strlen("65535") */
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
|
||||
snprintf(_port,6,"%d",port);
|
||||
memset(&hints,0,sizeof(hints));
|
||||
hints.ai_family = af;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE; /* No effect if bindaddr != NULL */
|
||||
|
||||
if ((rv = getaddrinfo(bindaddr,_port,&hints,&servinfo)) != 0) {
|
||||
anetSetError(err, "%s", gai_strerror(rv));
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
}
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
|
||||
continue;
|
||||
|
||||
if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen) == ANET_ERR) goto error;
|
||||
goto end;
|
||||
}
|
||||
if (p == NULL) {
|
||||
anetSetError(err, "unable to bind socket");
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
s = ANET_ERR;
|
||||
end:
|
||||
freeaddrinfo(servinfo);
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetTcpServer(char *err, int port, char *bindaddr)
|
||||
{
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET);
|
||||
}
|
||||
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr)
|
||||
{
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET6);
|
||||
}
|
||||
|
||||
int anetUnixServer(char *err, char *path, mode_t perm)
|
||||
{
|
||||
int s;
|
||||
@@ -447,22 +388,15 @@ static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *l
|
||||
return fd;
|
||||
}
|
||||
|
||||
int anetTcpAccept(char *err, int s, char *ip, size_t ip_len, int *port) {
|
||||
int anetTcpAccept(char *err, int s, char *ip, int *port) {
|
||||
int fd;
|
||||
struct sockaddr_storage sa;
|
||||
struct sockaddr_in sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
if (sa.ss_family == AF_INET) {
|
||||
struct sockaddr_in *s = (struct sockaddr_in *)&sa;
|
||||
if (ip) inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len);
|
||||
if (port) *port = ntohs(s->sin_port);
|
||||
} else {
|
||||
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa;
|
||||
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
|
||||
if (port) *port = ntohs(s->sin6_port);
|
||||
}
|
||||
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
|
||||
if (port) *port = ntohs(sa.sin_port);
|
||||
return fd;
|
||||
}
|
||||
|
||||
@@ -476,8 +410,8 @@ int anetUnixAccept(char *err, int s) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) {
|
||||
struct sockaddr_storage sa;
|
||||
int anetPeerToString(int fd, char *ip, int *port) {
|
||||
struct sockaddr_in sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) {
|
||||
@@ -486,20 +420,13 @@ int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) {
|
||||
ip[1] = '\0';
|
||||
return -1;
|
||||
}
|
||||
if (sa.ss_family == AF_INET) {
|
||||
struct sockaddr_in *s = (struct sockaddr_in *)&sa;
|
||||
if (ip) inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len);
|
||||
if (port) *port = ntohs(s->sin_port);
|
||||
} else {
|
||||
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa;
|
||||
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
|
||||
if (port) *port = ntohs(s->sin6_port);
|
||||
}
|
||||
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
|
||||
if (port) *port = ntohs(sa.sin_port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int anetSockName(int fd, char *ip, size_t ip_len, int *port) {
|
||||
struct sockaddr_storage sa;
|
||||
int anetSockName(int fd, char *ip, int *port) {
|
||||
struct sockaddr_in sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
if (getsockname(fd,(struct sockaddr*)&sa,&salen) == -1) {
|
||||
@@ -508,14 +435,7 @@ int anetSockName(int fd, char *ip, size_t ip_len, int *port) {
|
||||
ip[1] = '\0';
|
||||
return -1;
|
||||
}
|
||||
if (sa.ss_family == AF_INET) {
|
||||
struct sockaddr_in *s = (struct sockaddr_in *)&sa;
|
||||
if (ip) inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len);
|
||||
if (port) *port = ntohs(s->sin_port);
|
||||
} else {
|
||||
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa;
|
||||
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
|
||||
if (port) *port = ntohs(s->sin6_port);
|
||||
}
|
||||
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
|
||||
if (port) *port = ntohs(sa.sin_port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
+3
-5
@@ -44,19 +44,17 @@ int anetTcpNonBlockConnect(char *err, char *addr, int port);
|
||||
int anetUnixConnect(char *err, char *path);
|
||||
int anetUnixNonBlockConnect(char *err, char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetResolve(char *err, char *host, char *ipbuf);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr);
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr);
|
||||
int anetUnixServer(char *err, char *path, mode_t perm);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, int *port);
|
||||
int anetUnixAccept(char *err, int serversock);
|
||||
int anetWrite(int fd, char *buf, int count);
|
||||
int anetNonBlock(char *err, int fd);
|
||||
int anetEnableTcpNoDelay(char *err, int fd);
|
||||
int anetDisableTcpNoDelay(char *err, int fd);
|
||||
int anetTcpKeepAlive(char *err, int fd);
|
||||
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port);
|
||||
int anetPeerToString(int fd, char *ip, int *port);
|
||||
int anetKeepAlive(char *err, int fd, int interval);
|
||||
int anetSockName(int fd, char *ip, size_t ip_len, int *port);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -456,7 +456,7 @@ struct redisClient *createFakeClient(void) {
|
||||
c->reply_bytes = 0;
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
c->watched_keys = listCreate();
|
||||
listSetFreeMethod(c->reply,decrRefCountVoid);
|
||||
listSetFreeMethod(c->reply,decrRefCount);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
initClientMultiState(c);
|
||||
return c;
|
||||
@@ -536,7 +536,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
/* Command lookup */
|
||||
cmd = lookupCommand(argv[0]->ptr);
|
||||
if (!cmd) {
|
||||
redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);
|
||||
redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", argv[0]->ptr);
|
||||
exit(1);
|
||||
}
|
||||
/* Run the command in the context of a fake client */
|
||||
@@ -884,9 +884,6 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
|
||||
expiretime = getExpire(db,&key);
|
||||
|
||||
/* If this key is already expired skip it */
|
||||
if (expiretime != -1 && expiretime < now) continue;
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == REDIS_STRING) {
|
||||
/* Emit a SET command */
|
||||
@@ -909,6 +906,8 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
/* Save the expire time */
|
||||
if (expiretime != -1) {
|
||||
char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";
|
||||
/* If this key is already expired skip it */
|
||||
if (expiretime < now) continue;
|
||||
if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;
|
||||
if (rioWriteBulkObject(&aof,&key) == 0) goto werr;
|
||||
if (rioWriteBulkLongLong(&aof,expiretime) == 0) goto werr;
|
||||
@@ -962,15 +961,15 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
char tmpfile[256];
|
||||
|
||||
/* Child */
|
||||
closeListeningSockets(0);
|
||||
redisSetProcTitle("redis-aof-rewrite");
|
||||
if (server.ipfd > 0) close(server.ipfd);
|
||||
if (server.sofd > 0) close(server.sofd);
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||
if (rewriteAppendOnlyFile(tmpfile) == REDIS_OK) {
|
||||
size_t private_dirty = zmalloc_get_private_dirty();
|
||||
|
||||
if (private_dirty) {
|
||||
redisLog(REDIS_NOTICE,
|
||||
"AOF rewrite: %zu MB of memory used by copy-on-write",
|
||||
"AOF rewrite: %lu MB of memory used by copy-on-write",
|
||||
private_dirty/(1024*1024));
|
||||
}
|
||||
exitFromChild(0);
|
||||
@@ -997,7 +996,6 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
* accumulated by the parent into server.aof_rewrite_buf will start
|
||||
* with a SELECT statement and it will be safe to merge. */
|
||||
server.aof_selected_db = -1;
|
||||
replicationScriptCacheFlush();
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_OK; /* unreached */
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
#include "redis.h"
|
||||
#include "bio.h"
|
||||
|
||||
static pthread_t bio_threads[REDIS_BIO_NUM_OPS];
|
||||
static pthread_mutex_t bio_mutex[REDIS_BIO_NUM_OPS];
|
||||
static pthread_cond_t bio_condvar[REDIS_BIO_NUM_OPS];
|
||||
static list *bio_jobs[REDIS_BIO_NUM_OPS];
|
||||
@@ -119,7 +118,6 @@ void bioInit(void) {
|
||||
redisLog(REDIS_WARNING,"Fatal: Can't initialize Background Jobs.");
|
||||
exit(1);
|
||||
}
|
||||
bio_threads[j] = thread;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,11 +140,7 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
unsigned long type = (unsigned long) arg;
|
||||
sigset_t sigset;
|
||||
|
||||
/* Make the thread killable at any time, so that bioKillThreads()
|
||||
* can work reliably. */
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
|
||||
|
||||
pthread_detach(pthread_self());
|
||||
pthread_mutex_lock(&bio_mutex[type]);
|
||||
/* Block SIGALRM so we are sure that only the main thread will
|
||||
* receive the watchdog signal. */
|
||||
@@ -198,23 +192,56 @@ unsigned long long bioPendingJobsOfType(int type) {
|
||||
return val;
|
||||
}
|
||||
|
||||
/* Kill the running bio threads in an unclean way. This function should be
|
||||
* used only when it's critical to stop the threads for some reason.
|
||||
* Currently Redis does this only on crash (for instance on SIGSEGV) in order
|
||||
* to perform a fast memory check without other threads messing with memory. */
|
||||
void bioKillThreads(void) {
|
||||
int err, j;
|
||||
#if 0 /* We don't use the following code for now, and bioWaitPendingJobsLE
|
||||
probably needs a rewrite using conditional variables instead of the
|
||||
current implementation. */
|
||||
|
||||
|
||||
for (j = 0; j < REDIS_BIO_NUM_OPS; j++) {
|
||||
if (pthread_cancel(bio_threads[j]) == 0) {
|
||||
if ((err = pthread_join(bio_threads[j],NULL)) != 0) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Bio thread for job type #%d can be joined: %s",
|
||||
j, strerror(err));
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Bio thread for job type #%d terminated",j);
|
||||
}
|
||||
/* Wait until the number of pending jobs of the specified type are
|
||||
* less or equal to the specified number.
|
||||
*
|
||||
* This function may block for long time, it should only be used to perform
|
||||
* the following tasks:
|
||||
*
|
||||
* 1) To avoid that the main thread is pushing jobs of a given time so fast
|
||||
* that the background thread can't process them at the same speed.
|
||||
* So before creating a new job of a given type the main thread should
|
||||
* call something like: bioWaitPendingJobsLE(job_type,10000);
|
||||
* 2) In order to perform special operations that make it necessary to be sure
|
||||
* no one is touching shared resourced in the background.
|
||||
*/
|
||||
void bioWaitPendingJobsLE(int type, unsigned long long num) {
|
||||
unsigned long long iteration = 0;
|
||||
|
||||
/* We poll the jobs queue aggressively to start, and gradually relax
|
||||
* the polling speed if it is going to take too much time. */
|
||||
while(1) {
|
||||
iteration++;
|
||||
if (iteration > 1000 && iteration <= 10000) {
|
||||
usleep(100);
|
||||
} else if (iteration > 10000) {
|
||||
usleep(1000);
|
||||
}
|
||||
if (bioPendingJobsOfType(type) <= num) break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the older job of the specified type. */
|
||||
time_t bioOlderJobOfType(int type) {
|
||||
time_t time;
|
||||
listNode *ln;
|
||||
struct bio_job *job;
|
||||
|
||||
pthread_mutex_lock(&bio_mutex[type]);
|
||||
ln = listFirst(bio_jobs[type]);
|
||||
if (ln == NULL) {
|
||||
pthread_mutex_unlock(&bio_mutex[type]);
|
||||
return 0;
|
||||
}
|
||||
job = ln->value;
|
||||
time = job->time;
|
||||
pthread_mutex_unlock(&bio_mutex[type]);
|
||||
return time;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -33,7 +33,6 @@ void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3);
|
||||
unsigned long long bioPendingJobsOfType(int type);
|
||||
void bioWaitPendingJobsLE(int type, unsigned long long num);
|
||||
time_t bioOlderJobOfType(int type);
|
||||
void bioKillThreads(void);
|
||||
|
||||
/* Background job opcodes */
|
||||
#define REDIS_BIO_CLOSE_FILE 0 /* Deferred close(2) syscall. */
|
||||
|
||||
+3
-6
@@ -58,8 +58,8 @@ static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
|
||||
/* 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. */
|
||||
size_t redisPopcount(void *s, long count) {
|
||||
size_t bits = 0;
|
||||
long popcount(void *s, long count) {
|
||||
long bits = 0;
|
||||
unsigned char *p;
|
||||
uint32_t *p4 = s;
|
||||
static const unsigned char bitsinbyte[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
|
||||
@@ -153,7 +153,6 @@ void setbitCommand(redisClient *c) {
|
||||
byteval |= ((on & 0x1) << bit);
|
||||
((uint8_t*)o->ptr)[byte] = byteval;
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
addReply(c, bitval ? shared.cone : shared.czero);
|
||||
}
|
||||
@@ -347,11 +346,9 @@ void bitopCommand(redisClient *c) {
|
||||
if (maxlen) {
|
||||
o = createObject(REDIS_STRING,res);
|
||||
setKey(c->db,targetkey,o);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",targetkey,c->db->id);
|
||||
decrRefCount(o);
|
||||
} else if (dbDelete(c->db,targetkey)) {
|
||||
signalModifiedKey(c->db,targetkey);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",targetkey,c->db->id);
|
||||
}
|
||||
server.dirty++;
|
||||
addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */
|
||||
@@ -407,6 +404,6 @@ void bitcountCommand(redisClient *c) {
|
||||
} else {
|
||||
long bytes = end-start+1;
|
||||
|
||||
addReplyLongLong(c,redisPopcount(p+start,bytes));
|
||||
addReplyLongLong(c,popcount(p+start,bytes));
|
||||
}
|
||||
}
|
||||
|
||||
+38
-781
@@ -28,33 +28,9 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#include "redis.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static struct {
|
||||
const char *name;
|
||||
const int value;
|
||||
} validSyslogFacilities[] = {
|
||||
{"user", LOG_USER},
|
||||
{"local0", LOG_LOCAL0},
|
||||
{"local1", LOG_LOCAL1},
|
||||
{"local2", LOG_LOCAL2},
|
||||
{"local3", LOG_LOCAL3},
|
||||
{"local4", LOG_LOCAL4},
|
||||
{"local5", LOG_LOCAL5},
|
||||
{"local6", LOG_LOCAL6},
|
||||
{"local7", LOG_LOCAL7},
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
clientBufferLimitsConfig clientBufferLimitsDefaults[REDIS_CLIENT_LIMIT_NUM_CLASSES] = {
|
||||
{0, 0, 0}, /* normal */
|
||||
{1024*1024*256, 1024*1024*64, 60}, /* slave */
|
||||
{1024*1024*32, 1024*1024*8, 60} /* pubsub */
|
||||
};
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Config file parsing
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -105,7 +81,7 @@ void loadServerConfigFromString(char *config) {
|
||||
/* Skip this line if the resulting command vector is empty. */
|
||||
if (argc == 0) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
sdstolower(argv[0]);
|
||||
|
||||
@@ -125,15 +101,8 @@ void loadServerConfigFromString(char *config) {
|
||||
if (server.port < 0 || server.port > 65535) {
|
||||
err = "Invalid port"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"bind") && argc >= 2) {
|
||||
int j, addresses = argc-1;
|
||||
|
||||
if (addresses > REDIS_BINDADDR_MAX) {
|
||||
err = "Too many bind addresses specified"; goto loaderr;
|
||||
}
|
||||
for (j = 0; j < addresses; j++)
|
||||
server.bindaddr[j] = zstrdup(argv[j+1]);
|
||||
server.bindaddr_count = addresses;
|
||||
} else if (!strcasecmp(argv[0],"bind") && argc == 2) {
|
||||
server.bindaddr = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"unixsocket") && argc == 2) {
|
||||
server.unixsocket = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"unixsocketperm") && argc == 2) {
|
||||
@@ -171,9 +140,12 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if (!strcasecmp(argv[0],"logfile") && argc == 2) {
|
||||
FILE *logfp;
|
||||
|
||||
zfree(server.logfile);
|
||||
server.logfile = zstrdup(argv[1]);
|
||||
if (server.logfile[0] != '\0') {
|
||||
if (!strcasecmp(server.logfile,"stdout")) {
|
||||
zfree(server.logfile);
|
||||
server.logfile = NULL;
|
||||
}
|
||||
if (server.logfile) {
|
||||
/* Test if we are able to open the file. The server will not
|
||||
* be able to abort just for this problem later... */
|
||||
logfp = fopen(server.logfile,"a");
|
||||
@@ -192,6 +164,21 @@ void loadServerConfigFromString(char *config) {
|
||||
if (server.syslog_ident) zfree(server.syslog_ident);
|
||||
server.syslog_ident = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"syslog-facility") && argc == 2) {
|
||||
struct {
|
||||
const char *name;
|
||||
const int value;
|
||||
} validSyslogFacilities[] = {
|
||||
{"user", LOG_USER},
|
||||
{"local0", LOG_LOCAL0},
|
||||
{"local1", LOG_LOCAL1},
|
||||
{"local2", LOG_LOCAL2},
|
||||
{"local3", LOG_LOCAL3},
|
||||
{"local4", LOG_LOCAL4},
|
||||
{"local5", LOG_LOCAL5},
|
||||
{"local6", LOG_LOCAL6},
|
||||
{"local7", LOG_LOCAL7},
|
||||
{NULL, 0}
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; validSyslogFacilities[i].name; i++) {
|
||||
@@ -262,19 +249,6 @@ void loadServerConfigFromString(char *config) {
|
||||
if ((server.repl_disable_tcp_nodelay = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"repl-backlog-size") && argc == 2) {
|
||||
long long size = memtoll(argv[1],NULL);
|
||||
if (size <= 0) {
|
||||
err = "repl-backlog-size must be 1 or greater.";
|
||||
goto loaderr;
|
||||
}
|
||||
resizeReplicationBacklog(size);
|
||||
} else if (!strcasecmp(argv[0],"repl-backlog-ttl") && argc == 2) {
|
||||
server.repl_backlog_time_limit = atoi(argv[1]);
|
||||
if (server.repl_backlog_time_limit < 0) {
|
||||
err = "repl-backlog-ttl can't be negative ";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
|
||||
server.masterauth = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) {
|
||||
@@ -313,10 +287,6 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
server.aof_state = yes ? REDIS_AOF_ON : REDIS_AOF_OFF;
|
||||
} else if (!strcasecmp(argv[0],"appendfilename") && argc == 2) {
|
||||
if (!pathIsBaseName(argv[1])) {
|
||||
err = "appendfilename can't be a path, just a filename";
|
||||
goto loaderr;
|
||||
}
|
||||
zfree(server.aof_filename);
|
||||
server.aof_filename = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"no-appendfsync-on-rewrite")
|
||||
@@ -363,10 +333,6 @@ void loadServerConfigFromString(char *config) {
|
||||
zfree(server.pidfile);
|
||||
server.pidfile = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"dbfilename") && argc == 2) {
|
||||
if (!pathIsBaseName(argv[1])) {
|
||||
err = "dbfilename can't be a path, just a filename";
|
||||
goto loaderr;
|
||||
}
|
||||
zfree(server.rdb_filename);
|
||||
server.rdb_filename = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"hash-max-ziplist-entries") && argc == 2) {
|
||||
@@ -443,24 +409,6 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"slave-priority") && argc == 2) {
|
||||
server.slave_priority = atoi(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"min-slaves-to-write") && argc == 2) {
|
||||
server.repl_min_slaves_to_write = atoi(argv[1]);
|
||||
if (server.repl_min_slaves_to_write < 0) {
|
||||
err = "Invalid value for min-slaves-to-write."; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"min-slaves-max-lag") && argc == 2) {
|
||||
server.repl_min_slaves_max_lag = atoi(argv[1]);
|
||||
if (server.repl_min_slaves_max_lag < 0) {
|
||||
err = "Invalid value for min-slaves-max-lag."; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) {
|
||||
int flags = keyspaceEventsStringToFlags(argv[1]);
|
||||
|
||||
if (flags == -1) {
|
||||
err = "Invalid event class character. Use 'g$lshzxeA'.";
|
||||
goto loaderr;
|
||||
}
|
||||
server.notify_keyspace_events = flags;
|
||||
} else if (!strcasecmp(argv[0],"sentinel")) {
|
||||
/* argc == 1 is handled by main() as we need to enter the sentinel
|
||||
* mode ASAP. */
|
||||
@@ -526,7 +474,7 @@ void loadServerConfig(char *filename, char *options) {
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* CONFIG SET implementation
|
||||
* CONFIG command for remote configuration
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void configSetCommand(redisClient *c) {
|
||||
@@ -537,10 +485,6 @@ void configSetCommand(redisClient *c) {
|
||||
o = c->argv[3];
|
||||
|
||||
if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) {
|
||||
if (!pathIsBaseName(o->ptr)) {
|
||||
addReplyError(c, "dbfilename can't be a path, just a filename");
|
||||
return;
|
||||
}
|
||||
zfree(server.rdb_filename);
|
||||
server.rdb_filename = zstrdup(o->ptr);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"requirepass")) {
|
||||
@@ -549,7 +493,7 @@ void configSetCommand(redisClient *c) {
|
||||
server.requirepass = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"masterauth")) {
|
||||
zfree(server.masterauth);
|
||||
server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
|
||||
server.masterauth = zstrdup(o->ptr);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
@@ -560,35 +504,10 @@ void configSetCommand(redisClient *c) {
|
||||
}
|
||||
freeMemoryIfNeeded();
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxclients")) {
|
||||
int orig_value = server.maxclients;
|
||||
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
|
||||
/* Try to check if the OS is capable of supporting so many FDs. */
|
||||
server.maxclients = ll;
|
||||
if (ll > orig_value) {
|
||||
adjustOpenFilesLimit();
|
||||
if (server.maxclients != ll) {
|
||||
addReplyErrorFormat(c,"The operating system is not able to handle the specified number of clients, try with %d", server.maxclients);
|
||||
server.maxclients = orig_value;
|
||||
return;
|
||||
}
|
||||
if (aeGetSetSize(server.el) <
|
||||
server.maxclients + REDIS_EVENTLOOP_FDSET_INCR)
|
||||
{
|
||||
if (aeResizeSetSize(server.el,
|
||||
server.maxclients + REDIS_EVENTLOOP_FDSET_INCR) == AE_ERR)
|
||||
{
|
||||
addReplyError(c,"The event loop API used by Redis is not able to handle the specified number of clients");
|
||||
server.maxclients = orig_value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"hz")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.hz = ll;
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.hz = (int) ll;
|
||||
if (server.hz < REDIS_MIN_HZ) server.hz = REDIS_MIN_HZ;
|
||||
if (server.hz > REDIS_MAX_HZ) server.hz = REDIS_MAX_HZ;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-policy")) {
|
||||
@@ -806,12 +725,6 @@ void configSetCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"repl-timeout")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
|
||||
server.repl_timeout = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-size")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
|
||||
resizeReplicationBacklog(ll);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-ttl")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.repl_backlog_time_limit = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"watchdog-period")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
if (ll)
|
||||
@@ -823,11 +736,11 @@ void configSetCommand(redisClient *c) {
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.rdb_compression = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"notify-keyspace-events")) {
|
||||
int flags = keyspaceEventsStringToFlags(o->ptr);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"rdbchecksum")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (flags == -1) goto badfmt;
|
||||
server.notify_keyspace_events = flags;
|
||||
if (yn == -1) goto badfmt;
|
||||
server.rdb_checksum = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"repl-disable-tcp-nodelay")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
@@ -835,18 +748,8 @@ void configSetCommand(redisClient *c) {
|
||||
server.repl_disable_tcp_nodelay = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"slave-priority")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
ll <= 0) goto badfmt;
|
||||
server.slave_priority = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"min-slaves-to-write")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.repl_min_slaves_to_write = ll;
|
||||
refreshGoodSlavesCount();
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"min-slaves-max-lag")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.repl_min_slaves_max_lag = ll;
|
||||
refreshGoodSlavesCount();
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||
(char*)c->argv[2]->ptr);
|
||||
@@ -861,10 +764,6 @@ badfmt: /* Bad format errors */
|
||||
(char*)c->argv[2]->ptr);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* CONFIG GET implementation
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#define config_get_string_field(_name,_var) do { \
|
||||
if (stringmatch(pattern,_name,0)) { \
|
||||
addReplyBulkCString(c,_name); \
|
||||
@@ -902,6 +801,7 @@ void configGetCommand(redisClient *c) {
|
||||
config_get_string_field("dbfilename",server.rdb_filename);
|
||||
config_get_string_field("requirepass",server.requirepass);
|
||||
config_get_string_field("masterauth",server.masterauth);
|
||||
config_get_string_field("bind",server.bindaddr);
|
||||
config_get_string_field("unixsocket",server.unixsocket);
|
||||
config_get_string_field("logfile",server.logfile);
|
||||
config_get_string_field("pidfile",server.pidfile);
|
||||
@@ -938,13 +838,9 @@ void configGetCommand(redisClient *c) {
|
||||
config_get_numerical_field("databases",server.dbnum);
|
||||
config_get_numerical_field("repl-ping-slave-period",server.repl_ping_slave_period);
|
||||
config_get_numerical_field("repl-timeout",server.repl_timeout);
|
||||
config_get_numerical_field("repl-backlog-size",server.repl_backlog_size);
|
||||
config_get_numerical_field("repl-backlog-ttl",server.repl_backlog_time_limit);
|
||||
config_get_numerical_field("maxclients",server.maxclients);
|
||||
config_get_numerical_field("watchdog-period",server.watchdog_period);
|
||||
config_get_numerical_field("slave-priority",server.slave_priority);
|
||||
config_get_numerical_field("min-slaves-to-write",server.repl_min_slaves_to_write);
|
||||
config_get_numerical_field("min-slaves-max-lag",server.repl_min_slaves_max_lag);
|
||||
config_get_numerical_field("hz",server.hz);
|
||||
|
||||
/* Bool (yes/no) values */
|
||||
@@ -1016,8 +912,8 @@ void configGetCommand(redisClient *c) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
buf = sdscatprintf(buf,"%jd %d",
|
||||
(intmax_t)server.saveparams[j].seconds,
|
||||
buf = sdscatprintf(buf,"%ld %d",
|
||||
server.saveparams[j].seconds,
|
||||
server.saveparams[j].changes);
|
||||
if (j != server.saveparamslen-1)
|
||||
buf = sdscatlen(buf," ",1);
|
||||
@@ -1078,637 +974,9 @@ void configGetCommand(redisClient *c) {
|
||||
addReplyBulkCString(c,buf);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"notify-keyspace-events",0)) {
|
||||
robj *flagsobj = createObject(REDIS_STRING,
|
||||
keyspaceEventsFlagsToString(server.notify_keyspace_events));
|
||||
|
||||
addReplyBulkCString(c,"notify-keyspace-events");
|
||||
addReplyBulk(c,flagsobj);
|
||||
decrRefCount(flagsobj);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"bind",0)) {
|
||||
sds aux = sdsjoin(server.bindaddr,server.bindaddr_count," ");
|
||||
|
||||
addReplyBulkCString(c,"bind");
|
||||
addReplyBulkCString(c,aux);
|
||||
sdsfree(aux);
|
||||
matches++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,replylen,matches*2);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* CONFIG REWRITE implementation
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#define REDIS_CONFIG_REWRITE_SIGNATURE "# Generated by CONFIG REWRITE"
|
||||
|
||||
/* We use the following dictionary type to store where a configuration
|
||||
* option is mentioned in the old configuration file, so it's
|
||||
* like "maxmemory" -> list of line numbers (first line is zero). */
|
||||
unsigned int dictSdsHash(const void *key);
|
||||
int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2);
|
||||
void dictSdsDestructor(void *privdata, void *val);
|
||||
void dictListDestructor(void *privdata, void *val);
|
||||
|
||||
/* Sentinel config rewriting is implemented inside sentinel.c by
|
||||
* rewriteConfigSentinelOption(). */
|
||||
void rewriteConfigSentinelOption(struct rewriteConfigState *state);
|
||||
|
||||
dictType optionToLineDictType = {
|
||||
dictSdsHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCompare, /* key compare */
|
||||
dictSdsDestructor, /* key destructor */
|
||||
dictListDestructor /* val destructor */
|
||||
};
|
||||
|
||||
/* The config rewrite state. */
|
||||
struct rewriteConfigState {
|
||||
dict *option_to_line; /* Option -> list of config file lines map */
|
||||
int numlines; /* Number of lines in current config */
|
||||
sds *lines; /* Current lines as an array of sds strings */
|
||||
int has_tail; /* True if we already added directives that were
|
||||
not present in the original config file. */
|
||||
};
|
||||
|
||||
/* Append the new line to the current configuration state. */
|
||||
void rewriteConfigAppendLine(struct rewriteConfigState *state, sds line) {
|
||||
state->lines = zrealloc(state->lines, sizeof(char*) * (state->numlines+1));
|
||||
state->lines[state->numlines++] = line;
|
||||
}
|
||||
|
||||
/* Populate the option -> list of line numbers map. */
|
||||
void rewriteConfigAddLineNumberToOption(struct rewriteConfigState *state, sds option, int linenum) {
|
||||
list *l = dictFetchValue(state->option_to_line,option);
|
||||
|
||||
if (l == NULL) {
|
||||
l = listCreate();
|
||||
dictAdd(state->option_to_line,sdsdup(option),l);
|
||||
}
|
||||
listAddNodeTail(l,(void*)(long)linenum);
|
||||
}
|
||||
|
||||
/* Read the old file, split it into lines to populate a newly created
|
||||
* config rewrite state, and return it to the caller.
|
||||
*
|
||||
* If it is impossible to read the old file, NULL is returned.
|
||||
* If the old file does not exist at all, an empty state is returned. */
|
||||
struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
|
||||
FILE *fp = fopen(path,"r");
|
||||
struct rewriteConfigState *state = zmalloc(sizeof(*state));
|
||||
char buf[REDIS_CONFIGLINE_MAX+1];
|
||||
int linenum = -1;
|
||||
|
||||
if (fp == NULL && errno != ENOENT) return NULL;
|
||||
|
||||
state->option_to_line = dictCreate(&optionToLineDictType,NULL);
|
||||
state->numlines = 0;
|
||||
state->lines = NULL;
|
||||
state->has_tail = 0;
|
||||
if (fp == NULL) return state;
|
||||
|
||||
/* Read the old file line by line, populate the state. */
|
||||
while(fgets(buf,REDIS_CONFIGLINE_MAX+1,fp) != NULL) {
|
||||
int argc;
|
||||
sds *argv;
|
||||
sds line = sdstrim(sdsnew(buf),"\r\n\t ");
|
||||
|
||||
linenum++; /* Zero based, so we init at -1 */
|
||||
|
||||
/* Handle comments and empty lines. */
|
||||
if (line[0] == '#' || line[0] == '\0') {
|
||||
if (!state->has_tail && !strcmp(line,REDIS_CONFIG_REWRITE_SIGNATURE))
|
||||
state->has_tail = 1;
|
||||
rewriteConfigAppendLine(state,line);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Not a comment, split into arguments. */
|
||||
argv = sdssplitargs(line,&argc);
|
||||
if (argv == NULL) {
|
||||
/* Apparently the line is unparsable for some reason, for
|
||||
* instance it may have unbalanced quotes. Load it as a
|
||||
* comment. */
|
||||
sds aux = sdsnew("# ??? ");
|
||||
aux = sdscatsds(aux,line);
|
||||
sdsfree(line);
|
||||
rewriteConfigAppendLine(state,aux);
|
||||
continue;
|
||||
}
|
||||
|
||||
sdstolower(argv[0]); /* We only want lowercase config directives. */
|
||||
|
||||
/* Now we populate the state according to the content of this line.
|
||||
* Append the line and populate the option -> line numbers map. */
|
||||
rewriteConfigAppendLine(state,line);
|
||||
rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
|
||||
|
||||
sdsfreesplitres(argv,argc);
|
||||
}
|
||||
fclose(fp);
|
||||
return state;
|
||||
}
|
||||
|
||||
/* Rewrite the specified configuration option with the new "line".
|
||||
* It progressively uses lines of the file that were already used for the same
|
||||
* configuration option in the old version of the file, removing that line from
|
||||
* the map of options -> line numbers.
|
||||
*
|
||||
* If there are lines associated with a given configuration option and
|
||||
* "force" is non-zero, the line is appended to the configuration file.
|
||||
* Usually "force" is true when an option has not its default value, so it
|
||||
* must be rewritten even if not present previously.
|
||||
*
|
||||
* The first time a line is appended into a configuration file, a comment
|
||||
* is added to show that starting from that point the config file was generated
|
||||
* by CONFIG REWRITE.
|
||||
*
|
||||
* "line" is either used, or freed, so the caller does not need to free it
|
||||
* in any way. */
|
||||
void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sds line, int force) {
|
||||
sds o = sdsnew(option);
|
||||
list *l = dictFetchValue(state->option_to_line,o);
|
||||
|
||||
if (!l && !force) {
|
||||
/* Option not used previously, and we are not forced to use it. */
|
||||
sdsfree(line);
|
||||
sdsfree(o);
|
||||
return;
|
||||
}
|
||||
|
||||
if (l) {
|
||||
listNode *ln = listFirst(l);
|
||||
int linenum = (long) ln->value;
|
||||
|
||||
/* There are still lines in the old configuration file we can reuse
|
||||
* for this option. Replace the line with the new one. */
|
||||
listDelNode(l,ln);
|
||||
if (listLength(l) == 0) dictDelete(state->option_to_line,o);
|
||||
sdsfree(state->lines[linenum]);
|
||||
state->lines[linenum] = line;
|
||||
} else {
|
||||
/* Append a new line. */
|
||||
if (!state->has_tail) {
|
||||
rewriteConfigAppendLine(state,
|
||||
sdsnew(REDIS_CONFIG_REWRITE_SIGNATURE));
|
||||
state->has_tail = 1;
|
||||
}
|
||||
rewriteConfigAppendLine(state,line);
|
||||
}
|
||||
sdsfree(o);
|
||||
}
|
||||
|
||||
/* Write the long long 'bytes' value as a string in a way that is parsable
|
||||
* inside redis.conf. If possible uses the GB, MB, KB notation. */
|
||||
int rewriteConfigFormatMemory(char *buf, size_t len, long long bytes) {
|
||||
int gb = 1024*1024*1024;
|
||||
int mb = 1024*1024;
|
||||
int kb = 1024;
|
||||
|
||||
if (bytes && (bytes % gb) == 0) {
|
||||
return snprintf(buf,len,"%lldgb",bytes/gb);
|
||||
} else if (bytes && (bytes % mb) == 0) {
|
||||
return snprintf(buf,len,"%lldmb",bytes/mb);
|
||||
} else if (bytes && (bytes % kb) == 0) {
|
||||
return snprintf(buf,len,"%lldkb",bytes/kb);
|
||||
} else {
|
||||
return snprintf(buf,len,"%lld",bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/* Rewrite a simple "option-name <bytes>" configuration option. */
|
||||
void rewriteConfigBytesOption(struct rewriteConfigState *state, char *option, long long value, long long defvalue) {
|
||||
char buf[64];
|
||||
int force = value != defvalue;
|
||||
sds line;
|
||||
|
||||
rewriteConfigFormatMemory(buf,sizeof(buf),value);
|
||||
line = sdscatprintf(sdsempty(),"%s %s",option,buf);
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
|
||||
}
|
||||
|
||||
/* Rewrite a yes/no option. */
|
||||
void rewriteConfigYesNoOption(struct rewriteConfigState *state, char *option, int value, int defvalue) {
|
||||
int force = value != defvalue;
|
||||
sds line = sdscatprintf(sdsempty(),"%s %s",option,
|
||||
value ? "yes" : "no");
|
||||
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite a string option. */
|
||||
void rewriteConfigStringOption(struct rewriteConfigState *state, char *option, char *value, char *defvalue) {
|
||||
int force = 1;
|
||||
sds line;
|
||||
|
||||
/* String options set to NULL need to be not present at all in the
|
||||
* configuration file to be set to NULL again at the next reboot. */
|
||||
if (value == NULL) return;
|
||||
|
||||
/* Compare the strings as sds strings to have a binary safe comparison. */
|
||||
if (defvalue && strcmp(value,defvalue) == 0) force = 0;
|
||||
|
||||
line = sdsnew(option);
|
||||
line = sdscatlen(line, " ", 1);
|
||||
line = sdscatrepr(line, value, strlen(value));
|
||||
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite a numerical (long long range) option. */
|
||||
void rewriteConfigNumericalOption(struct rewriteConfigState *state, char *option, long long value, long long defvalue) {
|
||||
int force = value != defvalue;
|
||||
sds line = sdscatprintf(sdsempty(),"%s %lld",option,value);
|
||||
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite a octal option. */
|
||||
void rewriteConfigOctalOption(struct rewriteConfigState *state, char *option, int value, int defvalue) {
|
||||
int force = value != defvalue;
|
||||
sds line = sdscatprintf(sdsempty(),"%s %o",option,value);
|
||||
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite an enumeration option, after the "value" every enum/value pair
|
||||
* is specified, terminated by NULL. After NULL the default value is
|
||||
* specified. See how the function is used for more information. */
|
||||
void rewriteConfigEnumOption(struct rewriteConfigState *state, char *option, int value, ...) {
|
||||
va_list ap;
|
||||
char *enum_name, *matching_name = NULL;
|
||||
int enum_val, def_val, force;
|
||||
sds line;
|
||||
|
||||
va_start(ap, value);
|
||||
while(1) {
|
||||
enum_name = va_arg(ap,char*);
|
||||
enum_val = va_arg(ap,int);
|
||||
if (enum_name == NULL) {
|
||||
def_val = enum_val;
|
||||
break;
|
||||
}
|
||||
if (value == enum_val) matching_name = enum_name;
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
force = value != def_val;
|
||||
line = sdscatprintf(sdsempty(),"%s %s",option,matching_name);
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite the syslog-fability option. */
|
||||
void rewriteConfigSyslogfacilityOption(struct rewriteConfigState *state) {
|
||||
int value = server.syslog_facility, j;
|
||||
int force = value != LOG_LOCAL0;
|
||||
char *name = NULL, *option = "syslog-facility";
|
||||
sds line;
|
||||
|
||||
for (j = 0; validSyslogFacilities[j].name; j++) {
|
||||
if (validSyslogFacilities[j].value == value) {
|
||||
name = (char*) validSyslogFacilities[j].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
line = sdscatprintf(sdsempty(),"%s %s",option,name);
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite the save option. */
|
||||
void rewriteConfigSaveOption(struct rewriteConfigState *state) {
|
||||
int j;
|
||||
sds line;
|
||||
|
||||
/* Note that if there are no save parameters at all, all the current
|
||||
* config line with "save" will be detected as orphaned and deleted,
|
||||
* resulting into no RDB persistence as expected. */
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
line = sdscatprintf(sdsempty(),"save %ld %d",
|
||||
server.saveparams[j].seconds, server.saveparams[j].changes);
|
||||
rewriteConfigRewriteLine(state,"save",line,1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Rewrite the dir option, always using absolute paths.*/
|
||||
void rewriteConfigDirOption(struct rewriteConfigState *state) {
|
||||
char cwd[1024];
|
||||
|
||||
if (getcwd(cwd,sizeof(cwd)) == NULL) return; /* no rewrite on error. */
|
||||
rewriteConfigStringOption(state,"dir",cwd,NULL);
|
||||
}
|
||||
|
||||
/* Rewrite the slaveof option. */
|
||||
void rewriteConfigSlaveofOption(struct rewriteConfigState *state) {
|
||||
char *option = "slaveof";
|
||||
sds line;
|
||||
|
||||
/* If this is a master, we want all the slaveof config options
|
||||
* in the file to be removed. */
|
||||
if (server.masterhost == NULL) return;
|
||||
line = sdscatprintf(sdsempty(),"%s %s %d", option,
|
||||
server.masterhost, server.masterport);
|
||||
rewriteConfigRewriteLine(state,option,line,1);
|
||||
}
|
||||
|
||||
/* Rewrite the appendonly option. */
|
||||
void rewriteConfigAppendonlyOption(struct rewriteConfigState *state) {
|
||||
int force = server.aof_state != REDIS_AOF_OFF;
|
||||
char *option = "appendonly";
|
||||
sds line;
|
||||
|
||||
line = sdscatprintf(sdsempty(),"%s %s", option,
|
||||
(server.aof_state == REDIS_AOF_OFF) ? "no" : "yes");
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite the notify-keyspace-events option. */
|
||||
void rewriteConfigNotifykeyspaceeventsOption(struct rewriteConfigState *state) {
|
||||
int force = server.notify_keyspace_events != 0;
|
||||
char *option = "notify-keyspace-events";
|
||||
sds line, flags;
|
||||
|
||||
flags = keyspaceEventsFlagsToString(server.notify_keyspace_events);
|
||||
line = sdsnew(option);
|
||||
line = sdscatlen(line, " ", 1);
|
||||
line = sdscatrepr(line, flags, sdslen(flags));
|
||||
sdsfree(flags);
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite the client-output-buffer-limit option. */
|
||||
void rewriteConfigClientoutputbufferlimitOption(struct rewriteConfigState *state) {
|
||||
int j;
|
||||
char *option = "client-output-buffer-limit";
|
||||
|
||||
for (j = 0; j < REDIS_CLIENT_LIMIT_NUM_CLASSES; j++) {
|
||||
int force = (server.client_obuf_limits[j].hard_limit_bytes !=
|
||||
clientBufferLimitsDefaults[j].hard_limit_bytes) ||
|
||||
(server.client_obuf_limits[j].soft_limit_bytes !=
|
||||
clientBufferLimitsDefaults[j].soft_limit_bytes) ||
|
||||
(server.client_obuf_limits[j].soft_limit_seconds !=
|
||||
clientBufferLimitsDefaults[j].soft_limit_seconds);
|
||||
sds line;
|
||||
char hard[64], soft[64];
|
||||
|
||||
rewriteConfigFormatMemory(hard,sizeof(hard),
|
||||
server.client_obuf_limits[j].hard_limit_bytes);
|
||||
rewriteConfigFormatMemory(soft,sizeof(soft),
|
||||
server.client_obuf_limits[j].soft_limit_bytes);
|
||||
|
||||
line = sdscatprintf(sdsempty(),"%s %s %s %s %ld",
|
||||
option, getClientLimitClassName(j), hard, soft,
|
||||
(long) server.client_obuf_limits[j].soft_limit_seconds);
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
}
|
||||
|
||||
/* Rewrite the bind option. */
|
||||
void rewriteConfigBindOption(struct rewriteConfigState *state) {
|
||||
int force = 1;
|
||||
sds line, addresses;
|
||||
char *option = "bind";
|
||||
|
||||
/* Nothing to rewrite if we don't have bind addresses. */
|
||||
if (server.bindaddr_count == 0) return;
|
||||
|
||||
/* Rewrite as bind <addr1> <addr2> ... <addrN> */
|
||||
addresses = sdsjoin(server.bindaddr,server.bindaddr_count," ");
|
||||
line = sdsnew(option);
|
||||
line = sdscatlen(line, " ", 1);
|
||||
line = sdscatsds(line, addresses);
|
||||
sdsfree(addresses);
|
||||
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Glue together the configuration lines in the current configuration
|
||||
* rewrite state into a single string, stripping multiple empty lines. */
|
||||
sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
|
||||
sds content = sdsempty();
|
||||
int j, was_empty = 0;
|
||||
|
||||
for (j = 0; j < state->numlines; j++) {
|
||||
/* Every cluster of empty lines is turned into a single empty line. */
|
||||
if (sdslen(state->lines[j]) == 0) {
|
||||
if (was_empty) continue;
|
||||
was_empty = 1;
|
||||
} else {
|
||||
was_empty = 0;
|
||||
}
|
||||
content = sdscatsds(content,state->lines[j]);
|
||||
content = sdscatlen(content,"\n",1);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/* Free the configuration rewrite state. */
|
||||
void rewriteConfigReleaseState(struct rewriteConfigState *state) {
|
||||
sdsfreesplitres(state->lines,state->numlines);
|
||||
dictRelease(state->option_to_line);
|
||||
zfree(state);
|
||||
}
|
||||
|
||||
/* At the end of the rewrite process the state contains the remaining
|
||||
* map between "option name" => "lines in the original config file".
|
||||
* Lines used by the rewrite process were removed by the function
|
||||
* rewriteConfigRewriteLine(), all the other lines are "orphaned" and
|
||||
* should be replaced by empty lines.
|
||||
*
|
||||
* This function does just this, iterating all the option names and
|
||||
* blanking all the lines still associated. */
|
||||
void rewriteConfigRemoveOrphaned(struct rewriteConfigState *state) {
|
||||
dictIterator *di = dictGetIterator(state->option_to_line);
|
||||
dictEntry *de;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
list *l = dictGetVal(de);
|
||||
|
||||
while(listLength(l)) {
|
||||
listNode *ln = listFirst(l);
|
||||
int linenum = (long) ln->value;
|
||||
|
||||
sdsfree(state->lines[linenum]);
|
||||
state->lines[linenum] = sdsempty();
|
||||
listDelNode(l,ln);
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* This function overwrites the old configuration file with the new content.
|
||||
*
|
||||
* 1) The old file length is obtained.
|
||||
* 2) If the new content is smaller, padding is added.
|
||||
* 3) A single write(2) call is used to replace the content of the file.
|
||||
* 4) Later the file is truncated to the length of the new content.
|
||||
*
|
||||
* This way we are sure the file is left in a consistent state even if the
|
||||
* process is stopped between any of the four operations.
|
||||
*
|
||||
* The function returns 0 on success, otherwise -1 is returned and errno
|
||||
* set accordingly. */
|
||||
int rewriteConfigOverwriteFile(char *configfile, sds content) {
|
||||
int retval = 0;
|
||||
int fd = open(configfile,O_RDWR|O_CREAT,0644);
|
||||
int content_size = sdslen(content), padding = 0;
|
||||
struct stat sb;
|
||||
sds content_padded;
|
||||
|
||||
/* 1) Open the old file (or create a new one if it does not
|
||||
* exist), get the size. */
|
||||
if (fd == -1) return -1; /* errno set by open(). */
|
||||
if (fstat(fd,&sb) == -1) {
|
||||
close(fd);
|
||||
return -1; /* errno set by fstat(). */
|
||||
}
|
||||
|
||||
/* 2) Pad the content at least match the old file size. */
|
||||
content_padded = sdsdup(content);
|
||||
if (content_size < sb.st_size) {
|
||||
/* If the old file was bigger, pad the content with
|
||||
* a newline plus as many "#" chars as required. */
|
||||
padding = sb.st_size - content_size;
|
||||
content_padded = sdsgrowzero(content_padded,sb.st_size);
|
||||
content_padded[content_size] = '\n';
|
||||
memset(content_padded+content_size+1,'#',padding-1);
|
||||
}
|
||||
|
||||
/* 3) Write the new content using a single write(2). */
|
||||
if (write(fd,content_padded,strlen(content_padded)) == -1) {
|
||||
retval = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* 4) Truncate the file to the right length if we used padding. */
|
||||
if (padding) {
|
||||
if (ftruncate(fd,content_size) == -1) {
|
||||
/* Non critical error... */
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
sdsfree(content_padded);
|
||||
close(fd);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Rewrite the configuration file at "path".
|
||||
* If the configuration file already exists, we try at best to retain comments
|
||||
* and overall structure.
|
||||
*
|
||||
* Configuration parameters that are at their default value, unless already
|
||||
* explicitly included in the old configuration file, are not rewritten.
|
||||
*
|
||||
* On error -1 is returned and errno is set accordingly, otherwise 0. */
|
||||
int rewriteConfig(char *path) {
|
||||
struct rewriteConfigState *state;
|
||||
sds newcontent;
|
||||
int retval;
|
||||
|
||||
/* Step 1: read the old config into our rewrite state. */
|
||||
if ((state = rewriteConfigReadOldFile(path)) == NULL) return -1;
|
||||
|
||||
/* Step 2: rewrite every single option, replacing or appending it inside
|
||||
* the rewrite state. */
|
||||
|
||||
/* TODO: Turn every default into a define, use it also in
|
||||
* initServerConfig(). */
|
||||
rewriteConfigYesNoOption(state,"daemonize",server.daemonize,0);
|
||||
rewriteConfigStringOption(state,"pidfile",server.pidfile,REDIS_DEFAULT_PID_FILE);
|
||||
rewriteConfigNumericalOption(state,"port",server.port,REDIS_SERVERPORT);
|
||||
rewriteConfigBindOption(state);
|
||||
rewriteConfigStringOption(state,"unixsocket",server.unixsocket,NULL);
|
||||
rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,REDIS_DEFAULT_UNIX_SOCKET_PERM);
|
||||
rewriteConfigNumericalOption(state,"timeout",server.maxidletime,REDIS_MAXIDLETIME);
|
||||
rewriteConfigNumericalOption(state,"tcp-keepalive",server.tcpkeepalive,REDIS_DEFAULT_TCP_KEEPALIVE);
|
||||
rewriteConfigEnumOption(state,"loglevel",server.verbosity,
|
||||
"debug", REDIS_DEBUG,
|
||||
"verbose", REDIS_VERBOSE,
|
||||
"notice", REDIS_NOTICE,
|
||||
"warning", REDIS_WARNING,
|
||||
NULL, REDIS_DEFAULT_VERBOSITY);
|
||||
rewriteConfigStringOption(state,"logfile",server.logfile,REDIS_DEFAULT_LOGFILE);
|
||||
rewriteConfigYesNoOption(state,"syslog-enabled",server.syslog_enabled,REDIS_DEFAULT_SYSLOG_ENABLED);
|
||||
rewriteConfigStringOption(state,"syslog-ident",server.syslog_ident,REDIS_DEFAULT_SYSLOG_IDENT);
|
||||
rewriteConfigSyslogfacilityOption(state);
|
||||
rewriteConfigSaveOption(state);
|
||||
rewriteConfigNumericalOption(state,"databases",server.dbnum,REDIS_DEFAULT_DBNUM);
|
||||
rewriteConfigYesNoOption(state,"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err,REDIS_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR);
|
||||
rewriteConfigYesNoOption(state,"rdbcompression",server.rdb_compression,REDIS_DEFAULT_RDB_COMPRESSION);
|
||||
rewriteConfigYesNoOption(state,"rdbchecksum",server.rdb_checksum,REDIS_DEFAULT_RDB_CHECKSUM);
|
||||
rewriteConfigStringOption(state,"dbfilename",server.rdb_filename,REDIS_DEFAULT_RDB_FILENAME);
|
||||
rewriteConfigDirOption(state);
|
||||
rewriteConfigSlaveofOption(state);
|
||||
rewriteConfigStringOption(state,"masterauth",server.masterauth,NULL);
|
||||
rewriteConfigYesNoOption(state,"slave-serve-stale-data",server.repl_serve_stale_data,REDIS_DEFAULT_SLAVE_SERVE_STALE_DATA);
|
||||
rewriteConfigYesNoOption(state,"slave-read-only",server.repl_slave_ro,REDIS_DEFAULT_SLAVE_READ_ONLY);
|
||||
rewriteConfigNumericalOption(state,"repl-ping-slave-period",server.repl_ping_slave_period,REDIS_REPL_PING_SLAVE_PERIOD);
|
||||
rewriteConfigNumericalOption(state,"repl-timeout",server.repl_timeout,REDIS_REPL_TIMEOUT);
|
||||
rewriteConfigBytesOption(state,"repl-backlog-size",server.repl_backlog_size,REDIS_DEFAULT_REPL_BACKLOG_SIZE);
|
||||
rewriteConfigBytesOption(state,"repl-backlog-ttl",server.repl_backlog_time_limit,REDIS_DEFAULT_REPL_BACKLOG_TIME_LIMIT);
|
||||
rewriteConfigYesNoOption(state,"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay,REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY);
|
||||
rewriteConfigNumericalOption(state,"slave-priority",server.slave_priority,REDIS_DEFAULT_SLAVE_PRIORITY);
|
||||
rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL);
|
||||
rewriteConfigNumericalOption(state,"maxclients",server.maxclients,REDIS_MAX_CLIENTS);
|
||||
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,REDIS_DEFAULT_MAXMEMORY);
|
||||
rewriteConfigEnumOption(state,"maxmemory-policy",server.maxmemory_policy,
|
||||
"volatile-lru", REDIS_MAXMEMORY_VOLATILE_LRU,
|
||||
"allkeys-lru", REDIS_MAXMEMORY_ALLKEYS_LRU,
|
||||
"volatile-random", REDIS_MAXMEMORY_VOLATILE_RANDOM,
|
||||
"allkeys-random", REDIS_MAXMEMORY_ALLKEYS_RANDOM,
|
||||
"volatile-ttl", REDIS_MAXMEMORY_VOLATILE_TTL,
|
||||
"noeviction", REDIS_MAXMEMORY_NO_EVICTION,
|
||||
NULL, REDIS_DEFAULT_MAXMEMORY_POLICY);
|
||||
rewriteConfigNumericalOption(state,"maxmemory-samples",server.maxmemory_samples,REDIS_DEFAULT_MAXMEMORY_SAMPLES);
|
||||
rewriteConfigAppendonlyOption(state);
|
||||
rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,
|
||||
"everysec", AOF_FSYNC_EVERYSEC,
|
||||
"always", AOF_FSYNC_ALWAYS,
|
||||
"no", AOF_FSYNC_NO,
|
||||
NULL, REDIS_DEFAULT_AOF_FSYNC);
|
||||
rewriteConfigYesNoOption(state,"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite,REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE);
|
||||
rewriteConfigNumericalOption(state,"auto-aof-rewrite-percentage",server.aof_rewrite_perc,REDIS_AOF_REWRITE_PERC);
|
||||
rewriteConfigBytesOption(state,"auto-aof-rewrite-min-size",server.aof_rewrite_min_size,REDIS_AOF_REWRITE_MIN_SIZE);
|
||||
rewriteConfigNumericalOption(state,"lua-time-limit",server.lua_time_limit,REDIS_LUA_TIME_LIMIT);
|
||||
rewriteConfigNumericalOption(state,"slowlog-log-slower-than",server.slowlog_log_slower_than,REDIS_SLOWLOG_LOG_SLOWER_THAN);
|
||||
rewriteConfigNumericalOption(state,"slowlog-max-len",server.slowlog_max_len,REDIS_SLOWLOG_MAX_LEN);
|
||||
rewriteConfigNotifykeyspaceeventsOption(state);
|
||||
rewriteConfigNumericalOption(state,"hash-max-ziplist-entries",server.hash_max_ziplist_entries,REDIS_HASH_MAX_ZIPLIST_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"hash-max-ziplist-value",server.hash_max_ziplist_value,REDIS_HASH_MAX_ZIPLIST_VALUE);
|
||||
rewriteConfigNumericalOption(state,"list-max-ziplist-entries",server.list_max_ziplist_entries,REDIS_LIST_MAX_ZIPLIST_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"list-max-ziplist-value",server.list_max_ziplist_value,REDIS_LIST_MAX_ZIPLIST_VALUE);
|
||||
rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,REDIS_SET_MAX_INTSET_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,REDIS_ZSET_MAX_ZIPLIST_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,REDIS_ZSET_MAX_ZIPLIST_VALUE);
|
||||
rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,REDIS_DEFAULT_ACTIVE_REHASHING);
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
rewriteConfigNumericalOption(state,"hz",server.hz,REDIS_DEFAULT_HZ);
|
||||
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
|
||||
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
|
||||
|
||||
/* Step 3: remove all the orphaned lines in the old file, that is, lines
|
||||
* that were used by a config option and are no longer used, like in case
|
||||
* of multiple "save" options or duplicated options. */
|
||||
rewriteConfigRemoveOrphaned(state);
|
||||
|
||||
/* Step 4: generate a new configuration file from the modified state
|
||||
* and write it into the original file. */
|
||||
newcontent = rewriteConfigGetContentFromState(state);
|
||||
retval = rewriteConfigOverwriteFile(server.configfile,newcontent);
|
||||
|
||||
sdsfree(newcontent);
|
||||
rewriteConfigReleaseState(state);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* CONFIG command entry point
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void configCommand(redisClient *c) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"set")) {
|
||||
if (c->argc != 4) goto badarity;
|
||||
@@ -1728,20 +996,9 @@ void configCommand(redisClient *c) {
|
||||
server.aof_delayed_fsync = 0;
|
||||
resetCommandTableStats();
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"rewrite")) {
|
||||
if (c->argc != 2) goto badarity;
|
||||
if (server.configfile == NULL) {
|
||||
addReplyError(c,"The server is running without a config file");
|
||||
return;
|
||||
}
|
||||
if (rewriteConfig(server.configfile) == -1) {
|
||||
addReplyErrorFormat(c,"Rewriting config file: %s", strerror(errno));
|
||||
} else {
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else {
|
||||
addReplyError(c,
|
||||
"CONFIG subcommand must be one of GET, SET, RESETSTAT, REWRITE");
|
||||
"CONFIG subcommand must be one of GET, SET, RESETSTAT");
|
||||
}
|
||||
return;
|
||||
|
||||
|
||||
+1
-17
@@ -45,9 +45,7 @@
|
||||
|
||||
/* Test for proc filesystem */
|
||||
#ifdef __linux__
|
||||
#define HAVE_PROC_STAT 1
|
||||
#define HAVE_PROC_MAPS 1
|
||||
#define HAVE_PROC_SMAPS 1
|
||||
#define HAVE_PROCFS 1
|
||||
#endif
|
||||
|
||||
/* Test for task_info() */
|
||||
@@ -105,20 +103,6 @@
|
||||
#define rdb_fsync_range(fd,off,size) fsync(fd)
|
||||
#endif
|
||||
|
||||
/* Check if we can use setproctitle().
|
||||
* BSD systems have support for it, we provide an implementation for
|
||||
* Linux and osx. */
|
||||
#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
|
||||
#define USE_SETPROCTITLE
|
||||
#endif
|
||||
|
||||
#if (defined __linux || defined __APPLE__)
|
||||
#define USE_SETPROCTITLE
|
||||
#define INIT_SETPROCTITLE_REPLACEMENT
|
||||
void spt_init(int argc, char *argv[]);
|
||||
void setproctitle(const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
/* Byte ordering detection */
|
||||
#include <sys/types.h> /* This will likely define BYTE_ORDER */
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#ifndef CRC64_H
|
||||
#define CRC64_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
|
||||
#endif
|
||||
@@ -238,8 +238,6 @@ void delCommand(redisClient *c) {
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
if (dbDelete(c->db,c->argv[j])) {
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
|
||||
"del",c->argv[j],c->db->id);
|
||||
server.dirty++;
|
||||
deleted++;
|
||||
}
|
||||
@@ -309,248 +307,6 @@ void keysCommand(redisClient *c) {
|
||||
setDeferredMultiBulkLength(c,replylen,numkeys);
|
||||
}
|
||||
|
||||
/* This callback is used by scanGenericCommand in order to collect elements
|
||||
* returned by the dictionary iterator into a list. */
|
||||
void scanCallback(void *privdata, const dictEntry *de) {
|
||||
void **pd = (void**) privdata;
|
||||
list *keys = pd[0];
|
||||
robj *o = pd[1];
|
||||
robj *key, *val = NULL;
|
||||
|
||||
if (o == NULL) {
|
||||
sds sdskey = dictGetKey(de);
|
||||
key = createStringObject(sdskey, sdslen(sdskey));
|
||||
} else if (o->type == REDIS_SET) {
|
||||
key = dictGetKey(de);
|
||||
incrRefCount(key);
|
||||
} else if (o->type == REDIS_HASH) {
|
||||
key = dictGetKey(de);
|
||||
incrRefCount(key);
|
||||
val = dictGetVal(de);
|
||||
incrRefCount(val);
|
||||
} else if (o->type == REDIS_ZSET) {
|
||||
key = dictGetKey(de);
|
||||
incrRefCount(key);
|
||||
val = createStringObjectFromLongDouble(*(double*)dictGetVal(de));
|
||||
} else {
|
||||
redisPanic("Type not handled in SCAN callback.");
|
||||
}
|
||||
|
||||
listAddNodeTail(keys, key);
|
||||
if (val) listAddNodeTail(keys, val);
|
||||
}
|
||||
|
||||
/* Try to parse a SCAN cursor stored at object 'o':
|
||||
* if the cursor is valid, store it as unsigned integer into *cursor and
|
||||
* returns REDIS_OK. Otherwise return REDIS_ERR and send an error to the
|
||||
* client. */
|
||||
int parseScanCursorOrReply(redisClient *c, robj *o, unsigned long *cursor) {
|
||||
char *eptr;
|
||||
|
||||
/* Use strtoul() because we need an *unsigned* long, so
|
||||
* getLongLongFromObject() does not cover the whole cursor space. */
|
||||
errno = 0;
|
||||
*cursor = strtoul(o->ptr, &eptr, 10);
|
||||
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' || errno == ERANGE)
|
||||
{
|
||||
addReplyError(c, "invalid cursor");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* This command implements SCAN, HSCAN and SSCAN commands.
|
||||
* If object 'o' is passed, then it must be an Hash or Set object, otherwise
|
||||
* if 'o' is NULL the command will operate on the dictionary associated with
|
||||
* the current database.
|
||||
*
|
||||
* When 'o' is not NULL the function assumes that the first argument in
|
||||
* the client arguments vector is a key so it skips it before iterating
|
||||
* in order to parse options.
|
||||
*
|
||||
* In the case of an Hash object the function returns both the field and value
|
||||
* of every element on the Hash. */
|
||||
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
int rv;
|
||||
int i, j;
|
||||
char buf[REDIS_LONGSTR_SIZE];
|
||||
list *keys = listCreate();
|
||||
listNode *node, *nextnode;
|
||||
long count = 10;
|
||||
sds pat;
|
||||
int patlen, use_pattern = 0;
|
||||
dict *ht;
|
||||
|
||||
/* Object must be NULL (to iterate keys names), or the type of the object
|
||||
* must be Set, Sorted Set, or Hash. */
|
||||
redisAssert(o == NULL || o->type == REDIS_SET || o->type == REDIS_HASH ||
|
||||
o->type == REDIS_ZSET);
|
||||
|
||||
/* Set i to the first option argument. The previous one is the cursor. */
|
||||
i = (o == NULL) ? 2 : 3; /* Skip the key argument if needed. */
|
||||
|
||||
/* Step 1: Parse options. */
|
||||
while (i < c->argc) {
|
||||
j = c->argc - i;
|
||||
if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) {
|
||||
if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL)
|
||||
!= REDIS_OK)
|
||||
{
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (count < 1) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
i += 2;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) {
|
||||
pat = c->argv[i+1]->ptr;
|
||||
patlen = sdslen(pat);
|
||||
|
||||
/* The pattern always matches if it is exactly "*", so it is
|
||||
* equivalent to disabling it. */
|
||||
use_pattern = !(pat[0] == '*' && patlen == 1);
|
||||
|
||||
i += 2;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 2: Iterate the collection.
|
||||
*
|
||||
* Note that if the object is encoded with a ziplist, intset, or any other
|
||||
* representation that is not an hash table, we are sure that it is also
|
||||
* composed of a small number of elements. So to avoid taking state we
|
||||
* just return everything inside the object in a single call, setting the
|
||||
* cursor to zero to signal the end of the iteration. */
|
||||
|
||||
/* Handle the case of an hash table. */
|
||||
ht = NULL;
|
||||
if (o == NULL) {
|
||||
ht = c->db->dict;
|
||||
} else if (o->type == REDIS_SET && o->encoding == REDIS_ENCODING_HT) {
|
||||
ht = o->ptr;
|
||||
} else if (o->type == REDIS_HASH && o->encoding == REDIS_ENCODING_HT) {
|
||||
ht = o->ptr;
|
||||
count *= 2; /* We return key / value for this type. */
|
||||
} else if (o->type == REDIS_ZSET && o->encoding == REDIS_ENCODING_SKIPLIST) {
|
||||
zset *zs = o->ptr;
|
||||
ht = zs->dict;
|
||||
count *= 2; /* We return key / value for this type. */
|
||||
}
|
||||
|
||||
if (ht) {
|
||||
void *privdata[2];
|
||||
|
||||
/* We pass two pointers to the callback: the list to which it will
|
||||
* add new elements, and the object containing the dictionary so that
|
||||
* it is possible to fetch more data in a type-dependent way. */
|
||||
privdata[0] = keys;
|
||||
privdata[1] = o;
|
||||
do {
|
||||
cursor = dictScan(ht, cursor, scanCallback, privdata);
|
||||
} while (cursor && listLength(keys) < count);
|
||||
} else if (o->type == REDIS_SET) {
|
||||
int pos = 0;
|
||||
int64_t ll;
|
||||
|
||||
while(intsetGet(o->ptr,pos++,&ll))
|
||||
listAddNodeTail(keys,createStringObjectFromLongLong(ll));
|
||||
cursor = 0;
|
||||
} else if (o->type == REDIS_HASH || o->type == REDIS_ZSET) {
|
||||
unsigned char *p = ziplistIndex(o->ptr,0);
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vll;
|
||||
|
||||
while(p) {
|
||||
ziplistGet(p,&vstr,&vlen,&vll);
|
||||
listAddNodeTail(keys,
|
||||
(vstr != NULL) ? createStringObject((char*)vstr,vlen) :
|
||||
createStringObjectFromLongLong(vll));
|
||||
p = ziplistNext(o->ptr,p);
|
||||
}
|
||||
cursor = 0;
|
||||
} else {
|
||||
redisPanic("Not handled encoding in SCAN.");
|
||||
}
|
||||
|
||||
/* Step 3: Filter elements. */
|
||||
node = listFirst(keys);
|
||||
while (node) {
|
||||
robj *kobj = listNodeValue(node);
|
||||
nextnode = listNextNode(node);
|
||||
int filter = 0;
|
||||
|
||||
/* Filter element if it does not match the pattern. */
|
||||
if (!filter && use_pattern) {
|
||||
if (kobj->encoding == REDIS_ENCODING_INT) {
|
||||
char buf[REDIS_LONGSTR_SIZE];
|
||||
int len;
|
||||
|
||||
redisAssert(kobj->encoding == REDIS_ENCODING_INT);
|
||||
len = ll2string(buf,sizeof(buf),(long)kobj->ptr);
|
||||
if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1;
|
||||
} else {
|
||||
if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))
|
||||
filter = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Filter element if it is an expired key. */
|
||||
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
|
||||
|
||||
/* Remove the element and its associted value if needed. */
|
||||
if (filter) {
|
||||
decrRefCount(kobj);
|
||||
listDelNode(keys, node);
|
||||
}
|
||||
|
||||
/* If this is an hash or a sorted set, we have a flat list of
|
||||
* key-value elements, so if this element was filtered, remove the
|
||||
* value, or skip it if it was not filtered: we only match keys. */
|
||||
if (o && (o->type == REDIS_ZSET || o->type == REDIS_HASH)) {
|
||||
node = nextnode;
|
||||
nextnode = listNextNode(node);
|
||||
if (filter) {
|
||||
kobj = listNodeValue(node);
|
||||
decrRefCount(kobj);
|
||||
listDelNode(keys, node);
|
||||
}
|
||||
}
|
||||
node = nextnode;
|
||||
}
|
||||
|
||||
/* Step 4: Reply to the client. */
|
||||
addReplyMultiBulkLen(c, 2);
|
||||
rv = snprintf(buf, sizeof(buf), "%lu", cursor);
|
||||
redisAssert(rv < sizeof(buf));
|
||||
addReplyBulkCBuffer(c, buf, rv);
|
||||
|
||||
addReplyMultiBulkLen(c, listLength(keys));
|
||||
while ((node = listFirst(keys)) != NULL) {
|
||||
robj *kobj = listNodeValue(node);
|
||||
addReplyBulk(c, kobj);
|
||||
decrRefCount(kobj);
|
||||
listDelNode(keys, node);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
listSetFreeMethod(keys,decrRefCountVoid);
|
||||
listRelease(keys);
|
||||
}
|
||||
|
||||
/* The SCAN command completely relies on scanGenericCommand. */
|
||||
void scanCommand(redisClient *c) {
|
||||
unsigned long cursor;
|
||||
if (parseScanCursorOrReply(c,c->argv[1],&cursor) == REDIS_ERR) return;
|
||||
scanGenericCommand(c,NULL,cursor);
|
||||
}
|
||||
|
||||
void dbsizeCommand(redisClient *c) {
|
||||
addReplyLongLong(c,dictSize(c->db->dict));
|
||||
}
|
||||
@@ -595,12 +351,6 @@ void shutdownCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* SHUTDOWN can be called even while the server is in "loading" state.
|
||||
* When this happens we need to make sure no attempt is performed to save
|
||||
* the dataset on shutdown (otherwise it could overwrite the current DB
|
||||
* with half-read data). */
|
||||
if (server.loading)
|
||||
flags = (flags & ~REDIS_SHUTDOWN_SAVE) | REDIS_SHUTDOWN_NOSAVE;
|
||||
if (prepareForShutdown(flags) == REDIS_OK) exit(0);
|
||||
addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
|
||||
}
|
||||
@@ -626,8 +376,7 @@ void renameGenericCommand(redisClient *c, int nx) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
/* Overwrite: delete the old key before creating the new one
|
||||
* with the same name. */
|
||||
/* Overwrite: delete the old key before creating the new one with the same name. */
|
||||
dbDelete(c->db,c->argv[2]);
|
||||
}
|
||||
dbAdd(c->db,c->argv[2],o);
|
||||
@@ -635,10 +384,6 @@ void renameGenericCommand(redisClient *c, int nx) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"rename_from",
|
||||
c->argv[1],c->db->id);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"rename_to",
|
||||
c->argv[2],c->db->id);
|
||||
server.dirty++;
|
||||
addReply(c,nx ? shared.cone : shared.ok);
|
||||
}
|
||||
@@ -748,7 +493,8 @@ void propagateExpire(redisDb *db, robj *key) {
|
||||
|
||||
if (server.aof_state != REDIS_AOF_OFF)
|
||||
feedAppendOnlyFile(server.delCommand,db->id,argv,2);
|
||||
replicationFeedSlaves(server.slaves,db->id,argv,2);
|
||||
if (listLength(server.slaves))
|
||||
replicationFeedSlaves(server.slaves,db->id,argv,2);
|
||||
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
@@ -779,8 +525,6 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
/* Delete the key */
|
||||
server.stat_expiredkeys++;
|
||||
propagateExpire(db,key);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,
|
||||
"expired",key,db->id);
|
||||
return dbDelete(db,key);
|
||||
}
|
||||
|
||||
@@ -828,14 +572,12 @@ void expireGenericCommand(redisClient *c, long long basetime, int unit) {
|
||||
rewriteClientCommandVector(c,2,aux,key);
|
||||
decrRefCount(aux);
|
||||
signalModifiedKey(c->db,key);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
addReply(c, shared.cone);
|
||||
return;
|
||||
} else {
|
||||
setExpire(c->db,key,when);
|
||||
addReply(c,shared.cone);
|
||||
signalModifiedKey(c->db,key);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"expire",key,c->db->id);
|
||||
server.dirty++;
|
||||
return;
|
||||
}
|
||||
@@ -860,17 +602,10 @@ void pexpireatCommand(redisClient *c) {
|
||||
void ttlGenericCommand(redisClient *c, int output_ms) {
|
||||
long long expire, ttl = -1;
|
||||
|
||||
/* If the key does not exist at all, return -2 */
|
||||
if (lookupKeyRead(c->db,c->argv[1]) == NULL) {
|
||||
addReplyLongLong(c,-2);
|
||||
return;
|
||||
}
|
||||
/* The key exists. Return -1 if it has no expire, or the actual
|
||||
* TTL value otherwise. */
|
||||
expire = getExpire(c->db,c->argv[1]);
|
||||
if (expire != -1) {
|
||||
ttl = expire-mstime();
|
||||
if (ttl < 0) ttl = 0;
|
||||
if (ttl < 0) ttl = -1;
|
||||
}
|
||||
if (ttl == -1) {
|
||||
addReplyLongLong(c,-1);
|
||||
|
||||
+84
-215
@@ -29,7 +29,6 @@
|
||||
|
||||
#include "redis.h"
|
||||
#include "sha1.h" /* SHA1 is used for DEBUG DIGEST */
|
||||
#include "crc64.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <signal.h>
|
||||
@@ -38,7 +37,6 @@
|
||||
#include <execinfo.h>
|
||||
#include <ucontext.h>
|
||||
#include <fcntl.h>
|
||||
#include "bio.h"
|
||||
#endif /* HAVE_BACKTRACE */
|
||||
|
||||
/* ================================= Debugging ============================== */
|
||||
@@ -296,29 +294,6 @@ void debugCommand(redisClient *c) {
|
||||
(void*)val, val->refcount,
|
||||
strenc, (long long) rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val));
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) {
|
||||
dictEntry *de;
|
||||
robj *val;
|
||||
sds key;
|
||||
|
||||
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
||||
addReply(c,shared.nokeyerr);
|
||||
return;
|
||||
}
|
||||
val = dictGetVal(de);
|
||||
key = dictGetKey(de);
|
||||
|
||||
if (val->type != REDIS_STRING || val->encoding != REDIS_ENCODING_RAW) {
|
||||
addReplyError(c,"Not an sds encoded string.");
|
||||
} else {
|
||||
addReplyStatusFormat(c,
|
||||
"key_sds_len:%lld, key_sds_avail:%lld, "
|
||||
"val_sds_len:%lld, val_sds_avail:%lld",
|
||||
(long long) sdslen(key),
|
||||
(long long) sdsavail(key),
|
||||
(long long) sdslen(val->ptr),
|
||||
(long long) sdsavail(val->ptr));
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"populate") && c->argc == 3) {
|
||||
long keys, j;
|
||||
robj *key, *val;
|
||||
@@ -352,11 +327,8 @@ void debugCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
|
||||
double dtime = strtod(c->argv[2]->ptr,NULL);
|
||||
long long utime = dtime*1000000;
|
||||
struct timespec tv;
|
||||
|
||||
tv.tv_sec = utime / 1000000;
|
||||
tv.tv_nsec = (utime % 1000000) * 1000;
|
||||
nanosleep(&tv, NULL);
|
||||
usleep(utime);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"set-active-expire") &&
|
||||
c->argc == 3)
|
||||
@@ -415,12 +387,9 @@ void redisLogObjectDebugInfo(robj *o) {
|
||||
redisLog(REDIS_WARNING,"Object encoding: %d", o->encoding);
|
||||
redisLog(REDIS_WARNING,"Object refcount: %d", o->refcount);
|
||||
if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_RAW) {
|
||||
redisLog(REDIS_WARNING,"Object raw string len: %zu", sdslen(o->ptr));
|
||||
if (sdslen(o->ptr) < 4096) {
|
||||
sds repr = sdscatrepr(sdsempty(),o->ptr,sdslen(o->ptr));
|
||||
redisLog(REDIS_WARNING,"Object raw string content: %s", repr);
|
||||
sdsfree(repr);
|
||||
}
|
||||
redisLog(REDIS_WARNING,"Object raw string len: %d", sdslen(o->ptr));
|
||||
if (sdslen(o->ptr) < 4096)
|
||||
redisLog(REDIS_WARNING,"Object raw string content: \"%s\"", (char*)o->ptr);
|
||||
} else if (o->type == REDIS_LIST) {
|
||||
redisLog(REDIS_WARNING,"List length: %d", (int) listTypeLength(o));
|
||||
} else if (o->type == REDIS_SET) {
|
||||
@@ -501,13 +470,10 @@ static void *getMcontextEip(ucontext_t *uc) {
|
||||
void logStackContent(void **sp) {
|
||||
int i;
|
||||
for (i = 15; i >= 0; i--) {
|
||||
unsigned long addr = (unsigned long) sp+i;
|
||||
unsigned long val = (unsigned long) sp[i];
|
||||
|
||||
if (sizeof(long) == 4)
|
||||
redisLog(REDIS_WARNING, "(%08lx) -> %08lx", addr, val);
|
||||
redisLog(REDIS_WARNING, "(%08lx) -> %08lx", sp+i, sp[i]);
|
||||
else
|
||||
redisLog(REDIS_WARNING, "(%016lx) -> %016lx", addr, val);
|
||||
redisLog(REDIS_WARNING, "(%016lx) -> %016lx", sp+i, sp[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,27 +491,27 @@ void logRegisters(ucontext_t *uc) {
|
||||
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
|
||||
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
|
||||
"RIP:%016lx EFL:%016lx\nCS :%016lx FS:%016lx GS:%016lx",
|
||||
(unsigned long) uc->uc_mcontext->__ss.__rax,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__rbx,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__rcx,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__rdx,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__rdi,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__rsi,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__rbp,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__rsp,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__r8,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__r9,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__r10,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__r11,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__r12,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__r13,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__r14,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__r15,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__rip,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__rflags,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__cs,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__fs,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__gs
|
||||
uc->uc_mcontext->__ss.__rax,
|
||||
uc->uc_mcontext->__ss.__rbx,
|
||||
uc->uc_mcontext->__ss.__rcx,
|
||||
uc->uc_mcontext->__ss.__rdx,
|
||||
uc->uc_mcontext->__ss.__rdi,
|
||||
uc->uc_mcontext->__ss.__rsi,
|
||||
uc->uc_mcontext->__ss.__rbp,
|
||||
uc->uc_mcontext->__ss.__rsp,
|
||||
uc->uc_mcontext->__ss.__r8,
|
||||
uc->uc_mcontext->__ss.__r9,
|
||||
uc->uc_mcontext->__ss.__r10,
|
||||
uc->uc_mcontext->__ss.__r11,
|
||||
uc->uc_mcontext->__ss.__r12,
|
||||
uc->uc_mcontext->__ss.__r13,
|
||||
uc->uc_mcontext->__ss.__r14,
|
||||
uc->uc_mcontext->__ss.__r15,
|
||||
uc->uc_mcontext->__ss.__rip,
|
||||
uc->uc_mcontext->__ss.__rflags,
|
||||
uc->uc_mcontext->__ss.__cs,
|
||||
uc->uc_mcontext->__ss.__fs,
|
||||
uc->uc_mcontext->__ss.__gs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext->__ss.__rsp);
|
||||
#else
|
||||
@@ -556,22 +522,22 @@ void logRegisters(ucontext_t *uc) {
|
||||
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
|
||||
"SS:%08lx EFL:%08lx EIP:%08lx CS :%08lx\n"
|
||||
"DS:%08lx ES:%08lx FS :%08lx GS :%08lx",
|
||||
(unsigned long) uc->uc_mcontext->__ss.__eax,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__ebx,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__ecx,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__edx,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__edi,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__esi,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__ebp,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__esp,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__ss,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__eflags,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__eip,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__cs,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__ds,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__es,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__fs,
|
||||
(unsigned long) uc->uc_mcontext->__ss.__gs
|
||||
uc->uc_mcontext->__ss.__eax,
|
||||
uc->uc_mcontext->__ss.__ebx,
|
||||
uc->uc_mcontext->__ss.__ecx,
|
||||
uc->uc_mcontext->__ss.__edx,
|
||||
uc->uc_mcontext->__ss.__edi,
|
||||
uc->uc_mcontext->__ss.__esi,
|
||||
uc->uc_mcontext->__ss.__ebp,
|
||||
uc->uc_mcontext->__ss.__esp,
|
||||
uc->uc_mcontext->__ss.__ss,
|
||||
uc->uc_mcontext->__ss.__eflags,
|
||||
uc->uc_mcontext->__ss.__eip,
|
||||
uc->uc_mcontext->__ss.__cs,
|
||||
uc->uc_mcontext->__ss.__ds,
|
||||
uc->uc_mcontext->__ss.__es,
|
||||
uc->uc_mcontext->__ss.__fs,
|
||||
uc->uc_mcontext->__ss.__gs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext->__ss.__esp);
|
||||
#endif
|
||||
@@ -585,22 +551,22 @@ void logRegisters(ucontext_t *uc) {
|
||||
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
|
||||
"SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
|
||||
"DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
|
||||
(unsigned long) uc->uc_mcontext.gregs[11],
|
||||
(unsigned long) uc->uc_mcontext.gregs[8],
|
||||
(unsigned long) uc->uc_mcontext.gregs[10],
|
||||
(unsigned long) uc->uc_mcontext.gregs[9],
|
||||
(unsigned long) uc->uc_mcontext.gregs[4],
|
||||
(unsigned long) uc->uc_mcontext.gregs[5],
|
||||
(unsigned long) uc->uc_mcontext.gregs[6],
|
||||
(unsigned long) uc->uc_mcontext.gregs[7],
|
||||
(unsigned long) uc->uc_mcontext.gregs[18],
|
||||
(unsigned long) uc->uc_mcontext.gregs[17],
|
||||
(unsigned long) uc->uc_mcontext.gregs[14],
|
||||
(unsigned long) uc->uc_mcontext.gregs[15],
|
||||
(unsigned long) uc->uc_mcontext.gregs[3],
|
||||
(unsigned long) uc->uc_mcontext.gregs[2],
|
||||
(unsigned long) uc->uc_mcontext.gregs[1],
|
||||
(unsigned long) uc->uc_mcontext.gregs[0]
|
||||
uc->uc_mcontext.gregs[11],
|
||||
uc->uc_mcontext.gregs[8],
|
||||
uc->uc_mcontext.gregs[10],
|
||||
uc->uc_mcontext.gregs[9],
|
||||
uc->uc_mcontext.gregs[4],
|
||||
uc->uc_mcontext.gregs[5],
|
||||
uc->uc_mcontext.gregs[6],
|
||||
uc->uc_mcontext.gregs[7],
|
||||
uc->uc_mcontext.gregs[18],
|
||||
uc->uc_mcontext.gregs[17],
|
||||
uc->uc_mcontext.gregs[14],
|
||||
uc->uc_mcontext.gregs[15],
|
||||
uc->uc_mcontext.gregs[3],
|
||||
uc->uc_mcontext.gregs[2],
|
||||
uc->uc_mcontext.gregs[1],
|
||||
uc->uc_mcontext.gregs[0]
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.gregs[7]);
|
||||
#elif defined(__X86_64__) || defined(__x86_64__)
|
||||
@@ -612,25 +578,25 @@ void logRegisters(ucontext_t *uc) {
|
||||
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
|
||||
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
|
||||
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
|
||||
(unsigned long) uc->uc_mcontext.gregs[13],
|
||||
(unsigned long) uc->uc_mcontext.gregs[11],
|
||||
(unsigned long) uc->uc_mcontext.gregs[14],
|
||||
(unsigned long) uc->uc_mcontext.gregs[12],
|
||||
(unsigned long) uc->uc_mcontext.gregs[8],
|
||||
(unsigned long) uc->uc_mcontext.gregs[9],
|
||||
(unsigned long) uc->uc_mcontext.gregs[10],
|
||||
(unsigned long) uc->uc_mcontext.gregs[15],
|
||||
(unsigned long) uc->uc_mcontext.gregs[0],
|
||||
(unsigned long) uc->uc_mcontext.gregs[1],
|
||||
(unsigned long) uc->uc_mcontext.gregs[2],
|
||||
(unsigned long) uc->uc_mcontext.gregs[3],
|
||||
(unsigned long) uc->uc_mcontext.gregs[4],
|
||||
(unsigned long) uc->uc_mcontext.gregs[5],
|
||||
(unsigned long) uc->uc_mcontext.gregs[6],
|
||||
(unsigned long) uc->uc_mcontext.gregs[7],
|
||||
(unsigned long) uc->uc_mcontext.gregs[16],
|
||||
(unsigned long) uc->uc_mcontext.gregs[17],
|
||||
(unsigned long) uc->uc_mcontext.gregs[18]
|
||||
uc->uc_mcontext.gregs[13],
|
||||
uc->uc_mcontext.gregs[11],
|
||||
uc->uc_mcontext.gregs[14],
|
||||
uc->uc_mcontext.gregs[12],
|
||||
uc->uc_mcontext.gregs[8],
|
||||
uc->uc_mcontext.gregs[9],
|
||||
uc->uc_mcontext.gregs[10],
|
||||
uc->uc_mcontext.gregs[15],
|
||||
uc->uc_mcontext.gregs[0],
|
||||
uc->uc_mcontext.gregs[1],
|
||||
uc->uc_mcontext.gregs[2],
|
||||
uc->uc_mcontext.gregs[3],
|
||||
uc->uc_mcontext.gregs[4],
|
||||
uc->uc_mcontext.gregs[5],
|
||||
uc->uc_mcontext.gregs[6],
|
||||
uc->uc_mcontext.gregs[7],
|
||||
uc->uc_mcontext.gregs[16],
|
||||
uc->uc_mcontext.gregs[17],
|
||||
uc->uc_mcontext.gregs[18]
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.gregs[15]);
|
||||
#endif
|
||||
@@ -645,12 +611,11 @@ void logRegisters(ucontext_t *uc) {
|
||||
void logStackTrace(ucontext_t *uc) {
|
||||
void *trace[100];
|
||||
int trace_size = 0, fd;
|
||||
int log_to_stdout = server.logfile[0] == '\0';
|
||||
|
||||
/* Open the log file in append mode. */
|
||||
fd = log_to_stdout ?
|
||||
STDOUT_FILENO :
|
||||
open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644);
|
||||
fd = server.logfile ?
|
||||
open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644) :
|
||||
STDOUT_FILENO;
|
||||
if (fd == -1) return;
|
||||
|
||||
/* Generate the stack trace */
|
||||
@@ -664,7 +629,7 @@ void logStackTrace(ucontext_t *uc) {
|
||||
backtrace_symbols_fd(trace, trace_size, fd);
|
||||
|
||||
/* Cleanup */
|
||||
if (!log_to_stdout) close(fd);
|
||||
if (server.logfile) close(fd);
|
||||
}
|
||||
|
||||
/* Log information about the "current" client, that is, the client that is
|
||||
@@ -698,96 +663,13 @@ void logCurrentClient(void) {
|
||||
de = dictFind(cc->db->dict, key->ptr);
|
||||
if (de) {
|
||||
val = dictGetVal(de);
|
||||
redisLog(REDIS_WARNING,"key '%s' found in DB containing the following object:", (char*)key->ptr);
|
||||
redisLog(REDIS_WARNING,"key '%s' found in DB containing the following object:", key->ptr);
|
||||
redisLogObjectDebugInfo(val);
|
||||
}
|
||||
decrRefCount(key);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(HAVE_PROC_MAPS)
|
||||
void memtest_non_destructive_invert(void *addr, size_t size);
|
||||
void memtest_non_destructive_swap(void *addr, size_t size);
|
||||
#define MEMTEST_MAX_REGIONS 128
|
||||
|
||||
int memtest_test_linux_anonymous_maps(void) {
|
||||
FILE *fp = fopen("/proc/self/maps","r");
|
||||
char line[1024];
|
||||
size_t start_addr, end_addr, size;
|
||||
size_t start_vect[MEMTEST_MAX_REGIONS];
|
||||
size_t size_vect[MEMTEST_MAX_REGIONS];
|
||||
int regions = 0, j;
|
||||
uint64_t crc1 = 0, crc2 = 0, crc3 = 0;
|
||||
|
||||
while(fgets(line,sizeof(line),fp) != NULL) {
|
||||
char *start, *end, *p = line;
|
||||
|
||||
start = p;
|
||||
p = strchr(p,'-');
|
||||
if (!p) continue;
|
||||
*p++ = '\0';
|
||||
end = p;
|
||||
p = strchr(p,' ');
|
||||
if (!p) continue;
|
||||
*p++ = '\0';
|
||||
if (strstr(p,"stack") ||
|
||||
strstr(p,"vdso") ||
|
||||
strstr(p,"vsyscall")) continue;
|
||||
if (!strstr(p,"00:00")) continue;
|
||||
if (!strstr(p,"rw")) continue;
|
||||
|
||||
start_addr = strtoul(start,NULL,16);
|
||||
end_addr = strtoul(end,NULL,16);
|
||||
size = end_addr-start_addr;
|
||||
|
||||
start_vect[regions] = start_addr;
|
||||
size_vect[regions] = size;
|
||||
printf("Testing %lx %lu\n", (unsigned long) start_vect[regions],
|
||||
(unsigned long) size_vect[regions]);
|
||||
regions++;
|
||||
}
|
||||
|
||||
/* Test all the regions as an unique sequential region.
|
||||
* 1) Take the CRC64 of the memory region. */
|
||||
for (j = 0; j < regions; j++) {
|
||||
crc1 = crc64(crc1,(void*)start_vect[j],size_vect[j]);
|
||||
}
|
||||
|
||||
/* 2) Invert bits, swap adjacent words, swap again, invert bits.
|
||||
* This is the error amplification step. */
|
||||
for (j = 0; j < regions; j++)
|
||||
memtest_non_destructive_invert((void*)start_vect[j],size_vect[j]);
|
||||
for (j = 0; j < regions; j++)
|
||||
memtest_non_destructive_swap((void*)start_vect[j],size_vect[j]);
|
||||
for (j = 0; j < regions; j++)
|
||||
memtest_non_destructive_swap((void*)start_vect[j],size_vect[j]);
|
||||
for (j = 0; j < regions; j++)
|
||||
memtest_non_destructive_invert((void*)start_vect[j],size_vect[j]);
|
||||
|
||||
/* 3) Take the CRC64 sum again. */
|
||||
for (j = 0; j < regions; j++)
|
||||
crc2 = crc64(crc2,(void*)start_vect[j],size_vect[j]);
|
||||
|
||||
/* 4) Swap + Swap again */
|
||||
for (j = 0; j < regions; j++)
|
||||
memtest_non_destructive_swap((void*)start_vect[j],size_vect[j]);
|
||||
for (j = 0; j < regions; j++)
|
||||
memtest_non_destructive_swap((void*)start_vect[j],size_vect[j]);
|
||||
|
||||
/* 5) Take the CRC64 sum again. */
|
||||
for (j = 0; j < regions; j++)
|
||||
crc3 = crc64(crc3,(void*)start_vect[j],size_vect[j]);
|
||||
|
||||
/* NOTE: It is very important to close the file descriptor only now
|
||||
* because closing it before may result into unmapping of some memory
|
||||
* region that we are testing. */
|
||||
fclose(fp);
|
||||
|
||||
/* If the two CRC are not the same, we trapped a memory error. */
|
||||
return crc1 != crc2 || crc2 != crc3;
|
||||
}
|
||||
#endif
|
||||
|
||||
void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
ucontext_t *uc = (ucontext_t*) secret;
|
||||
sds infostring, clients;
|
||||
@@ -823,19 +705,6 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
/* Log dump of processor registers */
|
||||
logRegisters(uc);
|
||||
|
||||
#if defined(HAVE_PROC_MAPS)
|
||||
/* Test memory */
|
||||
redisLog(REDIS_WARNING, "--- FAST MEMORY TEST");
|
||||
bioKillThreads();
|
||||
if (memtest_test_linux_anonymous_maps()) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"!!! MEMORY ERROR DETECTED! Check your memory ASAP !!!");
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Fast memory test PASSED, however your memory can still be broken. Please run a memory test for several hours if possible.");
|
||||
}
|
||||
#endif
|
||||
|
||||
redisLog(REDIS_WARNING,
|
||||
"\n=== REDIS BUG REPORT END. Make sure to include from START to END. ===\n\n"
|
||||
" Please report the crash opening an issue on github:\n\n"
|
||||
@@ -913,7 +782,7 @@ void watchdogScheduleSignal(int period) {
|
||||
setitimer(ITIMER_REAL, &it, NULL);
|
||||
}
|
||||
|
||||
/* Enable the software watchdog with the specified period in milliseconds. */
|
||||
/* Enable the software watchdong with the specified period in milliseconds. */
|
||||
void enableWatchdog(int period) {
|
||||
int min_period;
|
||||
|
||||
|
||||
+7
-219
@@ -39,13 +39,13 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <sys/time.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "dict.h"
|
||||
#include "zmalloc.h"
|
||||
#include "redisassert.h"
|
||||
|
||||
/* Using dictEnableResize() / dictDisableResize() we make possible to
|
||||
* enable/disable resizing of the hash table as needed. This is very important
|
||||
@@ -505,44 +505,6 @@ void *dictFetchValue(dict *d, const void *key) {
|
||||
return he ? dictGetVal(he) : NULL;
|
||||
}
|
||||
|
||||
/* A fingerprint is a 64 bit number that represents the state of the dictionary
|
||||
* at a given time, it's just a few dict properties xored together.
|
||||
* When an unsafe iterator is initialized, we get the dict fingerprint, and check
|
||||
* the fingerprint again when the iterator is released.
|
||||
* If the two fingerprints are different it means that the user of the iterator
|
||||
* performed forbidden operations against the dictionary while iterating. */
|
||||
long long dictFingerprint(dict *d) {
|
||||
long long integers[6], hash = 0;
|
||||
int j;
|
||||
|
||||
integers[0] = (long) d->ht[0].table;
|
||||
integers[1] = d->ht[0].size;
|
||||
integers[2] = d->ht[0].used;
|
||||
integers[3] = (long) d->ht[1].table;
|
||||
integers[4] = d->ht[1].size;
|
||||
integers[5] = d->ht[1].used;
|
||||
|
||||
/* We hash N integers by summing every successive integer with the integer
|
||||
* hashing of the previous sum. Basically:
|
||||
*
|
||||
* Result = hash(hash(hash(int1)+int2)+int3) ...
|
||||
*
|
||||
* This way the same set of integers in a different order will (likely) hash
|
||||
* to a different number. */
|
||||
for (j = 0; j < 6; j++) {
|
||||
hash += integers[j];
|
||||
/* For the hashing step we use Tomas Wang's 64 bit integer hash. */
|
||||
hash = (~hash) + (hash << 21); // hash = (hash << 21) - hash - 1;
|
||||
hash = hash ^ (hash >> 24);
|
||||
hash = (hash + (hash << 3)) + (hash << 8); // hash * 265
|
||||
hash = hash ^ (hash >> 14);
|
||||
hash = (hash + (hash << 2)) + (hash << 4); // hash * 21
|
||||
hash = hash ^ (hash >> 28);
|
||||
hash = hash + (hash << 31);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
dictIterator *dictGetIterator(dict *d)
|
||||
{
|
||||
dictIterator *iter = zmalloc(sizeof(*iter));
|
||||
@@ -568,12 +530,8 @@ dictEntry *dictNext(dictIterator *iter)
|
||||
while (1) {
|
||||
if (iter->entry == NULL) {
|
||||
dictht *ht = &iter->d->ht[iter->table];
|
||||
if (iter->index == -1 && iter->table == 0) {
|
||||
if (iter->safe)
|
||||
iter->d->iterators++;
|
||||
else
|
||||
iter->fingerprint = dictFingerprint(iter->d);
|
||||
}
|
||||
if (iter->safe && iter->index == -1 && iter->table == 0)
|
||||
iter->d->iterators++;
|
||||
iter->index++;
|
||||
if (iter->index >= (signed) ht->size) {
|
||||
if (dictIsRehashing(iter->d) && iter->table == 0) {
|
||||
@@ -600,12 +558,8 @@ dictEntry *dictNext(dictIterator *iter)
|
||||
|
||||
void dictReleaseIterator(dictIterator *iter)
|
||||
{
|
||||
if (!(iter->index == -1 && iter->table == 0)) {
|
||||
if (iter->safe)
|
||||
iter->d->iterators--;
|
||||
else
|
||||
assert(iter->fingerprint == dictFingerprint(iter->d));
|
||||
}
|
||||
if (iter->safe && !(iter->index == -1 && iter->table == 0))
|
||||
iter->d->iterators--;
|
||||
zfree(iter);
|
||||
}
|
||||
|
||||
@@ -648,173 +602,6 @@ dictEntry *dictGetRandomKey(dict *d)
|
||||
return he;
|
||||
}
|
||||
|
||||
/* Function to reverse bits. Algorithm from:
|
||||
* http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
|
||||
static unsigned long rev(unsigned long v) {
|
||||
unsigned long s = 8 * sizeof(v); // bit size; must be power of 2
|
||||
unsigned long mask = ~0;
|
||||
while ((s >>= 1) > 0) {
|
||||
mask ^= (mask << s);
|
||||
v = ((v >> s) & mask) | ((v << s) & ~mask);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/* dictScan() is used to iterate over the elements of a dictionary.
|
||||
*
|
||||
* Iterating works in the following way:
|
||||
*
|
||||
* 1) Initially you call the function using a cursor (v) value of 0.
|
||||
* 2) The function performs one step of the iteration, and returns the
|
||||
* new cursor value that you must use in the next call.
|
||||
* 3) When the returned cursor is 0, the iteration is complete.
|
||||
*
|
||||
* The function guarantees that all the elements that are present in the
|
||||
* dictionary from the start to the end of the iteration are returned.
|
||||
* However it is possible that some element is returned multiple time.
|
||||
*
|
||||
* For every element returned, the callback 'fn' passed as argument is
|
||||
* called, with 'privdata' as first argument and the dictionar entry
|
||||
* 'de' as second argument.
|
||||
*
|
||||
* HOW IT WORKS.
|
||||
*
|
||||
* The algorithm used in the iteration was designed by Pieter Noordhuis.
|
||||
* The main idea is to increment a cursor starting from the higher order
|
||||
* bits, that is, instead of incrementing the cursor normally, the bits
|
||||
* of the cursor are reversed, then the cursor is incremented, and finally
|
||||
* the bits are reversed again.
|
||||
*
|
||||
* This strategy is needed because the hash table may be resized from one
|
||||
* call to the other call of the same iteration.
|
||||
*
|
||||
* dict.c hash tables are always power of two in size, and they
|
||||
* use chaining, so the position of an element in a given table is given
|
||||
* always by computing the bitwise AND between Hash(key) and SIZE-1
|
||||
* (where SIZE-1 is always the mask that is equivalent to taking the rest
|
||||
* of the division between the Hash of the key and SIZE).
|
||||
*
|
||||
* For example if the current hash table size is 64, the mask is
|
||||
* (in binary) 1111. The position of a key in the hash table will be always
|
||||
* the last four bits of the hash output, and so forth.
|
||||
*
|
||||
* WHAT HAPPENS IF THE TABLE CHANGES IN SIZE?
|
||||
*
|
||||
* If the hash table grows, elements can go anyway in one multiple of
|
||||
* the old bucket: for example let's say that we already iterated with
|
||||
* a 4 bit cursor 1100, since the mask is 1111 (hash table size = 16).
|
||||
*
|
||||
* If the hash table will be resized to 64 elements, and the new mask will
|
||||
* be 111111, the new buckets that you obtain substituting in ??1100
|
||||
* either 0 or 1, can be targeted only by keys that we already visited
|
||||
* when scanning the bucket 1100 in the smaller hash table.
|
||||
*
|
||||
* By iterating the higher bits first, because of the inverted counter, the
|
||||
* cursor does not need to restart if the table size gets bigger, and will
|
||||
* just continue iterating with cursors that don't have '1100' at the end,
|
||||
* nor any other combination of final 4 bits already explored.
|
||||
*
|
||||
* Similarly when the table size shrinks over time, for example going from
|
||||
* 16 to 8, If a combination of the lower three bits (the mask for size 8
|
||||
* is 111) was already completely explored, it will not be visited again
|
||||
* as we are sure that, we tried for example, both 0111 and 1111 (all the
|
||||
* variations of the higher bit) so we don't need to test it again.
|
||||
*
|
||||
* WAIT... YOU HAVE *TWO* TABLES DURING REHASHING!
|
||||
*
|
||||
* Yes, this is true, but we always iterate the smaller one of the tables,
|
||||
* testing also all the expansions of the current cursor into the larger
|
||||
* table. So for example if the current cursor is 101 and we also have a
|
||||
* larger table of size 16, we also test (0)101 and (1)101 inside the larger
|
||||
* table. This reduces the problem back to having only one table, where
|
||||
* the larger one, if exists, is just an expansion of the smaller one.
|
||||
*
|
||||
* LIMITATIONS
|
||||
*
|
||||
* This iterator is completely stateless, and this is a huge advantage,
|
||||
* including no additional memory used.
|
||||
*
|
||||
* The disadvantages resulting from this design are:
|
||||
*
|
||||
* 1) It is possible that we return duplicated elements. However this is usually
|
||||
* easy to deal with in the application level.
|
||||
* 2) The iterator must return multiple elements per call, as it needs to always
|
||||
* return all the keys chained in a given bucket, and all the expansions, so
|
||||
* we are sure we don't miss keys moving.
|
||||
* 3) The reverse cursor is somewhat hard to understand at first, but this
|
||||
* comment is supposed to help.
|
||||
*/
|
||||
unsigned long dictScan(dict *d,
|
||||
unsigned long v,
|
||||
dictScanFunction *fn,
|
||||
void *privdata)
|
||||
{
|
||||
dictht *t0, *t1;
|
||||
const dictEntry *de;
|
||||
unsigned long m0, m1;
|
||||
|
||||
if (dictSize(d) == 0) return 0;
|
||||
|
||||
if (!dictIsRehashing(d)) {
|
||||
t0 = &(d->ht[0]);
|
||||
m0 = t0->sizemask;
|
||||
|
||||
/* Emit entries at cursor */
|
||||
de = t0->table[v & m0];
|
||||
while (de) {
|
||||
fn(privdata, de);
|
||||
de = de->next;
|
||||
}
|
||||
|
||||
} else {
|
||||
t0 = &d->ht[0];
|
||||
t1 = &d->ht[1];
|
||||
|
||||
/* Make sure t0 is the smaller and t1 is the bigger table */
|
||||
if (t0->size > t1->size) {
|
||||
t0 = &d->ht[1];
|
||||
t1 = &d->ht[0];
|
||||
}
|
||||
|
||||
m0 = t0->sizemask;
|
||||
m1 = t1->sizemask;
|
||||
|
||||
/* Emit entries at cursor */
|
||||
de = t0->table[v & m0];
|
||||
while (de) {
|
||||
fn(privdata, de);
|
||||
de = de->next;
|
||||
}
|
||||
|
||||
/* Iterate over indices in larger table that are the expansion
|
||||
* of the index pointed to by the cursor in the smaller table */
|
||||
do {
|
||||
/* Emit entries at cursor */
|
||||
de = t1->table[v & m1];
|
||||
while (de) {
|
||||
fn(privdata, de);
|
||||
de = de->next;
|
||||
}
|
||||
|
||||
/* Increment bits not covered by the smaller mask */
|
||||
v = (((v | m0) + 1) & ~m0) | (v & m0);
|
||||
|
||||
/* Continue while bits covered by mask difference is non-zero */
|
||||
} while (v & (m0 ^ m1));
|
||||
}
|
||||
|
||||
/* Set unmasked bits so incrementing the reversed cursor
|
||||
* operates on the masked bits of the smaller table */
|
||||
v |= ~m0;
|
||||
|
||||
/* Increment the reverse cursor */
|
||||
v = rev(v);
|
||||
v++;
|
||||
v = rev(v);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/* ------------------------- private functions ------------------------------ */
|
||||
|
||||
/* Expand the hash table if needed */
|
||||
@@ -834,7 +621,8 @@ static int _dictExpandIfNeeded(dict *d)
|
||||
(dict_can_resize ||
|
||||
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
|
||||
{
|
||||
return dictExpand(d, d->ht[0].used*2);
|
||||
return dictExpand(d, ((d->ht[0].size > d->ht[0].used) ?
|
||||
d->ht[0].size : d->ht[0].used)*2);
|
||||
}
|
||||
return DICT_OK;
|
||||
}
|
||||
|
||||
@@ -88,11 +88,8 @@ typedef struct dictIterator {
|
||||
dict *d;
|
||||
int table, index, safe;
|
||||
dictEntry *entry, *nextEntry;
|
||||
long long fingerprint; /* unsafe iterator fingerprint for misuse detection */
|
||||
} dictIterator;
|
||||
|
||||
typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
|
||||
/* This is the initial size of every hash table */
|
||||
#define DICT_HT_INITIAL_SIZE 4
|
||||
|
||||
@@ -167,7 +164,6 @@ int dictRehash(dict *d, int n);
|
||||
int dictRehashMilliseconds(dict *d, int ms);
|
||||
void dictSetHashFunctionSeed(unsigned int initval);
|
||||
unsigned int dictGetHashFunctionSeed(void);
|
||||
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, void *privdata);
|
||||
|
||||
/* Hash table types */
|
||||
extern dictType dictTypeHeapStringCopyKey;
|
||||
|
||||
+2
-6
@@ -36,13 +36,9 @@
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__OpenBSD__)
|
||||
#if defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
#define _XOPEN_SOURCE 700
|
||||
/*
|
||||
* On NetBSD, _XOPEN_SOURCE undefines _NETBSD_SOURCE and
|
||||
* thus hides inet_aton etc.
|
||||
*/
|
||||
#elif !defined(__NetBSD__)
|
||||
#else
|
||||
#define _XOPEN_SOURCE
|
||||
#endif
|
||||
|
||||
|
||||
+3
-13
@@ -69,11 +69,6 @@ struct commandHelp {
|
||||
"Pop a value from a list, push it to another list and return it; or block until one is available",
|
||||
2,
|
||||
"2.2.0" },
|
||||
{ "CLIENT GETNAME",
|
||||
"-",
|
||||
"Get the current connection name",
|
||||
9,
|
||||
"2.6.9" },
|
||||
{ "CLIENT KILL",
|
||||
"ip:port",
|
||||
"Kill the connection of a client",
|
||||
@@ -84,11 +79,6 @@ struct commandHelp {
|
||||
"Get the list of client connections",
|
||||
9,
|
||||
"2.4.0" },
|
||||
{ "CLIENT SETNAME",
|
||||
"connection-name",
|
||||
"Set the current connection name",
|
||||
9,
|
||||
"2.6.9" },
|
||||
{ "CONFIG GET",
|
||||
"parameter",
|
||||
"Get the value of a configuration parameter",
|
||||
@@ -290,7 +280,7 @@ struct commandHelp {
|
||||
1,
|
||||
"2.6.0" },
|
||||
{ "INFO",
|
||||
"[section]",
|
||||
"-",
|
||||
"Get information and statistics about the server",
|
||||
9,
|
||||
"1.0.0" },
|
||||
@@ -535,7 +525,7 @@ struct commandHelp {
|
||||
8,
|
||||
"1.0.0" },
|
||||
{ "SET",
|
||||
"key value [EX seconds] [PX milliseconds] [NX|XX]",
|
||||
"key value",
|
||||
"Set the string value of a key",
|
||||
1,
|
||||
"1.0.0" },
|
||||
@@ -675,7 +665,7 @@ struct commandHelp {
|
||||
7,
|
||||
"2.2.0" },
|
||||
{ "ZADD",
|
||||
"key score member [score member ...]",
|
||||
"key score member [score] [member]",
|
||||
"Add one or more members to a sorted set, or update its score if it already exists",
|
||||
4,
|
||||
"1.2.0" },
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#include <errno.h>
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "config.h"
|
||||
|
||||
#if (ULONG_MAX == 4294967295UL)
|
||||
#define MEMTEST_32BIT
|
||||
@@ -239,32 +238,6 @@ void memtest_test(size_t megabytes, int passes) {
|
||||
}
|
||||
}
|
||||
|
||||
void memtest_non_destructive_invert(void *addr, size_t size) {
|
||||
volatile unsigned long *p = addr;
|
||||
size_t words = size / sizeof(unsigned long);
|
||||
size_t j;
|
||||
|
||||
/* Invert */
|
||||
for (j = 0; j < words; j++)
|
||||
p[j] = ~p[j];
|
||||
}
|
||||
|
||||
void memtest_non_destructive_swap(void *addr, size_t size) {
|
||||
volatile unsigned long *p = addr;
|
||||
size_t words = size / sizeof(unsigned long);
|
||||
size_t j;
|
||||
|
||||
/* Swap */
|
||||
for (j = 0; j < words; j += 2) {
|
||||
unsigned long a, b;
|
||||
|
||||
a = p[j];
|
||||
b = p[j+1];
|
||||
p[j] = b;
|
||||
p[j+1] = a;
|
||||
}
|
||||
}
|
||||
|
||||
void memtest(size_t megabytes, int passes) {
|
||||
if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
|
||||
ws.ws_col = 80;
|
||||
|
||||
+3
-2
@@ -139,7 +139,7 @@ void migrateCommand(redisClient *c) {
|
||||
return;
|
||||
if (getLongFromObjectOrReply(c,c->argv[4],&dbid,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (timeout <= 0) timeout = 1;
|
||||
if (timeout <= 0) timeout = 1000;
|
||||
|
||||
/* Check if the key is here. If not we reply with success as there is
|
||||
* nothing to migrate (for instance the key expired in the meantime), but
|
||||
@@ -157,7 +157,8 @@ void migrateCommand(redisClient *c) {
|
||||
server.neterr);
|
||||
return;
|
||||
}
|
||||
if ((aeWait(fd,AE_WRITABLE,timeout*1000) & AE_WRITABLE) == 0) {
|
||||
if ((aeWait(fd,AE_WRITABLE,timeout) & AE_WRITABLE) == 0) {
|
||||
close(fd);
|
||||
addReplySds(c,sdsnew("-IOERR error or timeout connecting to the client\r\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#!/bin/sh
|
||||
GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n1`
|
||||
GIT_DIRTY=`git diff --no-ext-diff 2> /dev/null | wc -l`
|
||||
BUILD_ID=`uname -n`"-"`date +%s`
|
||||
test -f release.h || touch release.h
|
||||
(cat release.h | grep SHA1 | grep $GIT_SHA1) && \
|
||||
(cat release.h | grep DIRTY | grep $GIT_DIRTY) && exit 0 # Already up-to-date
|
||||
echo "#define REDIS_GIT_SHA1 \"$GIT_SHA1\"" > release.h
|
||||
echo "#define REDIS_GIT_DIRTY \"$GIT_DIRTY\"" >> release.h
|
||||
echo "#define REDIS_BUILD_ID \"$BUILD_ID\"" >> release.h
|
||||
touch release.c # Force recompile of release.c
|
||||
|
||||
+67
-156
@@ -29,7 +29,6 @@
|
||||
|
||||
#include "redis.h"
|
||||
#include <sys/uio.h>
|
||||
#include <math.h>
|
||||
|
||||
static void setProtocolError(redisClient *c, int pos);
|
||||
|
||||
@@ -88,24 +87,21 @@ redisClient *createClient(int fd) {
|
||||
c->ctime = c->lastinteraction = server.unixtime;
|
||||
c->authenticated = 0;
|
||||
c->replstate = REDIS_REPL_NONE;
|
||||
c->reploff = 0;
|
||||
c->repl_ack_off = 0;
|
||||
c->repl_ack_time = 0;
|
||||
c->slave_listening_port = 0;
|
||||
c->reply = listCreate();
|
||||
c->reply_bytes = 0;
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
listSetFreeMethod(c->reply,decrRefCountVoid);
|
||||
listSetFreeMethod(c->reply,decrRefCount);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
c->bpop.keys = dictCreate(&setDictType,NULL);
|
||||
c->bpop.timeout = 0;
|
||||
c->bpop.target = NULL;
|
||||
c->io_keys = listCreate();
|
||||
c->watched_keys = listCreate();
|
||||
listSetFreeMethod(c->io_keys,decrRefCountVoid);
|
||||
listSetFreeMethod(c->io_keys,decrRefCount);
|
||||
c->pubsub_channels = dictCreate(&setDictType,NULL);
|
||||
c->pubsub_patterns = listCreate();
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCount);
|
||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||
if (fd != -1) listAddNodeTail(server.clients,c);
|
||||
initClientMultiState(c);
|
||||
@@ -119,17 +115,15 @@ redisClient *createClient(int fd) {
|
||||
* returns REDIS_OK, and make sure to install the write handler in our event
|
||||
* loop so that when the socket is writable new data gets written.
|
||||
*
|
||||
* If the client should not receive new data, because it is a fake client,
|
||||
* a master, a slave not yet online, or because the setup of the write handler
|
||||
* failed, the function returns REDIS_ERR.
|
||||
* If the client should not receive new data, because it is a fake client
|
||||
* or a slave, or because the setup of the write handler failed, the function
|
||||
* returns REDIS_ERR.
|
||||
*
|
||||
* Typically gets called every time a reply is built, before adding more
|
||||
* data to the clients output buffers. If the function returns REDIS_ERR no
|
||||
* data should be appended to the output buffers. */
|
||||
int prepareClientToWrite(redisClient *c) {
|
||||
if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK;
|
||||
if ((c->flags & REDIS_MASTER) &&
|
||||
!(c->flags & REDIS_MASTER_FORCE_REPLY)) return REDIS_ERR;
|
||||
if (c->fd <= 0) return REDIS_ERR; /* Fake client */
|
||||
if (c->bufpos == 0 && listLength(c->reply) == 0 &&
|
||||
(c->replstate == REDIS_REPL_NONE ||
|
||||
@@ -416,15 +410,9 @@ void setDeferredMultiBulkLength(redisClient *c, void *node, long length) {
|
||||
void addReplyDouble(redisClient *c, double d) {
|
||||
char dbuf[128], sbuf[128];
|
||||
int dlen, slen;
|
||||
if (isinf(d)) {
|
||||
/* Libc in odd systems (Hi Solaris!) will format infinite in a
|
||||
* different way, so better to handle it in an explicit way. */
|
||||
addReplyBulkCString(c, d > 0 ? "inf" : "-inf");
|
||||
} else {
|
||||
dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
|
||||
slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
|
||||
addReplyString(c,sbuf,slen);
|
||||
}
|
||||
dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
|
||||
slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
|
||||
addReplyString(c,sbuf,slen);
|
||||
}
|
||||
|
||||
/* Add a long long as integer reply or bulk len / multi bulk count.
|
||||
@@ -461,10 +449,7 @@ void addReplyLongLong(redisClient *c, long long ll) {
|
||||
}
|
||||
|
||||
void addReplyMultiBulkLen(redisClient *c, long length) {
|
||||
if (length < REDIS_SHARED_BULKHDR_LEN)
|
||||
addReply(c,shared.mbulkhdr[length]);
|
||||
else
|
||||
addReplyLongLongWithPrefix(c,length,'*');
|
||||
addReplyLongLongWithPrefix(c,length,'*');
|
||||
}
|
||||
|
||||
/* Create the length prefix of a bulk reply, example: $2234 */
|
||||
@@ -486,11 +471,7 @@ void addReplyBulkLen(redisClient *c, robj *obj) {
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
if (len < REDIS_SHARED_BULKHDR_LEN)
|
||||
addReply(c,shared.bulkhdr[len]);
|
||||
else
|
||||
addReplyLongLongWithPrefix(c,len,'$');
|
||||
addReplyLongLongWithPrefix(c,len,'$');
|
||||
}
|
||||
|
||||
/* Add a Redis Object as a bulk reply */
|
||||
@@ -566,12 +547,12 @@ static void acceptCommonHandler(int fd, int flags) {
|
||||
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cport, cfd;
|
||||
char cip[REDIS_IP_STR_LEN];
|
||||
char cip[128];
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, &cport);
|
||||
if (cfd == AE_ERR) {
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
@@ -614,42 +595,12 @@ void disconnectSlaves(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called when the slave lose the connection with the
|
||||
* master into an unexpected way. */
|
||||
void replicationHandleMasterDisconnection(void) {
|
||||
server.master = NULL;
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
server.repl_down_since = server.unixtime;
|
||||
/* We lost connection with our master, force our slaves to resync
|
||||
* with us as well to load the new data set.
|
||||
*
|
||||
* If server.masterhost is NULL the user called SLAVEOF NO ONE so
|
||||
* slave resync is not needed. */
|
||||
if (server.masterhost != NULL) disconnectSlaves();
|
||||
}
|
||||
|
||||
void freeClient(redisClient *c) {
|
||||
listNode *ln;
|
||||
|
||||
/* If this is marked as current client unset it */
|
||||
if (server.current_client == c) server.current_client = NULL;
|
||||
|
||||
/* If it is our master that's beging disconnected we should make sure
|
||||
* to cache the state to try a partial resynchronization later.
|
||||
*
|
||||
* Note that before doing this we make sure that the client is not in
|
||||
* some unexpected state, by checking its flags. */
|
||||
if (server.master &&
|
||||
(c->flags & REDIS_MASTER) &&
|
||||
!(c->flags & (REDIS_CLOSE_AFTER_REPLY|
|
||||
REDIS_CLOSE_ASAP|
|
||||
REDIS_BLOCKED|
|
||||
REDIS_UNBLOCKED)))
|
||||
{
|
||||
replicationCacheMaster(c);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Note that if the client we are freeing is blocked into a blocking
|
||||
* call, we have to set querybuf to NULL *before* to call
|
||||
* unblockClientWaitingData() to avoid processInputBuffer() will get
|
||||
@@ -669,21 +620,16 @@ void freeClient(redisClient *c) {
|
||||
pubsubUnsubscribeAllPatterns(c,0);
|
||||
dictRelease(c->pubsub_channels);
|
||||
listRelease(c->pubsub_patterns);
|
||||
/* Close socket, unregister events, and remove list of replies and
|
||||
* accumulated arguments. */
|
||||
if (c->fd != -1) {
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
||||
close(c->fd);
|
||||
}
|
||||
/* Obvious cleanup */
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
||||
listRelease(c->reply);
|
||||
freeClientArgv(c);
|
||||
close(c->fd);
|
||||
/* Remove from the list of clients */
|
||||
if (c->fd != -1) {
|
||||
ln = listSearchKey(server.clients,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(server.clients,ln);
|
||||
}
|
||||
ln = listSearchKey(server.clients,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(server.clients,ln);
|
||||
/* When client was just unblocked because of a blocking operation,
|
||||
* remove it from the list with unblocked clients. */
|
||||
if (c->flags & REDIS_UNBLOCKED) {
|
||||
@@ -701,16 +647,20 @@ void freeClient(redisClient *c) {
|
||||
ln = listSearchKey(l,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(l,ln);
|
||||
/* We need to remember the time when we started to have zero
|
||||
* attached slaves, as after some time we'll free the replication
|
||||
* backlog. */
|
||||
if (c->flags & REDIS_SLAVE && listLength(server.slaves) == 0)
|
||||
server.repl_no_slaves_since = server.unixtime;
|
||||
refreshGoodSlavesCount();
|
||||
}
|
||||
|
||||
/* Case 2: we lost the connection with the master. */
|
||||
if (c->flags & REDIS_MASTER) replicationHandleMasterDisconnection();
|
||||
if (c->flags & REDIS_MASTER) {
|
||||
server.master = NULL;
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
server.repl_down_since = server.unixtime;
|
||||
/* We lost connection with our master, force our slaves to resync
|
||||
* with us as well to load the new data set.
|
||||
*
|
||||
* If server.masterhost is NULL the user called SLAVEOF NO ONE so
|
||||
* slave resync is not needed. */
|
||||
if (server.masterhost != NULL) disconnectSlaves();
|
||||
}
|
||||
|
||||
/* If this client was scheduled for async freeing we need to remove it
|
||||
* from the queue. */
|
||||
@@ -758,8 +708,13 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
|
||||
while(c->bufpos > 0 || listLength(c->reply)) {
|
||||
if (c->bufpos > 0) {
|
||||
nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
|
||||
if (nwritten <= 0) break;
|
||||
if (c->flags & REDIS_MASTER) {
|
||||
/* Don't reply to a master */
|
||||
nwritten = c->bufpos - c->sentlen;
|
||||
} else {
|
||||
nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
|
||||
if (nwritten <= 0) break;
|
||||
}
|
||||
c->sentlen += nwritten;
|
||||
totwritten += nwritten;
|
||||
|
||||
@@ -779,8 +734,13 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen);
|
||||
if (nwritten <= 0) break;
|
||||
if (c->flags & REDIS_MASTER) {
|
||||
/* Don't reply to a master */
|
||||
nwritten = objlen - c->sentlen;
|
||||
} else {
|
||||
nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen);
|
||||
if (nwritten <= 0) break;
|
||||
}
|
||||
c->sentlen += nwritten;
|
||||
totwritten += nwritten;
|
||||
|
||||
@@ -813,13 +773,7 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (totwritten > 0) {
|
||||
/* For clients representing masters we don't count sending data
|
||||
* as an interaction, since we always send REPLCONF ACK commands
|
||||
* that take some time to just fill the socket output buffer.
|
||||
* We just rely on data / pings received for timeout detection. */
|
||||
if (!(c->flags & REDIS_MASTER)) c->lastinteraction = server.unixtime;
|
||||
}
|
||||
if (totwritten > 0) c->lastinteraction = server.unixtime;
|
||||
if (c->bufpos == 0 && listLength(c->reply) == 0) {
|
||||
c->sentlen = 0;
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
||||
@@ -842,7 +796,7 @@ void resetClient(redisClient *c) {
|
||||
int processInlineBuffer(redisClient *c) {
|
||||
char *newline = strstr(c->querybuf,"\r\n");
|
||||
int argc, j;
|
||||
sds *argv, aux;
|
||||
sds *argv;
|
||||
size_t querylen;
|
||||
|
||||
/* Nothing to do without a \r\n */
|
||||
@@ -856,12 +810,10 @@ int processInlineBuffer(redisClient *c) {
|
||||
|
||||
/* Split the input buffer up to the \r\n */
|
||||
querylen = newline-(c->querybuf);
|
||||
aux = sdsnewlen(c->querybuf,querylen);
|
||||
argv = sdssplitargs(aux,&argc);
|
||||
sdsfree(aux);
|
||||
argv = sdssplitlen(c->querybuf,querylen," ",1,&argc);
|
||||
|
||||
/* Leave data after the first line of the query in the buffer */
|
||||
sdsrange(c->querybuf,querylen+2,-1);
|
||||
c->querybuf = sdsrange(c->querybuf,querylen+2,-1);
|
||||
|
||||
/* Setup argv array on client structure */
|
||||
if (c->argv) zfree(c->argv);
|
||||
@@ -890,7 +842,7 @@ static void setProtocolError(redisClient *c, int pos) {
|
||||
sdsfree(client);
|
||||
}
|
||||
c->flags |= REDIS_CLOSE_AFTER_REPLY;
|
||||
sdsrange(c->querybuf,pos,-1);
|
||||
c->querybuf = sdsrange(c->querybuf,pos,-1);
|
||||
}
|
||||
|
||||
int processMultibulkBuffer(redisClient *c) {
|
||||
@@ -928,7 +880,7 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
|
||||
pos = (newline-c->querybuf)+2;
|
||||
if (ll <= 0) {
|
||||
sdsrange(c->querybuf,pos,-1);
|
||||
c->querybuf = sdsrange(c->querybuf,pos,-1);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -973,19 +925,15 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
|
||||
pos += newline-(c->querybuf+pos)+2;
|
||||
if (ll >= REDIS_MBULK_BIG_ARG) {
|
||||
size_t qblen;
|
||||
|
||||
/* If we are going to read a large object from network
|
||||
* try to make it likely that it will start at c->querybuf
|
||||
* boundary so that we can optimize object creation
|
||||
* boundary so that we can optimized object creation
|
||||
* avoiding a large copy of data. */
|
||||
sdsrange(c->querybuf,pos,-1);
|
||||
c->querybuf = sdsrange(c->querybuf,pos,-1);
|
||||
pos = 0;
|
||||
qblen = sdslen(c->querybuf);
|
||||
/* Hint the sds library about the amount of bytes this string is
|
||||
* going to contain. */
|
||||
if (qblen < ll+2)
|
||||
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2-qblen);
|
||||
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2);
|
||||
}
|
||||
c->bulklen = ll;
|
||||
}
|
||||
@@ -1020,7 +968,7 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
}
|
||||
|
||||
/* Trim to pos */
|
||||
if (pos) sdsrange(c->querybuf,pos,-1);
|
||||
if (pos) c->querybuf = sdsrange(c->querybuf,pos,-1);
|
||||
|
||||
/* We're done when c->multibulk == 0 */
|
||||
if (c->multibulklen == 0) return REDIS_OK;
|
||||
@@ -1111,7 +1059,6 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
if (nread) {
|
||||
sdsIncrLen(c->querybuf,nread);
|
||||
c->lastinteraction = server.unixtime;
|
||||
if (c->flags & REDIS_MASTER) c->reploff += nread;
|
||||
} else {
|
||||
server.current_client = NULL;
|
||||
return;
|
||||
@@ -1148,52 +1095,14 @@ void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
*biggest_input_buffer = bib;
|
||||
}
|
||||
|
||||
/* This is an helper function for getClientPeerId().
|
||||
* It writes the specified ip/port to "peerid" as a null termiated string
|
||||
* in the form ip:port if ip does not contain ":" itself, otherwise
|
||||
* [ip]:port format is used (for IPv6 addresses basically). */
|
||||
void formatPeerId(char *peerid, size_t peerid_len, char *ip, int port) {
|
||||
if (strchr(ip,':'))
|
||||
snprintf(peerid,peerid_len,"[%s]:%d",ip,port);
|
||||
else
|
||||
snprintf(peerid,peerid_len,"%s:%d",ip,port);
|
||||
}
|
||||
|
||||
/* A Redis "Peer ID" is a colon separated ip:port pair.
|
||||
* For IPv4 it's in the form x.y.z.k:pork, example: "127.0.0.1:1234".
|
||||
* For IPv6 addresses we use [] around the IP part, like in "[::1]:1234".
|
||||
* For Unix socekts we use path:0, like in "/tmp/redis:0".
|
||||
*
|
||||
* A Peer ID always fits inside a buffer of REDIS_PEER_ID_LEN bytes, including
|
||||
* the null term.
|
||||
*
|
||||
* The function returns REDIS_OK on succcess, and REDIS_ERR on failure.
|
||||
*
|
||||
* On failure the function still populates 'peerid' with the "?:0" string
|
||||
* in case you want to relax error checking or need to display something
|
||||
* anyway (see anetPeerToString implementation for more info). */
|
||||
int getClientPeerId(redisClient *client, char *peerid, size_t peerid_len) {
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
int port;
|
||||
|
||||
if (client->flags & REDIS_UNIX_SOCKET) {
|
||||
/* Unix socket client. */
|
||||
snprintf(peerid,peerid_len,"%s:0",server.unixsocket);
|
||||
return REDIS_OK;
|
||||
} else {
|
||||
/* TCP client. */
|
||||
int retval = anetPeerToString(client->fd,ip,sizeof(ip),&port);
|
||||
formatPeerId(peerid,peerid_len,ip,port);
|
||||
return (retval == -1) ? REDIS_ERR : REDIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* Turn a Redis client into an sds string representing its state. */
|
||||
sds getClientInfoString(redisClient *client) {
|
||||
char peerid[REDIS_PEER_ID_LEN], flags[16], events[3], *p;
|
||||
char ip[32], flags[16], events[3], *p;
|
||||
int port = 0; /* initialized to zero for the unix socket case. */
|
||||
int emask;
|
||||
|
||||
getClientPeerId(client,peerid,sizeof(peerid));
|
||||
if (!(client->flags & REDIS_UNIX_SOCKET))
|
||||
anetPeerToString(client->fd,ip,&port);
|
||||
p = flags;
|
||||
if (client->flags & REDIS_SLAVE) {
|
||||
if (client->flags & REDIS_MONITOR)
|
||||
@@ -1218,8 +1127,9 @@ sds getClientInfoString(redisClient *client) {
|
||||
if (emask & AE_WRITABLE) *p++ = 'w';
|
||||
*p = '\0';
|
||||
return sdscatprintf(sdsempty(),
|
||||
"addr=%s fd=%d name=%s age=%ld idle=%ld flags=%s db=%d sub=%d psub=%d multi=%d qbuf=%lu qbuf-free=%lu obl=%lu oll=%lu omem=%lu events=%s cmd=%s",
|
||||
peerid,
|
||||
"addr=%s:%d fd=%d name=%s age=%ld idle=%ld flags=%s db=%d sub=%d psub=%d multi=%d qbuf=%lu qbuf-free=%lu obl=%lu oll=%lu omem=%lu events=%s cmd=%s",
|
||||
(client->flags & REDIS_UNIX_SOCKET) ? server.unixsocket : ip,
|
||||
port,
|
||||
client->fd,
|
||||
client->name ? (char*)client->name->ptr : "",
|
||||
(long)(server.unixtime - client->ctime),
|
||||
@@ -1269,12 +1179,13 @@ void clientCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"kill") && c->argc == 3) {
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
char peerid[REDIS_PEER_ID_LEN];
|
||||
char ip[32], addr[64];
|
||||
int port;
|
||||
|
||||
client = listNodeValue(ln);
|
||||
if (getClientPeerId(client,peerid,sizeof(peerid)) == REDIS_ERR)
|
||||
continue;
|
||||
if (strcmp(peerid,c->argv[2]->ptr) == 0) {
|
||||
if (anetPeerToString(client->fd,ip,&port) == -1) continue;
|
||||
snprintf(addr,sizeof(addr),"%s:%d",ip,port);
|
||||
if (strcmp(addr,c->argv[2]->ptr) == 0) {
|
||||
addReply(c,shared.ok);
|
||||
if (c == client) {
|
||||
client->flags |= REDIS_CLOSE_AFTER_REPLY;
|
||||
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "redis.h"
|
||||
|
||||
/* This file implements keyspace events notification via Pub/Sub ad
|
||||
* described at http://redis.io/topics/keyspace-events. */
|
||||
|
||||
/* Turn a string representing notification classes into an integer
|
||||
* representing notification classes flags xored.
|
||||
*
|
||||
* The function returns -1 if the input contains characters not mapping to
|
||||
* any class. */
|
||||
int keyspaceEventsStringToFlags(char *classes) {
|
||||
char *p = classes;
|
||||
int c, flags = 0;
|
||||
|
||||
while((c = *p++) != '\0') {
|
||||
switch(c) {
|
||||
case 'A': flags |= REDIS_NOTIFY_ALL; break;
|
||||
case 'g': flags |= REDIS_NOTIFY_GENERIC; break;
|
||||
case '$': flags |= REDIS_NOTIFY_STRING; break;
|
||||
case 'l': flags |= REDIS_NOTIFY_LIST; break;
|
||||
case 's': flags |= REDIS_NOTIFY_SET; break;
|
||||
case 'h': flags |= REDIS_NOTIFY_HASH; break;
|
||||
case 'z': flags |= REDIS_NOTIFY_ZSET; break;
|
||||
case 'x': flags |= REDIS_NOTIFY_EXPIRED; break;
|
||||
case 'e': flags |= REDIS_NOTIFY_EVICTED; break;
|
||||
case 'K': flags |= REDIS_NOTIFY_KEYSPACE; break;
|
||||
case 'E': flags |= REDIS_NOTIFY_KEYEVENT; break;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
/* This function does exactly the revese of the function above: it gets
|
||||
* as input an integer with the xored flags and returns a string representing
|
||||
* the selected classes. The string returned is an sds string that needs to
|
||||
* be released with sdsfree(). */
|
||||
sds keyspaceEventsFlagsToString(int flags) {
|
||||
sds res;
|
||||
|
||||
if ((flags & REDIS_NOTIFY_ALL) == REDIS_NOTIFY_ALL)
|
||||
return sdsnew("A");
|
||||
res = sdsempty();
|
||||
if (flags & REDIS_NOTIFY_GENERIC) res = sdscatlen(res,"g",1);
|
||||
if (flags & REDIS_NOTIFY_STRING) res = sdscatlen(res,"$",1);
|
||||
if (flags & REDIS_NOTIFY_LIST) res = sdscatlen(res,"l",1);
|
||||
if (flags & REDIS_NOTIFY_SET) res = sdscatlen(res,"s",1);
|
||||
if (flags & REDIS_NOTIFY_HASH) res = sdscatlen(res,"h",1);
|
||||
if (flags & REDIS_NOTIFY_ZSET) res = sdscatlen(res,"z",1);
|
||||
if (flags & REDIS_NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
|
||||
if (flags & REDIS_NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
|
||||
if (flags & REDIS_NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
|
||||
if (flags & REDIS_NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* The API provided to the rest of the Redis core is a simple function:
|
||||
*
|
||||
* notifyKeyspaceEvent(char *event, robj *key, int dbid);
|
||||
*
|
||||
* 'event' is a C string representing the event name.
|
||||
* 'key' is a Redis object representing the key name.
|
||||
* 'dbid' is the database ID where the key lives. */
|
||||
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
|
||||
sds chan;
|
||||
robj *chanobj, *eventobj;
|
||||
int len = -1;
|
||||
char buf[24];
|
||||
|
||||
/* If notifications for this class of events are off, return ASAP. */
|
||||
if (!(server.notify_keyspace_events & type)) return;
|
||||
|
||||
eventobj = createStringObject(event,strlen(event));
|
||||
|
||||
/* __keyspace@<db>__:<key> <event> notifications. */
|
||||
if (server.notify_keyspace_events & REDIS_NOTIFY_KEYSPACE) {
|
||||
chan = sdsnewlen("__keyspace@",11);
|
||||
len = ll2string(buf,sizeof(buf),dbid);
|
||||
chan = sdscatlen(chan, buf, len);
|
||||
chan = sdscatlen(chan, "__:", 3);
|
||||
chan = sdscatsds(chan, key->ptr);
|
||||
chanobj = createObject(REDIS_STRING, chan);
|
||||
pubsubPublishMessage(chanobj, eventobj);
|
||||
decrRefCount(chanobj);
|
||||
}
|
||||
|
||||
/* __keyevente@<db>__:<event> <key> notifications. */
|
||||
if (server.notify_keyspace_events & REDIS_NOTIFY_KEYEVENT) {
|
||||
chan = sdsnewlen("__keyevent@",11);
|
||||
if (len == -1) len = ll2string(buf,sizeof(buf),dbid);
|
||||
chan = sdscatlen(chan, buf, len);
|
||||
chan = sdscatlen(chan, "__:", 3);
|
||||
chan = sdscatsds(chan, eventobj->ptr);
|
||||
chanobj = createObject(REDIS_STRING, chan);
|
||||
pubsubPublishMessage(chanobj, key);
|
||||
decrRefCount(chanobj);
|
||||
}
|
||||
decrRefCount(eventobj);
|
||||
}
|
||||
+18
-70
@@ -97,7 +97,7 @@ robj *dupStringObject(robj *o) {
|
||||
robj *createListObject(void) {
|
||||
list *l = listCreate();
|
||||
robj *o = createObject(REDIS_LIST,l);
|
||||
listSetFreeMethod(l,decrRefCountVoid);
|
||||
listSetFreeMethod(l,decrRefCount);
|
||||
o->encoding = REDIS_ENCODING_LINKEDLIST;
|
||||
return o;
|
||||
}
|
||||
@@ -215,7 +215,9 @@ void incrRefCount(robj *o) {
|
||||
o->refcount++;
|
||||
}
|
||||
|
||||
void decrRefCount(robj *o) {
|
||||
void decrRefCount(void *obj) {
|
||||
robj *o = obj;
|
||||
|
||||
if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
|
||||
if (o->refcount == 1) {
|
||||
switch(o->type) {
|
||||
@@ -232,13 +234,6 @@ void decrRefCount(robj *o) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This variant of decrRefCount() gets its argument as void, and is useful
|
||||
* as free method in data structures that expect a 'void free_object(void*)'
|
||||
* prototype for the free method. */
|
||||
void decrRefCountVoid(void *o) {
|
||||
decrRefCount(o);
|
||||
}
|
||||
|
||||
/* This function set the ref count to zero without freeing the object.
|
||||
* It is useful in order to pass a new object to functions incrementing
|
||||
* the ref count of the received object. Example:
|
||||
@@ -278,7 +273,6 @@ int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
|
||||
robj *tryObjectEncoding(robj *o) {
|
||||
long value;
|
||||
sds s = o->ptr;
|
||||
size_t len;
|
||||
|
||||
if (o->encoding != REDIS_ENCODING_RAW)
|
||||
return o; /* Already encoded */
|
||||
@@ -292,27 +286,7 @@ robj *tryObjectEncoding(robj *o) {
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
|
||||
/* Check if we can represent this string as a long integer */
|
||||
len = sdslen(s);
|
||||
if (len > 21 || !string2l(s,len,&value)) {
|
||||
/* We can't encode the object...
|
||||
*
|
||||
* Do the last try, and at least optimize the SDS string inside
|
||||
* the string object to require little space, in case there
|
||||
* is more than 10% of free space at the end of the SDS string.
|
||||
*
|
||||
* We do that for larger strings, using the arbitrary value
|
||||
* of 32 bytes. This code was backported from the unstable branch
|
||||
* where this is performed when the object is too large to be
|
||||
* encoded as EMBSTR. */
|
||||
if (len > 32 &&
|
||||
o->encoding == REDIS_ENCODING_RAW &&
|
||||
sdsavail(s) > len/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
/* Return the original object. */
|
||||
return o;
|
||||
}
|
||||
if (!string2l(s,sdslen(s),&value)) return o;
|
||||
|
||||
/* Ok, this object can be encoded...
|
||||
*
|
||||
@@ -353,57 +327,35 @@ robj *getDecodedObject(robj *o) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Compare two string objects via strcmp() or strcoll() depending on flags.
|
||||
/* Compare two string objects via strcmp() or alike.
|
||||
* Note that the objects may be integer-encoded. In such a case we
|
||||
* use ll2string() to get a string representation of the numbers on the stack
|
||||
* and compare the strings, it's much faster than calling getDecodedObject().
|
||||
*
|
||||
* Important note: when REDIS_COMPARE_BINARY is used a binary-safe comparison
|
||||
* is used. */
|
||||
|
||||
#define REDIS_COMPARE_BINARY (1<<0)
|
||||
#define REDIS_COMPARE_COLL (1<<1)
|
||||
|
||||
int compareStringObjectsWithFlags(robj *a, robj *b, int flags) {
|
||||
* Important note: if objects are not integer encoded, but binary-safe strings,
|
||||
* sdscmp() from sds.c will apply memcmp() so this function ca be considered
|
||||
* binary safe. */
|
||||
int compareStringObjects(robj *a, robj *b) {
|
||||
redisAssertWithInfo(NULL,a,a->type == REDIS_STRING && b->type == REDIS_STRING);
|
||||
char bufa[128], bufb[128], *astr, *bstr;
|
||||
size_t alen, blen, minlen;
|
||||
int bothsds = 1;
|
||||
|
||||
if (a == b) return 0;
|
||||
if (a->encoding != REDIS_ENCODING_RAW) {
|
||||
alen = ll2string(bufa,sizeof(bufa),(long) a->ptr);
|
||||
ll2string(bufa,sizeof(bufa),(long) a->ptr);
|
||||
astr = bufa;
|
||||
bothsds = 0;
|
||||
} else {
|
||||
astr = a->ptr;
|
||||
alen = sdslen(astr);
|
||||
}
|
||||
if (b->encoding != REDIS_ENCODING_RAW) {
|
||||
blen = ll2string(bufb,sizeof(bufb),(long) b->ptr);
|
||||
ll2string(bufb,sizeof(bufb),(long) b->ptr);
|
||||
bstr = bufb;
|
||||
bothsds = 0;
|
||||
} else {
|
||||
bstr = b->ptr;
|
||||
blen = sdslen(bstr);
|
||||
}
|
||||
if (flags & REDIS_COMPARE_COLL) {
|
||||
return strcoll(astr,bstr);
|
||||
} else {
|
||||
int cmp;
|
||||
|
||||
minlen = (alen < blen) ? alen : blen;
|
||||
cmp = memcmp(astr,bstr,minlen);
|
||||
if (cmp == 0) return alen-blen;
|
||||
return cmp;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wrapper for compareStringObjectsWithFlags() using binary comparison. */
|
||||
int compareStringObjects(robj *a, robj *b) {
|
||||
return compareStringObjectsWithFlags(a,b,REDIS_COMPARE_BINARY);
|
||||
}
|
||||
|
||||
/* Wrapper for compareStringObjectsWithFlags() using collation. */
|
||||
int collateStringObjects(robj *a, robj *b) {
|
||||
return compareStringObjectsWithFlags(a,b,REDIS_COMPARE_COLL);
|
||||
return bothsds ? sdscmp(astr,bstr) : strcmp(astr,bstr);
|
||||
}
|
||||
|
||||
/* Equal string objects return 1 if the two objects are the same from the
|
||||
@@ -440,12 +392,8 @@ int getDoubleFromObject(robj *o, double *target) {
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
errno = 0;
|
||||
value = strtod(o->ptr, &eptr);
|
||||
if (isspace(((char*)o->ptr)[0]) ||
|
||||
eptr[0] != '\0' ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
errno == EINVAL ||
|
||||
isnan(value))
|
||||
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
|
||||
errno == ERANGE || isnan(value))
|
||||
return REDIS_ERR;
|
||||
} else if (o->encoding == REDIS_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
|
||||
+13
-1
@@ -39,6 +39,7 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -101,9 +102,10 @@ _pqsort(void *a, size_t n, size_t es,
|
||||
{
|
||||
char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
|
||||
size_t d, r;
|
||||
int swaptype, cmp_result;
|
||||
int swaptype, swap_cnt, cmp_result;
|
||||
|
||||
loop: SWAPINIT(a, es);
|
||||
swap_cnt = 0;
|
||||
if (n < 7) {
|
||||
for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es)
|
||||
for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0;
|
||||
@@ -130,6 +132,7 @@ loop: SWAPINIT(a, es);
|
||||
for (;;) {
|
||||
while (pb <= pc && (cmp_result = cmp(pb, a)) <= 0) {
|
||||
if (cmp_result == 0) {
|
||||
swap_cnt = 1;
|
||||
swap(pa, pb);
|
||||
pa += es;
|
||||
}
|
||||
@@ -137,6 +140,7 @@ loop: SWAPINIT(a, es);
|
||||
}
|
||||
while (pb <= pc && (cmp_result = cmp(pc, a)) >= 0) {
|
||||
if (cmp_result == 0) {
|
||||
swap_cnt = 1;
|
||||
swap(pc, pd);
|
||||
pd -= es;
|
||||
}
|
||||
@@ -145,9 +149,17 @@ loop: SWAPINIT(a, es);
|
||||
if (pb > pc)
|
||||
break;
|
||||
swap(pb, pc);
|
||||
swap_cnt = 1;
|
||||
pb += es;
|
||||
pc -= es;
|
||||
}
|
||||
if (swap_cnt == 0) { /* Switch to insertion sort */
|
||||
for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es)
|
||||
for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0;
|
||||
pl -= es)
|
||||
swap(pl, pl - es);
|
||||
return;
|
||||
}
|
||||
|
||||
pn = (char *) a + n * es;
|
||||
r = min(pa - (char *) a, pb - pa);
|
||||
|
||||
@@ -306,53 +306,5 @@ void punsubscribeCommand(redisClient *c) {
|
||||
|
||||
void publishCommand(redisClient *c) {
|
||||
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
|
||||
forceCommandPropagation(c,REDIS_PROPAGATE_REPL);
|
||||
addReplyLongLong(c,receivers);
|
||||
}
|
||||
|
||||
/* PUBSUB command for Pub/Sub introspection. */
|
||||
void pubsubCommand(redisClient *c) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"channels") &&
|
||||
(c->argc == 2 || c->argc ==3))
|
||||
{
|
||||
/* PUBSUB CHANNELS [<pattern>] */
|
||||
sds pat = (c->argc == 2) ? NULL : c->argv[2]->ptr;
|
||||
dictIterator *di = dictGetIterator(server.pubsub_channels);
|
||||
dictEntry *de;
|
||||
long mblen = 0;
|
||||
void *replylen;
|
||||
|
||||
replylen = addDeferredMultiBulkLength(c);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *cobj = dictGetKey(de);
|
||||
sds channel = cobj->ptr;
|
||||
|
||||
if (!pat || stringmatchlen(pat, sdslen(pat),
|
||||
channel, sdslen(channel),0))
|
||||
{
|
||||
addReplyBulk(c,cobj);
|
||||
mblen++;
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredMultiBulkLength(c,replylen,mblen);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) {
|
||||
/* PUBSUB NUMSUB [Channel_1 ... Channel_N] */
|
||||
int j;
|
||||
|
||||
addReplyMultiBulkLen(c,(c->argc-2)*2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
list *l = dictFetchValue(server.pubsub_channels,c->argv[j]);
|
||||
|
||||
addReplyBulk(c,c->argv[j]);
|
||||
addReplyBulkLongLong(c,l ? listLength(l) : 0);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) {
|
||||
/* PUBSUB NUMPAT */
|
||||
addReplyLongLong(c,listLength(server.pubsub_patterns));
|
||||
} else {
|
||||
addReplyErrorFormat(c,
|
||||
"Unknown PUBSUB subcommand or wrong number of arguments for '%s'",
|
||||
(char*)c->argv[1]->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ int rdbSaveLen(rio *rdb, uint32_t len) {
|
||||
buf[0] = (REDIS_RDB_32BITLEN<<6);
|
||||
if (rdbWriteRaw(rdb,buf,1) == -1) return -1;
|
||||
len = htonl(len);
|
||||
if (rdbWriteRaw(rdb,&len,4) == -1) return -1;
|
||||
if (rdbWriteRaw(rdb,&len,4) == -4) return -1;
|
||||
nwritten = 1+4;
|
||||
}
|
||||
return nwritten;
|
||||
@@ -729,15 +729,15 @@ int rdbSaveBackground(char *filename) {
|
||||
int retval;
|
||||
|
||||
/* Child */
|
||||
closeListeningSockets(0);
|
||||
redisSetProcTitle("redis-rdb-bgsave");
|
||||
if (server.ipfd > 0) close(server.ipfd);
|
||||
if (server.sofd > 0) close(server.sofd);
|
||||
retval = rdbSave(filename);
|
||||
if (retval == REDIS_OK) {
|
||||
size_t private_dirty = zmalloc_get_private_dirty();
|
||||
|
||||
if (private_dirty) {
|
||||
redisLog(REDIS_NOTICE,
|
||||
"RDB: %zu MB of memory used by copy-on-write",
|
||||
"RDB: %lu MB of memory used by copy-on-write",
|
||||
private_dirty/(1024*1024));
|
||||
}
|
||||
}
|
||||
@@ -746,7 +746,6 @@ int rdbSaveBackground(char *filename) {
|
||||
/* Parent */
|
||||
server.stat_fork_time = ustime()-start;
|
||||
if (childpid == -1) {
|
||||
server.lastbgsave_status = REDIS_ERR;
|
||||
redisLog(REDIS_WARNING,"Can't save in background: fork: %s",
|
||||
strerror(errno));
|
||||
return REDIS_ERR;
|
||||
@@ -774,6 +773,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
size_t len;
|
||||
unsigned int i;
|
||||
|
||||
redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",rdbtype,rioTell(rdb));
|
||||
if (rdbtype == REDIS_RDB_TYPE_STRING) {
|
||||
/* Read string value */
|
||||
if ((o = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
|
||||
@@ -1058,32 +1058,21 @@ void stopLoading(void) {
|
||||
server.loading = 0;
|
||||
}
|
||||
|
||||
/* Track loading progress in order to serve client's from time to time
|
||||
and if needed calculate rdb checksum */
|
||||
void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
||||
if (server.rdb_checksum)
|
||||
rioGenericUpdateChecksum(r, buf, len);
|
||||
if (server.loading_process_events_interval_bytes &&
|
||||
(r->processed_bytes + len)/server.loading_process_events_interval_bytes > r->processed_bytes/server.loading_process_events_interval_bytes) {
|
||||
loadingProgress(r->processed_bytes);
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
}
|
||||
}
|
||||
|
||||
int rdbLoad(char *filename) {
|
||||
uint32_t dbid;
|
||||
int type, rdbver;
|
||||
redisDb *db = server.db+0;
|
||||
char buf[1024];
|
||||
long long expiretime, now = mstime();
|
||||
long loops = 0;
|
||||
FILE *fp;
|
||||
rio rdb;
|
||||
|
||||
if ((fp = fopen(filename,"r")) == NULL) return REDIS_ERR;
|
||||
|
||||
rioInitWithFile(&rdb,fp);
|
||||
rdb.update_cksum = rdbLoadProgressCallback;
|
||||
rdb.max_processing_chunk = server.loading_process_events_interval_bytes;
|
||||
if (server.rdb_checksum)
|
||||
rdb.update_cksum = rioGenericUpdateChecksum;
|
||||
if (rioRead(&rdb,buf,9) == 0) goto eoferr;
|
||||
buf[9] = '\0';
|
||||
if (memcmp(buf,"REDIS",5) != 0) {
|
||||
@@ -1105,6 +1094,12 @@ int rdbLoad(char *filename) {
|
||||
robj *key, *val;
|
||||
expiretime = -1;
|
||||
|
||||
/* Serve the clients from time to time */
|
||||
if (!(loops++ % 1000)) {
|
||||
loadingProgress(rioTell(&rdb));
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
}
|
||||
|
||||
/* Read type. */
|
||||
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
|
||||
if (type == REDIS_RDB_OPCODE_EXPIRETIME) {
|
||||
|
||||
+38
-151
@@ -35,7 +35,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <signal.h>
|
||||
#include <assert.h>
|
||||
@@ -47,7 +46,6 @@
|
||||
#include "zmalloc.h"
|
||||
|
||||
#define REDIS_NOTUSED(V) ((void) V)
|
||||
#define RANDPTR_INITIAL_SIZE 8
|
||||
|
||||
static struct config {
|
||||
aeEventLoop *el;
|
||||
@@ -74,24 +72,18 @@ static struct config {
|
||||
int csv;
|
||||
int loop;
|
||||
int idlemode;
|
||||
int dbnum;
|
||||
sds dbnumstr;
|
||||
char *tests;
|
||||
} config;
|
||||
|
||||
typedef struct _client {
|
||||
redisContext *context;
|
||||
sds obuf;
|
||||
char **randptr; /* Pointers to :rand: strings inside the command buf */
|
||||
size_t randlen; /* Number of pointers in client->randptr */
|
||||
size_t randfree; /* Number of unused pointers in client->randptr */
|
||||
unsigned int written; /* Bytes of 'obuf' already written */
|
||||
long long start; /* Start time of a request */
|
||||
long long latency; /* Request latency */
|
||||
int pending; /* Number of pending requests (replies to consume) */
|
||||
int selectlen; /* If non-zero, a SELECT of 'selectlen' bytes is currently
|
||||
used as a prefix of the pipline of commands. This gets
|
||||
discarded the first time it's sent. */
|
||||
char *randptr[32]; /* needed for MSET against 10 keys */
|
||||
size_t randlen;
|
||||
unsigned int written; /* bytes of 'obuf' already written */
|
||||
long long start; /* start time of a request */
|
||||
long long latency; /* request latency */
|
||||
int pending; /* Number of pending requests (sent but no reply received) */
|
||||
} *client;
|
||||
|
||||
/* Prototypes */
|
||||
@@ -125,7 +117,6 @@ static void freeClient(client c) {
|
||||
aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
|
||||
redisFree(c->context);
|
||||
sdsfree(c->obuf);
|
||||
zfree(c->randptr);
|
||||
zfree(c);
|
||||
config.liveclients--;
|
||||
ln = listSearchKey(config.clients,c);
|
||||
@@ -152,18 +143,13 @@ static void resetClient(client c) {
|
||||
}
|
||||
|
||||
static void randomizeClientKey(client c) {
|
||||
size_t i;
|
||||
char buf[32];
|
||||
size_t i, r;
|
||||
|
||||
for (i = 0; i < c->randlen; i++) {
|
||||
char *p = c->randptr[i]+11;
|
||||
size_t r = random() % config.randomkeys_keyspacelen;
|
||||
size_t j;
|
||||
|
||||
for (j = 0; j < 12; j++) {
|
||||
*p = '0'+r%10;
|
||||
r/=10;
|
||||
p--;
|
||||
}
|
||||
r = random() % config.randomkeys_keyspacelen;
|
||||
snprintf(buf,sizeof(buf),"%012zu",r);
|
||||
memcpy(c->randptr[i],buf,12);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,21 +198,6 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
|
||||
freeReplyObject(reply);
|
||||
|
||||
if (c->selectlen) {
|
||||
int j;
|
||||
|
||||
/* This is the OK from SELECT. Just discard the SELECT
|
||||
* from the buffer. */
|
||||
c->pending--;
|
||||
sdsrange(c->obuf,c->selectlen,-1);
|
||||
/* We also need to fix the pointers to the strings
|
||||
* we need to randomize. */
|
||||
for (j = 0; j < c->randlen; j++)
|
||||
c->randptr[j] -= c->selectlen;
|
||||
c->selectlen = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.requests_finished < config.requests)
|
||||
config.latency[config.requests_finished++] = c->latency;
|
||||
c->pending--;
|
||||
@@ -278,29 +249,7 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Create a benchmark client, configured to send the command passed as 'cmd' of
|
||||
* 'len' bytes.
|
||||
*
|
||||
* The command is copied N times in the client output buffer (that is reused
|
||||
* again and again to send the request to the server) accordingly to the configured
|
||||
* pipeline size.
|
||||
*
|
||||
* Also an initial SELECT command is prepended in order to make sure the right
|
||||
* database is selected, if needed. The initial SELECT will be discarded as soon
|
||||
* as the first reply is received.
|
||||
*
|
||||
* To create a client from scratch, the 'from' pointer is set to NULL. If instead
|
||||
* we want to create a client using another client as reference, the 'from' pointer
|
||||
* points to the client to use as reference. In such a case the following
|
||||
* information is take from the 'from' client:
|
||||
*
|
||||
* 1) The command line to use.
|
||||
* 2) The offsets of the __rand_int__ elements inside the command line, used
|
||||
* for arguments randomization.
|
||||
*
|
||||
* Even when cloning another client, the SELECT command is automatically prefixed
|
||||
* if needed. */
|
||||
static client createClient(char *cmd, size_t len, client from) {
|
||||
static client createClient(char *cmd, size_t len) {
|
||||
int j;
|
||||
client c = zmalloc(sizeof(struct _client));
|
||||
|
||||
@@ -319,68 +268,25 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
}
|
||||
/* Suppress hiredis cleanup of unused buffers for max speed. */
|
||||
c->context->reader->maxbuf = 0;
|
||||
|
||||
/* Build the request buffer:
|
||||
* Queue N requests accordingly to the pipeline size, or simply clone
|
||||
* the example client buffer. */
|
||||
/* Queue N requests accordingly to the pipeline size. */
|
||||
c->obuf = sdsempty();
|
||||
|
||||
/* If a DB number different than zero is selected, prefix our request
|
||||
* buffer with the SELECT command, that will be discarded the first
|
||||
* time the replies are received, so if the client is reused the
|
||||
* SELECT command will not be used again. */
|
||||
if (config.dbnum != 0) {
|
||||
c->obuf = sdscatprintf(c->obuf,"*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n",
|
||||
(int)sdslen(config.dbnumstr),config.dbnumstr);
|
||||
c->selectlen = sdslen(c->obuf);
|
||||
} else {
|
||||
c->selectlen = 0;
|
||||
}
|
||||
|
||||
/* Append the request itself. */
|
||||
if (from) {
|
||||
c->obuf = sdscatlen(c->obuf,
|
||||
from->obuf+from->selectlen,
|
||||
sdslen(from->obuf)-from->selectlen);
|
||||
} else {
|
||||
for (j = 0; j < config.pipeline; j++)
|
||||
c->obuf = sdscatlen(c->obuf,cmd,len);
|
||||
}
|
||||
for (j = 0; j < config.pipeline; j++)
|
||||
c->obuf = sdscatlen(c->obuf,cmd,len);
|
||||
c->randlen = 0;
|
||||
c->written = 0;
|
||||
c->pending = config.pipeline;
|
||||
c->randptr = NULL;
|
||||
c->randlen = 0;
|
||||
if (c->selectlen) c->pending++;
|
||||
|
||||
/* Find substrings in the output buffer that need to be randomized. */
|
||||
if (config.randomkeys) {
|
||||
if (from) {
|
||||
c->randlen = from->randlen;
|
||||
c->randfree = 0;
|
||||
c->randptr = zmalloc(sizeof(char*)*c->randlen);
|
||||
/* copy the offsets. */
|
||||
for (j = 0; j < c->randlen; j++) {
|
||||
c->randptr[j] = c->obuf + (from->randptr[j]-from->obuf);
|
||||
/* Adjust for the different select prefix length. */
|
||||
c->randptr[j] += c->selectlen - from->selectlen;
|
||||
}
|
||||
} else {
|
||||
char *p = c->obuf;
|
||||
|
||||
c->randlen = 0;
|
||||
c->randfree = RANDPTR_INITIAL_SIZE;
|
||||
c->randptr = zmalloc(sizeof(char*)*c->randfree);
|
||||
while ((p = strstr(p,"__rand_int__")) != NULL) {
|
||||
if (c->randfree == 0) {
|
||||
c->randptr = zrealloc(c->randptr,sizeof(char*)*c->randlen*2);
|
||||
c->randfree += c->randlen;
|
||||
}
|
||||
c->randptr[c->randlen++] = p;
|
||||
c->randfree--;
|
||||
p += 12; /* 12 is strlen("__rand_int__). */
|
||||
}
|
||||
char *p = c->obuf;
|
||||
while ((p = strstr(p,":rand:")) != NULL) {
|
||||
assert(c->randlen < (signed)(sizeof(c->randptr)/sizeof(char*)));
|
||||
c->randptr[c->randlen++] = p+6;
|
||||
p += 6;
|
||||
}
|
||||
}
|
||||
|
||||
/* redisSetReplyObjectFunctions(c->context,NULL); */
|
||||
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
|
||||
listAddNodeTail(config.clients,c);
|
||||
config.liveclients++;
|
||||
@@ -389,18 +295,9 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
|
||||
static void createMissingClients(client c) {
|
||||
int n = 0;
|
||||
char *buf = c->obuf;
|
||||
size_t buflen = sdslen(c->obuf);
|
||||
|
||||
/* If we are cloning from a client with a SELECT prefix, skip it since the
|
||||
* client will be created with the prefixed SELECT if needed. */
|
||||
if (c->selectlen) {
|
||||
buf += c->selectlen;
|
||||
buflen -= c->selectlen;
|
||||
}
|
||||
|
||||
while(config.liveclients < config.numclients) {
|
||||
createClient(NULL,0,c);
|
||||
createClient(c->obuf,sdslen(c->obuf)/config.pipeline);
|
||||
|
||||
/* Listen backlog is quite limited on most systems */
|
||||
if (++n > 64) {
|
||||
@@ -451,7 +348,7 @@ static void benchmark(char *title, char *cmd, int len) {
|
||||
config.requests_issued = 0;
|
||||
config.requests_finished = 0;
|
||||
|
||||
c = createClient(cmd,len,NULL);
|
||||
c = createClient(cmd,len);
|
||||
createMissingClients(c);
|
||||
|
||||
config.start = mstime();
|
||||
@@ -523,10 +420,6 @@ int parseOptions(int argc, const char **argv) {
|
||||
config.tests = sdscat(config.tests,(char*)argv[++i]);
|
||||
config.tests = sdscat(config.tests,",");
|
||||
sdstolower(config.tests);
|
||||
} else if (!strcmp(argv[i],"--dbnum")) {
|
||||
if (lastarg) goto invalid;
|
||||
config.dbnum = atoi(argv[++i]);
|
||||
config.dbnumstr = sdsfromlonglong(config.dbnum);
|
||||
} else if (!strcmp(argv[i],"--help")) {
|
||||
exit_status = 0;
|
||||
goto usage;
|
||||
@@ -553,14 +446,14 @@ usage:
|
||||
" -c <clients> Number of parallel connections (default 50)\n"
|
||||
" -n <requests> Total number of requests (default 10000)\n"
|
||||
" -d <size> Data size of SET/GET value in bytes (default 2)\n"
|
||||
" -dbnum <db> SELECT the specified db number (default 0)\n"
|
||||
" -k <boolean> 1=keep alive 0=reconnect (default 1)\n"
|
||||
" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD\n"
|
||||
" Using this option the benchmark will expand the string __rand_int__\n"
|
||||
" inside an argument with a 12 digits number in the specified range\n"
|
||||
" from 0 to keyspacelen-1. The substitution changes every time a command\n"
|
||||
" is executed. Default tests use this to hit random keys in the\n"
|
||||
" specified range.\n"
|
||||
" Using this option the benchmark will get/set keys\n"
|
||||
" in the form mykey_rand:000000012456 instead of constant\n"
|
||||
" keys, the <keyspacelen> argument determines the max\n"
|
||||
" number of values for the random number. For instance\n"
|
||||
" if set to 10 only rand:000000000000 - rand:000000000009\n"
|
||||
" range will be allowed.\n"
|
||||
" -P <numreq> Pipeline <numreq> requests. Default 1 (no pipeline).\n"
|
||||
" -q Quiet. Just show query/sec values\n"
|
||||
" --csv Output in CSV format\n"
|
||||
@@ -577,12 +470,8 @@ usage:
|
||||
" $ redis-benchmark -t set -n 1000000 -r 100000000\n\n"
|
||||
" Benchmark 127.0.0.1:6379 for a few commands producing CSV output:\n"
|
||||
" $ redis-benchmark -t ping,set,get -n 100000 --csv\n\n"
|
||||
" Benchmark a specific command line:\n"
|
||||
" $ redis-benchmark -r 10000 -n 10000 eval 'return redis.call(\"ping\")' 0\n\n"
|
||||
" Fill a list with 10000 random elements:\n"
|
||||
" $ redis-benchmark -r 10000 -n 10000 lpush mylist __rand_int__\n\n"
|
||||
" On user specified command lines __rand_int__ is replaced with a random integer\n"
|
||||
" with a range of values selected by the -r option.\n"
|
||||
" $ redis-benchmark -r 10000 -n 10000 lpush mylist ele:rand:000000000000\n\n"
|
||||
);
|
||||
exit(exit_status);
|
||||
}
|
||||
@@ -621,7 +510,6 @@ int main(int argc, const char **argv) {
|
||||
|
||||
client c;
|
||||
|
||||
srandom(time(NULL));
|
||||
signal(SIGHUP, SIG_IGN);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
@@ -645,7 +533,6 @@ int main(int argc, const char **argv) {
|
||||
config.hostport = 6379;
|
||||
config.hostsocket = NULL;
|
||||
config.tests = NULL;
|
||||
config.dbnum = 0;
|
||||
|
||||
i = parseOptions(argc,argv);
|
||||
argc -= i;
|
||||
@@ -659,7 +546,7 @@ int main(int argc, const char **argv) {
|
||||
|
||||
if (config.idlemode) {
|
||||
printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
|
||||
c = createClient("",0,NULL); /* will never receive a reply */
|
||||
c = createClient("",0); /* will never receive a reply */
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
/* and will wait for every */
|
||||
@@ -698,19 +585,19 @@ int main(int argc, const char **argv) {
|
||||
}
|
||||
|
||||
if (test_is_selected("set")) {
|
||||
len = redisFormatCommand(&cmd,"SET key:__rand_int__ %s",data);
|
||||
len = redisFormatCommand(&cmd,"SET foo:rand:000000000000 %s",data);
|
||||
benchmark("SET",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("get")) {
|
||||
len = redisFormatCommand(&cmd,"GET key:__rand_int__");
|
||||
len = redisFormatCommand(&cmd,"GET foo:rand:000000000000");
|
||||
benchmark("GET",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("incr")) {
|
||||
len = redisFormatCommand(&cmd,"INCR counter:__rand_int__");
|
||||
len = redisFormatCommand(&cmd,"INCR counter:rand:000000000000");
|
||||
benchmark("INCR",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
@@ -729,7 +616,7 @@ int main(int argc, const char **argv) {
|
||||
|
||||
if (test_is_selected("sadd")) {
|
||||
len = redisFormatCommand(&cmd,
|
||||
"SADD myset element:__rand_int__");
|
||||
"SADD myset counter:rand:000000000000");
|
||||
benchmark("SADD",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
@@ -779,7 +666,7 @@ int main(int argc, const char **argv) {
|
||||
const char *argv[21];
|
||||
argv[0] = "MSET";
|
||||
for (i = 1; i < 21; i += 2) {
|
||||
argv[i] = "key:__rand_int__";
|
||||
argv[i] = "foo:rand:000000000000";
|
||||
argv[i+1] = data;
|
||||
}
|
||||
len = redisFormatCommandArgv(&cmd,21,argv,NULL);
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include "lzf.h"
|
||||
#include "crc64.h"
|
||||
|
||||
/* Object types */
|
||||
#define REDIS_STRING 0
|
||||
@@ -141,6 +140,9 @@ static double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
/* store string types for output */
|
||||
static char types[256][16];
|
||||
|
||||
/* Prototypes */
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
|
||||
/* Return true if 't' is a valid object type. */
|
||||
int checkType(unsigned char t) {
|
||||
/* In case a new object type is added, update the following
|
||||
|
||||
+29
-58
@@ -58,7 +58,6 @@
|
||||
#define OUTPUT_RAW 1
|
||||
#define OUTPUT_CSV 2
|
||||
#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||
#define REDIS_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
|
||||
|
||||
static redisContext *context;
|
||||
static struct config {
|
||||
@@ -78,7 +77,6 @@ static struct config {
|
||||
int cluster_reissue_command;
|
||||
int slave_mode;
|
||||
int pipe_mode;
|
||||
int pipe_timeout;
|
||||
int getrdb_mode;
|
||||
int stat_mode;
|
||||
char *rdb_filename;
|
||||
@@ -116,8 +114,7 @@ static void cliRefreshPrompt(void) {
|
||||
len = snprintf(config.prompt,sizeof(config.prompt),"redis %s",
|
||||
config.hostsocket);
|
||||
else
|
||||
len = snprintf(config.prompt,sizeof(config.prompt),
|
||||
strchr(config.hostip,':') ? "[%s]:%d" : "%s:%d",
|
||||
len = snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d",
|
||||
config.hostip, config.hostport);
|
||||
/* Add [dbnum] if needed */
|
||||
if (config.dbnum != 0)
|
||||
@@ -716,8 +713,6 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.rdb_filename = argv[++i];
|
||||
} else if (!strcmp(argv[i],"--pipe")) {
|
||||
config.pipe_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--pipe-timeout") && !lastarg) {
|
||||
config.pipe_timeout = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--bigkeys")) {
|
||||
config.bigkeys = 1;
|
||||
} else if (!strcmp(argv[i],"--eval") && !lastarg) {
|
||||
@@ -770,33 +765,29 @@ static void usage() {
|
||||
"redis-cli %s\n"
|
||||
"\n"
|
||||
"Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
|
||||
" -h <hostname> Server hostname (default: 127.0.0.1)\n"
|
||||
" -p <port> Server port (default: 6379)\n"
|
||||
" -s <socket> Server socket (overrides hostname and port)\n"
|
||||
" -a <password> Password to use when connecting to the server\n"
|
||||
" -r <repeat> Execute specified command N times\n"
|
||||
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
|
||||
" It is possible to specify sub-second times like -i 0.1\n"
|
||||
" -n <db> Database number\n"
|
||||
" -x Read last argument from STDIN\n"
|
||||
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n"
|
||||
" -c Enable cluster mode (follow -ASK and -MOVED redirections)\n"
|
||||
" --raw Use raw formatting for replies (default when STDOUT is\n"
|
||||
" not a tty)\n"
|
||||
" --csv Output in CSV format\n"
|
||||
" --latency Enter a special mode continuously sampling latency\n"
|
||||
" --latency-history Like --latency but tracking latency changes over time.\n"
|
||||
" Default time interval is 15 sec. Change it using -i.\n"
|
||||
" --slave Simulate a slave showing commands received from the master\n"
|
||||
" --rdb <filename> Transfer an RDB dump from remote server to local file.\n"
|
||||
" --pipe Transfer raw Redis protocol from stdin to server\n"
|
||||
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data\n"
|
||||
" no reply is received within <n> seconds.\n"
|
||||
" Default timeout: %d. Use 0 to wait forever.\n"
|
||||
" --bigkeys Sample Redis keys looking for big keys\n"
|
||||
" --eval <file> Send an EVAL command using the Lua script at <file>\n"
|
||||
" --help Output this help and exit\n"
|
||||
" --version Output version and exit\n"
|
||||
" -h <hostname> Server hostname (default: 127.0.0.1)\n"
|
||||
" -p <port> Server port (default: 6379)\n"
|
||||
" -s <socket> Server socket (overrides hostname and port)\n"
|
||||
" -a <password> Password to use when connecting to the server\n"
|
||||
" -r <repeat> Execute specified command N times\n"
|
||||
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
|
||||
" It is possible to specify sub-second times like -i 0.1\n"
|
||||
" -n <db> Database number\n"
|
||||
" -x Read last argument from STDIN\n"
|
||||
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n"
|
||||
" -c Enable cluster mode (follow -ASK and -MOVED redirections)\n"
|
||||
" --raw Use raw formatting for replies (default when STDOUT is\n"
|
||||
" not a tty)\n"
|
||||
" --latency Enter a special mode continuously sampling latency\n"
|
||||
" --latency-history Like --latency but tracking latency changes over time.\n"
|
||||
" Default time interval is 15 sec. Change it using -i.\n"
|
||||
" --slave Simulate a slave showing commands received from the master\n"
|
||||
" --rdb <filename> Transfer an RDB dump from remote server to local file.\n"
|
||||
" --pipe Transfer raw Redis protocol from stdin to server\n"
|
||||
" --bigkeys Sample Redis keys looking for big keys\n"
|
||||
" --eval <file> Send an EVAL command using the Lua script at <file>\n"
|
||||
" --help Output this help and exit\n"
|
||||
" --version Output version and exit\n"
|
||||
"\n"
|
||||
"Examples:\n"
|
||||
" cat /etc/passwd | redis-cli -x set mypasswd\n"
|
||||
@@ -809,7 +800,7 @@ static void usage() {
|
||||
"When no command is given, redis-cli starts in interactive mode.\n"
|
||||
"Type \"help\" in interactive mode for information on available commands.\n"
|
||||
"\n",
|
||||
version, REDIS_DEFAULT_PIPE_TIMEOUT);
|
||||
version);
|
||||
sdsfree(version);
|
||||
exit(1);
|
||||
}
|
||||
@@ -1127,7 +1118,6 @@ static void pipeMode(void) {
|
||||
int eof = 0; /* True once we consumed all the standard input. */
|
||||
int done = 0;
|
||||
char magic[20]; /* Special reply we recognize. */
|
||||
time_t last_read_time = time(NULL);
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
@@ -1158,10 +1148,7 @@ static void pipeMode(void) {
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
if (nread > 0) {
|
||||
redisReaderFeed(reader,ibuf,nread);
|
||||
last_read_time = time(NULL);
|
||||
}
|
||||
if (nread > 0) redisReaderFeed(reader,ibuf,nread);
|
||||
} while(nread > 0);
|
||||
|
||||
/* Consume replies. */
|
||||
@@ -1216,12 +1203,8 @@ static void pipeMode(void) {
|
||||
ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf));
|
||||
|
||||
if (nread == 0) {
|
||||
/* The ECHO sequence starts with a "\r\n" so that if there
|
||||
* is garbage in the protocol we read from stdin, the ECHO
|
||||
* will likely still be properly formatted.
|
||||
* CRLF is ignored by Redis, so it has no effects. */
|
||||
char echo[] =
|
||||
"\r\n*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n";
|
||||
"*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n";
|
||||
int j;
|
||||
|
||||
eof = 1;
|
||||
@@ -1230,7 +1213,7 @@ static void pipeMode(void) {
|
||||
* to make sure everything was read from the server. */
|
||||
for (j = 0; j < 20; j++)
|
||||
magic[j] = rand() & 0xff;
|
||||
memcpy(echo+21,magic,20);
|
||||
memcpy(echo+19,magic,20);
|
||||
memcpy(obuf,echo,sizeof(echo)-1);
|
||||
obuf_len = sizeof(echo)-1;
|
||||
obuf_pos = 0;
|
||||
@@ -1247,18 +1230,6 @@ static void pipeMode(void) {
|
||||
if (obuf_len == 0 && eof) break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle timeout, that is, we reached EOF, and we are not getting
|
||||
* replies from the server for a few seconds, nor the final ECHO is
|
||||
* received. */
|
||||
if (eof && config.pipe_timeout > 0 &&
|
||||
time(NULL)-last_read_time > config.pipe_timeout)
|
||||
{
|
||||
fprintf(stderr,"No replies for %d seconds: exiting.\n",
|
||||
config.pipe_timeout);
|
||||
errors++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
redisReaderFree(reader);
|
||||
printf("errors: %lld, replies: %lld\n", errors, replies);
|
||||
@@ -1324,6 +1295,7 @@ static void findBigKeys(void) {
|
||||
} else if (!strcmp(reply2->str,"none")) {
|
||||
freeReplyObject(reply1);
|
||||
freeReplyObject(reply2);
|
||||
freeReplyObject(reply3);
|
||||
continue;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown key type '%s' for key '%s'\n",
|
||||
@@ -1513,7 +1485,6 @@ int main(int argc, char **argv) {
|
||||
config.getrdb_mode = 0;
|
||||
config.rdb_filename = NULL;
|
||||
config.pipe_mode = 0;
|
||||
config.pipe_timeout = REDIS_DEFAULT_PIPE_TIMEOUT;
|
||||
config.bigkeys = 0;
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
|
||||
+136
-439
File diff suppressed because it is too large
Load Diff
+24
-166
@@ -74,6 +74,8 @@
|
||||
#define REDIS_MAXIDLETIME 0 /* default client timeout: infinite */
|
||||
#define REDIS_DEFAULT_DBNUM 16
|
||||
#define REDIS_CONFIGLINE_MAX 1024
|
||||
#define REDIS_EXPIRELOOKUPS_PER_CRON 10 /* lookup 10 expires per loop */
|
||||
#define REDIS_EXPIRELOOKUPS_TIME_PERC 25 /* CPU max % for keys collection */
|
||||
#define REDIS_DBCRON_DBS_PER_CALL 16
|
||||
#define REDIS_MAX_WRITE_PER_EVENT (1024*64)
|
||||
#define REDIS_SHARED_SELECT_CMDS 10
|
||||
@@ -92,41 +94,7 @@
|
||||
#define REDIS_REPL_PING_SLAVE_PERIOD 10
|
||||
#define REDIS_RUN_ID_SIZE 40
|
||||
#define REDIS_OPS_SEC_SAMPLES 16
|
||||
#define REDIS_DEFAULT_REPL_BACKLOG_SIZE (1024*1024) /* 1mb */
|
||||
#define REDIS_DEFAULT_REPL_BACKLOG_TIME_LIMIT (60*60) /* 1 hour */
|
||||
#define REDIS_REPL_BACKLOG_MIN_SIZE (1024*16) /* 16k */
|
||||
#define REDIS_BGSAVE_RETRY_DELAY 5 /* Wait a few secs before trying again. */
|
||||
#define REDIS_DEFAULT_PID_FILE "/var/run/redis.pid"
|
||||
#define REDIS_DEFAULT_SYSLOG_IDENT "redis"
|
||||
#define REDIS_DEFAULT_CLUSTER_CONFIG_FILE "nodes.conf"
|
||||
#define REDIS_DEFAULT_DAEMONIZE 0
|
||||
#define REDIS_DEFAULT_UNIX_SOCKET_PERM 0
|
||||
#define REDIS_DEFAULT_TCP_KEEPALIVE 0
|
||||
#define REDIS_DEFAULT_LOGFILE ""
|
||||
#define REDIS_DEFAULT_SYSLOG_ENABLED 0
|
||||
#define REDIS_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR 1
|
||||
#define REDIS_DEFAULT_RDB_COMPRESSION 1
|
||||
#define REDIS_DEFAULT_RDB_CHECKSUM 1
|
||||
#define REDIS_DEFAULT_RDB_FILENAME "dump.rdb"
|
||||
#define REDIS_DEFAULT_SLAVE_SERVE_STALE_DATA 1
|
||||
#define REDIS_DEFAULT_SLAVE_READ_ONLY 1
|
||||
#define REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY 0
|
||||
#define REDIS_DEFAULT_MAXMEMORY 0
|
||||
#define REDIS_DEFAULT_MAXMEMORY_SAMPLES 3
|
||||
#define REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE 0
|
||||
#define REDIS_DEFAULT_ACTIVE_REHASHING 1
|
||||
#define REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1
|
||||
#define REDIS_DEFAULT_MIN_SLAVES_TO_WRITE 0
|
||||
#define REDIS_DEFAULT_MIN_SLAVES_MAX_LAG 10
|
||||
#define REDIS_IP_STR_LEN INET6_ADDRSTRLEN
|
||||
#define REDIS_PEER_ID_LEN (REDIS_IP_STR_LEN+32) /* Must be enough for ip:port */
|
||||
#define REDIS_BINDADDR_MAX 16
|
||||
|
||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
|
||||
#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* CPU max % for keys collection */
|
||||
#define ACTIVE_EXPIRE_CYCLE_SLOW 0
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST 1
|
||||
|
||||
/* Protocol and I/O related defines */
|
||||
#define REDIS_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */
|
||||
@@ -134,12 +102,7 @@
|
||||
#define REDIS_REPLY_CHUNK_BYTES (16*1024) /* 16k output buffer */
|
||||
#define REDIS_INLINE_MAX_SIZE (1024*64) /* Max size of inline reads */
|
||||
#define REDIS_MBULK_BIG_ARG (1024*32)
|
||||
#define REDIS_LONGSTR_SIZE 21 /* Bytes needed for long -> str */
|
||||
#define REDIS_AOF_AUTOSYNC_BYTES (1024*1024*32) /* fdatasync every 32MB */
|
||||
/* When configuring the Redis eventloop, we setup it so that the total number
|
||||
* of file descriptors we can handle are server.maxclients + FDSET_INCR
|
||||
* that is our safety margin. */
|
||||
#define REDIS_EVENTLOOP_FDSET_INCR 128
|
||||
|
||||
/* Hash table parameters */
|
||||
#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */
|
||||
@@ -149,7 +112,7 @@
|
||||
#define REDIS_CMD_WRITE 1 /* "w" flag */
|
||||
#define REDIS_CMD_READONLY 2 /* "r" flag */
|
||||
#define REDIS_CMD_DENYOOM 4 /* "m" flag */
|
||||
#define REDIS_CMD_NOT_USED_1 8 /* no longer used flag */
|
||||
#define REDIS_CMD_FORCE_REPLICATION 8 /* "f" flag */
|
||||
#define REDIS_CMD_ADMIN 16 /* "a" flag */
|
||||
#define REDIS_CMD_PUBSUB 32 /* "p" flag */
|
||||
#define REDIS_CMD_NOSCRIPT 64 /* "s" flag */
|
||||
@@ -225,10 +188,6 @@
|
||||
#define REDIS_CLOSE_ASAP (1<<10)/* Close this client ASAP */
|
||||
#define REDIS_UNIX_SOCKET (1<<11) /* Client connected via Unix domain socket */
|
||||
#define REDIS_DIRTY_EXEC (1<<12) /* EXEC will fail for errors while queueing */
|
||||
#define REDIS_MASTER_FORCE_REPLY (1<<13) /* Queue replies even if is master */
|
||||
#define REDIS_FORCE_AOF (1<<14) /* Force AOF propagation of current cmd. */
|
||||
#define REDIS_FORCE_REPL (1<<15) /* Force replication of current cmd. */
|
||||
#define REDIS_PRE_PSYNC_SLAVE (1<<16) /* Slave don't understand PSYNC. */
|
||||
|
||||
/* Client request types */
|
||||
#define REDIS_REQ_INLINE 1
|
||||
@@ -241,7 +200,7 @@
|
||||
#define REDIS_CLIENT_LIMIT_CLASS_PUBSUB 2
|
||||
#define REDIS_CLIENT_LIMIT_NUM_CLASSES 3
|
||||
|
||||
/* Slave replication state - from the point of view of the slave. */
|
||||
/* Slave replication state - slave side */
|
||||
#define REDIS_REPL_NONE 0 /* No active replication */
|
||||
#define REDIS_REPL_CONNECT 1 /* Must connect to master */
|
||||
#define REDIS_REPL_CONNECTING 2 /* Connecting to master */
|
||||
@@ -249,18 +208,18 @@
|
||||
#define REDIS_REPL_TRANSFER 4 /* Receiving .rdb from master */
|
||||
#define REDIS_REPL_CONNECTED 5 /* Connected to master */
|
||||
|
||||
/* Slave replication state - from the point of view of the master.
|
||||
* In SEND_BULK and ONLINE state the slave receives new updates
|
||||
* in its output queue. In the WAIT_BGSAVE state instead the server is waiting
|
||||
* to start the next background saving in order to send updates to it. */
|
||||
#define REDIS_REPL_WAIT_BGSAVE_START 6 /* We need to produce a new RDB file. */
|
||||
#define REDIS_REPL_WAIT_BGSAVE_END 7 /* Waiting RDB file creation to finish. */
|
||||
#define REDIS_REPL_SEND_BULK 8 /* Sending RDB file to slave. */
|
||||
#define REDIS_REPL_ONLINE 9 /* RDB file transmitted, sending just updates. */
|
||||
|
||||
/* Synchronous read timeout - slave side */
|
||||
#define REDIS_REPL_SYNCIO_TIMEOUT 5
|
||||
|
||||
/* Slave replication state - from the point of view of master
|
||||
* Note that in SEND_BULK and ONLINE state the slave receives new updates
|
||||
* in its output queue. In the WAIT_BGSAVE state instead the server is waiting
|
||||
* to start the next background saving in order to send updates to it. */
|
||||
#define REDIS_REPL_WAIT_BGSAVE_START 3 /* master waits bgsave to start feeding it */
|
||||
#define REDIS_REPL_WAIT_BGSAVE_END 4 /* master waits bgsave to start bulk DB transmission */
|
||||
#define REDIS_REPL_SEND_BULK 5 /* master is sending the bulk DB */
|
||||
#define REDIS_REPL_ONLINE 6 /* bulk DB already transmitted, receive updates */
|
||||
|
||||
/* List related stuff */
|
||||
#define REDIS_HEAD 0
|
||||
#define REDIS_TAIL 1
|
||||
@@ -277,7 +236,6 @@
|
||||
#define REDIS_NOTICE 2
|
||||
#define REDIS_WARNING 3
|
||||
#define REDIS_LOG_RAW (1<<10) /* Modifier to log without timestamp */
|
||||
#define REDIS_DEFAULT_VERBOSITY REDIS_NOTICE
|
||||
|
||||
/* Anti-warning macro... */
|
||||
#define REDIS_NOTUSED(V) ((void) V)
|
||||
@@ -289,7 +247,6 @@
|
||||
#define AOF_FSYNC_NO 0
|
||||
#define AOF_FSYNC_ALWAYS 1
|
||||
#define AOF_FSYNC_EVERYSEC 2
|
||||
#define REDIS_DEFAULT_AOF_FSYNC AOF_FSYNC_EVERYSEC
|
||||
|
||||
/* Zip structure related defaults */
|
||||
#define REDIS_HASH_MAX_ZIPLIST_ENTRIES 512
|
||||
@@ -312,7 +269,6 @@
|
||||
#define REDIS_MAXMEMORY_ALLKEYS_LRU 3
|
||||
#define REDIS_MAXMEMORY_ALLKEYS_RANDOM 4
|
||||
#define REDIS_MAXMEMORY_NO_EVICTION 5
|
||||
#define REDIS_DEFAULT_MAXMEMORY_POLICY REDIS_MAXMEMORY_VOLATILE_LRU
|
||||
|
||||
/* Scripting */
|
||||
#define REDIS_LUA_TIME_LIMIT 5000 /* milliseconds */
|
||||
@@ -338,20 +294,6 @@
|
||||
#define REDIS_PROPAGATE_AOF 1
|
||||
#define REDIS_PROPAGATE_REPL 2
|
||||
|
||||
/* Keyspace changes notification classes. Every class is associated with a
|
||||
* character for configuration purposes. */
|
||||
#define REDIS_NOTIFY_KEYSPACE (1<<0) /* K */
|
||||
#define REDIS_NOTIFY_KEYEVENT (1<<1) /* E */
|
||||
#define REDIS_NOTIFY_GENERIC (1<<2) /* g */
|
||||
#define REDIS_NOTIFY_STRING (1<<3) /* $ */
|
||||
#define REDIS_NOTIFY_LIST (1<<4) /* l */
|
||||
#define REDIS_NOTIFY_SET (1<<5) /* s */
|
||||
#define REDIS_NOTIFY_HASH (1<<6) /* h */
|
||||
#define REDIS_NOTIFY_ZSET (1<<7) /* z */
|
||||
#define REDIS_NOTIFY_EXPIRED (1<<8) /* x */
|
||||
#define REDIS_NOTIFY_EVICTED (1<<9) /* e */
|
||||
#define REDIS_NOTIFY_ALL (REDIS_NOTIFY_GENERIC | REDIS_NOTIFY_STRING | REDIS_NOTIFY_LIST | REDIS_NOTIFY_SET | REDIS_NOTIFY_HASH | REDIS_NOTIFY_ZSET | REDIS_NOTIFY_EXPIRED | REDIS_NOTIFY_EVICTED) /* A */
|
||||
|
||||
/* Using the following macro you can run code inside serverCron() with the
|
||||
* specified period, specified in milliseconds.
|
||||
* The actual resolution depends on server.hz. */
|
||||
@@ -398,7 +340,6 @@ typedef struct redisDb {
|
||||
dict *ready_keys; /* Blocked keys that received a PUSH */
|
||||
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
|
||||
int id;
|
||||
long long avg_ttl; /* Average TTL, just for stats */
|
||||
} redisDb;
|
||||
|
||||
/* Client MULTI/EXEC state */
|
||||
@@ -411,15 +352,13 @@ typedef struct multiCmd {
|
||||
typedef struct multiState {
|
||||
multiCmd *commands; /* Array of MULTI commands */
|
||||
int count; /* Total number of MULTI commands */
|
||||
int minreplicas; /* MINREPLICAS for synchronous replication */
|
||||
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
|
||||
} multiState;
|
||||
|
||||
typedef struct blockingState {
|
||||
dict *keys; /* The keys we are waiting to terminate a blocking
|
||||
* operation such as BLPOP. Otherwise NULL. */
|
||||
time_t timeout; /* Blocking operation timeout. If UNIX current time
|
||||
* is > timeout then the operation timed out. */
|
||||
* is >= timeout then the operation timed out. */
|
||||
robj *target; /* The key that should receive the element,
|
||||
* for BRPOPLPUSH. */
|
||||
} blockingState;
|
||||
@@ -457,21 +396,17 @@ typedef struct redisClient {
|
||||
long bulklen; /* length of bulk argument in multi bulk request */
|
||||
list *reply;
|
||||
unsigned long reply_bytes; /* Tot bytes of objects in reply list */
|
||||
int sentlen; /* Amount of bytes already sent in the current
|
||||
buffer or object being sent. */
|
||||
int sentlen;
|
||||
time_t ctime; /* Client creation time */
|
||||
time_t lastinteraction; /* time of the last interaction, used for timeout */
|
||||
time_t obuf_soft_limit_reached_time;
|
||||
int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */
|
||||
int slaveseldb; /* slave selected db, if this client is a slave */
|
||||
int authenticated; /* when requirepass is non-NULL */
|
||||
int replstate; /* replication state if this is a slave */
|
||||
int repldbfd; /* replication DB file descriptor */
|
||||
long repldboff; /* replication DB file offset */
|
||||
off_t repldbsize; /* replication DB file size */
|
||||
long long reploff; /* replication offset if this is our master */
|
||||
long long repl_ack_off; /* replication ack offset, if this is a slave */
|
||||
long long repl_ack_time;/* replication ack time, if this is a slave */
|
||||
char replrunid[REDIS_RUN_ID_SIZE+1]; /* master run id if this is a master */
|
||||
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
|
||||
multiState mstate; /* MULTI/EXEC state */
|
||||
blockingState bpop; /* blocking state */
|
||||
@@ -496,10 +431,10 @@ struct sharedObjectsStruct {
|
||||
*colon, *nullbulk, *nullmultibulk, *queued,
|
||||
*emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
|
||||
*outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
|
||||
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
|
||||
*masterdownerr, *roslaveerr, *execaborterr,
|
||||
*oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop,
|
||||
*lpush, *emptyscan,
|
||||
*lpush,
|
||||
*select[REDIS_SHARED_SELECT_CMDS],
|
||||
*integers[REDIS_SHARED_INTEGERS],
|
||||
*mbulkhdr[REDIS_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
|
||||
@@ -534,8 +469,6 @@ typedef struct clientBufferLimitsConfig {
|
||||
time_t soft_limit_seconds;
|
||||
} clientBufferLimitsConfig;
|
||||
|
||||
extern clientBufferLimitsConfig clientBufferLimitsDefaults[REDIS_CLIENT_LIMIT_NUM_CLASSES];
|
||||
|
||||
/* The redisOp structure defines a Redis Operation, that is an instance of
|
||||
* a command with an argument vector, database ID, propagation target
|
||||
* (REDIS_PROPAGATE_*), and command pointer.
|
||||
@@ -566,7 +499,6 @@ typedef struct redisOpArray {
|
||||
|
||||
struct redisServer {
|
||||
/* General */
|
||||
char *configfile; /* Absolute config file path, or NULL */
|
||||
int hz; /* serverCron() calls frequency in hertz */
|
||||
redisDb *db;
|
||||
dict *commands; /* Command table */
|
||||
@@ -584,12 +516,10 @@ struct redisServer {
|
||||
int sentinel_mode; /* True if this instance is a Sentinel. */
|
||||
/* Networking */
|
||||
int port; /* TCP listening port */
|
||||
char *bindaddr[REDIS_BINDADDR_MAX]; /* Addresses we should bind to */
|
||||
int bindaddr_count; /* Number of addresses in server.bindaddr[] */
|
||||
char *bindaddr; /* Bind address or NULL */
|
||||
char *unixsocket; /* UNIX socket path */
|
||||
mode_t unixsocketperm; /* UNIX socket permission */
|
||||
int ipfd[REDIS_BINDADDR_MAX]; /* TCP socket file descriptors */
|
||||
int ipfd_count; /* Used slots in ipfd[] */
|
||||
int ipfd; /* TCP socket file descriptor */
|
||||
int sofd; /* Unix socket file descriptor */
|
||||
list *clients; /* List of active clients */
|
||||
list *clients_to_close; /* Clients to close asynchronously */
|
||||
@@ -601,7 +531,6 @@ struct redisServer {
|
||||
off_t loading_total_bytes;
|
||||
off_t loading_loaded_bytes;
|
||||
time_t loading_start_time;
|
||||
off_t loading_process_events_interval_bytes;
|
||||
/* Fast pointers to often looked up command */
|
||||
struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand,
|
||||
*rpopCommand;
|
||||
@@ -616,9 +545,6 @@ struct redisServer {
|
||||
size_t stat_peak_memory; /* Max used memory record */
|
||||
long long stat_fork_time; /* Time needed to perform latest fork() */
|
||||
long long stat_rejected_conn; /* Clients rejected because of maxclients */
|
||||
long long stat_sync_full; /* Number of full resyncs with slaves. */
|
||||
long long stat_sync_partial_ok; /* Number of accepted PSYNC requests. */
|
||||
long long stat_sync_partial_err;/* Number of unaccepted PSYNC requests. */
|
||||
list *slowlog; /* SLOWLOG list of commands */
|
||||
long long slowlog_entry_id; /* SLOWLOG current entry ID */
|
||||
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
|
||||
@@ -682,30 +608,13 @@ struct redisServer {
|
||||
int syslog_enabled; /* Is syslog enabled? */
|
||||
char *syslog_ident; /* Syslog ident */
|
||||
int syslog_facility; /* Syslog facility */
|
||||
/* Replication (master) */
|
||||
int slaveseldb; /* Last SELECTed DB in replication output */
|
||||
long long master_repl_offset; /* Global replication offset */
|
||||
int repl_ping_slave_period; /* Master pings the slave every N seconds */
|
||||
char *repl_backlog; /* Replication backlog for partial syncs */
|
||||
long long repl_backlog_size; /* Backlog circular buffer size */
|
||||
long long repl_backlog_histlen; /* Backlog actual data length */
|
||||
long long repl_backlog_idx; /* Backlog circular buffer current offset */
|
||||
long long repl_backlog_off; /* Replication offset of first byte in the
|
||||
backlog buffer. */
|
||||
time_t repl_backlog_time_limit; /* Time without slaves after the backlog
|
||||
gets released. */
|
||||
time_t repl_no_slaves_since; /* We have no slaves since that time.
|
||||
Only valid if server.slaves len is 0. */
|
||||
int repl_min_slaves_to_write; /* Min number of slaves to write. */
|
||||
int repl_min_slaves_max_lag; /* Max lag of <count> slaves to write. */
|
||||
int repl_good_slaves_count; /* Number of slaves with lag <= max_lag. */
|
||||
/* Replication (slave) */
|
||||
/* Slave specific fields */
|
||||
char *masterauth; /* AUTH with this password with master */
|
||||
char *masterhost; /* Hostname of master */
|
||||
int masterport; /* Port of master */
|
||||
int repl_ping_slave_period; /* Master pings the slave every N seconds */
|
||||
int repl_timeout; /* Timeout after N seconds of master idle */
|
||||
redisClient *master; /* Client that is master for this slave */
|
||||
redisClient *cached_master; /* Cached master to be reused for PSYNC. */
|
||||
int repl_syncio_timeout; /* Timeout for synchronous I/O calls */
|
||||
int repl_state; /* Replication status if the instance is a slave */
|
||||
off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
|
||||
@@ -720,12 +629,6 @@ struct redisServer {
|
||||
time_t repl_down_since; /* Unix time at which link with master went down */
|
||||
int repl_disable_tcp_nodelay; /* Disable TCP_NODELAY after SYNC? */
|
||||
int slave_priority; /* Reported in INFO and used by Sentinel. */
|
||||
char repl_master_runid[REDIS_RUN_ID_SIZE+1]; /* Master run id for PSYNC. */
|
||||
long long repl_master_initial_offset; /* Master PSYNC offset. */
|
||||
/* Replication script cache. */
|
||||
dict *repl_scriptcache_dict; /* SHA1 all slaves are aware of. */
|
||||
list *repl_scriptcache_fifo; /* First in, first out LRU eviction. */
|
||||
int repl_scriptcache_size; /* Max number of elements. */
|
||||
/* Limits */
|
||||
unsigned int maxclients; /* Max number of simultaneous clients */
|
||||
unsigned long long maxmemory; /* Max number of memory bytes to use */
|
||||
@@ -740,7 +643,6 @@ struct redisServer {
|
||||
int sort_desc;
|
||||
int sort_alpha;
|
||||
int sort_bypattern;
|
||||
int sort_store;
|
||||
/* Zip structure config, see redis.conf for more information */
|
||||
size_t hash_max_ziplist_entries;
|
||||
size_t hash_max_ziplist_value;
|
||||
@@ -749,13 +651,10 @@ struct redisServer {
|
||||
size_t set_max_intset_entries;
|
||||
size_t zset_max_ziplist_entries;
|
||||
size_t zset_max_ziplist_value;
|
||||
time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
|
||||
time_t unixtime; /* Unix time sampled every second. */
|
||||
/* Pubsub */
|
||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||
list *pubsub_patterns; /* A list of pubsub_patterns */
|
||||
int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
|
||||
xor of REDIS_NOTIFY... flags. */
|
||||
/* Scripting */
|
||||
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
|
||||
redisClient *lua_client; /* The "fake client" to query Redis from Lua */
|
||||
@@ -871,7 +770,6 @@ extern dictType dbDictType;
|
||||
extern dictType shaScriptObjectDictType;
|
||||
extern double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
extern dictType hashDictType;
|
||||
extern dictType replScriptCacheDictType;
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Functions prototypes
|
||||
@@ -883,14 +781,11 @@ long long mstime(void);
|
||||
void getRandomHexChars(char *p, unsigned int len);
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
void exitFromChild(int retcode);
|
||||
size_t redisPopcount(void *s, long count);
|
||||
void redisSetProcTitle(char *title);
|
||||
|
||||
/* networking.c -- Networking and Client related operations */
|
||||
redisClient *createClient(int fd);
|
||||
void closeTimedoutClients(void);
|
||||
void freeClient(redisClient *c);
|
||||
void freeClientAsync(redisClient *c);
|
||||
void resetClient(redisClient *c);
|
||||
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReply(redisClient *c, robj *obj);
|
||||
@@ -917,8 +812,6 @@ void copyClientOutputBuffer(redisClient *dst, redisClient *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);
|
||||
int getClientPeerId(redisClient *client, char *peerid, size_t peerid_len);
|
||||
sds getClientInfoString(redisClient *client);
|
||||
sds getAllClientsInfoString(void);
|
||||
void rewriteClientCommandVector(redisClient *c, int argc, ...);
|
||||
@@ -969,8 +862,7 @@ void discardTransaction(redisClient *c);
|
||||
void flagTransaction(redisClient *c);
|
||||
|
||||
/* Redis object implementation */
|
||||
void decrRefCount(robj *o);
|
||||
void decrRefCountVoid(void *o);
|
||||
void decrRefCount(void *o);
|
||||
void incrRefCount(robj *o);
|
||||
robj *resetRefCount(robj *obj);
|
||||
void freeStringObject(robj *o);
|
||||
@@ -1003,7 +895,6 @@ int getLongDoubleFromObject(robj *o, long double *target);
|
||||
int getLongDoubleFromObjectOrReply(redisClient *c, robj *o, long double *target, const char *msg);
|
||||
char *strEncoding(int encoding);
|
||||
int compareStringObjects(robj *a, robj *b);
|
||||
int collateStringObjects(robj *a, robj *b);
|
||||
int equalStringObjects(robj *a, robj *b);
|
||||
unsigned long estimateObjectIdleTime(robj *o);
|
||||
|
||||
@@ -1017,14 +908,6 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc);
|
||||
void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **argv, int argc);
|
||||
void updateSlavesWaitingBgsave(int bgsaveerr);
|
||||
void replicationCron(void);
|
||||
void replicationHandleMasterDisconnection(void);
|
||||
void replicationCacheMaster(redisClient *c);
|
||||
void resizeReplicationBacklog(long long newsize);
|
||||
void refreshGoodSlavesCount(void);
|
||||
void replicationScriptCacheInit(void);
|
||||
void replicationScriptCacheFlush(void);
|
||||
void replicationScriptCacheAdd(sds sha1);
|
||||
int replicationScriptCacheExists(sds sha1);
|
||||
|
||||
/* Generic persistence functions */
|
||||
void startLoading(FILE *fp);
|
||||
@@ -1076,14 +959,8 @@ struct redisCommand *lookupCommandOrOriginal(sds name);
|
||||
void call(redisClient *c, int flags);
|
||||
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
|
||||
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
|
||||
void forceCommandPropagation(redisClient *c, int flags);
|
||||
int prepareForShutdown();
|
||||
#ifdef __GNUC__
|
||||
void redisLog(int level, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
#else
|
||||
void redisLog(int level, const char *fmt, ...);
|
||||
#endif
|
||||
void redisLogRaw(int level, const char *msg);
|
||||
void redisLogFromHandler(int level, const char *msg);
|
||||
void usage();
|
||||
@@ -1092,8 +969,6 @@ int htNeedsResize(dict *dict);
|
||||
void oom(const char *msg);
|
||||
void populateCommandTable(void);
|
||||
void resetCommandTableStats(void);
|
||||
void adjustOpenFilesLimit(void);
|
||||
void closeListeningSockets(int unlink_unix_socket);
|
||||
|
||||
/* Set data type */
|
||||
robj *setTypeCreate(robj *value);
|
||||
@@ -1135,18 +1010,10 @@ void freePubsubPattern(void *p);
|
||||
int listMatchPubsubPattern(void *a, void *b);
|
||||
int pubsubPublishMessage(robj *channel, robj *message);
|
||||
|
||||
/* Keyspace events notification */
|
||||
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid);
|
||||
int keyspaceEventsStringToFlags(char *classes);
|
||||
sds keyspaceEventsFlagsToString(int flags);
|
||||
|
||||
/* Configuration */
|
||||
void loadServerConfig(char *filename, char *options);
|
||||
void appendServerSaveParams(time_t seconds, int changes);
|
||||
void resetServerSaveParams();
|
||||
struct rewriteConfigState; /* Forward declaration to export API. */
|
||||
void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sds line, int force);
|
||||
int rewriteConfig(char *path);
|
||||
|
||||
/* db.c -- Keyspace access API */
|
||||
int removeExpire(redisDb *db, robj *key);
|
||||
@@ -1170,8 +1037,6 @@ int selectDb(redisClient *c, int id);
|
||||
void signalModifiedKey(redisDb *db, robj *key);
|
||||
void signalFlushedDb(int dbid);
|
||||
unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count);
|
||||
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor);
|
||||
int parseScanCursorOrReply(redisClient *c, robj *o, unsigned long *cursor);
|
||||
|
||||
/* API to get key arguments from commands */
|
||||
#define REDIS_GETKEYS_ALL 0
|
||||
@@ -1187,7 +1052,6 @@ void initSentinelConfig(void);
|
||||
void initSentinel(void);
|
||||
void sentinelTimer(void);
|
||||
char *sentinelHandleConfiguration(char **argv, int argc);
|
||||
void sentinelIsRunning(void);
|
||||
|
||||
/* Scripting */
|
||||
void scriptingInit(void);
|
||||
@@ -1195,7 +1059,6 @@ void scriptingInit(void);
|
||||
/* Git SHA1 */
|
||||
char *redisGitSHA1(void);
|
||||
char *redisGitDirty(void);
|
||||
uint64_t redisBuildId(void);
|
||||
|
||||
/* Commands prototypes */
|
||||
void authCommand(redisClient *c);
|
||||
@@ -1220,7 +1083,6 @@ void incrbyfloatCommand(redisClient *c);
|
||||
void selectCommand(redisClient *c);
|
||||
void randomkeyCommand(redisClient *c);
|
||||
void keysCommand(redisClient *c);
|
||||
void scanCommand(redisClient *c);
|
||||
void dbsizeCommand(redisClient *c);
|
||||
void lastsaveCommand(redisClient *c);
|
||||
void saveCommand(redisClient *c);
|
||||
@@ -1256,7 +1118,6 @@ void sunionCommand(redisClient *c);
|
||||
void sunionstoreCommand(redisClient *c);
|
||||
void sdiffCommand(redisClient *c);
|
||||
void sdiffstoreCommand(redisClient *c);
|
||||
void sscanCommand(redisClient *c);
|
||||
void syncCommand(redisClient *c);
|
||||
void flushdbCommand(redisClient *c);
|
||||
void flushallCommand(redisClient *c);
|
||||
@@ -1309,12 +1170,10 @@ void hlenCommand(redisClient *c);
|
||||
void zremrangebyrankCommand(redisClient *c);
|
||||
void zunionstoreCommand(redisClient *c);
|
||||
void zinterstoreCommand(redisClient *c);
|
||||
void zscanCommand(redisClient *c);
|
||||
void hkeysCommand(redisClient *c);
|
||||
void hvalsCommand(redisClient *c);
|
||||
void hgetallCommand(redisClient *c);
|
||||
void hexistsCommand(redisClient *c);
|
||||
void hscanCommand(redisClient *c);
|
||||
void configCommand(redisClient *c);
|
||||
void hincrbyCommand(redisClient *c);
|
||||
void hincrbyfloatCommand(redisClient *c);
|
||||
@@ -1323,7 +1182,6 @@ void unsubscribeCommand(redisClient *c);
|
||||
void psubscribeCommand(redisClient *c);
|
||||
void punsubscribeCommand(redisClient *c);
|
||||
void publishCommand(redisClient *c);
|
||||
void pubsubCommand(redisClient *c);
|
||||
void watchCommand(redisClient *c);
|
||||
void unwatchCommand(redisClient *c);
|
||||
void restoreCommand(redisClient *c);
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/* redisassert.h -- Drop in replacemnet assert.h that prints the stack trace
|
||||
* in the Redis logs.
|
||||
*
|
||||
* This file should be included instead of "assert.h" inside libraries used by
|
||||
* Redis that are using assertions, so instead of Redis disappearing with
|
||||
* SIGABORT, we get the details and stack trace inside the log file.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __REDIS_ASSERT_H__
|
||||
#define __REDIS_ASSERT_H__
|
||||
|
||||
#include <unistd.h> /* for _exit() */
|
||||
|
||||
#define assert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1)))
|
||||
|
||||
void _redisAssert(char *estr, char *file, int line);
|
||||
|
||||
#endif
|
||||
@@ -31,11 +31,7 @@
|
||||
* file is recompiled, as we access this information in all the other
|
||||
* files using this functions. */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "release.h"
|
||||
#include "version.h"
|
||||
#include "crc64.h"
|
||||
|
||||
char *redisGitSHA1(void) {
|
||||
return REDIS_GIT_SHA1;
|
||||
@@ -44,9 +40,3 @@ char *redisGitSHA1(void) {
|
||||
char *redisGitDirty(void) {
|
||||
return REDIS_GIT_DIRTY;
|
||||
}
|
||||
|
||||
uint64_t redisBuildId(void) {
|
||||
char *buildid = REDIS_VERSION REDIS_BUILD_ID REDIS_GIT_DIRTY REDIS_GIT_SHA1;
|
||||
|
||||
return crc64(0,(unsigned char*)buildid,strlen(buildid));
|
||||
}
|
||||
|
||||
+59
-823
File diff suppressed because it is too large
Load Diff
@@ -51,10 +51,11 @@
|
||||
#include <unistd.h>
|
||||
#include "rio.h"
|
||||
#include "util.h"
|
||||
#include "crc64.h"
|
||||
#include "config.h"
|
||||
#include "redis.h"
|
||||
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioBufferWrite(rio *r, const void *buf, size_t len) {
|
||||
r->io.buffer.ptr = sdscatlen(r->io.buffer.ptr,(char*)buf,len);
|
||||
@@ -108,8 +109,6 @@ static const rio rioBufferIO = {
|
||||
rioBufferTell,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
@@ -119,8 +118,6 @@ static const rio rioFileIO = {
|
||||
rioFileTell,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
|
||||
@@ -53,12 +53,6 @@ struct _rio {
|
||||
/* The current checksum */
|
||||
uint64_t cksum;
|
||||
|
||||
/* number of bytes read or written */
|
||||
size_t processed_bytes;
|
||||
|
||||
/* maximum single read or write chunk size */
|
||||
size_t max_processing_chunk;
|
||||
|
||||
/* Backend-specific vars. */
|
||||
union {
|
||||
struct {
|
||||
@@ -80,29 +74,16 @@ typedef struct _rio rio;
|
||||
* if needed. */
|
||||
|
||||
static inline size_t rioWrite(rio *r, const void *buf, size_t len) {
|
||||
while (len) {
|
||||
size_t bytes_to_write = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
|
||||
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_write);
|
||||
if (r->write(r,buf,bytes_to_write) == 0)
|
||||
return 0;
|
||||
buf = (char*)buf + bytes_to_write;
|
||||
len -= bytes_to_write;
|
||||
r->processed_bytes += bytes_to_write;
|
||||
}
|
||||
return 1;
|
||||
if (r->update_cksum) r->update_cksum(r,buf,len);
|
||||
return r->write(r,buf,len);
|
||||
}
|
||||
|
||||
static inline size_t rioRead(rio *r, void *buf, size_t len) {
|
||||
while (len) {
|
||||
size_t bytes_to_read = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
|
||||
if (r->read(r,buf,bytes_to_read) == 0)
|
||||
return 0;
|
||||
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_read);
|
||||
buf = (char*)buf + bytes_to_read;
|
||||
len -= bytes_to_read;
|
||||
r->processed_bytes += bytes_to_read;
|
||||
if (r->read(r,buf,len) == 1) {
|
||||
if (r->update_cksum) r->update_cksum(r,buf,len);
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline off_t rioTell(rio *r) {
|
||||
|
||||
+23
-73
@@ -152,20 +152,9 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply) {
|
||||
}
|
||||
|
||||
void luaPushError(lua_State *lua, char *error) {
|
||||
lua_Debug dbg;
|
||||
|
||||
lua_newtable(lua);
|
||||
lua_pushstring(lua,"err");
|
||||
|
||||
/* Attempt to figure out where this function was called, if possible */
|
||||
if(lua_getstack(lua, 1, &dbg) && lua_getinfo(lua, "nSl", &dbg)) {
|
||||
sds msg = sdscatprintf(sdsempty(), "%s: %d: %s",
|
||||
dbg.source, dbg.currentline, error);
|
||||
lua_pushstring(lua, msg);
|
||||
sdsfree(msg);
|
||||
} else {
|
||||
lua_pushstring(lua, error);
|
||||
}
|
||||
lua_pushstring(lua, error);
|
||||
lua_settable(lua,-3);
|
||||
}
|
||||
|
||||
@@ -269,7 +258,6 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
"Write commands not allowed after non deterministic commands");
|
||||
goto cleanup;
|
||||
} else if (server.masterhost && server.repl_slave_ro &&
|
||||
!server.loading &&
|
||||
!(server.lua_caller->flags & REDIS_MASTER))
|
||||
{
|
||||
luaPushError(lua, shared.roslaveerr->ptr);
|
||||
@@ -621,26 +609,6 @@ void scriptingInit(void) {
|
||||
lua_pcall(lua,0,0,0);
|
||||
}
|
||||
|
||||
/* Add a helper function we use for pcall error reporting.
|
||||
* Note that when the error is in the C function we want to report the
|
||||
* information about the caller, that's what makes sense from the point
|
||||
* of view of the user debugging a script. */
|
||||
{
|
||||
char *errh_func = "function __redis__err__handler(err)\n"
|
||||
" local i = debug.getinfo(2,'nSl')\n"
|
||||
" if i and i.what == 'C' then\n"
|
||||
" i = debug.getinfo(3,'nSl')\n"
|
||||
" end\n"
|
||||
" if i then\n"
|
||||
" return i.source .. ':' .. i.currentline .. ': ' .. err\n"
|
||||
" else\n"
|
||||
" return err\n"
|
||||
" end\n"
|
||||
"end\n";
|
||||
luaL_loadbuffer(lua,errh_func,strlen(errh_func),"@err_handler_def");
|
||||
lua_pcall(lua,0,0,0);
|
||||
}
|
||||
|
||||
/* Create the (non connected) client that we use to execute Redis commands
|
||||
* inside the Lua interpreter.
|
||||
* Note: there is no need to create it again when this function is called
|
||||
@@ -861,30 +829,21 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
funcname[42] = '\0';
|
||||
}
|
||||
|
||||
/* Push the pcall error handler function on the stack. */
|
||||
lua_getglobal(lua, "__redis__err__handler");
|
||||
|
||||
/* Try to lookup the Lua function */
|
||||
lua_getglobal(lua, funcname);
|
||||
if (lua_isnil(lua,-1)) {
|
||||
if (lua_isnil(lua,1)) {
|
||||
lua_pop(lua,1); /* remove the nil from the stack */
|
||||
/* Function not defined... let's define it if we have the
|
||||
* body of the function. If this is an EVALSHA call we can just
|
||||
* return an error. */
|
||||
if (evalsha) {
|
||||
lua_pop(lua,1); /* remove the error handler from the stack. */
|
||||
addReply(c, shared.noscripterr);
|
||||
return;
|
||||
}
|
||||
if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) {
|
||||
lua_pop(lua,1); /* remove the error handler from the stack. */
|
||||
/* The error is sent to the client by luaCreateFunction()
|
||||
* itself when it returns REDIS_ERR. */
|
||||
return;
|
||||
}
|
||||
if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) return;
|
||||
/* Now the following is guaranteed to return non nil */
|
||||
lua_getglobal(lua, funcname);
|
||||
redisAssert(!lua_isnil(lua,-1));
|
||||
redisAssert(!lua_isnil(lua,1));
|
||||
}
|
||||
|
||||
/* Populate the argv and keys table accordingly to the arguments that
|
||||
@@ -907,10 +866,10 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
delhook = 1;
|
||||
}
|
||||
|
||||
/* At this point whether this script was never seen before or if it was
|
||||
/* At this point whatever this script was never seen before or if it was
|
||||
* already defined, we can call it. We have zero arguments and expect
|
||||
* a single return value. */
|
||||
err = lua_pcall(lua,0,1,-2);
|
||||
err = lua_pcall(lua,0,1,0);
|
||||
|
||||
/* Perform some cleanup that we need to do both on error and success. */
|
||||
if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */
|
||||
@@ -928,37 +887,30 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
if (err) {
|
||||
addReplyErrorFormat(c,"Error running script (call to %s): %s\n",
|
||||
funcname, lua_tostring(lua,-1));
|
||||
lua_pop(lua,2); /* Consume the Lua reply and remove error handler. */
|
||||
lua_pop(lua,1); /* Consume the Lua reply. */
|
||||
} else {
|
||||
/* On success convert the Lua return value into Redis protocol, and
|
||||
* send it to * the client. */
|
||||
luaReplyToRedisReply(c,lua); /* Convert and consume the reply. */
|
||||
lua_pop(lua,1); /* Remove the error handler. */
|
||||
luaReplyToRedisReply(c,lua);
|
||||
}
|
||||
|
||||
/* EVALSHA should be propagated to Slave and AOF file as full EVAL, unless
|
||||
* we are sure that the script was already in the context of all the
|
||||
* attached slaves *and* the current AOF file if enabled.
|
||||
/* If we have slaves attached we want to replicate this command as
|
||||
* EVAL instead of EVALSHA. We do this also in the AOF as currently there
|
||||
* is no easy way to propagate a command in a different way in the AOF
|
||||
* and in the replication link.
|
||||
*
|
||||
* To do so we use a cache of SHA1s of scripts that we already propagated
|
||||
* as full EVAL, that's called the Replication Script Cache.
|
||||
*
|
||||
* For repliation, everytime a new slave attaches to the master, we need to
|
||||
* flush our cache of scripts that can be replicated as EVALSHA, while
|
||||
* for AOF we need to do so every time we rewrite the AOF file. */
|
||||
* IMPROVEMENT POSSIBLE:
|
||||
* 1) Replicate this command as EVALSHA in the AOF.
|
||||
* 2) Remember what slave already received a given script, and replicate
|
||||
* the EVALSHA against this slaves when possible.
|
||||
*/
|
||||
if (evalsha) {
|
||||
if (!replicationScriptCacheExists(c->argv[1]->ptr)) {
|
||||
/* This script is not in our script cache, replicate it as
|
||||
* EVAL, then add it into the script cache, as from now on
|
||||
* slaves and AOF know about it. */
|
||||
robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr);
|
||||
robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr);
|
||||
|
||||
replicationScriptCacheAdd(c->argv[1]->ptr);
|
||||
redisAssertWithInfo(c,NULL,script != NULL);
|
||||
rewriteClientCommandArgument(c,0,
|
||||
resetRefCount(createStringObject("EVAL",4)));
|
||||
rewriteClientCommandArgument(c,1,script);
|
||||
}
|
||||
redisAssertWithInfo(c,NULL,script != NULL);
|
||||
rewriteClientCommandArgument(c,0,
|
||||
resetRefCount(createStringObject("EVAL",4)));
|
||||
rewriteClientCommandArgument(c,1,script);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1025,8 +977,7 @@ void scriptCommand(redisClient *c) {
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"flush")) {
|
||||
scriptingReset();
|
||||
addReply(c,shared.ok);
|
||||
replicationScriptCacheFlush();
|
||||
server.dirty++; /* Propagating this command is a good idea. */
|
||||
server.dirty++; /* Replicating this command is a good idea. */
|
||||
} else if (c->argc >= 2 && !strcasecmp(c->argv[1]->ptr,"exists")) {
|
||||
int j;
|
||||
|
||||
@@ -1054,7 +1005,6 @@ void scriptCommand(redisClient *c) {
|
||||
}
|
||||
addReplyBulkCBuffer(c,funcname+2,40);
|
||||
sdsfree(sha);
|
||||
forceCommandPropagation(c,REDIS_PROPAGATE_REPL|REDIS_PROPAGATE_AOF);
|
||||
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
|
||||
if (server.lua_caller == NULL) {
|
||||
addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n"));
|
||||
|
||||
@@ -36,18 +36,6 @@
|
||||
#include "sds.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
/* Create a new sds string with the content specified by the 'init' pointer
|
||||
* and 'initlen'.
|
||||
* If NULL is used for 'init' the string is initialized with zero bytes.
|
||||
*
|
||||
* The string is always null-termined (all the sds strings are, always) so
|
||||
* even if you create an sds string with:
|
||||
*
|
||||
* mystring = sdsnewlen("abc",3");
|
||||
*
|
||||
* You can print the string with printf() as there is an implicit \0 at the
|
||||
* end of the string. However the string is binary safe and can contain
|
||||
* \0 characters in the middle, as the length is stored in the sds header. */
|
||||
sds sdsnewlen(const void *init, size_t initlen) {
|
||||
struct sdshdr *sh;
|
||||
|
||||
@@ -65,43 +53,24 @@ sds sdsnewlen(const void *init, size_t initlen) {
|
||||
return (char*)sh->buf;
|
||||
}
|
||||
|
||||
/* Create an empty (zero length) sds string. Even in this case the string
|
||||
* always has an implicit null term. */
|
||||
sds sdsempty(void) {
|
||||
return sdsnewlen("",0);
|
||||
}
|
||||
|
||||
/* Create a new sds string starting from a null termined C string. */
|
||||
sds sdsnew(const char *init) {
|
||||
size_t initlen = (init == NULL) ? 0 : strlen(init);
|
||||
return sdsnewlen(init, initlen);
|
||||
}
|
||||
|
||||
/* Duplicate an sds string. */
|
||||
sds sdsdup(const sds s) {
|
||||
return sdsnewlen(s, sdslen(s));
|
||||
}
|
||||
|
||||
/* Free an sds string. No operation is performed if 's' is NULL. */
|
||||
void sdsfree(sds s) {
|
||||
if (s == NULL) return;
|
||||
zfree(s-sizeof(struct sdshdr));
|
||||
}
|
||||
|
||||
/* Set the sds string length to the length as obtained with strlen(), so
|
||||
* considering as content only up to the first null term character.
|
||||
*
|
||||
* This function is useful when the sds string is hacked manually in some
|
||||
* way, like in the following example:
|
||||
*
|
||||
* s = sdsnew("foobar");
|
||||
* s[2] = '\0';
|
||||
* sdsupdatelen(s);
|
||||
* printf("%d\n", sdslen(s));
|
||||
*
|
||||
* The output will be "2", but if we comment out the call to sdsupdatelen()
|
||||
* the output will be "6" as the string was modified but the logical length
|
||||
* remains 6 bytes. */
|
||||
void sdsupdatelen(sds s) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
int reallen = strlen(s);
|
||||
@@ -109,10 +78,6 @@ void sdsupdatelen(sds s) {
|
||||
sh->len = reallen;
|
||||
}
|
||||
|
||||
/* Modify an sds string on-place to make it empty (zero length).
|
||||
* However all the existing buffer is not discarded but set as free space
|
||||
* so that next append operations will not require allocations up to the
|
||||
* number of bytes previously available. */
|
||||
void sdsclear(sds s) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
sh->free += sh->len;
|
||||
@@ -124,7 +89,7 @@ void sdsclear(sds s) {
|
||||
* is sure that after calling this function can overwrite up to addlen
|
||||
* bytes after the end of the string, plus one more byte for nul term.
|
||||
*
|
||||
* Note: this does not change the *length* of the sds string as returned
|
||||
* Note: this does not change the *size* of the sds string as returned
|
||||
* by sdslen(), but only the free buffer space we have. */
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
struct sdshdr *sh, *newsh;
|
||||
@@ -148,10 +113,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
|
||||
/* Reallocate the sds string so that it has no free space at the end. The
|
||||
* contained string remains not altered, but next concatenation operations
|
||||
* will require a reallocation.
|
||||
*
|
||||
* After the call, the passed sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call. */
|
||||
* will require a reallocation. */
|
||||
sds sdsRemoveFreeSpace(sds s) {
|
||||
struct sdshdr *sh;
|
||||
|
||||
@@ -161,13 +123,6 @@ sds sdsRemoveFreeSpace(sds s) {
|
||||
return sh->buf;
|
||||
}
|
||||
|
||||
/* Return the total size of the allocation of the specifed sds string,
|
||||
* including:
|
||||
* 1) The sds header before the pointer.
|
||||
* 2) The string.
|
||||
* 3) The free buffer at the end if any.
|
||||
* 4) The implicit null term.
|
||||
*/
|
||||
size_t sdsAllocSize(sds s) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
|
||||
@@ -175,7 +130,7 @@ size_t sdsAllocSize(sds s) {
|
||||
}
|
||||
|
||||
/* Increment the sds length and decrements the left free space at the
|
||||
* end of the string according to 'incr'. Also set the null term
|
||||
* end of the string accordingly to 'incr'. Also set the null term
|
||||
* in the new end of the string.
|
||||
*
|
||||
* This function is used in order to fix the string length after the
|
||||
@@ -185,17 +140,15 @@ size_t sdsAllocSize(sds s) {
|
||||
* Note: it is possible to use a negative increment in order to
|
||||
* right-trim the string.
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the
|
||||
* following schema, to cat bytes coming from the kernel to the end of an
|
||||
* sds string without copying into an intermediate buffer:
|
||||
* following schema to cat bytes coming from the kernel to the end of an
|
||||
* sds string new things without copying into an intermediate buffer:
|
||||
*
|
||||
* oldlen = sdslen(s);
|
||||
* s = sdsMakeRoomFor(s, BUFFER_SIZE);
|
||||
* nread = read(fd, s+oldlen, BUFFER_SIZE);
|
||||
* ... check for nread <= 0 and handle it ...
|
||||
* sdsIncrLen(s, nread);
|
||||
* sdsIncrLen(s, nhread);
|
||||
*/
|
||||
void sdsIncrLen(sds s, int incr) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
@@ -208,10 +161,7 @@ void sdsIncrLen(sds s, int incr) {
|
||||
}
|
||||
|
||||
/* Grow the sds to have the specified length. Bytes that were not part of
|
||||
* the original length of the sds will be set to zero.
|
||||
*
|
||||
* if the specified length is smaller than the current length, no operation
|
||||
* is performed. */
|
||||
* the original length of the sds will be set to zero. */
|
||||
sds sdsgrowzero(sds s, size_t len) {
|
||||
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
|
||||
size_t totlen, curlen = sh->len;
|
||||
@@ -229,11 +179,6 @@ sds sdsgrowzero(sds s, size_t len) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the
|
||||
* end of the specified sds string 's'.
|
||||
*
|
||||
* After the call, the passed sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call. */
|
||||
sds sdscatlen(sds s, const void *t, size_t len) {
|
||||
struct sdshdr *sh;
|
||||
size_t curlen = sdslen(s);
|
||||
@@ -248,24 +193,14 @@ sds sdscatlen(sds s, const void *t, size_t len) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Append the specified null termianted C string to the sds string 's'.
|
||||
*
|
||||
* After the call, the passed sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call. */
|
||||
sds sdscat(sds s, const char *t) {
|
||||
return sdscatlen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
/* Append the specified sds 't' to the existing sds 's'.
|
||||
*
|
||||
* After the call, the modified sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call. */
|
||||
sds sdscatsds(sds s, const sds t) {
|
||||
return sdscatlen(s, t, sdslen(t));
|
||||
}
|
||||
|
||||
/* Destructively modify the sds string 's' to hold the specified binary
|
||||
* safe string pointed by 't' of length 'len' bytes. */
|
||||
sds sdscpylen(sds s, const char *t, size_t len) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t totlen = sh->free+sh->len;
|
||||
@@ -283,13 +218,10 @@ sds sdscpylen(sds s, const char *t, size_t len) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Like sdscpylen() but 't' must be a null-termined string so that the length
|
||||
* of the string is obtained with strlen(). */
|
||||
sds sdscpy(sds s, const char *t) {
|
||||
return sdscpylen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
/* Like sdscatpritf() but gets va_list instead of being variadic. */
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
va_list cpy;
|
||||
char *buf, *t;
|
||||
@@ -313,22 +245,6 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
return t;
|
||||
}
|
||||
|
||||
/* Append to the sds string 's' a string obtained using printf-alike format
|
||||
* specifier.
|
||||
*
|
||||
* After the call, the modified sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* s = sdsempty("Sum is: ");
|
||||
* s = sdscatprintf(s,"%d+%d = %d",a,b,a+b).
|
||||
*
|
||||
* Often you need to create a string from scratch with the printf-alike
|
||||
* format. When this is the need, just use sdsempty() as the target string:
|
||||
*
|
||||
* s = sdscatprintf(sdsempty(), "... your format ...", args);
|
||||
*/
|
||||
sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
char *t;
|
||||
@@ -338,20 +254,6 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
return t;
|
||||
}
|
||||
|
||||
/* Remove the part of the string from left and from right composed just of
|
||||
* contiguous characters found in 'cset', that is a null terminted C string.
|
||||
*
|
||||
* After the call, the modified sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
|
||||
* s = sdstrim(s,"A. :");
|
||||
* printf("%s\n", s);
|
||||
*
|
||||
* Output will be just "Hello World".
|
||||
*/
|
||||
sds sdstrim(sds s, const char *cset) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
char *start, *end, *sp, *ep;
|
||||
@@ -369,27 +271,11 @@ sds sdstrim(sds s, const char *cset) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Turn the string into a smaller (or equal) string containing only the
|
||||
* substring specified by the 'start' and 'end' indexes.
|
||||
*
|
||||
* start and end can be negative, where -1 means the last character of the
|
||||
* string, -2 the penultimate character, and so forth.
|
||||
*
|
||||
* The interval is inclusive, so the start and end characters will be part
|
||||
* of the resulting string.
|
||||
*
|
||||
* The string is modified in-place.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* s = sdsnew("Hello World");
|
||||
* sdstrim(s,1,-1); => "ello Worl"
|
||||
*/
|
||||
void sdsrange(sds s, int start, int end) {
|
||||
sds sdsrange(sds s, int start, int end) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t newlen, len = sdslen(s);
|
||||
|
||||
if (len == 0) return;
|
||||
if (len == 0) return s;
|
||||
if (start < 0) {
|
||||
start = len+start;
|
||||
if (start < 0) start = 0;
|
||||
@@ -413,33 +299,21 @@ void sdsrange(sds s, int start, int end) {
|
||||
sh->buf[newlen] = 0;
|
||||
sh->free = sh->free+(sh->len-newlen);
|
||||
sh->len = newlen;
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Apply tolower() to every character of the sds string 's'. */
|
||||
void sdstolower(sds s) {
|
||||
int len = sdslen(s), j;
|
||||
|
||||
for (j = 0; j < len; j++) s[j] = tolower(s[j]);
|
||||
}
|
||||
|
||||
/* Apply toupper() to every character of the sds string 's'. */
|
||||
void sdstoupper(sds s) {
|
||||
int len = sdslen(s), j;
|
||||
|
||||
for (j = 0; j < len; j++) s[j] = toupper(s[j]);
|
||||
}
|
||||
|
||||
/* Compare two sds strings s1 and s2 with memcmp().
|
||||
*
|
||||
* Return value:
|
||||
*
|
||||
* 1 if s1 > s2.
|
||||
* -1 if s1 < s2.
|
||||
* 0 if s1 and s2 are exactly the same binary string.
|
||||
*
|
||||
* If two strings share exactly the same prefix, but one of the two has
|
||||
* additional characters, the longer string is considered to be greater than
|
||||
* the smaller one. */
|
||||
int sdscmp(const sds s1, const sds s2) {
|
||||
size_t l1, l2, minlen;
|
||||
int cmp;
|
||||
@@ -517,7 +391,6 @@ cleanup:
|
||||
}
|
||||
}
|
||||
|
||||
/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */
|
||||
void sdsfreesplitres(sds *tokens, int count) {
|
||||
if (!tokens) return;
|
||||
while(count--)
|
||||
@@ -525,10 +398,6 @@ void sdsfreesplitres(sds *tokens, int count) {
|
||||
zfree(tokens);
|
||||
}
|
||||
|
||||
/* Create an sds string from a long long value. It is much faster than:
|
||||
*
|
||||
* sdscatprintf(sdsempty(),"%lld\n", value);
|
||||
*/
|
||||
sds sdsfromlonglong(long long value) {
|
||||
char buf[32], *p;
|
||||
unsigned long long v;
|
||||
@@ -544,12 +413,6 @@ sds sdsfromlonglong(long long value) {
|
||||
return sdsnewlen(p,32-(p-buf));
|
||||
}
|
||||
|
||||
/* Append to the sds string "s" an escaped string representation where
|
||||
* all the non-printable characters (tested with isprint()) are turned into
|
||||
* escapes in the form "\n\r\a...." or "\x<hex-number>".
|
||||
*
|
||||
* After the call, the modified sds string is no longer valid and all the
|
||||
* references must be substituted with the new pointer returned by the call. */
|
||||
sds sdscatrepr(sds s, const char *p, size_t len) {
|
||||
s = sdscatlen(s,"\"",1);
|
||||
while(len--) {
|
||||
@@ -758,19 +621,6 @@ sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) {
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Join an array of C strings using the specified separator (also a C string).
|
||||
* Returns the result as an sds string. */
|
||||
sds sdsjoin(char **argv, int argc, char *sep) {
|
||||
sds join = sdsempty();
|
||||
int j;
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
join = sdscat(join, argv[j]);
|
||||
if (j != argc-1) join = sdscat(join,sep);
|
||||
}
|
||||
return join;
|
||||
}
|
||||
|
||||
#ifdef SDS_TEST_MAIN
|
||||
#include <stdio.h>
|
||||
#include "testhelp.h"
|
||||
|
||||
@@ -56,7 +56,7 @@ static inline size_t sdsavail(const sds s) {
|
||||
|
||||
sds sdsnewlen(const void *init, size_t initlen);
|
||||
sds sdsnew(const char *init);
|
||||
sds sdsempty(void);
|
||||
sds sdsempty();
|
||||
size_t sdslen(const sds s);
|
||||
sds sdsdup(const sds s);
|
||||
void sdsfree(sds s);
|
||||
@@ -77,7 +77,7 @@ sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
void sdsrange(sds s, int start, int end);
|
||||
sds sdsrange(sds s, int start, int end);
|
||||
void sdsupdatelen(sds s);
|
||||
void sdsclear(sds s);
|
||||
int sdscmp(const sds s1, const sds s2);
|
||||
@@ -89,7 +89,6 @@ sds sdsfromlonglong(long long value);
|
||||
sds sdscatrepr(sds s, const char *p, size_t len);
|
||||
sds *sdssplitargs(const char *line, int *argc);
|
||||
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
|
||||
sds sdsjoin(char **argv, int argc, char *sep);
|
||||
|
||||
/* Low level functions exposed to the user API */
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen);
|
||||
|
||||
+637
-881
File diff suppressed because it is too large
Load Diff
@@ -1,261 +0,0 @@
|
||||
/* ==========================================================================
|
||||
* setproctitle.c - Linux/Darwin setproctitle.
|
||||
* --------------------------------------------------------------------------
|
||||
* Copyright (C) 2010 William Ahern
|
||||
* Copyright (C) 2013 Salvatore Sanfilippo
|
||||
* Copyright (C) 2013 Stam He
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
* persons to whom the Software is furnished to do so, subject to the
|
||||
* following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
* USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
* ==========================================================================
|
||||
*/
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include <stddef.h> /* NULL size_t */
|
||||
#include <stdarg.h> /* va_list va_start va_end */
|
||||
#include <stdlib.h> /* malloc(3) setenv(3) clearenv(3) setproctitle(3) getprogname(3) */
|
||||
#include <stdio.h> /* vsnprintf(3) snprintf(3) */
|
||||
|
||||
#include <string.h> /* strlen(3) strchr(3) strdup(3) memset(3) memcpy(3) */
|
||||
|
||||
#include <errno.h> /* errno program_invocation_name program_invocation_short_name */
|
||||
|
||||
#if !defined(HAVE_SETPROCTITLE)
|
||||
#define HAVE_SETPROCTITLE (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
|
||||
#endif
|
||||
|
||||
|
||||
#if !HAVE_SETPROCTITLE
|
||||
#if (defined __linux || defined __APPLE__)
|
||||
|
||||
extern char **environ;
|
||||
|
||||
static struct {
|
||||
/* original value */
|
||||
const char *arg0;
|
||||
|
||||
/* title space available */
|
||||
char *base, *end;
|
||||
|
||||
/* pointer to original nul character within base */
|
||||
char *nul;
|
||||
|
||||
_Bool reset;
|
||||
int error;
|
||||
} SPT;
|
||||
|
||||
|
||||
#ifndef SPT_MIN
|
||||
#define SPT_MIN(a, b) (((a) < (b))? (a) : (b))
|
||||
#endif
|
||||
|
||||
static inline size_t spt_min(size_t a, size_t b) {
|
||||
return SPT_MIN(a, b);
|
||||
} /* spt_min() */
|
||||
|
||||
|
||||
/*
|
||||
* For discussion on the portability of the various methods, see
|
||||
* http://lists.freebsd.org/pipermail/freebsd-stable/2008-June/043136.html
|
||||
*/
|
||||
static int spt_clearenv(void) {
|
||||
#if __GLIBC__
|
||||
clearenv();
|
||||
|
||||
return 0;
|
||||
#else
|
||||
extern char **environ;
|
||||
static char **tmp;
|
||||
|
||||
if (!(tmp = malloc(sizeof *tmp)))
|
||||
return errno;
|
||||
|
||||
tmp[0] = NULL;
|
||||
environ = tmp;
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
} /* spt_clearenv() */
|
||||
|
||||
|
||||
static int spt_copyenv(char *oldenv[]) {
|
||||
extern char **environ;
|
||||
char *eq;
|
||||
int i, error;
|
||||
|
||||
if (environ != oldenv)
|
||||
return 0;
|
||||
|
||||
if ((error = spt_clearenv()))
|
||||
goto error;
|
||||
|
||||
for (i = 0; oldenv[i]; i++) {
|
||||
if (!(eq = strchr(oldenv[i], '=')))
|
||||
continue;
|
||||
|
||||
*eq = '\0';
|
||||
error = (0 != setenv(oldenv[i], eq + 1, 1))? errno : 0;
|
||||
*eq = '=';
|
||||
|
||||
if (error)
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
error:
|
||||
environ = oldenv;
|
||||
|
||||
return error;
|
||||
} /* spt_copyenv() */
|
||||
|
||||
|
||||
static int spt_copyargs(int argc, char *argv[]) {
|
||||
char *tmp;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < argc || (i >= argc && argv[i]); i++) {
|
||||
if (!argv[i])
|
||||
continue;
|
||||
|
||||
if (!(tmp = strdup(argv[i])))
|
||||
return errno;
|
||||
|
||||
argv[i] = tmp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} /* spt_copyargs() */
|
||||
|
||||
|
||||
void spt_init(int argc, char *argv[]) {
|
||||
char **envp = environ;
|
||||
char *base, *end, *nul, *tmp;
|
||||
int i, error;
|
||||
|
||||
if (!(base = argv[0]))
|
||||
return;
|
||||
|
||||
nul = &base[strlen(base)];
|
||||
end = nul + 1;
|
||||
|
||||
for (i = 0; i < argc || (i >= argc && argv[i]); i++) {
|
||||
if (!argv[i] || argv[i] < end)
|
||||
continue;
|
||||
|
||||
end = argv[i] + strlen(argv[i]) + 1;
|
||||
}
|
||||
|
||||
for (i = 0; envp[i]; i++) {
|
||||
if (envp[i] < end)
|
||||
continue;
|
||||
|
||||
end = envp[i] + strlen(envp[i]) + 1;
|
||||
}
|
||||
|
||||
if (!(SPT.arg0 = strdup(argv[0])))
|
||||
goto syerr;
|
||||
|
||||
#if __GLIBC__
|
||||
if (!(tmp = strdup(program_invocation_name)))
|
||||
goto syerr;
|
||||
|
||||
program_invocation_name = tmp;
|
||||
|
||||
if (!(tmp = strdup(program_invocation_short_name)))
|
||||
goto syerr;
|
||||
|
||||
program_invocation_short_name = tmp;
|
||||
#elif __APPLE__
|
||||
if (!(tmp = strdup(getprogname())))
|
||||
goto syerr;
|
||||
|
||||
setprogname(tmp);
|
||||
#endif
|
||||
|
||||
|
||||
if ((error = spt_copyenv(envp)))
|
||||
goto error;
|
||||
|
||||
if ((error = spt_copyargs(argc, argv)))
|
||||
goto error;
|
||||
|
||||
SPT.nul = nul;
|
||||
SPT.base = base;
|
||||
SPT.end = end;
|
||||
|
||||
return;
|
||||
syerr:
|
||||
error = errno;
|
||||
error:
|
||||
SPT.error = error;
|
||||
} /* spt_init() */
|
||||
|
||||
|
||||
#ifndef SPT_MAXTITLE
|
||||
#define SPT_MAXTITLE 255
|
||||
#endif
|
||||
|
||||
void setproctitle(const char *fmt, ...) {
|
||||
char buf[SPT_MAXTITLE + 1]; /* use buffer in case argv[0] is passed */
|
||||
va_list ap;
|
||||
char *nul;
|
||||
int len, error;
|
||||
|
||||
if (!SPT.base)
|
||||
return;
|
||||
|
||||
if (fmt) {
|
||||
va_start(ap, fmt);
|
||||
len = vsnprintf(buf, sizeof buf, fmt, ap);
|
||||
va_end(ap);
|
||||
} else {
|
||||
len = snprintf(buf, sizeof buf, "%s", SPT.arg0);
|
||||
}
|
||||
|
||||
if (len <= 0)
|
||||
{ error = errno; goto error; }
|
||||
|
||||
if (!SPT.reset) {
|
||||
memset(SPT.base, 0, SPT.end - SPT.base);
|
||||
SPT.reset = 1;
|
||||
} else {
|
||||
memset(SPT.base, 0, spt_min(sizeof buf, SPT.end - SPT.base));
|
||||
}
|
||||
|
||||
len = spt_min(len, spt_min(sizeof buf, SPT.end - SPT.base) - 1);
|
||||
memcpy(SPT.base, buf, len);
|
||||
nul = &SPT.base[len];
|
||||
|
||||
if (nul < SPT.nul) {
|
||||
*SPT.nul = '.';
|
||||
} else if (nul == SPT.nul && &nul[1] < SPT.end) {
|
||||
*SPT.nul = ' ';
|
||||
*++nul = '\0';
|
||||
}
|
||||
|
||||
return;
|
||||
error:
|
||||
SPT.error = error;
|
||||
} /* setproctitle() */
|
||||
|
||||
|
||||
#endif /* __linux || __APPLE__ */
|
||||
#endif /* !HAVE_SETPROCTITLE */
|
||||
+5
-17
@@ -163,22 +163,12 @@ int sortCompare(const void *s1, const void *s2) {
|
||||
else
|
||||
cmp = 1;
|
||||
} else {
|
||||
/* We have both the objects, compare them. */
|
||||
if (server.sort_store) {
|
||||
cmp = compareStringObjects(so1->u.cmpobj,so2->u.cmpobj);
|
||||
} else {
|
||||
/* Here we can use strcoll() directly as we are sure that
|
||||
* the objects are decoded string objects. */
|
||||
cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr);
|
||||
}
|
||||
/* We have both the objects, use strcoll */
|
||||
cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr);
|
||||
}
|
||||
} else {
|
||||
/* Compare elements directly. */
|
||||
if (server.sort_store) {
|
||||
cmp = compareStringObjects(so1->obj,so2->obj);
|
||||
} else {
|
||||
cmp = collateStringObjects(so1->obj,so2->obj);
|
||||
}
|
||||
cmp = compareStringObjects(so1->obj,so2->obj);
|
||||
}
|
||||
}
|
||||
return server.sort_desc ? -cmp : cmp;
|
||||
@@ -442,7 +432,6 @@ void sortCommand(redisClient *c) {
|
||||
server.sort_desc = desc;
|
||||
server.sort_alpha = alpha;
|
||||
server.sort_bypattern = sortby ? 1 : 0;
|
||||
server.sort_store = storekey ? 1 : 0;
|
||||
if (sortby && (start != 0 || end != vectorlen-1))
|
||||
pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end);
|
||||
else
|
||||
@@ -515,12 +504,9 @@ void sortCommand(redisClient *c) {
|
||||
}
|
||||
if (outputlen) {
|
||||
setKey(c->db,storekey,sobj);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"sortstore",storekey,
|
||||
c->db->id);
|
||||
server.dirty += outputlen;
|
||||
} else if (dbDelete(c->db,storekey)) {
|
||||
signalModifiedKey(c->db,storekey);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",storekey,c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
decrRefCount(sobj);
|
||||
@@ -539,3 +525,5 @@ void sortCommand(redisClient *c) {
|
||||
}
|
||||
zfree(vector);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-21
@@ -474,7 +474,6 @@ void hsetCommand(redisClient *c) {
|
||||
update = hashTypeSet(o,c->argv[2],c->argv[3]);
|
||||
addReply(c, update ? shared.czero : shared.cone);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -490,7 +489,6 @@ void hsetnxCommand(redisClient *c) {
|
||||
hashTypeSet(o,c->argv[2],c->argv[3]);
|
||||
addReply(c, shared.cone);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
}
|
||||
@@ -512,7 +510,6 @@ void hmsetCommand(redisClient *c) {
|
||||
}
|
||||
addReply(c, shared.ok);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -546,7 +543,6 @@ void hincrbyCommand(redisClient *c) {
|
||||
decrRefCount(new);
|
||||
addReplyLongLong(c,value);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hincrby",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -573,7 +569,6 @@ void hincrbyfloatCommand(redisClient *c) {
|
||||
hashTypeSet(o,c->argv[2],new);
|
||||
addReplyBulk(c,new);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hincrbyfloat",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
/* Always replicate HINCRBYFLOAT as an HSET command with the final value
|
||||
@@ -654,7 +649,7 @@ void hmgetCommand(redisClient *c) {
|
||||
|
||||
void hdelCommand(redisClient *c) {
|
||||
robj *o;
|
||||
int j, deleted = 0, keyremoved = 0;
|
||||
int j, deleted = 0;
|
||||
|
||||
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,o,REDIS_HASH)) return;
|
||||
@@ -664,17 +659,12 @@ void hdelCommand(redisClient *c) {
|
||||
deleted++;
|
||||
if (hashTypeLength(o) == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
keyremoved = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hdel",c->argv[1],c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],
|
||||
c->db->id);
|
||||
server.dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
@@ -762,13 +752,3 @@ void hexistsCommand(redisClient *c) {
|
||||
|
||||
addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero);
|
||||
}
|
||||
|
||||
void hscanCommand(redisClient *c) {
|
||||
robj *o;
|
||||
unsigned long cursor;
|
||||
|
||||
if (parseScanCursorOrReply(c,c->argv[2],&cursor) == REDIS_ERR) return;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
|
||||
checkType(c,o,REDIS_HASH)) return;
|
||||
scanGenericCommand(c,o,cursor);
|
||||
}
|
||||
|
||||
+10
-43
@@ -275,7 +275,7 @@ void listTypeConvert(robj *subject, int enc) {
|
||||
|
||||
if (enc == REDIS_ENCODING_LINKEDLIST) {
|
||||
list *l = listCreate();
|
||||
listSetFreeMethod(l,decrRefCountVoid);
|
||||
listSetFreeMethod(l,decrRefCount);
|
||||
|
||||
/* listTypeGet returns a robj with incremented refcount */
|
||||
li = listTypeInitIterator(subject,0,REDIS_TAIL);
|
||||
@@ -316,12 +316,7 @@ void pushGenericCommand(redisClient *c, int where) {
|
||||
pushed++;
|
||||
}
|
||||
addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0));
|
||||
if (pushed) {
|
||||
char *event = (where == REDIS_HEAD) ? "lpush" : "rpush";
|
||||
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
|
||||
}
|
||||
if (pushed) signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty += pushed;
|
||||
}
|
||||
|
||||
@@ -343,6 +338,10 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
|
||||
checkType(c,subject,REDIS_LIST)) return;
|
||||
|
||||
if (refval != NULL) {
|
||||
/* Note: we expect refval to be string-encoded because it is *not* the
|
||||
* last argument of the multi-bulk LINSERT. */
|
||||
redisAssertWithInfo(c,refval,refval->encoding == REDIS_ENCODING_RAW);
|
||||
|
||||
/* We're not sure if this value can be inserted yet, but we cannot
|
||||
* convert the list inside the iterator. We don't want to loop over
|
||||
* the list twice (once to see if the value can be inserted and once
|
||||
@@ -367,8 +366,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
|
||||
ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
|
||||
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"linsert",
|
||||
c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
} else {
|
||||
/* Notify client of a failed insert */
|
||||
@@ -376,11 +373,8 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
char *event = (where == REDIS_HEAD) ? "lpush" : "rpush";
|
||||
|
||||
listTypePush(subject,val,where);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -475,7 +469,6 @@ void lsetCommand(redisClient *c) {
|
||||
decrRefCount(value);
|
||||
addReply(c,shared.ok);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
@@ -488,7 +481,6 @@ void lsetCommand(redisClient *c) {
|
||||
incrRefCount(value);
|
||||
addReply(c,shared.ok);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
} else {
|
||||
@@ -504,16 +496,9 @@ void popGenericCommand(redisClient *c, int where) {
|
||||
if (value == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
} else {
|
||||
char *event = (where == REDIS_HEAD) ? "lpop" : "rpop";
|
||||
|
||||
addReplyBulk(c,value);
|
||||
decrRefCount(value);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
|
||||
if (listTypeLength(o) == 0) {
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
|
||||
c->argv[1],c->db->id);
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
}
|
||||
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -633,12 +618,7 @@ void ltrimCommand(redisClient *c) {
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"ltrim",c->argv[1],c->db->id);
|
||||
if (listTypeLength(o) == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
}
|
||||
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c,shared.ok);
|
||||
@@ -713,7 +693,6 @@ void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
listTypePush(dstobj,value,REDIS_HEAD);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lpush",dstkey,c->db->id);
|
||||
/* Always send the pushed value to the client. */
|
||||
addReplyBulk(c,value);
|
||||
}
|
||||
@@ -743,12 +722,7 @@ void rpoplpushCommand(redisClient *c) {
|
||||
decrRefCount(value);
|
||||
|
||||
/* Delete the source list when it is empty */
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"rpop",touchedkey,c->db->id);
|
||||
if (listTypeLength(sobj) == 0) {
|
||||
dbDelete(c->db,touchedkey);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
|
||||
touchedkey,c->db->id);
|
||||
}
|
||||
if (listTypeLength(sobj) == 0) dbDelete(c->db,touchedkey);
|
||||
signalModifiedKey(c->db,touchedkey);
|
||||
decrRefCount(touchedkey);
|
||||
server.dirty++;
|
||||
@@ -1072,7 +1046,6 @@ void blockingPopGenericCommand(redisClient *c, int where) {
|
||||
} else {
|
||||
if (listTypeLength(o) != 0) {
|
||||
/* Non empty list, this is like a non normal [LR]POP. */
|
||||
char *event = (where == REDIS_HEAD) ? "lpop" : "rpop";
|
||||
robj *value = listTypePop(o,where);
|
||||
redisAssert(value != NULL);
|
||||
|
||||
@@ -1080,13 +1053,7 @@ void blockingPopGenericCommand(redisClient *c, int where) {
|
||||
addReplyBulk(c,c->argv[j]);
|
||||
addReplyBulk(c,value);
|
||||
decrRefCount(value);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,
|
||||
c->argv[j],c->db->id);
|
||||
if (listTypeLength(o) == 0) {
|
||||
dbDelete(c->db,c->argv[j]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
|
||||
c->argv[j],c->db->id);
|
||||
}
|
||||
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[j]);
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
server.dirty++;
|
||||
|
||||
|
||||
+23
-144
@@ -266,17 +266,14 @@ void saddCommand(redisClient *c) {
|
||||
c->argv[j] = tryObjectEncoding(c->argv[j]);
|
||||
if (setTypeAdd(set,c->argv[j])) added++;
|
||||
}
|
||||
if (added) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[1],c->db->id);
|
||||
}
|
||||
if (added) signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty += added;
|
||||
addReplyLongLong(c,added);
|
||||
}
|
||||
|
||||
void sremCommand(redisClient *c) {
|
||||
robj *set;
|
||||
int j, deleted = 0, keyremoved = 0;
|
||||
int j, deleted = 0;
|
||||
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,set,REDIS_SET)) return;
|
||||
@@ -286,17 +283,12 @@ void sremCommand(redisClient *c) {
|
||||
deleted++;
|
||||
if (setTypeSize(set) == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
keyremoved = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"srem",c->argv[1],c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],
|
||||
c->db->id);
|
||||
server.dirty += deleted;
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
@@ -330,13 +322,9 @@ void smoveCommand(redisClient *c) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"srem",c->argv[1],c->db->id);
|
||||
|
||||
/* Remove the src set from the database when empty */
|
||||
if (setTypeSize(srcset) == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
}
|
||||
if (setTypeSize(srcset) == 0) dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
server.dirty++;
|
||||
@@ -348,10 +336,7 @@ void smoveCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
/* An extra key has changed when ele was successfully added to dstset */
|
||||
if (setTypeAdd(dstset,ele)) {
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[2],c->db->id);
|
||||
}
|
||||
if (setTypeAdd(dstset,ele)) server.dirty++;
|
||||
addReply(c,shared.cone);
|
||||
}
|
||||
|
||||
@@ -393,7 +378,6 @@ void spopCommand(redisClient *c) {
|
||||
incrRefCount(ele);
|
||||
setTypeRemove(set,ele);
|
||||
}
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id);
|
||||
|
||||
/* Replicate/AOF this command as an SREM operation */
|
||||
aux = createStringObject("SREM",4);
|
||||
@@ -402,10 +386,7 @@ void spopCommand(redisClient *c) {
|
||||
decrRefCount(aux);
|
||||
|
||||
addReplyBulk(c,ele);
|
||||
if (setTypeSize(set) == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
}
|
||||
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -584,14 +565,6 @@ int qsortCompareSetsByCardinality(const void *s1, const void *s2) {
|
||||
return setTypeSize(*(robj**)s1)-setTypeSize(*(robj**)s2);
|
||||
}
|
||||
|
||||
/* This is used by SDIFF and in this case we can receive NULL that should
|
||||
* be handled as empty sets. */
|
||||
int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) {
|
||||
robj *o1 = *(robj**)s1, *o2 = *(robj**)s2;
|
||||
|
||||
return (o2 ? setTypeSize(o2) : 0) - (o1 ? setTypeSize(o1) : 0);
|
||||
}
|
||||
|
||||
void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) {
|
||||
robj **sets = zmalloc(sizeof(robj*)*setnum);
|
||||
setTypeIterator *si;
|
||||
@@ -706,18 +679,13 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
|
||||
if (dstkey) {
|
||||
/* Store the resulting set into the target, if the intersection
|
||||
* is not an empty set. */
|
||||
int deleted = dbDelete(c->db,dstkey);
|
||||
dbDelete(c->db,dstkey);
|
||||
if (setTypeSize(dstset) > 0) {
|
||||
dbAdd(c->db,dstkey,dstset);
|
||||
addReplyLongLong(c,setTypeSize(dstset));
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sinterstore",
|
||||
dstkey,c->db->id);
|
||||
} else {
|
||||
decrRefCount(dstset);
|
||||
addReply(c,shared.czero);
|
||||
if (deleted)
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
|
||||
dstkey,c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
@@ -744,7 +712,6 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
|
||||
setTypeIterator *si;
|
||||
robj *ele, *dstset = NULL;
|
||||
int j, cardinality = 0;
|
||||
int diff_algo = 1;
|
||||
|
||||
for (j = 0; j < setnum; j++) {
|
||||
robj *setobj = dstkey ?
|
||||
@@ -761,106 +728,34 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
|
||||
sets[j] = setobj;
|
||||
}
|
||||
|
||||
/* Select what DIFF algorithm to use.
|
||||
*
|
||||
* Algorithm 1 is O(N*M) where N is the size of the element first set
|
||||
* and M the total number of sets.
|
||||
*
|
||||
* Algorithm 2 is O(N) where N is the total number of elements in all
|
||||
* the sets.
|
||||
*
|
||||
* We compute what is the best bet with the current input here. */
|
||||
if (op == REDIS_OP_DIFF && sets[0]) {
|
||||
long long algo_one_work = 0, algo_two_work = 0;
|
||||
|
||||
for (j = 0; j < setnum; j++) {
|
||||
if (sets[j] == NULL) continue;
|
||||
|
||||
algo_one_work += setTypeSize(sets[0]);
|
||||
algo_two_work += setTypeSize(sets[j]);
|
||||
}
|
||||
|
||||
/* Algorithm 1 has better constant times and performs less operations
|
||||
* if there are elements in common. Give it some advantage. */
|
||||
algo_one_work /= 2;
|
||||
diff_algo = (algo_one_work <= algo_two_work) ? 1 : 2;
|
||||
|
||||
if (diff_algo == 1 && setnum > 1) {
|
||||
/* With algorithm 1 it is better to order the sets to subtract
|
||||
* by decreasing size, so that we are more likely to find
|
||||
* duplicated elements ASAP. */
|
||||
qsort(sets+1,setnum-1,sizeof(robj*),
|
||||
qsortCompareSetsByRevCardinality);
|
||||
}
|
||||
}
|
||||
|
||||
/* We need a temp set object to store our union. If the dstkey
|
||||
* is not NULL (that is, we are inside an SUNIONSTORE operation) then
|
||||
* this set object will be the resulting object to set into the target key*/
|
||||
dstset = createIntsetObject();
|
||||
|
||||
if (op == REDIS_OP_UNION) {
|
||||
/* Union is trivial, just add every element of every set to the
|
||||
* temporary set. */
|
||||
for (j = 0; j < setnum; j++) {
|
||||
if (!sets[j]) continue; /* non existing keys are like empty sets */
|
||||
/* Iterate all the elements of all the sets, add every element a single
|
||||
* time to the result set */
|
||||
for (j = 0; j < setnum; j++) {
|
||||
if (op == REDIS_OP_DIFF && j == 0 && !sets[j]) break; /* result set is empty */
|
||||
if (!sets[j]) continue; /* non existing keys are like empty sets */
|
||||
|
||||
si = setTypeInitIterator(sets[j]);
|
||||
while((ele = setTypeNextObject(si)) != NULL) {
|
||||
if (setTypeAdd(dstset,ele)) cardinality++;
|
||||
decrRefCount(ele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
}
|
||||
} else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 1) {
|
||||
/* DIFF Algorithm 1:
|
||||
*
|
||||
* We perform the diff by iterating all the elements of the first set,
|
||||
* and only adding it to the target set if the element does not exist
|
||||
* into all the other sets.
|
||||
*
|
||||
* This way we perform at max N*M operations, where N is the size of
|
||||
* the first set, and M the number of sets. */
|
||||
si = setTypeInitIterator(sets[0]);
|
||||
si = setTypeInitIterator(sets[j]);
|
||||
while((ele = setTypeNextObject(si)) != NULL) {
|
||||
for (j = 1; j < setnum; j++) {
|
||||
if (!sets[j]) continue; /* no key is an empty set. */
|
||||
if (setTypeIsMember(sets[j],ele)) break;
|
||||
}
|
||||
if (j == setnum) {
|
||||
/* There is no other set with this element. Add it. */
|
||||
setTypeAdd(dstset,ele);
|
||||
cardinality++;
|
||||
if (op == REDIS_OP_UNION || j == 0) {
|
||||
if (setTypeAdd(dstset,ele)) {
|
||||
cardinality++;
|
||||
}
|
||||
} else if (op == REDIS_OP_DIFF) {
|
||||
if (setTypeRemove(dstset,ele)) {
|
||||
cardinality--;
|
||||
}
|
||||
}
|
||||
decrRefCount(ele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
} else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 2) {
|
||||
/* DIFF Algorithm 2:
|
||||
*
|
||||
* Add all the elements of the first set to the auxiliary set.
|
||||
* Then remove all the elements of all the next sets from it.
|
||||
*
|
||||
* This is O(N) where N is the sum of all the elements in every
|
||||
* set. */
|
||||
for (j = 0; j < setnum; j++) {
|
||||
if (!sets[j]) continue; /* non existing keys are like empty sets */
|
||||
|
||||
si = setTypeInitIterator(sets[j]);
|
||||
while((ele = setTypeNextObject(si)) != NULL) {
|
||||
if (j == 0) {
|
||||
if (setTypeAdd(dstset,ele)) cardinality++;
|
||||
} else {
|
||||
if (setTypeRemove(dstset,ele)) cardinality--;
|
||||
}
|
||||
decrRefCount(ele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
|
||||
/* Exit if result set is empty as any additional removal
|
||||
* of elements will have no effect. */
|
||||
if (cardinality == 0) break;
|
||||
}
|
||||
/* Exit when result set is empty. */
|
||||
if (op == REDIS_OP_DIFF && cardinality == 0) break;
|
||||
}
|
||||
|
||||
/* Output the content of the resulting set, if not in STORE mode */
|
||||
@@ -876,19 +771,13 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
|
||||
} else {
|
||||
/* If we have a target key where to store the resulting set
|
||||
* create this key with the result set inside */
|
||||
int deleted = dbDelete(c->db,dstkey);
|
||||
dbDelete(c->db,dstkey);
|
||||
if (setTypeSize(dstset) > 0) {
|
||||
dbAdd(c->db,dstkey,dstset);
|
||||
addReplyLongLong(c,setTypeSize(dstset));
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_SET,
|
||||
op == REDIS_OP_UNION ? "sunionstore" : "sdiffstore",
|
||||
dstkey,c->db->id);
|
||||
} else {
|
||||
decrRefCount(dstset);
|
||||
addReply(c,shared.czero);
|
||||
if (deleted)
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
|
||||
dstkey,c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
server.dirty++;
|
||||
@@ -911,13 +800,3 @@ void sdiffCommand(redisClient *c) {
|
||||
void sdiffstoreCommand(redisClient *c) {
|
||||
sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],REDIS_OP_DIFF);
|
||||
}
|
||||
|
||||
void sscanCommand(redisClient *c) {
|
||||
robj *set;
|
||||
unsigned long cursor;
|
||||
|
||||
if (parseScanCursorOrReply(c,c->argv[2],&cursor) == REDIS_ERR) return;
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
|
||||
checkType(c,set,REDIS_SET)) return;
|
||||
scanGenericCommand(c,set,cursor);
|
||||
}
|
||||
|
||||
@@ -84,9 +84,6 @@ void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *ex
|
||||
setKey(c->db,key,val);
|
||||
server.dirty++;
|
||||
if (expire) setExpire(c->db,key,mstime()+milliseconds);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",key,c->db->id);
|
||||
if (expire) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
|
||||
"expire",key,c->db->id);
|
||||
addReply(c, ok_reply ? ok_reply : shared.ok);
|
||||
}
|
||||
|
||||
@@ -165,7 +162,6 @@ void getsetCommand(redisClient *c) {
|
||||
if (getGenericCommand(c) == REDIS_ERR) return;
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
setKey(c->db,c->argv[1],c->argv[2]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
@@ -227,8 +223,6 @@ void setrangeCommand(redisClient *c) {
|
||||
o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value));
|
||||
memcpy((char*)o->ptr+offset,value,sdslen(value));
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,
|
||||
"setrange",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
addReplyLongLong(c,sdslen(o->ptr));
|
||||
@@ -313,7 +307,6 @@ void msetGenericCommand(redisClient *c, int nx) {
|
||||
for (j = 1; j < c->argc; j += 2) {
|
||||
c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
|
||||
setKey(c->db,c->argv[j],c->argv[j+1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[j],c->db->id);
|
||||
}
|
||||
server.dirty += (c->argc-1)/2;
|
||||
addReply(c, nx ? shared.cone : shared.ok);
|
||||
@@ -348,7 +341,6 @@ void incrDecrCommand(redisClient *c, long long incr) {
|
||||
else
|
||||
dbAdd(c->db,c->argv[1],new);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
addReply(c,shared.colon);
|
||||
addReply(c,new);
|
||||
@@ -398,7 +390,6 @@ void incrbyfloatCommand(redisClient *c) {
|
||||
else
|
||||
dbAdd(c->db,c->argv[1],new);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
addReplyBulk(c,new);
|
||||
|
||||
@@ -446,7 +437,6 @@ void appendCommand(redisClient *c) {
|
||||
totlen = sdslen(o->ptr);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"append",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
addReplyLongLong(c,totlen);
|
||||
}
|
||||
|
||||
+55
-90
@@ -844,9 +844,9 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
robj *ele;
|
||||
robj *zobj;
|
||||
robj *curobj;
|
||||
double score = 0, *scores = NULL, curscore = 0.0;
|
||||
double score = 0, *scores, curscore = 0.0;
|
||||
int j, elements = (c->argc-2)/2;
|
||||
int added = 0, updated = 0;
|
||||
int added = 0;
|
||||
|
||||
if (c->argc % 2) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
@@ -859,7 +859,11 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
scores = zmalloc(sizeof(double)*elements);
|
||||
for (j = 0; j < elements; j++) {
|
||||
if (getDoubleFromObjectOrReply(c,c->argv[2+j*2],&scores[j],NULL)
|
||||
!= REDIS_OK) goto cleanup;
|
||||
!= REDIS_OK)
|
||||
{
|
||||
zfree(scores);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Lookup the key and create the sorted set if does not exist. */
|
||||
@@ -876,7 +880,8 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
} else {
|
||||
if (zobj->type != REDIS_ZSET) {
|
||||
addReply(c,shared.wrongtypeerr);
|
||||
goto cleanup;
|
||||
zfree(scores);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -893,7 +898,10 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
score += curscore;
|
||||
if (isnan(score)) {
|
||||
addReplyError(c,nanerr);
|
||||
goto cleanup;
|
||||
/* Don't need to check if the sorted set is empty
|
||||
* because we know it has at least one element. */
|
||||
zfree(scores);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -901,8 +909,9 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
if (score != curscore) {
|
||||
zobj->ptr = zzlDelete(zobj->ptr,eptr);
|
||||
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
|
||||
|
||||
signalModifiedKey(c->db,key);
|
||||
server.dirty++;
|
||||
updated++;
|
||||
}
|
||||
} else {
|
||||
/* Optimize: check if the element is too large or the list
|
||||
@@ -912,8 +921,10 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);
|
||||
if (sdslen(ele->ptr) > server.zset_max_ziplist_value)
|
||||
zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);
|
||||
|
||||
signalModifiedKey(c->db,key);
|
||||
server.dirty++;
|
||||
added++;
|
||||
if (!incr) added++;
|
||||
}
|
||||
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
|
||||
zset *zs = zobj->ptr;
|
||||
@@ -932,7 +943,8 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
addReplyError(c,nanerr);
|
||||
/* Don't need to check if the sorted set is empty
|
||||
* because we know it has at least one element. */
|
||||
goto cleanup;
|
||||
zfree(scores);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -944,33 +956,29 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
znode = zslInsert(zs->zsl,score,curobj);
|
||||
incrRefCount(curobj); /* Re-inserted in skiplist. */
|
||||
dictGetVal(de) = &znode->score; /* Update score ptr. */
|
||||
|
||||
signalModifiedKey(c->db,key);
|
||||
server.dirty++;
|
||||
updated++;
|
||||
}
|
||||
} else {
|
||||
znode = zslInsert(zs->zsl,score,ele);
|
||||
incrRefCount(ele); /* Inserted in skiplist. */
|
||||
redisAssertWithInfo(c,NULL,dictAdd(zs->dict,ele,&znode->score) == DICT_OK);
|
||||
incrRefCount(ele); /* Added to dictionary. */
|
||||
|
||||
signalModifiedKey(c->db,key);
|
||||
server.dirty++;
|
||||
added++;
|
||||
if (!incr) added++;
|
||||
}
|
||||
} else {
|
||||
redisPanic("Unknown sorted set encoding");
|
||||
}
|
||||
}
|
||||
zfree(scores);
|
||||
if (incr) /* ZINCRBY */
|
||||
addReplyDouble(c,score);
|
||||
else /* ZADD */
|
||||
addReplyLongLong(c,added);
|
||||
|
||||
cleanup:
|
||||
zfree(scores);
|
||||
if (added || updated) {
|
||||
signalModifiedKey(c->db,key);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_ZSET,
|
||||
incr ? "zincr" : "zadd", key, c->db->id);
|
||||
}
|
||||
}
|
||||
|
||||
void zaddCommand(redisClient *c) {
|
||||
@@ -984,7 +992,7 @@ void zincrbyCommand(redisClient *c) {
|
||||
void zremCommand(redisClient *c) {
|
||||
robj *key = c->argv[1];
|
||||
robj *zobj;
|
||||
int deleted = 0, keyremoved = 0, j;
|
||||
int deleted = 0, j;
|
||||
|
||||
if ((zobj = lookupKeyWriteOrReply(c,key,shared.czero)) == NULL ||
|
||||
checkType(c,zobj,REDIS_ZSET)) return;
|
||||
@@ -1030,9 +1038,6 @@ void zremCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
if (deleted) {
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_ZSET,"zrem",key,c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
signalModifiedKey(c->db,key);
|
||||
server.dirty += deleted;
|
||||
}
|
||||
@@ -1043,7 +1048,6 @@ void zremrangebyscoreCommand(redisClient *c) {
|
||||
robj *key = c->argv[1];
|
||||
robj *zobj;
|
||||
zrangespec range;
|
||||
int keyremoved = 0;
|
||||
unsigned long deleted;
|
||||
|
||||
/* Parse the range arguments. */
|
||||
@@ -1057,28 +1061,17 @@ void zremrangebyscoreCommand(redisClient *c) {
|
||||
|
||||
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
zobj->ptr = zzlDeleteRangeByScore(zobj->ptr,range,&deleted);
|
||||
if (zzlLength(zobj->ptr) == 0) {
|
||||
dbDelete(c->db,key);
|
||||
keyremoved = 1;
|
||||
}
|
||||
if (zzlLength(zobj->ptr) == 0) dbDelete(c->db,key);
|
||||
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
|
||||
zset *zs = zobj->ptr;
|
||||
deleted = zslDeleteRangeByScore(zs->zsl,range,zs->dict);
|
||||
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
|
||||
if (dictSize(zs->dict) == 0) {
|
||||
dbDelete(c->db,key);
|
||||
keyremoved = 1;
|
||||
}
|
||||
if (dictSize(zs->dict) == 0) dbDelete(c->db,key);
|
||||
} else {
|
||||
redisPanic("Unknown sorted set encoding");
|
||||
}
|
||||
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,key);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_ZSET,"zrembyscore",key,c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
}
|
||||
if (deleted) signalModifiedKey(c->db,key);
|
||||
server.dirty += deleted;
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
@@ -1090,7 +1083,6 @@ void zremrangebyrankCommand(redisClient *c) {
|
||||
long end;
|
||||
int llen;
|
||||
unsigned long deleted;
|
||||
int keyremoved = 0;
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
|
||||
@@ -1115,30 +1107,19 @@ void zremrangebyrankCommand(redisClient *c) {
|
||||
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
/* Correct for 1-based rank. */
|
||||
zobj->ptr = zzlDeleteRangeByRank(zobj->ptr,start+1,end+1,&deleted);
|
||||
if (zzlLength(zobj->ptr) == 0) {
|
||||
dbDelete(c->db,key);
|
||||
keyremoved = 1;
|
||||
}
|
||||
if (zzlLength(zobj->ptr) == 0) dbDelete(c->db,key);
|
||||
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
|
||||
zset *zs = zobj->ptr;
|
||||
|
||||
/* Correct for 1-based rank. */
|
||||
deleted = zslDeleteRangeByRank(zs->zsl,start+1,end+1,zs->dict);
|
||||
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
|
||||
if (dictSize(zs->dict) == 0) {
|
||||
dbDelete(c->db,key);
|
||||
keyremoved = 1;
|
||||
}
|
||||
if (dictSize(zs->dict) == 0) dbDelete(c->db,key);
|
||||
} else {
|
||||
redisPanic("Unknown sorted set encoding");
|
||||
}
|
||||
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,key);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_ZSET,"zrembyrank",key,c->db->id);
|
||||
if (keyremoved)
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
}
|
||||
if (deleted) signalModifiedKey(c->db,key);
|
||||
server.dirty += deleted;
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
@@ -1270,20 +1251,20 @@ int zuiLength(zsetopsrc *op) {
|
||||
return 0;
|
||||
|
||||
if (op->type == REDIS_SET) {
|
||||
iterset *it = &op->iter.set;
|
||||
if (op->encoding == REDIS_ENCODING_INTSET) {
|
||||
return intsetLen(op->subject->ptr);
|
||||
return intsetLen(it->is.is);
|
||||
} else if (op->encoding == REDIS_ENCODING_HT) {
|
||||
dict *ht = op->subject->ptr;
|
||||
return dictSize(ht);
|
||||
return dictSize(it->ht.dict);
|
||||
} else {
|
||||
redisPanic("Unknown set encoding");
|
||||
}
|
||||
} else if (op->type == REDIS_ZSET) {
|
||||
iterzset *it = &op->iter.zset;
|
||||
if (op->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
return zzlLength(op->subject->ptr);
|
||||
return zzlLength(it->zl.zl);
|
||||
} else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
|
||||
zset *zs = op->subject->ptr;
|
||||
return zs->zsl->length;
|
||||
return it->sl.zs->zsl->length;
|
||||
} else {
|
||||
redisPanic("Unknown sorted set encoding");
|
||||
}
|
||||
@@ -1419,19 +1400,18 @@ int zuiFind(zsetopsrc *op, zsetopval *val, double *score) {
|
||||
return 0;
|
||||
|
||||
if (op->type == REDIS_SET) {
|
||||
iterset *it = &op->iter.set;
|
||||
|
||||
if (op->encoding == REDIS_ENCODING_INTSET) {
|
||||
if (zuiLongLongFromValue(val) &&
|
||||
intsetFind(op->subject->ptr,val->ell))
|
||||
{
|
||||
if (zuiLongLongFromValue(val) && intsetFind(it->is.is,val->ell)) {
|
||||
*score = 1.0;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else if (op->encoding == REDIS_ENCODING_HT) {
|
||||
dict *ht = op->subject->ptr;
|
||||
zuiObjectFromValue(val);
|
||||
if (dictFind(ht,val->ele) != NULL) {
|
||||
if (dictFind(it->ht.dict,val->ele) != NULL) {
|
||||
*score = 1.0;
|
||||
return 1;
|
||||
} else {
|
||||
@@ -1441,19 +1421,19 @@ int zuiFind(zsetopsrc *op, zsetopval *val, double *score) {
|
||||
redisPanic("Unknown set encoding");
|
||||
}
|
||||
} else if (op->type == REDIS_ZSET) {
|
||||
iterzset *it = &op->iter.zset;
|
||||
zuiObjectFromValue(val);
|
||||
|
||||
if (op->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
if (zzlFind(op->subject->ptr,val->ele,score) != NULL) {
|
||||
if (zzlFind(it->zl.zl,val->ele,score) != NULL) {
|
||||
/* Score is already set by zzlFind. */
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
|
||||
zset *zs = op->subject->ptr;
|
||||
dictEntry *de;
|
||||
if ((de = dictFind(zs->dict,val->ele)) != NULL) {
|
||||
if ((de = dictFind(it->sl.zs->dict,val->ele)) != NULL) {
|
||||
*score = *(double*)dictGetVal(de);
|
||||
return 1;
|
||||
} else {
|
||||
@@ -1581,6 +1561,9 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < setnum; i++)
|
||||
zuiInitIterator(&src[i]);
|
||||
|
||||
/* sort sets from the smallest to largest, this will improve our
|
||||
* algorithm's performance */
|
||||
qsort(src,setnum,sizeof(zsetopsrc),zuiCompareByCardinality);
|
||||
@@ -1594,7 +1577,6 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
if (zuiLength(&src[0]) > 0) {
|
||||
/* Precondition: as src[0] is non-empty and the inputs are ordered
|
||||
* by size, all src[i > 0] are non-empty too. */
|
||||
zuiInitIterator(&src[0]);
|
||||
while (zuiNext(&src[0],&zval)) {
|
||||
double score, value;
|
||||
|
||||
@@ -1628,18 +1610,16 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
maxelelen = sdslen(tmp->ptr);
|
||||
}
|
||||
}
|
||||
zuiClearIterator(&src[0]);
|
||||
}
|
||||
} else if (op == REDIS_OP_UNION) {
|
||||
for (i = 0; i < setnum; i++) {
|
||||
if (zuiLength(&src[i]) == 0)
|
||||
continue;
|
||||
|
||||
zuiInitIterator(&src[i]);
|
||||
while (zuiNext(&src[i],&zval)) {
|
||||
double score, value;
|
||||
|
||||
/* Skip an element that when already processed */
|
||||
/* Skip key when already processed */
|
||||
if (dictFind(dstzset->dict,zuiObjectFromValue(&zval)) != NULL)
|
||||
continue;
|
||||
|
||||
@@ -1647,10 +1627,8 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
score = src[i].weight * zval.score;
|
||||
if (isnan(score)) score = 0;
|
||||
|
||||
/* We need to check only next sets to see if this element
|
||||
* exists, since we process every element just one time so
|
||||
* it can't exist in a previous set (otherwise it would be
|
||||
* already processed). */
|
||||
/* Because the inputs are sorted by size, it's only possible
|
||||
* for sets at larger indices to hold this element. */
|
||||
for (j = (i+1); j < setnum; j++) {
|
||||
/* It is not safe to access the zset we are
|
||||
* iterating, so explicitly check for equal object. */
|
||||
@@ -1673,12 +1651,14 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
if (sdslen(tmp->ptr) > maxelelen)
|
||||
maxelelen = sdslen(tmp->ptr);
|
||||
}
|
||||
zuiClearIterator(&src[i]);
|
||||
}
|
||||
} else {
|
||||
redisPanic("Unknown operator");
|
||||
}
|
||||
|
||||
for (i = 0; i < setnum; i++)
|
||||
zuiClearIterator(&src[i]);
|
||||
|
||||
if (dbDelete(c->db,dstkey)) {
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
touched = 1;
|
||||
@@ -1693,15 +1673,10 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
addReplyLongLong(c,zsetLength(dstobj));
|
||||
if (!touched) signalModifiedKey(c->db,dstkey);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_ZSET,
|
||||
(op == REDIS_OP_UNION) ? "zunionstore" : "zinterstore",
|
||||
dstkey,c->db->id);
|
||||
server.dirty++;
|
||||
} else {
|
||||
decrRefCount(dstobj);
|
||||
addReply(c,shared.czero);
|
||||
if (touched)
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",dstkey,c->db->id);
|
||||
}
|
||||
zfree(src);
|
||||
}
|
||||
@@ -2204,13 +2179,3 @@ void zrankCommand(redisClient *c) {
|
||||
void zrevrankCommand(redisClient *c) {
|
||||
zrankGenericCommand(c, 1);
|
||||
}
|
||||
|
||||
void zscanCommand(redisClient *c) {
|
||||
robj *o;
|
||||
unsigned long cursor;
|
||||
|
||||
if (parseScanCursorOrReply(c,c->argv[2],&cursor) == REDIS_ERR) return;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
|
||||
checkType(c,o,REDIS_ZSET)) return;
|
||||
scanGenericCommand(c,o,cursor);
|
||||
}
|
||||
|
||||
-60
@@ -405,66 +405,6 @@ void getRandomHexChars(char *p, unsigned int len) {
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/* Given the filename, return the absolute path as an SDS string, or NULL
|
||||
* if it fails for some reason. Note that "filename" may be an absolute path
|
||||
* already, this will be detected and handled correctly.
|
||||
*
|
||||
* The function does not try to normalize everything, but only the obvious
|
||||
* case of one or more "../" appearning at the start of "filename"
|
||||
* relative path. */
|
||||
sds getAbsolutePath(char *filename) {
|
||||
char cwd[1024];
|
||||
sds abspath;
|
||||
sds relpath = sdsnew(filename);
|
||||
|
||||
relpath = sdstrim(relpath," \r\n\t");
|
||||
if (relpath[0] == '/') return relpath; /* Path is already absolute. */
|
||||
|
||||
/* If path is relative, join cwd and relative path. */
|
||||
if (getcwd(cwd,sizeof(cwd)) == NULL) {
|
||||
sdsfree(relpath);
|
||||
return NULL;
|
||||
}
|
||||
abspath = sdsnew(cwd);
|
||||
if (sdslen(abspath) && abspath[sdslen(abspath)-1] != '/')
|
||||
abspath = sdscat(abspath,"/");
|
||||
|
||||
/* At this point we have the current path always ending with "/", and
|
||||
* the trimmed relative path. Try to normalize the obvious case of
|
||||
* trailing ../ elements at the start of the path.
|
||||
*
|
||||
* For every "../" we find in the filename, we remove it and also remove
|
||||
* the last element of the cwd, unless the current cwd is "/". */
|
||||
while (sdslen(relpath) >= 3 &&
|
||||
relpath[0] == '.' && relpath[1] == '.' && relpath[2] == '/')
|
||||
{
|
||||
sdsrange(relpath,3,-1);
|
||||
if (sdslen(abspath) > 1) {
|
||||
char *p = abspath + sdslen(abspath)-2;
|
||||
int trimlen = 1;
|
||||
|
||||
while(*p != '/') {
|
||||
p--;
|
||||
trimlen++;
|
||||
}
|
||||
sdsrange(abspath,0,-(trimlen+1));
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally glue the two parts together. */
|
||||
abspath = sdscatsds(abspath,relpath);
|
||||
sdsfree(relpath);
|
||||
return abspath;
|
||||
}
|
||||
|
||||
/* Return true if the specified path is just a file basename without any
|
||||
* relative or absolute path. This function just checks that no / or \
|
||||
* character exists inside the specified path, that's enough in the
|
||||
* environments where Redis runs. */
|
||||
int pathIsBaseName(char *path) {
|
||||
return strchr(path,'/') == NULL && strchr(path,'\\') == NULL;
|
||||
}
|
||||
|
||||
#ifdef UTIL_TEST_MAIN
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
#ifndef __REDIS_UTIL_H
|
||||
#define __REDIS_UTIL_H
|
||||
|
||||
#include "sds.h"
|
||||
|
||||
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
|
||||
int stringmatch(const char *p, const char *s, int nocase);
|
||||
long long memtoll(const char *p, int *err);
|
||||
@@ -39,7 +37,5 @@ int ll2string(char *s, size_t len, long long value);
|
||||
int string2ll(const char *s, size_t slen, long long *value);
|
||||
int string2l(const char *s, size_t slen, long *value);
|
||||
int d2string(char *buf, size_t len, double value);
|
||||
sds getAbsolutePath(char *filename);
|
||||
int pathIsBaseName(char *path);
|
||||
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.8.0"
|
||||
#define REDIS_VERSION "2.6.13"
|
||||
|
||||
+2
-3
@@ -105,12 +105,12 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include "zmalloc.h"
|
||||
#include "util.h"
|
||||
#include "ziplist.h"
|
||||
#include "endianconv.h"
|
||||
#include "redisassert.h"
|
||||
|
||||
#define ZIP_END 255
|
||||
#define ZIP_BIGLEN 254
|
||||
@@ -1041,8 +1041,7 @@ void pop(unsigned char *zl, int where) {
|
||||
}
|
||||
|
||||
int randstring(char *target, unsigned int min, unsigned int max) {
|
||||
int p = 0;
|
||||
int len = min+rand()%(max-min+1);
|
||||
int p, len = min+rand()%(max-min+1);
|
||||
int minval, maxval;
|
||||
switch(rand() % 3) {
|
||||
case 0:
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "zmalloc.h"
|
||||
#include "endianconv.h"
|
||||
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _ZIPMAP_H
|
||||
#ifndef _ZIMMAP_H
|
||||
#define _ZIPMAP_H
|
||||
|
||||
unsigned char *zipmapNew(void);
|
||||
|
||||
+2
-2
@@ -252,7 +252,7 @@ void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) {
|
||||
* function RedisEstimateRSS() that is a much faster (and less precise)
|
||||
* version of the function. */
|
||||
|
||||
#if defined(HAVE_PROC_STAT)
|
||||
#if defined(HAVE_PROCFS)
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -325,7 +325,7 @@ float zmalloc_get_fragmentation_ratio(void) {
|
||||
return (float)zmalloc_get_rss()/zmalloc_used_memory();
|
||||
}
|
||||
|
||||
#if defined(HAVE_PROC_SMAPS)
|
||||
#if defined(HAVE_PROCFS)
|
||||
size_t zmalloc_get_private_dirty(void) {
|
||||
char line[1024];
|
||||
size_t pd = 0;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
# Redis configuration for testing.
|
||||
|
||||
notify-keyspace-events KEA
|
||||
daemonize no
|
||||
pidfile /var/run/redis.pid
|
||||
port 6379
|
||||
timeout 0
|
||||
bind 127.0.0.1
|
||||
loglevel verbose
|
||||
logfile ''
|
||||
logfile stdout
|
||||
databases 16
|
||||
|
||||
save 900 1
|
||||
|
||||
@@ -98,7 +98,7 @@ tags {"aof"} {
|
||||
}
|
||||
}
|
||||
|
||||
## Test that SPOP (that modifies the client's argc/argv) is correctly free'd
|
||||
## Test that SPOP (that modifies the client its argc/argv) is correctly free'd
|
||||
create_aof {
|
||||
append_to_aof [formatCommand sadd set foo]
|
||||
append_to_aof [formatCommand sadd set bar]
|
||||
|
||||
@@ -2,12 +2,9 @@ start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
test {First server should have role slave after SLAVEOF} {
|
||||
r -1 slaveof [srv 0 host] [srv 0 port]
|
||||
wait_for_condition 50 100 {
|
||||
[s -1 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
}
|
||||
after 1000
|
||||
s -1 role
|
||||
} {slave}
|
||||
|
||||
if {$::accurate} {set numops 50000} else {set numops 5000}
|
||||
|
||||
@@ -32,70 +29,3 @@ start_server {tags {"repl"}} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
test {First server should have role slave after SLAVEOF} {
|
||||
r -1 slaveof [srv 0 host] [srv 0 port]
|
||||
wait_for_condition 50 100 {
|
||||
[s -1 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
}
|
||||
|
||||
set numops 20000 ;# Enough to trigger the Script Cache LRU eviction.
|
||||
|
||||
# While we are at it, enable AOF to test it will be consistent as well
|
||||
# after the test.
|
||||
r config set appendonly yes
|
||||
|
||||
test {MASTER and SLAVE consistency with EVALSHA replication} {
|
||||
array set oldsha {}
|
||||
for {set j 0} {$j < $numops} {incr j} {
|
||||
set key "key:$j"
|
||||
# Make sure to create scripts that have different SHA1s
|
||||
set script "return redis.call('incr','$key')"
|
||||
set sha1 [r eval "return redis.sha1hex(\"$script\")" 0]
|
||||
set oldsha($j) $sha1
|
||||
r eval $script 0
|
||||
set res [r evalsha $sha1 0]
|
||||
assert {$res == 2}
|
||||
# Additionally call one of the old scripts as well, at random.
|
||||
set res [r evalsha $oldsha([randomInt $j]) 0]
|
||||
assert {$res > 2}
|
||||
|
||||
# Trigger an AOF rewrite while we are half-way, this also
|
||||
# forces the flush of the script cache, and we will cover
|
||||
# more code as a result.
|
||||
if {$j == $numops / 2} {
|
||||
catch {r bgrewriteaof}
|
||||
}
|
||||
}
|
||||
|
||||
wait_for_condition 50 100 {
|
||||
[r dbsize] == $numops &&
|
||||
[r -1 dbsize] == $numops &&
|
||||
[r debug digest] eq [r -1 debug digest]
|
||||
} else {
|
||||
set csv1 [csvdump r]
|
||||
set csv2 [csvdump {r -1}]
|
||||
set fd [open /tmp/repldump1.txt w]
|
||||
puts -nonewline $fd $csv1
|
||||
close $fd
|
||||
set fd [open /tmp/repldump2.txt w]
|
||||
puts -nonewline $fd $csv2
|
||||
close $fd
|
||||
puts "Master - Slave inconsistency"
|
||||
puts "Run diff -u against /tmp/repldump*.txt for more info"
|
||||
|
||||
}
|
||||
|
||||
set old_digest [r debug digest]
|
||||
r config set appendonly no
|
||||
r debug loadaof
|
||||
set new_digest [r debug digest]
|
||||
assert {$old_digest eq $new_digest}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,22 +10,21 @@ proc stop_bg_complex_data {handle} {
|
||||
start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
set master_port [srv -1 port]
|
||||
set slave [srv 0 client]
|
||||
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
set load_handle0 [start_bg_complex_data $master_host $master_port 9 100000]
|
||||
set load_handle1 [start_bg_complex_data $master_host $master_port 11 100000]
|
||||
set load_handle2 [start_bg_complex_data $master_host $master_port 12 100000]
|
||||
|
||||
test {First server should have role slave after SLAVEOF} {
|
||||
$slave slaveof $master_host $master_port
|
||||
r -1 slaveof [srv 0 host] [srv 0 port]
|
||||
after 1000
|
||||
s 0 role
|
||||
s -1 role
|
||||
} {slave}
|
||||
|
||||
test {Test replication with parallel clients writing in differnet DBs} {
|
||||
lappend slave [srv 0 client]
|
||||
after 5000
|
||||
stop_bg_complex_data $load_handle0
|
||||
stop_bg_complex_data $load_handle1
|
||||
@@ -38,7 +37,7 @@ start_server {tags {"repl"}} {
|
||||
}
|
||||
assert {[$master dbsize] > 0}
|
||||
|
||||
if {[$master debug digest] ne [$slave debug digest]} {
|
||||
if {[r debug digest] ne [r -1 debug digest]} {
|
||||
set csv1 [csvdump r]
|
||||
set csv2 [csvdump {r -1}]
|
||||
set fd [open /tmp/repldump1.txt w]
|
||||
@@ -54,83 +53,3 @@ start_server {tags {"repl"}} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
set master_port [srv -1 port]
|
||||
set slave [srv 0 client]
|
||||
|
||||
test {First server should have role slave after SLAVEOF} {
|
||||
$slave slaveof $master_host $master_port
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
}
|
||||
|
||||
test {With min-slaves-to-write (1,3): master should be writable} {
|
||||
$master config set min-slaves-max-lag 3
|
||||
$master config set min-slaves-to-write 1
|
||||
$master set foo bar
|
||||
} {OK}
|
||||
|
||||
test {With min-slaves-to-write (2,3): master should not be writable} {
|
||||
$master config set min-slaves-max-lag 3
|
||||
$master config set min-slaves-to-write 2
|
||||
catch {$master set foo bar} e
|
||||
set e
|
||||
} {NOREPLICAS*}
|
||||
|
||||
test {With min-slaves-to-write: master not writable with lagged slave} {
|
||||
$master config set min-slaves-max-lag 2
|
||||
$master config set min-slaves-to-write 1
|
||||
assert {[$master set foo bar] eq {OK}}
|
||||
$slave deferred 1
|
||||
$slave debug sleep 6
|
||||
after 4000
|
||||
catch {$master set foo bar} e
|
||||
set e
|
||||
} {NOREPLICAS*}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
set master_port [srv -1 port]
|
||||
set slave [srv 0 client]
|
||||
|
||||
test {First server should have role slave after SLAVEOF} {
|
||||
$slave slaveof $master_host $master_port
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 role] eq {slave}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
}
|
||||
|
||||
test {Replication: commands with many arguments (issue #1221)} {
|
||||
# We now issue large MSET commands, that may trigger a specific
|
||||
# class of bugs, see issue #1221.
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
set cmd [list mset]
|
||||
for {set x 0} {$x < 1000} {incr x} {
|
||||
lappend cmd [randomKey] [randomValue]
|
||||
}
|
||||
$master {*}$cmd
|
||||
}
|
||||
|
||||
set retry 10
|
||||
while {$retry && ([$master debug digest] ne [$slave debug digest])}\
|
||||
{
|
||||
after 1000
|
||||
incr retry -1
|
||||
}
|
||||
assert {[$master dbsize] > 0}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
proc start_bg_complex_data {host port db ops} {
|
||||
set tclsh [info nameofexecutable]
|
||||
exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops &
|
||||
}
|
||||
|
||||
proc stop_bg_complex_data {handle} {
|
||||
catch {exec /bin/kill -9 $handle}
|
||||
}
|
||||
|
||||
# Creates a master-slave pair and breaks the link continuously to force
|
||||
# partial resyncs attempts, all this while flooding the master with
|
||||
# write queries.
|
||||
#
|
||||
# You can specifiy backlog size, ttl, delay before reconnection, test duration
|
||||
# in seconds, and an additional condition to verify at the end.
|
||||
proc test_psync {descr duration backlog_size backlog_ttl delay cond} {
|
||||
start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
set master_port [srv -1 port]
|
||||
set slave [srv 0 client]
|
||||
|
||||
$master config set repl-backlog-size $backlog_size
|
||||
$master config set repl-backlog-ttl $backlog_ttl
|
||||
|
||||
set load_handle0 [start_bg_complex_data $master_host $master_port 9 100000]
|
||||
set load_handle1 [start_bg_complex_data $master_host $master_port 11 100000]
|
||||
set load_handle2 [start_bg_complex_data $master_host $master_port 12 100000]
|
||||
|
||||
test {First server should have role slave after SLAVEOF} {
|
||||
$slave slaveof $master_host $master_port
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 role] eq {slave}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
}
|
||||
|
||||
test "Test replication partial resync: $descr" {
|
||||
# Now while the clients are writing data, break the maste-slave
|
||||
# link multiple times.
|
||||
for {set j 0} {$j < $duration*10} {incr j} {
|
||||
after 100
|
||||
# catch {puts "MASTER [$master dbsize] keys, SLAVE [$slave dbsize] keys"}
|
||||
|
||||
if {($j % 20) == 0} {
|
||||
catch {
|
||||
if {$delay} {
|
||||
$slave multi
|
||||
$slave client kill $master_host:$master_port
|
||||
$slave debug sleep $delay
|
||||
$slave exec
|
||||
} else {
|
||||
$slave client kill $master_host:$master_port
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stop_bg_complex_data $load_handle0
|
||||
stop_bg_complex_data $load_handle1
|
||||
stop_bg_complex_data $load_handle2
|
||||
set retry 10
|
||||
while {$retry && ([$master debug digest] ne [$slave debug digest])}\
|
||||
{
|
||||
after 1000
|
||||
incr retry -1
|
||||
}
|
||||
assert {[$master dbsize] > 0}
|
||||
|
||||
if {[$master debug digest] ne [$slave debug digest]} {
|
||||
set csv1 [csvdump r]
|
||||
set csv2 [csvdump {r -1}]
|
||||
set fd [open /tmp/repldump1.txt w]
|
||||
puts -nonewline $fd $csv1
|
||||
close $fd
|
||||
set fd [open /tmp/repldump2.txt w]
|
||||
puts -nonewline $fd $csv2
|
||||
close $fd
|
||||
puts "Master - Slave inconsistency"
|
||||
puts "Run diff -u against /tmp/repldump*.txt for more info"
|
||||
}
|
||||
assert_equal [r debug digest] [r -1 debug digest]
|
||||
eval $cond
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test_psync {ok psync} 6 1000000 3600 0 {
|
||||
assert {[s -1 sync_partial_ok] > 0}
|
||||
}
|
||||
|
||||
test_psync {no backlog} 6 100 3600 0 {
|
||||
assert {[s -1 sync_partial_err] > 0}
|
||||
}
|
||||
|
||||
test_psync {ok after delay} 3 100000000 3600 3 {
|
||||
assert {[s -1 sync_partial_ok] > 0}
|
||||
}
|
||||
|
||||
test_psync {backlog expired} 3 100000000 1 3 {
|
||||
assert {[s -1 sync_partial_err] > 0}
|
||||
}
|
||||
@@ -112,7 +112,7 @@ start_server {tags {"repl"}} {
|
||||
set retry 500
|
||||
while {$retry} {
|
||||
set info [r -3 info]
|
||||
if {[string match {*slave0:*state=online*slave1:*state=online*slave2:*state=online*} $info]} {
|
||||
if {[string match {*slave0:*,online*slave1:*,online*slave2:*,online*} $info]} {
|
||||
break
|
||||
} else {
|
||||
incr retry -1
|
||||
|
||||
@@ -115,10 +115,6 @@ proc ::redis::__method__channel {id fd} {
|
||||
return $fd
|
||||
}
|
||||
|
||||
proc ::redis::__method__deferred {id fd val} {
|
||||
set ::redis::deferred($id) $val
|
||||
}
|
||||
|
||||
proc ::redis::redis_write {fd buf} {
|
||||
puts -nonewline $fd $buf
|
||||
}
|
||||
|
||||
@@ -84,8 +84,8 @@ proc ping_server {host port} {
|
||||
puts $fd "PING\r\n"
|
||||
flush $fd
|
||||
set reply [gets $fd]
|
||||
if {[string range $reply 0 0] eq {+} ||
|
||||
[string range $reply 0 0] eq {-}} {
|
||||
if {[string range $reply 0 4] eq {+PONG} ||
|
||||
[string range $reply 0 3] eq {-ERR}} {
|
||||
set retval 1
|
||||
}
|
||||
close $fd
|
||||
|
||||
@@ -91,12 +91,10 @@ proc wait_for_sync r {
|
||||
}
|
||||
}
|
||||
|
||||
# Random integer between 0 and max (excluded).
|
||||
proc randomInt {max} {
|
||||
expr {int(rand()*$max)}
|
||||
}
|
||||
|
||||
# Random signed integer between -max and max (both extremes excluded).
|
||||
proc randomSignedInt {max} {
|
||||
set i [randomInt $max]
|
||||
if {rand() > 0.5} {
|
||||
|
||||
@@ -16,7 +16,6 @@ set ::all_tests {
|
||||
unit/auth
|
||||
unit/protocol
|
||||
unit/basic
|
||||
unit/scan
|
||||
unit/type/list
|
||||
unit/type/list-2
|
||||
unit/type/list-3
|
||||
@@ -33,7 +32,6 @@ set ::all_tests {
|
||||
integration/replication-2
|
||||
integration/replication-3
|
||||
integration/replication-4
|
||||
integration/replication-psync
|
||||
integration/aof
|
||||
integration/rdb
|
||||
integration/convert-zipmap-hash-on-load
|
||||
@@ -46,7 +44,6 @@ set ::all_tests {
|
||||
unit/obuf-limits
|
||||
unit/dump
|
||||
unit/bitops
|
||||
unit/memefficiency
|
||||
}
|
||||
# Index to the next test to run in the ::all_tests list.
|
||||
set ::next_test 0
|
||||
@@ -188,7 +185,7 @@ proc test_server_main {} {
|
||||
if {!$::quiet} {
|
||||
puts "Starting test server at port $port"
|
||||
}
|
||||
socket -server accept_test_clients -myaddr 127.0.0.1 $port
|
||||
socket -server accept_test_clients $port
|
||||
|
||||
# Start the client instances
|
||||
set ::clients_pids {}
|
||||
@@ -441,9 +438,15 @@ proc read_from_replication_stream {s} {
|
||||
after 100
|
||||
}
|
||||
fconfigure $s -blocking 1
|
||||
set count [string range $count 1 end]
|
||||
|
||||
# Workaround for Redis 2.6, not always using the new protocol in the
|
||||
# replication channel (this was fixed in >= 2.8).
|
||||
if {[string tolower [lindex [split $count] 0]] eq {select}} {
|
||||
return $count
|
||||
}
|
||||
|
||||
# Return a list of arguments for the command.
|
||||
set count [string range $count 1 end]
|
||||
set res {}
|
||||
for {set j 0} {$j < $count} {incr j} {
|
||||
read $s 1
|
||||
@@ -467,7 +470,7 @@ proc close_replication_stream {s} {
|
||||
# With the parallel test running multiple Redis instances at the same time
|
||||
# we need a fast enough computer, otherwise a lot of tests may generate
|
||||
# false positives.
|
||||
# If the computer is too slow we revert the sequential test without any
|
||||
# If the computer is too slow we revert the sequetial test without any
|
||||
# parallelism, that is, clients == 1.
|
||||
proc is_a_slow_computer {} {
|
||||
set start [clock milliseconds]
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ start_server {tags {"auth"} overrides {requirepass foobar}} {
|
||||
test {Arbitrary command gives an error when AUTH is required} {
|
||||
catch {r set foo bar} err
|
||||
set _ $err
|
||||
} {NOAUTH*}
|
||||
} {ERR*operation not permitted}
|
||||
|
||||
test {AUTH succeeds when the right password is given} {
|
||||
r auth foobar
|
||||
|
||||
+4
-11
@@ -143,7 +143,7 @@ start_server {tags {"basic"}} {
|
||||
catch {r incr mylist} err
|
||||
r rpop mylist
|
||||
format $err
|
||||
} {WRONGTYPE*}
|
||||
} {ERR*}
|
||||
|
||||
test {DECRBY over 32bit value with over 32bit increment, negative res} {
|
||||
r set novar 17179869184
|
||||
@@ -201,7 +201,7 @@ start_server {tags {"basic"}} {
|
||||
catch {r incrbyfloat mylist 1.0} err
|
||||
r del mylist
|
||||
format $err
|
||||
} {WRONGTYPE*}
|
||||
} {ERR*kind*}
|
||||
|
||||
test {INCRBYFLOAT does not allow NaN or Infinity} {
|
||||
r set foo 0
|
||||
@@ -531,7 +531,7 @@ start_server {tags {"basic"}} {
|
||||
test "SETBIT against key with wrong type" {
|
||||
r del mykey
|
||||
r lpush mykey "foo"
|
||||
assert_error "WRONGTYPE*" {r setbit mykey 0 1}
|
||||
assert_error "*wrong kind*" {r setbit mykey 0 1}
|
||||
}
|
||||
|
||||
test "SETBIT with out of range bit offset" {
|
||||
@@ -665,7 +665,7 @@ start_server {tags {"basic"}} {
|
||||
test "SETRANGE against key with wrong type" {
|
||||
r del mykey
|
||||
r lpush mykey "foo"
|
||||
assert_error "WRONGTYPE*" {r setrange mykey 0 bar}
|
||||
assert_error "*wrong kind*" {r setrange mykey 0 bar}
|
||||
}
|
||||
|
||||
test "SETRANGE with out of range offset" {
|
||||
@@ -754,11 +754,4 @@ start_server {tags {"basic"}} {
|
||||
set ttl [r ttl foo]
|
||||
assert {$ttl <= 10 && $ttl > 5}
|
||||
}
|
||||
|
||||
test {KEYS * two times with long key, Github issue #1208} {
|
||||
r flushdb
|
||||
r set dlskeriewrioeuwqoirueioqwrueoqwrueqw test
|
||||
r keys *
|
||||
r keys *
|
||||
} {dlskeriewrioeuwqoirueioqwrueoqwrueqw}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ start_server {tags {"bitops"}} {
|
||||
r lpush c foo
|
||||
catch {r bitop xor dest a b c d} e
|
||||
set e
|
||||
} {WRONGTYPE*}
|
||||
} {*ERR*}
|
||||
|
||||
test {BITOP with empty string after non empty string (issue #529)} {
|
||||
r flushdb
|
||||
|
||||
+1
-19
@@ -121,31 +121,13 @@ start_server {tags {"expire"}} {
|
||||
list $a $b
|
||||
} {somevalue {}}
|
||||
|
||||
test {TTL returns tiem to live in seconds} {
|
||||
r del x
|
||||
r setex x 10 somevalue
|
||||
set ttl [r ttl x]
|
||||
assert {$ttl > 8 && $ttl <= 10}
|
||||
}
|
||||
|
||||
test {PTTL returns time to live in milliseconds} {
|
||||
test {PTTL returns millisecond time to live} {
|
||||
r del x
|
||||
r setex x 1 somevalue
|
||||
set ttl [r pttl x]
|
||||
assert {$ttl > 900 && $ttl <= 1000}
|
||||
}
|
||||
|
||||
test {TTL / PTTL return -1 if key has no expire} {
|
||||
r del x
|
||||
r set x hello
|
||||
list [r ttl x] [r pttl x]
|
||||
} {-1 -1}
|
||||
|
||||
test {TTL / PTTL return -2 if key does not exit} {
|
||||
r del x
|
||||
list [r ttl x] [r pttl x]
|
||||
} {-2 -2}
|
||||
|
||||
test {Redis should actively expire keys incrementally} {
|
||||
r flushdb
|
||||
r psetex key1 500 a
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
proc test_memory_efficiency {range} {
|
||||
r flushall
|
||||
set base_mem [s used_memory]
|
||||
set written 0
|
||||
for {set j 0} {$j < 10000} {incr j} {
|
||||
set key key:$j
|
||||
set val [string repeat A [expr {int(rand()*$range)}]]
|
||||
r set $key $val
|
||||
incr written [string length $key]
|
||||
incr written [string length $val]
|
||||
incr written 2 ;# A separator is the minimum to store key-value data.
|
||||
}
|
||||
set current_mem [s used_memory]
|
||||
set used [expr {$current_mem-$base_mem}]
|
||||
set efficiency [expr {double($written)/$used}]
|
||||
return $efficiency
|
||||
}
|
||||
|
||||
start_server {tags {"memefficiency"}} {
|
||||
foreach {size_range expected_min_efficiency} {
|
||||
32 0.15
|
||||
64 0.25
|
||||
128 0.35
|
||||
1024 0.75
|
||||
16384 0.90
|
||||
} {
|
||||
test "Memory efficiency with values in range $size_range" {
|
||||
set efficiency [test_memory_efficiency $size_range]
|
||||
assert {$efficiency >= $expected_min_efficiency}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-155
@@ -193,7 +193,7 @@ start_server {tags {"pubsub"}} {
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "PUNSUBSCRIBE and UNSUBSCRIBE should always reply" {
|
||||
test "PUNSUBSCRIBE and UNSUBSCRIBE should always reply." {
|
||||
# Make sure we are not subscribed to any channel at all.
|
||||
r punsubscribe
|
||||
r unsubscribe
|
||||
@@ -202,158 +202,4 @@ start_server {tags {"pubsub"}} {
|
||||
set reply2 [r unsubscribe]
|
||||
concat $reply1 $reply2
|
||||
} {punsubscribe {} 0 unsubscribe {} 0}
|
||||
|
||||
### Keyspace events notification tests
|
||||
|
||||
test "Keyspace notifications: we receive keyspace notifications" {
|
||||
r config set notify-keyspace-events KA
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r set foo bar
|
||||
assert_equal {pmessage * __keyspace@9__:foo set} [$rd1 read]
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: we receive keyevent notifications" {
|
||||
r config set notify-keyspace-events EA
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r set foo bar
|
||||
assert_equal {pmessage * __keyevent@9__:set foo} [$rd1 read]
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: we can receive both kind of events" {
|
||||
r config set notify-keyspace-events KEA
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r set foo bar
|
||||
assert_equal {pmessage * __keyspace@9__:foo set} [$rd1 read]
|
||||
assert_equal {pmessage * __keyevent@9__:set foo} [$rd1 read]
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: we are able to mask events" {
|
||||
r config set notify-keyspace-events KEl
|
||||
r del mylist
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r set foo bar
|
||||
r lpush mylist a
|
||||
# No notification for set, because only list commands are enabled.
|
||||
assert_equal {pmessage * __keyspace@9__:mylist lpush} [$rd1 read]
|
||||
assert_equal {pmessage * __keyevent@9__:lpush mylist} [$rd1 read]
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: general events test" {
|
||||
r config set notify-keyspace-events KEg
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r set foo bar
|
||||
r expire foo 1
|
||||
r del foo
|
||||
assert_equal {pmessage * __keyspace@9__:foo expire} [$rd1 read]
|
||||
assert_equal {pmessage * __keyevent@9__:expire foo} [$rd1 read]
|
||||
assert_equal {pmessage * __keyspace@9__:foo del} [$rd1 read]
|
||||
assert_equal {pmessage * __keyevent@9__:del foo} [$rd1 read]
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: list events test" {
|
||||
r config set notify-keyspace-events KEl
|
||||
r del mylist
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r lpush mylist a
|
||||
r rpush mylist a
|
||||
r rpop mylist
|
||||
assert_equal {pmessage * __keyspace@9__:mylist lpush} [$rd1 read]
|
||||
assert_equal {pmessage * __keyevent@9__:lpush mylist} [$rd1 read]
|
||||
assert_equal {pmessage * __keyspace@9__:mylist rpush} [$rd1 read]
|
||||
assert_equal {pmessage * __keyevent@9__:rpush mylist} [$rd1 read]
|
||||
assert_equal {pmessage * __keyspace@9__:mylist rpop} [$rd1 read]
|
||||
assert_equal {pmessage * __keyevent@9__:rpop mylist} [$rd1 read]
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: set events test" {
|
||||
r config set notify-keyspace-events Ks
|
||||
r del myset
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r sadd myset a b c d
|
||||
r srem myset x
|
||||
r sadd myset x y z
|
||||
r srem myset x
|
||||
assert_equal {pmessage * __keyspace@9__:myset sadd} [$rd1 read]
|
||||
assert_equal {pmessage * __keyspace@9__:myset sadd} [$rd1 read]
|
||||
assert_equal {pmessage * __keyspace@9__:myset srem} [$rd1 read]
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: zset events test" {
|
||||
r config set notify-keyspace-events Kz
|
||||
r del myzset
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r zadd myzset 1 a 2 b
|
||||
r zrem myzset x
|
||||
r zadd myzset 3 x 4 y 5 z
|
||||
r zrem myzset x
|
||||
assert_equal {pmessage * __keyspace@9__:myzset zadd} [$rd1 read]
|
||||
assert_equal {pmessage * __keyspace@9__:myzset zadd} [$rd1 read]
|
||||
assert_equal {pmessage * __keyspace@9__:myzset zrem} [$rd1 read]
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: hash events test" {
|
||||
r config set notify-keyspace-events Kh
|
||||
r del myhash
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r hmset myhash yes 1 no 0
|
||||
r hincrby myhash yes 10
|
||||
assert_equal {pmessage * __keyspace@9__:myhash hset} [$rd1 read]
|
||||
assert_equal {pmessage * __keyspace@9__:myhash hincrby} [$rd1 read]
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: expired events (triggered expire)" {
|
||||
r config set notify-keyspace-events Ex
|
||||
r del foo
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r psetex foo 100 1
|
||||
wait_for_condition 50 100 {
|
||||
[r exists foo] == 0
|
||||
} else {
|
||||
fail "Key does not expire?!"
|
||||
}
|
||||
assert_equal {pmessage * __keyevent@9__:expired foo} [$rd1 read]
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: expired events (background expire)" {
|
||||
r config set notify-keyspace-events Ex
|
||||
r del foo
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r psetex foo 100 1
|
||||
assert_equal {pmessage * __keyevent@9__:expired foo} [$rd1 read]
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: evicted events" {
|
||||
r config set notify-keyspace-events Ee
|
||||
r config set maxmemory-policy allkeys-lru
|
||||
r flushdb
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [psubscribe $rd1 *]
|
||||
r set foo bar
|
||||
r config set maxmemory 1
|
||||
assert_equal {pmessage * __keyevent@9__:evicted foo} [$rd1 read]
|
||||
r config set maxmemory 0
|
||||
$rd1 close
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
start_server {tags {"scan"}} {
|
||||
test "SCAN basic" {
|
||||
r flushdb
|
||||
r debug populate 1000
|
||||
|
||||
set cur 0
|
||||
set keys {}
|
||||
while 1 {
|
||||
set res [r scan $cur]
|
||||
set cur [lindex $res 0]
|
||||
set k [lindex $res 1]
|
||||
lappend keys {*}$k
|
||||
if {$cur == 0} break
|
||||
}
|
||||
|
||||
set keys [lsort -unique $keys]
|
||||
assert_equal 1000 [llength $keys]
|
||||
}
|
||||
|
||||
test "SCAN COUNT" {
|
||||
r flushdb
|
||||
r debug populate 1000
|
||||
|
||||
set cur 0
|
||||
set keys {}
|
||||
while 1 {
|
||||
set res [r scan $cur count 5]
|
||||
set cur [lindex $res 0]
|
||||
set k [lindex $res 1]
|
||||
lappend keys {*}$k
|
||||
if {$cur == 0} break
|
||||
}
|
||||
|
||||
set keys [lsort -unique $keys]
|
||||
assert_equal 1000 [llength $keys]
|
||||
}
|
||||
|
||||
test "SCAN MATCH" {
|
||||
r flushdb
|
||||
r debug populate 1000
|
||||
|
||||
set cur 0
|
||||
set keys {}
|
||||
while 1 {
|
||||
set res [r scan $cur match "key:1??"]
|
||||
set cur [lindex $res 0]
|
||||
set k [lindex $res 1]
|
||||
lappend keys {*}$k
|
||||
if {$cur == 0} break
|
||||
}
|
||||
|
||||
set keys [lsort -unique $keys]
|
||||
assert_equal 100 [llength $keys]
|
||||
}
|
||||
|
||||
foreach enc {intset hashtable} {
|
||||
test "SSCAN with encoding $enc" {
|
||||
# Create the Set
|
||||
r del set
|
||||
if {$enc eq {intset}} {
|
||||
set prefix ""
|
||||
} else {
|
||||
set prefix "ele:"
|
||||
}
|
||||
set elements {}
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
lappend elements ${prefix}${j}
|
||||
}
|
||||
r sadd set {*}$elements
|
||||
|
||||
# Verify that the encoding matches.
|
||||
assert {[r object encoding set] eq $enc}
|
||||
|
||||
# Test SSCAN
|
||||
set cur 0
|
||||
set keys {}
|
||||
while 1 {
|
||||
set res [r sscan set $cur]
|
||||
set cur [lindex $res 0]
|
||||
set k [lindex $res 1]
|
||||
lappend keys {*}$k
|
||||
if {$cur == 0} break
|
||||
}
|
||||
|
||||
set keys [lsort -unique $keys]
|
||||
assert_equal 100 [llength $keys]
|
||||
}
|
||||
}
|
||||
|
||||
foreach enc {ziplist hashtable} {
|
||||
test "HSCAN with encoding $enc" {
|
||||
# Create the Hash
|
||||
r del hash
|
||||
if {$enc eq {ziplist}} {
|
||||
set count 30
|
||||
} else {
|
||||
set count 1000
|
||||
}
|
||||
set elements {}
|
||||
for {set j 0} {$j < $count} {incr j} {
|
||||
lappend elements key:$j $j
|
||||
}
|
||||
r hmset hash {*}$elements
|
||||
|
||||
# Verify that the encoding matches.
|
||||
assert {[r object encoding hash] eq $enc}
|
||||
|
||||
# Test HSCAN
|
||||
set cur 0
|
||||
set keys {}
|
||||
while 1 {
|
||||
set res [r hscan hash $cur]
|
||||
set cur [lindex $res 0]
|
||||
set k [lindex $res 1]
|
||||
lappend keys {*}$k
|
||||
if {$cur == 0} break
|
||||
}
|
||||
|
||||
set keys2 {}
|
||||
foreach {k v} $keys {
|
||||
assert {$k eq "key:$v"}
|
||||
lappend keys2 $k
|
||||
}
|
||||
|
||||
set keys2 [lsort -unique $keys2]
|
||||
assert_equal $count [llength $keys2]
|
||||
}
|
||||
}
|
||||
|
||||
foreach enc {ziplist skiplist} {
|
||||
test "ZSCAN with encoding $enc" {
|
||||
# Create the Sorted Set
|
||||
r del zset
|
||||
if {$enc eq {ziplist}} {
|
||||
set count 30
|
||||
} else {
|
||||
set count 1000
|
||||
}
|
||||
set elements {}
|
||||
for {set j 0} {$j < $count} {incr j} {
|
||||
lappend elements $j key:$j
|
||||
}
|
||||
r zadd zset {*}$elements
|
||||
|
||||
# Verify that the encoding matches.
|
||||
assert {[r object encoding zset] eq $enc}
|
||||
|
||||
# Test ZSCAN
|
||||
set cur 0
|
||||
set keys {}
|
||||
while 1 {
|
||||
set res [r zscan zset $cur]
|
||||
set cur [lindex $res 0]
|
||||
set k [lindex $res 1]
|
||||
lappend keys {*}$k
|
||||
if {$cur == 0} break
|
||||
}
|
||||
|
||||
set keys2 {}
|
||||
foreach {k v} $keys {
|
||||
assert {$k eq "key:$v"}
|
||||
lappend keys2 $k
|
||||
}
|
||||
|
||||
set keys2 [lsort -unique $keys2]
|
||||
assert_equal $count [llength $keys2]
|
||||
}
|
||||
}
|
||||
|
||||
test "SCAN guarantees check under write load" {
|
||||
r flushdb
|
||||
r debug populate 100
|
||||
|
||||
# We start scanning here, so keys from 0 to 99 should all be
|
||||
# reported at the end of the iteration.
|
||||
set keys {}
|
||||
while 1 {
|
||||
set res [r scan $cur]
|
||||
set cur [lindex $res 0]
|
||||
set k [lindex $res 1]
|
||||
lappend keys {*}$k
|
||||
if {$cur == 0} break
|
||||
# Write 10 random keys at every SCAN iteration.
|
||||
for {set j 0} {$j < 10} {incr j} {
|
||||
r set addedkey:[randomInt 1000] foo
|
||||
}
|
||||
}
|
||||
|
||||
set keys2 {}
|
||||
foreach k $keys {
|
||||
if {[string length $k] > 6} continue
|
||||
lappend keys2 $k
|
||||
}
|
||||
|
||||
set keys2 [lsort -unique $keys2]
|
||||
assert_equal 100 [llength $keys2]
|
||||
}
|
||||
|
||||
test "SSCAN with integer encoded object (issue #1345)" {
|
||||
set objects {1 a}
|
||||
r del set
|
||||
r sadd set {*}$objects
|
||||
set res [r sscan set 0 MATCH *a* COUNT 100]
|
||||
assert_equal [lsort -unique [lindex $res 1]] {a}
|
||||
set res [r sscan set 0 MATCH *1* COUNT 100]
|
||||
assert_equal [lsort -unique [lindex $res 1]] {1}
|
||||
}
|
||||
|
||||
test "SSCAN with PATTERN" {
|
||||
r del mykey
|
||||
r sadd mykey foo fab fiz foobar 1 2 3 4
|
||||
set res [r sscan mykey 0 MATCH foo* COUNT 10000]
|
||||
lsort -unique [lindex $res 1]
|
||||
} {foo foobar}
|
||||
|
||||
test "HSCAN with PATTERN" {
|
||||
r del mykey
|
||||
r hmset mykey foo 1 fab 2 fiz 3 foobar 10 1 a 2 b 3 c 4 d
|
||||
set res [r hscan mykey 0 MATCH foo* COUNT 10000]
|
||||
lsort -unique [lindex $res 1]
|
||||
} {1 10 foo foobar}
|
||||
|
||||
test "ZSCAN with PATTERN" {
|
||||
r del mykey
|
||||
r zadd mykey 1 foo 2 fab 3 fiz 10 foobar
|
||||
set res [r zscan mykey 0 MATCH foo* COUNT 10000]
|
||||
lsort -unique [lindex $res 1]
|
||||
}
|
||||
}
|
||||
@@ -281,38 +281,6 @@ start_server {tags {"scripting"}} {
|
||||
assert_equal $rand1 $rand2
|
||||
assert {$rand2 ne $rand3}
|
||||
}
|
||||
|
||||
test {EVAL does not leak in the Lua stack} {
|
||||
r set x 0
|
||||
# Use a non blocking client to speedup the loop.
|
||||
set rd [redis_deferring_client]
|
||||
for {set j 0} {$j < 10000} {incr j} {
|
||||
$rd eval {return redis.call("incr",KEYS[1])} 1 x
|
||||
}
|
||||
for {set j 0} {$j < 10000} {incr j} {
|
||||
$rd read
|
||||
}
|
||||
assert {[s used_memory_lua] < 1024*100}
|
||||
$rd close
|
||||
r get x
|
||||
} {10000}
|
||||
|
||||
test {EVAL processes writes from AOF in read-only slaves} {
|
||||
r flushall
|
||||
r config set appendonly yes
|
||||
r eval {redis.call("set","foo","100")} 0
|
||||
r eval {redis.call("incr","foo")} 0
|
||||
r eval {redis.call("incr","foo")} 0
|
||||
wait_for_condition 50 100 {
|
||||
[s aof_rewrite_in_progress] == 0
|
||||
} else {
|
||||
fail "AOF rewrite can't complete after CONFIG SET appendonly yes."
|
||||
}
|
||||
r config set slave-read-only yes
|
||||
r slaveof 127.0.0.1 0
|
||||
r debug loadaof
|
||||
r get foo
|
||||
} {102}
|
||||
}
|
||||
|
||||
# Start a new server since the last test in this stanza will kill the
|
||||
|
||||
+15
-15
@@ -263,7 +263,7 @@ start_server {
|
||||
r del blist target
|
||||
r set blist nolist
|
||||
$rd brpoplpush blist target 1
|
||||
assert_error "WRONGTYPE*" {$rd read}
|
||||
assert_error "ERR*wrong kind*" {$rd read}
|
||||
}
|
||||
|
||||
test "BRPOPLPUSH with wrong destination type" {
|
||||
@@ -272,7 +272,7 @@ start_server {
|
||||
r set target nolist
|
||||
r lpush blist foo
|
||||
$rd brpoplpush blist target 1
|
||||
assert_error "WRONGTYPE*" {$rd read}
|
||||
assert_error "ERR*wrong kind*" {$rd read}
|
||||
|
||||
set rd [redis_deferring_client]
|
||||
r del blist target
|
||||
@@ -280,7 +280,7 @@ start_server {
|
||||
$rd brpoplpush blist target 0
|
||||
after 1000
|
||||
r rpush blist foo
|
||||
assert_error "WRONGTYPE*" {$rd read}
|
||||
assert_error "ERR*wrong kind*" {$rd read}
|
||||
assert_equal {foo} [r lrange blist 0 -1]
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ start_server {
|
||||
r set target nolist
|
||||
$rd brpoplpush blist target 0
|
||||
r rpush blist a b c
|
||||
assert_error "WRONGTYPE*" {$rd read}
|
||||
assert_error "ERR*wrong kind*" {$rd read}
|
||||
r lrange blist 0 -1
|
||||
} {a b c}
|
||||
|
||||
@@ -303,7 +303,7 @@ start_server {
|
||||
$rd2 brpoplpush blist target2 0
|
||||
r lpush blist foo
|
||||
|
||||
assert_error "WRONGTYPE*" {$rd1 read}
|
||||
assert_error "ERR*wrong kind*" {$rd1 read}
|
||||
assert_equal {foo} [$rd2 read]
|
||||
assert_equal {foo} [r lrange target2 0 -1]
|
||||
}
|
||||
@@ -445,7 +445,7 @@ start_server {
|
||||
r del blist1 blist2
|
||||
r set blist2 nolist
|
||||
$rd $pop blist1 blist2 1
|
||||
assert_error "WRONGTYPE*" {$rd read}
|
||||
assert_error "ERR*wrong kind*" {$rd read}
|
||||
}
|
||||
|
||||
test "$pop: timeout" {
|
||||
@@ -615,7 +615,7 @@ start_server {
|
||||
test {LLEN against non-list value error} {
|
||||
r del mylist
|
||||
r set mylist foobar
|
||||
assert_error WRONGTYPE* {r llen mylist}
|
||||
assert_error ERR* {r llen mylist}
|
||||
}
|
||||
|
||||
test {LLEN against non existing key} {
|
||||
@@ -623,7 +623,7 @@ start_server {
|
||||
}
|
||||
|
||||
test {LINDEX against non-list value error} {
|
||||
assert_error WRONGTYPE* {r lindex mylist 0}
|
||||
assert_error ERR* {r lindex mylist 0}
|
||||
}
|
||||
|
||||
test {LINDEX against non existing key} {
|
||||
@@ -631,11 +631,11 @@ start_server {
|
||||
}
|
||||
|
||||
test {LPUSH against non-list value error} {
|
||||
assert_error WRONGTYPE* {r lpush mylist 0}
|
||||
assert_error ERR* {r lpush mylist 0}
|
||||
}
|
||||
|
||||
test {RPUSH against non-list value error} {
|
||||
assert_error WRONGTYPE* {r rpush mylist 0}
|
||||
assert_error ERR* {r rpush mylist 0}
|
||||
}
|
||||
|
||||
foreach {type large} [array get largevalue] {
|
||||
@@ -684,7 +684,7 @@ start_server {
|
||||
test {RPOPLPUSH against non list src key} {
|
||||
r del srclist dstlist
|
||||
r set srclist x
|
||||
assert_error WRONGTYPE* {r rpoplpush srclist dstlist}
|
||||
assert_error ERR* {r rpoplpush srclist dstlist}
|
||||
assert_type string srclist
|
||||
assert_equal 0 [r exists newlist]
|
||||
}
|
||||
@@ -692,7 +692,7 @@ start_server {
|
||||
test {RPOPLPUSH against non list dst key} {
|
||||
create_ziplist srclist {a b c d}
|
||||
r set dstlist x
|
||||
assert_error WRONGTYPE* {r rpoplpush srclist dstlist}
|
||||
assert_error ERR* {r rpoplpush srclist dstlist}
|
||||
assert_type string dstlist
|
||||
assert_equal {a b c d} [r lrange srclist 0 -1]
|
||||
}
|
||||
@@ -718,8 +718,8 @@ start_server {
|
||||
|
||||
test {LPOP/RPOP against non list value} {
|
||||
r set notalist foo
|
||||
assert_error WRONGTYPE* {r lpop notalist}
|
||||
assert_error WRONGTYPE* {r rpop notalist}
|
||||
assert_error ERR*kind* {r lpop notalist}
|
||||
assert_error ERR*kind* {r rpop notalist}
|
||||
}
|
||||
|
||||
foreach {type num} {ziplist 250 linkedlist 500} {
|
||||
@@ -819,7 +819,7 @@ start_server {
|
||||
|
||||
test {LSET against non list value} {
|
||||
r set nolist foobar
|
||||
assert_error WRONGTYPE* {r lset nolist 0 foo}
|
||||
assert_error ERR*value* {r lset nolist 0 foo}
|
||||
}
|
||||
|
||||
foreach {type e} [array get largevalue] {
|
||||
|
||||
+8
-35
@@ -35,7 +35,7 @@ start_server {
|
||||
|
||||
test {SADD against non set} {
|
||||
r lpush mylist foo
|
||||
assert_error WRONGTYPE* {r sadd mylist bar}
|
||||
assert_error ERR*kind* {r sadd mylist bar}
|
||||
}
|
||||
|
||||
test "SADD a non-integer against an intset" {
|
||||
@@ -199,10 +199,9 @@ start_server {
|
||||
|
||||
test "SDIFFSTORE with three sets - $type" {
|
||||
r sdiffstore setres set1 set4 set5
|
||||
# When we start with intsets, we should always end with intsets.
|
||||
if {$type eq {intset}} {
|
||||
assert_encoding intset setres
|
||||
}
|
||||
# The type is determined by type of the first key to diff against.
|
||||
# See the implementation for more information.
|
||||
assert_encoding $type setres
|
||||
assert_equal {1 2 3 4} [lsort [r smembers setres]]
|
||||
}
|
||||
}
|
||||
@@ -214,40 +213,14 @@ start_server {
|
||||
r sdiff set1 set2 set3
|
||||
} {}
|
||||
|
||||
test "SDIFF fuzzing" {
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
unset -nocomplain s
|
||||
array set s {}
|
||||
set args {}
|
||||
set num_sets [expr {[randomInt 10]+1}]
|
||||
for {set i 0} {$i < $num_sets} {incr i} {
|
||||
set num_elements [randomInt 100]
|
||||
r del set_$i
|
||||
lappend args set_$i
|
||||
while {$num_elements} {
|
||||
set ele [randomValue]
|
||||
r sadd set_$i $ele
|
||||
if {$i == 0} {
|
||||
set s($ele) x
|
||||
} else {
|
||||
unset -nocomplain s($ele)
|
||||
}
|
||||
incr num_elements -1
|
||||
}
|
||||
}
|
||||
set result [lsort [r sdiff {*}$args]]
|
||||
assert_equal $result [lsort [array names s]]
|
||||
}
|
||||
}
|
||||
|
||||
test "SINTER against non-set should throw error" {
|
||||
r set key1 x
|
||||
assert_error "WRONGTYPE*" {r sinter key1 noset}
|
||||
assert_error "ERR*wrong kind*" {r sinter key1 noset}
|
||||
}
|
||||
|
||||
test "SUNION against non-set should throw error" {
|
||||
r set key1 x
|
||||
assert_error "WRONGTYPE*" {r sunion key1 noset}
|
||||
assert_error "ERR*wrong kind*" {r sunion key1 noset}
|
||||
}
|
||||
|
||||
test "SINTER should handle non existing key as empty" {
|
||||
@@ -472,12 +445,12 @@ start_server {
|
||||
|
||||
test "SMOVE wrong src key type" {
|
||||
r set x 10
|
||||
assert_error "WRONGTYPE*" {r smove x myset2 foo}
|
||||
assert_error "ERR*wrong kind*" {r smove x myset2 foo}
|
||||
}
|
||||
|
||||
test "SMOVE wrong dst key type" {
|
||||
r set x 10
|
||||
assert_error "WRONGTYPE*" {r smove myset2 x foo}
|
||||
assert_error "ERR*wrong kind*" {r smove myset2 x foo}
|
||||
}
|
||||
|
||||
test "SMOVE with identical source and destination" {
|
||||
|
||||
Reference in New Issue
Block a user