Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
508d3c1bb7 | ||
|
|
df0c96002d | ||
|
|
d7fa6d9aba | ||
|
|
c8c1006cf4 | ||
|
|
81f614ef6f | ||
|
|
20c506f432 | ||
|
|
af967ff9cb | ||
|
|
2d55f1ca7f | ||
|
|
9a0f27a5d0 | ||
|
|
c713b1ebdd | ||
|
|
56ce068664 | ||
|
|
23acf93f97 | ||
|
|
0ea9a20d47 | ||
|
|
752b1fca90 | ||
|
|
3e62d7c308 | ||
|
|
a57294b339 | ||
|
|
facc5c0661 | ||
|
|
79a1d335c8 | ||
|
|
0f32c37fc9 | ||
|
|
df21771c69 | ||
|
|
f05b1f6889 | ||
|
|
9c3e6c3aa0 | ||
|
|
9df63fcabf | ||
|
|
f5903846bc | ||
|
|
bd6327855b | ||
|
|
2c94d80f58 | ||
|
|
778f753deb | ||
|
|
6c2b34a465 | ||
|
|
0a4656d63f | ||
|
|
7c861340b1 | ||
|
|
2c018b14fb | ||
|
|
6a88a84ec7 | ||
|
|
5621272575 | ||
|
|
009515f914 | ||
|
|
837817b2b7 | ||
|
|
bd353d9a3c | ||
|
|
c7da5fc6c1 | ||
|
|
8368550733 | ||
|
|
3ad87c652b | ||
|
|
05379f49ee | ||
|
|
706056ca16 | ||
|
|
14a1cba3e3 | ||
|
|
004f00bfa4 | ||
|
|
45d4e06e93 | ||
|
|
3eab283b65 | ||
|
|
0e72cc601f | ||
|
|
689829f2e1 | ||
|
|
4fe67cc103 | ||
|
|
a256b83448 | ||
|
|
a79862de2a | ||
|
|
bcc7965931 | ||
|
|
46c1bafeab | ||
|
|
151ad68540 | ||
|
|
fa48b1fa32 | ||
|
|
31d0f34100 | ||
|
|
00c8cfef74 | ||
|
|
8686a70303 | ||
|
|
d54e373b84 | ||
|
|
500155b91b | ||
|
|
cc94697284 | ||
|
|
f8e43aba78 | ||
|
|
13f7ade551 | ||
|
|
78644f5e3e | ||
|
|
aea8020554 | ||
|
|
c3e1724a57 | ||
|
|
f899ab55ca | ||
|
|
a1d37ba469 | ||
|
|
57c8e026b9 | ||
|
|
f3b44f291d | ||
|
|
5d0ad2f983 | ||
|
|
349f6349be | ||
|
|
5b19e9d433 | ||
|
|
3cb3714e99 | ||
|
|
a50635bb6c | ||
|
|
2e449d42e0 | ||
|
|
9f6f436a51 | ||
|
|
112e763618 | ||
|
|
fe04710908 | ||
|
|
98757b4a6e | ||
|
|
18fabeb264 | ||
|
|
d8fcbb6645 | ||
|
|
1bcbb7a90c | ||
|
|
1e23848ed3 | ||
|
|
076f6395b9 | ||
|
|
4cfd70e903 | ||
|
|
98d0abcecd | ||
|
|
8669e70921 | ||
|
|
9e089e7c5d | ||
|
|
cdf801d92e | ||
|
|
3472d045d8 | ||
|
|
da18366609 | ||
|
|
4fa68b2815 | ||
|
|
a7451c1b6d | ||
|
|
2e75d3947c | ||
|
|
b5423b099c | ||
|
|
1e7949227c | ||
|
|
0d8f254359 | ||
|
|
5938126c98 | ||
|
|
d3bf85423a | ||
|
|
47620a3186 | ||
|
|
fd8a6ae7a6 | ||
|
|
f638236902 | ||
|
|
71795d4e7e | ||
|
|
d37d006cd2 | ||
|
|
57e2b23dfb | ||
|
|
39d57c057f | ||
|
|
f1151f869e | ||
|
|
fefa14914e | ||
|
|
77ddec8582 | ||
|
|
4b2e374e4a | ||
|
|
3570411def | ||
|
|
68d72aa5b1 | ||
|
|
30836721f2 | ||
|
|
920ab4c926 | ||
|
|
aee5ee70fa | ||
|
|
e50f2c93bc | ||
|
|
d36dc1fc82 | ||
|
|
855ff8df9f | ||
|
|
33c5f0a243 | ||
|
|
323e5e2058 | ||
|
|
fc022ca300 | ||
|
|
00063bea33 | ||
|
|
6dabd34ad0 | ||
|
|
14857decfa | ||
|
|
2150cfebb7 | ||
|
|
3e3501262b | ||
|
|
24b3799238 | ||
|
|
2f6560f351 | ||
|
|
847a40805f | ||
|
|
aaf5623535 | ||
|
|
25a3ad7f74 | ||
|
|
028fdbb70b | ||
|
|
8090c37f71 | ||
|
|
75dc48f04a | ||
|
|
0a0fd37e5b | ||
|
|
7bfbc38068 | ||
|
|
bb0d0fd479 | ||
|
|
cdf79c063f | ||
|
|
7cca200603 | ||
|
|
29308077a8 | ||
|
|
9faa6b9dc3 | ||
|
|
b0e0f3c5b2 | ||
|
|
4e2b97bb89 | ||
|
|
d34c1ac208 | ||
|
|
b7233f40fb | ||
|
|
8450d6e24d | ||
|
|
545fe0c318 | ||
|
|
9d894b1b8c | ||
|
|
8328d993e1 | ||
|
|
aeab473dfb | ||
|
|
a8f1474dfd | ||
|
|
72d3fbea65 | ||
|
|
3642557ae6 | ||
|
|
30f3ae03a5 | ||
|
|
f27896a17d | ||
|
|
9a7b09ed35 | ||
|
|
90c3a14ac0 | ||
|
|
9af8125c7d | ||
|
|
be8d5fd954 | ||
|
|
86147afd08 | ||
|
|
449ec38fe4 | ||
|
|
bf8e078aa2 | ||
|
|
b3fbc84cc1 | ||
|
|
1cf839a216 | ||
|
|
e34f00842f | ||
|
|
b435817b17 | ||
|
|
054632f551 | ||
|
|
d34c623e10 | ||
|
|
fe4b62fcaf | ||
|
|
971217c7b4 | ||
|
|
f5275da6e9 | ||
|
|
9a7ea3dae9 | ||
|
|
4333e6ce20 | ||
|
|
9d1bc3efcf | ||
|
|
d0d67f8d42 | ||
|
|
fc2587ec6e | ||
|
|
75fcf5c936 | ||
|
|
c64338aa8f | ||
|
|
889d39b5ba | ||
|
|
1b87b3ef38 | ||
|
|
2d497014ea | ||
|
|
75bf91abea | ||
|
|
308940aa2c | ||
|
|
45e6a4023a | ||
|
|
a74f8fe1ad | ||
|
|
0000d5334d | ||
|
|
146f1d7d86 | ||
|
|
1e77b77de4 | ||
|
|
0387f86947 | ||
|
|
7ae29d5d9e | ||
|
|
302b0a5c7b | ||
|
|
0b398b6ffa | ||
|
|
059018c4cd | ||
|
|
414d5db85c | ||
|
|
604efb02ab | ||
|
|
7d338fd982 | ||
|
|
2173f30836 | ||
|
|
901ceab20b | ||
|
|
b4ce02988c | ||
|
|
2a29cf430a | ||
|
|
c95a60960b | ||
|
|
e8cc959300 | ||
|
|
180cfaae8e | ||
|
|
417253afc3 | ||
|
|
8feb3d261a | ||
|
|
a461376ca9 | ||
|
|
8cf0b66606 | ||
|
|
cb8433f313 | ||
|
|
f62851aea9 | ||
|
|
91ef10ef45 | ||
|
|
973f793b04 | ||
|
|
7a5d3d91a1 | ||
|
|
d8dd70c2dd | ||
|
|
41babac68d | ||
|
|
cb55ad991b | ||
|
|
c847c73e78 | ||
|
|
2865bb7f1f | ||
|
|
3d93dffd5d | ||
|
|
85671d0e3b | ||
|
|
b05d2516fe | ||
|
|
b60465eb78 | ||
|
|
a94db7908a | ||
|
|
e7bcec829c | ||
|
|
4028a777b6 | ||
|
|
70845320cc | ||
|
|
2b427a89fb | ||
|
|
d2ff5ed603 | ||
|
|
b06f13e7b7 | ||
|
|
9ca306d874 | ||
|
|
5276996cf1 | ||
|
|
e426ea5245 | ||
|
|
d0c9a2a767 | ||
|
|
a500c120ba | ||
|
|
3a865829a7 | ||
|
|
0e3baa5241 | ||
|
|
42ba5d3788 | ||
|
|
0d7b10ee6e | ||
|
|
8d81e68d18 | ||
|
|
d83d72f380 | ||
|
|
d6b0c18c51 | ||
|
|
0bc7dbda1c | ||
|
|
034d94193d | ||
|
|
2094eb36e7 | ||
|
|
31844fe138 | ||
|
|
c72459bf18 | ||
|
|
ca0b1c2436 | ||
|
|
49eef7738e | ||
|
|
0c8690d46e | ||
|
|
cf8484b723 | ||
|
|
0c447d3506 | ||
|
|
24aa61785d | ||
|
|
e6e07042a1 | ||
|
|
be832929c8 | ||
|
|
4de4080ea5 | ||
|
|
978185bf67 | ||
|
|
10d8e6a712 | ||
|
|
c6a9a20d3b | ||
|
|
61b1d6da18 | ||
|
|
7009ff98b8 | ||
|
|
a452b54849 | ||
|
|
10c6195e3f | ||
|
|
2618101ad5 | ||
|
|
82516e8168 | ||
|
|
d91dfaf6dc | ||
|
|
150f7e43ca | ||
|
|
382420ec6e | ||
|
|
6b7562e204 | ||
|
|
ec18d4bfba | ||
|
|
58708fa65a | ||
|
|
ba8367ee49 | ||
|
|
67a745515d | ||
|
|
7b6fb960cc | ||
|
|
64b91c1493 | ||
|
|
edb3c10361 | ||
|
|
b2e42a90d3 | ||
|
|
378fcaad55 | ||
|
|
8e7d962291 | ||
|
|
c5afbb6e2a | ||
|
|
7e7ee815b0 | ||
|
|
6a8288766c | ||
|
|
9afb7789b3 | ||
|
|
1a73260dce | ||
|
|
8abfe5fe8b | ||
|
|
a4bb4b29fb | ||
|
|
aa7f74c7e9 | ||
|
|
21822fd0e9 | ||
|
|
6ae8fb477a | ||
|
|
bc1b2e8f96 | ||
|
|
deccd104d8 | ||
|
|
5bcfa2ebbc | ||
|
|
00a5e86e44 | ||
|
|
f72a3406a7 | ||
|
|
68532cf01e | ||
|
|
a70e3ffb16 | ||
|
|
a6f557657c | ||
|
|
5aeb1f71a3 | ||
|
|
aadb8aaa08 | ||
|
|
df3b492045 | ||
|
|
e618eb4fb3 | ||
|
|
a74056f1e0 | ||
|
|
02cd545f7d | ||
|
|
b148513fec | ||
|
|
5e9e96999f | ||
|
|
d2a37badc2 | ||
|
|
0d8d7f106a | ||
|
|
438dfdd13d | ||
|
|
6f96ac1c1c | ||
|
|
f69b0a0db8 | ||
|
|
ac3100bc3b | ||
|
|
fedcd51185 | ||
|
|
ff56772115 | ||
|
|
964ee00de0 | ||
|
|
3b4c0d80d5 | ||
|
|
d96a66f531 | ||
|
|
31f0a6ec50 | ||
|
|
5fe2577a19 | ||
|
|
9854252798 | ||
|
|
5a8cf340a4 | ||
|
|
0ae1a6b1c3 | ||
|
|
2b1398e870 | ||
|
|
5a4ef3e5b6 | ||
|
|
4fe7672dfc | ||
|
|
8529dd218f | ||
|
|
6726ea5b53 | ||
|
|
f5edd535d1 | ||
|
|
700e5eb4fc | ||
|
|
4d8655cfd3 | ||
|
|
01c21f9943 | ||
|
|
5a35e485f9 | ||
|
|
c970816e4d | ||
|
|
8a22934c68 | ||
|
|
b06b90b5d8 | ||
|
|
0a14a6542c | ||
|
|
2ed3fc1502 | ||
|
|
f2817cbd9e | ||
|
|
fcfdbda104 | ||
|
|
0d470b4c43 | ||
|
|
cc55a4525a | ||
|
|
5eb0ec142d | ||
|
|
5f7dff4d16 | ||
|
|
1d80acae54 | ||
|
|
14daa01c52 | ||
|
|
d2a8bca82b | ||
|
|
1124cda37a | ||
|
|
e92edd3cee | ||
|
|
2543fa6476 | ||
|
|
44d0eac568 | ||
|
|
57e411c687 | ||
|
|
8024de7eb0 | ||
|
|
ca8e7d4f8a | ||
|
|
664de412af | ||
|
|
d3cf778d82 | ||
|
|
9db8fbcfdd | ||
|
|
b9bc4f9132 | ||
|
|
6de052cd77 | ||
|
|
81af92b023 | ||
|
|
4ff138fbbe | ||
|
|
2825f21fd8 | ||
|
|
a64383c861 | ||
|
|
fdfb59beae | ||
|
|
dd2ce743ca | ||
|
|
6e64525cc2 | ||
|
|
212edbc409 | ||
|
|
153766e137 | ||
|
|
767a53aa84 | ||
|
|
3ff75e58e8 | ||
|
|
850117a8a5 | ||
|
|
2f1318ab29 | ||
|
|
f6f43d7da0 | ||
|
|
635c532c89 | ||
|
|
39f0a33f78 | ||
|
|
1e20c939fe | ||
|
|
d766907cfb | ||
|
|
1caf09399e | ||
|
|
ff1e4d22a6 | ||
|
|
a8f9cec16a | ||
|
|
aadcda9977 | ||
|
|
f2bc198d40 | ||
|
|
c48584b85d | ||
|
|
7897e0a0d6 | ||
|
|
f31d10b9a6 | ||
|
|
786bd3938e | ||
|
|
aa9497fdb3 | ||
|
|
64ee9c861f | ||
|
|
8a70007f9e | ||
|
|
e2e36cf19b | ||
|
|
414e06653c | ||
|
|
cefff3a1b3 | ||
|
|
6570d2f89e | ||
|
|
858b5576ac | ||
|
|
61fb0aacf5 | ||
|
|
6fa2610ac9 | ||
|
|
a6d117b6c0 | ||
|
|
5207c03c49 | ||
|
|
bbee226ecd | ||
|
|
17e31eb03a | ||
|
|
124cb6dda1 | ||
|
|
e39735214d | ||
|
|
07a9f85405 | ||
|
|
fbf3e33d18 | ||
|
|
64e69c0d5f | ||
|
|
c1eda786d6 | ||
|
|
ccc974d90b | ||
|
|
2572bb13c6 | ||
|
|
e34c14df1a | ||
|
|
3b71404d70 | ||
|
|
cd99c14e38 | ||
|
|
053f1f357d | ||
|
|
1f0d0b094a | ||
|
|
ccb500cf36 | ||
|
|
ed48550fe6 | ||
|
|
d0570c9693 | ||
|
|
cc85ed41df | ||
|
|
ca9321afe2 | ||
|
|
a527235f33 | ||
|
|
53600e34db | ||
|
|
60f9dac672 | ||
|
|
dff1ec4a36 | ||
|
|
d4b5c4803b | ||
|
|
dd905417ec | ||
|
|
c342b075df | ||
|
|
1d8af6074c | ||
|
|
1a706c729a | ||
|
|
50c41de752 | ||
|
|
388cc20418 | ||
|
|
cda5c0ba06 | ||
|
|
8ddb23b90c | ||
|
|
46c5d39660 | ||
|
|
2b28ef0e61 | ||
|
|
64be5e365a | ||
|
|
fecc8797c2 | ||
|
|
d775b35715 | ||
|
|
655e6838fb | ||
|
|
428b706308 | ||
|
|
9fab63b334 | ||
|
|
537235bbbb | ||
|
|
38272f961a | ||
|
|
6c7e957d17 | ||
|
|
6bd105032c | ||
|
|
72f42cf40c | ||
|
|
2a49aa1da2 | ||
|
|
24ee20e5ca | ||
|
|
f7b42b1749 |
+7
-2
@@ -1,8 +1,13 @@
|
||||
.*.swp
|
||||
*.o
|
||||
*.rdb
|
||||
*.log
|
||||
redis-*
|
||||
dump.rdb
|
||||
redis-benchmark
|
||||
redis-check-aof
|
||||
redis-check-dump
|
||||
redis-cli
|
||||
redis-sentinel
|
||||
redis-server
|
||||
doc-tools
|
||||
release
|
||||
misc/*
|
||||
|
||||
+93
-312
@@ -1,9 +1,9 @@
|
||||
Redis 2.6 release notes
|
||||
Redis 2.8 release notes
|
||||
=======================
|
||||
|
||||
** 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.
|
||||
** 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.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Upgrade urgency levels:
|
||||
@@ -14,345 +14,126 @@ 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.6.11 ]
|
||||
--[ Redis 2.8 Release Candidate 5 (2.7.105) ] Release date: 9 Oct 2013
|
||||
|
||||
UPGRADE URGENCY: LOW, however updating is encouraged if you have many instances
|
||||
per server and you want to lower the CPU / energy usage.
|
||||
This is the 5th release candidate of Redis 2.8 (official version is 2.7.105).
|
||||
Important bugs fixed inside.
|
||||
|
||||
* [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.
|
||||
* [IMPROVED] serverCron() frequency is now a runtime parameter (was REDIS_HZ).
|
||||
* [IMPROVED] Use a lot less CPU when idle, even with many configured DBs.
|
||||
# UPGRADE URGENCY: HIGH because of many non critical replication bugs fixed.
|
||||
|
||||
--[ Redis 2.6.10 ]
|
||||
* [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.
|
||||
|
||||
UPGRADE URGENCY: MODERATE, this release contains many non-critical fixes
|
||||
and many small improvements.
|
||||
--[ Redis 2.8 Release Candidate 4 (2.7.104) ] Release date: 30 Aug 2013
|
||||
|
||||
* [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.
|
||||
This is the fourth release candidate of Redis 2.8 (official version is 2.7.104).
|
||||
Important bugs fixed inside.
|
||||
|
||||
--[ Redis 2.6.9 ]
|
||||
# UPGRADE URGENCY: HIGH because of the EVAL memory leak.
|
||||
|
||||
UPGRADE URGENCY: MODERATE if you use replication.
|
||||
* [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.
|
||||
|
||||
* [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.
|
||||
--[ Redis 2.8 Release Candidate 3 (2.7.103) ] Release date: 19 Aug 2013
|
||||
|
||||
--[ Redis 2.6.8 ]
|
||||
This is the third release candidate of Redis 2.8 (official version is 2.7.103).
|
||||
Important bugs fixed inside.
|
||||
|
||||
UPGRADE URGENCY: MODERATE if you use Lua scripting. Otherwise LOW.
|
||||
# UPGRADE URGENCY: HIGH
|
||||
|
||||
* [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.
|
||||
* [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.
|
||||
|
||||
--[ Redis 2.6.7 ]
|
||||
--[ Redis 2.8 Release Candidate 2 (2.7.102) ] Release date: 30 Jul 2013
|
||||
|
||||
UPGRADE URGENCY: MODERATE (unless you BLPOP using the same key multiple times).
|
||||
This is the second release candidate of Redis 2.8 (official version is 2.7.102).
|
||||
Important bugs fixed inside.
|
||||
|
||||
* [BUGFIX] Don't crash if BLPOP & co are called with the same key repeated
|
||||
multiple times (Issue #801).
|
||||
# UPGRADE URGENCY: HIGH
|
||||
|
||||
--[ Redis 2.6.6 ]
|
||||
* [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".
|
||||
|
||||
UPGRADE URGENCY: CRITICAL if you experienced one more more crashes.
|
||||
MODERATE if Redis is running fine for you.
|
||||
--[ Redis 2.8 Release Candidate 1 (2.7.101) ] Release date: 18 Jul 2013
|
||||
|
||||
* [BUGFIX] Jemalloc updated to 3.2.0.
|
||||
This is the first release candidate of Redis 2.8 (official version is 2.7.101).
|
||||
|
||||
--[ Redis 2.6.5 ]
|
||||
The following is a list of improvements in Redis 2.8, compared to Redis 2.6.
|
||||
|
||||
UPGRADE URGENCY: MODERATE
|
||||
* [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.
|
||||
|
||||
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
|
||||
Migrating from 2.6 to 2.8
|
||||
=========================
|
||||
|
||||
Redis 2.4 is mostly a strict subset of 2.6. However there are a few things
|
||||
Redis 2.6 is mostly a strict subset of 2.8. However there are a few things
|
||||
that you should be aware of:
|
||||
|
||||
* 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.
|
||||
The following commands changed behavior:
|
||||
|
||||
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
|
||||
* 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.
|
||||
|
||||
The following redis.conf and CONFIG GET / SET parameters changed:
|
||||
|
||||
* 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)
|
||||
* 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.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Credits: Where not specified the implementation and design are done by
|
||||
Salvatore Sanfilippo and Pieter Noordhuis. Thanks to VMware for making all
|
||||
Credits: Where not specified the implementation and design is done by
|
||||
Salvatore Sanfilippo. Thanks to VMware and Pivotal for making all
|
||||
this possible. Also many thanks to all the other contributors and the amazing
|
||||
community we have.
|
||||
|
||||
|
||||
@@ -26,9 +26,20 @@ After building Redis is a good idea to test it, using:
|
||||
|
||||
% make test
|
||||
|
||||
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.
|
||||
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"
|
||||
|
||||
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) {
|
||||
r->buf = sdsrange(r->buf,r->pos,-1);
|
||||
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 {
|
||||
c->obuf = sdsrange(c->obuf,nwritten,-1);
|
||||
sdsrange(c->obuf,nwritten,-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+10
-2
@@ -215,9 +215,17 @@ 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) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
|
||||
return REDIS_ERR;
|
||||
hints.ai_family = AF_INET6;
|
||||
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
|
||||
__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
+386
-109
@@ -1,6 +1,6 @@
|
||||
/* SDSLib, A C dynamic strings library
|
||||
*
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* 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
|
||||
@@ -32,52 +32,76 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include "sds.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
static void sdsOomAbort(void) {
|
||||
fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n");
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 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;
|
||||
|
||||
sh = malloc(sizeof(struct sdshdr)+initlen+1);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (sh == NULL) sdsOomAbort();
|
||||
#else
|
||||
if (init) {
|
||||
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
|
||||
} else {
|
||||
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
|
||||
}
|
||||
if (sh == NULL) return NULL;
|
||||
#endif
|
||||
sh->len = initlen;
|
||||
sh->free = 0;
|
||||
if (initlen) {
|
||||
if (init) memcpy(sh->buf, init, initlen);
|
||||
else memset(sh->buf,0,initlen);
|
||||
}
|
||||
if (initlen && init)
|
||||
memcpy(sh->buf, init, 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;
|
||||
free(s-sizeof(struct sdshdr));
|
||||
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);
|
||||
@@ -85,7 +109,24 @@ void sdsupdatelen(sds s) {
|
||||
sh->len = reallen;
|
||||
}
|
||||
|
||||
static sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
/* 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) {
|
||||
struct sdshdr *sh, *newsh;
|
||||
size_t free = sdsavail(s);
|
||||
size_t len, newlen;
|
||||
@@ -93,20 +134,84 @@ static sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
if (free >= addlen) return s;
|
||||
len = sdslen(s);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
newlen = (len+addlen)*2;
|
||||
newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (newsh == NULL) sdsOomAbort();
|
||||
#else
|
||||
newlen = (len+addlen);
|
||||
if (newlen < SDS_MAX_PREALLOC)
|
||||
newlen *= 2;
|
||||
else
|
||||
newlen += SDS_MAX_PREALLOC;
|
||||
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
|
||||
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. */
|
||||
* 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. */
|
||||
sds sdsgrowzero(sds s, size_t len) {
|
||||
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
|
||||
size_t totlen, curlen = sh->len;
|
||||
@@ -124,6 +229,11 @@ 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);
|
||||
@@ -138,11 +248,25 @@ 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));
|
||||
}
|
||||
|
||||
sds sdscpylen(sds s, char *t, size_t len) {
|
||||
/* 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;
|
||||
|
||||
@@ -159,37 +283,52 @@ sds sdscpylen(sds s, char *t, size_t len) {
|
||||
return s;
|
||||
}
|
||||
|
||||
sds sdscpy(sds s, char *t) {
|
||||
/* 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;
|
||||
size_t buflen = 16;
|
||||
|
||||
while(1) {
|
||||
buf = malloc(buflen);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (buf == NULL) sdsOomAbort();
|
||||
#else
|
||||
buf = zmalloc(buflen);
|
||||
if (buf == NULL) return NULL;
|
||||
#endif
|
||||
buf[buflen-2] = '\0';
|
||||
va_copy(cpy,ap);
|
||||
vsnprintf(buf, buflen, fmt, cpy);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
free(buf);
|
||||
zfree(buf);
|
||||
buflen *= 2;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
t = sdscat(s, buf);
|
||||
free(buf);
|
||||
zfree(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;
|
||||
@@ -199,6 +338,20 @@ 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;
|
||||
@@ -216,11 +369,27 @@ sds sdstrim(sds s, const char *cset) {
|
||||
return s;
|
||||
}
|
||||
|
||||
sds sdsrange(sds s, int start, int end) {
|
||||
/* 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) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t newlen, len = sdslen(s);
|
||||
|
||||
if (len == 0) return s;
|
||||
if (len == 0) return;
|
||||
if (start < 0) {
|
||||
start = len+start;
|
||||
if (start < 0) start = 0;
|
||||
@@ -244,22 +413,34 @@ sds 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]);
|
||||
}
|
||||
|
||||
int sdscmp(sds s1, sds s2) {
|
||||
/* 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;
|
||||
|
||||
@@ -287,14 +468,15 @@ int sdscmp(sds s1, sds s2) {
|
||||
* requires length arguments. sdssplit() is just the
|
||||
* same function but for zero-terminated strings.
|
||||
*/
|
||||
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
|
||||
sds *sdssplitlen(const char *s, int len, const 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;
|
||||
@@ -305,26 +487,14 @@ sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
|
||||
sds *newtokens;
|
||||
|
||||
slots *= 2;
|
||||
newtokens = realloc(tokens,sizeof(sds)*slots);
|
||||
if (newtokens == NULL) {
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
sdsOomAbort();
|
||||
#else
|
||||
goto cleanup;
|
||||
#endif
|
||||
}
|
||||
newtokens = zrealloc(tokens,sizeof(sds)*slots);
|
||||
if (newtokens == NULL) goto cleanup;
|
||||
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) {
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
sdsOomAbort();
|
||||
#else
|
||||
goto cleanup;
|
||||
#endif
|
||||
}
|
||||
if (tokens[elements] == NULL) goto cleanup;
|
||||
elements++;
|
||||
start = j+seplen;
|
||||
j = j+seplen-1; /* skip the separator */
|
||||
@@ -332,35 +502,33 @@ sds *sdssplitlen(char *s, int len, 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) {
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
sdsOomAbort();
|
||||
#else
|
||||
goto cleanup;
|
||||
#endif
|
||||
}
|
||||
if (tokens[elements] == NULL) goto cleanup;
|
||||
elements++;
|
||||
*count = elements;
|
||||
return tokens;
|
||||
|
||||
#ifndef SDS_ABORT_ON_OOM
|
||||
cleanup:
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < elements; i++) sdsfree(tokens[i]);
|
||||
free(tokens);
|
||||
zfree(tokens);
|
||||
*count = 0;
|
||||
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]);
|
||||
free(tokens);
|
||||
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;
|
||||
@@ -376,10 +544,14 @@ sds sdsfromlonglong(long long value) {
|
||||
return sdsnewlen(p,32-(p-buf));
|
||||
}
|
||||
|
||||
sds sdscatrepr(sds s, char *p, size_t len) {
|
||||
/* 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);
|
||||
if (s == NULL) return NULL;
|
||||
|
||||
while(len--) {
|
||||
switch(*p) {
|
||||
case '\\':
|
||||
@@ -399,27 +571,64 @@ sds sdscatrepr(sds s, 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 sdsfree() all the returned
|
||||
* strings and finally free() the array itself.
|
||||
* of sds is returned.
|
||||
*
|
||||
* The caller should free the resulting array of sds strings with
|
||||
* sdsfreesplitres().
|
||||
*
|
||||
* 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(char *line, int *argc) {
|
||||
char *p = line;
|
||||
sds *sdssplitargs(const char *line, int *argc) {
|
||||
const char *p = line;
|
||||
char *current = NULL;
|
||||
char **vector = NULL, **_vector = NULL;
|
||||
char **vector = NULL;
|
||||
|
||||
*argc = 0;
|
||||
while(1) {
|
||||
@@ -427,17 +636,24 @@ sds *sdssplitargs(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 inq=0; /* set to 1 if we are in "quotes" */
|
||||
int insq=0; /* set to 1 if we are in 'single quotes' */
|
||||
int done=0;
|
||||
|
||||
if (current == NULL) {
|
||||
current = sdsempty();
|
||||
if (current == NULL) goto err;
|
||||
}
|
||||
|
||||
if (current == NULL) current = sdsempty();
|
||||
while(!done) {
|
||||
if (inq) {
|
||||
if (*p == '\\' && *(p+1)) {
|
||||
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)) {
|
||||
char c;
|
||||
|
||||
p++;
|
||||
@@ -451,7 +667,23 @@ sds *sdssplitargs(char *line, int *argc) {
|
||||
}
|
||||
current = sdscatlen(current,&c,1);
|
||||
} else if (*p == '"') {
|
||||
/* closing quote must be followed by a space */
|
||||
/* 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. */
|
||||
if (*(p+1) && !isspace(*(p+1))) goto err;
|
||||
done=1;
|
||||
} else if (!*p) {
|
||||
@@ -472,23 +704,24 @@ sds *sdssplitargs(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 = realloc(vector,((*argc)+1)*sizeof(char*));
|
||||
if (_vector == NULL) goto err;
|
||||
|
||||
vector = _vector;
|
||||
vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -496,30 +729,55 @@ sds *sdssplitargs(char *line, int *argc) {
|
||||
err:
|
||||
while((*argc)--)
|
||||
sdsfree(vector[*argc]);
|
||||
if (vector != NULL) free(vector);
|
||||
if (current != NULL) sdsfree(current);
|
||||
zfree(vector);
|
||||
if (current) sdsfree(current);
|
||||
*argc = 0;
|
||||
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>
|
||||
|
||||
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);
|
||||
#include "testhelp.h"
|
||||
|
||||
int main(void) {
|
||||
{
|
||||
struct sdshdr *sh;
|
||||
sds x = sdsnew("foo"), y;
|
||||
|
||||
test_cond("Create a string and obtain the length",
|
||||
@@ -599,7 +857,26 @@ 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
+20
-8
@@ -31,6 +31,8 @@
|
||||
#ifndef __SDS_H
|
||||
#define __SDS_H
|
||||
|
||||
#define SDS_MAX_PREALLOC (1024*1024)
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
@@ -58,12 +60,13 @@ sds sdsempty(void);
|
||||
size_t sdslen(const sds s);
|
||||
sds sdsdup(const sds s);
|
||||
void sdsfree(sds s);
|
||||
size_t sdsavail(sds s);
|
||||
size_t sdsavail(const 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 sdscpylen(sds s, char *t, size_t len);
|
||||
sds sdscpy(sds s, 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 sdscatvprintf(sds s, const char *fmt, va_list ap);
|
||||
#ifdef __GNUC__
|
||||
@@ -74,15 +77,24 @@ sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
sds sdsrange(sds s, int start, int end);
|
||||
void sdsrange(sds s, int start, int end);
|
||||
void sdsupdatelen(sds s);
|
||||
int sdscmp(sds s1, sds s2);
|
||||
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count);
|
||||
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);
|
||||
void sdsfreesplitres(sds *tokens, int count);
|
||||
void sdstolower(sds s);
|
||||
void sdstoupper(sds s);
|
||||
sds sdsfromlonglong(long long value);
|
||||
sds sdscatrepr(sds s, char *p, size_t len);
|
||||
sds *sdssplitargs(char *line, int *argc);
|
||||
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);
|
||||
|
||||
#endif
|
||||
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
/* 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
|
||||
@@ -2,24 +2,19 @@
|
||||
/config.stamp
|
||||
/config.log
|
||||
/config.status
|
||||
/configure
|
||||
/doc/html.xsl
|
||||
/doc/manpages.xsl
|
||||
/doc/jemalloc.xml
|
||||
/doc/jemalloc.html
|
||||
/doc/jemalloc.3
|
||||
/lib/
|
||||
/Makefile
|
||||
/include/jemalloc/internal/jemalloc_internal\.h
|
||||
/include/jemalloc/internal/size_classes\.h
|
||||
/include/jemalloc/jemalloc\.h
|
||||
/include/jemalloc/jemalloc_defs\.h
|
||||
/test/jemalloc_test\.h
|
||||
/src/*.[od]
|
||||
/test/*.[od]
|
||||
/test/*.out
|
||||
/test/[a-zA-Z_]*
|
||||
!test/*.c
|
||||
!test/*.exp
|
||||
/VERSION
|
||||
/bin/jemalloc.sh
|
||||
|
||||
Vendored
+1
-1
@@ -9,7 +9,7 @@ For details and rationale, see http://www.lua.org/license.html .
|
||||
|
||||
===============================================================================
|
||||
|
||||
Copyright (C) 1994-2008 Lua.org, PUC-Rio.
|
||||
Copyright (C) 1994-2012 Lua.org, PUC-Rio.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
Vendored
+1
-1
@@ -48,7 +48,7 @@ TO_MAN= lua.1 luac.1
|
||||
|
||||
# Lua version and release.
|
||||
V= 5.1
|
||||
R= 5.1.4
|
||||
R= 5.1.5
|
||||
|
||||
all: $(PLAT)
|
||||
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 797 B |
Vendored
+79
-81
@@ -3,11 +3,10 @@
|
||||
<HEAD>
|
||||
<TITLE>Lua 5.1 Reference Manual - contents</TITLE>
|
||||
<LINK REL="stylesheet" TYPE="text/css" HREF="lua.css">
|
||||
<META HTTP-EQUIV="content-type" CONTENT="text/html; charset=iso-8859-1">
|
||||
<META HTTP-EQUIV="content-type" CONTENT="text/html; charset=utf-8">
|
||||
<STYLE TYPE="text/css">
|
||||
ul {
|
||||
list-style-type: none ;
|
||||
list-style-position: outside ;
|
||||
}
|
||||
</STYLE>
|
||||
</HEAD>
|
||||
@@ -20,7 +19,13 @@ ul {
|
||||
Lua 5.1 Reference Manual
|
||||
</H1>
|
||||
|
||||
This is an online version of
|
||||
<P>
|
||||
The reference manual is the official definition of the Lua language.
|
||||
For a complete introduction to Lua programming, see the book
|
||||
<A HREF="http://www.lua.org/docs.html#pil">Programming in Lua</A>.
|
||||
|
||||
<P>
|
||||
This manual is also available as a book:
|
||||
<BLOCKQUOTE>
|
||||
<A HREF="http://www.amazon.com/exec/obidos/ASIN/8590379833/lua-indexmanual-20">
|
||||
<IMG SRC="cover.png" ALT="" TITLE="buy from Amazon" BORDER=1 ALIGN="left" HSPACE=12>
|
||||
@@ -29,128 +34,119 @@ This is an online version of
|
||||
<BR>by R. Ierusalimschy, L. H. de Figueiredo, W. Celes
|
||||
<BR>Lua.org, August 2006
|
||||
<BR>ISBN 85-903798-3-3
|
||||
<BR><A HREF="http://www.amazon.com/exec/obidos/ASIN/8590379833/lua-indexmanual-20">
|
||||
<IMG SRC="amazon.gif" ALT="[Buy from Amazon]" BORDER=0></A>
|
||||
<BR CLEAR="all">
|
||||
</BLOCKQUOTE>
|
||||
<P>
|
||||
|
||||
Buy a copy of this book and
|
||||
<P>
|
||||
<A HREF="http://www.amazon.com/exec/obidos/ASIN/8590379833/lua-indexmanual-20">Buy a copy</A>
|
||||
of this book and
|
||||
<A HREF="http://www.lua.org/donations.html">help to support</A>
|
||||
the Lua project.
|
||||
<P>
|
||||
|
||||
The reference manual is the official definition of the Lua language.
|
||||
For a complete introduction to Lua programming, see the book
|
||||
<A HREF="http://www.lua.org/docs.html#books">Programming in Lua</A>.
|
||||
<P>
|
||||
|
||||
<A HREF="manual.html">start</A>
|
||||
·
|
||||
<A HREF="#contents">contents</A>
|
||||
·
|
||||
<A HREF="#index">index</A>
|
||||
·
|
||||
<A HREF="http://www.lua.org/manual/5.1/pt/">português</A>
|
||||
·
|
||||
<A HREF="http://www.lua.org/manual/5.1/es/">español</A>
|
||||
<A HREF="http://www.lua.org/manual/">other versions</A>
|
||||
<HR>
|
||||
<SMALL>
|
||||
Copyright © 2006-2008 Lua.org, PUC-Rio.
|
||||
Copyright © 2006–2012 Lua.org, PUC-Rio.
|
||||
Freely available under the terms of the
|
||||
<a href="http://www.lua.org/license.html#5">Lua license</a>.
|
||||
<A HREF="http://www.lua.org/license.html">Lua license</A>.
|
||||
</SMALL>
|
||||
<P>
|
||||
|
||||
<H2><A NAME="contents">Contents</A></H2>
|
||||
<UL style="padding: 0">
|
||||
<LI><A HREF="manual.html">1 - Introduction</A>
|
||||
<LI><A HREF="manual.html">1 – Introduction</A>
|
||||
<P>
|
||||
<LI><A HREF="manual.html#2">2 - The Language</A>
|
||||
<LI><A HREF="manual.html#2">2 – The Language</A>
|
||||
<UL>
|
||||
<LI><A HREF="manual.html#2.1">2.1 - Lexical Conventions</A>
|
||||
<LI><A HREF="manual.html#2.2">2.2 - Values and Types</A>
|
||||
<LI><A HREF="manual.html#2.1">2.1 – Lexical Conventions</A>
|
||||
<LI><A HREF="manual.html#2.2">2.2 – Values and Types</A>
|
||||
<UL>
|
||||
<LI><A HREF="manual.html#2.2.1">2.2.1 - Coercion</A>
|
||||
<LI><A HREF="manual.html#2.2.1">2.2.1 – Coercion</A>
|
||||
</UL>
|
||||
<LI><A HREF="manual.html#2.3">2.3 - Variables</A>
|
||||
<LI><A HREF="manual.html#2.4">2.4 - Statements</A>
|
||||
<LI><A HREF="manual.html#2.3">2.3 – Variables</A>
|
||||
<LI><A HREF="manual.html#2.4">2.4 – Statements</A>
|
||||
<UL>
|
||||
<LI><A HREF="manual.html#2.4.1">2.4.1 - Chunks</A>
|
||||
<LI><A HREF="manual.html#2.4.2">2.4.2 - Blocks</A>
|
||||
<LI><A HREF="manual.html#2.4.3">2.4.3 - Assignment</A>
|
||||
<LI><A HREF="manual.html#2.4.4">2.4.4 - Control Structures</A>
|
||||
<LI><A HREF="manual.html#2.4.5">2.4.5 - For Statement</A>
|
||||
<LI><A HREF="manual.html#2.4.6">2.4.6 - Function Calls as Statements</A>
|
||||
<LI><A HREF="manual.html#2.4.7">2.4.7 - Local Declarations</A>
|
||||
<LI><A HREF="manual.html#2.4.1">2.4.1 – Chunks</A>
|
||||
<LI><A HREF="manual.html#2.4.2">2.4.2 – Blocks</A>
|
||||
<LI><A HREF="manual.html#2.4.3">2.4.3 – Assignment</A>
|
||||
<LI><A HREF="manual.html#2.4.4">2.4.4 – Control Structures</A>
|
||||
<LI><A HREF="manual.html#2.4.5">2.4.5 – For Statement</A>
|
||||
<LI><A HREF="manual.html#2.4.6">2.4.6 – Function Calls as Statements</A>
|
||||
<LI><A HREF="manual.html#2.4.7">2.4.7 – Local Declarations</A>
|
||||
</UL>
|
||||
<LI><A HREF="manual.html#2.5">2.5 - Expressions</A>
|
||||
<LI><A HREF="manual.html#2.5">2.5 – Expressions</A>
|
||||
<UL>
|
||||
<LI><A HREF="manual.html#2.5.1">2.5.1 - Arithmetic Operators</A>
|
||||
<LI><A HREF="manual.html#2.5.2">2.5.2 - Relational Operators</A>
|
||||
<LI><A HREF="manual.html#2.5.3">2.5.3 - Logical Operators</A>
|
||||
<LI><A HREF="manual.html#2.5.4">2.5.4 - Concatenation</A>
|
||||
<LI><A HREF="manual.html#2.5.5">2.5.5 - The Length Operator</A>
|
||||
<LI><A HREF="manual.html#2.5.6">2.5.6 - Precedence</A>
|
||||
<LI><A HREF="manual.html#2.5.7">2.5.7 - Table Constructors</A>
|
||||
<LI><A HREF="manual.html#2.5.8">2.5.8 - Function Calls</A>
|
||||
<LI><A HREF="manual.html#2.5.9">2.5.9 - Function Definitions</A>
|
||||
<LI><A HREF="manual.html#2.5.1">2.5.1 – Arithmetic Operators</A>
|
||||
<LI><A HREF="manual.html#2.5.2">2.5.2 – Relational Operators</A>
|
||||
<LI><A HREF="manual.html#2.5.3">2.5.3 – Logical Operators</A>
|
||||
<LI><A HREF="manual.html#2.5.4">2.5.4 – Concatenation</A>
|
||||
<LI><A HREF="manual.html#2.5.5">2.5.5 – The Length Operator</A>
|
||||
<LI><A HREF="manual.html#2.5.6">2.5.6 – Precedence</A>
|
||||
<LI><A HREF="manual.html#2.5.7">2.5.7 – Table Constructors</A>
|
||||
<LI><A HREF="manual.html#2.5.8">2.5.8 – Function Calls</A>
|
||||
<LI><A HREF="manual.html#2.5.9">2.5.9 – Function Definitions</A>
|
||||
</UL>
|
||||
<LI><A HREF="manual.html#2.6">2.6 - Visibility Rules</A>
|
||||
<LI><A HREF="manual.html#2.7">2.7 - Error Handling</A>
|
||||
<LI><A HREF="manual.html#2.8">2.8 - Metatables</A>
|
||||
<LI><A HREF="manual.html#2.9">2.9 - Environments</A>
|
||||
<LI><A HREF="manual.html#2.10">2.10 - Garbage Collection</A>
|
||||
<LI><A HREF="manual.html#2.6">2.6 – Visibility Rules</A>
|
||||
<LI><A HREF="manual.html#2.7">2.7 – Error Handling</A>
|
||||
<LI><A HREF="manual.html#2.8">2.8 – Metatables</A>
|
||||
<LI><A HREF="manual.html#2.9">2.9 – Environments</A>
|
||||
<LI><A HREF="manual.html#2.10">2.10 – Garbage Collection</A>
|
||||
<UL>
|
||||
<LI><A HREF="manual.html#2.10.1">2.10.1 - Garbage-Collection Metamethods</A>
|
||||
<LI><A HREF="manual.html#2.10.2">2.10.2 - Weak Tables</A>
|
||||
<LI><A HREF="manual.html#2.10.1">2.10.1 – Garbage-Collection Metamethods</A>
|
||||
<LI><A HREF="manual.html#2.10.2">2.10.2 – Weak Tables</A>
|
||||
</UL>
|
||||
<LI><A HREF="manual.html#2.11">2.11 - Coroutines</A>
|
||||
<LI><A HREF="manual.html#2.11">2.11 – Coroutines</A>
|
||||
</UL>
|
||||
<P>
|
||||
<LI><A HREF="manual.html#3">3 - The Application Program Interface</A>
|
||||
<LI><A HREF="manual.html#3">3 – The Application Program Interface</A>
|
||||
<UL>
|
||||
<LI><A HREF="manual.html#3.1">3.1 - The Stack</A>
|
||||
<LI><A HREF="manual.html#3.2">3.2 - Stack Size</A>
|
||||
<LI><A HREF="manual.html#3.3">3.3 - Pseudo-Indices</A>
|
||||
<LI><A HREF="manual.html#3.4">3.4 - C Closures</A>
|
||||
<LI><A HREF="manual.html#3.5">3.5 - Registry</A>
|
||||
<LI><A HREF="manual.html#3.6">3.6 - Error Handling in C</A>
|
||||
<LI><A HREF="manual.html#3.7">3.7 - Functions and Types</A>
|
||||
<LI><A HREF="manual.html#3.8">3.8 - The Debug Interface</A>
|
||||
<LI><A HREF="manual.html#3.1">3.1 – The Stack</A>
|
||||
<LI><A HREF="manual.html#3.2">3.2 – Stack Size</A>
|
||||
<LI><A HREF="manual.html#3.3">3.3 – Pseudo-Indices</A>
|
||||
<LI><A HREF="manual.html#3.4">3.4 – C Closures</A>
|
||||
<LI><A HREF="manual.html#3.5">3.5 – Registry</A>
|
||||
<LI><A HREF="manual.html#3.6">3.6 – Error Handling in C</A>
|
||||
<LI><A HREF="manual.html#3.7">3.7 – Functions and Types</A>
|
||||
<LI><A HREF="manual.html#3.8">3.8 – The Debug Interface</A>
|
||||
</UL>
|
||||
<P>
|
||||
<LI><A HREF="manual.html#4">4 - The Auxiliary Library</A>
|
||||
<LI><A HREF="manual.html#4">4 – The Auxiliary Library</A>
|
||||
<UL>
|
||||
<LI><A HREF="manual.html#4.1">4.1 - Functions and Types</A>
|
||||
<LI><A HREF="manual.html#4.1">4.1 – Functions and Types</A>
|
||||
</UL>
|
||||
<P>
|
||||
<LI><A HREF="manual.html#5">5 - Standard Libraries</A>
|
||||
<LI><A HREF="manual.html#5">5 – Standard Libraries</A>
|
||||
<UL>
|
||||
<LI><A HREF="manual.html#5.1">5.1 - Basic Functions</A>
|
||||
<LI><A HREF="manual.html#5.2">5.2 - Coroutine Manipulation</A>
|
||||
<LI><A HREF="manual.html#5.3">5.3 - Modules</A>
|
||||
<LI><A HREF="manual.html#5.4">5.4 - String Manipulation</A>
|
||||
<LI><A HREF="manual.html#5.1">5.1 – Basic Functions</A>
|
||||
<LI><A HREF="manual.html#5.2">5.2 – Coroutine Manipulation</A>
|
||||
<LI><A HREF="manual.html#5.3">5.3 – Modules</A>
|
||||
<LI><A HREF="manual.html#5.4">5.4 – String Manipulation</A>
|
||||
<UL>
|
||||
<LI><A HREF="manual.html#5.4.1">5.4.1 - Patterns</A>
|
||||
<LI><A HREF="manual.html#5.4.1">5.4.1 – Patterns</A>
|
||||
</UL>
|
||||
<LI><A HREF="manual.html#5.5">5.5 - Table Manipulation</A>
|
||||
<LI><A HREF="manual.html#5.6">5.6 - Mathematical Functions</A>
|
||||
<LI><A HREF="manual.html#5.7">5.7 - Input and Output Facilities</A>
|
||||
<LI><A HREF="manual.html#5.8">5.8 - Operating System Facilities</A>
|
||||
<LI><A HREF="manual.html#5.9">5.9 - The Debug Library</A>
|
||||
<LI><A HREF="manual.html#5.5">5.5 – Table Manipulation</A>
|
||||
<LI><A HREF="manual.html#5.6">5.6 – Mathematical Functions</A>
|
||||
<LI><A HREF="manual.html#5.7">5.7 – Input and Output Facilities</A>
|
||||
<LI><A HREF="manual.html#5.8">5.8 – Operating System Facilities</A>
|
||||
<LI><A HREF="manual.html#5.9">5.9 – The Debug Library</A>
|
||||
</UL>
|
||||
<P>
|
||||
<LI><A HREF="manual.html#6">6 - Lua Stand-alone</A>
|
||||
<LI><A HREF="manual.html#6">6 – Lua Stand-alone</A>
|
||||
<P>
|
||||
<LI><A HREF="manual.html#7">7 - Incompatibilities with the Previous Version</A>
|
||||
<LI><A HREF="manual.html#7">7 – Incompatibilities with the Previous Version</A>
|
||||
<UL>
|
||||
<LI><A HREF="manual.html#7.1">7.1 - Changes in the Language</A>
|
||||
<LI><A HREF="manual.html#7.2">7.2 - Changes in the Libraries</A>
|
||||
<LI><A HREF="manual.html#7.3">7.3 - Changes in the API</A>
|
||||
<LI><A HREF="manual.html#7.1">7.1 – Changes in the Language</A>
|
||||
<LI><A HREF="manual.html#7.2">7.2 – Changes in the Libraries</A>
|
||||
<LI><A HREF="manual.html#7.3">7.3 – Changes in the API</A>
|
||||
</UL>
|
||||
<P>
|
||||
<LI><A HREF="manual.html#8">8 - The Complete Syntax of Lua</A>
|
||||
<LI><A HREF="manual.html#8">8 – The Complete Syntax of Lua</A>
|
||||
</UL>
|
||||
|
||||
<H2><A NAME="index">Index</A></H2>
|
||||
@@ -160,6 +156,8 @@ Freely available under the terms of the
|
||||
<H3><A NAME="functions">Lua functions</A></H3>
|
||||
<A HREF="manual.html#pdf-_G">_G</A><BR>
|
||||
<A HREF="manual.html#pdf-_VERSION">_VERSION</A><BR>
|
||||
<P>
|
||||
|
||||
<A HREF="manual.html#pdf-assert">assert</A><BR>
|
||||
<A HREF="manual.html#pdf-collectgarbage">collectgarbage</A><BR>
|
||||
<A HREF="manual.html#pdf-dofile">dofile</A><BR>
|
||||
@@ -487,12 +485,12 @@ Freely available under the terms of the
|
||||
<P>
|
||||
|
||||
<HR>
|
||||
<SMALL>
|
||||
<SMALL CLASS="footer">
|
||||
Last update:
|
||||
Sat Jan 19 13:24:29 BRST 2008
|
||||
Mon Feb 13 18:53:32 BRST 2012
|
||||
</SMALL>
|
||||
<!--
|
||||
Last change: revised for Lua 5.1.3
|
||||
Last change: revised for Lua 5.1.5
|
||||
-->
|
||||
|
||||
</BODY>
|
||||
|
||||
Vendored
+45
-3
@@ -1,17 +1,37 @@
|
||||
body {
|
||||
color: #000000 ;
|
||||
background-color: #FFFFFF ;
|
||||
font-family: sans-serif ;
|
||||
font-family: Helvetica, Arial, sans-serif ;
|
||||
text-align: justify ;
|
||||
margin-right: 20px ;
|
||||
margin-left: 20px ;
|
||||
margin-right: 30px ;
|
||||
margin-left: 30px ;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
font-family: Verdana, Geneva, sans-serif ;
|
||||
font-weight: normal ;
|
||||
font-style: italic ;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-top: 0.4em ;
|
||||
padding-bottom: 0.4em ;
|
||||
padding-left: 30px ;
|
||||
padding-right: 30px ;
|
||||
margin-left: -30px ;
|
||||
background-color: #E0E0FF ;
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding-left: 0.5em ;
|
||||
border-left: solid #E0E0FF 1em ;
|
||||
}
|
||||
|
||||
table h3 {
|
||||
padding-left: 0px ;
|
||||
border-left: none ;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #000080 ;
|
||||
background-color: inherit ;
|
||||
@@ -39,3 +59,25 @@ hr {
|
||||
background-color: #a0a0a0 ;
|
||||
}
|
||||
|
||||
:target {
|
||||
background-color: #F8F8F8 ;
|
||||
padding: 8px ;
|
||||
border: solid #a0a0a0 2px ;
|
||||
}
|
||||
|
||||
.footer {
|
||||
color: gray ;
|
||||
font-size: small ;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
border: solid #a0a0a0 2px ;
|
||||
border-radius: 2em ;
|
||||
-moz-border-radius: 2em ;
|
||||
background-image: url('images/search.png') ;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 4px center ;
|
||||
padding-left: 20px ;
|
||||
height: 2em ;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+13
-2
@@ -1,13 +1,24 @@
|
||||
h3 code {
|
||||
font-family: inherit ;
|
||||
font-size: inherit ;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 105% ;
|
||||
pre, code {
|
||||
font-size: 12pt ;
|
||||
}
|
||||
|
||||
span.apii {
|
||||
float: right ;
|
||||
font-family: inherit ;
|
||||
font-style: normal ;
|
||||
font-size: small ;
|
||||
color: gray ;
|
||||
}
|
||||
|
||||
p+h1, ul+h1 {
|
||||
padding-top: 0.4em ;
|
||||
padding-bottom: 0.4em ;
|
||||
padding-left: 30px ;
|
||||
margin-left: -30px ;
|
||||
background-color: #E0E0FF ;
|
||||
}
|
||||
|
||||
Vendored
+17
-14
@@ -19,9 +19,9 @@ Lua 5.1 Reference Manual
|
||||
by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes
|
||||
<p>
|
||||
<small>
|
||||
Copyright © 2006-2008 Lua.org, PUC-Rio.
|
||||
Copyright © 2006–2012 Lua.org, PUC-Rio.
|
||||
Freely available under the terms of the
|
||||
<a href="http://www.lua.org/license.html#5">Lua license</a>.
|
||||
<a href="http://www.lua.org/license.html">Lua license</a>.
|
||||
</small>
|
||||
<hr>
|
||||
<p>
|
||||
@@ -29,11 +29,13 @@ Freely available under the terms of the
|
||||
<a href="contents.html#contents">contents</A>
|
||||
·
|
||||
<a href="contents.html#index">index</A>
|
||||
·
|
||||
<A HREF="http://www.lua.org/manual/">other versions</A>
|
||||
|
||||
<!-- ====================================================================== -->
|
||||
<p>
|
||||
|
||||
<!-- $Id: manual.of,v 1.48 2008/08/18 15:24:20 roberto Exp $ -->
|
||||
<!-- $Id: manual.of,v 1.49.1.2 2012/01/13 20:23:26 roberto Exp $ -->
|
||||
|
||||
|
||||
|
||||
@@ -5645,7 +5647,7 @@ Currently, Lua has the following standard libraries:
|
||||
|
||||
<ul>
|
||||
|
||||
<li>basic library,</li> which includes the coroutine sub-library;
|
||||
<li>basic library, which includes the coroutine sub-library;</li>
|
||||
|
||||
<li>package library;</li>
|
||||
|
||||
@@ -5709,7 +5711,7 @@ when absent, it defaults to "assertion failed!"
|
||||
|
||||
|
||||
<p>
|
||||
<hr><h3><a name="pdf-collectgarbage"><code>collectgarbage (opt [, arg])</code></a></h3>
|
||||
<hr><h3><a name="pdf-collectgarbage"><code>collectgarbage ([opt [, arg]])</code></a></h3>
|
||||
|
||||
|
||||
<p>
|
||||
@@ -5718,6 +5720,11 @@ It performs different functions according to its first argument, <code>opt</code
|
||||
|
||||
<ul>
|
||||
|
||||
<li><b>"collect":</b>
|
||||
performs a full garbage-collection cycle.
|
||||
This is the default option.
|
||||
</li>
|
||||
|
||||
<li><b>"stop":</b>
|
||||
stops the garbage collector.
|
||||
</li>
|
||||
@@ -5726,10 +5733,6 @@ stops the garbage collector.
|
||||
restarts the garbage collector.
|
||||
</li>
|
||||
|
||||
<li><b>"collect":</b>
|
||||
performs a full garbage-collection cycle.
|
||||
</li>
|
||||
|
||||
<li><b>"count":</b>
|
||||
returns the total memory in use by Lua (in Kbytes).
|
||||
</li>
|
||||
@@ -5760,7 +5763,7 @@ Returns the previous value for <em>step</em>.
|
||||
|
||||
|
||||
<p>
|
||||
<hr><h3><a name="pdf-dofile"><code>dofile (filename)</code></a></h3>
|
||||
<hr><h3><a name="pdf-dofile"><code>dofile ([filename])</code></a></h3>
|
||||
Opens the named file and executes its contents as a Lua chunk.
|
||||
When called without arguments,
|
||||
<code>dofile</code> executes the contents of the standard input (<code>stdin</code>).
|
||||
@@ -8421,7 +8424,7 @@ Otherwise, it returns the name of the upvalue.
|
||||
|
||||
|
||||
<p>
|
||||
<hr><h3><a name="pdf-debug.traceback"><code>debug.traceback ([thread,] [message] [, level])</code></a></h3>
|
||||
<hr><h3><a name="pdf-debug.traceback"><code>debug.traceback ([thread,] [message [, level]])</code></a></h3>
|
||||
|
||||
|
||||
<p>
|
||||
@@ -8789,12 +8792,12 @@ Here is the complete syntax of Lua in extended BNF.
|
||||
|
||||
|
||||
<HR>
|
||||
<SMALL>
|
||||
<SMALL CLASS="footer">
|
||||
Last update:
|
||||
Mon Aug 18 13:25:46 BRT 2008
|
||||
Mon Feb 13 18:54:19 BRST 2012
|
||||
</SMALL>
|
||||
<!--
|
||||
Last change: revised for Lua 5.1.4
|
||||
Last change: revised for Lua 5.1.5
|
||||
-->
|
||||
|
||||
</body></html>
|
||||
|
||||
Vendored
+2
-2
@@ -12,7 +12,7 @@
|
||||
Documentation
|
||||
</H1>
|
||||
|
||||
This is the documentation included in the source distribution of Lua 5.1.4.
|
||||
This is the documentation included in the source distribution of Lua 5.1.5.
|
||||
|
||||
<UL>
|
||||
<LI><A HREF="contents.html">Reference manual</A>
|
||||
@@ -33,7 +33,7 @@ especially the
|
||||
<HR>
|
||||
<SMALL>
|
||||
Last update:
|
||||
Tue Aug 12 14:46:07 BRT 2008
|
||||
Fri Feb 3 09:44:42 BRST 2012
|
||||
</SMALL>
|
||||
|
||||
</BODY>
|
||||
|
||||
Vendored
+1
-1
@@ -5,7 +5,7 @@
|
||||
# grep '^V=' ../Makefile
|
||||
V= 5.1
|
||||
# grep '^R=' ../Makefile
|
||||
R= 5.1.4
|
||||
R= 5.1.5
|
||||
|
||||
# grep '^INSTALL_.*=' ../Makefile | sed 's/INSTALL_TOP/prefix/'
|
||||
prefix= /usr/local
|
||||
|
||||
Vendored
+2
-2
@@ -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
|
||||
@@ -48,7 +48,7 @@ o: $(ALL_O)
|
||||
a: $(ALL_A)
|
||||
|
||||
$(LUA_A): $(CORE_O) $(LIB_O)
|
||||
$(AR) $@ $?
|
||||
$(AR) $@ $(CORE_O) $(LIB_O) # DLL needs all object files
|
||||
$(RANLIB) $@
|
||||
|
||||
$(LUA_T): $(LUA_O) $(LUA_A)
|
||||
|
||||
Vendored
+1
-1
@@ -631,7 +631,7 @@ static void base_open (lua_State *L) {
|
||||
luaL_register(L, "_G", base_funcs);
|
||||
lua_pushliteral(L, LUA_VERSION);
|
||||
lua_setglobal(L, "_VERSION"); /* set global _VERSION */
|
||||
/* `ipairs' and `pairs' need auxliliary functions as upvalues */
|
||||
/* `ipairs' and `pairs' need auxiliary functions as upvalues */
|
||||
auxopen(L, "ipairs", luaB_ipairs, ipairsaux);
|
||||
auxopen(L, "pairs", luaB_pairs, luaB_next);
|
||||
/* `newproxy' needs a weaktable as upvalue */
|
||||
|
||||
Vendored
+1
-9
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** $Id: lcode.c,v 2.25.1.3 2007/12/28 15:32:23 roberto Exp $
|
||||
** $Id: lcode.c,v 2.25.1.5 2011/01/31 14:53:16 roberto Exp $
|
||||
** Code generator for Lua
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
@@ -544,10 +544,6 @@ void luaK_goiftrue (FuncState *fs, expdesc *e) {
|
||||
pc = NO_JUMP; /* always true; do nothing */
|
||||
break;
|
||||
}
|
||||
case VFALSE: {
|
||||
pc = luaK_jump(fs); /* always jump */
|
||||
break;
|
||||
}
|
||||
case VJMP: {
|
||||
invertjump(fs, e);
|
||||
pc = e->u.s.info;
|
||||
@@ -572,10 +568,6 @@ static void luaK_goiffalse (FuncState *fs, expdesc *e) {
|
||||
pc = NO_JUMP; /* always false; do nothing */
|
||||
break;
|
||||
}
|
||||
case VTRUE: {
|
||||
pc = luaK_jump(fs); /* always jump */
|
||||
break;
|
||||
}
|
||||
case VJMP: {
|
||||
pc = e->u.s.info;
|
||||
break;
|
||||
|
||||
Vendored
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** $Id: ldblib.c,v 1.104.1.3 2008/01/21 13:11:21 roberto Exp $
|
||||
** $Id: ldblib.c,v 1.104.1.4 2009/08/04 18:50:18 roberto Exp $
|
||||
** Interface from Lua to its debug API
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
@@ -45,6 +45,7 @@ static int db_setmetatable (lua_State *L) {
|
||||
|
||||
|
||||
static int db_getfenv (lua_State *L) {
|
||||
luaL_checkany(L, 1);
|
||||
lua_getfenv(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Vendored
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** $Id: ldo.c,v 2.38.1.3 2008/01/18 22:31:22 roberto Exp $
|
||||
** $Id: ldo.c,v 2.38.1.4 2012/01/18 02:27:10 roberto Exp $
|
||||
** Stack and Call structure of Lua
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
@@ -217,6 +217,7 @@ static StkId adjust_varargs (lua_State *L, Proto *p, int actual) {
|
||||
int nvar = actual - nfixargs; /* number of extra arguments */
|
||||
lua_assert(p->is_vararg & VARARG_HASARG);
|
||||
luaC_checkGC(L);
|
||||
luaD_checkstack(L, p->maxstacksize);
|
||||
htab = luaH_new(L, nvar, 1); /* create `arg' table */
|
||||
for (i=0; i<nvar; i++) /* put extra arguments into `arg' table */
|
||||
setobj2n(L, luaH_setnum(L, htab, i+1), L->top - nvar + i);
|
||||
|
||||
Vendored
+1
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** $Id: lgc.c,v 2.38.1.1 2007/12/27 13:02:25 roberto Exp $
|
||||
** $Id: lgc.c,v 2.38.1.2 2011/03/18 18:05:38 roberto Exp $
|
||||
** Garbage Collector
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
@@ -627,7 +627,6 @@ void luaC_step (lua_State *L) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
lua_assert(g->totalbytes >= g->estimate);
|
||||
setthreshold(g);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+5
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** $Id: liolib.c,v 2.73.1.3 2008/01/18 17:47:43 roberto Exp $
|
||||
** $Id: liolib.c,v 2.73.1.4 2010/05/14 15:33:51 roberto Exp $
|
||||
** Standard I/O (and system) library
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
@@ -276,7 +276,10 @@ static int read_number (lua_State *L, FILE *f) {
|
||||
lua_pushnumber(L, d);
|
||||
return 1;
|
||||
}
|
||||
else return 0; /* read fails */
|
||||
else {
|
||||
lua_pushnil(L); /* "result" to be removed */
|
||||
return 0; /* read fails */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Vendored
+4
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** $Id: llex.c,v 2.20.1.1 2007/12/27 13:02:25 roberto Exp $
|
||||
** $Id: llex.c,v 2.20.1.2 2009/11/23 14:58:22 roberto Exp $
|
||||
** Lexical Analyzer
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
@@ -118,8 +118,10 @@ TString *luaX_newstring (LexState *ls, const char *str, size_t l) {
|
||||
lua_State *L = ls->L;
|
||||
TString *ts = luaS_newlstr(L, str, l);
|
||||
TValue *o = luaH_setstr(L, ls->fs->h, ts); /* entry for `str' */
|
||||
if (ttisnil(o))
|
||||
if (ttisnil(o)) {
|
||||
setbvalue(o, 1); /* make sure `str' will not be collected */
|
||||
luaC_checkGC(L);
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** $Id: loadlib.c,v 1.52.1.3 2008/08/06 13:29:28 roberto Exp $
|
||||
** $Id: loadlib.c,v 1.52.1.4 2009/09/09 13:17:16 roberto Exp $
|
||||
** Dynamic library loader for Lua
|
||||
** See Copyright Notice in lua.h
|
||||
**
|
||||
@@ -639,7 +639,7 @@ LUALIB_API int luaopen_package (lua_State *L) {
|
||||
lua_pushvalue(L, -1);
|
||||
lua_replace(L, LUA_ENVIRONINDEX);
|
||||
/* create `loaders' table */
|
||||
lua_createtable(L, 0, sizeof(loaders)/sizeof(loaders[0]) - 1);
|
||||
lua_createtable(L, sizeof(loaders)/sizeof(loaders[0]) - 1, 0);
|
||||
/* fill it with pre-defined loaders */
|
||||
for (i=0; loaders[i] != NULL; i++) {
|
||||
lua_pushcfunction(L, loaders[i]);
|
||||
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** $Id: lparser.c,v 2.42.1.3 2007/12/28 15:32:23 roberto Exp $
|
||||
** $Id: lparser.c,v 2.42.1.4 2011/10/21 19:31:42 roberto Exp $
|
||||
** Lua Parser
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
@@ -374,9 +374,9 @@ static void close_func (LexState *ls) {
|
||||
lua_assert(luaG_checkcode(f));
|
||||
lua_assert(fs->bl == NULL);
|
||||
ls->fs = fs->prev;
|
||||
L->top -= 2; /* remove table and prototype from the stack */
|
||||
/* last token read was anchored in defunct function; must reanchor it */
|
||||
if (fs) anchor_token(ls);
|
||||
L->top -= 2; /* remove table and prototype from the stack */
|
||||
}
|
||||
|
||||
|
||||
|
||||
Vendored
+4
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** $Id: lstrlib.c,v 1.132.1.4 2008/07/11 17:27:21 roberto Exp $
|
||||
** $Id: lstrlib.c,v 1.132.1.5 2010/05/14 15:34:19 roberto Exp $
|
||||
** Standard library for string operations and pattern-matching
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
@@ -754,6 +754,7 @@ static void addintlen (char *form) {
|
||||
|
||||
|
||||
static int str_format (lua_State *L) {
|
||||
int top = lua_gettop(L);
|
||||
int arg = 1;
|
||||
size_t sfl;
|
||||
const char *strfrmt = luaL_checklstring(L, arg, &sfl);
|
||||
@@ -768,7 +769,8 @@ static int str_format (lua_State *L) {
|
||||
else { /* format item */
|
||||
char form[MAX_FORMAT]; /* to store the format (`%...') */
|
||||
char buff[MAX_ITEM]; /* to store the formatted item */
|
||||
arg++;
|
||||
if (++arg > top)
|
||||
luaL_argerror(L, arg, "no value");
|
||||
strfrmt = scanformat(L, strfrmt, form);
|
||||
switch (*strfrmt++) {
|
||||
case 'c': {
|
||||
|
||||
Vendored
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** $Id: lua.h,v 1.218.1.5 2008/08/06 13:30:12 roberto Exp $
|
||||
** $Id: lua.h,v 1.218.1.7 2012/01/13 20:36:20 roberto Exp $
|
||||
** Lua - An Extensible Extension Language
|
||||
** Lua.org, PUC-Rio, Brazil (http://www.lua.org)
|
||||
** See Copyright Notice at the end of this file
|
||||
@@ -17,9 +17,9 @@
|
||||
|
||||
|
||||
#define LUA_VERSION "Lua 5.1"
|
||||
#define LUA_RELEASE "Lua 5.1.4"
|
||||
#define LUA_RELEASE "Lua 5.1.5"
|
||||
#define LUA_VERSION_NUM 501
|
||||
#define LUA_COPYRIGHT "Copyright (C) 1994-2008 Lua.org, PUC-Rio"
|
||||
#define LUA_COPYRIGHT "Copyright (C) 1994-2012 Lua.org, PUC-Rio"
|
||||
#define LUA_AUTHORS "R. Ierusalimschy, L. H. de Figueiredo & W. Celes"
|
||||
|
||||
|
||||
@@ -362,7 +362,7 @@ struct lua_Debug {
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved.
|
||||
* Copyright (C) 1994-2012 Lua.org, PUC-Rio. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
|
||||
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_isnumber(L,-1)) goto not_array;
|
||||
if (lua_type(L,-1) != LUA_TNUMBER) goto not_array;
|
||||
n = lua_tonumber(L,-1);
|
||||
idx = n;
|
||||
if (idx != n || idx < 1) goto not_array;
|
||||
|
||||
Vendored
+6
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
** $Id: lvm.c,v 2.63.1.3 2007/12/28 15:32:23 roberto Exp $
|
||||
** $Id: lvm.c,v 2.63.1.5 2011/08/17 20:43:11 roberto Exp $
|
||||
** Lua virtual machine
|
||||
** See Copyright Notice in lua.h
|
||||
*/
|
||||
@@ -133,6 +133,7 @@ void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) {
|
||||
|
||||
void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) {
|
||||
int loop;
|
||||
TValue temp;
|
||||
for (loop = 0; loop < MAXTAGLOOP; loop++) {
|
||||
const TValue *tm;
|
||||
if (ttistable(t)) { /* `t' is a table? */
|
||||
@@ -141,6 +142,7 @@ void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) {
|
||||
if (!ttisnil(oldval) || /* result is no nil? */
|
||||
(tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */
|
||||
setobj2t(L, oldval, val);
|
||||
h->flags = 0;
|
||||
luaC_barriert(L, h, val);
|
||||
return;
|
||||
}
|
||||
@@ -152,7 +154,9 @@ void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) {
|
||||
callTM(L, tm, t, key, val);
|
||||
return;
|
||||
}
|
||||
t = tm; /* else repeat with `tm' */
|
||||
/* else repeat with `tm' */
|
||||
setobj(L, &temp, tm); /* avoid pointing inside table (may rehash) */
|
||||
t = &temp;
|
||||
}
|
||||
luaG_runerror(L, "loop in settable");
|
||||
}
|
||||
|
||||
+111
-7
@@ -24,9 +24,14 @@ pidfile /var/run/redis.pid
|
||||
# If port 0 is specified Redis will not listen on a TCP socket.
|
||||
port 6379
|
||||
|
||||
# If you want you can bind a single interface, if the bind option is not
|
||||
# specified all the interfaces will listen for incoming connections.
|
||||
# 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.
|
||||
#
|
||||
# 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
|
||||
@@ -63,10 +68,10 @@ tcp-keepalive 0
|
||||
# warning (only very important / critical messages are logged)
|
||||
loglevel notice
|
||||
|
||||
# Specify the log file name. Also 'stdout' can be used to force
|
||||
# Specify the log file name. Also the emptry string 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 stdout
|
||||
logfile ""
|
||||
|
||||
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
|
||||
# and optionally update the other syslog parameters to suit your needs.
|
||||
@@ -203,8 +208,11 @@ slave-read-only yes
|
||||
#
|
||||
# repl-ping-slave-period 10
|
||||
|
||||
# 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.
|
||||
# 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).
|
||||
#
|
||||
# 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
|
||||
@@ -227,6 +235,28 @@ 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.
|
||||
@@ -242,6 +272,28 @@ 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
|
||||
@@ -479,6 +531,52 @@ 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
|
||||
@@ -567,7 +665,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 perforemd with the same frequency, but Redis checks for
|
||||
# Not all tasks are performed 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
|
||||
@@ -580,6 +678,12 @@ client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
# 100 only in environments where very low latency is required.
|
||||
hz 10
|
||||
|
||||
# When a child rewrites the AOF file, if the following option is enabled
|
||||
# the file will be fsync-ed every 32 MB of data generated. This is useful
|
||||
# in order to commit the file to the disk more incrementally and avoid
|
||||
# big latency spikes.
|
||||
aof-rewrite-incremental-fsync yes
|
||||
|
||||
################################## INCLUDES ###################################
|
||||
|
||||
# Include one or more other config files here. This is useful if you
|
||||
|
||||
+45
-39
@@ -18,72 +18,76 @@ OPTIMIZATION?=-O2
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua
|
||||
|
||||
# Default settings
|
||||
STD= -std=c99 -pedantic
|
||||
WARN= -Wall
|
||||
OPT= $(OPTIMIZATION)
|
||||
STD=-std=c99 -pedantic
|
||||
WARN=-Wall
|
||||
OPT=$(OPTIMIZATION)
|
||||
|
||||
PREFIX?=/usr/local
|
||||
INSTALL_BIN=$(PREFIX)/bin
|
||||
INSTALL=install
|
||||
|
||||
# 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)
|
||||
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
|
||||
INSTALL=cp -pf
|
||||
FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lpthread
|
||||
else ifeq ($(uname_S),Darwin)
|
||||
|
||||
else
|
||||
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
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
FINAL_LIBS+= -pthread
|
||||
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"
|
||||
@@ -97,17 +101,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
|
||||
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
|
||||
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
|
||||
|
||||
all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
@echo ""
|
||||
@@ -196,6 +200,8 @@ distclean: clean
|
||||
test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
@(cd ..; ./runtest)
|
||||
|
||||
check: test
|
||||
|
||||
lcov:
|
||||
$(MAKE) gcov
|
||||
@(set -e; cd ..; ./runtest --clients 1)
|
||||
@@ -226,7 +232,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)
|
||||
|
||||
+9
-5
@@ -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
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h crc64.h bio.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
|
||||
memtest.o: memtest.c config.h
|
||||
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,6 +40,9 @@ 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
|
||||
@@ -55,19 +58,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
|
||||
redis-check-dump.o: redis-check-dump.c lzf.h crc64.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
|
||||
release.o: release.c release.h version.h crc64.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
|
||||
rio.o: rio.c fmacros.h rio.h sds.h util.h crc64.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 \
|
||||
@@ -79,6 +82,7 @@ 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,6 +91,36 @@ 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,5 +114,7 @@ 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,6 +55,13 @@ 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,6 +94,11 @@ 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;
|
||||
|
||||
|
||||
+7
-1
@@ -54,10 +54,16 @@ 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,6 +48,12 @@ 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);
|
||||
}
|
||||
|
||||
+147
-67
@@ -163,27 +163,44 @@ int anetTcpKeepAlive(char *err, int fd)
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetResolve(char *err, char *host, char *ipbuf)
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len)
|
||||
{
|
||||
struct sockaddr_in sa;
|
||||
struct addrinfo hints, *info;
|
||||
int rv;
|
||||
|
||||
sa.sin_family = AF_INET;
|
||||
if (inet_aton(host, &sa.sin_addr) == 0) {
|
||||
struct hostent *he;
|
||||
memset(&hints,0,sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM; /* specify socktype to avoid dups */
|
||||
|
||||
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));
|
||||
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;
|
||||
}
|
||||
strcpy(ipbuf,inet_ntoa(sa.sin_addr));
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
static int anetCreateSocket(char *err, int domain) {
|
||||
int s, on = 1;
|
||||
int s;
|
||||
if ((s = socket(domain, SOCK_STREAM, 0)) == -1) {
|
||||
anetSetError(err, "creating socket: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
@@ -191,8 +208,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 (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_REUSEADDR: %s", strerror(errno));
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) {
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
return s;
|
||||
@@ -202,38 +219,45 @@ 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;
|
||||
struct sockaddr_in sa;
|
||||
int s, rv;
|
||||
char _port[6]; /* strlen("65535"); */
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
|
||||
if ((s = anetCreateSocket(err,AF_INET)) == ANET_ERR)
|
||||
snprintf(_port,6,"%d",port);
|
||||
memset(&hints,0,sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
|
||||
anetSetError(err, "%s", gai_strerror(rv));
|
||||
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;
|
||||
|
||||
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);
|
||||
/* 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);
|
||||
return ANET_ERR;
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
@@ -331,28 +355,63 @@ static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) {
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
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");
|
||||
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));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa)) == 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));
|
||||
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;
|
||||
@@ -388,15 +447,22 @@ static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *l
|
||||
return fd;
|
||||
}
|
||||
|
||||
int anetTcpAccept(char *err, int s, char *ip, int *port) {
|
||||
int anetTcpAccept(char *err, int s, char *ip, size_t ip_len, int *port) {
|
||||
int fd;
|
||||
struct sockaddr_in sa;
|
||||
struct sockaddr_storage sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
|
||||
if (port) *port = ntohs(sa.sin_port);
|
||||
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);
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
@@ -410,8 +476,8 @@ int anetUnixAccept(char *err, int s) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
int anetPeerToString(int fd, char *ip, int *port) {
|
||||
struct sockaddr_in sa;
|
||||
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) {
|
||||
struct sockaddr_storage sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) {
|
||||
@@ -420,13 +486,20 @@ int anetPeerToString(int fd, char *ip, int *port) {
|
||||
ip[1] = '\0';
|
||||
return -1;
|
||||
}
|
||||
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
|
||||
if (port) *port = ntohs(sa.sin_port);
|
||||
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);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int anetSockName(int fd, char *ip, int *port) {
|
||||
struct sockaddr_in sa;
|
||||
int anetSockName(int fd, char *ip, size_t ip_len, int *port) {
|
||||
struct sockaddr_storage sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
if (getsockname(fd,(struct sockaddr*)&sa,&salen) == -1) {
|
||||
@@ -435,7 +508,14 @@ int anetSockName(int fd, char *ip, int *port) {
|
||||
ip[1] = '\0';
|
||||
return -1;
|
||||
}
|
||||
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
|
||||
if (port) *port = ntohs(sa.sin_port);
|
||||
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);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
+5
-3
@@ -44,17 +44,19 @@ 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);
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr);
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr);
|
||||
int anetUnixServer(char *err, char *path, mode_t perm);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, int *port);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
|
||||
int anetUnixAccept(char *err, int serversock);
|
||||
int anetWrite(int fd, char *buf, int count);
|
||||
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, int *port);
|
||||
int anetPeerToString(int fd, char *ip, size_t ip_len, 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,decrRefCount);
|
||||
listSetFreeMethod(c->reply,decrRefCountVoid);
|
||||
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", argv[0]->ptr);
|
||||
redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);
|
||||
exit(1);
|
||||
}
|
||||
/* Run the command in the context of a fake client */
|
||||
@@ -855,6 +855,8 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
}
|
||||
|
||||
rioInitWithFile(&aof,fp);
|
||||
if (server.aof_rewrite_incremental_fsync)
|
||||
rioSetAutoSync(&aof,REDIS_AOF_AUTOSYNC_BYTES);
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
|
||||
redisDb *db = server.db+j;
|
||||
@@ -882,6 +884,9 @@ 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 */
|
||||
@@ -904,8 +909,6 @@ 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;
|
||||
@@ -959,15 +962,15 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
char tmpfile[256];
|
||||
|
||||
/* Child */
|
||||
if (server.ipfd > 0) close(server.ipfd);
|
||||
if (server.sofd > 0) close(server.sofd);
|
||||
closeListeningSockets(0);
|
||||
redisSetProcTitle("redis-aof-rewrite");
|
||||
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: %lu MB of memory used by copy-on-write",
|
||||
"AOF rewrite: %zu MB of memory used by copy-on-write",
|
||||
private_dirty/(1024*1024));
|
||||
}
|
||||
exitFromChild(0);
|
||||
@@ -994,6 +997,7 @@ 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,6 +61,7 @@
|
||||
#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];
|
||||
@@ -118,6 +119,7 @@ void bioInit(void) {
|
||||
redisLog(REDIS_WARNING,"Fatal: Can't initialize Background Jobs.");
|
||||
exit(1);
|
||||
}
|
||||
bio_threads[j] = thread;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +142,11 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
unsigned long type = (unsigned long) arg;
|
||||
sigset_t sigset;
|
||||
|
||||
pthread_detach(pthread_self());
|
||||
/* 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_mutex_lock(&bio_mutex[type]);
|
||||
/* Block SIGALRM so we are sure that only the main thread will
|
||||
* receive the watchdog signal. */
|
||||
@@ -192,56 +198,23 @@ unsigned long long bioPendingJobsOfType(int type) {
|
||||
return val;
|
||||
}
|
||||
|
||||
#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. */
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
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,6 +33,7 @@ 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. */
|
||||
|
||||
+6
-3
@@ -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. */
|
||||
long popcount(void *s, long count) {
|
||||
long bits = 0;
|
||||
size_t redisPopcount(void *s, long count) {
|
||||
size_t 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,6 +153,7 @@ 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);
|
||||
}
|
||||
@@ -346,9 +347,11 @@ 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. */
|
||||
@@ -404,6 +407,6 @@ void bitcountCommand(redisClient *c) {
|
||||
} else {
|
||||
long bytes = end-start+1;
|
||||
|
||||
addReplyLongLong(c,popcount(p+start,bytes));
|
||||
addReplyLongLong(c,redisPopcount(p+start,bytes));
|
||||
}
|
||||
}
|
||||
|
||||
+786
-39
@@ -28,9 +28,33 @@
|
||||
* 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
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -81,7 +105,7 @@ void loadServerConfigFromString(char *config) {
|
||||
/* Skip this line if the resulting command vector is empty. */
|
||||
if (argc == 0) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
sdstolower(argv[0]);
|
||||
|
||||
@@ -101,8 +125,15 @@ void loadServerConfigFromString(char *config) {
|
||||
if (server.port < 0 || server.port > 65535) {
|
||||
err = "Invalid port"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"bind") && argc == 2) {
|
||||
server.bindaddr = zstrdup(argv[1]);
|
||||
} 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],"unixsocket") && argc == 2) {
|
||||
server.unixsocket = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"unixsocketperm") && argc == 2) {
|
||||
@@ -140,12 +171,9 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if (!strcasecmp(argv[0],"logfile") && argc == 2) {
|
||||
FILE *logfp;
|
||||
|
||||
zfree(server.logfile);
|
||||
server.logfile = zstrdup(argv[1]);
|
||||
if (!strcasecmp(server.logfile,"stdout")) {
|
||||
zfree(server.logfile);
|
||||
server.logfile = NULL;
|
||||
}
|
||||
if (server.logfile) {
|
||||
if (server.logfile[0] != '\0') {
|
||||
/* 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");
|
||||
@@ -164,21 +192,6 @@ 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++) {
|
||||
@@ -249,6 +262,19 @@ 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) {
|
||||
@@ -287,6 +313,10 @@ 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")
|
||||
@@ -317,6 +347,12 @@ void loadServerConfigFromString(char *config) {
|
||||
argc == 2)
|
||||
{
|
||||
server.aof_rewrite_min_size = memtoll(argv[1],NULL);
|
||||
} else if (!strcasecmp(argv[0],"aof-rewrite-incremental-fsync") &&
|
||||
argc == 2)
|
||||
{
|
||||
if ((server.aof_rewrite_incremental_fsync = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
|
||||
if (strlen(argv[1]) > REDIS_AUTHPASS_MAX_LEN) {
|
||||
err = "Password is longer than REDIS_AUTHPASS_MAX_LEN";
|
||||
@@ -327,6 +363,10 @@ 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) {
|
||||
@@ -403,6 +443,24 @@ 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. */
|
||||
@@ -468,7 +526,7 @@ void loadServerConfig(char *filename, char *options) {
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* CONFIG command for remote configuration
|
||||
* CONFIG SET implementation
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void configSetCommand(redisClient *c) {
|
||||
@@ -479,6 +537,10 @@ 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")) {
|
||||
@@ -487,7 +549,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 = zstrdup(o->ptr);
|
||||
server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
@@ -498,10 +560,35 @@ 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 = (int) ll;
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.hz = 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")) {
|
||||
@@ -566,6 +653,11 @@ void configSetCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"auto-aof-rewrite-min-size")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.aof_rewrite_min_size = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"aof-rewrite-incremental-fsync")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.aof_rewrite_incremental_fsync = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"save")) {
|
||||
int vlen, j;
|
||||
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
|
||||
@@ -714,6 +806,12 @@ 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)
|
||||
@@ -725,11 +823,11 @@ void configSetCommand(redisClient *c) {
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.rdb_compression = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"rdbchecksum")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"notify-keyspace-events")) {
|
||||
int flags = keyspaceEventsStringToFlags(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.rdb_checksum = yn;
|
||||
if (flags == -1) goto badfmt;
|
||||
server.notify_keyspace_events = flags;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"repl-disable-tcp-nodelay")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
@@ -737,8 +835,18 @@ 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);
|
||||
@@ -753,6 +861,10 @@ 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); \
|
||||
@@ -789,8 +901,7 @@ void configGetCommand(redisClient *c) {
|
||||
/* String values */
|
||||
config_get_string_field("dbfilename",server.rdb_filename);
|
||||
config_get_string_field("requirepass",server.requirepass);
|
||||
config_get_string_field("masterauth",server.requirepass);
|
||||
config_get_string_field("bind",server.bindaddr);
|
||||
config_get_string_field("masterauth",server.masterauth);
|
||||
config_get_string_field("unixsocket",server.unixsocket);
|
||||
config_get_string_field("logfile",server.logfile);
|
||||
config_get_string_field("pidfile",server.pidfile);
|
||||
@@ -827,9 +938,13 @@ 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 */
|
||||
@@ -847,6 +962,8 @@ void configGetCommand(redisClient *c) {
|
||||
config_get_bool_field("activerehashing", server.activerehashing);
|
||||
config_get_bool_field("repl-disable-tcp-nodelay",
|
||||
server.repl_disable_tcp_nodelay);
|
||||
config_get_bool_field("aof-rewrite-incremental-fsync",
|
||||
server.aof_rewrite_incremental_fsync);
|
||||
|
||||
/* Everything we can't handle with macros follows. */
|
||||
|
||||
@@ -899,8 +1016,8 @@ void configGetCommand(redisClient *c) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
buf = sdscatprintf(buf,"%ld %d",
|
||||
server.saveparams[j].seconds,
|
||||
buf = sdscatprintf(buf,"%jd %d",
|
||||
(intmax_t)server.saveparams[j].seconds,
|
||||
server.saveparams[j].changes);
|
||||
if (j != server.saveparamslen-1)
|
||||
buf = sdscatlen(buf," ",1);
|
||||
@@ -961,9 +1078,628 @@ 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
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* 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);
|
||||
|
||||
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') {
|
||||
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
|
||||
* configuraiton 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("# Generated by CONFIG REWRITE"));
|
||||
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);
|
||||
|
||||
/* 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;
|
||||
@@ -983,9 +1719,20 @@ 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");
|
||||
"CONFIG subcommand must be one of GET, SET, RESETSTAT, REWRITE");
|
||||
}
|
||||
return;
|
||||
|
||||
|
||||
+17
-1
@@ -45,7 +45,9 @@
|
||||
|
||||
/* Test for proc filesystem */
|
||||
#ifdef __linux__
|
||||
#define HAVE_PROCFS 1
|
||||
#define HAVE_PROC_STAT 1
|
||||
#define HAVE_PROC_MAPS 1
|
||||
#define HAVE_PROC_SMAPS 1
|
||||
#endif
|
||||
|
||||
/* Test for task_info() */
|
||||
@@ -103,6 +105,20 @@
|
||||
#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 */
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef CRC64_H
|
||||
#define CRC64_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
|
||||
#endif
|
||||
@@ -238,6 +238,8 @@ 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++;
|
||||
}
|
||||
@@ -351,6 +353,12 @@ 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.");
|
||||
}
|
||||
@@ -376,7 +384,8 @@ 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);
|
||||
@@ -384,6 +393,10 @@ 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);
|
||||
}
|
||||
@@ -493,8 +506,7 @@ void propagateExpire(redisDb *db, robj *key) {
|
||||
|
||||
if (server.aof_state != REDIS_AOF_OFF)
|
||||
feedAppendOnlyFile(server.delCommand,db->id,argv,2);
|
||||
if (listLength(server.slaves))
|
||||
replicationFeedSlaves(server.slaves,db->id,argv,2);
|
||||
replicationFeedSlaves(server.slaves,db->id,argv,2);
|
||||
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
@@ -525,6 +537,8 @@ 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);
|
||||
}
|
||||
|
||||
@@ -540,7 +554,6 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
* unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for
|
||||
* the argv[2] parameter. The basetime is always specified in milliseconds. */
|
||||
void expireGenericCommand(redisClient *c, long long basetime, int unit) {
|
||||
dictEntry *de;
|
||||
robj *key = c->argv[1], *param = c->argv[2];
|
||||
long long when; /* unix time in milliseconds when the key will expire. */
|
||||
|
||||
@@ -550,11 +563,12 @@ void expireGenericCommand(redisClient *c, long long basetime, int unit) {
|
||||
if (unit == UNIT_SECONDS) when *= 1000;
|
||||
when += basetime;
|
||||
|
||||
de = dictFind(c->db->dict,key->ptr);
|
||||
if (de == NULL) {
|
||||
/* No key, return zero. */
|
||||
if (lookupKeyRead(c->db,key) == NULL) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
|
||||
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
|
||||
* should never be executed as a DEL when load the AOF or in the context
|
||||
* of a slave instance.
|
||||
@@ -572,12 +586,14 @@ 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;
|
||||
}
|
||||
@@ -602,10 +618,17 @@ 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 = -1;
|
||||
if (ttl < 0) ttl = 0;
|
||||
}
|
||||
if (ttl == -1) {
|
||||
addReplyLongLong(c,-1);
|
||||
|
||||
+222
-86
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "redis.h"
|
||||
#include "sha1.h" /* SHA1 is used for DEBUG DIGEST */
|
||||
#include "crc64.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <signal.h>
|
||||
@@ -37,6 +38,7 @@
|
||||
#include <execinfo.h>
|
||||
#include <ucontext.h>
|
||||
#include <fcntl.h>
|
||||
#include "bio.h"
|
||||
#endif /* HAVE_BACKTRACE */
|
||||
|
||||
/* ================================= Debugging ============================== */
|
||||
@@ -294,6 +296,29 @@ 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;
|
||||
@@ -327,12 +352,20 @@ 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;
|
||||
|
||||
usleep(utime);
|
||||
tv.tv_sec = utime / 1000000;
|
||||
tv.tv_nsec = (utime % 1000000) * 1000;
|
||||
nanosleep(&tv, NULL);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"set-active-expire") &&
|
||||
c->argc == 3)
|
||||
{
|
||||
server.active_expire_enabled = atoi(c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyError(c,
|
||||
"Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]");
|
||||
addReplyErrorFormat(c, "Unknown DEBUG subcommand or wrong number of arguments for '%s'",
|
||||
(char*)c->argv[1]->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,9 +415,12 @@ 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: %d", sdslen(o->ptr));
|
||||
if (sdslen(o->ptr) < 4096)
|
||||
redisLog(REDIS_WARNING,"Object raw string content: \"%s\"", (char*)o->ptr);
|
||||
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);
|
||||
}
|
||||
} else if (o->type == REDIS_LIST) {
|
||||
redisLog(REDIS_WARNING,"List length: %d", (int) listTypeLength(o));
|
||||
} else if (o->type == REDIS_SET) {
|
||||
@@ -465,10 +501,13 @@ 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", sp+i, sp[i]);
|
||||
redisLog(REDIS_WARNING, "(%08lx) -> %08lx", addr, val);
|
||||
else
|
||||
redisLog(REDIS_WARNING, "(%016lx) -> %016lx", sp+i, sp[i]);
|
||||
redisLog(REDIS_WARNING, "(%016lx) -> %016lx", addr, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,27 +525,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",
|
||||
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
|
||||
(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
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext->__ss.__rsp);
|
||||
#else
|
||||
@@ -517,22 +556,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",
|
||||
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
|
||||
(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
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext->__ss.__esp);
|
||||
#endif
|
||||
@@ -546,22 +585,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",
|
||||
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]
|
||||
(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]
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.gregs[7]);
|
||||
#elif defined(__X86_64__) || defined(__x86_64__)
|
||||
@@ -573,25 +612,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",
|
||||
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]
|
||||
(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]
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.gregs[15]);
|
||||
#endif
|
||||
@@ -606,11 +645,12 @@ 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 = server.logfile ?
|
||||
open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644) :
|
||||
STDOUT_FILENO;
|
||||
fd = log_to_stdout ?
|
||||
STDOUT_FILENO :
|
||||
open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644);
|
||||
if (fd == -1) return;
|
||||
|
||||
/* Generate the stack trace */
|
||||
@@ -624,7 +664,7 @@ void logStackTrace(ucontext_t *uc) {
|
||||
backtrace_symbols_fd(trace, trace_size, fd);
|
||||
|
||||
/* Cleanup */
|
||||
if (server.logfile) close(fd);
|
||||
if (!log_to_stdout) close(fd);
|
||||
}
|
||||
|
||||
/* Log information about the "current" client, that is, the client that is
|
||||
@@ -658,13 +698,96 @@ 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:", key->ptr);
|
||||
redisLog(REDIS_WARNING,"key '%s' found in DB containing the following object:", (char*)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;
|
||||
@@ -700,6 +823,19 @@ 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"
|
||||
@@ -777,7 +913,7 @@ void watchdogScheduleSignal(int period) {
|
||||
setitimer(ITIMER_REAL, &it, NULL);
|
||||
}
|
||||
|
||||
/* Enable the software watchdong with the specified period in milliseconds. */
|
||||
/* Enable the software watchdog with the specified period in milliseconds. */
|
||||
void enableWatchdog(int period) {
|
||||
int min_period;
|
||||
|
||||
|
||||
+52
-7
@@ -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,6 +505,44 @@ 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));
|
||||
@@ -530,8 +568,12 @@ dictEntry *dictNext(dictIterator *iter)
|
||||
while (1) {
|
||||
if (iter->entry == NULL) {
|
||||
dictht *ht = &iter->d->ht[iter->table];
|
||||
if (iter->safe && iter->index == -1 && iter->table == 0)
|
||||
iter->d->iterators++;
|
||||
if (iter->index == -1 && iter->table == 0) {
|
||||
if (iter->safe)
|
||||
iter->d->iterators++;
|
||||
else
|
||||
iter->fingerprint = dictFingerprint(iter->d);
|
||||
}
|
||||
iter->index++;
|
||||
if (iter->index >= (signed) ht->size) {
|
||||
if (dictIsRehashing(iter->d) && iter->table == 0) {
|
||||
@@ -558,8 +600,12 @@ dictEntry *dictNext(dictIterator *iter)
|
||||
|
||||
void dictReleaseIterator(dictIterator *iter)
|
||||
{
|
||||
if (iter->safe && !(iter->index == -1 && iter->table == 0))
|
||||
iter->d->iterators--;
|
||||
if (!(iter->index == -1 && iter->table == 0)) {
|
||||
if (iter->safe)
|
||||
iter->d->iterators--;
|
||||
else
|
||||
assert(iter->fingerprint == dictFingerprint(iter->d));
|
||||
}
|
||||
zfree(iter);
|
||||
}
|
||||
|
||||
@@ -621,8 +667,7 @@ 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].size > d->ht[0].used) ?
|
||||
d->ht[0].size : d->ht[0].used)*2);
|
||||
return dictExpand(d, d->ht[0].used*2);
|
||||
}
|
||||
return DICT_OK;
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ typedef struct dictIterator {
|
||||
dict *d;
|
||||
int table, index, safe;
|
||||
dictEntry *entry, *nextEntry;
|
||||
long long fingerprint; /* unsafe iterator fingerprint for misuse detection */
|
||||
} dictIterator;
|
||||
|
||||
/* This is the initial size of every hash table */
|
||||
|
||||
+6
-2
@@ -36,9 +36,13 @@
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
#if defined(__linux__) || defined(__OpenBSD__)
|
||||
#define _XOPEN_SOURCE 700
|
||||
#else
|
||||
/*
|
||||
* On NetBSD, _XOPEN_SOURCE undefines _NETBSD_SOURCE and
|
||||
* thus hides inet_aton etc.
|
||||
*/
|
||||
#elif !defined(__NetBSD__)
|
||||
#define _XOPEN_SOURCE
|
||||
#endif
|
||||
|
||||
|
||||
+13
-3
@@ -69,6 +69,11 @@ 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",
|
||||
@@ -79,6 +84,11 @@ 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",
|
||||
@@ -280,7 +290,7 @@ struct commandHelp {
|
||||
1,
|
||||
"2.6.0" },
|
||||
{ "INFO",
|
||||
"-",
|
||||
"[section]",
|
||||
"Get information and statistics about the server",
|
||||
9,
|
||||
"1.0.0" },
|
||||
@@ -525,7 +535,7 @@ struct commandHelp {
|
||||
8,
|
||||
"1.0.0" },
|
||||
{ "SET",
|
||||
"key value",
|
||||
"key value [EX seconds] [PX milliseconds] [NX|XX]",
|
||||
"Set the string value of a key",
|
||||
1,
|
||||
"1.0.0" },
|
||||
@@ -665,7 +675,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,6 +35,7 @@
|
||||
#include <errno.h>
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "config.h"
|
||||
|
||||
#if (ULONG_MAX == 4294967295UL)
|
||||
#define MEMTEST_32BIT
|
||||
@@ -238,6 +239,32 @@ 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;
|
||||
|
||||
+2
-3
@@ -139,7 +139,7 @@ void migrateCommand(redisClient *c) {
|
||||
return;
|
||||
if (getLongFromObjectOrReply(c,c->argv[4],&dbid,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (timeout <= 0) timeout = 1000;
|
||||
if (timeout <= 0) timeout = 1;
|
||||
|
||||
/* 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,8 +157,7 @@ void migrateCommand(redisClient *c) {
|
||||
server.neterr);
|
||||
return;
|
||||
}
|
||||
if ((aeWait(fd,AE_WRITABLE,timeout) & AE_WRITABLE) == 0) {
|
||||
close(fd);
|
||||
if ((aeWait(fd,AE_WRITABLE,timeout*1000) & AE_WRITABLE) == 0) {
|
||||
addReplySds(c,sdsnew("-IOERR error or timeout connecting to the client\r\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#!/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
|
||||
|
||||
+19
-22
@@ -103,13 +103,11 @@ void discardCommand(redisClient *c) {
|
||||
|
||||
/* Send a MULTI command to all the slaves and AOF file. Check the execCommand
|
||||
* implementation for more information. */
|
||||
void execCommandReplicateMulti(redisClient *c) {
|
||||
void execCommandPropagateMulti(redisClient *c) {
|
||||
robj *multistring = createStringObject("MULTI",5);
|
||||
|
||||
if (server.aof_state != REDIS_AOF_OFF)
|
||||
feedAppendOnlyFile(server.multiCommand,c->db->id,&multistring,1);
|
||||
if (listLength(server.slaves))
|
||||
replicationFeedSlaves(server.slaves,c->db->id,&multistring,1);
|
||||
propagate(server.multiCommand,c->db->id,&multistring,1,
|
||||
REDIS_PROPAGATE_AOF|REDIS_PROPAGATE_REPL);
|
||||
decrRefCount(multistring);
|
||||
}
|
||||
|
||||
@@ -118,6 +116,7 @@ void execCommand(redisClient *c) {
|
||||
robj **orig_argv;
|
||||
int orig_argc;
|
||||
struct redisCommand *orig_cmd;
|
||||
int must_propagate = 0; /* Need to propagate MULTI/EXEC to AOF / slaves? */
|
||||
|
||||
if (!(c->flags & REDIS_MULTI)) {
|
||||
addReplyError(c,"EXEC without MULTI");
|
||||
@@ -133,19 +132,10 @@ void execCommand(redisClient *c) {
|
||||
if (c->flags & (REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC)) {
|
||||
addReply(c, c->flags & REDIS_DIRTY_EXEC ? shared.execaborterr :
|
||||
shared.nullmultibulk);
|
||||
freeClientMultiState(c);
|
||||
initClientMultiState(c);
|
||||
c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC);
|
||||
unwatchAllKeys(c);
|
||||
discardTransaction(c);
|
||||
goto handle_monitor;
|
||||
}
|
||||
|
||||
/* Replicate a MULTI request now that we are sure the block is executed.
|
||||
* This way we'll deliver the MULTI/..../EXEC block as a whole and
|
||||
* both the AOF and the replication link will have the same consistency
|
||||
* and atomicity guarantees. */
|
||||
execCommandReplicateMulti(c);
|
||||
|
||||
/* Exec all the queued commands */
|
||||
unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
|
||||
orig_argv = c->argv;
|
||||
@@ -156,6 +146,16 @@ void execCommand(redisClient *c) {
|
||||
c->argc = c->mstate.commands[j].argc;
|
||||
c->argv = c->mstate.commands[j].argv;
|
||||
c->cmd = c->mstate.commands[j].cmd;
|
||||
|
||||
/* Propagate a MULTI request once we encounter the first write op.
|
||||
* This way we'll deliver the MULTI/..../EXEC block as a whole and
|
||||
* both the AOF and the replication link will have the same consistency
|
||||
* and atomicity guarantees. */
|
||||
if (!must_propagate && !(c->cmd->flags & REDIS_CMD_READONLY)) {
|
||||
execCommandPropagateMulti(c);
|
||||
must_propagate = 1;
|
||||
}
|
||||
|
||||
call(c,REDIS_CALL_FULL);
|
||||
|
||||
/* Commands may alter argc/argv, restore mstate. */
|
||||
@@ -166,13 +166,10 @@ void execCommand(redisClient *c) {
|
||||
c->argv = orig_argv;
|
||||
c->argc = orig_argc;
|
||||
c->cmd = orig_cmd;
|
||||
freeClientMultiState(c);
|
||||
initClientMultiState(c);
|
||||
c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC);
|
||||
/* Make sure the EXEC command is always replicated / AOF, since we
|
||||
* always send the MULTI command (we can't know beforehand if the
|
||||
* next operations will contain at least a modification to the DB). */
|
||||
server.dirty++;
|
||||
discardTransaction(c);
|
||||
/* Make sure the EXEC command will be propagated as well if MULTI
|
||||
* was already propagated. */
|
||||
if (must_propagate) server.dirty++;
|
||||
|
||||
handle_monitor:
|
||||
/* Send EXEC to clients waiting data from MONITOR. We do it here
|
||||
|
||||
+156
-67
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "redis.h"
|
||||
#include <sys/uio.h>
|
||||
#include <math.h>
|
||||
|
||||
static void setProtocolError(redisClient *c, int pos);
|
||||
|
||||
@@ -87,21 +88,24 @@ 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,decrRefCount);
|
||||
listSetFreeMethod(c->reply,decrRefCountVoid);
|
||||
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,decrRefCount);
|
||||
listSetFreeMethod(c->io_keys,decrRefCountVoid);
|
||||
c->pubsub_channels = dictCreate(&setDictType,NULL);
|
||||
c->pubsub_patterns = listCreate();
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCount);
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||
if (fd != -1) listAddNodeTail(server.clients,c);
|
||||
initClientMultiState(c);
|
||||
@@ -115,15 +119,17 @@ 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
|
||||
* or a slave, 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,
|
||||
* a master, a slave not yet online, 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 ||
|
||||
@@ -410,9 +416,15 @@ void setDeferredMultiBulkLength(redisClient *c, void *node, long length) {
|
||||
void addReplyDouble(redisClient *c, double d) {
|
||||
char dbuf[128], sbuf[128];
|
||||
int dlen, 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/* Add a long long as integer reply or bulk len / multi bulk count.
|
||||
@@ -449,7 +461,10 @@ void addReplyLongLong(redisClient *c, long long ll) {
|
||||
}
|
||||
|
||||
void addReplyMultiBulkLen(redisClient *c, long length) {
|
||||
addReplyLongLongWithPrefix(c,length,'*');
|
||||
if (length < REDIS_SHARED_BULKHDR_LEN)
|
||||
addReply(c,shared.mbulkhdr[length]);
|
||||
else
|
||||
addReplyLongLongWithPrefix(c,length,'*');
|
||||
}
|
||||
|
||||
/* Create the length prefix of a bulk reply, example: $2234 */
|
||||
@@ -471,7 +486,11 @@ void addReplyBulkLen(redisClient *c, robj *obj) {
|
||||
len++;
|
||||
}
|
||||
}
|
||||
addReplyLongLongWithPrefix(c,len,'$');
|
||||
|
||||
if (len < REDIS_SHARED_BULKHDR_LEN)
|
||||
addReply(c,shared.bulkhdr[len]);
|
||||
else
|
||||
addReplyLongLongWithPrefix(c,len,'$');
|
||||
}
|
||||
|
||||
/* Add a Redis Object as a bulk reply */
|
||||
@@ -547,12 +566,12 @@ static void acceptCommonHandler(int fd, int flags) {
|
||||
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cport, cfd;
|
||||
char cip[128];
|
||||
char cip[REDIS_IP_STR_LEN];
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, &cport);
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == AE_ERR) {
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
@@ -595,12 +614,42 @@ 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
|
||||
@@ -620,16 +669,21 @@ void freeClient(redisClient *c) {
|
||||
pubsubUnsubscribeAllPatterns(c,0);
|
||||
dictRelease(c->pubsub_channels);
|
||||
listRelease(c->pubsub_patterns);
|
||||
/* Obvious cleanup */
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
||||
/* 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);
|
||||
}
|
||||
listRelease(c->reply);
|
||||
freeClientArgv(c);
|
||||
close(c->fd);
|
||||
/* Remove from the list of clients */
|
||||
ln = listSearchKey(server.clients,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(server.clients,ln);
|
||||
if (c->fd != -1) {
|
||||
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) {
|
||||
@@ -647,20 +701,16 @@ 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) {
|
||||
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 (c->flags & REDIS_MASTER) replicationHandleMasterDisconnection();
|
||||
|
||||
/* If this client was scheduled for async freeing we need to remove it
|
||||
* from the queue. */
|
||||
@@ -708,13 +758,8 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
|
||||
while(c->bufpos > 0 || listLength(c->reply)) {
|
||||
if (c->bufpos > 0) {
|
||||
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;
|
||||
}
|
||||
nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
|
||||
if (nwritten <= 0) break;
|
||||
c->sentlen += nwritten;
|
||||
totwritten += nwritten;
|
||||
|
||||
@@ -734,13 +779,8 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen);
|
||||
if (nwritten <= 0) break;
|
||||
c->sentlen += nwritten;
|
||||
totwritten += nwritten;
|
||||
|
||||
@@ -773,7 +813,13 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (totwritten > 0) c->lastinteraction = server.unixtime;
|
||||
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 (c->bufpos == 0 && listLength(c->reply) == 0) {
|
||||
c->sentlen = 0;
|
||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
||||
@@ -796,7 +842,7 @@ void resetClient(redisClient *c) {
|
||||
int processInlineBuffer(redisClient *c) {
|
||||
char *newline = strstr(c->querybuf,"\r\n");
|
||||
int argc, j;
|
||||
sds *argv;
|
||||
sds *argv, aux;
|
||||
size_t querylen;
|
||||
|
||||
/* Nothing to do without a \r\n */
|
||||
@@ -810,10 +856,12 @@ int processInlineBuffer(redisClient *c) {
|
||||
|
||||
/* Split the input buffer up to the \r\n */
|
||||
querylen = newline-(c->querybuf);
|
||||
argv = sdssplitlen(c->querybuf,querylen," ",1,&argc);
|
||||
aux = sdsnewlen(c->querybuf,querylen);
|
||||
argv = sdssplitargs(aux,&argc);
|
||||
sdsfree(aux);
|
||||
|
||||
/* Leave data after the first line of the query in the buffer */
|
||||
c->querybuf = sdsrange(c->querybuf,querylen+2,-1);
|
||||
sdsrange(c->querybuf,querylen+2,-1);
|
||||
|
||||
/* Setup argv array on client structure */
|
||||
if (c->argv) zfree(c->argv);
|
||||
@@ -842,7 +890,7 @@ static void setProtocolError(redisClient *c, int pos) {
|
||||
sdsfree(client);
|
||||
}
|
||||
c->flags |= REDIS_CLOSE_AFTER_REPLY;
|
||||
c->querybuf = sdsrange(c->querybuf,pos,-1);
|
||||
sdsrange(c->querybuf,pos,-1);
|
||||
}
|
||||
|
||||
int processMultibulkBuffer(redisClient *c) {
|
||||
@@ -880,7 +928,7 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
|
||||
pos = (newline-c->querybuf)+2;
|
||||
if (ll <= 0) {
|
||||
c->querybuf = sdsrange(c->querybuf,pos,-1);
|
||||
sdsrange(c->querybuf,pos,-1);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -925,15 +973,19 @@ 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 optimized object creation
|
||||
* boundary so that we can optimize object creation
|
||||
* avoiding a large copy of data. */
|
||||
c->querybuf = sdsrange(c->querybuf,pos,-1);
|
||||
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. */
|
||||
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2);
|
||||
if (qblen < ll+2)
|
||||
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2-qblen);
|
||||
}
|
||||
c->bulklen = ll;
|
||||
}
|
||||
@@ -968,7 +1020,7 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
}
|
||||
|
||||
/* Trim to pos */
|
||||
if (pos) c->querybuf = sdsrange(c->querybuf,pos,-1);
|
||||
if (pos) sdsrange(c->querybuf,pos,-1);
|
||||
|
||||
/* We're done when c->multibulk == 0 */
|
||||
if (c->multibulklen == 0) return REDIS_OK;
|
||||
@@ -1059,6 +1111,7 @@ 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;
|
||||
@@ -1095,14 +1148,52 @@ 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 ip[32], flags[16], events[3], *p;
|
||||
int port = 0; /* initialized to zero for the unix socket case. */
|
||||
char peerid[REDIS_PEER_ID_LEN], flags[16], events[3], *p;
|
||||
int emask;
|
||||
|
||||
if (!(client->flags & REDIS_UNIX_SOCKET))
|
||||
anetPeerToString(client->fd,ip,&port);
|
||||
getClientPeerId(client,peerid,sizeof(peerid));
|
||||
p = flags;
|
||||
if (client->flags & REDIS_SLAVE) {
|
||||
if (client->flags & REDIS_MONITOR)
|
||||
@@ -1127,9 +1218,8 @@ sds getClientInfoString(redisClient *client) {
|
||||
if (emask & AE_WRITABLE) *p++ = 'w';
|
||||
*p = '\0';
|
||||
return sdscatprintf(sdsempty(),
|
||||
"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,
|
||||
"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,
|
||||
client->fd,
|
||||
client->name ? (char*)client->name->ptr : "",
|
||||
(long)(server.unixtime - client->ctime),
|
||||
@@ -1179,13 +1269,12 @@ 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 ip[32], addr[64];
|
||||
int port;
|
||||
char peerid[REDIS_PEER_ID_LEN];
|
||||
|
||||
client = listNodeValue(ln);
|
||||
if (anetPeerToString(client->fd,ip,&port) == -1) continue;
|
||||
snprintf(addr,sizeof(addr),"%s:%d",ip,port);
|
||||
if (strcmp(addr,c->argv[2]->ptr) == 0) {
|
||||
if (getClientPeerId(client,peerid,sizeof(peerid)) == REDIS_ERR)
|
||||
continue;
|
||||
if (strcmp(peerid,c->argv[2]->ptr) == 0) {
|
||||
addReply(c,shared.ok);
|
||||
if (c == client) {
|
||||
client->flags |= REDIS_CLOSE_AFTER_REPLY;
|
||||
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
+70
-18
@@ -97,7 +97,7 @@ robj *dupStringObject(robj *o) {
|
||||
robj *createListObject(void) {
|
||||
list *l = listCreate();
|
||||
robj *o = createObject(REDIS_LIST,l);
|
||||
listSetFreeMethod(l,decrRefCount);
|
||||
listSetFreeMethod(l,decrRefCountVoid);
|
||||
o->encoding = REDIS_ENCODING_LINKEDLIST;
|
||||
return o;
|
||||
}
|
||||
@@ -215,9 +215,7 @@ void incrRefCount(robj *o) {
|
||||
o->refcount++;
|
||||
}
|
||||
|
||||
void decrRefCount(void *obj) {
|
||||
robj *o = obj;
|
||||
|
||||
void decrRefCount(robj *o) {
|
||||
if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
|
||||
if (o->refcount == 1) {
|
||||
switch(o->type) {
|
||||
@@ -234,6 +232,13 @@ void decrRefCount(void *obj) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 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:
|
||||
@@ -273,6 +278,7 @@ 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 */
|
||||
@@ -286,7 +292,27 @@ robj *tryObjectEncoding(robj *o) {
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
|
||||
/* Check if we can represent this string as a long integer */
|
||||
if (!string2l(s,sdslen(s),&value)) return o;
|
||||
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;
|
||||
}
|
||||
|
||||
/* Ok, this object can be encoded...
|
||||
*
|
||||
@@ -327,35 +353,57 @@ robj *getDecodedObject(robj *o) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Compare two string objects via strcmp() or alike.
|
||||
/* Compare two string objects via strcmp() or strcoll() depending on flags.
|
||||
* 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: 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) {
|
||||
* 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) {
|
||||
redisAssertWithInfo(NULL,a,a->type == REDIS_STRING && b->type == REDIS_STRING);
|
||||
char bufa[128], bufb[128], *astr, *bstr;
|
||||
int bothsds = 1;
|
||||
size_t alen, blen, minlen;
|
||||
|
||||
if (a == b) return 0;
|
||||
if (a->encoding != REDIS_ENCODING_RAW) {
|
||||
ll2string(bufa,sizeof(bufa),(long) a->ptr);
|
||||
alen = ll2string(bufa,sizeof(bufa),(long) a->ptr);
|
||||
astr = bufa;
|
||||
bothsds = 0;
|
||||
} else {
|
||||
astr = a->ptr;
|
||||
alen = sdslen(astr);
|
||||
}
|
||||
if (b->encoding != REDIS_ENCODING_RAW) {
|
||||
ll2string(bufb,sizeof(bufb),(long) b->ptr);
|
||||
blen = ll2string(bufb,sizeof(bufb),(long) b->ptr);
|
||||
bstr = bufb;
|
||||
bothsds = 0;
|
||||
} else {
|
||||
bstr = b->ptr;
|
||||
blen = sdslen(bstr);
|
||||
}
|
||||
return bothsds ? sdscmp(astr,bstr) : strcmp(astr,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);
|
||||
}
|
||||
|
||||
/* Equal string objects return 1 if the two objects are the same from the
|
||||
@@ -392,8 +440,12 @@ 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 || isnan(value))
|
||||
if (isspace(((char*)o->ptr)[0]) ||
|
||||
eptr[0] != '\0' ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
errno == EINVAL ||
|
||||
isnan(value))
|
||||
return REDIS_ERR;
|
||||
} else if (o->encoding == REDIS_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
|
||||
+1
-13
@@ -39,7 +39,6 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -102,10 +101,9 @@ _pqsort(void *a, size_t n, size_t es,
|
||||
{
|
||||
char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
|
||||
size_t d, r;
|
||||
int swaptype, swap_cnt, cmp_result;
|
||||
int swaptype, 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;
|
||||
@@ -132,7 +130,6 @@ 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;
|
||||
}
|
||||
@@ -140,7 +137,6 @@ 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;
|
||||
}
|
||||
@@ -149,17 +145,9 @@ 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,5 +306,53 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -722,21 +722,22 @@ int rdbSaveBackground(char *filename) {
|
||||
if (server.rdb_child_pid != -1) return REDIS_ERR;
|
||||
|
||||
server.dirty_before_bgsave = server.dirty;
|
||||
server.lastbgsave_try = time(NULL);
|
||||
|
||||
start = ustime();
|
||||
if ((childpid = fork()) == 0) {
|
||||
int retval;
|
||||
|
||||
/* Child */
|
||||
if (server.ipfd > 0) close(server.ipfd);
|
||||
if (server.sofd > 0) close(server.sofd);
|
||||
closeListeningSockets(0);
|
||||
redisSetProcTitle("redis-rdb-bgsave");
|
||||
retval = rdbSave(filename);
|
||||
if (retval == REDIS_OK) {
|
||||
size_t private_dirty = zmalloc_get_private_dirty();
|
||||
|
||||
if (private_dirty) {
|
||||
redisLog(REDIS_NOTICE,
|
||||
"RDB: %lu MB of memory used by copy-on-write",
|
||||
"RDB: %zu MB of memory used by copy-on-write",
|
||||
private_dirty/(1024*1024));
|
||||
}
|
||||
}
|
||||
@@ -745,6 +746,7 @@ 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;
|
||||
@@ -772,7 +774,6 @@ 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;
|
||||
@@ -1057,24 +1058,32 @@ 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;
|
||||
|
||||
fp = fopen(filename,"r");
|
||||
if (!fp) {
|
||||
errno = ENOENT;
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if ((fp = fopen(filename,"r")) == NULL) return REDIS_ERR;
|
||||
|
||||
rioInitWithFile(&rdb,fp);
|
||||
if (server.rdb_checksum)
|
||||
rdb.update_cksum = rioGenericUpdateChecksum;
|
||||
rdb.update_cksum = rdbLoadProgressCallback;
|
||||
rdb.max_processing_chunk = server.loading_process_events_interval_bytes;
|
||||
if (rioRead(&rdb,buf,9) == 0) goto eoferr;
|
||||
buf[9] = '\0';
|
||||
if (memcmp(buf,"REDIS",5) != 0) {
|
||||
@@ -1096,12 +1105,6 @@ 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) {
|
||||
|
||||
+146
-32
@@ -35,6 +35,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <signal.h>
|
||||
#include <assert.h>
|
||||
@@ -46,6 +47,7 @@
|
||||
#include "zmalloc.h"
|
||||
|
||||
#define REDIS_NOTUSED(V) ((void) V)
|
||||
#define RANDPTR_INITIAL_SIZE 8
|
||||
|
||||
static struct config {
|
||||
aeEventLoop *el;
|
||||
@@ -72,18 +74,24 @@ 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[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) */
|
||||
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. */
|
||||
} *client;
|
||||
|
||||
/* Prototypes */
|
||||
@@ -117,6 +125,7 @@ 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);
|
||||
@@ -143,13 +152,18 @@ static void resetClient(client c) {
|
||||
}
|
||||
|
||||
static void randomizeClientKey(client c) {
|
||||
char buf[32];
|
||||
size_t i, r;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < c->randlen; i++) {
|
||||
r = random() % config.randomkeys_keyspacelen;
|
||||
snprintf(buf,sizeof(buf),"%012zu",r);
|
||||
memcpy(c->randptr[i],buf,12);
|
||||
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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +212,21 @@ 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--;
|
||||
@@ -249,7 +278,29 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
}
|
||||
|
||||
static client createClient(char *cmd, size_t len) {
|
||||
/* 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) {
|
||||
int j;
|
||||
client c = zmalloc(sizeof(struct _client));
|
||||
|
||||
@@ -268,25 +319,68 @@ static client createClient(char *cmd, size_t len) {
|
||||
}
|
||||
/* Suppress hiredis cleanup of unused buffers for max speed. */
|
||||
c->context->reader->maxbuf = 0;
|
||||
/* Queue N requests accordingly to the pipeline size. */
|
||||
|
||||
/* Build the request buffer:
|
||||
* Queue N requests accordingly to the pipeline size, or simply clone
|
||||
* the example client buffer. */
|
||||
c->obuf = sdsempty();
|
||||
for (j = 0; j < config.pipeline; j++)
|
||||
c->obuf = sdscatlen(c->obuf,cmd,len);
|
||||
c->randlen = 0;
|
||||
|
||||
/* 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);
|
||||
}
|
||||
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) {
|
||||
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;
|
||||
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__). */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* redisSetReplyObjectFunctions(c->context,NULL); */
|
||||
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
|
||||
listAddNodeTail(config.clients,c);
|
||||
config.liveclients++;
|
||||
@@ -295,9 +389,18 @@ static client createClient(char *cmd, size_t len) {
|
||||
|
||||
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(c->obuf,sdslen(c->obuf)/config.pipeline);
|
||||
createClient(NULL,0,c);
|
||||
|
||||
/* Listen backlog is quite limited on most systems */
|
||||
if (++n > 64) {
|
||||
@@ -348,7 +451,7 @@ static void benchmark(char *title, char *cmd, int len) {
|
||||
config.requests_issued = 0;
|
||||
config.requests_finished = 0;
|
||||
|
||||
c = createClient(cmd,len);
|
||||
c = createClient(cmd,len,NULL);
|
||||
createMissingClients(c);
|
||||
|
||||
config.start = mstime();
|
||||
@@ -420,6 +523,10 @@ 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;
|
||||
@@ -446,6 +553,7 @@ 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 get/set keys\n"
|
||||
@@ -470,8 +578,12 @@ 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 ele:rand:000000000000\n\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"
|
||||
);
|
||||
exit(exit_status);
|
||||
}
|
||||
@@ -510,6 +622,7 @@ int main(int argc, const char **argv) {
|
||||
|
||||
client c;
|
||||
|
||||
srandom(time(NULL));
|
||||
signal(SIGHUP, SIG_IGN);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
@@ -533,6 +646,7 @@ 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;
|
||||
@@ -546,7 +660,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); /* will never receive a reply */
|
||||
c = createClient("",0,NULL); /* will never receive a reply */
|
||||
createMissingClients(c);
|
||||
aeMain(config.el);
|
||||
/* and will wait for every */
|
||||
@@ -585,19 +699,19 @@ int main(int argc, const char **argv) {
|
||||
}
|
||||
|
||||
if (test_is_selected("set")) {
|
||||
len = redisFormatCommand(&cmd,"SET foo:rand:000000000000 %s",data);
|
||||
len = redisFormatCommand(&cmd,"SET key:__rand_int__ %s",data);
|
||||
benchmark("SET",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("get")) {
|
||||
len = redisFormatCommand(&cmd,"GET foo:rand:000000000000");
|
||||
len = redisFormatCommand(&cmd,"GET key:__rand_int__");
|
||||
benchmark("GET",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("incr")) {
|
||||
len = redisFormatCommand(&cmd,"INCR counter:rand:000000000000");
|
||||
len = redisFormatCommand(&cmd,"INCR counter:__rand_int__");
|
||||
benchmark("INCR",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
@@ -616,7 +730,7 @@ int main(int argc, const char **argv) {
|
||||
|
||||
if (test_is_selected("sadd")) {
|
||||
len = redisFormatCommand(&cmd,
|
||||
"SADD myset counter:rand:000000000000");
|
||||
"SADD myset element:__rand_int__");
|
||||
benchmark("SADD",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
@@ -666,7 +780,7 @@ int main(int argc, const char **argv) {
|
||||
const char *argv[21];
|
||||
argv[0] = "MSET";
|
||||
for (i = 1; i < 21; i += 2) {
|
||||
argv[i] = "foo:rand:000000000000";
|
||||
argv[i] = "key:__rand_int__";
|
||||
argv[i+1] = data;
|
||||
}
|
||||
len = redisFormatCommandArgv(&cmd,21,argv,NULL);
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include "lzf.h"
|
||||
#include "crc64.h"
|
||||
|
||||
/* Object types */
|
||||
#define REDIS_STRING 0
|
||||
@@ -140,9 +141,6 @@ 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
|
||||
|
||||
+269
-28
@@ -42,6 +42,7 @@
|
||||
#include <sys/time.h>
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "sds.h"
|
||||
@@ -57,6 +58,7 @@
|
||||
#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 {
|
||||
@@ -71,11 +73,14 @@ static struct config {
|
||||
int monitor_mode;
|
||||
int pubsub_mode;
|
||||
int latency_mode;
|
||||
int latency_history;
|
||||
int cluster_mode;
|
||||
int cluster_reissue_command;
|
||||
int slave_mode;
|
||||
int pipe_mode;
|
||||
int pipe_timeout;
|
||||
int getrdb_mode;
|
||||
int stat_mode;
|
||||
char *rdb_filename;
|
||||
int bigkeys;
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
@@ -111,7 +116,8 @@ static void cliRefreshPrompt(void) {
|
||||
len = snprintf(config.prompt,sizeof(config.prompt),"redis %s",
|
||||
config.hostsocket);
|
||||
else
|
||||
len = snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d",
|
||||
len = snprintf(config.prompt,sizeof(config.prompt),
|
||||
strchr(config.hostip,':') ? "[%s]:%d" : "%s:%d",
|
||||
config.hostip, config.hostport);
|
||||
/* Add [dbnum] if needed */
|
||||
if (config.dbnum != 0)
|
||||
@@ -630,6 +636,36 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Send the INFO command, reconnecting the link if needed. */
|
||||
static redisReply *reconnectingInfo(void) {
|
||||
redisContext *c = context;
|
||||
redisReply *reply = NULL;
|
||||
int tries = 0;
|
||||
|
||||
assert(!c->err);
|
||||
while(reply == NULL) {
|
||||
while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
|
||||
printf("Reconnecting (%d)...\r", ++tries);
|
||||
fflush(stdout);
|
||||
|
||||
redisFree(c);
|
||||
c = redisConnect(config.hostip,config.hostport);
|
||||
usleep(1000000);
|
||||
}
|
||||
|
||||
reply = redisCommand(c,"INFO");
|
||||
if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
|
||||
fprintf(stderr, "Error: %s\n", c->errstr);
|
||||
exit(1);
|
||||
} else if (tries > 0) {
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
context = c;
|
||||
return reply;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* User interface
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -668,13 +704,20 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.output = OUTPUT_CSV;
|
||||
} else if (!strcmp(argv[i],"--latency")) {
|
||||
config.latency_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--latency-history")) {
|
||||
config.latency_mode = 1;
|
||||
config.latency_history = 1;
|
||||
} else if (!strcmp(argv[i],"--slave")) {
|
||||
config.slave_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--stat")) {
|
||||
config.stat_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--rdb") && !lastarg) {
|
||||
config.getrdb_mode = 1;
|
||||
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) {
|
||||
@@ -690,7 +733,15 @@ static int parseOptions(int argc, char **argv) {
|
||||
sdsfree(version);
|
||||
exit(0);
|
||||
} else {
|
||||
break;
|
||||
if (argv[i][0] == '-') {
|
||||
fprintf(stderr,
|
||||
"Unrecognized option or bad number of args for: '%s'\n",
|
||||
argv[i]);
|
||||
exit(1);
|
||||
} else {
|
||||
/* Likely the command name, stop here. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return i;
|
||||
@@ -719,26 +770,33 @@ 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 not a tty)\n"
|
||||
" --latency Enter a special mode continuously sampling latency\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"
|
||||
" -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"
|
||||
"\n"
|
||||
"Examples:\n"
|
||||
" cat /etc/passwd | redis-cli -x set mypasswd\n"
|
||||
@@ -751,7 +809,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);
|
||||
version, REDIS_DEFAULT_PIPE_TIMEOUT);
|
||||
sdsfree(version);
|
||||
exit(1);
|
||||
}
|
||||
@@ -909,10 +967,16 @@ static int evalMode(int argc, char **argv) {
|
||||
return cliSendCommand(argc+3-got_comma, argv2, config.repeat);
|
||||
}
|
||||
|
||||
#define LATENCY_SAMPLE_RATE 10 /* milliseconds. */
|
||||
#define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */
|
||||
static void latencyMode(void) {
|
||||
redisReply *reply;
|
||||
long long start, latency, min = 0, max = 0, tot = 0, count = 0;
|
||||
long long history_interval =
|
||||
config.interval ? config.interval/1000 :
|
||||
LATENCY_HISTORY_DEFAULT_INTERVAL;
|
||||
double avg;
|
||||
long long history_start = mstime();
|
||||
|
||||
if (!context) exit(1);
|
||||
while(1) {
|
||||
@@ -937,7 +1001,13 @@ static void latencyMode(void) {
|
||||
printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)",
|
||||
min, max, avg, count);
|
||||
fflush(stdout);
|
||||
usleep(10000);
|
||||
if (config.latency_history && mstime()-history_start > history_interval)
|
||||
{
|
||||
printf(" -- %.2f seconds range\n", (float)(mstime()-history_start)/1000);
|
||||
history_start = mstime();
|
||||
min = max = tot = count = 0;
|
||||
}
|
||||
usleep(LATENCY_SAMPLE_RATE * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1057,6 +1127,7 @@ 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));
|
||||
|
||||
@@ -1087,7 +1158,10 @@ static void pipeMode(void) {
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
if (nread > 0) redisReaderFeed(reader,ibuf,nread);
|
||||
if (nread > 0) {
|
||||
redisReaderFeed(reader,ibuf,nread);
|
||||
last_read_time = time(NULL);
|
||||
}
|
||||
} while(nread > 0);
|
||||
|
||||
/* Consume replies. */
|
||||
@@ -1142,8 +1216,12 @@ 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[] =
|
||||
"*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n";
|
||||
"\r\n*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n";
|
||||
int j;
|
||||
|
||||
eof = 1;
|
||||
@@ -1152,7 +1230,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+19,magic,20);
|
||||
memcpy(echo+21,magic,20);
|
||||
memcpy(obuf,echo,sizeof(echo)-1);
|
||||
obuf_len = sizeof(echo)-1;
|
||||
obuf_pos = 0;
|
||||
@@ -1169,6 +1247,18 @@ 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);
|
||||
@@ -1205,7 +1295,11 @@ static void findBigKeys(void) {
|
||||
fprintf(stderr, "RANDOMKEY error: %s\n",
|
||||
reply1->str);
|
||||
exit(1);
|
||||
} else if (reply1->type == REDIS_REPLY_NIL) {
|
||||
fprintf(stderr, "It looks like the database is empty!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Get the key type */
|
||||
reply2 = redisCommand(context,"TYPE %s",reply1->str);
|
||||
assert(reply2 && reply2->type == REDIS_REPLY_STATUS);
|
||||
@@ -1230,7 +1324,6 @@ 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",
|
||||
@@ -1261,6 +1354,145 @@ static void findBigKeys(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the specified INFO field from the INFO command output "info".
|
||||
* A new buffer is allocated for the result, that needs to be free'd.
|
||||
* If the field is not found NULL is returned. */
|
||||
static char *getInfoField(char *info, char *field) {
|
||||
char *p = strstr(info,field);
|
||||
char *n1, *n2;
|
||||
char *result;
|
||||
|
||||
if (!p) return NULL;
|
||||
p += strlen(field)+1;
|
||||
n1 = strchr(p,'\r');
|
||||
n2 = strchr(p,',');
|
||||
if (n2 && n2 < n1) n1 = n2;
|
||||
result = malloc(sizeof(char)*(n1-p)+1);
|
||||
memcpy(result,p,(n1-p));
|
||||
result[n1-p] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Like the above function but automatically convert the result into
|
||||
* a long. On error (missing field) LONG_MIN is returned. */
|
||||
static long getLongInfoField(char *info, char *field) {
|
||||
char *value = getInfoField(info,field);
|
||||
long l;
|
||||
|
||||
if (!value) return LONG_MIN;
|
||||
l = strtol(value,NULL,10);
|
||||
free(value);
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Convert number of bytes into a human readable string of the form:
|
||||
* 100B, 2G, 100M, 4K, and so forth. */
|
||||
void bytesToHuman(char *s, long long n) {
|
||||
double d;
|
||||
|
||||
if (n < 0) {
|
||||
*s = '-';
|
||||
s++;
|
||||
n = -n;
|
||||
}
|
||||
if (n < 1024) {
|
||||
/* Bytes */
|
||||
sprintf(s,"%lluB",n);
|
||||
return;
|
||||
} else if (n < (1024*1024)) {
|
||||
d = (double)n/(1024);
|
||||
sprintf(s,"%.2fK",d);
|
||||
} else if (n < (1024LL*1024*1024)) {
|
||||
d = (double)n/(1024*1024);
|
||||
sprintf(s,"%.2fM",d);
|
||||
} else if (n < (1024LL*1024*1024*1024)) {
|
||||
d = (double)n/(1024LL*1024*1024);
|
||||
sprintf(s,"%.2fG",d);
|
||||
}
|
||||
}
|
||||
|
||||
static void statMode() {
|
||||
redisReply *reply;
|
||||
long aux, requests = 0;
|
||||
int i = 0;
|
||||
|
||||
while(1) {
|
||||
char buf[64];
|
||||
int j;
|
||||
|
||||
reply = reconnectingInfo();
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
printf("ERROR: %s\n", reply->str);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((i++ % 20) == 0) {
|
||||
printf(
|
||||
"------- data ------ --------------------- load -------------------- - child -\n"
|
||||
"keys mem clients blocked requests connections \n");
|
||||
}
|
||||
|
||||
/* Keys */
|
||||
aux = 0;
|
||||
for (j = 0; j < 20; j++) {
|
||||
long k;
|
||||
|
||||
sprintf(buf,"db%d:keys",j);
|
||||
k = getLongInfoField(reply->str,buf);
|
||||
if (k == LONG_MIN) continue;
|
||||
aux += k;
|
||||
}
|
||||
sprintf(buf,"%ld",aux);
|
||||
printf("%-11s",buf);
|
||||
|
||||
/* Used memory */
|
||||
aux = getLongInfoField(reply->str,"used_memory");
|
||||
bytesToHuman(buf,aux);
|
||||
printf("%-8s",buf);
|
||||
|
||||
/* Clients */
|
||||
aux = getLongInfoField(reply->str,"connected_clients");
|
||||
sprintf(buf,"%ld",aux);
|
||||
printf(" %-8s",buf);
|
||||
|
||||
/* Blocked (BLPOPPING) Clients */
|
||||
aux = getLongInfoField(reply->str,"blocked_clients");
|
||||
sprintf(buf,"%ld",aux);
|
||||
printf("%-8s",buf);
|
||||
|
||||
/* Requets */
|
||||
aux = getLongInfoField(reply->str,"total_commands_processed");
|
||||
sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests);
|
||||
printf("%-19s",buf);
|
||||
requests = aux;
|
||||
|
||||
/* Connections */
|
||||
aux = getLongInfoField(reply->str,"total_connections_received");
|
||||
sprintf(buf,"%ld",aux);
|
||||
printf(" %-12s",buf);
|
||||
|
||||
/* Children */
|
||||
aux = getLongInfoField(reply->str,"bgsave_in_progress");
|
||||
aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1;
|
||||
switch(aux) {
|
||||
case 0: break;
|
||||
case 1:
|
||||
printf("SAVE");
|
||||
break;
|
||||
case 2:
|
||||
printf("AOF");
|
||||
break;
|
||||
case 3:
|
||||
printf("SAVE+AOF");
|
||||
break;
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
freeReplyObject(reply);
|
||||
usleep(config.interval);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int firstarg;
|
||||
|
||||
@@ -1275,11 +1507,13 @@ int main(int argc, char **argv) {
|
||||
config.monitor_mode = 0;
|
||||
config.pubsub_mode = 0;
|
||||
config.latency_mode = 0;
|
||||
config.latency_history = 0;
|
||||
config.cluster_mode = 0;
|
||||
config.slave_mode = 0;
|
||||
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;
|
||||
@@ -1325,6 +1559,13 @@ int main(int argc, char **argv) {
|
||||
findBigKeys();
|
||||
}
|
||||
|
||||
/* Stat mode */
|
||||
if (config.stat_mode) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
if (config.interval == 0) config.interval = 1000000;
|
||||
statMode();
|
||||
}
|
||||
|
||||
/* Start interactive mode when no command is provided */
|
||||
if (argc == 0 && !config.eval) {
|
||||
/* Note that in repl mode we don't abort on connection error.
|
||||
|
||||
+450
-142
File diff suppressed because it is too large
Load Diff
+160
-23
@@ -74,8 +74,6 @@
|
||||
#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
|
||||
@@ -94,6 +92,41 @@
|
||||
#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. */
|
||||
@@ -101,6 +134,12 @@
|
||||
#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% */
|
||||
@@ -110,7 +149,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_FORCE_REPLICATION 8 /* "f" flag */
|
||||
#define REDIS_CMD_NOT_USED_1 8 /* no longer used flag */
|
||||
#define REDIS_CMD_ADMIN 16 /* "a" flag */
|
||||
#define REDIS_CMD_PUBSUB 32 /* "p" flag */
|
||||
#define REDIS_CMD_NOSCRIPT 64 /* "s" flag */
|
||||
@@ -186,6 +225,10 @@
|
||||
#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
|
||||
@@ -198,7 +241,7 @@
|
||||
#define REDIS_CLIENT_LIMIT_CLASS_PUBSUB 2
|
||||
#define REDIS_CLIENT_LIMIT_NUM_CLASSES 3
|
||||
|
||||
/* Slave replication state - slave side */
|
||||
/* Slave replication state - from the point of view of the slave. */
|
||||
#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 */
|
||||
@@ -206,17 +249,17 @@
|
||||
#define REDIS_REPL_TRANSFER 4 /* Receiving .rdb from master */
|
||||
#define REDIS_REPL_CONNECTED 5 /* Connected to master */
|
||||
|
||||
/* 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
|
||||
/* 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 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 */
|
||||
#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
|
||||
|
||||
/* List related stuff */
|
||||
#define REDIS_HEAD 0
|
||||
@@ -234,6 +277,7 @@
|
||||
#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)
|
||||
@@ -245,6 +289,7 @@
|
||||
#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
|
||||
@@ -267,6 +312,7 @@
|
||||
#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 */
|
||||
@@ -292,6 +338,20 @@
|
||||
#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. */
|
||||
@@ -338,6 +398,7 @@ 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 */
|
||||
@@ -350,13 +411,15 @@ 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;
|
||||
@@ -394,17 +457,21 @@ 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;
|
||||
int sentlen; /* Amount of bytes already sent in the current
|
||||
buffer or object being sent. */
|
||||
time_t ctime; /* Client creation time */
|
||||
time_t lastinteraction; /* time of the last interaction, used for timeout */
|
||||
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 */
|
||||
@@ -429,7 +496,7 @@ struct sharedObjectsStruct {
|
||||
*colon, *nullbulk, *nullmultibulk, *queued,
|
||||
*emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
|
||||
*outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
|
||||
*masterdownerr, *roslaveerr, *execaborterr,
|
||||
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
|
||||
*oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop,
|
||||
*lpush,
|
||||
@@ -467,6 +534,8 @@ 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.
|
||||
@@ -497,6 +566,7 @@ 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 */
|
||||
@@ -514,10 +584,12 @@ struct redisServer {
|
||||
int sentinel_mode; /* True if this instance is a Sentinel. */
|
||||
/* Networking */
|
||||
int port; /* TCP listening port */
|
||||
char *bindaddr; /* Bind address or NULL */
|
||||
char *bindaddr[REDIS_BINDADDR_MAX]; /* Addresses we should bind to */
|
||||
int bindaddr_count; /* Number of addresses in server.bindaddr[] */
|
||||
char *unixsocket; /* UNIX socket path */
|
||||
mode_t unixsocketperm; /* UNIX socket permission */
|
||||
int ipfd; /* TCP socket file descriptor */
|
||||
int ipfd[REDIS_BINDADDR_MAX]; /* TCP socket file descriptors */
|
||||
int ipfd_count; /* Used slots in ipfd[] */
|
||||
int sofd; /* Unix socket file descriptor */
|
||||
list *clients; /* List of active clients */
|
||||
list *clients_to_close; /* Clients to close asynchronously */
|
||||
@@ -529,6 +601,7 @@ 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;
|
||||
@@ -543,6 +616,9 @@ 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) */
|
||||
@@ -557,6 +633,7 @@ struct redisServer {
|
||||
int verbosity; /* Loglevel in redis.conf */
|
||||
int maxidletime; /* Client timeout in seconds */
|
||||
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
||||
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
||||
size_t client_max_querybuf_len; /* Limit for client query buffer length */
|
||||
int dbnum; /* Total number of configured DBs */
|
||||
int daemonize; /* True if running as a daemon */
|
||||
@@ -582,6 +659,7 @@ struct redisServer {
|
||||
time_t aof_rewrite_time_start; /* Current AOF rewrite start time. */
|
||||
int aof_lastbgrewrite_status; /* REDIS_OK or REDIS_ERR */
|
||||
unsigned long aof_delayed_fsync; /* delayed AOF fsync() counter */
|
||||
int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
|
||||
/* RDB persistence */
|
||||
long long dirty; /* Changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
|
||||
@@ -592,6 +670,7 @@ struct redisServer {
|
||||
int rdb_compression; /* Use compression in RDB? */
|
||||
int rdb_checksum; /* Use RDB checksum? */
|
||||
time_t lastsave; /* Unix time of last successful save */
|
||||
time_t lastbgsave_try; /* Unix time of last attempted bgsave */
|
||||
time_t rdb_save_time_last; /* Time used by last RDB save run. */
|
||||
time_t rdb_save_time_start; /* Current RDB save start time. */
|
||||
int lastbgsave_status; /* REDIS_OK or REDIS_ERR */
|
||||
@@ -603,13 +682,30 @@ struct redisServer {
|
||||
int syslog_enabled; /* Is syslog enabled? */
|
||||
char *syslog_ident; /* Syslog ident */
|
||||
int syslog_facility; /* Syslog facility */
|
||||
/* Slave specific fields */
|
||||
/* 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) */
|
||||
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. */
|
||||
@@ -624,6 +720,12 @@ 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 */
|
||||
@@ -638,6 +740,7 @@ 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;
|
||||
@@ -646,10 +749,13 @@ 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 second. */
|
||||
time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
|
||||
/* 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 */
|
||||
@@ -765,6 +871,7 @@ extern dictType dbDictType;
|
||||
extern dictType shaScriptObjectDictType;
|
||||
extern double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
extern dictType hashDictType;
|
||||
extern dictType replScriptCacheDictType;
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Functions prototypes
|
||||
@@ -776,11 +883,14 @@ 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);
|
||||
@@ -807,6 +917,8 @@ 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, ...);
|
||||
@@ -857,7 +969,8 @@ void discardTransaction(redisClient *c);
|
||||
void flagTransaction(redisClient *c);
|
||||
|
||||
/* Redis object implementation */
|
||||
void decrRefCount(void *o);
|
||||
void decrRefCount(robj *o);
|
||||
void decrRefCountVoid(void *o);
|
||||
void incrRefCount(robj *o);
|
||||
robj *resetRefCount(robj *obj);
|
||||
void freeStringObject(robj *o);
|
||||
@@ -890,6 +1003,7 @@ 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);
|
||||
|
||||
@@ -903,6 +1017,14 @@ 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);
|
||||
@@ -954,8 +1076,14 @@ 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();
|
||||
@@ -964,6 +1092,8 @@ 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);
|
||||
@@ -1005,6 +1135,11 @@ 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);
|
||||
@@ -1054,6 +1189,7 @@ void scriptingInit(void);
|
||||
/* Git SHA1 */
|
||||
char *redisGitSHA1(void);
|
||||
char *redisGitDirty(void);
|
||||
uint64_t redisBuildId(void);
|
||||
|
||||
/* Commands prototypes */
|
||||
void authCommand(redisClient *c);
|
||||
@@ -1177,6 +1313,7 @@ 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);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/* 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,7 +31,11 @@
|
||||
* 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;
|
||||
@@ -40,3 +44,9 @@ 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));
|
||||
}
|
||||
|
||||
+821
-58
File diff suppressed because it is too large
Load Diff
@@ -48,10 +48,12 @@
|
||||
#include "fmacros.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include "rio.h"
|
||||
#include "util.h"
|
||||
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
#include "crc64.h"
|
||||
#include "config.h"
|
||||
#include "redis.h"
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioBufferWrite(rio *r, const void *buf, size_t len) {
|
||||
@@ -76,7 +78,18 @@ static off_t rioBufferTell(rio *r) {
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
|
||||
return fwrite(buf,len,1,r->io.file.fp);
|
||||
size_t retval;
|
||||
|
||||
retval = fwrite(buf,len,1,r->io.file.fp);
|
||||
r->io.file.buffered += len;
|
||||
|
||||
if (r->io.file.autosync &&
|
||||
r->io.file.buffered >= r->io.file.autosync)
|
||||
{
|
||||
aof_fsync(fileno(r->io.file.fp));
|
||||
r->io.file.buffered = 0;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
@@ -95,6 +108,8 @@ 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 */
|
||||
};
|
||||
|
||||
@@ -104,12 +119,16 @@ 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 */
|
||||
};
|
||||
|
||||
void rioInitWithFile(rio *r, FILE *fp) {
|
||||
*r = rioFileIO;
|
||||
r->io.file.fp = fp;
|
||||
r->io.file.buffered = 0;
|
||||
r->io.file.autosync = 0;
|
||||
}
|
||||
|
||||
void rioInitWithBuffer(rio *r, sds s) {
|
||||
@@ -124,6 +143,19 @@ void rioGenericUpdateChecksum(rio *r, const void *buf, size_t len) {
|
||||
r->cksum = crc64(r->cksum,buf,len);
|
||||
}
|
||||
|
||||
/* Set the file-based rio object to auto-fsync every 'bytes' file written.
|
||||
* By default this is set to zero that means no automatic file sync is
|
||||
* performed.
|
||||
*
|
||||
* This feature is useful in a few contexts since when we rely on OS write
|
||||
* buffers sometimes the OS buffers way too much, resulting in too many
|
||||
* disk I/O concentrated in very little time. When we fsync in an explicit
|
||||
* way instead the I/O pressure is more distributed across time. */
|
||||
void rioSetAutoSync(rio *r, off_t bytes) {
|
||||
redisAssert(r->read == rioFileIO.read);
|
||||
r->io.file.autosync = bytes;
|
||||
}
|
||||
|
||||
/* ------------------------------ Higher level interface ---------------------------
|
||||
* The following higher level functions use lower level rio.c functions to help
|
||||
* generating the Redis protocol for the Append Only File. */
|
||||
|
||||
@@ -43,15 +43,22 @@ struct _rio {
|
||||
size_t (*read)(struct _rio *, void *buf, size_t len);
|
||||
size_t (*write)(struct _rio *, const void *buf, size_t len);
|
||||
off_t (*tell)(struct _rio *);
|
||||
/* The update_cksum method if not NULL is used to compute the checksum of all the
|
||||
* data that was read or written so far. The method should be designed so that
|
||||
* can be called with the current checksum, and the buf and len fields pointing
|
||||
* to the new block of data to add to the checksum computation. */
|
||||
/* The update_cksum method if not NULL is used to compute the checksum of
|
||||
* all the data that was read or written so far. The method should be
|
||||
* designed so that can be called with the current checksum, and the buf
|
||||
* and len fields pointing to the new block of data to add to the checksum
|
||||
* computation. */
|
||||
void (*update_cksum)(struct _rio *, const void *buf, size_t len);
|
||||
|
||||
/* 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 {
|
||||
@@ -60,6 +67,8 @@ struct _rio {
|
||||
} buffer;
|
||||
struct {
|
||||
FILE *fp;
|
||||
off_t buffered; /* Bytes written since last fsync. */
|
||||
off_t autosync; /* fsync after 'autosync' bytes written. */
|
||||
} file;
|
||||
} io;
|
||||
};
|
||||
@@ -71,16 +80,29 @@ typedef struct _rio rio;
|
||||
* if needed. */
|
||||
|
||||
static inline size_t rioWrite(rio *r, const void *buf, size_t len) {
|
||||
if (r->update_cksum) r->update_cksum(r,buf,len);
|
||||
return r->write(r,buf,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;
|
||||
}
|
||||
|
||||
static inline size_t rioRead(rio *r, void *buf, size_t len) {
|
||||
if (r->read(r,buf,len) == 1) {
|
||||
if (r->update_cksum) r->update_cksum(r,buf,len);
|
||||
return 1;
|
||||
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;
|
||||
}
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline off_t rioTell(rio *r) {
|
||||
@@ -96,5 +118,6 @@ size_t rioWriteBulkLongLong(rio *r, long long l);
|
||||
size_t rioWriteBulkDouble(rio *r, double d);
|
||||
|
||||
void rioGenericUpdateChecksum(rio *r, const void *buf, size_t len);
|
||||
void rioSetAutoSync(rio *r, off_t bytes);
|
||||
|
||||
#endif
|
||||
|
||||
+73
-23
@@ -152,9 +152,20 @@ 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");
|
||||
lua_pushstring(lua, error);
|
||||
|
||||
/* 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_settable(lua,-3);
|
||||
}
|
||||
|
||||
@@ -258,6 +269,7 @@ 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);
|
||||
@@ -609,6 +621,26 @@ 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
|
||||
@@ -829,21 +861,30 @@ 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) 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;
|
||||
}
|
||||
/* 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
|
||||
@@ -866,10 +907,10 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
delhook = 1;
|
||||
}
|
||||
|
||||
/* At this point whatever this script was never seen before or if it was
|
||||
/* At this point whether 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,0);
|
||||
err = lua_pcall(lua,0,1,-2);
|
||||
|
||||
/* Perform some cleanup that we need to do both on error and success. */
|
||||
if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */
|
||||
@@ -887,30 +928,37 @@ 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,1); /* Consume the Lua reply. */
|
||||
lua_pop(lua,2); /* Consume the Lua reply and remove error handler. */
|
||||
} else {
|
||||
/* On success convert the Lua return value into Redis protocol, and
|
||||
* send it to * the client. */
|
||||
luaReplyToRedisReply(c,lua);
|
||||
luaReplyToRedisReply(c,lua); /* Convert and consume the reply. */
|
||||
lua_pop(lua,1); /* Remove the error handler. */
|
||||
}
|
||||
|
||||
/* 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.
|
||||
/* 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
* 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. */
|
||||
if (evalsha) {
|
||||
robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr);
|
||||
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);
|
||||
|
||||
redisAssertWithInfo(c,NULL,script != NULL);
|
||||
rewriteClientCommandArgument(c,0,
|
||||
resetRefCount(createStringObject("EVAL",4)));
|
||||
rewriteClientCommandArgument(c,1,script);
|
||||
replicationScriptCacheAdd(c->argv[1]->ptr);
|
||||
redisAssertWithInfo(c,NULL,script != NULL);
|
||||
rewriteClientCommandArgument(c,0,
|
||||
resetRefCount(createStringObject("EVAL",4)));
|
||||
rewriteClientCommandArgument(c,1,script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -977,7 +1025,8 @@ void scriptCommand(redisClient *c) {
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"flush")) {
|
||||
scriptingReset();
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++; /* Replicating this command is a good idea. */
|
||||
replicationScriptCacheFlush();
|
||||
server.dirty++; /* Propagating this command is a good idea. */
|
||||
} else if (c->argc >= 2 && !strcasecmp(c->argv[1]->ptr,"exists")) {
|
||||
int j;
|
||||
|
||||
@@ -1005,6 +1054,7 @@ 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,6 +36,18 @@
|
||||
#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;
|
||||
|
||||
@@ -53,24 +65,43 @@ 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);
|
||||
@@ -78,6 +109,10 @@ 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;
|
||||
@@ -89,7 +124,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 *size* of the sds string as returned
|
||||
* 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) {
|
||||
struct sdshdr *sh, *newsh;
|
||||
@@ -113,7 +148,10 @@ 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. */
|
||||
* 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;
|
||||
|
||||
@@ -123,6 +161,13 @@ 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)));
|
||||
|
||||
@@ -130,7 +175,7 @@ size_t sdsAllocSize(sds s) {
|
||||
}
|
||||
|
||||
/* Increment the sds length and decrements the left free space at the
|
||||
* end of the string accordingly to 'incr'. Also set the null term
|
||||
* 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
|
||||
@@ -140,15 +185,17 @@ 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 new things without copying into an intermediate buffer:
|
||||
* 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, nhread);
|
||||
* sdsIncrLen(s, nread);
|
||||
*/
|
||||
void sdsIncrLen(sds s, int incr) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
@@ -161,7 +208,10 @@ 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. */
|
||||
* 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. */
|
||||
sds sdsgrowzero(sds s, size_t len) {
|
||||
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
|
||||
size_t totlen, curlen = sh->len;
|
||||
@@ -179,6 +229,11 @@ 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);
|
||||
@@ -193,14 +248,24 @@ 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;
|
||||
@@ -218,10 +283,13 @@ 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;
|
||||
@@ -245,6 +313,22 @@ 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;
|
||||
@@ -254,6 +338,20 @@ 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;
|
||||
@@ -271,11 +369,27 @@ sds sdstrim(sds s, const char *cset) {
|
||||
return s;
|
||||
}
|
||||
|
||||
sds sdsrange(sds s, int start, int end) {
|
||||
/* 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) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t newlen, len = sdslen(s);
|
||||
|
||||
if (len == 0) return s;
|
||||
if (len == 0) return;
|
||||
if (start < 0) {
|
||||
start = len+start;
|
||||
if (start < 0) start = 0;
|
||||
@@ -299,21 +413,33 @@ sds 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;
|
||||
@@ -391,6 +517,7 @@ 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--)
|
||||
@@ -398,6 +525,10 @@ 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;
|
||||
@@ -413,6 +544,12 @@ 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--) {
|
||||
@@ -621,6 +758,19 @@ 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();
|
||||
sds sdsempty(void);
|
||||
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);
|
||||
sds sdsrange(sds s, int start, int end);
|
||||
void sdsrange(sds s, int start, int end);
|
||||
void sdsupdatelen(sds s);
|
||||
void sdsclear(sds s);
|
||||
int sdscmp(const sds s1, const sds s2);
|
||||
@@ -89,6 +89,7 @@ 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);
|
||||
|
||||
+151
-50
@@ -74,6 +74,8 @@ typedef struct sentinelAddr {
|
||||
#define SRI_RECONF_DONE (1<<13) /* Slave synchronized with new master. */
|
||||
#define SRI_FORCE_FAILOVER (1<<14) /* Force failover with master up. */
|
||||
#define SRI_SCRIPT_KILL_SENT (1<<15) /* SCRIPT KILL already sent on -BUSY */
|
||||
#define SRI_DEMOTE (1<<16) /* If the instance claims to be a master, demote
|
||||
it into a slave sending SLAVEOF. */
|
||||
|
||||
#define SENTINEL_INFO_PERIOD 10000
|
||||
#define SENTINEL_PING_PERIOD 1000
|
||||
@@ -403,7 +405,7 @@ void initSentinel(void) {
|
||||
/* Initialize various data structures. */
|
||||
sentinel.masters = dictCreate(&instancesDictType,NULL);
|
||||
sentinel.tilt = 0;
|
||||
sentinel.tilt_start_time = mstime();
|
||||
sentinel.tilt_start_time = 0;
|
||||
sentinel.previous_time = mstime();
|
||||
sentinel.running_scripts = 0;
|
||||
sentinel.scripts_queue = listCreate();
|
||||
@@ -424,7 +426,7 @@ sentinelAddr *createSentinelAddr(char *hostname, int port) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
if (anetResolve(NULL,hostname,buf) == ANET_ERR) {
|
||||
if (anetResolve(NULL,hostname,buf,sizeof(buf)) == ANET_ERR) {
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
@@ -833,7 +835,9 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
|
||||
/* For slaves and sentinel we use ip:port as name. */
|
||||
if (flags & (SRI_SLAVE|SRI_SENTINEL)) {
|
||||
snprintf(slavename,sizeof(slavename),"%s:%d",hostname,port);
|
||||
snprintf(slavename,sizeof(slavename),
|
||||
strchr(hostname,':') ? "[%s]:%d" : "%s:%d",
|
||||
hostname,port);
|
||||
name = slavename;
|
||||
}
|
||||
|
||||
@@ -941,7 +945,9 @@ sentinelRedisInstance *sentinelRedisInstanceLookupSlave(
|
||||
sentinelRedisInstance *slave;
|
||||
|
||||
redisAssert(ri->flags & SRI_MASTER);
|
||||
key = sdscatprintf(sdsempty(),"%s:%d",ip,port);
|
||||
key = sdscatprintf(sdsempty(),
|
||||
strchr(ip,':') ? "[%s]:%d" : "%s:%d",
|
||||
ip,port);
|
||||
slave = dictFetchValue(ri->slaves,key);
|
||||
sdsfree(key);
|
||||
return slave;
|
||||
@@ -1130,7 +1136,6 @@ int sentinelResetMastersByPattern(char *pattern, int flags) {
|
||||
* TODO: make this reset so that original sentinels are re-added with
|
||||
* same ip / port / runid.
|
||||
*/
|
||||
|
||||
int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip, int port) {
|
||||
sentinelAddr *oldaddr, *newaddr;
|
||||
|
||||
@@ -1139,12 +1144,26 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip,
|
||||
sentinelResetMaster(master,SENTINEL_NO_FLAGS);
|
||||
oldaddr = master->addr;
|
||||
master->addr = newaddr;
|
||||
master->o_down_since_time = 0;
|
||||
master->s_down_since_time = 0;
|
||||
|
||||
/* Release the old address at the end so we are safe even if the function
|
||||
* gets the master->addr->ip and master->addr->port as arguments. */
|
||||
releaseSentinelAddr(oldaddr);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Return non-zero if there was no SDOWN or ODOWN error associated to this
|
||||
* instance in the latest 'ms' milliseconds. */
|
||||
int sentinelRedisInstanceNoDownFor(sentinelRedisInstance *ri, mstime_t ms) {
|
||||
mstime_t most_recent;
|
||||
|
||||
most_recent = ri->s_down_since_time;
|
||||
if (ri->o_down_since_time > most_recent)
|
||||
most_recent = ri->o_down_since_time;
|
||||
return most_recent == 0 || (mstime() - most_recent) > ms;
|
||||
}
|
||||
|
||||
/* ============================ Config handling ============================= */
|
||||
char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
sentinelRedisInstance *ri;
|
||||
@@ -1382,26 +1401,39 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
}
|
||||
}
|
||||
|
||||
/* slave0:<ip>,<port>,<state> */
|
||||
/* old versions: slave0:<ip>,<port>,<state>
|
||||
* new versions: slave0:ip=127.0.0.1,port=9999,... */
|
||||
if ((ri->flags & SRI_MASTER) &&
|
||||
sdslen(l) >= 7 &&
|
||||
!memcmp(l,"slave",5) && isdigit(l[5]))
|
||||
{
|
||||
char *ip, *port, *end;
|
||||
|
||||
ip = strchr(l,':'); if (!ip) continue;
|
||||
ip++; /* Now ip points to start of ip address. */
|
||||
port = strchr(ip,','); if (!port) continue;
|
||||
*port = '\0'; /* nul term for easy access. */
|
||||
port++; /* Now port points to start of port number. */
|
||||
end = strchr(port,','); if (!end) continue;
|
||||
*end = '\0'; /* nul term for easy access. */
|
||||
if (strstr(l,"ip=") == NULL) {
|
||||
/* Old format. */
|
||||
ip = strchr(l,':'); if (!ip) continue;
|
||||
ip++; /* Now ip points to start of ip address. */
|
||||
port = strchr(ip,','); if (!port) continue;
|
||||
*port = '\0'; /* nul term for easy access. */
|
||||
port++; /* Now port points to start of port number. */
|
||||
end = strchr(port,','); if (!end) continue;
|
||||
*end = '\0'; /* nul term for easy access. */
|
||||
} else {
|
||||
/* New format. */
|
||||
ip = strstr(l,"ip="); if (!ip) continue;
|
||||
ip += 3; /* Now ip points to start of ip address. */
|
||||
port = strstr(l,"port="); if (!port) continue;
|
||||
port += 5; /* Now port points to start of port number. */
|
||||
/* Nul term both fields for easy access. */
|
||||
end = strchr(ip,','); if (end) *end = '\0';
|
||||
end = strchr(port,','); if (end) *end = '\0';
|
||||
}
|
||||
|
||||
/* Check if we already have this slave into our table,
|
||||
* otherwise add it. */
|
||||
if (sentinelRedisInstanceLookupSlave(ri,ip,atoi(port)) == NULL) {
|
||||
if ((slave = createSentinelRedisInstance(NULL,SRI_SLAVE,ip,
|
||||
atoi(port), ri->quorum,ri)) != NULL)
|
||||
atoi(port), ri->quorum, ri)) != NULL)
|
||||
{
|
||||
sentinelEvent(REDIS_NOTICE,"+slave",slave,"%@");
|
||||
}
|
||||
@@ -1446,44 +1478,80 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
ri->info_refresh = mstime();
|
||||
sdsfreesplitres(lines,numlines);
|
||||
|
||||
/* ---------------------------- Acting half ----------------------------- */
|
||||
if (sentinel.tilt) return;
|
||||
/* ---------------------------- Acting half -----------------------------
|
||||
* Some things will not happen if sentinel.tilt is true, but some will
|
||||
* still be processed. */
|
||||
|
||||
/* Act if a master turned into a slave. */
|
||||
if ((ri->flags & SRI_MASTER) && role == SRI_SLAVE) {
|
||||
if ((first_runid || runid_changed) && ri->slave_master_host) {
|
||||
/* If it is the first time we receive INFO from it, but it's
|
||||
* a slave while it was configured as a master, we want to monitor
|
||||
* its master instead. */
|
||||
sentinelEvent(REDIS_WARNING,"+redirect-to-master",ri,
|
||||
"%s %s %d %s %d",
|
||||
ri->name, ri->addr->ip, ri->addr->port,
|
||||
ri->slave_master_host, ri->slave_master_port);
|
||||
sentinelResetMasterAndChangeAddress(ri,ri->slave_master_host,
|
||||
ri->slave_master_port);
|
||||
return;
|
||||
}
|
||||
/* When what we believe is our master, turned into a slave, the wiser
|
||||
* thing we can do is to follow the events and redirect to the new
|
||||
* master, always. */
|
||||
if ((ri->flags & SRI_MASTER) && role == SRI_SLAVE && ri->slave_master_host)
|
||||
{
|
||||
sentinelEvent(REDIS_WARNING,"+redirect-to-master",ri,
|
||||
"%s %s %d %s %d",
|
||||
ri->name, ri->addr->ip, ri->addr->port,
|
||||
ri->slave_master_host, ri->slave_master_port);
|
||||
sentinelResetMasterAndChangeAddress(ri,ri->slave_master_host,
|
||||
ri->slave_master_port);
|
||||
return; /* Don't process anything after this event. */
|
||||
}
|
||||
|
||||
/* Act if a slave turned into a master. */
|
||||
/* Handle slave -> master role switch. */
|
||||
if ((ri->flags & SRI_SLAVE) && role == SRI_MASTER) {
|
||||
if (!(ri->master->flags & SRI_FAILOVER_IN_PROGRESS) &&
|
||||
(runid_changed || first_runid))
|
||||
if (!sentinel.tilt && ri->flags & SRI_DEMOTE) {
|
||||
/* If this sentinel was partitioned from the slave's master,
|
||||
* or tilted recently, wait some time before to act,
|
||||
* so that DOWN and roles INFO will be refreshed. */
|
||||
mstime_t wait_time = SENTINEL_INFO_PERIOD*2 +
|
||||
ri->master->down_after_period*2;
|
||||
|
||||
if (!sentinelRedisInstanceNoDownFor(ri->master,wait_time) ||
|
||||
(mstime()-sentinel.tilt_start_time) < wait_time)
|
||||
return;
|
||||
|
||||
/* Old master returned back? Turn it into a slave ASAP if
|
||||
* we can reach what we believe is the new master now, and
|
||||
* have a recent role information for it.
|
||||
*
|
||||
* Note: we'll clear the DEMOTE flag only when we have the
|
||||
* acknowledge that it's a slave again. */
|
||||
if (ri->master->flags & SRI_MASTER &&
|
||||
(ri->master->flags & (SRI_S_DOWN|SRI_O_DOWN)) == 0 &&
|
||||
(mstime() - ri->master->info_refresh) < SENTINEL_INFO_PERIOD*2)
|
||||
{
|
||||
int retval;
|
||||
retval = redisAsyncCommand(ri->cc,
|
||||
sentinelDiscardReplyCallback, NULL, "SLAVEOF %s %d",
|
||||
ri->master->addr->ip,
|
||||
ri->master->addr->port);
|
||||
if (retval == REDIS_OK)
|
||||
sentinelEvent(REDIS_NOTICE,"+demote-old-slave",ri,"%@");
|
||||
} else {
|
||||
/* Otherwise if there are not the conditions to demote, we
|
||||
* no longer trust the DEMOTE flag and remove it. */
|
||||
ri->flags &= ~SRI_DEMOTE;
|
||||
sentinelEvent(REDIS_NOTICE,"-demote-flag-cleared",ri,"%@");
|
||||
}
|
||||
} else if (!(ri->master->flags & SRI_FAILOVER_IN_PROGRESS) &&
|
||||
(runid_changed || first_runid))
|
||||
{
|
||||
/* If a slave turned into master but:
|
||||
*
|
||||
* 1) Failover not in progress.
|
||||
* 2) RunID hs changed, or its the first time we see an INFO output.
|
||||
* 2) RunID has changed or its the first time we see an INFO output.
|
||||
*
|
||||
* We assume this is a reboot with a wrong configuration.
|
||||
* Log the event and remove the slave. */
|
||||
* Log the event and remove the slave. Note that this is processed
|
||||
* in tilt mode as well, otherwise we lose the information that the
|
||||
* runid changed (reboot?) and when the tilt mode ends a fake
|
||||
* failover will be detected. */
|
||||
int retval;
|
||||
|
||||
sentinelEvent(REDIS_WARNING,"-slave-restart-as-master",ri,"%@ #removing it from the attached slaves");
|
||||
retval = dictDelete(ri->master->slaves,ri->name);
|
||||
redisAssert(retval == REDIS_OK);
|
||||
return;
|
||||
} else if (ri->flags & SRI_PROMOTED) {
|
||||
} else if (!sentinel.tilt && ri->flags & SRI_PROMOTED) {
|
||||
/* If this is a promoted slave we can change state to the
|
||||
* failover state machine. */
|
||||
if ((ri->master->flags & SRI_FAILOVER_IN_PROGRESS) &&
|
||||
@@ -1499,11 +1567,12 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
sentinelCallClientReconfScript(ri->master,SENTINEL_LEADER,
|
||||
"start",ri->master->addr,ri->addr);
|
||||
}
|
||||
} else if (!(ri->master->flags & SRI_FAILOVER_IN_PROGRESS) ||
|
||||
((ri->master->flags & SRI_FAILOVER_IN_PROGRESS) &&
|
||||
(ri->master->flags & SRI_I_AM_THE_LEADER) &&
|
||||
ri->master->failover_state ==
|
||||
SENTINEL_FAILOVER_STATE_WAIT_START))
|
||||
} else if (!sentinel.tilt && (
|
||||
!(ri->master->flags & SRI_FAILOVER_IN_PROGRESS) ||
|
||||
((ri->master->flags & SRI_FAILOVER_IN_PROGRESS) &&
|
||||
(ri->master->flags & SRI_I_AM_THE_LEADER) &&
|
||||
ri->master->failover_state ==
|
||||
SENTINEL_FAILOVER_STATE_WAIT_START)))
|
||||
{
|
||||
/* No failover in progress? Then it is the start of a failover
|
||||
* and we are an observer.
|
||||
@@ -1523,6 +1592,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
ri->master->failover_state_change_time = mstime();
|
||||
ri->master->promoted_slave = ri;
|
||||
ri->flags |= SRI_PROMOTED;
|
||||
ri->flags &= ~SRI_DEMOTE;
|
||||
sentinelCallClientReconfScript(ri->master,SENTINEL_OBSERVER,
|
||||
"start", ri->master->addr,ri->addr);
|
||||
/* We are an observer, so we can only assume that the leader
|
||||
@@ -1534,6 +1604,10 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
}
|
||||
}
|
||||
|
||||
/* None of the following conditions are processed when in tilt mode, so
|
||||
* return asap. */
|
||||
if (sentinel.tilt) return;
|
||||
|
||||
/* Detect if the slave that is in the process of being reconfigured
|
||||
* changed state. */
|
||||
if ((ri->flags & SRI_SLAVE) && role == SRI_SLAVE &&
|
||||
@@ -1564,6 +1638,13 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
ri->failover_state_change_time = mstime();
|
||||
}
|
||||
}
|
||||
|
||||
/* Detect if the old master was demoted as slave and generate the
|
||||
* +slave event. */
|
||||
if (role == SRI_SLAVE && ri->flags & SRI_DEMOTE) {
|
||||
sentinelEvent(REDIS_NOTICE,"+slave",ri,"%@");
|
||||
ri->flags &= ~SRI_DEMOTE;
|
||||
}
|
||||
}
|
||||
|
||||
void sentinelInfoReplyCallback(redisAsyncContext *c, void *reply, void *privdata) {
|
||||
@@ -1664,9 +1745,13 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd
|
||||
|
||||
{
|
||||
int numtokens, port, removed, canfailover;
|
||||
/* Separator changed from ":" to "," in recent versions in order to
|
||||
* play well with IPv6 addresses. For now we make sure to parse both
|
||||
* correctly detecting if there is "," inside the string. */
|
||||
char *sep = strchr(r->element[2]->str,',') ? "," : ":";
|
||||
char **token = sdssplitlen(r->element[2]->str,
|
||||
r->element[2]->len,
|
||||
":",1,&numtokens);
|
||||
sep,1,&numtokens);
|
||||
sentinelRedisInstance *sentinel;
|
||||
|
||||
if (numtokens == 4) {
|
||||
@@ -1760,14 +1845,12 @@ void sentinelPingInstance(sentinelRedisInstance *ri) {
|
||||
(now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD)
|
||||
{
|
||||
/* PUBLISH hello messages only to masters. */
|
||||
struct sockaddr_in sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
if (anetSockName(ri->cc->c.fd,ip,sizeof(ip),NULL) != -1) {
|
||||
char myaddr[REDIS_IP_STR_LEN+128];
|
||||
|
||||
if (getsockname(ri->cc->c.fd,(struct sockaddr*)&sa,&salen) != -1) {
|
||||
char myaddr[128];
|
||||
|
||||
snprintf(myaddr,sizeof(myaddr),"%s:%d:%s:%d",
|
||||
inet_ntoa(sa.sin_addr), server.port, server.runid,
|
||||
snprintf(myaddr,sizeof(myaddr),"%s,%d,%s,%d",
|
||||
ip, server.port, server.runid,
|
||||
(ri->flags & SRI_CAN_FAILOVER) != 0);
|
||||
retval = redisAsyncCommand(ri->cc,
|
||||
sentinelPublishReplyCallback, NULL, "PUBLISH %s %s",
|
||||
@@ -1835,8 +1918,9 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) {
|
||||
if (ri->flags & SRI_RECONF_SENT) flags = sdscat(flags,"reconf_sent,");
|
||||
if (ri->flags & SRI_RECONF_INPROG) flags = sdscat(flags,"reconf_inprog,");
|
||||
if (ri->flags & SRI_RECONF_DONE) flags = sdscat(flags,"reconf_done,");
|
||||
if (ri->flags & SRI_DEMOTE) flags = sdscat(flags,"demote,");
|
||||
|
||||
if (sdslen(flags) != 0) flags = sdsrange(flags,0,-2); /* remove last "," */
|
||||
if (sdslen(flags) != 0) sdsrange(flags,0,-2); /* remove last "," */
|
||||
addReplyBulkCString(c,flags);
|
||||
sdsfree(flags);
|
||||
fields++;
|
||||
@@ -2592,6 +2676,7 @@ sentinelRedisInstance *sentinelSelectSlave(sentinelRedisInstance *master) {
|
||||
mstime_t info_validity_time = mstime()-SENTINEL_INFO_VALIDITY_TIME;
|
||||
|
||||
if (slave->flags & (SRI_S_DOWN|SRI_O_DOWN|SRI_DISCONNECTED)) continue;
|
||||
if (slave->flags & SRI_DEMOTE) continue; /* Old master not yet ready. */
|
||||
if (slave->last_avail_time < info_validity_time) continue;
|
||||
if (slave->slave_priority == 0) continue;
|
||||
|
||||
@@ -2837,12 +2922,28 @@ void sentinelFailoverReconfNextSlave(sentinelRedisInstance *master) {
|
||||
void sentinelFailoverSwitchToPromotedSlave(sentinelRedisInstance *master) {
|
||||
sentinelRedisInstance *ref = master->promoted_slave ?
|
||||
master->promoted_slave : master;
|
||||
sds old_master_ip;
|
||||
int old_master_port;
|
||||
|
||||
sentinelEvent(REDIS_WARNING,"+switch-master",master,"%s %s %d %s %d",
|
||||
master->name, master->addr->ip, master->addr->port,
|
||||
ref->addr->ip, ref->addr->port);
|
||||
|
||||
old_master_ip = sdsdup(master->addr->ip);
|
||||
old_master_port = master->addr->port;
|
||||
sentinelResetMasterAndChangeAddress(master,ref->addr->ip,ref->addr->port);
|
||||
/* If this is a real switch, that is, we have master->promoted_slave not
|
||||
* NULL, then we want to add the old master as a slave of the new master,
|
||||
* but flagging it with SRI_DEMOTE so that we know we'll need to send
|
||||
* SLAVEOF once the old master is reachable again. */
|
||||
if (master != ref) {
|
||||
/* Add the new slave, but don't generate a Sentinel event as it will
|
||||
* happen later when finally the instance will claim to be a slave
|
||||
* in the INFO output. */
|
||||
createSentinelRedisInstance(NULL,SRI_SLAVE|SRI_DEMOTE,
|
||||
old_master_ip, old_master_port, master->quorum, master);
|
||||
}
|
||||
sdsfree(old_master_ip);
|
||||
}
|
||||
|
||||
void sentinelFailoverStateMachine(sentinelRedisInstance *ri) {
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/* ==========================================================================
|
||||
* 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 */
|
||||
+17
-5
@@ -163,12 +163,22 @@ int sortCompare(const void *s1, const void *s2) {
|
||||
else
|
||||
cmp = 1;
|
||||
} else {
|
||||
/* We have both the objects, use strcoll */
|
||||
cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr);
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Compare elements directly. */
|
||||
cmp = compareStringObjects(so1->obj,so2->obj);
|
||||
if (server.sort_store) {
|
||||
cmp = compareStringObjects(so1->obj,so2->obj);
|
||||
} else {
|
||||
cmp = collateStringObjects(so1->obj,so2->obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
return server.sort_desc ? -cmp : cmp;
|
||||
@@ -432,6 +442,7 @@ 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
|
||||
@@ -504,9 +515,12 @@ 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);
|
||||
@@ -525,5 +539,3 @@ void sortCommand(redisClient *c) {
|
||||
}
|
||||
zfree(vector);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+11
-1
@@ -474,6 +474,7 @@ 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++;
|
||||
}
|
||||
|
||||
@@ -489,6 +490,7 @@ 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++;
|
||||
}
|
||||
}
|
||||
@@ -510,6 +512,7 @@ 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++;
|
||||
}
|
||||
|
||||
@@ -543,6 +546,7 @@ 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++;
|
||||
}
|
||||
|
||||
@@ -569,6 +573,7 @@ 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
|
||||
@@ -649,7 +654,7 @@ void hmgetCommand(redisClient *c) {
|
||||
|
||||
void hdelCommand(redisClient *c) {
|
||||
robj *o;
|
||||
int j, deleted = 0;
|
||||
int j, deleted = 0, keyremoved = 0;
|
||||
|
||||
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,o,REDIS_HASH)) return;
|
||||
@@ -659,12 +664,17 @@ 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);
|
||||
|
||||
+43
-10
@@ -275,7 +275,7 @@ void listTypeConvert(robj *subject, int enc) {
|
||||
|
||||
if (enc == REDIS_ENCODING_LINKEDLIST) {
|
||||
list *l = listCreate();
|
||||
listSetFreeMethod(l,decrRefCount);
|
||||
listSetFreeMethod(l,decrRefCountVoid);
|
||||
|
||||
/* listTypeGet returns a robj with incremented refcount */
|
||||
li = listTypeInitIterator(subject,0,REDIS_TAIL);
|
||||
@@ -316,7 +316,12 @@ void pushGenericCommand(redisClient *c, int where) {
|
||||
pushed++;
|
||||
}
|
||||
addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0));
|
||||
if (pushed) signalModifiedKey(c->db,c->argv[1]);
|
||||
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);
|
||||
}
|
||||
server.dirty += pushed;
|
||||
}
|
||||
|
||||
@@ -338,10 +343,6 @@ 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
|
||||
@@ -366,6 +367,8 @@ 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 */
|
||||
@@ -373,8 +376,11 @@ 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++;
|
||||
}
|
||||
|
||||
@@ -469,6 +475,7 @@ 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) {
|
||||
@@ -481,6 +488,7 @@ 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 {
|
||||
@@ -496,9 +504,16 @@ 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);
|
||||
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
|
||||
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]);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -618,7 +633,12 @@ void ltrimCommand(redisClient *c) {
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
|
||||
|
||||
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);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
addReply(c,shared.ok);
|
||||
@@ -693,6 +713,7 @@ 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);
|
||||
}
|
||||
@@ -722,7 +743,12 @@ void rpoplpushCommand(redisClient *c) {
|
||||
decrRefCount(value);
|
||||
|
||||
/* Delete the source list when it is empty */
|
||||
if (listTypeLength(sobj) == 0) dbDelete(c->db,touchedkey);
|
||||
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);
|
||||
}
|
||||
signalModifiedKey(c->db,touchedkey);
|
||||
decrRefCount(touchedkey);
|
||||
server.dirty++;
|
||||
@@ -1046,6 +1072,7 @@ 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);
|
||||
|
||||
@@ -1053,7 +1080,13 @@ void blockingPopGenericCommand(redisClient *c, int where) {
|
||||
addReplyBulk(c,c->argv[j]);
|
||||
addReplyBulk(c,value);
|
||||
decrRefCount(value);
|
||||
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[j]);
|
||||
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);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
server.dirty++;
|
||||
|
||||
|
||||
+134
-23
@@ -266,14 +266,17 @@ 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]);
|
||||
if (added) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[1],c->db->id);
|
||||
}
|
||||
server.dirty += added;
|
||||
addReplyLongLong(c,added);
|
||||
}
|
||||
|
||||
void sremCommand(redisClient *c) {
|
||||
robj *set;
|
||||
int j, deleted = 0;
|
||||
int j, deleted = 0, keyremoved = 0;
|
||||
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,set,REDIS_SET)) return;
|
||||
@@ -283,12 +286,17 @@ 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);
|
||||
@@ -322,9 +330,13 @@ 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]);
|
||||
if (setTypeSize(srcset) == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
server.dirty++;
|
||||
@@ -336,7 +348,10 @@ void smoveCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
/* An extra key has changed when ele was successfully added to dstset */
|
||||
if (setTypeAdd(dstset,ele)) server.dirty++;
|
||||
if (setTypeAdd(dstset,ele)) {
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[2],c->db->id);
|
||||
}
|
||||
addReply(c,shared.cone);
|
||||
}
|
||||
|
||||
@@ -378,6 +393,7 @@ 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);
|
||||
@@ -386,7 +402,10 @@ void spopCommand(redisClient *c) {
|
||||
decrRefCount(aux);
|
||||
|
||||
addReplyBulk(c,ele);
|
||||
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
|
||||
if (setTypeSize(set) == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
}
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -565,6 +584,14 @@ 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;
|
||||
@@ -679,13 +706,18 @@ 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. */
|
||||
dbDelete(c->db,dstkey);
|
||||
int deleted = 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++;
|
||||
@@ -712,6 +744,7 @@ 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 ?
|
||||
@@ -728,34 +761,106 @@ 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();
|
||||
|
||||
/* 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 */
|
||||
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 */
|
||||
|
||||
si = setTypeInitIterator(sets[j]);
|
||||
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]);
|
||||
while((ele = setTypeNextObject(si)) != NULL) {
|
||||
if (op == REDIS_OP_UNION || j == 0) {
|
||||
if (setTypeAdd(dstset,ele)) {
|
||||
cardinality++;
|
||||
}
|
||||
} else if (op == REDIS_OP_DIFF) {
|
||||
if (setTypeRemove(dstset,ele)) {
|
||||
cardinality--;
|
||||
}
|
||||
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++;
|
||||
}
|
||||
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 */
|
||||
|
||||
/* Exit when result set is empty. */
|
||||
if (op == REDIS_OP_DIFF && cardinality == 0) break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Output the content of the resulting set, if not in STORE mode */
|
||||
@@ -771,13 +876,19 @@ 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 */
|
||||
dbDelete(c->db,dstkey);
|
||||
int deleted = 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++;
|
||||
|
||||
+72
-8
@@ -42,7 +42,27 @@ static int checkStringLength(redisClient *c, long long size) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire, int unit) {
|
||||
/* The setGenericCommand() function implements the SET operation with different
|
||||
* options and variants. This function is called in order to implement the
|
||||
* following commands: SET, SETEX, PSETEX, SETNX.
|
||||
*
|
||||
* 'flags' changes the behavior of the command (NX or XX, see belove).
|
||||
*
|
||||
* 'expire' represents an expire to set in form of a Redis object as passed
|
||||
* by the user. It is interpreted according to the specified 'unit'.
|
||||
*
|
||||
* 'ok_reply' and 'abort_reply' is what the function will reply to the client
|
||||
* if the operation is performed, or when it is not because of NX or
|
||||
* XX flags.
|
||||
*
|
||||
* If ok_reply is NULL "+OK" is used.
|
||||
* If abort_reply is NULL, "$-1" is used. */
|
||||
|
||||
#define REDIS_SET_NO_FLAGS 0
|
||||
#define REDIS_SET_NX (1<<0) /* Set if key not exists. */
|
||||
#define REDIS_SET_XX (1<<1) /* Set if key exists. */
|
||||
|
||||
void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
|
||||
long long milliseconds = 0; /* initialized to avoid any harmness warning */
|
||||
|
||||
if (expire) {
|
||||
@@ -55,34 +75,71 @@ void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expir
|
||||
if (unit == UNIT_SECONDS) milliseconds *= 1000;
|
||||
}
|
||||
|
||||
if (nx && lookupKeyWrite(c->db,key) != NULL) {
|
||||
addReply(c,shared.czero);
|
||||
if ((flags & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
|
||||
(flags & REDIS_SET_XX && lookupKeyWrite(c->db,key) == NULL))
|
||||
{
|
||||
addReply(c, abort_reply ? abort_reply : shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
setKey(c->db,key,val);
|
||||
server.dirty++;
|
||||
if (expire) setExpire(c->db,key,mstime()+milliseconds);
|
||||
addReply(c, nx ? shared.cone : shared.ok);
|
||||
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);
|
||||
}
|
||||
|
||||
/* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
|
||||
void setCommand(redisClient *c) {
|
||||
int j;
|
||||
robj *expire = NULL;
|
||||
int unit = UNIT_SECONDS;
|
||||
int flags = REDIS_SET_NO_FLAGS;
|
||||
|
||||
for (j = 3; j < c->argc; j++) {
|
||||
char *a = c->argv[j]->ptr;
|
||||
robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
|
||||
|
||||
if ((a[0] == 'n' || a[0] == 'N') &&
|
||||
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
|
||||
flags |= REDIS_SET_NX;
|
||||
} else if ((a[0] == 'x' || a[0] == 'X') &&
|
||||
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
|
||||
flags |= REDIS_SET_XX;
|
||||
} else if ((a[0] == 'e' || a[0] == 'E') &&
|
||||
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
|
||||
unit = UNIT_SECONDS;
|
||||
expire = next;
|
||||
j++;
|
||||
} else if ((a[0] == 'p' || a[0] == 'P') &&
|
||||
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
|
||||
unit = UNIT_MILLISECONDS;
|
||||
expire = next;
|
||||
j++;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
setGenericCommand(c,0,c->argv[1],c->argv[2],NULL,0);
|
||||
setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
|
||||
}
|
||||
|
||||
void setnxCommand(redisClient *c) {
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
setGenericCommand(c,1,c->argv[1],c->argv[2],NULL,0);
|
||||
setGenericCommand(c,REDIS_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero);
|
||||
}
|
||||
|
||||
void setexCommand(redisClient *c) {
|
||||
c->argv[3] = tryObjectEncoding(c->argv[3]);
|
||||
setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS);
|
||||
setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL);
|
||||
}
|
||||
|
||||
void psetexCommand(redisClient *c) {
|
||||
c->argv[3] = tryObjectEncoding(c->argv[3]);
|
||||
setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS);
|
||||
setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL);
|
||||
}
|
||||
|
||||
int getGenericCommand(redisClient *c) {
|
||||
@@ -108,6 +165,7 @@ 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++;
|
||||
}
|
||||
|
||||
@@ -169,6 +227,8 @@ 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));
|
||||
@@ -253,6 +313,7 @@ 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);
|
||||
@@ -287,6 +348,7 @@ 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);
|
||||
@@ -336,6 +398,7 @@ 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);
|
||||
|
||||
@@ -383,6 +446,7 @@ 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);
|
||||
}
|
||||
|
||||
+80
-55
@@ -844,9 +844,9 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
robj *ele;
|
||||
robj *zobj;
|
||||
robj *curobj;
|
||||
double score = 0, *scores, curscore = 0.0;
|
||||
double score = 0, *scores = NULL, curscore = 0.0;
|
||||
int j, elements = (c->argc-2)/2;
|
||||
int added = 0;
|
||||
int added = 0, updated = 0;
|
||||
|
||||
if (c->argc % 2) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
@@ -859,11 +859,7 @@ 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)
|
||||
{
|
||||
zfree(scores);
|
||||
return;
|
||||
}
|
||||
!= REDIS_OK) goto cleanup;
|
||||
}
|
||||
|
||||
/* Lookup the key and create the sorted set if does not exist. */
|
||||
@@ -880,8 +876,7 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
} else {
|
||||
if (zobj->type != REDIS_ZSET) {
|
||||
addReply(c,shared.wrongtypeerr);
|
||||
zfree(scores);
|
||||
return;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,10 +893,7 @@ void zaddGenericCommand(redisClient *c, int incr) {
|
||||
score += curscore;
|
||||
if (isnan(score)) {
|
||||
addReplyError(c,nanerr);
|
||||
/* Don't need to check if the sorted set is empty
|
||||
* because we know it has at least one element. */
|
||||
zfree(scores);
|
||||
return;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -909,9 +901,8 @@ 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
|
||||
@@ -921,10 +912,8 @@ 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++;
|
||||
if (!incr) added++;
|
||||
added++;
|
||||
}
|
||||
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
|
||||
zset *zs = zobj->ptr;
|
||||
@@ -943,8 +932,7 @@ 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. */
|
||||
zfree(scores);
|
||||
return;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -956,29 +944,33 @@ 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++;
|
||||
if (!incr) added++;
|
||||
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) {
|
||||
@@ -992,7 +984,7 @@ void zincrbyCommand(redisClient *c) {
|
||||
void zremCommand(redisClient *c) {
|
||||
robj *key = c->argv[1];
|
||||
robj *zobj;
|
||||
int deleted = 0, j;
|
||||
int deleted = 0, keyremoved = 0, j;
|
||||
|
||||
if ((zobj = lookupKeyWriteOrReply(c,key,shared.czero)) == NULL ||
|
||||
checkType(c,zobj,REDIS_ZSET)) return;
|
||||
@@ -1038,6 +1030,9 @@ 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;
|
||||
}
|
||||
@@ -1048,6 +1043,7 @@ void zremrangebyscoreCommand(redisClient *c) {
|
||||
robj *key = c->argv[1];
|
||||
robj *zobj;
|
||||
zrangespec range;
|
||||
int keyremoved = 0;
|
||||
unsigned long deleted;
|
||||
|
||||
/* Parse the range arguments. */
|
||||
@@ -1061,17 +1057,28 @@ 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);
|
||||
if (zzlLength(zobj->ptr) == 0) {
|
||||
dbDelete(c->db,key);
|
||||
keyremoved = 1;
|
||||
}
|
||||
} 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);
|
||||
if (dictSize(zs->dict) == 0) {
|
||||
dbDelete(c->db,key);
|
||||
keyremoved = 1;
|
||||
}
|
||||
} else {
|
||||
redisPanic("Unknown sorted set encoding");
|
||||
}
|
||||
|
||||
if (deleted) signalModifiedKey(c->db,key);
|
||||
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);
|
||||
}
|
||||
server.dirty += deleted;
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
@@ -1083,6 +1090,7 @@ 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;
|
||||
@@ -1107,19 +1115,30 @@ 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);
|
||||
if (zzlLength(zobj->ptr) == 0) {
|
||||
dbDelete(c->db,key);
|
||||
keyremoved = 1;
|
||||
}
|
||||
} 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);
|
||||
if (dictSize(zs->dict) == 0) {
|
||||
dbDelete(c->db,key);
|
||||
keyremoved = 1;
|
||||
}
|
||||
} else {
|
||||
redisPanic("Unknown sorted set encoding");
|
||||
}
|
||||
|
||||
if (deleted) signalModifiedKey(c->db,key);
|
||||
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);
|
||||
}
|
||||
server.dirty += deleted;
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
@@ -1251,20 +1270,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(it->is.is);
|
||||
return intsetLen(op->subject->ptr);
|
||||
} else if (op->encoding == REDIS_ENCODING_HT) {
|
||||
return dictSize(it->ht.dict);
|
||||
dict *ht = op->subject->ptr;
|
||||
return dictSize(ht);
|
||||
} else {
|
||||
redisPanic("Unknown set encoding");
|
||||
}
|
||||
} else if (op->type == REDIS_ZSET) {
|
||||
iterzset *it = &op->iter.zset;
|
||||
if (op->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
return zzlLength(it->zl.zl);
|
||||
return zzlLength(op->subject->ptr);
|
||||
} else if (op->encoding == REDIS_ENCODING_SKIPLIST) {
|
||||
return it->sl.zs->zsl->length;
|
||||
zset *zs = op->subject->ptr;
|
||||
return zs->zsl->length;
|
||||
} else {
|
||||
redisPanic("Unknown sorted set encoding");
|
||||
}
|
||||
@@ -1400,18 +1419,19 @@ 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(it->is.is,val->ell)) {
|
||||
if (zuiLongLongFromValue(val) &&
|
||||
intsetFind(op->subject->ptr,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(it->ht.dict,val->ele) != NULL) {
|
||||
if (dictFind(ht,val->ele) != NULL) {
|
||||
*score = 1.0;
|
||||
return 1;
|
||||
} else {
|
||||
@@ -1421,19 +1441,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(it->zl.zl,val->ele,score) != NULL) {
|
||||
if (zzlFind(op->subject->ptr,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(it->sl.zs->dict,val->ele)) != NULL) {
|
||||
if ((de = dictFind(zs->dict,val->ele)) != NULL) {
|
||||
*score = *(double*)dictGetVal(de);
|
||||
return 1;
|
||||
} else {
|
||||
@@ -1561,9 +1581,6 @@ 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);
|
||||
@@ -1577,6 +1594,7 @@ 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;
|
||||
|
||||
@@ -1610,16 +1628,18 @@ 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 key when already processed */
|
||||
/* Skip an element that when already processed */
|
||||
if (dictFind(dstzset->dict,zuiObjectFromValue(&zval)) != NULL)
|
||||
continue;
|
||||
|
||||
@@ -1627,8 +1647,10 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
score = src[i].weight * zval.score;
|
||||
if (isnan(score)) score = 0;
|
||||
|
||||
/* Because the inputs are sorted by size, it's only possible
|
||||
* for sets at larger indices to hold this element. */
|
||||
/* 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). */
|
||||
for (j = (i+1); j < setnum; j++) {
|
||||
/* It is not safe to access the zset we are
|
||||
* iterating, so explicitly check for equal object. */
|
||||
@@ -1651,14 +1673,12 @@ 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;
|
||||
@@ -1673,10 +1693,15 @@ 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);
|
||||
}
|
||||
|
||||
+60
@@ -405,6 +405,66 @@ 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,6 +30,8 @@
|
||||
#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);
|
||||
@@ -37,5 +39,7 @@ 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.6.11"
|
||||
#define REDIS_VERSION "2.7.105"
|
||||
|
||||
+3
-2
@@ -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,7 +1041,8 @@ void pop(unsigned char *zl, int where) {
|
||||
}
|
||||
|
||||
int randstring(char *target, unsigned int min, unsigned int max) {
|
||||
int p, len = min+rand()%(max-min+1);
|
||||
int p = 0;
|
||||
int len = min+rand()%(max-min+1);
|
||||
int minval, maxval;
|
||||
switch(rand() % 3) {
|
||||
case 0:
|
||||
|
||||
@@ -78,7 +78,6 @@
|
||||
|
||||
#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 _ZIMMAP_H
|
||||
#ifndef _ZIPMAP_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_PROCFS)
|
||||
#if defined(HAVE_PROC_STAT)
|
||||
#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_PROCFS)
|
||||
#if defined(HAVE_PROC_SMAPS)
|
||||
size_t zmalloc_get_private_dirty(void) {
|
||||
char line[1024];
|
||||
size_t pd = 0;
|
||||
|
||||
+4
-323
@@ -1,344 +1,25 @@
|
||||
# Redis configuration file example
|
||||
# Redis configuration for testing.
|
||||
|
||||
# Note on units: when memory size is needed, it is possible to specifiy
|
||||
# it in the usual form of 1k 5GB 4M and so forth:
|
||||
#
|
||||
# 1k => 1000 bytes
|
||||
# 1kb => 1024 bytes
|
||||
# 1m => 1000000 bytes
|
||||
# 1mb => 1024*1024 bytes
|
||||
# 1g => 1000000000 bytes
|
||||
# 1gb => 1024*1024*1024 bytes
|
||||
#
|
||||
# units are case insensitive so 1GB 1Gb 1gB are all the same.
|
||||
|
||||
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
|
||||
notify-keyspace-events KEA
|
||||
daemonize no
|
||||
|
||||
# When running daemonized, Redis writes a pid file in /var/run/redis.pid by
|
||||
# default. You can specify a custom pid file location here.
|
||||
pidfile /var/run/redis.pid
|
||||
|
||||
# Accept connections on the specified port, default is 6379.
|
||||
port 6379
|
||||
|
||||
# If you want you can bind a single interface, if the bind option is not
|
||||
# specified all the interfaces will listen for incoming connections.
|
||||
#
|
||||
# bind 127.0.0.1
|
||||
|
||||
# Specify the path for the unix socket that will be used to listen for
|
||||
# incoming connections. There is no default, so Redis will not listen
|
||||
# on a unix socket when not specified.
|
||||
#
|
||||
# unixsocket /tmp/redis.sock
|
||||
|
||||
# Close the connection after a client is idle for N seconds (0 to disable)
|
||||
timeout 0
|
||||
|
||||
# Set server verbosity to 'debug'
|
||||
# it can be one of:
|
||||
# debug (a lot of information, useful for development/testing)
|
||||
# verbose (many rarely useful info, but not a mess like the debug level)
|
||||
# notice (moderately verbose, what you want in production probably)
|
||||
# warning (only very important / critical messages are logged)
|
||||
bind 127.0.0.1
|
||||
loglevel verbose
|
||||
|
||||
# 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 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.
|
||||
# syslog-enabled no
|
||||
|
||||
# Specify the syslog identity.
|
||||
# syslog-ident redis
|
||||
|
||||
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
|
||||
# syslog-facility local0
|
||||
|
||||
# Set the number of databases. The default database is DB 0, you can select
|
||||
# a different one on a per-connection basis using SELECT <dbid> where
|
||||
# dbid is a number between 0 and 'databases'-1
|
||||
logfile ''
|
||||
databases 16
|
||||
|
||||
################################ SNAPSHOTTING #################################
|
||||
#
|
||||
# Save the DB on disk:
|
||||
#
|
||||
# save <seconds> <changes>
|
||||
#
|
||||
# Will save the DB if both the given number of seconds and the given
|
||||
# number of write operations against the DB occurred.
|
||||
#
|
||||
# In the example below the behaviour will be to save:
|
||||
# after 900 sec (15 min) if at least 1 key changed
|
||||
# after 300 sec (5 min) if at least 10 keys changed
|
||||
# after 60 sec if at least 10000 keys changed
|
||||
#
|
||||
# Note: you can disable saving at all commenting all the "save" lines.
|
||||
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
|
||||
# Compress string objects using LZF when dump .rdb databases?
|
||||
# For default that's set to 'yes' as it's almost always a win.
|
||||
# If you want to save some CPU in the saving child set it to 'no' but
|
||||
# the dataset will likely be bigger if you have compressible values or keys.
|
||||
rdbcompression yes
|
||||
|
||||
# The filename where to dump the DB
|
||||
dbfilename dump.rdb
|
||||
|
||||
# The working directory.
|
||||
#
|
||||
# The DB will be written inside this directory, with the filename specified
|
||||
# above using the 'dbfilename' configuration directive.
|
||||
#
|
||||
# Also the Append Only File will be created inside this directory.
|
||||
#
|
||||
# Note that you must specify a directory here, not a file name.
|
||||
dir ./
|
||||
|
||||
################################# REPLICATION #################################
|
||||
|
||||
# Master-Slave replication. Use slaveof to make a Redis instance a copy of
|
||||
# another Redis server. Note that the configuration is local to the slave
|
||||
# so for example it is possible to configure the slave to save the DB with a
|
||||
# different interval, or to listen to another port, and so on.
|
||||
#
|
||||
# slaveof <masterip> <masterport>
|
||||
|
||||
# If the master is password protected (using the "requirepass" configuration
|
||||
# directive below) it is possible to tell the slave to authenticate before
|
||||
# starting the replication synchronization process, otherwise the master will
|
||||
# refuse the slave request.
|
||||
#
|
||||
# masterauth <master-password>
|
||||
|
||||
# When a slave lost the connection with the master, or when the replication
|
||||
# is still in progress, the slave can act in two different ways:
|
||||
#
|
||||
# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
|
||||
# still reply to client requests, possibly with out of data data, or the
|
||||
# data set may just be empty if this is the first synchronization.
|
||||
#
|
||||
# 2) if slave-serve-stale data is set to 'no' the slave will reply with
|
||||
# an error "SYNC with master in progress" to all the kind of commands
|
||||
# but to INFO and SLAVEOF.
|
||||
#
|
||||
slave-serve-stale-data yes
|
||||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
# Require clients to issue AUTH <PASSWORD> before processing any other
|
||||
# commands. This might be useful in environments in which you do not trust
|
||||
# others with access to the host running redis-server.
|
||||
#
|
||||
# This should stay commented out for backward compatibility and because most
|
||||
# people do not need auth (e.g. they run their own servers).
|
||||
#
|
||||
# Warning: since Redis is pretty fast an outside user can try up to
|
||||
# 150k passwords per second against a good box. This means that you should
|
||||
# use a very strong password otherwise it will be very easy to break.
|
||||
#
|
||||
# requirepass foobared
|
||||
|
||||
# Command renaming.
|
||||
#
|
||||
# It is possilbe to change the name of dangerous commands in a shared
|
||||
# environment. For instance the CONFIG command may be renamed into something
|
||||
# of hard to guess so that it will be still available for internal-use
|
||||
# tools but not available for general clients.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
|
||||
#
|
||||
# It is also possilbe to completely kill a command renaming it into
|
||||
# an empty string:
|
||||
#
|
||||
# rename-command CONFIG ""
|
||||
|
||||
################################### LIMITS ####################################
|
||||
|
||||
# Set the max number of connected clients at the same time. By default there
|
||||
# is no limit, and it's up to the number of file descriptors the Redis process
|
||||
# is able to open. The special value '0' means no limits.
|
||||
# Once the limit is reached Redis will close all the new connections sending
|
||||
# an error 'max number of clients reached'.
|
||||
#
|
||||
# maxclients 128
|
||||
|
||||
# Don't use more memory than the specified amount of bytes.
|
||||
# When the memory limit is reached Redis will try to remove keys with an
|
||||
# EXPIRE set. It will try to start freeing keys that are going to expire
|
||||
# in little time and preserve keys with a longer time to live.
|
||||
# Redis will also try to remove objects from free lists if possible.
|
||||
#
|
||||
# If all this fails, Redis will start to reply with errors to commands
|
||||
# that will use more memory, like SET, LPUSH, and so on, and will continue
|
||||
# to reply to most read-only commands like GET.
|
||||
#
|
||||
# WARNING: maxmemory can be a good idea mainly if you want to use Redis as a
|
||||
# 'state' server or cache, not as a real DB. When Redis is used as a real
|
||||
# database the memory usage will grow over the weeks, it will be obvious if
|
||||
# it is going to use too much memory in the long run, and you'll have the time
|
||||
# to upgrade. With maxmemory after the limit is reached you'll start to get
|
||||
# errors for write operations, and this may even lead to DB inconsistency.
|
||||
#
|
||||
# maxmemory <bytes>
|
||||
|
||||
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
|
||||
# is reached? You can select among five behavior:
|
||||
#
|
||||
# volatile-lru -> remove the key with an expire set using an LRU algorithm
|
||||
# allkeys-lru -> remove any key accordingly to the LRU algorithm
|
||||
# volatile-random -> remove a random key with an expire set
|
||||
# allkeys->random -> remove a random key, any key
|
||||
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
|
||||
# noeviction -> don't expire at all, just return an error on write operations
|
||||
#
|
||||
# Note: with all the kind of policies, Redis will return an error on write
|
||||
# operations, when there are not suitable keys for eviction.
|
||||
#
|
||||
# At the date of writing this commands are: set setnx setex append
|
||||
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
|
||||
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
|
||||
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
|
||||
# getset mset msetnx exec sort
|
||||
#
|
||||
# The default is:
|
||||
#
|
||||
# maxmemory-policy volatile-lru
|
||||
|
||||
# LRU and minimal TTL algorithms are not precise algorithms but approximated
|
||||
# algorithms (in order to save memory), so you can select as well the sample
|
||||
# size to check. For instance for default Redis will check three keys and
|
||||
# pick the one that was used less recently, you can change the sample size
|
||||
# using the following configuration directive.
|
||||
#
|
||||
# maxmemory-samples 3
|
||||
|
||||
############################## APPEND ONLY MODE ###############################
|
||||
|
||||
# By default Redis asynchronously dumps the dataset on disk. If you can live
|
||||
# with the idea that the latest records will be lost if something like a crash
|
||||
# happens this is the preferred way to run Redis. If instead you care a lot
|
||||
# about your data and don't want to that a single record can get lost you should
|
||||
# enable the append only mode: when this mode is enabled Redis will append
|
||||
# every write operation received in the file appendonly.aof. This file will
|
||||
# be read on startup in order to rebuild the full dataset in memory.
|
||||
#
|
||||
# Note that you can have both the async dumps and the append only file if you
|
||||
# like (you have to comment the "save" statements above to disable the dumps).
|
||||
# Still if append only mode is enabled Redis will load the data from the
|
||||
# log file at startup ignoring the dump.rdb file.
|
||||
#
|
||||
# IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append
|
||||
# log file in background when it gets too big.
|
||||
|
||||
appendonly no
|
||||
|
||||
# The name of the append only file (default: "appendonly.aof")
|
||||
# appendfilename appendonly.aof
|
||||
|
||||
# The fsync() call tells the Operating System to actually write data on disk
|
||||
# instead to wait for more data in the output buffer. Some OS will really flush
|
||||
# data on disk, some other OS will just try to do it ASAP.
|
||||
#
|
||||
# Redis supports three different modes:
|
||||
#
|
||||
# no: don't fsync, just let the OS flush the data when it wants. Faster.
|
||||
# always: fsync after every write to the append only log . Slow, Safest.
|
||||
# everysec: fsync only if one second passed since the last fsync. Compromise.
|
||||
#
|
||||
# The default is "everysec" that's usually the right compromise between
|
||||
# speed and data safety. It's up to you to understand if you can relax this to
|
||||
# "no" that will will let the operating system flush the output buffer when
|
||||
# it wants, for better performances (but if you can live with the idea of
|
||||
# some data loss consider the default persistence mode that's snapshotting),
|
||||
# or on the contrary, use "always" that's very slow but a bit safer than
|
||||
# everysec.
|
||||
#
|
||||
# If unsure, use "everysec".
|
||||
|
||||
# appendfsync always
|
||||
appendfsync everysec
|
||||
# appendfsync no
|
||||
|
||||
# When the AOF fsync policy is set to always or everysec, and a background
|
||||
# saving process (a background save or AOF log background rewriting) is
|
||||
# performing a lot of I/O against the disk, in some Linux configurations
|
||||
# Redis may block too long on the fsync() call. Note that there is no fix for
|
||||
# this currently, as even performing fsync in a different thread will block
|
||||
# our synchronous write(2) call.
|
||||
#
|
||||
# In order to mitigate this problem it's possible to use the following option
|
||||
# that will prevent fsync() from being called in the main process while a
|
||||
# BGSAVE or BGREWRITEAOF is in progress.
|
||||
#
|
||||
# This means that while another child is saving the durability of Redis is
|
||||
# the same as "appendfsync none", that in pratical terms means that it is
|
||||
# possible to lost up to 30 seconds of log in the worst scenario (with the
|
||||
# default Linux settings).
|
||||
#
|
||||
# If you have latency problems turn this to "yes". Otherwise leave it as
|
||||
# "no" that is the safest pick from the point of view of durability.
|
||||
no-appendfsync-on-rewrite no
|
||||
|
||||
############################### ADVANCED CONFIG ###############################
|
||||
|
||||
# Hashes are encoded in a special way (much more memory efficient) when they
|
||||
# have at max a given numer of elements, and the biggest element does not
|
||||
# exceed a given threshold. You can configure this limits with the following
|
||||
# configuration directives.
|
||||
hash-max-ziplist-entries 64
|
||||
hash-max-ziplist-value 512
|
||||
|
||||
# Similarly to hashes, small lists are also encoded in a special way in order
|
||||
# to save a lot of space. The special representation is only used when
|
||||
# you are under the following limits:
|
||||
list-max-ziplist-entries 512
|
||||
list-max-ziplist-value 64
|
||||
|
||||
# Sets have a special encoding in just one case: when a set is composed
|
||||
# of just strings that happens to be integers in radix 10 in the range
|
||||
# of 64 bit signed integers.
|
||||
# The following configuration setting sets the limit in the size of the
|
||||
# set in order to use this special memory saving encoding.
|
||||
set-max-intset-entries 512
|
||||
|
||||
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation redis uses (see dict.c)
|
||||
# performs a lazy rehashing: the more operation you run into an hash table
|
||||
# that is rhashing, the more rehashing "steps" are performed, so if the
|
||||
# server is idle the rehashing is never complete and some more memory is used
|
||||
# by the hash table.
|
||||
#
|
||||
# The default is to use this millisecond 10 times every second in order to
|
||||
# active rehashing the main dictionaries, freeing memory when possible.
|
||||
#
|
||||
# If unsure:
|
||||
# use "activerehashing no" if you have hard latency requirements and it is
|
||||
# not a good thing in your environment that Redis can reply form time to time
|
||||
# to queries with 2 milliseconds delay.
|
||||
#
|
||||
# use "activerehashing yes" if you don't have such hard requirements but
|
||||
# want to free memory asap when possible.
|
||||
activerehashing yes
|
||||
|
||||
################################## INCLUDES ###################################
|
||||
|
||||
# Include one or more other config files here. This is useful if you
|
||||
# have a standard template that goes to all redis server but also need
|
||||
# to customize a few per-server settings. Include files can include
|
||||
# other files, so use this wisely.
|
||||
#
|
||||
# include /path/to/local.conf
|
||||
# include /path/to/other.conf
|
||||
|
||||
@@ -98,7 +98,7 @@ tags {"aof"} {
|
||||
}
|
||||
}
|
||||
|
||||
## Test that SPOP (that modifies the client its argc/argv) is correctly free'd
|
||||
## Test that SPOP (that modifies the client's argc/argv) is correctly free'd
|
||||
create_aof {
|
||||
append_to_aof [formatCommand sadd set foo]
|
||||
append_to_aof [formatCommand sadd set bar]
|
||||
|
||||
@@ -23,3 +23,76 @@ start_server [list overrides [list "dir" $server_path "dbfilename" "encodings.rd
|
||||
}
|
||||
}
|
||||
|
||||
set server_path [tmpdir "server.rdb-startup-test"]
|
||||
|
||||
start_server [list overrides [list "dir" $server_path]] {
|
||||
test {Server started empty with non-existing RDB file} {
|
||||
r debug digest
|
||||
} {0000000000000000000000000000000000000000}
|
||||
# Save an RDB file, needed for the next test.
|
||||
r save
|
||||
}
|
||||
|
||||
start_server [list overrides [list "dir" $server_path]] {
|
||||
test {Server started empty with empty RDB file} {
|
||||
r debug digest
|
||||
} {0000000000000000000000000000000000000000}
|
||||
}
|
||||
|
||||
# Helper function to start a server and kill it, just to check the error
|
||||
# logged.
|
||||
set defaults {}
|
||||
proc start_server_and_kill_it {overrides code} {
|
||||
upvar defaults defaults srv srv server_path server_path
|
||||
set config [concat $defaults $overrides]
|
||||
set srv [start_server [list overrides $config]]
|
||||
uplevel 1 $code
|
||||
kill_server $srv
|
||||
}
|
||||
|
||||
# Make the RDB file unreadable
|
||||
file attributes [file join $server_path dump.rdb] -permissions 0222
|
||||
|
||||
# Detect root account (it is able to read the file even with 002 perm)
|
||||
set isroot 0
|
||||
catch {
|
||||
open [file join $server_path dump.rdb]
|
||||
set isroot 1
|
||||
}
|
||||
|
||||
# Now make sure the server aborted with an error
|
||||
if {!$isroot} {
|
||||
start_server_and_kill_it [list "dir" $server_path] {
|
||||
test {Server should not start if RDB file can't be open} {
|
||||
wait_for_condition 50 100 {
|
||||
[string match {*Fatal error loading*} \
|
||||
[exec tail -n1 < [dict get $srv stdout]]]
|
||||
} else {
|
||||
fail "Server started even if RDB was unreadable!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Fix permissions of the RDB file.
|
||||
file attributes [file join $server_path dump.rdb] -permissions 0666
|
||||
|
||||
# Corrupt its CRC64 checksum.
|
||||
set filesize [file size [file join $server_path dump.rdb]]
|
||||
set fd [open [file join $server_path dump.rdb] r+]
|
||||
fconfigure $fd -translation binary
|
||||
seek $fd -8 end
|
||||
puts -nonewline $fd "foobar00"; # Corrupt the checksum
|
||||
close $fd
|
||||
|
||||
# Now make sure the server aborted with an error
|
||||
start_server_and_kill_it [list "dir" $server_path] {
|
||||
test {Server should not start if RDB is corrupted} {
|
||||
wait_for_condition 50 100 {
|
||||
[string match {*RDB checksum*} \
|
||||
[exec tail -n1 < [dict get $srv stdout]]]
|
||||
} else {
|
||||
fail "Server started even if RDB was corrupted!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
test {First server should have role slave after SLAVEOF} {
|
||||
r -1 slaveof [srv 0 host] [srv 0 port]
|
||||
after 1000
|
||||
s -1 role
|
||||
} {slave}
|
||||
wait_for_condition 50 100 {
|
||||
[s -1 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
}
|
||||
|
||||
if {$::accurate} {set numops 50000} else {set numops 5000}
|
||||
|
||||
@@ -29,3 +32,70 @@ 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,21 +10,22 @@ proc stop_bg_complex_data {handle} {
|
||||
start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
set master_port [srv -1 port]
|
||||
set slave [srv 0 client]
|
||||
|
||||
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} {
|
||||
r -1 slaveof [srv 0 host] [srv 0 port]
|
||||
$slave slaveof $master_host $master_port
|
||||
after 1000
|
||||
s -1 role
|
||||
s 0 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
|
||||
@@ -37,7 +38,7 @@ start_server {tags {"repl"}} {
|
||||
}
|
||||
assert {[$master dbsize] > 0}
|
||||
|
||||
if {[r debug digest] ne [r -1 debug digest]} {
|
||||
if {[$master debug digest] ne [$slave debug digest]} {
|
||||
set csv1 [csvdump r]
|
||||
set csv2 [csvdump {r -1}]
|
||||
set fd [open /tmp/repldump1.txt w]
|
||||
@@ -53,3 +54,83 @@ 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}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user