Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8995634014 | ||
|
|
9a8ae5a553 | ||
|
|
0ff078d8d0 | ||
|
|
25ba2e9607 | ||
|
|
563d6b3f98 | ||
|
|
b6610a569d | ||
|
|
26cf5c8ac6 | ||
|
|
303cc97ff9 | ||
|
|
75bf5a4a4a | ||
|
|
8d0083ba25 | ||
|
|
fba0b23e72 | ||
|
|
dceaca1f69 | ||
|
|
bf307cfb4d | ||
|
|
4f9d30b33b | ||
|
|
b67f39da09 | ||
|
|
4c53178c6c | ||
|
|
7f6743a581 | ||
|
|
edae78999c | ||
|
|
3d7263aa41 | ||
|
|
333453646c | ||
|
|
4d650a3baa | ||
|
|
dd0ac4ac72 | ||
|
|
75347ada7f | ||
|
|
83333b08d0 | ||
|
|
50d140e90b | ||
|
|
2eb8a46061 | ||
|
|
a46f841df3 | ||
|
|
e5c577e679 | ||
|
|
dd239c37db | ||
|
|
c61cc761d5 | ||
|
|
d13635b2a9 | ||
|
|
d240202261 | ||
|
|
e83741746c | ||
|
|
4e6d34bd2c | ||
|
|
f75fccafe7 | ||
|
|
312ca4dacc | ||
|
|
c84275ee60 | ||
|
|
0b2639123b | ||
|
|
750b007d7c | ||
|
|
4bd1dd1c53 | ||
|
|
46e2f3468c | ||
|
|
87098a39f0 | ||
|
|
9ecd819ecb | ||
|
|
eeb8cb3305 | ||
|
|
c517f72343 | ||
|
|
812f76a850 | ||
|
|
fc93198ff9 | ||
|
|
44b3684633 | ||
|
|
6d0400f569 | ||
|
|
1bbce5bf01 | ||
|
|
679dc8b09d | ||
|
|
3e1fc6278d | ||
|
|
9a9c0cfaa6 | ||
|
|
93d924ff1c | ||
|
|
a52909c5f2 | ||
|
|
8c3e197040 | ||
|
|
04b1fb0b1a | ||
|
|
0b9853ecdc | ||
|
|
737062745d | ||
|
|
66b03c1a40 | ||
|
|
8ba31c218b | ||
|
|
ccaba966bc | ||
|
|
3c4497e83c | ||
|
|
e15ba6a697 | ||
|
|
1a6abe7d79 | ||
|
|
0eeb0a0782 | ||
|
|
64c8de8657 | ||
|
|
782f9cacaf | ||
|
|
7dbc0a63f5 | ||
|
|
612dbb2a91 | ||
|
|
4ccf807abc | ||
|
|
e98d82c639 | ||
|
|
be19e5450c | ||
|
|
8c551d65a1 | ||
|
|
9577fed8a3 | ||
|
|
fc10fb17da | ||
|
|
b02ef3d59a | ||
|
|
1a1dc3de38 | ||
|
|
9f0e52a13d | ||
|
|
f7604b4c07 | ||
|
|
1569326277 | ||
|
|
9f20780de6 | ||
|
|
2488257ad8 | ||
|
|
6593f33222 | ||
|
|
48acc675dd | ||
|
|
447c2787a0 | ||
|
|
eba4775b5d | ||
|
|
b95c6ed7b7 | ||
|
|
663d79c0d5 | ||
|
|
b72985d7f7 | ||
|
|
fe7f96f18c | ||
|
|
be2ef1b59f | ||
|
|
98682cd178 | ||
|
|
68b911478f | ||
|
|
7d30b3121d | ||
|
|
875bc51909 | ||
|
|
c874e39c45 | ||
|
|
1767998751 | ||
|
|
97810c45e8 | ||
|
|
f7f97bf730 | ||
|
|
3a66e0c157 | ||
|
|
060d56e7eb | ||
|
|
162acd8ac2 | ||
|
|
406be0ea28 | ||
|
|
4b615eea8f | ||
|
|
54a5a7dff8 | ||
|
|
bebbc7f9f7 | ||
|
|
736e343509 | ||
|
|
59b30b327d | ||
|
|
3d3e350b12 | ||
|
|
3cc7d486bc | ||
|
|
06455014aa | ||
|
|
141f30f421 | ||
|
|
c53cab5dde | ||
|
|
ab6f4195dd | ||
|
|
13e879c9cf | ||
|
|
9ea69a58ac | ||
|
|
bd1962acc4 | ||
|
|
02617b6e92 | ||
|
|
bb2df795e0 | ||
|
|
35816a2e4d | ||
|
|
1cc7e646af | ||
|
|
24ecabc995 | ||
|
|
17785f40b5 | ||
|
|
7f063b1c8c | ||
|
|
9e62d9e79c | ||
|
|
1a06bbe430 | ||
|
|
0880bec4e2 | ||
|
|
1d659784f4 | ||
|
|
854b8be616 | ||
|
|
5fa5153bf5 | ||
|
|
108c6e6734 | ||
|
|
21da9b06a4 | ||
|
|
907e06cd61 | ||
|
|
b44f1589a4 | ||
|
|
d1c1f62ed5 | ||
|
|
8cdaf604ce | ||
|
|
33409b4c6c | ||
|
|
fd22106a98 | ||
|
|
8c4ff679fe | ||
|
|
0dd95e23ab | ||
|
|
9546f7846b | ||
|
|
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 | ||
|
|
5eec376c2f | ||
|
|
99d7dbe669 | ||
|
|
a61705dd95 | ||
|
|
16144589f2 | ||
|
|
a25b25f4ef | ||
|
|
ab55180883 | ||
|
|
a03c32702b | ||
|
|
2164523244 | ||
|
|
c3ff470889 | ||
|
|
0e25c0ccf4 | ||
|
|
f4eb4b3352 | ||
|
|
500dddc264 | ||
|
|
a6305f13d5 | ||
|
|
99c3338c23 | ||
|
|
05e06e1543 | ||
|
|
e2f2dab3ef | ||
|
|
e480c279bc | ||
|
|
0c19880ce6 | ||
|
|
2ba962714a | ||
|
|
151b606c11 | ||
|
|
f1466e11cd | ||
|
|
e061d797d7 | ||
|
|
04779bdfe1 | ||
|
|
e04be06e89 | ||
|
|
c4cbffa3a1 | ||
|
|
dfb7194cba | ||
|
|
b8ce9a84c5 | ||
|
|
1f8bd82332 | ||
|
|
ef792fc950 | ||
|
|
de499f7f7e | ||
|
|
b65f3c2176 | ||
|
|
161e137c55 | ||
|
|
d480b9ce7f | ||
|
|
fa23fc3363 | ||
|
|
fc0a0d4aa7 | ||
|
|
ea9bec50c6 | ||
|
|
26a340095d | ||
|
|
524b79d231 | ||
|
|
201ed6d42e | ||
|
|
7c9bfe1036 | ||
|
|
3da75e2ca4 | ||
|
|
e328e41a3a | ||
|
|
8a8e560b87 | ||
|
|
0d0975f2b6 | ||
|
|
af41f6cffb | ||
|
|
999fe0d352 | ||
|
|
f1057534e7 | ||
|
|
fcc8bf9974 | ||
|
|
374eed7d2a | ||
|
|
2085fdb1f4 | ||
|
|
f8a19e32e2 | ||
|
|
7c39b55d42 | ||
|
|
e47236d8d4 | ||
|
|
8ab7e998d1 | ||
|
|
e01a415d37 | ||
|
|
d26a8fb4db | ||
|
|
5b5eb192f5 | ||
|
|
120ba3922d | ||
|
|
2812b945f0 | ||
|
|
31fe053a62 | ||
|
|
dd94771578 | ||
|
|
8b6b1b27cc | ||
|
|
4403862623 | ||
|
|
174518ffb7 | ||
|
|
f444e2afdc | ||
|
|
b58f03a0e8 | ||
|
|
efb54f0593 | ||
|
|
58889867bb | ||
|
|
4c3d419013 | ||
|
|
0671d88cab | ||
|
|
5ddee9b7d5 | ||
|
|
fd2a8951bf | ||
|
|
42a239b888 | ||
|
|
851ac9d072 | ||
|
|
48d26a483d | ||
|
|
edfaa64f49 | ||
|
|
13732168a5 | ||
|
|
06bd3b9acd | ||
|
|
5de75120ba | ||
|
|
32095c4057 | ||
|
|
7fcba9fd9a | ||
|
|
2f44452612 | ||
|
|
89bf6f58fd | ||
|
|
013189e7db | ||
|
|
73d3e8751b | ||
|
|
628890e43e | ||
|
|
4c0c1fff5a | ||
|
|
f00b0844c9 | ||
|
|
889e443ce5 | ||
|
|
3bb3f12539 | ||
|
|
82675c86a6 | ||
|
|
8a8e01f4a7 | ||
|
|
d3d567428a | ||
|
|
dbd8c753c4 | ||
|
|
b3f28b90d2 | ||
|
|
4b3865cbdb | ||
|
|
0c9cf45270 | ||
|
|
6fe9c402a2 | ||
|
|
8361d6c406 | ||
|
|
e612508d38 | ||
|
|
1364395f18 | ||
|
|
0b8441c1b2 | ||
|
|
a85c89854f | ||
|
|
0ea1a9c452 | ||
|
|
1a3e9d951b | ||
|
|
3f12656781 | ||
|
|
591c9e6543 | ||
|
|
ebdcd723a7 | ||
|
|
8f0658cdd0 | ||
|
|
9a8d51add5 | ||
|
|
473f3090f2 | ||
|
|
69396249e5 | ||
|
|
6b4d92e21d | ||
|
|
8bbc0768b8 | ||
|
|
f0d962c0ec | ||
|
|
c19bfe9fcf | ||
|
|
e53a1fdd60 | ||
|
|
951213e2d2 | ||
|
|
d12a68d415 | ||
|
|
7ffa248c7c | ||
|
|
84ec6706cf | ||
|
|
1f40cdd0e5 | ||
|
|
4a789decaa | ||
|
|
bec74b1b9c | ||
|
|
edb952532d | ||
|
|
8152d0c046 | ||
|
|
9b6035bc7f | ||
|
|
27fc5bf582 | ||
|
|
dfa90b5969 | ||
|
|
a5a037bf81 | ||
|
|
4dada1b5bc | ||
|
|
afc6bd1b48 | ||
|
|
4934f93dfb | ||
|
|
14beba7868 | ||
|
|
404223547c | ||
|
|
d318803f94 | ||
|
|
72f30bcd30 | ||
|
|
2aa1efb8a5 | ||
|
|
e67d014d9a | ||
|
|
eee680541b | ||
|
|
a8a981a834 | ||
|
|
f7f2b2610e | ||
|
|
f078d5628d | ||
|
|
3a401464e9 | ||
|
|
064223107e | ||
|
|
25496f4700 | ||
|
|
346825c7ed | ||
|
|
dd4e8203b2 | ||
|
|
91d18504c2 | ||
|
|
f580a3e3a0 | ||
|
|
8afa5b7072 | ||
|
|
af2455be29 | ||
|
|
184b8e78c6 | ||
|
|
d23d73c746 | ||
|
|
0ef889274f | ||
|
|
1b1cf8e7bf | ||
|
|
7f10703b33 | ||
|
|
0cf10e8e86 | ||
|
|
9b43b1ef4d | ||
|
|
0b08d64882 | ||
|
|
96d9c6cb8b | ||
|
|
0f07781538 | ||
|
|
1858da2faa | ||
|
|
83b1092cf2 | ||
|
|
9311d2b527 | ||
|
|
8520066d7b | ||
|
|
8f984bef29 | ||
|
|
dd418873db | ||
|
|
b330de57ff | ||
|
|
18759c927a | ||
|
|
8b97442c20 | ||
|
|
7c5d96d98e | ||
|
|
603adb2b29 | ||
|
|
748f206e3d | ||
|
|
2c0aae760f | ||
|
|
b1aa7183d7 | ||
|
|
77a75fdef5 | ||
|
|
e69e76d442 | ||
|
|
9db4cea235 | ||
|
|
a28ab2a92b | ||
|
|
b1ee7da75a | ||
|
|
d7bad544dc | ||
|
|
8111a803cb | ||
|
|
c16bf717de | ||
|
|
12a042f863 | ||
|
|
717145c1a3 | ||
|
|
dcd4efe9ef | ||
|
|
62bfa66284 | ||
|
|
dd51571cc3 | ||
|
|
ad91404a32 | ||
|
|
9de5d4600a | ||
|
|
38b60dea54 | ||
|
|
69b30cfcb6 | ||
|
|
0a8a1e78dc | ||
|
|
537dafab84 | ||
|
|
7d6bf7956e | ||
|
|
e337b26050 | ||
|
|
590d55a206 | ||
|
|
c11a01a030 | ||
|
|
30e89410d4 | ||
|
|
abfd08f5ad | ||
|
|
ca577d162a | ||
|
|
d54943b76d | ||
|
|
c3312760fe | ||
|
|
1d82bbd432 | ||
|
|
0122cc4f42 | ||
|
|
e10768518c | ||
|
|
5c45ae1f7b | ||
|
|
7a2065ef33 | ||
|
|
eb624e3416 | ||
|
|
ff5e31f74b | ||
|
|
24982f2bbc | ||
|
|
60ef787efa | ||
|
|
bec200ec39 | ||
|
|
a00fcaa671 | ||
|
|
8d12645569 | ||
|
|
d00f51405b | ||
|
|
ae55245d1c | ||
|
|
212bb9ca2e | ||
|
|
727d6dd52a | ||
|
|
a1090c1193 | ||
|
|
68ee18558a | ||
|
|
96aeca4b9d | ||
|
|
d0cd262fdf | ||
|
|
864229585a | ||
|
|
3126e08757 | ||
|
|
61e8825e89 | ||
|
|
95bc195158 | ||
|
|
cb481f432a | ||
|
|
0b27a55f30 | ||
|
|
6cedb4d489 | ||
|
|
59333ffd37 | ||
|
|
9a2dd1eff9 | ||
|
|
c9853f537b | ||
|
|
d63a1716eb | ||
|
|
d2906893e8 | ||
|
|
97cab30993 | ||
|
|
6255a5ae66 | ||
|
|
e387dc52a0 | ||
|
|
3e6a4463e0 | ||
|
|
430602b26c | ||
|
|
236adc2809 | ||
|
|
d298825803 | ||
|
|
206568257a | ||
|
|
50fb330399 | ||
|
|
69ac4d063d | ||
|
|
b9cd703b5b | ||
|
|
79e3df9d72 | ||
|
|
d5ec389585 | ||
|
|
336ba6a152 | ||
|
|
ef278d110f | ||
|
|
de07849e0d | ||
|
|
e74bec56fa | ||
|
|
3ba5eab774 | ||
|
|
b9aa332843 | ||
|
|
3f64694e71 | ||
|
|
fdf8bd4025 | ||
|
|
e95740392b | ||
|
|
39d1e350d9 | ||
|
|
7f4f86f427 | ||
|
|
1bcb45d118 | ||
|
|
b4b923b04b | ||
|
|
bb99f42596 | ||
|
|
9ba4d5a3bb | ||
|
|
08211b25d3 | ||
|
|
55951f9005 | ||
|
|
2cf3f071a5 | ||
|
|
b162e6f133 | ||
|
|
4f0bd607d9 | ||
|
|
7dc1d2ba17 | ||
|
|
eb6bc2e047 | ||
|
|
3984108474 | ||
|
|
8d3f5ce9db | ||
|
|
8382453ecf | ||
|
|
a3ec16f0c2 | ||
|
|
3aad0de2e9 | ||
|
|
81a28fe131 | ||
|
|
b518a83525 | ||
|
|
9f899440d2 | ||
|
|
937abebbe1 | ||
|
|
c537980d98 | ||
|
|
c9adc81e2e | ||
|
|
b811b334ae | ||
|
|
4d57e44839 | ||
|
|
6c52d5ce27 | ||
|
|
09eb448746 | ||
|
|
fa2a27cfeb | ||
|
|
a5b75e9a86 | ||
|
|
df35d87336 | ||
|
|
3c413d59c8 | ||
|
|
e3fd3ccd72 | ||
|
|
fb8409a5c8 | ||
|
|
bde80cb28b | ||
|
|
fbce475270 | ||
|
|
31e2156c4b | ||
|
|
b739506936 | ||
|
|
37cc07dd41 | ||
|
|
285df7b0e0 | ||
|
|
8cf8974a03 | ||
|
|
bff31e129d | ||
|
|
179ee2db78 | ||
|
|
11dae1711f | ||
|
|
f08aa2bf48 | ||
|
|
fa34ba3908 | ||
|
|
4ccf671cc6 | ||
|
|
88bd32f1b7 | ||
|
|
ae15f75089 | ||
|
|
0f51e3c564 | ||
|
|
f1e38b352e | ||
|
|
ed4d4f1145 | ||
|
|
ee704a0ff1 | ||
|
|
2f2e6ad487 | ||
|
|
e51f7d2c17 | ||
|
|
be4f8cccaa | ||
|
|
f3e159bc93 | ||
|
|
dd896e1b8d | ||
|
|
59d884af8c | ||
|
|
e4cd5838fc | ||
|
|
1507be5b01 | ||
|
|
fc030ac7de | ||
|
|
085aaef325 | ||
|
|
92dd4e4301 | ||
|
|
56ff70f8e0 | ||
|
|
38d6976c03 | ||
|
|
efd412f908 | ||
|
|
9ea95e6c64 | ||
|
|
d0407c2d85 | ||
|
|
81f32c7b65 | ||
|
|
754643c1c7 | ||
|
|
0387e9c199 | ||
|
|
29e1976855 | ||
|
|
188a17ed56 | ||
|
|
9542d9d8d7 | ||
|
|
2d04eef425 | ||
|
|
925927fc8f | ||
|
|
27688de10e | ||
|
|
9b962d10a4 | ||
|
|
ab0603812d | ||
|
|
b64281cc0e | ||
|
|
52192552bd | ||
|
|
b54cdfb2b7 | ||
|
|
7b22c44ccd | ||
|
|
9aba884b34 | ||
|
|
3f7ad83398 | ||
|
|
38bb45223a | ||
|
|
054061685a | ||
|
|
7c1cec2525 | ||
|
|
0380c13bbe | ||
|
|
518e7202b2 | ||
|
|
f597910121 | ||
|
|
24b0942275 | ||
|
|
6e6bbac7a5 | ||
|
|
a7ef5ce1b0 | ||
|
|
ea693f0282 | ||
|
|
74760d3ccd | ||
|
|
32f62ed6d0 | ||
|
|
d4e6ce3e97 | ||
|
|
d605fdabfa | ||
|
|
f4df22d1c5 | ||
|
|
78d6a02b0c | ||
|
|
f1eaf57200 | ||
|
|
749817b7c3 | ||
|
|
9fa9ccb04e | ||
|
|
6934832e53 | ||
|
|
f9322fb8ed | ||
|
|
5e473cd8fd | ||
|
|
3e8fcb6d03 | ||
|
|
cfa4b57cb0 | ||
|
|
57a5e54ddc | ||
|
|
41e8e5cb8e | ||
|
|
56de4964ee | ||
|
|
a74ab6478c | ||
|
|
ee61a4b99e | ||
|
|
dfc2545470 | ||
|
|
b014c1f211 | ||
|
|
37180ed9cd | ||
|
|
7551f2a0b1 | ||
|
|
00b3336334 | ||
|
|
571e257db1 |
+191
-11
@@ -1,16 +1,196 @@
|
||||
Hello! This file is just a placeholder, since this is the "unstable" branch
|
||||
of Redis, the place where all the development happens.
|
||||
Redis 2.8 release notes
|
||||
=======================
|
||||
|
||||
There is no release notes for this branch, it gets forked into another branch
|
||||
every time there is a partial feature freeze in order to eventually create
|
||||
a new stable release.
|
||||
** 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.
|
||||
|
||||
Usually "unstable" is stable enough for you to use it in development enviromnets
|
||||
however you should never use it in production environments. It is possible
|
||||
to download the latest stable release here:
|
||||
--------------------------------------------------------------------------------
|
||||
Upgrade urgency levels:
|
||||
|
||||
http://download.redis.io/releases/redis-stable.tar.gz
|
||||
LOW: No need to upgrade unless there are new features you want to use.
|
||||
MODERATE: Program an upgrade of the server, but it's not urgent.
|
||||
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.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
More information is available at http://redis.io
|
||||
--[ Redis 2.8.3 ] Release date: 11 Dec 2013
|
||||
|
||||
Happy hacking!
|
||||
# UPGRADE URGENCY: MODERATE for Redis, HIGH for Sentinel.
|
||||
|
||||
* [FIX] Sentinel instance role sampling fixed, the system is now more
|
||||
reliable during failover and when reconfiguring instances with
|
||||
non matching configuration.
|
||||
* [FIX] Inline requests are now handled even when terminated with just LF.
|
||||
* [FIX] Replication timeout handling greatly improved, now the slave is able
|
||||
to ping the master while removing the old data from memory, and while
|
||||
loading the new RDB file. This avoid false timeouts sensed by
|
||||
masters.
|
||||
* [FIX] Fixed a replication bug involving 32 bit instances and big datasets
|
||||
hard to compress that resulted into more than 2GB of RDB file sent.
|
||||
* [FIX] Return error for inline requests with unbalanced quotes.
|
||||
* [FIX] Publish the slave replication offset even when disconnected from the
|
||||
master if there is still a cached master instance.
|
||||
|
||||
--[ Redis 2.8.2 ] Release date: 2 Dec 2013
|
||||
|
||||
# UPGRADE URGENCY: MODERATE for both Redis and Sentinel.
|
||||
|
||||
* [FIX] Sentinel better desynchronization to avoid split-brain elections
|
||||
where no Sentinel managed to get elected.
|
||||
* [FIX] Stop accepting writes on "MISCONF" error only if master, not slave.
|
||||
* [FIX] Reply to PING with an error on "MISCONF" errors.
|
||||
|
||||
--[ Redis 2.8.1 ] Release date: 25 Nov 2013
|
||||
|
||||
# UPGRADE URGENCY: LOW for Redis, CRITICAL for Senitnel. You don't need to
|
||||
upgrade your Redis instances but it is highly recommended
|
||||
to upgrade and restart all the Sentinel processes.
|
||||
|
||||
* [FIX] Fixed a bug in "new Sentinel" config propagation.
|
||||
* [FIX] Fixed a false positive in Redis tests.
|
||||
|
||||
--[ Redis 2.8.0 ] Release date: 22 Nov 2013
|
||||
|
||||
# UPGRADE URGENCY: LOW, unless you want to upgrade to new Sentinel code.
|
||||
|
||||
* [FIX] Fixed an error in rdbWriteRaw() that should have no practical impact.
|
||||
* [NEW] Log the new master when SLAVEOF command is used.
|
||||
* [NEW] Sentinel code synchronized with the unstable branch, the new Sentinel
|
||||
is a reimplementation that uses more reliable algorithms.
|
||||
|
||||
--[ Redis 2.8 Release Candidate 6 (2.7.106) ] Release date: 6 Nov 2013
|
||||
|
||||
This is the 6th release candidate of Redis 2.8 (official version is 2.7.106).
|
||||
|
||||
# UPGRADE URGENCY: LOW, only new features back ported, no fixes.
|
||||
|
||||
* [NEW] SCAN, SSCAN, HSCAN, ZSCAN commands.
|
||||
|
||||
--[ Redis 2.8 Release Candidate 5 (2.7.105) ] Release date: 9 Oct 2013
|
||||
|
||||
This is the 5th release candidate of Redis 2.8 (official version is 2.7.105).
|
||||
Important bugs fixed inside.
|
||||
|
||||
# UPGRADE URGENCY: HIGH because of many non critical replication bugs fixed.
|
||||
|
||||
* [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.
|
||||
|
||||
--[ Redis 2.8 Release Candidate 4 (2.7.104) ] Release date: 30 Aug 2013
|
||||
|
||||
This is the fourth release candidate of Redis 2.8 (official version is 2.7.104).
|
||||
Important bugs fixed inside.
|
||||
|
||||
# UPGRADE URGENCY: HIGH because of the EVAL memory leak.
|
||||
|
||||
* [FIX] Fixed a serious EVAL memory leak in the Lua stack.
|
||||
* [FIX] Fixed server startup when no IPv6 address exists in any interface.
|
||||
* [FIX] Send MISCONFIG error when BGSAVE fails because can't fork.
|
||||
* [FIX] Memory efficiency with large (> a few kbytes) values improved considerably.
|
||||
* [NEW] DEBUG SDSLEN for sds memory debugging.
|
||||
|
||||
--[ Redis 2.8 Release Candidate 3 (2.7.103) ] Release date: 19 Aug 2013
|
||||
|
||||
This is the third release candidate of Redis 2.8 (official version is 2.7.103).
|
||||
Important bugs fixed inside.
|
||||
|
||||
# UPGRADE URGENCY: HIGH
|
||||
|
||||
* [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.8 Release Candidate 2 (2.7.102) ] Release date: 30 Jul 2013
|
||||
|
||||
This is the second release candidate of Redis 2.8 (official version is 2.7.102).
|
||||
Important bugs fixed inside.
|
||||
|
||||
# UPGRADE URGENCY: HIGH
|
||||
|
||||
* [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".
|
||||
|
||||
--[ Redis 2.8 Release Candidate 1 (2.7.101) ] Release date: 18 Jul 2013
|
||||
|
||||
This is the first release candidate of Redis 2.8 (official version is 2.7.101).
|
||||
|
||||
The following is a list of improvements in Redis 2.8, compared to Redis 2.6.
|
||||
|
||||
* [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.
|
||||
|
||||
Migrating from 2.6 to 2.8
|
||||
=========================
|
||||
|
||||
Redis 2.6 is mostly a strict subset of 2.8. However there are a few things
|
||||
that you should be aware of:
|
||||
|
||||
The following commands changed behavior:
|
||||
|
||||
* 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:
|
||||
|
||||
* 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 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.
|
||||
|
||||
See commit messages for more credits.
|
||||
|
||||
Cheers,
|
||||
Salvatore
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2006-2014, Salvatore Sanfilippo
|
||||
Copyright (c) 2006-2012, Salvatore Sanfilippo
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -130,7 +130,7 @@ it the proper way for a production system, we have a script doing this
|
||||
for Ubuntu and Debian systems:
|
||||
|
||||
% cd utils
|
||||
% ./install_server.sh
|
||||
% ./install_server
|
||||
|
||||
The script will ask you a few questions and will setup everything you need
|
||||
to run Redis properly as a background daemon that will start again on
|
||||
|
||||
Vendored
+232
-696
File diff suppressed because it is too large
Load Diff
Vendored
+24
-35
@@ -3,44 +3,39 @@
|
||||
*
|
||||
* See linenoise.c for more information.
|
||||
*
|
||||
* ------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* HOLDER 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.
|
||||
* * 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 __LINENOISE_H
|
||||
#define __LINENOISE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct linenoiseCompletions {
|
||||
size_t len;
|
||||
char **cvec;
|
||||
@@ -48,19 +43,13 @@ typedef struct linenoiseCompletions {
|
||||
|
||||
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
|
||||
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
|
||||
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
|
||||
void linenoiseAddCompletion(linenoiseCompletions *, char *);
|
||||
|
||||
char *linenoise(const char *prompt);
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
int linenoiseHistorySave(const char *filename);
|
||||
int linenoiseHistoryLoad(const char *filename);
|
||||
int linenoiseHistorySave(char *filename);
|
||||
int linenoiseHistoryLoad(char *filename);
|
||||
void linenoiseClearScreen(void);
|
||||
void linenoiseSetMultiLine(int ml);
|
||||
void linenoisePrintKeyCodes(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __LINENOISE_H */
|
||||
|
||||
+18
-91
@@ -12,26 +12,6 @@
|
||||
#
|
||||
# units are case insensitive so 1GB 1Gb 1gB are all the same.
|
||||
|
||||
################################## 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.
|
||||
#
|
||||
# Notice option "include" won't be rewritten by command "CONFIG REWRITE"
|
||||
# from admin or Redis Sentinel. Since Redis always uses the last processed
|
||||
# line as value of a configuration directive, you'd better put includes
|
||||
# at the beginning of this file to avoid overwriting config change at runtime.
|
||||
#
|
||||
# If instead you are interested in using includes to override configuration
|
||||
# options, it is better to use include as the last line.
|
||||
#
|
||||
# include /path/to/local.conf
|
||||
# include /path/to/other.conf
|
||||
|
||||
################################ GENERAL #####################################
|
||||
|
||||
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
|
||||
daemonize no
|
||||
@@ -44,15 +24,6 @@ pidfile /var/run/redis.pid
|
||||
# If port 0 is specified Redis will not listen on a TCP socket.
|
||||
port 6379
|
||||
|
||||
# TCP listen() backlog.
|
||||
#
|
||||
# In high requests-per-second environments you need an high backlog in order
|
||||
# to avoid slow clients connections issues. Note that the Linux kernel
|
||||
# will silently truncate it to the value of /proc/sys/net/core/somaxconn so
|
||||
# make sure to raise both the value of somaxconn and tcp_max_syn_backlog
|
||||
# in order to get the desired effect.
|
||||
tcp-backlog 511
|
||||
|
||||
# By default Redis listens for connections from all the network interfaces
|
||||
# available on the server. It is possible to listen to just one or multiple
|
||||
# interfaces using the "bind" configuration directive, followed by one or
|
||||
@@ -117,7 +88,7 @@ logfile ""
|
||||
# dbid is a number between 0 and 'databases'-1
|
||||
databases 16
|
||||
|
||||
################################ SNAPSHOTTING ################################
|
||||
################################ SNAPSHOTTING #################################
|
||||
#
|
||||
# Save the DB on disk:
|
||||
#
|
||||
@@ -416,18 +387,15 @@ slave-priority 100
|
||||
#
|
||||
# The default is:
|
||||
#
|
||||
# maxmemory-policy noeviction
|
||||
# maxmemory-policy volatile-lru
|
||||
|
||||
# LRU and minimal TTL algorithms are not precise algorithms but approximated
|
||||
# algorithms (in order to save memory), so you can tune it for speed or
|
||||
# accuracy. For default Redis will check five keys and pick the one that was
|
||||
# used less recently, you can change the sample size using the following
|
||||
# configuration directive.
|
||||
# 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.
|
||||
#
|
||||
# The default of 5 produces good enough results. 10 Approximates very closely
|
||||
# true LRU but costs a bit more CPU. 3 is very fast but not very accurate.
|
||||
#
|
||||
# maxmemory-samples 5
|
||||
# maxmemory-samples 3
|
||||
|
||||
############################## APPEND ONLY MODE ###############################
|
||||
|
||||
@@ -452,8 +420,7 @@ slave-priority 100
|
||||
appendonly no
|
||||
|
||||
# The name of the append only file (default: "appendonly.aof")
|
||||
|
||||
appendfilename "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
|
||||
@@ -500,7 +467,6 @@ appendfsync everysec
|
||||
#
|
||||
# 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
|
||||
|
||||
# Automatic rewrite of the append only file.
|
||||
@@ -541,50 +507,6 @@ auto-aof-rewrite-min-size 64mb
|
||||
# Set it to 0 or a negative value for unlimited execution without warnings.
|
||||
lua-time-limit 5000
|
||||
|
||||
################################ REDIS CLUSTER ###############################
|
||||
#
|
||||
# Normal Redis instances can't be part of a Redis Cluster; only nodes that are
|
||||
# started as cluster nodes can. In order to start a Redis instance as a
|
||||
# cluster node enable the cluster support uncommenting the following:
|
||||
#
|
||||
# cluster-enabled yes
|
||||
|
||||
# Every cluster node has a cluster configuration file. This file is not
|
||||
# intended to be edited by hand. It is created and updated by Redis nodes.
|
||||
# Every Redis Cluster node requires a different cluster configuration file.
|
||||
# Make sure that instances running in the same system does not have
|
||||
# overlapping cluster configuration file names.
|
||||
#
|
||||
# cluster-config-file nodes-6379.conf
|
||||
|
||||
# Cluster node timeout is the amount of milliseconds a node must be unreachable
|
||||
# for it to be considered in failure state.
|
||||
# Most other internal time limits are multiple of the node timeout.
|
||||
#
|
||||
# cluster-node-timeout 15000
|
||||
|
||||
# Cluster slaves are able to migrate to orphaned masters, that are masters
|
||||
# that are left without working slaves. This improves the cluster ability
|
||||
# to resist to failures as otherwise an orphaned master can't be failed over
|
||||
# in case of failure if it has no working slaves.
|
||||
#
|
||||
# Slaves migrate to orphaned masters only if there are still at least a
|
||||
# given number of other working slaves for their old master. This number
|
||||
# is the "migration barrier". A migration barrier of 1 means that a slave
|
||||
# will migrate only if there is at least 1 other working slave for its master
|
||||
# and so forth. It usually reflects the number of slaves you want for every
|
||||
# master in your cluster.
|
||||
#
|
||||
# Default is 1 (slaves migrate only if their masters remain with at least
|
||||
# one slave). To disable migration just set it to a very large value.
|
||||
# A value of 0 can be set but is useful only for debugging and dangerous
|
||||
# in production.
|
||||
#
|
||||
# cluster-migration-barrier 1
|
||||
|
||||
# In order to setup your cluster make sure to read the documentation
|
||||
# available at http://redis.io web site.
|
||||
|
||||
################################## SLOW LOG ###################################
|
||||
|
||||
# The Redis Slow Log is a system to log queries that exceeded a specified
|
||||
@@ -711,7 +633,7 @@ activerehashing yes
|
||||
#
|
||||
# normal -> normal clients
|
||||
# slave -> slave clients and MONITOR clients
|
||||
# pubsub -> clients subscribed to at least one pubsub channel or pattern
|
||||
# pubsub -> clients subcribed to at least one pubsub channel or pattern
|
||||
#
|
||||
# The syntax of every client-output-buffer-limit directive is the following:
|
||||
#
|
||||
@@ -762,7 +684,12 @@ hz 10
|
||||
# big latency spikes.
|
||||
aof-rewrite-incremental-fsync yes
|
||||
|
||||
# Transparently compress string objects in memory using LZF.
|
||||
# For default this is set to no since it may affect performances, however
|
||||
# when caching things like HTML fragments this may result into a big win.
|
||||
memcompression no
|
||||
################################## 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
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||
done
|
||||
|
||||
if [ -z $TCLSH ]
|
||||
then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis Sentinel test"
|
||||
exit 1
|
||||
fi
|
||||
$TCLSH tests/sentinel.tcl $*
|
||||
+5
-5
@@ -6,7 +6,7 @@ port 26379
|
||||
|
||||
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
|
||||
#
|
||||
# Tells Sentinel to monitor this master, and to consider it in O_DOWN
|
||||
# Tells Sentinel to monitor this slave, and to consider it in O_DOWN
|
||||
# (Objectively Down) state only if at least <quorum> sentinels agree.
|
||||
#
|
||||
# Note that whatever is the ODOWN quorum, a Sentinel will require to
|
||||
@@ -63,7 +63,7 @@ sentinel parallel-syncs mymaster 1
|
||||
# times the failover timeout.
|
||||
#
|
||||
# - The time needed for a slave replicating to a wrong master according
|
||||
# to a Sentinel current configuration, to be forced to replicate
|
||||
# to a Sentinel currnet configuration, to be forced to replicate
|
||||
# with the right master, is exactly the failover timeout (counting since
|
||||
# the moment a Sentinel detected the misconfiguration).
|
||||
#
|
||||
@@ -86,10 +86,10 @@ sentinel failover-timeout mymaster 180000
|
||||
# or to reconfigure clients after a failover. The scripts are executed
|
||||
# with the following rules for error handling:
|
||||
#
|
||||
# If script exits with "1" the execution is retried later (up to a maximum
|
||||
# If script exists with "1" the execution is retried later (up to a maximum
|
||||
# number of times currently set to 10).
|
||||
#
|
||||
# If script exits with "2" (or an higher value) the script execution is
|
||||
# If script exists with "2" (or an higher value) the script execution is
|
||||
# not retried.
|
||||
#
|
||||
# If script terminates because it receives a signal the behavior is the same
|
||||
@@ -102,7 +102,7 @@ sentinel failover-timeout mymaster 180000
|
||||
#
|
||||
# sentinel notification-script <master-name> <script-path>
|
||||
#
|
||||
# Call the specified notification script for any sentinel event that is
|
||||
# Call the specified notification script for any sentienl event that is
|
||||
# generated in the WARNING level (for instance -sdown, -odown, and so forth).
|
||||
# This script should notify the system administrator via email, SMS, or any
|
||||
# other messaging system, that there is something wrong with the monitored
|
||||
|
||||
+3
-10
@@ -55,19 +55,15 @@ FINAL_LIBS=-lm
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
INSTALL=cp -pf
|
||||
FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lpthread
|
||||
else ifeq ($(uname_S),Darwin)
|
||||
|
||||
else
|
||||
ifeq ($(uname_S),Darwin)
|
||||
# Darwin (nothing to do)
|
||||
else
|
||||
# All the other OSes (notably Linux)
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
FINAL_LIBS+= -pthread
|
||||
endif
|
||||
endif
|
||||
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
|
||||
@@ -107,7 +103,7 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o
|
||||
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
|
||||
@@ -204,9 +200,6 @@ distclean: clean
|
||||
test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
@(cd ..; ./runtest)
|
||||
|
||||
test-sentinel: $(REDIS_SENTINEL_NAME)
|
||||
@(cd ..; ./runtest-sentinel)
|
||||
|
||||
check: test
|
||||
|
||||
lcov:
|
||||
|
||||
+66
-78
@@ -1,126 +1,114 @@
|
||||
adlist.o: adlist.c adlist.h zmalloc.h
|
||||
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c ae_epoll.c ae_select.c ae_evport.c
|
||||
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c
|
||||
ae_epoll.o: ae_epoll.c
|
||||
ae_evport.o: ae_evport.c
|
||||
ae_kqueue.o: ae_kqueue.c
|
||||
ae_select.o: ae_select.c
|
||||
anet.o: anet.c fmacros.h anet.h
|
||||
aof.o: aof.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 bio.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 bio.h
|
||||
bio.o: bio.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 bio.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 bio.h
|
||||
bitops.o: bitops.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
|
||||
blocked.o: blocked.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
|
||||
cluster.o: cluster.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 cluster.h endianconv.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
|
||||
config.o: config.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 cluster.h
|
||||
crc16.o: crc16.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
|
||||
../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
|
||||
crc64.o: crc64.c
|
||||
db.o: db.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 cluster.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
|
||||
debug.o: debug.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h crc64.h bio.h
|
||||
dict.o: dict.c fmacros.h dict.h zmalloc.h redisassert.h
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h crc64.h bio.h
|
||||
dict.o: dict.c fmacros.h dict.h zmalloc.h
|
||||
endianconv.o: endianconv.c
|
||||
hyperloglog.o: hyperloglog.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
|
||||
intset.o: intset.c intset.h zmalloc.h endianconv.h config.h
|
||||
lzf_c.o: lzf_c.c lzfP.h
|
||||
lzf_d.o: lzf_d.c lzfP.h
|
||||
memtest.o: memtest.c config.h
|
||||
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
|
||||
multi.o: multi.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
|
||||
../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
|
||||
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
|
||||
../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
|
||||
../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
|
||||
../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
|
||||
pqsort.o: pqsort.c
|
||||
pubsub.o: pubsub.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
|
||||
../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
|
||||
rand.o: rand.c
|
||||
rdb.o: rdb.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 lzf.h zipmap.h \
|
||||
endianconv.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 lzf.h zipmap.h \
|
||||
endianconv.h
|
||||
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h \
|
||||
../deps/hiredis/hiredis.h sds.h adlist.h zmalloc.h
|
||||
../deps/hiredis/hiredis.h sds.h adlist.h zmalloc.h
|
||||
redis-check-aof.o: redis-check-aof.c fmacros.h config.h
|
||||
redis-check-dump.o: redis-check-dump.c lzf.h crc64.h
|
||||
redis-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
|
||||
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 cluster.h slowlog.h \
|
||||
bio.h asciilogo.h
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h slowlog.h bio.h \
|
||||
asciilogo.h
|
||||
release.o: release.c release.h version.h crc64.h
|
||||
replication.o: replication.c redis.h fmacros.h config.h \
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
|
||||
rio.h
|
||||
rio.o: rio.c fmacros.h rio.h sds.h util.h crc64.h config.h redis.h \
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h dict.h adlist.h \
|
||||
zmalloc.h anet.h ziplist.h intset.h version.h rdb.h
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
|
||||
rio.h
|
||||
rio.o: rio.c fmacros.h rio.h sds.h util.h crc64.h
|
||||
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 \
|
||||
../deps/lua/src/lauxlib.h ../deps/lua/src/lua.h ../deps/lua/src/lualib.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 \
|
||||
../deps/lua/src/lauxlib.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/lualib.h
|
||||
sds.o: sds.c sds.h zmalloc.h
|
||||
sentinel.o: sentinel.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 \
|
||||
../deps/hiredis/hiredis.h ../deps/hiredis/async.h \
|
||||
../deps/hiredis/hiredis.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 \
|
||||
../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 \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h slowlog.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
|
||||
sort.o: sort.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 pqsort.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 pqsort.h
|
||||
syncio.o: syncio.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
|
||||
../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
|
||||
t_hash.o: t_hash.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
|
||||
../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
|
||||
t_list.o: t_list.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
|
||||
../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
|
||||
t_set.o: t_set.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
|
||||
../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
|
||||
t_string.o: t_string.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
|
||||
../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
|
||||
t_zset.o: t_zset.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
|
||||
util.o: util.c fmacros.h util.h sds.h
|
||||
ziplist.o: ziplist.c zmalloc.h util.h sds.h ziplist.h endianconv.h \
|
||||
config.h redisassert.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
|
||||
util.o: util.c fmacros.h util.h
|
||||
ziplist.o: ziplist.c zmalloc.h util.h ziplist.h endianconv.h config.h
|
||||
zipmap.o: zipmap.c zmalloc.h endianconv.h config.h
|
||||
zmalloc.o: zmalloc.c config.h zmalloc.h
|
||||
|
||||
+32
-77
@@ -163,21 +163,12 @@ int anetTcpKeepAlive(char *err, int fd)
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to
|
||||
* do the actual work. It resolves the hostname "host" and set the string
|
||||
* representation of the IP address into the buffer pointed by "ipbuf".
|
||||
*
|
||||
* If flags is set to ANET_IP_ONLY the function only resolves hostnames
|
||||
* that are actually already IPv4 or IPv6 addresses. This turns the function
|
||||
* into a validating / normalizing function. */
|
||||
int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
|
||||
int flags)
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len)
|
||||
{
|
||||
struct addrinfo hints, *info;
|
||||
int rv;
|
||||
|
||||
memset(&hints,0,sizeof(hints));
|
||||
if (flags & ANET_IP_ONLY) hints.ai_flags = AI_NUMERICHOST;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM; /* specify socktype to avoid dups */
|
||||
|
||||
@@ -197,14 +188,6 @@ int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
|
||||
return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_NONE);
|
||||
}
|
||||
|
||||
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
|
||||
return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_IP_ONLY);
|
||||
}
|
||||
|
||||
static int anetSetReuseAddr(char *err, int fd) {
|
||||
int yes = 1;
|
||||
/* Make sure connection-intensive things like the redis benckmark
|
||||
@@ -234,71 +217,45 @@ static int anetCreateSocket(char *err, int domain) {
|
||||
|
||||
#define ANET_CONNECT_NONE 0
|
||||
#define ANET_CONNECT_NONBLOCK 1
|
||||
static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
char *source_addr, int flags)
|
||||
static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
|
||||
{
|
||||
int s = ANET_ERR, rv;
|
||||
char portstr[6]; /* strlen("65535") + 1; */
|
||||
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
|
||||
int s, rv;
|
||||
char _port[6]; /* strlen("65535"); */
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
|
||||
snprintf(portstr,sizeof(portstr),"%d",port);
|
||||
snprintf(_port,6,"%d",port);
|
||||
memset(&hints,0,sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
if ((rv = getaddrinfo(addr,portstr,&hints,&servinfo)) != 0) {
|
||||
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) {
|
||||
/* Try to create the socket and to connect it.
|
||||
* If we fail in the socket() call, or on connect(), we retry with
|
||||
* the next entry in servinfo. */
|
||||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
|
||||
continue;
|
||||
|
||||
/* if we set err then goto cleanup, otherwise next */
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
|
||||
if (flags & ANET_CONNECT_NONBLOCK && anetNonBlock(err,s) != ANET_OK)
|
||||
goto error;
|
||||
if (source_addr) {
|
||||
int bound = 0;
|
||||
/* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
|
||||
if ((rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)) != 0) {
|
||||
anetSetError(err, "%s", gai_strerror(rv));
|
||||
goto end;
|
||||
}
|
||||
for (b = bservinfo; b != NULL; b = b->ai_next) {
|
||||
if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
|
||||
bound = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bound) {
|
||||
anetSetError(err, "bind: %s", strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
|
||||
/* If the socket is non-blocking, it is ok for connect() to
|
||||
* return an EINPROGRESS error here. */
|
||||
if (errno == EINPROGRESS && flags & ANET_CONNECT_NONBLOCK)
|
||||
goto end;
|
||||
if (errno == EINPROGRESS && flags & ANET_CONNECT_NONBLOCK) goto end;
|
||||
close(s);
|
||||
s = ANET_ERR;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If we ended an iteration of the for loop without errors, we
|
||||
* have a connected socket. Let's return to the caller. */
|
||||
/* break with the socket */
|
||||
goto end;
|
||||
}
|
||||
if (p == NULL)
|
||||
if (p == NULL) {
|
||||
anetSetError(err, "creating socket: %s", strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
if (s != ANET_ERR) {
|
||||
close(s);
|
||||
s = ANET_ERR;
|
||||
}
|
||||
s = ANET_ERR;
|
||||
end:
|
||||
freeaddrinfo(servinfo);
|
||||
return s;
|
||||
@@ -306,17 +263,12 @@ end:
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE);
|
||||
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONE);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,ANET_CONNECT_NONBLOCK);
|
||||
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetUnixGenericConnect(char *err, char *path, int flags)
|
||||
@@ -385,14 +337,17 @@ int anetWrite(int fd, char *buf, int count)
|
||||
return totlen;
|
||||
}
|
||||
|
||||
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len, int backlog) {
|
||||
static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) {
|
||||
if (bind(s,sa,len) == -1) {
|
||||
anetSetError(err, "bind: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
|
||||
if (listen(s, backlog) == -1) {
|
||||
/* Use a backlog of 512 entries. We pass 511 to the listen() call because
|
||||
* the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1);
|
||||
* which will thus give us a backlog of 512 entries */
|
||||
if (listen(s, 511) == -1) {
|
||||
anetSetError(err, "listen: %s", strerror(errno));
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
@@ -410,7 +365,7 @@ static int anetV6Only(char *err, int s) {
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog)
|
||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af)
|
||||
{
|
||||
int s, rv;
|
||||
char _port[6]; /* strlen("65535") */
|
||||
@@ -432,7 +387,7 @@ static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backl
|
||||
|
||||
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,backlog) == ANET_ERR) goto error;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen) == ANET_ERR) goto error;
|
||||
goto end;
|
||||
}
|
||||
if (p == NULL) {
|
||||
@@ -447,17 +402,17 @@ end:
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog)
|
||||
int anetTcpServer(char *err, int port, char *bindaddr)
|
||||
{
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog);
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET);
|
||||
}
|
||||
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog)
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr)
|
||||
{
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog);
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET6);
|
||||
}
|
||||
|
||||
int anetUnixServer(char *err, char *path, mode_t perm, int backlog)
|
||||
int anetUnixServer(char *err, char *path, mode_t perm)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_un sa;
|
||||
@@ -468,7 +423,7 @@ int anetUnixServer(char *err, char *path, mode_t perm, int backlog)
|
||||
memset(&sa,0,sizeof(sa));
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa),backlog) == ANET_ERR)
|
||||
if (anetListen(err,s,(struct sockaddr*)&sa,sizeof(sa)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
if (perm)
|
||||
chmod(sa.sun_path, perm);
|
||||
@@ -526,7 +481,7 @@ int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) {
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) {
|
||||
if (port) *port = 0;
|
||||
*port = 0;
|
||||
ip[0] = '?';
|
||||
ip[1] = '\0';
|
||||
return -1;
|
||||
@@ -548,7 +503,7 @@ int anetSockName(int fd, char *ip, size_t ip_len, int *port) {
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
if (getsockname(fd,(struct sockaddr*)&sa,&salen) == -1) {
|
||||
if (port) *port = 0;
|
||||
*port = 0;
|
||||
ip[0] = '?';
|
||||
ip[1] = '\0';
|
||||
return -1;
|
||||
|
||||
+3
-9
@@ -35,25 +35,19 @@
|
||||
#define ANET_ERR -1
|
||||
#define ANET_ERR_LEN 256
|
||||
|
||||
/* Flags used with certain functions. */
|
||||
#define ANET_NONE 0
|
||||
#define ANET_IP_ONLY (1<<0)
|
||||
|
||||
#if defined(__sun)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr);
|
||||
int anetUnixConnect(char *err, char *path);
|
||||
int anetUnixNonBlockConnect(char *err, char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog);
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog);
|
||||
int anetUnixServer(char *err, char *path, mode_t perm, int backlog);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr);
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr);
|
||||
int anetUnixServer(char *err, char *path, mode_t perm);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
|
||||
int anetUnixAccept(char *err, int serversock);
|
||||
int anetWrite(int fd, char *buf, int count);
|
||||
|
||||
@@ -126,7 +126,7 @@ void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
|
||||
}
|
||||
|
||||
/* Write the buffer (possibly composed of multiple blocks) into the specified
|
||||
* fd. If a short write or any other error happens -1 is returned,
|
||||
* fd. If no short write or any other error happens -1 is returned,
|
||||
* otherwise the number of bytes written is returned. */
|
||||
ssize_t aofRewriteBufferWrite(int fd) {
|
||||
listNode *ln;
|
||||
@@ -226,7 +226,6 @@ int startAppendOnly(void) {
|
||||
*
|
||||
* However if force is set to 1 we'll write regardless of the background
|
||||
* fsync. */
|
||||
#define AOF_WRITE_LOG_ERROR_RATE 30 /* Seconds between errors logging. */
|
||||
void flushAppendOnlyFile(int force) {
|
||||
ssize_t nwritten;
|
||||
int sync_in_progress = 0;
|
||||
@@ -268,76 +267,27 @@ void flushAppendOnlyFile(int force) {
|
||||
* or alike */
|
||||
nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
|
||||
if (nwritten != (signed)sdslen(server.aof_buf)) {
|
||||
static time_t last_write_error_log = 0;
|
||||
int can_log = 0;
|
||||
|
||||
/* Limit logging rate to 1 line per AOF_WRITE_LOG_ERROR_RATE seconds. */
|
||||
if ((server.unixtime - last_write_error_log) > AOF_WRITE_LOG_ERROR_RATE) {
|
||||
can_log = 1;
|
||||
last_write_error_log = server.unixtime;
|
||||
}
|
||||
|
||||
/* Lof the AOF write error and record the error code. */
|
||||
/* Ooops, we are in troubles. The best thing to do for now is
|
||||
* aborting instead of giving the illusion that everything is
|
||||
* working as expected. */
|
||||
if (nwritten == -1) {
|
||||
if (can_log) {
|
||||
redisLog(REDIS_WARNING,"Error writing to the AOF file: %s",
|
||||
strerror(errno));
|
||||
server.aof_last_write_errno = errno;
|
||||
}
|
||||
redisLog(REDIS_WARNING,"Exiting on error writing to the append-only file: %s",strerror(errno));
|
||||
} else {
|
||||
if (can_log) {
|
||||
redisLog(REDIS_WARNING,"Short write while writing to "
|
||||
"the AOF file: (nwritten=%lld, "
|
||||
"expected=%lld)",
|
||||
(long long)nwritten,
|
||||
(long long)sdslen(server.aof_buf));
|
||||
}
|
||||
redisLog(REDIS_WARNING,"Exiting on short write while writing to "
|
||||
"the append-only file: %s (nwritten=%ld, "
|
||||
"expected=%ld)",
|
||||
strerror(errno),
|
||||
(long)nwritten,
|
||||
(long)sdslen(server.aof_buf));
|
||||
|
||||
if (ftruncate(server.aof_fd, server.aof_current_size) == -1) {
|
||||
if (can_log) {
|
||||
redisLog(REDIS_WARNING, "Could not remove short write "
|
||||
"from the append-only file. Redis may refuse "
|
||||
"to load the AOF the next time it starts. "
|
||||
"ftruncate: %s", strerror(errno));
|
||||
}
|
||||
} else {
|
||||
/* If the ftrunacate() succeeded we can set nwritten to
|
||||
* -1 since there is no longer partial data into the AOF. */
|
||||
nwritten = -1;
|
||||
redisLog(REDIS_WARNING, "Could not remove short write "
|
||||
"from the append-only file. Redis may refuse "
|
||||
"to load the AOF the next time it starts. "
|
||||
"ftruncate: %s", strerror(errno));
|
||||
}
|
||||
server.aof_last_write_errno = ENOSPC;
|
||||
}
|
||||
|
||||
/* Handle the AOF write error. */
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
/* We can't recover when the fsync policy is ALWAYS since the
|
||||
* reply for the client is already in the output buffers, and we
|
||||
* have the contract with the user that on acknowledged write data
|
||||
* is synched on disk. */
|
||||
redisLog(REDIS_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
|
||||
exit(1);
|
||||
} else {
|
||||
/* Recover from failed write leaving data into the buffer. However
|
||||
* set an error to stop accepting writes as long as the error
|
||||
* condition is not cleared. */
|
||||
server.aof_last_write_status = REDIS_ERR;
|
||||
|
||||
/* Trim the sds buffer if there was a partial write, and there
|
||||
* was no way to undo it with ftruncate(2). */
|
||||
if (nwritten > 0) {
|
||||
server.aof_current_size += nwritten;
|
||||
sdsrange(server.aof_buf,nwritten,-1);
|
||||
}
|
||||
return; /* We'll try again on the next call... */
|
||||
}
|
||||
} else {
|
||||
/* Successful write(2). If AOF was in error state, restore the
|
||||
* OK state and log the event. */
|
||||
if (server.aof_last_write_status == REDIS_ERR) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"AOF write error looks solved, Redis can write again.");
|
||||
server.aof_last_write_status = REDIS_OK;
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
server.aof_current_size += nwritten;
|
||||
|
||||
@@ -499,7 +449,6 @@ struct redisClient *createFakeClient(void) {
|
||||
c->argv = NULL;
|
||||
c->bufpos = 0;
|
||||
c->flags = 0;
|
||||
c->btype = REDIS_BLOCKED_NONE;
|
||||
/* We set the fake client as a slave waiting for the synchronization
|
||||
* so that Redis will not try to send replies to this client. */
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
@@ -642,7 +591,7 @@ int rioWriteBulkObject(rio *r, robj *obj) {
|
||||
* in a child process when this function is called). */
|
||||
if (obj->encoding == REDIS_ENCODING_INT) {
|
||||
return rioWriteBulkLongLong(r,(long)obj->ptr);
|
||||
} else if (sdsEncodedObject(obj)) {
|
||||
} else if (obj->encoding == REDIS_ENCODING_RAW) {
|
||||
return rioWriteBulkString(r,obj->ptr,sdslen(obj->ptr));
|
||||
} else {
|
||||
redisPanic("Unknown string encoding");
|
||||
@@ -969,9 +918,9 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
}
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
if (fflush(fp) == EOF) goto werr;
|
||||
if (aof_fsync(fileno(fp)) == -1) goto werr;
|
||||
if (fclose(fp) == EOF) goto werr;
|
||||
fflush(fp);
|
||||
aof_fsync(fileno(fp));
|
||||
fclose(fp);
|
||||
|
||||
/* Use RENAME to make sure the DB file is changed atomically only
|
||||
* if the generate DB file is ok. */
|
||||
|
||||
+15
-192
@@ -60,18 +60,11 @@ static int getBitOffsetFromArgument(redisClient *c, robj *o, size_t *offset) {
|
||||
* work with a input string length up to 512 MB. */
|
||||
size_t redisPopcount(void *s, long count) {
|
||||
size_t bits = 0;
|
||||
unsigned char *p = s;
|
||||
uint32_t *p4;
|
||||
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};
|
||||
|
||||
/* Count initial bytes not aligned to 32 bit. */
|
||||
while((unsigned long)p & 3 && count) {
|
||||
bits += bitsinbyte[*p++];
|
||||
count--;
|
||||
}
|
||||
|
||||
/* Count bits 16 bytes at a time */
|
||||
p4 = (uint32_t*)p;
|
||||
while(count>=16) {
|
||||
uint32_t aux1, aux2, aux3, aux4;
|
||||
|
||||
@@ -94,99 +87,12 @@ size_t redisPopcount(void *s, long count) {
|
||||
((((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +
|
||||
((((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24);
|
||||
}
|
||||
/* Count the remaining bytes. */
|
||||
/* Count the remaining bytes */
|
||||
p = (unsigned char*)p4;
|
||||
while(count--) bits += bitsinbyte[*p++];
|
||||
return bits;
|
||||
}
|
||||
|
||||
/* Return the position of the first bit set to one (if 'bit' is 1) or
|
||||
* zero (if 'bit' is 0) in the bitmap starting at 's' and long 'count' bytes.
|
||||
*
|
||||
* The function is guaranteed to return a value >= 0 if 'bit' is 0 since if
|
||||
* no zero bit is found, it returns count*8 assuming the string is zero
|
||||
* padded on the right. However if 'bit' is 1 it is possible that there is
|
||||
* not a single set bit in the bitmap. In this special case -1 is returned. */
|
||||
long redisBitpos(void *s, long count, int bit) {
|
||||
unsigned long *l;
|
||||
unsigned char *c;
|
||||
unsigned long skipval, word = 0, one;
|
||||
long pos = 0; /* Position of bit, to return to the caller. */
|
||||
int j;
|
||||
|
||||
/* Process whole words first, seeking for first word that is not
|
||||
* all ones or all zeros respectively if we are lookig for zeros
|
||||
* or ones. This is much faster with large strings having contiguous
|
||||
* blocks of 1 or 0 bits compared to the vanilla bit per bit processing.
|
||||
*
|
||||
* Note that if we start from an address that is not aligned
|
||||
* to sizeof(unsigned long) we consume it byte by byte until it is
|
||||
* aligned. */
|
||||
|
||||
/* Skip initial bits not aligned to sizeof(unsigned long) byte by byte. */
|
||||
skipval = bit ? 0 : UCHAR_MAX;
|
||||
c = (unsigned char*) s;
|
||||
while((unsigned long)c & (sizeof(*l)-1) && count) {
|
||||
if (*c != skipval) break;
|
||||
c++;
|
||||
count--;
|
||||
pos += 8;
|
||||
}
|
||||
|
||||
/* Skip bits with full word step. */
|
||||
skipval = bit ? 0 : ULONG_MAX;
|
||||
l = (unsigned long*) c;
|
||||
while (count >= sizeof(*l)) {
|
||||
if (*l != skipval) break;
|
||||
l++;
|
||||
count -= sizeof(*l);
|
||||
pos += sizeof(*l)*8;
|
||||
}
|
||||
|
||||
/* Load bytes into "word" considering the first byte as the most significant
|
||||
* (we basically consider it as written in big endian, since we consider the
|
||||
* string as a set of bits from left to right, with the first bit at position
|
||||
* zero.
|
||||
*
|
||||
* Note that the loading is designed to work even when the bytes left
|
||||
* (count) are less than a full word. We pad it with zero on the right. */
|
||||
c = (unsigned char*)l;
|
||||
for (j = 0; j < sizeof(*l); j++) {
|
||||
word <<= 8;
|
||||
if (count) {
|
||||
word |= *c;
|
||||
c++;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Special case:
|
||||
* If bits in the string are all zero and we are looking for one,
|
||||
* return -1 to signal that there is not a single "1" in the whole
|
||||
* string. This can't happen when we are looking for "0" as we assume
|
||||
* that the right of the string is zero padded. */
|
||||
if (bit == 1 && word == 0) return -1;
|
||||
|
||||
/* Last word left, scan bit by bit. The first thing we need is to
|
||||
* have a single "1" set in the most significant position in an
|
||||
* unsigned long. We don't know the size of the long so we use a
|
||||
* simple trick. */
|
||||
one = ULONG_MAX; /* All bits set to 1.*/
|
||||
one >>= 1; /* All bits set to 1 but the MSB. */
|
||||
one = ~one; /* All bits set to 0 but the MSB. */
|
||||
|
||||
while(one) {
|
||||
if (((one & word) != 0) == bit) return pos;
|
||||
pos++;
|
||||
one >>= 1;
|
||||
}
|
||||
|
||||
/* If we reached this point, there is a bug in the algorithm, since
|
||||
* the case of no match is handled as a special case before. */
|
||||
redisPanic("End of redisBitpos() reached.");
|
||||
return 0; /* Just to avoid warnings. */
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Bits related string commands: GETBIT, SETBIT, BITCOUNT, BITOP.
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -223,7 +129,14 @@ void setbitCommand(redisClient *c) {
|
||||
dbAdd(c->db,c->argv[1],o);
|
||||
} else {
|
||||
if (checkType(c,o,REDIS_STRING)) return;
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
|
||||
/* Create a copy when the object is shared or encoded. */
|
||||
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbOverwrite(c->db,c->argv[1],o);
|
||||
}
|
||||
}
|
||||
|
||||
/* Grow sds value to the right length if necessary */
|
||||
@@ -261,13 +174,12 @@ void getbitCommand(redisClient *c) {
|
||||
|
||||
byte = bitoffset >> 3;
|
||||
bit = 7 - (bitoffset & 0x7);
|
||||
if (lzfEncodedObject(o)) o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
if (sdsEncodedObject(o)) {
|
||||
if (byte < sdslen(o->ptr))
|
||||
bitval = ((uint8_t*)o->ptr)[byte] & (1 << bit);
|
||||
} else {
|
||||
if (o->encoding != REDIS_ENCODING_RAW) {
|
||||
if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr))
|
||||
bitval = llbuf[byte] & (1 << bit);
|
||||
} else {
|
||||
if (byte < sdslen(o->ptr))
|
||||
bitval = ((uint8_t*)o->ptr)[byte] & (1 << bit);
|
||||
}
|
||||
|
||||
addReply(c, bitval ? shared.cone : shared.czero);
|
||||
@@ -458,7 +370,6 @@ void bitcountCommand(redisClient *c) {
|
||||
|
||||
/* Set the 'p' pointer to the string, that can be just a stack allocated
|
||||
* array if our string was integer encoded. */
|
||||
if (lzfEncodedObject(o)) o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
if (o->encoding == REDIS_ENCODING_INT) {
|
||||
p = (unsigned char*) llbuf;
|
||||
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
|
||||
@@ -499,91 +410,3 @@ void bitcountCommand(redisClient *c) {
|
||||
addReplyLongLong(c,redisPopcount(p+start,bytes));
|
||||
}
|
||||
}
|
||||
|
||||
/* BITPOS key bit [start [end]] */
|
||||
void bitposCommand(redisClient *c) {
|
||||
robj *o;
|
||||
long bit, start, end, strlen;
|
||||
unsigned char *p;
|
||||
char llbuf[32];
|
||||
int end_given = 0;
|
||||
|
||||
/* Parse the bit argument to understand what we are looking for, set
|
||||
* or clear bits. */
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&bit,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (bit != 0 && bit != 1) {
|
||||
addReplyError(c, "The bit argument must be 1 or 0.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the key does not exist, from our point of view it is an infinite
|
||||
* array of 0 bits. If the user is looking for the fist clear bit return 0,
|
||||
* If the user is looking for the first set bit, return -1. */
|
||||
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
|
||||
addReplyLongLong(c, bit ? -1 : 0);
|
||||
return;
|
||||
}
|
||||
if (checkType(c,o,REDIS_STRING)) return;
|
||||
|
||||
/* Set the 'p' pointer to the string, that can be just a stack allocated
|
||||
* array if our string was integer encoded. */
|
||||
if (lzfEncodedObject(o)) o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
if (o->encoding == REDIS_ENCODING_INT) {
|
||||
p = (unsigned char*) llbuf;
|
||||
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
|
||||
} else {
|
||||
p = (unsigned char*) o->ptr;
|
||||
strlen = sdslen(o->ptr);
|
||||
}
|
||||
|
||||
/* Parse start/end range if any. */
|
||||
if (c->argc == 4 || c->argc == 5) {
|
||||
if (getLongFromObjectOrReply(c,c->argv[3],&start,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (c->argc == 5) {
|
||||
if (getLongFromObjectOrReply(c,c->argv[4],&end,NULL) != REDIS_OK)
|
||||
return;
|
||||
end_given = 1;
|
||||
} else {
|
||||
end = strlen-1;
|
||||
}
|
||||
/* Convert negative indexes */
|
||||
if (start < 0) start = strlen+start;
|
||||
if (end < 0) end = strlen+end;
|
||||
if (start < 0) start = 0;
|
||||
if (end < 0) end = 0;
|
||||
if (end >= strlen) end = strlen-1;
|
||||
} else if (c->argc == 3) {
|
||||
/* The whole string. */
|
||||
start = 0;
|
||||
end = strlen-1;
|
||||
} else {
|
||||
/* Syntax error. */
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* For empty ranges (start > end) we return -1 as an empty range does
|
||||
* not contain a 0 nor a 1. */
|
||||
if (start > end) {
|
||||
addReplyLongLong(c, -1);
|
||||
} else {
|
||||
long bytes = end-start+1;
|
||||
long pos = redisBitpos(p+start,bytes,bit);
|
||||
|
||||
/* If we are looking for clear bits, and the user specified an exact
|
||||
* range with start-end, we can't consider the right of the range as
|
||||
* zero padded (as we do when no explicit end is given).
|
||||
*
|
||||
* So if redisBitpos() returns the first bit outside the range,
|
||||
* we return -1 to the caller, to mean, in the specified range there
|
||||
* is not a single "0" bit. */
|
||||
if (end_given && bit == 0 && pos == bytes*8) {
|
||||
addReplyLongLong(c,-1);
|
||||
return;
|
||||
}
|
||||
if (pos != -1) pos += start*8; /* Adjust for the bytes we skipped. */
|
||||
addReplyLongLong(c,pos);
|
||||
}
|
||||
}
|
||||
|
||||
-158
@@ -1,158 +0,0 @@
|
||||
/* blocked.c - generic support for blocking operations like BLPOP & WAIT.
|
||||
*
|
||||
* Copyright (c) 2009-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.
|
||||
*
|
||||
* ---------------------------------------------------------------------------
|
||||
*
|
||||
* API:
|
||||
*
|
||||
* getTimeoutFromObjectOrReply() is just an utility function to parse a
|
||||
* timeout argument since blocking operations usually require a timeout.
|
||||
*
|
||||
* blockClient() set the REDIS_BLOCKED flag in the client, and set the
|
||||
* specified block type 'btype' filed to one of REDIS_BLOCKED_* macros.
|
||||
*
|
||||
* unblockClient() unblocks the client doing the following:
|
||||
* 1) It calls the btype-specific function to cleanup the state.
|
||||
* 2) It unblocks the client by unsetting the REDIS_BLOCKED flag.
|
||||
* 3) It puts the client into a list of just unblocked clients that are
|
||||
* processed ASAP in the beforeSleep() event loop callback, so that
|
||||
* if there is some query buffer to process, we do it. This is also
|
||||
* required because otherwise there is no 'readable' event fired, we
|
||||
* already read the pending commands. We also set the REDIS_UNBLOCKED
|
||||
* flag to remember the client is in the unblocked_clients list.
|
||||
*
|
||||
* processUnblockedClients() is called inside the beforeSleep() function
|
||||
* to process the query buffer from unblocked clients and remove the clients
|
||||
* from the blocked_clients queue.
|
||||
*
|
||||
* replyToBlockedClientTimedOut() is called by the cron function when
|
||||
* a client blocked reaches the specified timeout (if the timeout is set
|
||||
* to 0, no timeout is processed).
|
||||
* It usually just needs to send a reply to the client.
|
||||
*
|
||||
* When implementing a new type of blocking opeation, the implementation
|
||||
* should modify unblockClient() and replyToBlockedClientTimedOut() in order
|
||||
* to handle the btype-specific behavior of this two functions.
|
||||
*/
|
||||
|
||||
#include "redis.h"
|
||||
|
||||
/* Get a timeout value from an object and store it into 'timeout'.
|
||||
* The final timeout is always stored as milliseconds as a time where the
|
||||
* timeout will expire, however the parsing is performed according to
|
||||
* the 'unit' that can be seconds or milliseconds.
|
||||
*
|
||||
* Note that if the timeout is zero (usually from the point of view of
|
||||
* commands API this means no timeout) the value stored into 'timeout'
|
||||
* is zero. */
|
||||
int getTimeoutFromObjectOrReply(redisClient *c, robj *object, mstime_t *timeout, int unit) {
|
||||
long long tval;
|
||||
|
||||
if (getLongLongFromObjectOrReply(c,object,&tval,
|
||||
"timeout is not an integer or out of range") != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
|
||||
if (tval < 0) {
|
||||
addReplyError(c,"timeout is negative");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (tval > 0) {
|
||||
if (unit == UNIT_SECONDS) tval *= 1000;
|
||||
tval += mstime();
|
||||
}
|
||||
*timeout = tval;
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Block a client for the specific operation type. Once the REDIS_BLOCKED
|
||||
* flag is set client query buffer is not longer processed, but accumulated,
|
||||
* and will be processed when the client is unblocked. */
|
||||
void blockClient(redisClient *c, int btype) {
|
||||
c->flags |= REDIS_BLOCKED;
|
||||
c->btype = btype;
|
||||
server.bpop_blocked_clients++;
|
||||
}
|
||||
|
||||
/* This function is called in the beforeSleep() function of the event loop
|
||||
* in order to process the pending input buffer of clients that were
|
||||
* unblocked after a blocking operation. */
|
||||
void processUnblockedClients(void) {
|
||||
listNode *ln;
|
||||
redisClient *c;
|
||||
|
||||
while (listLength(server.unblocked_clients)) {
|
||||
ln = listFirst(server.unblocked_clients);
|
||||
redisAssert(ln != NULL);
|
||||
c = ln->value;
|
||||
listDelNode(server.unblocked_clients,ln);
|
||||
c->flags &= ~REDIS_UNBLOCKED;
|
||||
c->btype = REDIS_BLOCKED_NONE;
|
||||
|
||||
/* Process remaining data in the input buffer. */
|
||||
if (c->querybuf && sdslen(c->querybuf) > 0) {
|
||||
server.current_client = c;
|
||||
processInputBuffer(c);
|
||||
server.current_client = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Unblock a client calling the right function depending on the kind
|
||||
* of operation the client is blocking for. */
|
||||
void unblockClient(redisClient *c) {
|
||||
if (c->btype == REDIS_BLOCKED_LIST) {
|
||||
unblockClientWaitingData(c);
|
||||
} else if (c->btype == REDIS_BLOCKED_WAIT) {
|
||||
unblockClientWaitingReplicas(c);
|
||||
} else {
|
||||
redisPanic("Unknown btype in unblockClient().");
|
||||
}
|
||||
/* Clear the flags, and put the client in the unblocked list so that
|
||||
* we'll process new commands in its query buffer ASAP. */
|
||||
c->flags &= ~REDIS_BLOCKED;
|
||||
c->flags |= REDIS_UNBLOCKED;
|
||||
c->btype = REDIS_BLOCKED_NONE;
|
||||
server.bpop_blocked_clients--;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
|
||||
/* This function gets called when a blocked client timed out in order to
|
||||
* send it a reply of some kind. */
|
||||
void replyToBlockedClientTimedOut(redisClient *c) {
|
||||
if (c->btype == REDIS_BLOCKED_LIST) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
} else if (c->btype == REDIS_BLOCKED_WAIT) {
|
||||
addReplyLongLong(c,replicationCountAcksByOffset(c->bpop.reploffset));
|
||||
} else {
|
||||
redisPanic("Unknown btype in replyToBlockedClientTimedOut().");
|
||||
}
|
||||
}
|
||||
|
||||
-4182
File diff suppressed because it is too large
Load Diff
-237
@@ -1,237 +0,0 @@
|
||||
#ifndef __REDIS_CLUSTER_H
|
||||
#define __REDIS_CLUSTER_H
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Redis cluster data structures, defines, exported API.
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#define REDIS_CLUSTER_SLOTS 16384
|
||||
#define REDIS_CLUSTER_OK 0 /* Everything looks ok */
|
||||
#define REDIS_CLUSTER_FAIL 1 /* The cluster can't work */
|
||||
#define REDIS_CLUSTER_NAMELEN 40 /* sha1 hex length */
|
||||
#define REDIS_CLUSTER_PORT_INCR 10000 /* Cluster port = baseport + PORT_INCR */
|
||||
|
||||
/* The following defines are amunt of time, sometimes expressed as
|
||||
* multiplicators of the node timeout value (when ending with MULT). */
|
||||
#define REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT 15000
|
||||
#define REDIS_CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */
|
||||
#define REDIS_CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if master is back. */
|
||||
#define REDIS_CLUSTER_FAIL_UNDO_TIME_ADD 10 /* Some additional time. */
|
||||
#define REDIS_CLUSTER_SLAVE_VALIDITY_MULT 10 /* Slave data validity. */
|
||||
#define REDIS_CLUSTER_FAILOVER_DELAY 5 /* Seconds */
|
||||
#define REDIS_CLUSTER_DEFAULT_MIGRATION_BARRIER 1
|
||||
#define REDIS_CLUSTER_MF_TIMEOUT 5000 /* Milliseconds to do a manual failover. */
|
||||
#define REDIS_CLUSTER_MF_PAUSE_MULT 2 /* Master pause manual failover mult. */
|
||||
|
||||
/* Redirection errors returned by getNodeByQuery(). */
|
||||
#define REDIS_CLUSTER_REDIR_NONE 0 /* Node can serve the request. */
|
||||
#define REDIS_CLUSTER_REDIR_CROSS_SLOT 1 /* Keys in different slots. */
|
||||
#define REDIS_CLUSTER_REDIR_UNSTABLE 2 /* Keys in slot resharding. */
|
||||
#define REDIS_CLUSTER_REDIR_ASK 3 /* -ASK redirection required. */
|
||||
#define REDIS_CLUSTER_REDIR_MOVED 4 /* -MOVED redirection required. */
|
||||
|
||||
struct clusterNode;
|
||||
|
||||
/* clusterLink encapsulates everything needed to talk with a remote node. */
|
||||
typedef struct clusterLink {
|
||||
mstime_t ctime; /* Link creation time */
|
||||
int fd; /* TCP socket file descriptor */
|
||||
sds sndbuf; /* Packet send buffer */
|
||||
sds rcvbuf; /* Packet reception buffer */
|
||||
struct clusterNode *node; /* Node related to this link if any, or NULL */
|
||||
} clusterLink;
|
||||
|
||||
/* Cluster node flags and macros. */
|
||||
#define REDIS_NODE_MASTER 1 /* The node is a master */
|
||||
#define REDIS_NODE_SLAVE 2 /* The node is a slave */
|
||||
#define REDIS_NODE_PFAIL 4 /* Failure? Need acknowledge */
|
||||
#define REDIS_NODE_FAIL 8 /* The node is believed to be malfunctioning */
|
||||
#define REDIS_NODE_MYSELF 16 /* This node is myself */
|
||||
#define REDIS_NODE_HANDSHAKE 32 /* We have still to exchange the first ping */
|
||||
#define REDIS_NODE_NOADDR 64 /* We don't know the address of this node */
|
||||
#define REDIS_NODE_MEET 128 /* Send a MEET message to this node */
|
||||
#define REDIS_NODE_PROMOTED 256 /* Master was a slave propoted by failover */
|
||||
#define REDIS_NODE_NULL_NAME "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
|
||||
|
||||
#define nodeIsMaster(n) ((n)->flags & REDIS_NODE_MASTER)
|
||||
#define nodeIsSlave(n) ((n)->flags & REDIS_NODE_SLAVE)
|
||||
#define nodeInHandshake(n) ((n)->flags & REDIS_NODE_HANDSHAKE)
|
||||
#define nodeHasAddr(n) (!((n)->flags & REDIS_NODE_NOADDR))
|
||||
#define nodeWithoutAddr(n) ((n)->flags & REDIS_NODE_NOADDR)
|
||||
#define nodeTimedOut(n) ((n)->flags & REDIS_NODE_PFAIL)
|
||||
#define nodeFailed(n) ((n)->flags & REDIS_NODE_FAIL)
|
||||
|
||||
/* This structure represent elements of node->fail_reports. */
|
||||
struct clusterNodeFailReport {
|
||||
struct clusterNode *node; /* Node reporting the failure condition. */
|
||||
mstime_t time; /* Time of the last report from this node. */
|
||||
} typedef clusterNodeFailReport;
|
||||
|
||||
struct clusterNode {
|
||||
mstime_t ctime; /* Node object creation time. */
|
||||
char name[REDIS_CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
|
||||
int flags; /* REDIS_NODE_... */
|
||||
uint64_t configEpoch; /* Last configEpoch observed for this node */
|
||||
unsigned char slots[REDIS_CLUSTER_SLOTS/8]; /* slots handled by this node */
|
||||
int numslots; /* Number of slots handled by this node */
|
||||
int numslaves; /* Number of slave nodes, if this is a master */
|
||||
struct clusterNode **slaves; /* pointers to slave nodes */
|
||||
struct clusterNode *slaveof; /* pointer to the master node */
|
||||
mstime_t ping_sent; /* Unix time we sent latest ping */
|
||||
mstime_t pong_received; /* Unix time we received the pong */
|
||||
mstime_t fail_time; /* Unix time when FAIL flag was set */
|
||||
mstime_t voted_time; /* Last time we voted for a slave of this master */
|
||||
mstime_t repl_offset_time; /* Unix time we received offset for this node */
|
||||
long long repl_offset; /* Last known repl offset for this node. */
|
||||
char ip[REDIS_IP_STR_LEN]; /* Latest known IP address of this node */
|
||||
int port; /* Latest known port of this node */
|
||||
clusterLink *link; /* TCP/IP link with this node */
|
||||
list *fail_reports; /* List of nodes signaling this as failing */
|
||||
};
|
||||
typedef struct clusterNode clusterNode;
|
||||
|
||||
typedef struct clusterState {
|
||||
clusterNode *myself; /* This node */
|
||||
uint64_t currentEpoch;
|
||||
int state; /* REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL, ... */
|
||||
int size; /* Num of master nodes with at least one slot */
|
||||
dict *nodes; /* Hash table of name -> clusterNode structures */
|
||||
dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */
|
||||
clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];
|
||||
clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];
|
||||
clusterNode *slots[REDIS_CLUSTER_SLOTS];
|
||||
zskiplist *slots_to_keys;
|
||||
/* The following fields are used to take the slave state on elections. */
|
||||
mstime_t failover_auth_time; /* Time of previous or next election. */
|
||||
int failover_auth_count; /* Number of votes received so far. */
|
||||
int failover_auth_sent; /* True if we already asked for votes. */
|
||||
int failover_auth_rank; /* This slave rank for current auth request. */
|
||||
uint64_t failover_auth_epoch; /* Epoch of the current election. */
|
||||
/* Manual failover state in common. */
|
||||
mstime_t mf_end; /* Manual failover time limit (ms unixtime).
|
||||
It is zero if there is no MF in progress. */
|
||||
/* Manual failover state of master. */
|
||||
clusterNode *mf_slave; /* Slave performing the manual failover. */
|
||||
/* Manual failover state of slave. */
|
||||
long long mf_master_offset; /* Master offset the slave needs to start MF
|
||||
or zero if stil not received. */
|
||||
int mf_can_start; /* If non-zero signal that the manual failover
|
||||
can start requesting masters vote. */
|
||||
/* The followign fields are uesd by masters to take state on elections. */
|
||||
uint64_t lastVoteEpoch; /* Epoch of the last vote granted. */
|
||||
int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */
|
||||
long long stats_bus_messages_sent; /* Num of msg sent via cluster bus. */
|
||||
long long stats_bus_messages_received; /* Num of msg rcvd via cluster bus.*/
|
||||
} clusterState;
|
||||
|
||||
/* clusterState todo_before_sleep flags. */
|
||||
#define CLUSTER_TODO_HANDLE_FAILOVER (1<<0)
|
||||
#define CLUSTER_TODO_UPDATE_STATE (1<<1)
|
||||
#define CLUSTER_TODO_SAVE_CONFIG (1<<2)
|
||||
#define CLUSTER_TODO_FSYNC_CONFIG (1<<3)
|
||||
|
||||
/* Redis cluster messages header */
|
||||
|
||||
/* Note that the PING, PONG and MEET messages are actually the same exact
|
||||
* kind of packet. PONG is the reply to ping, in the exact format as a PING,
|
||||
* while MEET is a special PING that forces the receiver to add the sender
|
||||
* as a node (if it is not already in the list). */
|
||||
#define CLUSTERMSG_TYPE_PING 0 /* Ping */
|
||||
#define CLUSTERMSG_TYPE_PONG 1 /* Pong (reply to Ping) */
|
||||
#define CLUSTERMSG_TYPE_MEET 2 /* Meet "let's join" message */
|
||||
#define CLUSTERMSG_TYPE_FAIL 3 /* Mark node xxx as failing */
|
||||
#define CLUSTERMSG_TYPE_PUBLISH 4 /* Pub/Sub Publish propagation */
|
||||
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 5 /* May I failover? */
|
||||
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 6 /* Yes, you have my vote */
|
||||
#define CLUSTERMSG_TYPE_UPDATE 7 /* Another node slots configuration */
|
||||
#define CLUSTERMSG_TYPE_MFSTART 8 /* Pause clients for manual failover */
|
||||
|
||||
/* Initially we don't know our "name", but we'll find it once we connect
|
||||
* to the first node, using the getsockname() function. Then we'll use this
|
||||
* address for all the next messages. */
|
||||
typedef struct {
|
||||
char nodename[REDIS_CLUSTER_NAMELEN];
|
||||
uint32_t ping_sent;
|
||||
uint32_t pong_received;
|
||||
char ip[REDIS_IP_STR_LEN]; /* IP address last time it was seen */
|
||||
uint16_t port; /* port last time it was seen */
|
||||
uint16_t flags;
|
||||
uint32_t notused; /* for 64 bit alignment */
|
||||
} clusterMsgDataGossip;
|
||||
|
||||
typedef struct {
|
||||
char nodename[REDIS_CLUSTER_NAMELEN];
|
||||
} clusterMsgDataFail;
|
||||
|
||||
typedef struct {
|
||||
uint32_t channel_len;
|
||||
uint32_t message_len;
|
||||
unsigned char bulk_data[8]; /* defined as 8 just for alignment concerns. */
|
||||
} clusterMsgDataPublish;
|
||||
|
||||
typedef struct {
|
||||
uint64_t configEpoch; /* Config epoch of the specified instance. */
|
||||
char nodename[REDIS_CLUSTER_NAMELEN]; /* Name of the slots owner. */
|
||||
unsigned char slots[REDIS_CLUSTER_SLOTS/8]; /* Slots bitmap. */
|
||||
} clusterMsgDataUpdate;
|
||||
|
||||
union clusterMsgData {
|
||||
/* PING, MEET and PONG */
|
||||
struct {
|
||||
/* Array of N clusterMsgDataGossip structures */
|
||||
clusterMsgDataGossip gossip[1];
|
||||
} ping;
|
||||
|
||||
/* FAIL */
|
||||
struct {
|
||||
clusterMsgDataFail about;
|
||||
} fail;
|
||||
|
||||
/* PUBLISH */
|
||||
struct {
|
||||
clusterMsgDataPublish msg;
|
||||
} publish;
|
||||
|
||||
/* UPDATE */
|
||||
struct {
|
||||
clusterMsgDataUpdate nodecfg;
|
||||
} update;
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
char sig[4]; /* Siganture "RCmb" (Redis Cluster message bus). */
|
||||
uint32_t totlen; /* Total length of this message */
|
||||
uint16_t ver; /* Protocol version, currently set to 0. */
|
||||
uint16_t notused0; /* 2 bytes not used. */
|
||||
uint16_t type; /* Message type */
|
||||
uint16_t count; /* Only used for some kind of messages. */
|
||||
uint64_t currentEpoch; /* The epoch accordingly to the sending node. */
|
||||
uint64_t configEpoch; /* The config epoch if it's a master, or the last
|
||||
epoch advertised by its master if it is a
|
||||
slave. */
|
||||
uint64_t offset; /* Master replication offset if node is a master or
|
||||
processed replication offset if node is a slave. */
|
||||
char sender[REDIS_CLUSTER_NAMELEN]; /* Name of the sender node */
|
||||
unsigned char myslots[REDIS_CLUSTER_SLOTS/8];
|
||||
char slaveof[REDIS_CLUSTER_NAMELEN];
|
||||
char notused1[32]; /* 32 bytes reserved for future usage. */
|
||||
uint16_t port; /* Sender TCP base port */
|
||||
uint16_t flags; /* Sender node flags */
|
||||
unsigned char state; /* Cluster state from the POV of the sender */
|
||||
unsigned char mflags[3]; /* Message flags: CLUSTERMSG_FLAG[012]_... */
|
||||
union clusterMsgData data;
|
||||
} clusterMsg;
|
||||
|
||||
#define CLUSTERMSG_MIN_LEN (sizeof(clusterMsg)-sizeof(union clusterMsgData))
|
||||
|
||||
/* Message flags better specify the packet content or are used to
|
||||
* provide some information about the node state. */
|
||||
#define CLUSTERMSG_FLAG0_PAUSED (1<<0) /* Master paused for manual failover. */
|
||||
#define CLUSTERMSG_FLAG0_FORCEACK (1<<1) /* Give ACK to AUTH_REQUEST even if
|
||||
master is up. */
|
||||
|
||||
/* ---------------------- API exported outside cluster.c -------------------- */
|
||||
clusterNode *getNodeByQuery(redisClient *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *ask);
|
||||
|
||||
#endif /* __REDIS_CLUSTER_H */
|
||||
+39
-134
@@ -29,7 +29,6 @@
|
||||
*/
|
||||
|
||||
#include "redis.h"
|
||||
#include "cluster.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -82,7 +81,6 @@ void resetServerSaveParams() {
|
||||
void loadServerConfigFromString(char *config) {
|
||||
char *err = NULL;
|
||||
int linenum = 0, totlines, i;
|
||||
int slaveof_linenum = 0;
|
||||
sds *lines;
|
||||
|
||||
lines = sdssplitlen(config,strlen(config),"\n",1,&totlines);
|
||||
@@ -127,11 +125,6 @@ void loadServerConfigFromString(char *config) {
|
||||
if (server.port < 0 || server.port > 65535) {
|
||||
err = "Invalid port"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"tcp-backlog") && argc == 2) {
|
||||
server.tcp_backlog = atoi(argv[1]);
|
||||
if (server.tcp_backlog < 0) {
|
||||
err = "Invalid backlog value"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"bind") && argc >= 2) {
|
||||
int j, addresses = argc-1;
|
||||
|
||||
@@ -250,7 +243,6 @@ void loadServerConfigFromString(char *config) {
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"slaveof") && argc == 3) {
|
||||
slaveof_linenum = linenum;
|
||||
server.masterhost = sdsnew(argv[1]);
|
||||
server.masterport = atoi(argv[2]);
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
@@ -297,10 +289,6 @@ void loadServerConfigFromString(char *config) {
|
||||
if ((server.rdb_compression = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"memcompression") && argc == 2) {
|
||||
if ((server.mem_compression = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"rdbchecksum") && argc == 2) {
|
||||
if ((server.rdb_checksum = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
@@ -419,26 +407,6 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "Target command name already exists"; goto loaderr;
|
||||
}
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-enabled") && argc == 2) {
|
||||
if ((server.cluster_enabled = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-config-file") && argc == 2) {
|
||||
zfree(server.cluster_configfile);
|
||||
server.cluster_configfile = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"cluster-node-timeout") && argc == 2) {
|
||||
server.cluster_node_timeout = strtoll(argv[1],NULL,10);
|
||||
if (server.cluster_node_timeout <= 0) {
|
||||
err = "cluster node timeout must be 1 or greater"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-migration-barrier")
|
||||
&& argc == 2)
|
||||
{
|
||||
server.cluster_migration_barrier = atoi(argv[1]);
|
||||
if (server.cluster_migration_barrier < 0) {
|
||||
err = "cluster migration barrier must be positive";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
|
||||
server.lua_time_limit = strtoll(argv[1],NULL,10);
|
||||
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
|
||||
@@ -509,15 +477,6 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
sdsfreesplitres(argv,argc);
|
||||
}
|
||||
|
||||
/* Sanity checks. */
|
||||
if (server.cluster_enabled && server.masterhost) {
|
||||
linenum = slaveof_linenum;
|
||||
i = linenum-1;
|
||||
err = "slaveof directive not allowed in cluster mode";
|
||||
goto loaderr;
|
||||
}
|
||||
|
||||
sdsfreesplitres(lines,totlines);
|
||||
return;
|
||||
|
||||
@@ -573,8 +532,8 @@ void loadServerConfig(char *filename, char *options) {
|
||||
void configSetCommand(redisClient *c) {
|
||||
robj *o;
|
||||
long long ll;
|
||||
redisAssertWithInfo(c,c->argv[2],sdsEncodedObject(c->argv[2]));
|
||||
redisAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3]));
|
||||
redisAssertWithInfo(c,c->argv[2],c->argv[2]->encoding == REDIS_ENCODING_RAW);
|
||||
redisAssertWithInfo(c,c->argv[2],c->argv[3]->encoding == REDIS_ENCODING_RAW);
|
||||
o = c->argv[3];
|
||||
|
||||
if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) {
|
||||
@@ -604,7 +563,7 @@ void configSetCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxclients")) {
|
||||
int orig_value = server.maxclients;
|
||||
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 1) goto badfmt;
|
||||
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;
|
||||
@@ -864,11 +823,6 @@ void configSetCommand(redisClient *c) {
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.rdb_compression = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"memcompression")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.mem_compression = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"notify-keyspace-events")) {
|
||||
int flags = keyspaceEventsStringToFlags(o->ptr);
|
||||
|
||||
@@ -893,14 +847,6 @@ void configSetCommand(redisClient *c) {
|
||||
ll < 0) goto badfmt;
|
||||
server.repl_min_slaves_max_lag = ll;
|
||||
refreshGoodSlavesCount();
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"cluster-node-timeout")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll <= 0) goto badfmt;
|
||||
server.cluster_node_timeout = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"cluster-migration-barrier")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.cluster_migration_barrier = ll;
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||
(char*)c->argv[2]->ptr);
|
||||
@@ -950,7 +896,7 @@ void configGetCommand(redisClient *c) {
|
||||
char *pattern = o->ptr;
|
||||
char buf[128];
|
||||
int matches = 0;
|
||||
redisAssertWithInfo(c,o,sdsEncodedObject(o));
|
||||
redisAssertWithInfo(c,o,o->encoding == REDIS_ENCODING_RAW);
|
||||
|
||||
/* String values */
|
||||
config_get_string_field("dbfilename",server.rdb_filename);
|
||||
@@ -989,7 +935,6 @@ void configGetCommand(redisClient *c) {
|
||||
config_get_numerical_field("slowlog-max-len",
|
||||
server.slowlog_max_len);
|
||||
config_get_numerical_field("port",server.port);
|
||||
config_get_numerical_field("tcp-backlog",server.tcp_backlog);
|
||||
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);
|
||||
@@ -1001,8 +946,6 @@ void configGetCommand(redisClient *c) {
|
||||
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);
|
||||
config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout);
|
||||
config_get_numerical_field("cluster-migration-barrier",server.cluster_migration_barrier);
|
||||
|
||||
/* Bool (yes/no) values */
|
||||
config_get_bool_field("no-appendfsync-on-rewrite",
|
||||
@@ -1015,7 +958,6 @@ void configGetCommand(redisClient *c) {
|
||||
server.stop_writes_on_bgsave_err);
|
||||
config_get_bool_field("daemonize", server.daemonize);
|
||||
config_get_bool_field("rdbcompression", server.rdb_compression);
|
||||
config_get_bool_field("memcompression", server.mem_compression);
|
||||
config_get_bool_field("rdbchecksum", server.rdb_checksum);
|
||||
config_get_bool_field("activerehashing", server.activerehashing);
|
||||
config_get_bool_field("repl-disable-tcp-nodelay",
|
||||
@@ -1165,8 +1107,8 @@ void configGetCommand(redisClient *c) {
|
||||
/* 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 dictSdsCaseHash(const void *key);
|
||||
int dictSdsKeyCaseCompare(void *privdata, const void *key1, const void *key2);
|
||||
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);
|
||||
|
||||
@@ -1175,27 +1117,17 @@ void dictListDestructor(void *privdata, void *val);
|
||||
void rewriteConfigSentinelOption(struct rewriteConfigState *state);
|
||||
|
||||
dictType optionToLineDictType = {
|
||||
dictSdsCaseHash, /* hash function */
|
||||
dictSdsHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCaseCompare, /* key compare */
|
||||
dictSdsKeyCompare, /* key compare */
|
||||
dictSdsDestructor, /* key destructor */
|
||||
dictListDestructor /* val destructor */
|
||||
};
|
||||
|
||||
dictType optionSetDictType = {
|
||||
dictSdsCaseHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCaseCompare, /* key compare */
|
||||
dictSdsDestructor, /* key destructor */
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
/* The config rewrite state. */
|
||||
struct rewriteConfigState {
|
||||
dict *option_to_line; /* Option -> list of config file lines map */
|
||||
dict *rewritten; /* Dictionary of already processed options */
|
||||
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
|
||||
@@ -1219,16 +1151,6 @@ void rewriteConfigAddLineNumberToOption(struct rewriteConfigState *state, sds op
|
||||
listAddNodeTail(l,(void*)(long)linenum);
|
||||
}
|
||||
|
||||
/* Add the specified option to the set of processed options.
|
||||
* This is useful as only unused lines of processed options will be blanked
|
||||
* in the config file, while options the rewrite process does not understand
|
||||
* remain untouched. */
|
||||
void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, char *option) {
|
||||
sds opt = sdsnew(option);
|
||||
|
||||
if (dictAdd(state->rewritten,opt,NULL) != DICT_OK) sdsfree(opt);
|
||||
}
|
||||
|
||||
/* Read the old file, split it into lines to populate a newly created
|
||||
* config rewrite state, and return it to the caller.
|
||||
*
|
||||
@@ -1243,7 +1165,6 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
|
||||
if (fp == NULL && errno != ENOENT) return NULL;
|
||||
|
||||
state->option_to_line = dictCreate(&optionToLineDictType,NULL);
|
||||
state->rewritten = dictCreate(&optionSetDictType,NULL);
|
||||
state->numlines = 0;
|
||||
state->lines = NULL;
|
||||
state->has_tail = 0;
|
||||
@@ -1311,8 +1232,6 @@ void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sd
|
||||
sds o = sdsnew(option);
|
||||
list *l = dictFetchValue(state->option_to_line,o);
|
||||
|
||||
rewriteConfigMarkAsProcessed(state,option);
|
||||
|
||||
if (!l && !force) {
|
||||
/* Option not used previously, and we are not forced to use it. */
|
||||
sdsfree(line);
|
||||
@@ -1369,6 +1288,7 @@ void rewriteConfigBytesOption(struct rewriteConfigState *state, char *option, lo
|
||||
rewriteConfigFormatMemory(buf,sizeof(buf),value);
|
||||
line = sdscatprintf(sdsempty(),"%s %s",option,buf);
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
|
||||
}
|
||||
|
||||
/* Rewrite a yes/no option. */
|
||||
@@ -1387,10 +1307,7 @@ void rewriteConfigStringOption(struct rewriteConfigState *state, char *option, c
|
||||
|
||||
/* 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) {
|
||||
rewriteConfigMarkAsProcessed(state,option);
|
||||
return;
|
||||
}
|
||||
if (value == NULL) return;
|
||||
|
||||
/* Compare the strings as sds strings to have a binary safe comparison. */
|
||||
if (defvalue && strcmp(value,defvalue) == 0) force = 0;
|
||||
@@ -1426,7 +1343,7 @@ void rewriteConfigEnumOption(struct rewriteConfigState *state, char *option, int
|
||||
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*);
|
||||
@@ -1438,7 +1355,7 @@ void rewriteConfigEnumOption(struct rewriteConfigState *state, char *option, int
|
||||
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);
|
||||
@@ -1471,21 +1388,16 @@ void rewriteConfigSaveOption(struct rewriteConfigState *state) {
|
||||
* resulting into no RDB persistence as expected. */
|
||||
for (j = 0; j < server.saveparamslen; j++) {
|
||||
line = sdscatprintf(sdsempty(),"save %ld %d",
|
||||
(long) server.saveparams[j].seconds, server.saveparams[j].changes);
|
||||
server.saveparams[j].seconds, server.saveparams[j].changes);
|
||||
rewriteConfigRewriteLine(state,"save",line,1);
|
||||
}
|
||||
/* Mark "save" as processed in case server.saveparamslen is zero. */
|
||||
rewriteConfigMarkAsProcessed(state,"save");
|
||||
}
|
||||
|
||||
/* Rewrite the dir option, always using absolute paths.*/
|
||||
void rewriteConfigDirOption(struct rewriteConfigState *state) {
|
||||
char cwd[1024];
|
||||
|
||||
if (getcwd(cwd,sizeof(cwd)) == NULL) {
|
||||
rewriteConfigMarkAsProcessed(state,"dir");
|
||||
return; /* no rewrite on error. */
|
||||
}
|
||||
if (getcwd(cwd,sizeof(cwd)) == NULL) return; /* no rewrite on error. */
|
||||
rewriteConfigStringOption(state,"dir",cwd,NULL);
|
||||
}
|
||||
|
||||
@@ -1495,17 +1407,24 @@ void rewriteConfigSlaveofOption(struct rewriteConfigState *state) {
|
||||
sds line;
|
||||
|
||||
/* If this is a master, we want all the slaveof config options
|
||||
* in the file to be removed. Note that if this is a cluster instance
|
||||
* we don't want a slaveof directive inside redis.conf. */
|
||||
if (server.cluster_enabled || server.masterhost == NULL) {
|
||||
rewriteConfigMarkAsProcessed(state,"slaveof");
|
||||
return;
|
||||
}
|
||||
* 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;
|
||||
@@ -1554,10 +1473,7 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
|
||||
char *option = "bind";
|
||||
|
||||
/* Nothing to rewrite if we don't have bind addresses. */
|
||||
if (server.bindaddr_count == 0) {
|
||||
rewriteConfigMarkAsProcessed(state,option);
|
||||
return;
|
||||
}
|
||||
if (server.bindaddr_count == 0) return;
|
||||
|
||||
/* Rewrite as bind <addr1> <addr2> ... <addrN> */
|
||||
addresses = sdsjoin(server.bindaddr,server.bindaddr_count," ");
|
||||
@@ -1593,7 +1509,6 @@ sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
|
||||
void rewriteConfigReleaseState(struct rewriteConfigState *state) {
|
||||
sdsfreesplitres(state->lines,state->numlines);
|
||||
dictRelease(state->option_to_line);
|
||||
dictRelease(state->rewritten);
|
||||
zfree(state);
|
||||
}
|
||||
|
||||
@@ -1611,14 +1526,6 @@ void rewriteConfigRemoveOrphaned(struct rewriteConfigState *state) {
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
list *l = dictGetVal(de);
|
||||
sds option = dictGetKey(de);
|
||||
|
||||
/* Don't blank lines about options the rewrite process
|
||||
* don't understand. */
|
||||
if (dictFind(state->rewritten,option) == NULL) {
|
||||
redisLog(REDIS_DEBUG,"Not rewritten option: %s", option);
|
||||
continue;
|
||||
}
|
||||
|
||||
while(listLength(l)) {
|
||||
listNode *ln = listFirst(l);
|
||||
@@ -1708,10 +1615,11 @@ int rewriteConfig(char *path) {
|
||||
/* 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);
|
||||
rewriteConfigNumericalOption(state,"tcp-backlog",server.tcp_backlog,REDIS_TCP_BACKLOG);
|
||||
rewriteConfigBindOption(state);
|
||||
rewriteConfigStringOption(state,"unixsocket",server.unixsocket,NULL);
|
||||
rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,REDIS_DEFAULT_UNIX_SOCKET_PERM);
|
||||
@@ -1731,7 +1639,6 @@ int rewriteConfig(char *path) {
|
||||
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,"memcompression",server.mem_compression,REDIS_DEFAULT_MEM_COMPRESSION);
|
||||
rewriteConfigYesNoOption(state,"rdbchecksum",server.rdb_checksum,REDIS_DEFAULT_RDB_CHECKSUM);
|
||||
rewriteConfigStringOption(state,"dbfilename",server.rdb_filename,REDIS_DEFAULT_RDB_FILENAME);
|
||||
rewriteConfigDirOption(state);
|
||||
@@ -1745,8 +1652,6 @@ int rewriteConfig(char *path) {
|
||||
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);
|
||||
rewriteConfigNumericalOption(state,"min-slaves-to-write",server.repl_min_slaves_to_write,REDIS_DEFAULT_MIN_SLAVES_TO_WRITE);
|
||||
rewriteConfigNumericalOption(state,"min-slaves-max-lag",server.repl_min_slaves_max_lag,REDIS_DEFAULT_MIN_SLAVES_MAX_LAG);
|
||||
rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL);
|
||||
rewriteConfigNumericalOption(state,"maxclients",server.maxclients,REDIS_MAX_CLIENTS);
|
||||
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,REDIS_DEFAULT_MAXMEMORY);
|
||||
@@ -1759,8 +1664,7 @@ int rewriteConfig(char *path) {
|
||||
"noeviction", REDIS_MAXMEMORY_NO_EVICTION,
|
||||
NULL, REDIS_DEFAULT_MAXMEMORY_POLICY);
|
||||
rewriteConfigNumericalOption(state,"maxmemory-samples",server.maxmemory_samples,REDIS_DEFAULT_MAXMEMORY_SAMPLES);
|
||||
rewriteConfigYesNoOption(state,"appendonly",server.aof_state != REDIS_AOF_OFF,0);
|
||||
rewriteConfigStringOption(state,"appendfilename",server.aof_filename,REDIS_DEFAULT_AOF_FILENAME);
|
||||
rewriteConfigAppendonlyOption(state);
|
||||
rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,
|
||||
"everysec", AOF_FSYNC_EVERYSEC,
|
||||
"always", AOF_FSYNC_ALWAYS,
|
||||
@@ -1770,10 +1674,6 @@ int rewriteConfig(char *path) {
|
||||
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);
|
||||
rewriteConfigYesNoOption(state,"cluster-enabled",server.cluster_enabled,0);
|
||||
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,REDIS_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT);
|
||||
rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,REDIS_CLUSTER_DEFAULT_MIGRATION_BARRIER);
|
||||
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);
|
||||
@@ -1818,7 +1718,14 @@ void configCommand(redisClient *c) {
|
||||
configGetCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"resetstat")) {
|
||||
if (c->argc != 2) goto badarity;
|
||||
resetServerStats();
|
||||
server.stat_keyspace_hits = 0;
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
server.stat_rejected_conn = 0;
|
||||
server.stat_fork_time = 0;
|
||||
server.aof_delayed_fsync = 0;
|
||||
resetCommandTableStats();
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"rewrite")) {
|
||||
@@ -1828,10 +1735,8 @@ void configCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
if (rewriteConfig(server.configfile) == -1) {
|
||||
redisLog(REDIS_WARNING,"CONFIG REWRITE failed: %s", strerror(errno));
|
||||
addReplyErrorFormat(c,"Rewriting config file: %s", strerror(errno));
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"CONFIG REWRITE executed with success.");
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else {
|
||||
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
#include "redis.h"
|
||||
|
||||
/*
|
||||
* Copyright 2001-2010 Georges Menie (www.menie.org)
|
||||
* Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style)
|
||||
* 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 the University of California, Berkeley 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 REGENTS 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 REGENTS AND 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.
|
||||
*/
|
||||
|
||||
/* CRC16 implementation according to CCITT standards.
|
||||
*
|
||||
* Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the
|
||||
* following parameters:
|
||||
*
|
||||
* Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
|
||||
* Width : 16 bit
|
||||
* Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1)
|
||||
* Initialization : 0000
|
||||
* Reflect Input byte : False
|
||||
* Reflect Output CRC : False
|
||||
* Xor constant to output CRC : 0000
|
||||
* Output for "123456789" : 31C3
|
||||
*/
|
||||
|
||||
static const uint16_t crc16tab[256]= {
|
||||
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
|
||||
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
|
||||
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
|
||||
0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
|
||||
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
|
||||
0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
|
||||
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
|
||||
0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
|
||||
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
|
||||
0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
|
||||
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
|
||||
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
|
||||
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
|
||||
0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
|
||||
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
|
||||
0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
|
||||
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
|
||||
0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
|
||||
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
|
||||
0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
|
||||
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
|
||||
0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
|
||||
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
|
||||
0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
|
||||
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
|
||||
0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
|
||||
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
|
||||
0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
|
||||
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
|
||||
0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
|
||||
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
|
||||
0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
|
||||
};
|
||||
|
||||
uint16_t crc16(const char *buf, int len) {
|
||||
int counter;
|
||||
uint16_t crc = 0;
|
||||
for (counter = 0; counter < len; counter++)
|
||||
crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF];
|
||||
return crc;
|
||||
}
|
||||
@@ -28,14 +28,12 @@
|
||||
*/
|
||||
|
||||
#include "redis.h"
|
||||
#include "cluster.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <ctype.h>
|
||||
|
||||
void slotToKeyAdd(robj *key);
|
||||
void slotToKeyDel(robj *key);
|
||||
void slotToKeyFlush(void);
|
||||
void SlotToKeyAdd(robj *key);
|
||||
void SlotToKeyDel(robj *key);
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* C-level DB API
|
||||
@@ -50,7 +48,7 @@ robj *lookupKey(redisDb *db, robj *key) {
|
||||
* Don't do it if we have a saving child, as this will trigger
|
||||
* a copy on write madness. */
|
||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
|
||||
val->lru = LRU_CLOCK();
|
||||
val->lru = server.lruclock;
|
||||
return val;
|
||||
} else {
|
||||
return NULL;
|
||||
@@ -95,7 +93,6 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
int retval = dictAdd(db->dict, copy, val);
|
||||
|
||||
redisAssertWithInfo(NULL,key,retval == REDIS_OK);
|
||||
if (server.cluster_enabled) slotToKeyAdd(key);
|
||||
}
|
||||
|
||||
/* Overwrite an existing key with a new value. Incrementing the reference
|
||||
@@ -104,7 +101,7 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
*
|
||||
* The program is aborted if the key was not already present. */
|
||||
void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
struct dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
|
||||
redisAssertWithInfo(NULL,key,de != NULL);
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
@@ -136,7 +133,7 @@ int dbExists(redisDb *db, robj *key) {
|
||||
*
|
||||
* The function makes sure to return keys not already expired. */
|
||||
robj *dbRandomKey(redisDb *db) {
|
||||
dictEntry *de;
|
||||
struct dictEntry *de;
|
||||
|
||||
while(1) {
|
||||
sds key;
|
||||
@@ -163,51 +160,12 @@ int dbDelete(redisDb *db, robj *key) {
|
||||
* the key, because it is shared with the main dictionary. */
|
||||
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
|
||||
if (dictDelete(db->dict,key->ptr) == DICT_OK) {
|
||||
if (server.cluster_enabled) slotToKeyDel(key);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Prepare the string object stored at 'key' to be modified destructively
|
||||
* to implement commands like SETBIT or APPEND.
|
||||
*
|
||||
* An object is usually ready to be modified unless one of the two conditions
|
||||
* are true:
|
||||
*
|
||||
* 1) The object 'o' is shared (refcount > 1), we don't want to affect
|
||||
* other users.
|
||||
* 2) The object encoding is not "RAW".
|
||||
*
|
||||
* If the object is found in one of the above conditions (or both) by the
|
||||
* function, an unshared / not-encoded copy of the string object is stored
|
||||
* at 'key' in the specified 'db'. Otherwise the object 'o' itself is
|
||||
* returned.
|
||||
*
|
||||
* USAGE:
|
||||
*
|
||||
* The object 'o' is what the caller already obtained by looking up 'key'
|
||||
* in 'db', the usage pattern looks like this:
|
||||
*
|
||||
* o = lookupKeyWrite(db,key);
|
||||
* if (checkType(c,o,REDIS_STRING)) return;
|
||||
* o = dbUnshareStringValue(db,key,o);
|
||||
*
|
||||
* At this point the caller is ready to modify the object, for example
|
||||
* using an sdscat() call to append some data, or anything else.
|
||||
*/
|
||||
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
|
||||
redisAssert(o->type == REDIS_STRING);
|
||||
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbOverwrite(db,key,o);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
long long emptyDb(void(callback)(void*)) {
|
||||
int j;
|
||||
long long removed = 0;
|
||||
@@ -217,7 +175,6 @@ long long emptyDb(void(callback)(void*)) {
|
||||
dictEmpty(server.db[j].dict,callback);
|
||||
dictEmpty(server.db[j].expires,callback);
|
||||
}
|
||||
if (server.cluster_enabled) slotToKeyFlush();
|
||||
return removed;
|
||||
}
|
||||
|
||||
@@ -254,7 +211,6 @@ void flushdbCommand(redisClient *c) {
|
||||
signalFlushedDb(c->db->id);
|
||||
dictEmpty(c->db->dict,NULL);
|
||||
dictEmpty(c->db->expires,NULL);
|
||||
if (server.cluster_enabled) slotToKeyFlush();
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
@@ -307,10 +263,6 @@ void selectCommand(redisClient *c) {
|
||||
"invalid DB index") != REDIS_OK)
|
||||
return;
|
||||
|
||||
if (server.cluster_enabled && id != 0) {
|
||||
addReplyError(c,"SELECT is not allowed in cluster mode");
|
||||
return;
|
||||
}
|
||||
if (selectDb(c,id) == REDIS_ERR) {
|
||||
addReplyError(c,"invalid DB index");
|
||||
} else {
|
||||
@@ -536,16 +488,16 @@ void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
|
||||
/* Filter element if it does not match the pattern. */
|
||||
if (!filter && use_pattern) {
|
||||
if (sdsEncodedObject(kobj)) {
|
||||
if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))
|
||||
filter = 1;
|
||||
} else {
|
||||
if (kobj->encoding == REDIS_ENCODING_INT) {
|
||||
char buf[REDIS_LONGSTR_SIZE];
|
||||
int len;
|
||||
|
||||
redisAssert(kobj->encoding == REDIS_ENCODING_INT);
|
||||
len = ll2string(buf,sizeof(buf),(long)kobj->ptr);
|
||||
if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1;
|
||||
} else {
|
||||
if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))
|
||||
filter = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,13 +595,11 @@ void shutdownCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* When SHUTDOWN is called while the server is loading a dataset in
|
||||
* memory we need to make sure no attempt is performed to save
|
||||
/* 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).
|
||||
*
|
||||
* Also when in Sentinel mode clear the SAVE flag and force NOSAVE. */
|
||||
if (server.loading || server.sentinel_mode)
|
||||
* 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.");
|
||||
@@ -706,11 +656,6 @@ void moveCommand(redisClient *c) {
|
||||
redisDb *src, *dst;
|
||||
int srcid;
|
||||
|
||||
if (server.cluster_enabled) {
|
||||
addReplyError(c,"MOVE is not allowed in cluster mode");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Obtain source and target DB pointers */
|
||||
src = c->db;
|
||||
srcid = c->db->id;
|
||||
@@ -810,21 +755,13 @@ void propagateExpire(redisDb *db, robj *key) {
|
||||
}
|
||||
|
||||
int expireIfNeeded(redisDb *db, robj *key) {
|
||||
mstime_t when = getExpire(db,key);
|
||||
mstime_t now;
|
||||
long long when = getExpire(db,key);
|
||||
|
||||
if (when < 0) return 0; /* No expire for this key */
|
||||
|
||||
/* Don't expire anything while loading. It will be done later. */
|
||||
if (server.loading) return 0;
|
||||
|
||||
/* If we are in the context of a Lua script, we claim that time is
|
||||
* blocked to when the Lua script started. This way a key can expire
|
||||
* only the first time it is accessed and not in the middle of the
|
||||
* script execution, making propagation to slaves / AOF consistent.
|
||||
* See issue #1525 on Github for more information. */
|
||||
now = server.lua_caller ? server.lua_time_start : mstime();
|
||||
|
||||
/* If we are running in the context of a slave, return ASAP:
|
||||
* the slave key expiration is controlled by the master that will
|
||||
* send us synthesized DEL operations for expired keys.
|
||||
@@ -832,10 +769,12 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
* Still we try to return the right information to the caller,
|
||||
* that is, 0 if we think the key should be still valid, 1 if
|
||||
* we think the key is expired at this time. */
|
||||
if (server.masterhost != NULL) return now > when;
|
||||
if (server.masterhost != NULL) {
|
||||
return mstime() > when;
|
||||
}
|
||||
|
||||
/* Return when this key has not expired */
|
||||
if (now <= when) return 0;
|
||||
if (mstime() <= when) return 0;
|
||||
|
||||
/* Delete the key */
|
||||
server.stat_expiredkeys++;
|
||||
@@ -968,8 +907,6 @@ void persistCommand(redisClient *c) {
|
||||
* API to get key arguments from commands
|
||||
* ---------------------------------------------------------------------------*/
|
||||
|
||||
/* The base case is to use the keys position as given in the command table
|
||||
* (firstkey, lastkey, step). */
|
||||
int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) {
|
||||
int j, i = 0, last, *keys;
|
||||
REDIS_NOTUSED(argv);
|
||||
@@ -989,65 +926,42 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Return all the arguments that are keys in the command passed via argc / argv.
|
||||
*
|
||||
* The command returns the positions of all the key arguments inside the array,
|
||||
* so the actual return value is an heap allocated array of integers. The
|
||||
* length of the array is returned by reference into *numkeys.
|
||||
*
|
||||
* 'cmd' must be point to the corresponding entry into the redisCommand
|
||||
* table, according to the command name in argv[0].
|
||||
*
|
||||
* This function uses the command table if a command-specific helper function
|
||||
* is not required, otherwise it calls the command-specific function. */
|
||||
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int *getKeysFromCommand(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
|
||||
if (cmd->getkeys_proc) {
|
||||
return cmd->getkeys_proc(cmd,argv,argc,numkeys);
|
||||
return cmd->getkeys_proc(cmd,argv,argc,numkeys,flags);
|
||||
} else {
|
||||
return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free the result of getKeysFromCommand. */
|
||||
void getKeysFreeResult(int *result) {
|
||||
zfree(result);
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from following commands:
|
||||
* ZUNIONSTORE <destkey> <num-keys> <key> <key> ... <key> <options>
|
||||
* ZINTERSTORE <destkey> <num-keys> <key> <key> ... <key> <options> */
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, num, *keys;
|
||||
REDIS_NOTUSED(cmd);
|
||||
|
||||
num = atoi(argv[2]->ptr);
|
||||
/* Sanity check. Don't return any key if the command is going to
|
||||
* reply with syntax error. */
|
||||
if (num > (argc-3)) {
|
||||
int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
|
||||
if (flags & REDIS_GETKEYS_PRELOAD) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
} else {
|
||||
return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
|
||||
}
|
||||
|
||||
/* Keys in z{union,inter}store come from two places:
|
||||
* argv[1] = storage key,
|
||||
* argv[3...n] = keys to intersect */
|
||||
keys = zmalloc(sizeof(int)*(num+1));
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
for (i = 0; i < num; i++) keys[i] = 3+i;
|
||||
|
||||
/* Finally add the argv[1] key position (the storage key target). */
|
||||
keys[num] = 1;
|
||||
*numkeys = num+1; /* Total keys = {union,inter} keys + storage key */
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from the following commands:
|
||||
* EVAL <script> <num-keys> <key> <key> ... <key> [more stuff]
|
||||
* EVALSHA <script> <num-keys> <key> <key> ... <key> [more stuff] */
|
||||
int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
|
||||
if (flags & REDIS_GETKEYS_PRELOAD) {
|
||||
int *keys = zmalloc(sizeof(int));
|
||||
*numkeys = 1;
|
||||
keys[0] = 1;
|
||||
return keys;
|
||||
} else {
|
||||
return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
|
||||
}
|
||||
}
|
||||
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) {
|
||||
int i, num, *keys;
|
||||
REDIS_NOTUSED(cmd);
|
||||
REDIS_NOTUSED(flags);
|
||||
|
||||
num = atoi(argv[2]->ptr);
|
||||
/* Sanity check. Don't return any key if the command is going to
|
||||
@@ -1056,126 +970,8 @@ int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
*numkeys = num;
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
for (i = 0; i < num; i++) keys[i] = 3+i;
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from the SORT command.
|
||||
*
|
||||
* SORT <sort-key> ... STORE <store-key> ...
|
||||
*
|
||||
* The first argument of SORT is always a key, however a list of options
|
||||
* follow in SQL-alike style. Here we parse just the minimum in order to
|
||||
* correctly identify keys in the "STORE" option. */
|
||||
int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, j, num, *keys;
|
||||
REDIS_NOTUSED(cmd);
|
||||
|
||||
num = 0;
|
||||
keys = zmalloc(sizeof(int)*2); /* Alloc 2 places for the worst case. */
|
||||
|
||||
keys[num++] = 1; /* <sort-key> is always present. */
|
||||
|
||||
/* Search for STORE option. By default we consider options to don't
|
||||
* have arguments, so if we find an unknown option name we scan the
|
||||
* next. However there are options with 1 or 2 arguments, so we
|
||||
* provide a list here in order to skip the right number of args. */
|
||||
struct {
|
||||
char *name;
|
||||
int skip;
|
||||
} skiplist[] = {
|
||||
{"limit", 2},
|
||||
{"get", 1},
|
||||
{"by", 1},
|
||||
{NULL, 0} /* End of elements. */
|
||||
};
|
||||
|
||||
for (i = 2; i < argc; i++) {
|
||||
for (j = 0; skiplist[j].name != NULL; j++) {
|
||||
if (!strcasecmp(argv[i]->ptr,skiplist[j].name)) {
|
||||
i += skiplist[j].skip;
|
||||
break;
|
||||
} else if (!strcasecmp(argv[i]->ptr,"store") && i+1 < argc) {
|
||||
/* Note: we don't increment "num" here and continue the loop
|
||||
* to be sure to process the *last* "STORE" option if multiple
|
||||
* ones are provided. This is same behavior as SORT. */
|
||||
keys[num] = i+1; /* <store-key> */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Slot to Key API. This is used by Redis Cluster in order to obtain in
|
||||
* a fast way a key that belongs to a specified hash slot. This is useful
|
||||
* while rehashing the cluster. */
|
||||
void slotToKeyAdd(robj *key) {
|
||||
unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));
|
||||
|
||||
zslInsert(server.cluster->slots_to_keys,hashslot,key);
|
||||
incrRefCount(key);
|
||||
}
|
||||
|
||||
void slotToKeyDel(robj *key) {
|
||||
unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));
|
||||
|
||||
zslDelete(server.cluster->slots_to_keys,hashslot,key);
|
||||
}
|
||||
|
||||
void slotToKeyFlush(void) {
|
||||
zslFree(server.cluster->slots_to_keys);
|
||||
server.cluster->slots_to_keys = zslCreate();
|
||||
}
|
||||
|
||||
unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count) {
|
||||
zskiplistNode *n;
|
||||
zrangespec range;
|
||||
int j = 0;
|
||||
|
||||
range.min = range.max = hashslot;
|
||||
range.minex = range.maxex = 0;
|
||||
|
||||
n = zslFirstInRange(server.cluster->slots_to_keys, range);
|
||||
while(n && n->score == hashslot && count--) {
|
||||
keys[j++] = n->obj;
|
||||
n = n->level[0].forward;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
unsigned int countKeysInSlot(unsigned int hashslot) {
|
||||
zskiplist *zsl = server.cluster->slots_to_keys;
|
||||
zskiplistNode *zn;
|
||||
zrangespec range;
|
||||
int rank, count = 0;
|
||||
|
||||
range.min = range.max = hashslot;
|
||||
range.minex = range.maxex = 0;
|
||||
|
||||
/* Find first element in range */
|
||||
zn = zslFirstInRange(zsl, range);
|
||||
|
||||
/* Use rank of first element, if any, to determine preliminary count */
|
||||
if (zn != NULL) {
|
||||
rank = zslGetRank(zsl, zn->score, zn->obj);
|
||||
count = (zsl->length - (rank - 1));
|
||||
|
||||
/* Find last element in range */
|
||||
zn = zslLastInRange(zsl, range);
|
||||
|
||||
/* Use rank of last element, if any, to determine the actual count */
|
||||
if (zn != NULL) {
|
||||
rank = zslGetRank(zsl, zn->score, zn->obj);
|
||||
count -= (zsl->length - rank);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
+7
-32
@@ -292,7 +292,7 @@ void debugCommand(redisClient *c) {
|
||||
addReplyStatusFormat(c,
|
||||
"Value at:%p refcount:%d "
|
||||
"encoding:%s serializedlength:%lld "
|
||||
"lru:%d lru_seconds_idle:%llu",
|
||||
"lru:%d lru_seconds_idle:%lu",
|
||||
(void*)val, val->refcount,
|
||||
strenc, (long long) rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val));
|
||||
@@ -308,10 +308,8 @@ void debugCommand(redisClient *c) {
|
||||
val = dictGetVal(de);
|
||||
key = dictGetKey(de);
|
||||
|
||||
if (val->type != REDIS_STRING ||
|
||||
(!sdsEncodedObject(val) && val->encoding != REDIS_ENCODING_LZF))
|
||||
{
|
||||
addReplyError(c,"Not an sds/lzf encoded string.");
|
||||
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, "
|
||||
@@ -365,31 +363,6 @@ void debugCommand(redisClient *c) {
|
||||
{
|
||||
server.active_expire_enabled = atoi(c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"cmdkeys") && c->argc >= 3) {
|
||||
struct redisCommand *cmd = lookupCommand(c->argv[2]->ptr);
|
||||
int *keys, numkeys, j;
|
||||
|
||||
if (!cmd) {
|
||||
addReplyError(c,"Invalid command specified");
|
||||
return;
|
||||
} else if ((cmd->arity > 0 && cmd->arity != c->argc-2) ||
|
||||
((c->argc-2) < -cmd->arity))
|
||||
{
|
||||
addReplyError(c,"Invalid number of arguments specified for command");
|
||||
return;
|
||||
}
|
||||
|
||||
keys = getKeysFromCommand(cmd,c->argv+2,c->argc-2,&numkeys);
|
||||
addReplyMultiBulkLen(c,numkeys);
|
||||
for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]);
|
||||
getKeysFreeResult(keys);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"error") && c->argc == 3) {
|
||||
sds errstr = sdsnewlen("-",1);
|
||||
|
||||
errstr = sdscatsds(errstr,c->argv[2]->ptr);
|
||||
errstr = sdsmapchars(errstr,"\n\r"," ",2); /* no newlines in errors. */
|
||||
errstr = sdscatlen(errstr,"\r\n",2);
|
||||
addReplySds(c,errstr);
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown DEBUG subcommand or wrong number of arguments for '%s'",
|
||||
(char*)c->argv[1]->ptr);
|
||||
@@ -423,7 +396,9 @@ void _redisAssertPrintClientInfo(redisClient *c) {
|
||||
char buf[128];
|
||||
char *arg;
|
||||
|
||||
if (c->argv[j]->type == REDIS_STRING && sdsEncodedObject(c->argv[j])) {
|
||||
if (c->argv[j]->type == REDIS_STRING &&
|
||||
c->argv[j]->encoding == REDIS_ENCODING_RAW)
|
||||
{
|
||||
arg = (char*) c->argv[j]->ptr;
|
||||
} else {
|
||||
snprintf(buf,sizeof(buf),"Object type: %d, encoding: %d",
|
||||
@@ -439,7 +414,7 @@ void redisLogObjectDebugInfo(robj *o) {
|
||||
redisLog(REDIS_WARNING,"Object type: %d", o->type);
|
||||
redisLog(REDIS_WARNING,"Object encoding: %d", o->encoding);
|
||||
redisLog(REDIS_WARNING,"Object refcount: %d", o->refcount);
|
||||
if (o->type == REDIS_STRING && sdsEncodedObject(o)) {
|
||||
if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_RAW) {
|
||||
redisLog(REDIS_WARNING,"Object raw string len: %zu", sdslen(o->ptr));
|
||||
if (sdslen(o->ptr) < 4096) {
|
||||
sds repr = sdscatrepr(sdsempty(),o->ptr,sdslen(o->ptr));
|
||||
|
||||
+3
-55
@@ -239,7 +239,7 @@ int dictExpand(dict *d, unsigned long size)
|
||||
/* Performs N steps of incremental rehashing. Returns 1 if there are still
|
||||
* keys to move from the old to the new hash table, otherwise 0 is returned.
|
||||
* Note that a rehashing step consists in moving a bucket (that may have more
|
||||
* than one key as we use chaining) from the old to the new hash table. */
|
||||
* thank one key as we use chaining) from the old to the new hash table. */
|
||||
int dictRehash(dict *d, int n) {
|
||||
if (!dictIsRehashing(d)) return 0;
|
||||
|
||||
@@ -324,7 +324,7 @@ int dictAdd(dict *d, void *key, void *val)
|
||||
* a value returns the dictEntry structure to the user, that will make
|
||||
* sure to fill the value field as he wishes.
|
||||
*
|
||||
* This function is also directly exposed to the user API to be called
|
||||
* This function is also directly exposed to user API to be called
|
||||
* mainly in order to store non-pointers inside the hash value, example:
|
||||
*
|
||||
* entry = dictAddRaw(dict,mykey);
|
||||
@@ -649,58 +649,6 @@ dictEntry *dictGetRandomKey(dict *d)
|
||||
return he;
|
||||
}
|
||||
|
||||
/* This is a version of dictGetRandomKey() that is modified in order to
|
||||
* return multiple entries by jumping at a random place of the hash table
|
||||
* and scanning linearly for entries.
|
||||
*
|
||||
* Returned pointers to hash table entries are stored into 'des' that
|
||||
* points to an array of dictEntry pointers. The array must have room for
|
||||
* at least 'count' elements, that is the argument we pass to the function
|
||||
* to tell how many random elements we need.
|
||||
*
|
||||
* The function returns the number of items stored into 'des', that may
|
||||
* be less than 'count' if the hash table has less than 'count' elements
|
||||
* inside.
|
||||
*
|
||||
* Note that this function is not suitable when you need a good distribution
|
||||
* of the returned items, but only when you need to "sample" a given number
|
||||
* of continuous elements to run some kind of algorithm or to produce
|
||||
* statistics. However the function is much faster than dictGetRandomKey()
|
||||
* at producing N elements, and the elements are guaranteed to be non
|
||||
* repeating. */
|
||||
int dictGetRandomKeys(dict *d, dictEntry **des, int count) {
|
||||
int j; /* internal hash table id, 0 or 1. */
|
||||
int stored = 0;
|
||||
|
||||
if (dictSize(d) < count) count = dictSize(d);
|
||||
while(stored < count) {
|
||||
for (j = 0; j < 2; j++) {
|
||||
/* Pick a random point inside the hash table 0 or 1. */
|
||||
unsigned int i = random() & d->ht[j].sizemask;
|
||||
int size = d->ht[j].size;
|
||||
|
||||
/* Make sure to visit every bucket by iterating 'size' times. */
|
||||
while(size--) {
|
||||
dictEntry *he = d->ht[j].table[i];
|
||||
while (he) {
|
||||
/* Collect all the elements of the buckets found non
|
||||
* empty while iterating. */
|
||||
*des = he;
|
||||
des++;
|
||||
he = he->next;
|
||||
stored++;
|
||||
if (stored == count) return stored;
|
||||
}
|
||||
i = (i+1) & d->ht[j].sizemask;
|
||||
}
|
||||
/* If there is only one table and we iterated it all, we should
|
||||
* already have 'count' elements. Assert this condition. */
|
||||
assert(dictIsRehashing(d) != 0);
|
||||
}
|
||||
}
|
||||
return stored; /* Never reached. */
|
||||
}
|
||||
|
||||
/* Function to reverse bits. Algorithm from:
|
||||
* http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
|
||||
static unsigned long rev(unsigned long v) {
|
||||
@@ -747,7 +695,7 @@ static unsigned long rev(unsigned long v) {
|
||||
* (where SIZE-1 is always the mask that is equivalent to taking the rest
|
||||
* of the division between the Hash of the key and SIZE).
|
||||
*
|
||||
* For example if the current hash table size is 16, the mask is
|
||||
* For example if the current hash table size is 64, the mask is
|
||||
* (in binary) 1111. The position of a key in the hash table will be always
|
||||
* the last four bits of the hash output, and so forth.
|
||||
*
|
||||
|
||||
@@ -157,7 +157,6 @@ dictIterator *dictGetSafeIterator(dict *d);
|
||||
dictEntry *dictNext(dictIterator *iter);
|
||||
void dictReleaseIterator(dictIterator *iter);
|
||||
dictEntry *dictGetRandomKey(dict *d);
|
||||
int dictGetRandomKeys(dict *d, dictEntry **des, int count);
|
||||
void dictPrintStats(dict *d);
|
||||
unsigned int dictGenHashFunction(const void *key, int len);
|
||||
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
|
||||
|
||||
@@ -61,14 +61,4 @@ uint64_t intrev64(uint64_t v);
|
||||
#define intrev64ifbe(v) intrev64(v)
|
||||
#endif
|
||||
|
||||
/* The functions htonu64() and ntohu64() convert the specified value to
|
||||
* network byte ordering and back. In big endian systems they are no-ops. */
|
||||
#if (BYTE_ORDER == BIG_ENDIAN)
|
||||
#define htonu64(v) (v)
|
||||
#define ntohu64(v) (v)
|
||||
#else
|
||||
#define htonu64(v) intrev64(v)
|
||||
#define ntohu64(v) intrev64(v)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,714 +0,0 @@
|
||||
/* hyperloglog.c - Redis HyperLogLog probabilistic cardinality approximation.
|
||||
* This file implements the algorithm and the exported Redis commands.
|
||||
*
|
||||
* Copyright (c) 2014, 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"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
|
||||
/* The Redis HyperLogLog implementation is based on the following ideas:
|
||||
*
|
||||
* * The use of a 64 bit hash function as proposed in [1], in order to don't
|
||||
* limited to cardinalities up to 10^9, at the cost of just 1 additional
|
||||
* bit per register.
|
||||
* * The use of 16384 6-bit registers for a great level of accuracy, using
|
||||
* a total of 12k per key.
|
||||
* * The use of the Redis string data type. No new type is introduced.
|
||||
* * No attempt is made to compress the data structure as in [1]. Also the
|
||||
* algorithm used is the original HyperLogLog Algorithm as in [2], with
|
||||
* the only difference that a 64 bit hash function is used, so no correction
|
||||
* is performed for values near 2^32 as in [1].
|
||||
*
|
||||
* [1] Heule, Nunkesser, Hall: HyperLogLog in Practice: Algorithmic
|
||||
* Engineering of a State of The Art Cardinality Estimation Algorithm.
|
||||
*
|
||||
* [2] P. Flajolet, Éric Fusy, O. Gandouet, and F. Meunier. Hyperloglog: The
|
||||
* analysis of a near-optimal cardinality estimation algorithm.
|
||||
*
|
||||
* The representation used by Redis is the following:
|
||||
*
|
||||
* +--------+--------+--------+------// //--+----------+------+-----+
|
||||
* |11000000|22221111|33333322|55444444 .... | uint64_t | HYLL | Ver |
|
||||
* +--------+--------+--------+------// //--+----------+------+-----+
|
||||
*
|
||||
* The 6 bits counters are encoded one after the other starting from the
|
||||
* LSB to the MSB, and using the next bytes as needed.
|
||||
*
|
||||
* At the end of the 16k counters, there is an additional 64 bit integer
|
||||
* stored in little endian format with the latest cardinality computed that
|
||||
* can be reused if the data structure was not modified since the last
|
||||
* computation (this is useful because there are high probabilities that
|
||||
* HLLADD operations don't modify the actual data structure and hence the
|
||||
* approximated cardinality).
|
||||
*
|
||||
* After the cached cardinality there are 4 bytes of magic set to the
|
||||
* string "HYLL", and a 4 bytes version field that is reserved for
|
||||
* future uses and is currently set to 0.
|
||||
*
|
||||
* When the most significant bit in the most significant byte of the cached
|
||||
* cardinality is set, it means that the data structure was modified and
|
||||
* we can't reuse the cached value that must be recomputed. */
|
||||
|
||||
#define REDIS_HLL_P 14 /* The greater is P, the smaller the error. */
|
||||
#define REDIS_HLL_REGISTERS (1<<REDIS_HLL_P) /* With P=14, 16384 registers. */
|
||||
#define REDIS_HLL_P_MASK (REDIS_HLL_REGISTERS-1) /* Mask to index register. */
|
||||
#define REDIS_HLL_BITS 6 /* Enough to count up to 63 leading zeroes. */
|
||||
#define REDIS_HLL_REGISTER_MAX ((1<<REDIS_HLL_BITS)-1)
|
||||
/* Note: REDIS_HLL_SIZE define has a final "+8" since we store a 64 bit
|
||||
* integer at the end of the HyperLogLog structure to cache the cardinality. */
|
||||
#define REDIS_HLL_SIZE ((REDIS_HLL_REGISTERS*REDIS_HLL_BITS+7)/8)+8+8
|
||||
|
||||
/* =========================== Low level bit macros ========================= */
|
||||
|
||||
/* We need to get and set 6 bit counters in an array of 8 bit bytes.
|
||||
* We use macros to make sure the code is inlined since speed is critical
|
||||
* especially in order to compute the approximated cardinality in
|
||||
* HLLCOUNT where we need to access all the registers at once.
|
||||
* For the same reason we also want to avoid conditionals in this code path.
|
||||
*
|
||||
* +--------+--------+--------+------//
|
||||
* |11000000|22221111|33333322|55444444
|
||||
* +--------+--------+--------+------//
|
||||
*
|
||||
* Note: in the above representation the most significant bit (MSB)
|
||||
* of every byte is on the left. We start using bits from the LSB to MSB,
|
||||
* and so forth passing to the next byte.
|
||||
*
|
||||
* Example, we want to access to counter at pos = 1 ("111111" in the
|
||||
* illustration above).
|
||||
*
|
||||
* The index of the first byte b0 containing our data is:
|
||||
*
|
||||
* b0 = 6 * pos / 8 = 0
|
||||
*
|
||||
* +--------+
|
||||
* |11000000| <- Our byte at b0
|
||||
* +--------+
|
||||
*
|
||||
* The position of the first bit (counting from the LSB = 0) in the byte
|
||||
* is given by:
|
||||
*
|
||||
* fb = 6 * pos % 8 -> 6
|
||||
*
|
||||
* Right shift b0 of 'fb' bits.
|
||||
*
|
||||
* +--------+
|
||||
* |11000000| <- Initial value of b0
|
||||
* |00000011| <- After right shift of 6 pos.
|
||||
* +--------+
|
||||
*
|
||||
* Left shift b1 of bits 8-fb bits (2 bits)
|
||||
*
|
||||
* +--------+
|
||||
* |22221111| <- Initial value of b1
|
||||
* |22111100| <- After left shift of 2 bits.
|
||||
* +--------+
|
||||
*
|
||||
* OR the two bits, and finally AND with 111111 (63 in decimal) to
|
||||
* clean the higher order bits we are not interested in:
|
||||
*
|
||||
* +--------+
|
||||
* |00000011| <- b0 right shifted
|
||||
* |22111100| <- b1 left shifted
|
||||
* |22111111| <- b0 OR b1
|
||||
* | 111111| <- (b0 OR b1) AND 63, our value.
|
||||
* +--------+
|
||||
*
|
||||
* We can try with a different example, like pos = 0. In this case
|
||||
* the 6-bit counter is actually contained in a single byte.
|
||||
*
|
||||
* b0 = 6 * pos / 8 = 0
|
||||
*
|
||||
* +--------+
|
||||
* |11000000| <- Our byte at b0
|
||||
* +--------+
|
||||
*
|
||||
* fb = 6 * pos % 8 = 0
|
||||
*
|
||||
* So we right shift of 0 bits (no shift in practice) and
|
||||
* left shift the next byte of 8 bits, even if we don't use it,
|
||||
* but this has the effect of clearing the bits so the result
|
||||
* will not be affacted after the OR.
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
* Setting the register is a bit more complex, let's assume that 'val'
|
||||
* is the value we want to set, already in the right range.
|
||||
*
|
||||
* We need two steps, in one we need to clear the bits, and in the other
|
||||
* we need to bitwise-OR the new bits.
|
||||
*
|
||||
* Let's try with 'pos' = 1, so our first byte at 'b' is 0,
|
||||
*
|
||||
* "fb" is 6 in this case.
|
||||
*
|
||||
* +--------+
|
||||
* |11000000| <- Our byte at b0
|
||||
* +--------+
|
||||
*
|
||||
* To create a AND-mask to clear the bits about this position, we just
|
||||
* initialize the mask with the value 63, left shift it of "fs" bits,
|
||||
* and finally invert the result.
|
||||
*
|
||||
* +--------+
|
||||
* |00111111| <- "mask" starts at 63
|
||||
* |11000000| <- "mask" after left shift of "ls" bits.
|
||||
* |00111111| <- "mask" after invert.
|
||||
* +--------+
|
||||
*
|
||||
* Now we can bitwise-AND the byte at "b" with the mask, and bitwise-OR
|
||||
* it with "val" left-shifted of "ls" bits to set the new bits.
|
||||
*
|
||||
* Now let's focus on the next byte b1:
|
||||
*
|
||||
* +--------+
|
||||
* |22221111| <- Initial value of b1
|
||||
* +--------+
|
||||
*
|
||||
* To build the AND mask we start again with the 63 value, right shift
|
||||
* it by 8-fb bits, and invert it.
|
||||
*
|
||||
* +--------+
|
||||
* |00111111| <- "mask" set at 2&6-1
|
||||
* |00001111| <- "mask" after the right shift by 8-fb = 2 bits
|
||||
* |11110000| <- "mask" after bitwise not.
|
||||
* +--------+
|
||||
*
|
||||
* Now we can mask it with b+1 to clear the old bits, and bitwise-OR
|
||||
* with "val" left-shifted by "rs" bits to set the new value.
|
||||
*/
|
||||
|
||||
/* Note: if we access the last counter, we will also access the b+1 byte
|
||||
* that is out of the array, but sds strings always have an implicit null
|
||||
* term, so the byte exists, and we can skip the conditional (or the need
|
||||
* to allocate 1 byte more explicitly). */
|
||||
|
||||
/* Store the value of the register at position 'regnum' into variable 'target'.
|
||||
* 'p' is an array of unsigned bytes. */
|
||||
#define HLL_GET_REGISTER(target,p,regnum) do { \
|
||||
uint8_t *_p = (uint8_t*) p; \
|
||||
unsigned long _byte = regnum*REDIS_HLL_BITS/8; \
|
||||
unsigned long _fb = regnum*REDIS_HLL_BITS&7; \
|
||||
unsigned long _fb8 = 8 - _fb; \
|
||||
unsigned long b0 = _p[_byte]; \
|
||||
unsigned long b1 = _p[_byte+1]; \
|
||||
target = ((b0 >> _fb) | (b1 << _fb8)) & REDIS_HLL_REGISTER_MAX; \
|
||||
} while(0)
|
||||
|
||||
/* Set the value of the register at position 'regnum' to 'val'.
|
||||
* 'p' is an array of unsigned bytes. */
|
||||
#define HLL_SET_REGISTER(p,regnum,val) do { \
|
||||
uint8_t *_p = (uint8_t*) p; \
|
||||
unsigned long _byte = regnum*REDIS_HLL_BITS/8; \
|
||||
unsigned long _fb = regnum*REDIS_HLL_BITS&7; \
|
||||
unsigned long _fb8 = 8 - _fb; \
|
||||
unsigned long _v = val; \
|
||||
_p[_byte] &= ~(REDIS_HLL_REGISTER_MAX << _fb); \
|
||||
_p[_byte] |= _v << _fb; \
|
||||
_p[_byte+1] &= ~(REDIS_HLL_REGISTER_MAX >> _fb8); \
|
||||
_p[_byte+1] |= _v >> _fb8; \
|
||||
} while(0)
|
||||
|
||||
/* ========================= HyperLogLog algorithm ========================= */
|
||||
|
||||
/* Our hash function is MurmurHash2, 64 bit version.
|
||||
* It was modified for Redis in order to provide the same result in
|
||||
* big and little endian archs (endian neutral). */
|
||||
uint64_t MurmurHash64A (const void * key, int len, unsigned int seed) {
|
||||
const uint64_t m = 0xc6a4a7935bd1e995;
|
||||
const int r = 47;
|
||||
uint64_t h = seed ^ (len * m);
|
||||
const uint8_t *data = (const uint8_t *)key;
|
||||
const uint8_t *end = data + (len-(len&7));
|
||||
|
||||
while(data != end) {
|
||||
uint64_t k;
|
||||
|
||||
#if (BYTE_ORDER == LITTLE_ENDIAN)
|
||||
k = *((uint64_t*)data);
|
||||
#else
|
||||
k = (uint64_t) data[0];
|
||||
k |= (uint64_t) data[1] << 8;
|
||||
k |= (uint64_t) data[2] << 16;
|
||||
k |= (uint64_t) data[3] << 24;
|
||||
k |= (uint64_t) data[4] << 32;
|
||||
k |= (uint64_t) data[5] << 40;
|
||||
k |= (uint64_t) data[6] << 48;
|
||||
k |= (uint64_t) data[7] << 56;
|
||||
#endif
|
||||
|
||||
k *= m;
|
||||
k ^= k >> r;
|
||||
k *= m;
|
||||
h ^= k;
|
||||
h *= m;
|
||||
data += 8;
|
||||
}
|
||||
|
||||
switch(len & 7) {
|
||||
case 7: h ^= (uint64_t)data[6] << 48;
|
||||
case 6: h ^= (uint64_t)data[5] << 40;
|
||||
case 5: h ^= (uint64_t)data[4] << 32;
|
||||
case 4: h ^= (uint64_t)data[3] << 24;
|
||||
case 3: h ^= (uint64_t)data[2] << 16;
|
||||
case 2: h ^= (uint64_t)data[1] << 8;
|
||||
case 1: h ^= (uint64_t)data[0];
|
||||
h *= m;
|
||||
};
|
||||
|
||||
h ^= h >> r;
|
||||
h *= m;
|
||||
h ^= h >> r;
|
||||
return h;
|
||||
}
|
||||
|
||||
/* "Add" the element in the hyperloglog data structure.
|
||||
* Actually nothing is added, but the max 0 pattern counter of the subset
|
||||
* the element belongs to is incremented if needed.
|
||||
*
|
||||
* 'registers' is expected to have room for REDIS_HLL_REGISTERS plus an
|
||||
* additional byte on the right. This requirement is met by sds strings
|
||||
* automatically since they are implicitly null terminated.
|
||||
*
|
||||
* The function always succeed, however if as a result of the operation
|
||||
* the approximated cardinality changed, 1 is returned. Otherwise 0
|
||||
* is returned. */
|
||||
int hllAdd(uint8_t *registers, unsigned char *ele, size_t elesize) {
|
||||
uint64_t hash, bit, index;
|
||||
uint8_t oldcount, count;
|
||||
|
||||
/* Count the number of zeroes starting from bit REDIS_HLL_REGISTERS
|
||||
* (that is a power of two corresponding to the first bit we don't use
|
||||
* as index). The max run can be 64-P+1 bits.
|
||||
*
|
||||
* Note that the final "1" ending the sequence of zeroes must be
|
||||
* included in the count, so if we find "001" the count is 3, and
|
||||
* the smallest count possible is no zeroes at all, just a 1 bit
|
||||
* at the first position, that is a count of 1.
|
||||
*
|
||||
* This may sound like inefficient, but actually in the average case
|
||||
* there are high probabilities to find a 1 after a few iterations. */
|
||||
hash = MurmurHash64A(ele,elesize,0xadc83b19ULL);
|
||||
hash |= ((uint64_t)1<<63); /* Make sure the loop terminates. */
|
||||
bit = REDIS_HLL_REGISTERS; /* First bit not used to address the register. */
|
||||
count = 1; /* Initialized to 1 since we count the "00000...1" pattern. */
|
||||
while((hash & bit) == 0) {
|
||||
count++;
|
||||
bit <<= 1;
|
||||
}
|
||||
|
||||
/* Update the register if this element produced a longer run of zeroes. */
|
||||
index = hash & REDIS_HLL_P_MASK; /* Index a register inside registers. */
|
||||
HLL_GET_REGISTER(oldcount,registers,index);
|
||||
if (count > oldcount) {
|
||||
HLL_SET_REGISTER(registers,index,count);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the approximated cardinality of the set based on the armonic
|
||||
* mean of the registers values. */
|
||||
uint64_t hllCount(uint8_t *registers) {
|
||||
double m = REDIS_HLL_REGISTERS;
|
||||
double alpha = 0.7213/(1+1.079/m);
|
||||
double E = 0;
|
||||
int ez = 0; /* Number of registers equal to 0. */
|
||||
int j;
|
||||
|
||||
/* We precompute 2^(-reg[j]) in a small table in order to
|
||||
* speedup the computation of SUM(2^-register[0..i]). */
|
||||
static int initialized = 0;
|
||||
static double PE[64];
|
||||
if (!initialized) {
|
||||
PE[0] = 1; /* 2^(-reg[j]) is 1 when m is 0. */
|
||||
for (j = 1; j < 64; j++) {
|
||||
/* 2^(-reg[j]) is the same as 1/2^reg[j]. */
|
||||
PE[j] = 1.0/(1ULL << j);
|
||||
}
|
||||
initialized = 1;
|
||||
}
|
||||
|
||||
/* Compute SUM(2^-register[0..i]).
|
||||
* Redis default is to use 16384 registers 6 bits each. The code works
|
||||
* with other values by modifying the defines, but for our target value
|
||||
* we take a faster path with unrolled loops. */
|
||||
if (REDIS_HLL_REGISTERS == 16384 && REDIS_HLL_BITS == 6) {
|
||||
uint8_t *r = registers;
|
||||
unsigned long r0, r1, r2, r3, r4, r5, r6, r7, r8, r9,
|
||||
r10, r11, r12, r13, r14, r15;
|
||||
for (j = 0; j < 1024; j++) {
|
||||
/* Handle 16 registers per iteration. */
|
||||
r0 = r[0] & 63; if (r0 == 0) ez++;
|
||||
r1 = (r[0] >> 6 | r[1] << 2) & 63; if (r1 == 0) ez++;
|
||||
r2 = (r[1] >> 4 | r[2] << 4) & 63; if (r2 == 0) ez++;
|
||||
r3 = (r[2] >> 2) & 63; if (r3 == 0) ez++;
|
||||
r4 = r[3] & 63; if (r4 == 0) ez++;
|
||||
r5 = (r[3] >> 6 | r[4] << 2) & 63; if (r5 == 0) ez++;
|
||||
r6 = (r[4] >> 4 | r[5] << 4) & 63; if (r6 == 0) ez++;
|
||||
r7 = (r[5] >> 2) & 63; if (r7 == 0) ez++;
|
||||
r8 = r[6] & 63; if (r8 == 0) ez++;
|
||||
r9 = (r[6] >> 6 | r[7] << 2) & 63; if (r9 == 0) ez++;
|
||||
r10 = (r[7] >> 4 | r[8] << 4) & 63; if (r10 == 0) ez++;
|
||||
r11 = (r[8] >> 2) & 63; if (r11 == 0) ez++;
|
||||
r12 = r[9] & 63; if (r12 == 0) ez++;
|
||||
r13 = (r[9] >> 6 | r[10] << 2) & 63; if (r13 == 0) ez++;
|
||||
r14 = (r[10] >> 4 | r[11] << 4) & 63; if (r14 == 0) ez++;
|
||||
r15 = (r[11] >> 2) & 63; if (r15 == 0) ez++;
|
||||
|
||||
/* Additional parens will allow the compiler to optimize the
|
||||
* code more with a loss of precision that is not very relevant
|
||||
* here (floating point math is not commutative!). */
|
||||
E += (PE[r0] + PE[r1]) + (PE[r2] + PE[r3]) + (PE[r4] + PE[r5]) +
|
||||
(PE[r6] + PE[r7]) + (PE[r8] + PE[r9]) + (PE[r10] + PE[r11]) +
|
||||
(PE[r12] + PE[r13]) + (PE[r14] + PE[r15]);
|
||||
r += 12;
|
||||
}
|
||||
} else {
|
||||
for (j = 0; j < REDIS_HLL_REGISTERS; j++) {
|
||||
unsigned long reg;
|
||||
|
||||
HLL_GET_REGISTER(reg,registers,j);
|
||||
if (reg == 0) {
|
||||
ez++;
|
||||
E += 1; /* 2^(-reg[j]) is 1 when m is 0. */
|
||||
} else {
|
||||
E += PE[reg]; /* Precomputed 2^(-reg[j]). */
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Muliply the inverse of E for alpha_m * m^2 to have the raw estimate. */
|
||||
E = (1/E)*alpha*m*m;
|
||||
|
||||
/* Use the LINEARCOUNTING algorithm for small cardinalities.
|
||||
* For larger values but up to 72000 HyperLogLog raw approximation is
|
||||
* used since linear counting error starts to increase. However HyperLogLog
|
||||
* shows a strong bias in the range 2.5*16384 - 72000, so we try to
|
||||
* compensate for it. */
|
||||
if (E < m*2.5 && ez != 0) {
|
||||
E = m*log(m/ez); /* LINEARCOUNTING() */
|
||||
} else if (m == 16384 && E < 72000) {
|
||||
/* We did polynomial regression of the bias for this range, this
|
||||
* way we can compute the bias for a given cardinality and correct
|
||||
* according to it. Only apply the correction for P=14 that's what
|
||||
* we use and the value the correction was verified with. */
|
||||
double bias = 5.9119*1.0e-18*(E*E*E*E)
|
||||
-1.4253*1.0e-12*(E*E*E)+
|
||||
1.2940*1.0e-7*(E*E)
|
||||
-5.2921*1.0e-3*E+
|
||||
83.3216;
|
||||
E -= E*(bias/100);
|
||||
}
|
||||
/* We don't apply the correction for E > 1/30 of 2^32 since we use
|
||||
* a 64 bit function and 6 bit counters. To apply the correction for
|
||||
* 1/30 of 2^64 is not needed since it would require a huge set
|
||||
* to approach such a value. */
|
||||
return (uint64_t) E;
|
||||
}
|
||||
|
||||
/* ========================== HyperLogLog commands ========================== */
|
||||
|
||||
/* An HyperLogLog object is a string with space for 16k 6-bit integers,
|
||||
* a cached 64 bit cardinality value, and a 4 byte "magic" and additional
|
||||
* 4 bytes for version reserved for future use. */
|
||||
robj *createHLLObject(void) {
|
||||
robj *o;
|
||||
char *p;
|
||||
|
||||
/* Create a string of the right size filled with zero bytes.
|
||||
* Note that the cached cardinality is set to 0 as a side effect
|
||||
* that is exactly the cardinality of an empty HLL. */
|
||||
o = createObject(REDIS_STRING,sdsnewlen(NULL,REDIS_HLL_SIZE));
|
||||
p = o->ptr;
|
||||
memcpy(p+REDIS_HLL_SIZE-8,"HYLL",4);
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Check if the object is a String of REDIS_HLL_SIZE bytes.
|
||||
* Return REDIS_OK if this is true, otherwise reply to the client
|
||||
* with an error and return REDIS_ERR. */
|
||||
int isHLLObjectOrReply(redisClient *c, robj *o) {
|
||||
/* Key exists, check type */
|
||||
if (checkType(c,o,REDIS_STRING))
|
||||
return REDIS_ERR; /* Error already sent. */
|
||||
|
||||
/* If this is a string representing an HLL, the size should match
|
||||
* exactly. */
|
||||
if (stringObjectLen(o) != REDIS_HLL_SIZE) {
|
||||
addReplySds(c,
|
||||
sdsnew("-WRONGTYPE Key is not a valid "
|
||||
"HyperLogLog string value.\r\n"));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* PFADD var ele ele ele ... ele => :0 or :1 */
|
||||
void pfaddCommand(redisClient *c) {
|
||||
robj *o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
uint8_t *registers;
|
||||
int updated = 0, j;
|
||||
|
||||
if (o == NULL) {
|
||||
/* Create the key with a string value of the exact length to
|
||||
* hold our HLL data structure. sdsnewlen() when NULL is passed
|
||||
* is guaranteed to return bytes initialized to zero. */
|
||||
o = createHLLObject();
|
||||
dbAdd(c->db,c->argv[1],o);
|
||||
updated++;
|
||||
} else {
|
||||
if (isHLLObjectOrReply(c,o) != REDIS_OK) return;
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
}
|
||||
/* Perform the low level ADD operation for every element. */
|
||||
registers = o->ptr;
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
if (hllAdd(registers, (unsigned char*)c->argv[j]->ptr,
|
||||
sdslen(c->argv[j]->ptr)))
|
||||
{
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
/* Invalidate the cached cardinality. */
|
||||
registers[REDIS_HLL_SIZE-9] |= (1<<7);
|
||||
}
|
||||
addReply(c, updated ? shared.cone : shared.czero);
|
||||
}
|
||||
|
||||
/* PFCOUNT var -> approximated cardinality of set. */
|
||||
void pfcountCommand(redisClient *c) {
|
||||
robj *o = lookupKeyRead(c->db,c->argv[1]);
|
||||
uint8_t *registers;
|
||||
uint64_t card;
|
||||
|
||||
if (o == NULL) {
|
||||
/* No key? Cardinality is zero since no element was added, otherwise
|
||||
* we would have a key as HLLADD creates it as a side effect. */
|
||||
addReply(c,shared.czero);
|
||||
} else {
|
||||
if (isHLLObjectOrReply(c,o) != REDIS_OK) return;
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
|
||||
/* Check if the cached cardinality is valid. */
|
||||
registers = o->ptr;
|
||||
if ((registers[REDIS_HLL_SIZE-9] & (1<<7)) == 0) {
|
||||
/* Just return the cached value. */
|
||||
card = (uint64_t)registers[REDIS_HLL_SIZE-16];
|
||||
card |= (uint64_t)registers[REDIS_HLL_SIZE-15] << 8;
|
||||
card |= (uint64_t)registers[REDIS_HLL_SIZE-14] << 16;
|
||||
card |= (uint64_t)registers[REDIS_HLL_SIZE-13] << 24;
|
||||
card |= (uint64_t)registers[REDIS_HLL_SIZE-12] << 32;
|
||||
card |= (uint64_t)registers[REDIS_HLL_SIZE-11] << 40;
|
||||
card |= (uint64_t)registers[REDIS_HLL_SIZE-10] << 48;
|
||||
card |= (uint64_t)registers[REDIS_HLL_SIZE-9] << 56;
|
||||
} else {
|
||||
/* Recompute it and update the cached value. */
|
||||
card = hllCount(registers);
|
||||
registers[REDIS_HLL_SIZE-16] = card & 0xff;
|
||||
registers[REDIS_HLL_SIZE-15] = (card >> 8) & 0xff;
|
||||
registers[REDIS_HLL_SIZE-14] = (card >> 16) & 0xff;
|
||||
registers[REDIS_HLL_SIZE-13] = (card >> 24) & 0xff;
|
||||
registers[REDIS_HLL_SIZE-12] = (card >> 32) & 0xff;
|
||||
registers[REDIS_HLL_SIZE-11] = (card >> 40) & 0xff;
|
||||
registers[REDIS_HLL_SIZE-10] = (card >> 48) & 0xff;
|
||||
registers[REDIS_HLL_SIZE-9] = (card >> 56) & 0xff;
|
||||
/* This is not considered a read-only command even if the
|
||||
* data structure is not modified, since the cached value
|
||||
* may be modified and given that the HLL is a Redis string
|
||||
* we need to propagate the change. */
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
addReplyLongLong(c,card);
|
||||
}
|
||||
}
|
||||
|
||||
/* PFMERGE dest src1 src2 src3 ... srcN => OK */
|
||||
void pfmergeCommand(redisClient *c) {
|
||||
uint8_t max[REDIS_HLL_REGISTERS];
|
||||
uint8_t *registers;
|
||||
int j, i;
|
||||
|
||||
/* Compute an HLL with M[i] = MAX(M[i]_j).
|
||||
* We we the maximum into the max array of registers. We'll write
|
||||
* it to the target variable later. */
|
||||
memset(max,0,sizeof(max));
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
uint8_t val;
|
||||
|
||||
/* Check type and size. */
|
||||
robj *o = lookupKeyRead(c->db,c->argv[j]);
|
||||
if (o == NULL) continue; /* Assume empty HLL for non existing var. */
|
||||
if (isHLLObjectOrReply(c,o) != REDIS_OK) return;
|
||||
|
||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
||||
* to MAX(max[i],hll[i]). */
|
||||
registers = o->ptr;
|
||||
for (i = 0; i < REDIS_HLL_REGISTERS; i++) {
|
||||
HLL_GET_REGISTER(val,registers,i);
|
||||
if (val > max[i]) max[i] = val;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create / unshare the destination key's value if needed. */
|
||||
robj *o = lookupKeyRead(c->db,c->argv[1]);
|
||||
if (o == NULL) {
|
||||
/* Create the key with a string value of the exact length to
|
||||
* hold our HLL data structure. sdsnewlen() when NULL is passed
|
||||
* is guaranteed to return bytes initialized to zero. */
|
||||
o = createHLLObject();
|
||||
dbAdd(c->db,c->argv[1],o);
|
||||
} else {
|
||||
/* If key exists we are sure it's of the right type/size
|
||||
* since we checked when merging the different HLLs, so we
|
||||
* don't check again. */
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
}
|
||||
|
||||
/* Write the resulting HLL to the destination HLL registers and
|
||||
* invalidate the cached value. */
|
||||
registers = o->ptr;
|
||||
for (j = 0; j < REDIS_HLL_REGISTERS; j++) {
|
||||
HLL_SET_REGISTER(registers,j,max[j]);
|
||||
}
|
||||
registers[REDIS_HLL_SIZE-9] |= (1<<7);
|
||||
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
/* We generate an HLLADD event for HLLMERGE for semantical simplicity
|
||||
* since in theory this is a mass-add of elements. */
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
/* ========================== Testing / Debugging ========================== */
|
||||
|
||||
/* PFSELFTEST
|
||||
* This command performs a self-test of the HLL registers implementation.
|
||||
* Something that is not easy to test from within the outside. */
|
||||
#define REDIS_HLL_TEST_CYCLES 1000
|
||||
void pfselftestCommand(redisClient *c) {
|
||||
int j, i;
|
||||
sds bitcounters = sdsnewlen(NULL,REDIS_HLL_SIZE);
|
||||
uint8_t bytecounters[REDIS_HLL_REGISTERS];
|
||||
|
||||
/* Test 1: access registers.
|
||||
* The test is conceived to test that the different counters of our data
|
||||
* structure are accessible and that setting their values both result in
|
||||
* the correct value to be retained and not affect adjacent values. */
|
||||
for (j = 0; j < REDIS_HLL_TEST_CYCLES; j++) {
|
||||
/* Set the HLL counters and an array of unsigned byes of the
|
||||
* same size to the same set of random values. */
|
||||
for (i = 0; i < REDIS_HLL_REGISTERS; i++) {
|
||||
unsigned int r = rand() & REDIS_HLL_REGISTER_MAX;
|
||||
|
||||
bytecounters[i] = r;
|
||||
HLL_SET_REGISTER(bitcounters,i,r);
|
||||
}
|
||||
/* Check that we are able to retrieve the same values. */
|
||||
for (i = 0; i < REDIS_HLL_REGISTERS; i++) {
|
||||
unsigned int val;
|
||||
|
||||
HLL_GET_REGISTER(val,bitcounters,i);
|
||||
if (val != bytecounters[i]) {
|
||||
addReplyErrorFormat(c,
|
||||
"TESTFAILED Register %d should be %d but is %d",
|
||||
i, (int) bytecounters[i], (int) val);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Test 2: approximation error.
|
||||
* The test is adds unique elements and check that the estimated value
|
||||
* is always reasonable bounds.
|
||||
*
|
||||
* We check that the error is smaller than 4 times than the expected
|
||||
* standard error, to make it very unlikely for the test to fail because
|
||||
* of a "bad" run. */
|
||||
memset(bitcounters,0,REDIS_HLL_SIZE);
|
||||
double relerr = 1.04/sqrt(REDIS_HLL_REGISTERS);
|
||||
int64_t checkpoint = 1000;
|
||||
uint64_t seed = (uint64_t)rand() | (uint64_t)rand() << 32;
|
||||
uint64_t ele;
|
||||
for (j = 1; j <= 10000000; j++) {
|
||||
ele = j ^ seed;
|
||||
hllAdd((uint8_t*)bitcounters,(unsigned char*)&ele,sizeof(ele));
|
||||
if (j == checkpoint) {
|
||||
int64_t abserr = checkpoint-
|
||||
(int64_t)hllCount((uint8_t*)bitcounters);
|
||||
if (abserr < 0) abserr = -abserr;
|
||||
if (abserr > (uint64_t)(relerr*4*checkpoint)) {
|
||||
addReplyErrorFormat(c,
|
||||
"TESTFAILED Too big error. card:%llu abserr:%llu",
|
||||
(unsigned long long) checkpoint,
|
||||
(unsigned long long) abserr);
|
||||
goto cleanup;
|
||||
}
|
||||
checkpoint *= 10;
|
||||
}
|
||||
}
|
||||
|
||||
/* Success! */
|
||||
addReply(c,shared.ok);
|
||||
|
||||
cleanup:
|
||||
sdsfree(bitcounters);
|
||||
}
|
||||
|
||||
/* PFGETREG
|
||||
* Return the registers values of the specified HLL. */
|
||||
void pfgetregCommand(redisClient *c) {
|
||||
robj *o = lookupKeyRead(c->db,c->argv[1]);
|
||||
uint8_t *registers;
|
||||
int j;
|
||||
|
||||
if (o == NULL) {
|
||||
addReplyError(c,"The specified key does not exist");
|
||||
return;
|
||||
} else {
|
||||
if (isHLLObjectOrReply(c,o) != REDIS_OK) return;
|
||||
|
||||
registers = o->ptr;
|
||||
addReplyMultiBulkLen(c,REDIS_HLL_REGISTERS);
|
||||
for (j = 0; j < REDIS_HLL_REGISTERS; j++) {
|
||||
uint8_t val;
|
||||
|
||||
HLL_GET_REGISTER(val,registers,j);
|
||||
addReplyLongLong(c,val);
|
||||
}
|
||||
}
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
#include "redis.h"
|
||||
#include "endianconv.h"
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* DUMP, RESTORE and MIGRATE commands
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Generates a DUMP-format representation of the object 'o', adding it to the
|
||||
* io stream pointed by 'rio'. This function can't fail. */
|
||||
void createDumpPayload(rio *payload, robj *o) {
|
||||
unsigned char buf[2];
|
||||
uint64_t crc;
|
||||
|
||||
/* Serialize the object in a RDB-like format. It consist of an object type
|
||||
* byte followed by the serialized object. This is understood by RESTORE. */
|
||||
rioInitWithBuffer(payload,sdsempty());
|
||||
redisAssert(rdbSaveObjectType(payload,o));
|
||||
redisAssert(rdbSaveObject(payload,o));
|
||||
|
||||
/* Write the footer, this is how it looks like:
|
||||
* ----------------+---------------------+---------------+
|
||||
* ... RDB payload | 2 bytes RDB version | 8 bytes CRC64 |
|
||||
* ----------------+---------------------+---------------+
|
||||
* RDB version and CRC are both in little endian.
|
||||
*/
|
||||
|
||||
/* RDB version */
|
||||
buf[0] = REDIS_RDB_VERSION & 0xff;
|
||||
buf[1] = (REDIS_RDB_VERSION >> 8) & 0xff;
|
||||
payload->io.buffer.ptr = sdscatlen(payload->io.buffer.ptr,buf,2);
|
||||
|
||||
/* CRC64 */
|
||||
crc = crc64(0,(unsigned char*)payload->io.buffer.ptr,
|
||||
sdslen(payload->io.buffer.ptr));
|
||||
memrev64ifbe(&crc);
|
||||
payload->io.buffer.ptr = sdscatlen(payload->io.buffer.ptr,&crc,8);
|
||||
}
|
||||
|
||||
/* Verify that the RDB version of the dump payload matches the one of this Redis
|
||||
* instance and that the checksum is ok.
|
||||
* If the DUMP payload looks valid REDIS_OK is returned, otherwise REDIS_ERR
|
||||
* is returned. */
|
||||
int verifyDumpPayload(unsigned char *p, size_t len) {
|
||||
unsigned char *footer;
|
||||
uint16_t rdbver;
|
||||
uint64_t crc;
|
||||
|
||||
/* At least 2 bytes of RDB version and 8 of CRC64 should be present. */
|
||||
if (len < 10) return REDIS_ERR;
|
||||
footer = p+(len-10);
|
||||
|
||||
/* Verify RDB version */
|
||||
rdbver = (footer[1] << 8) | footer[0];
|
||||
if (rdbver != REDIS_RDB_VERSION) return REDIS_ERR;
|
||||
|
||||
/* Verify CRC64 */
|
||||
crc = crc64(0,p,len-8);
|
||||
memrev64ifbe(&crc);
|
||||
return (memcmp(&crc,footer+2,8) == 0) ? REDIS_OK : REDIS_ERR;
|
||||
}
|
||||
|
||||
/* DUMP keyname
|
||||
* DUMP is actually not used by Redis Cluster but it is the obvious
|
||||
* complement of RESTORE and can be useful for different applications. */
|
||||
void dumpCommand(redisClient *c) {
|
||||
robj *o, *dumpobj;
|
||||
rio payload;
|
||||
|
||||
/* Check if the key is here. */
|
||||
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create the DUMP encoded representation. */
|
||||
createDumpPayload(&payload,o);
|
||||
|
||||
/* Transfer to the client */
|
||||
dumpobj = createObject(REDIS_STRING,payload.io.buffer.ptr);
|
||||
addReplyBulk(c,dumpobj);
|
||||
decrRefCount(dumpobj);
|
||||
return;
|
||||
}
|
||||
|
||||
/* RESTORE key ttl serialized-value */
|
||||
void restoreCommand(redisClient *c) {
|
||||
long ttl;
|
||||
rio payload;
|
||||
int type;
|
||||
robj *obj;
|
||||
|
||||
/* Make sure this key does not already exist here... */
|
||||
if (lookupKeyWrite(c->db,c->argv[1]) != NULL) {
|
||||
addReplyError(c,"Target key name is busy.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if the TTL value makes sense */
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&ttl,NULL) != REDIS_OK) {
|
||||
return;
|
||||
} else if (ttl < 0) {
|
||||
addReplyError(c,"Invalid TTL value, must be >= 0");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Verify RDB version and data checksum. */
|
||||
if (verifyDumpPayload(c->argv[3]->ptr,sdslen(c->argv[3]->ptr)) == REDIS_ERR) {
|
||||
addReplyError(c,"DUMP payload version or checksum are wrong");
|
||||
return;
|
||||
}
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload)) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create the key and set the TTL if any */
|
||||
dbAdd(c->db,c->argv[1],obj);
|
||||
if (ttl) setExpire(c->db,c->argv[1],mstime()+ttl);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
/* MIGRATE host port key dbid timeout */
|
||||
void migrateCommand(redisClient *c) {
|
||||
int fd;
|
||||
long timeout;
|
||||
long dbid;
|
||||
long long ttl = 0, expireat;
|
||||
robj *o;
|
||||
rio cmd, payload;
|
||||
|
||||
/* Sanity check */
|
||||
if (getLongFromObjectOrReply(c,c->argv[5],&timeout,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (getLongFromObjectOrReply(c,c->argv[4],&dbid,NULL) != REDIS_OK)
|
||||
return;
|
||||
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
|
||||
* we include such information in the reply string. */
|
||||
if ((o = lookupKeyRead(c->db,c->argv[3])) == NULL) {
|
||||
addReplySds(c,sdsnew("+NOKEY\r\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Connect */
|
||||
fd = anetTcpNonBlockConnect(server.neterr,c->argv[1]->ptr,
|
||||
atoi(c->argv[2]->ptr));
|
||||
if (fd == -1) {
|
||||
addReplyErrorFormat(c,"Can't connect to target node: %s",
|
||||
server.neterr);
|
||||
return;
|
||||
}
|
||||
if ((aeWait(fd,AE_WRITABLE,timeout*1000) & AE_WRITABLE) == 0) {
|
||||
addReplySds(c,sdsnew("-IOERR error or timeout connecting to the client\r\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create RESTORE payload and generate the protocol to call the command. */
|
||||
rioInitWithBuffer(&cmd,sdsempty());
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',2));
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"SELECT",6));
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,dbid));
|
||||
|
||||
expireat = getExpire(c->db,c->argv[3]);
|
||||
if (expireat != -1) {
|
||||
ttl = expireat-mstime();
|
||||
if (ttl < 1) ttl = 1;
|
||||
}
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',4));
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"RESTORE",7));
|
||||
redisAssertWithInfo(c,NULL,c->argv[3]->encoding == REDIS_ENCODING_RAW);
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,c->argv[3]->ptr,sdslen(c->argv[3]->ptr)));
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,ttl));
|
||||
|
||||
/* Finally the last argument that is the serailized object payload
|
||||
* in the DUMP format. */
|
||||
createDumpPayload(&payload,o);
|
||||
redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,payload.io.buffer.ptr,
|
||||
sdslen(payload.io.buffer.ptr)));
|
||||
sdsfree(payload.io.buffer.ptr);
|
||||
|
||||
/* Tranfer the query to the other node in 64K chunks. */
|
||||
{
|
||||
sds buf = cmd.io.buffer.ptr;
|
||||
size_t pos = 0, towrite;
|
||||
int nwritten = 0;
|
||||
|
||||
while ((towrite = sdslen(buf)-pos) > 0) {
|
||||
towrite = (towrite > (64*1024) ? (64*1024) : towrite);
|
||||
nwritten = syncWrite(fd,buf+pos,towrite,timeout);
|
||||
if (nwritten != (signed)towrite) goto socket_wr_err;
|
||||
pos += nwritten;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read back the reply. */
|
||||
{
|
||||
char buf1[1024];
|
||||
char buf2[1024];
|
||||
|
||||
/* Read the two replies */
|
||||
if (syncReadLine(fd, buf1, sizeof(buf1), timeout) <= 0)
|
||||
goto socket_rd_err;
|
||||
if (syncReadLine(fd, buf2, sizeof(buf2), timeout) <= 0)
|
||||
goto socket_rd_err;
|
||||
if (buf1[0] == '-' || buf2[0] == '-') {
|
||||
addReplyErrorFormat(c,"Target instance replied with error: %s",
|
||||
(buf1[0] == '-') ? buf1+1 : buf2+1);
|
||||
} else {
|
||||
robj *aux;
|
||||
|
||||
dbDelete(c->db,c->argv[3]);
|
||||
signalModifiedKey(c->db,c->argv[3]);
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
|
||||
/* Translate MIGRATE as DEL for replication/AOF. */
|
||||
aux = createStringObject("DEL",3);
|
||||
rewriteClientCommandVector(c,2,aux,c->argv[3]);
|
||||
decrRefCount(aux);
|
||||
}
|
||||
}
|
||||
|
||||
sdsfree(cmd.io.buffer.ptr);
|
||||
close(fd);
|
||||
return;
|
||||
|
||||
socket_wr_err:
|
||||
addReplySds(c,sdsnew("-IOERR error or timeout writing to target instance\r\n"));
|
||||
sdsfree(cmd.io.buffer.ptr);
|
||||
close(fd);
|
||||
return;
|
||||
|
||||
socket_rd_err:
|
||||
addReplySds(c,sdsnew("-IOERR error or timeout reading from target node\r\n"));
|
||||
sdsfree(cmd.io.buffer.ptr);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
+28
-127
@@ -41,17 +41,6 @@ size_t zmalloc_size_sds(sds s) {
|
||||
return zmalloc_size(s-sizeof(struct sdshdr));
|
||||
}
|
||||
|
||||
/* Return the amount of memory used by the sds string at object->ptr
|
||||
* for a string object. */
|
||||
size_t getStringObjectSdsUsedMemory(robj *o) {
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
switch(o->encoding) {
|
||||
case REDIS_ENCODING_RAW: return zmalloc_size_sds(o->ptr);
|
||||
case REDIS_ENCODING_EMBSTR: return sdslen(o->ptr);
|
||||
default: return 0; /* Just integer encoding for now. */
|
||||
}
|
||||
}
|
||||
|
||||
void *dupClientReplyValue(void *o) {
|
||||
incrRefCount((robj*)o);
|
||||
return o;
|
||||
@@ -108,13 +97,9 @@ redisClient *createClient(int fd) {
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
listSetFreeMethod(c->reply,decrRefCountVoid);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
c->btype = REDIS_BLOCKED_NONE;
|
||||
c->bpop.timeout = 0;
|
||||
c->bpop.keys = dictCreate(&setDictType,NULL);
|
||||
c->bpop.timeout = 0;
|
||||
c->bpop.target = NULL;
|
||||
c->bpop.numreplicas = 0;
|
||||
c->bpop.reploffset = 0;
|
||||
c->woff = 0;
|
||||
c->watched_keys = listCreate();
|
||||
c->pubsub_channels = dictCreate(&setDictType,NULL);
|
||||
c->pubsub_patterns = listCreate();
|
||||
@@ -197,13 +182,12 @@ void _addReplyObjectToList(redisClient *c, robj *o) {
|
||||
if (listLength(c->reply) == 0) {
|
||||
incrRefCount(o);
|
||||
listAddNodeTail(c->reply,o);
|
||||
c->reply_bytes += getStringObjectSdsUsedMemory(o);
|
||||
c->reply_bytes += zmalloc_size_sds(o->ptr);
|
||||
} else {
|
||||
tail = listNodeValue(listLast(c->reply));
|
||||
|
||||
/* Append to this object when possible. */
|
||||
if (tail->ptr != NULL &&
|
||||
tail->encoding == REDIS_ENCODING_RAW &&
|
||||
sdslen(tail->ptr)+sdslen(o->ptr) <= REDIS_REPLY_CHUNK_BYTES)
|
||||
{
|
||||
c->reply_bytes -= zmalloc_size_sds(tail->ptr);
|
||||
@@ -213,7 +197,7 @@ void _addReplyObjectToList(redisClient *c, robj *o) {
|
||||
} else {
|
||||
incrRefCount(o);
|
||||
listAddNodeTail(c->reply,o);
|
||||
c->reply_bytes += getStringObjectSdsUsedMemory(o);
|
||||
c->reply_bytes += zmalloc_size_sds(o->ptr);
|
||||
}
|
||||
}
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
@@ -236,7 +220,7 @@ void _addReplySdsToList(redisClient *c, sds s) {
|
||||
tail = listNodeValue(listLast(c->reply));
|
||||
|
||||
/* Append to this object when possible. */
|
||||
if (tail->ptr != NULL && tail->encoding == REDIS_ENCODING_RAW &&
|
||||
if (tail->ptr != NULL &&
|
||||
sdslen(tail->ptr)+sdslen(s) <= REDIS_REPLY_CHUNK_BYTES)
|
||||
{
|
||||
c->reply_bytes -= zmalloc_size_sds(tail->ptr);
|
||||
@@ -261,12 +245,12 @@ void _addReplyStringToList(redisClient *c, char *s, size_t len) {
|
||||
robj *o = createStringObject(s,len);
|
||||
|
||||
listAddNodeTail(c->reply,o);
|
||||
c->reply_bytes += getStringObjectSdsUsedMemory(o);
|
||||
c->reply_bytes += zmalloc_size_sds(o->ptr);
|
||||
} else {
|
||||
tail = listNodeValue(listLast(c->reply));
|
||||
|
||||
/* Append to this object when possible. */
|
||||
if (tail->ptr != NULL && tail->encoding == REDIS_ENCODING_RAW &&
|
||||
if (tail->ptr != NULL &&
|
||||
sdslen(tail->ptr)+len <= REDIS_REPLY_CHUNK_BYTES)
|
||||
{
|
||||
c->reply_bytes -= zmalloc_size_sds(tail->ptr);
|
||||
@@ -277,7 +261,7 @@ void _addReplyStringToList(redisClient *c, char *s, size_t len) {
|
||||
robj *o = createStringObject(s,len);
|
||||
|
||||
listAddNodeTail(c->reply,o);
|
||||
c->reply_bytes += getStringObjectSdsUsedMemory(o);
|
||||
c->reply_bytes += zmalloc_size_sds(o->ptr);
|
||||
}
|
||||
}
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
@@ -298,7 +282,7 @@ void addReply(redisClient *c, robj *obj) {
|
||||
* If the encoding is RAW and there is room in the static buffer
|
||||
* we'll be able to send the object to the client without
|
||||
* messing with its page. */
|
||||
if (sdsEncodedObject(obj)) {
|
||||
if (obj->encoding == REDIS_ENCODING_RAW) {
|
||||
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
|
||||
_addReplyObjectToList(c,obj);
|
||||
} else if (obj->encoding == REDIS_ENCODING_INT) {
|
||||
@@ -319,11 +303,6 @@ void addReply(redisClient *c, robj *obj) {
|
||||
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
|
||||
_addReplyObjectToList(c,obj);
|
||||
decrRefCount(obj);
|
||||
} else if (obj->encoding == REDIS_ENCODING_LZF) {
|
||||
obj = getDecodedObject(obj);
|
||||
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
|
||||
_addReplyObjectToList(c,obj);
|
||||
decrRefCount(obj);
|
||||
} else {
|
||||
redisPanic("Wrong obj->encoding in addReply()");
|
||||
}
|
||||
@@ -415,7 +394,6 @@ void setDeferredMultiBulkLength(redisClient *c, void *node, long length) {
|
||||
|
||||
len = listNodeValue(ln);
|
||||
len->ptr = sdscatprintf(sdsempty(),"*%ld\r\n",length);
|
||||
len->encoding = REDIS_ENCODING_RAW; /* in case it was an EMBSTR. */
|
||||
c->reply_bytes += zmalloc_size_sds(len->ptr);
|
||||
if (ln->next != NULL) {
|
||||
next = listNodeValue(ln->next);
|
||||
@@ -423,7 +401,7 @@ void setDeferredMultiBulkLength(redisClient *c, void *node, long length) {
|
||||
/* Only glue when the next node is non-NULL (an sds in this case) */
|
||||
if (next->ptr != NULL) {
|
||||
c->reply_bytes -= zmalloc_size_sds(len->ptr);
|
||||
c->reply_bytes -= getStringObjectSdsUsedMemory(next);
|
||||
c->reply_bytes -= zmalloc_size_sds(next->ptr);
|
||||
len->ptr = sdscatlen(len->ptr,next->ptr,sdslen(next->ptr));
|
||||
c->reply_bytes += zmalloc_size_sds(len->ptr);
|
||||
listDelNode(c->reply,ln->next);
|
||||
@@ -491,9 +469,9 @@ void addReplyMultiBulkLen(redisClient *c, long length) {
|
||||
void addReplyBulkLen(redisClient *c, robj *obj) {
|
||||
size_t len;
|
||||
|
||||
if (sdsEncodedObject(obj)) {
|
||||
if (obj->encoding == REDIS_ENCODING_RAW) {
|
||||
len = sdslen(obj->ptr);
|
||||
} else if (obj->encoding == REDIS_ENCODING_INT) {
|
||||
} else {
|
||||
long n = (long)obj->ptr;
|
||||
|
||||
/* Compute how many bytes will take this integer as a radix 10 string */
|
||||
@@ -505,9 +483,6 @@ void addReplyBulkLen(redisClient *c, robj *obj) {
|
||||
while((n = n/10) != 0) {
|
||||
len++;
|
||||
}
|
||||
} else {
|
||||
/* LZF and others not handled explicitly. */
|
||||
len = stringObjectLen(obj);
|
||||
}
|
||||
|
||||
if (len < REDIS_SHARED_BULKHDR_LEN)
|
||||
@@ -595,7 +570,7 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (cfd == AE_ERR) {
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
@@ -662,26 +637,15 @@ void freeClient(redisClient *c) {
|
||||
*
|
||||
* 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) {
|
||||
redisLog(REDIS_WARNING,"Connection with master lost.");
|
||||
if (!(c->flags & (REDIS_CLOSE_AFTER_REPLY|
|
||||
REDIS_CLOSE_ASAP|
|
||||
REDIS_BLOCKED|
|
||||
REDIS_UNBLOCKED)))
|
||||
{
|
||||
replicationCacheMaster(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Log link disconnection with slave */
|
||||
if ((c->flags & REDIS_SLAVE) && !(c->flags & REDIS_MONITOR)) {
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
|
||||
if (anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1) {
|
||||
redisLog(REDIS_WARNING,"Connection with slave %s:%d lost.",
|
||||
ip, c->slave_listening_port);
|
||||
}
|
||||
if (server.master &&
|
||||
(c->flags & REDIS_MASTER) &&
|
||||
!(c->flags & (REDIS_CLOSE_AFTER_REPLY|
|
||||
REDIS_CLOSE_ASAP|
|
||||
REDIS_BLOCKED|
|
||||
REDIS_UNBLOCKED)))
|
||||
{
|
||||
replicationCacheMaster(c);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Free the query buffer */
|
||||
@@ -689,7 +653,8 @@ void freeClient(redisClient *c) {
|
||||
c->querybuf = NULL;
|
||||
|
||||
/* Deallocate structures used to block on blocking ops. */
|
||||
if (c->flags & REDIS_BLOCKED) unblockClient(c);
|
||||
if (c->flags & REDIS_BLOCKED)
|
||||
unblockClientWaitingData(c);
|
||||
dictRelease(c->bpop.keys);
|
||||
|
||||
/* UNWATCH all the keys */
|
||||
@@ -730,10 +695,8 @@ void freeClient(redisClient *c) {
|
||||
/* Master/slave cleanup Case 1:
|
||||
* we lost the connection with a slave. */
|
||||
if (c->flags & REDIS_SLAVE) {
|
||||
if (c->replstate == REDIS_REPL_SEND_BULK) {
|
||||
if (c->repldbfd != -1) close(c->repldbfd);
|
||||
if (c->replpreamble) sdsfree(c->replpreamble);
|
||||
}
|
||||
if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1)
|
||||
close(c->repldbfd);
|
||||
list *l = (c->flags & REDIS_MONITOR) ? server.monitors : server.slaves;
|
||||
ln = listSearchKey(l,c);
|
||||
redisAssert(ln != NULL);
|
||||
@@ -811,11 +774,10 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
} else {
|
||||
o = listNodeValue(listFirst(c->reply));
|
||||
objlen = sdslen(o->ptr);
|
||||
objmem = getStringObjectSdsUsedMemory(o);
|
||||
objmem = zmalloc_size_sds(o->ptr);
|
||||
|
||||
if (objlen == 0) {
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
c->reply_bytes -= objmem;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -871,16 +833,12 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
|
||||
/* resetClient prepare the client to process the next command */
|
||||
void resetClient(redisClient *c) {
|
||||
redisCommandProc *prevcmd = c->cmd ? c->cmd->proc : NULL;
|
||||
|
||||
freeClientArgv(c);
|
||||
c->reqtype = 0;
|
||||
c->multibulklen = 0;
|
||||
c->bulklen = -1;
|
||||
/* We clear the ASKING flag as well if we are not inside a MULTI, and
|
||||
* if what we just executed is not the ASKING command itself. */
|
||||
if (!(c->flags & REDIS_MULTI) && prevcmd != askingCommand)
|
||||
c->flags &= (~REDIS_ASKING);
|
||||
/* We clear the ASKING flag as well if we are not inside a MULTI. */
|
||||
if (!(c->flags & REDIS_MULTI)) c->flags &= (~REDIS_ASKING);
|
||||
}
|
||||
|
||||
int processInlineBuffer(redisClient *c) {
|
||||
@@ -1094,9 +1052,6 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
void processInputBuffer(redisClient *c) {
|
||||
/* Keep processing while there is something in the input buffer */
|
||||
while(sdslen(c->querybuf)) {
|
||||
/* Return if clients are paused. */
|
||||
if (!(c->flags & REDIS_SLAVE) && clientsArePaused()) return;
|
||||
|
||||
/* Immediately abort if the client is in the middle of something. */
|
||||
if (c->flags & REDIS_BLOCKED) return;
|
||||
|
||||
@@ -1274,7 +1229,6 @@ sds getClientInfoString(redisClient *client) {
|
||||
if (client->flags & REDIS_UNBLOCKED) *p++ = 'u';
|
||||
if (client->flags & REDIS_CLOSE_ASAP) *p++ = 'A';
|
||||
if (client->flags & REDIS_UNIX_SOCKET) *p++ = 'U';
|
||||
if (client->flags & REDIS_READONLY) *p++ = 'r';
|
||||
if (p == flags) *p++ = 'N';
|
||||
*p++ = '\0';
|
||||
|
||||
@@ -1384,13 +1338,6 @@ void clientCommand(redisClient *c) {
|
||||
addReplyBulk(c,c->name);
|
||||
else
|
||||
addReply(c,shared.nullbulk);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"pause") && c->argc == 3) {
|
||||
long long duration;
|
||||
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[2],&duration,UNIT_MILLISECONDS)
|
||||
!= REDIS_OK) return;
|
||||
pauseClients(duration);
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyError(c, "Syntax error, try CLIENT (LIST | KILL ip:port | GETNAME | SETNAME connection-name)");
|
||||
}
|
||||
@@ -1573,49 +1520,3 @@ void flushSlavesOutputBuffers(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Pause clients up to the specified unixtime (in ms). While clients
|
||||
* are paused no command is processed from clients, so the data set can't
|
||||
* change during that time.
|
||||
*
|
||||
* However while this function pauses normal and Pub/Sub clients, slaves are
|
||||
* still served, so this function can be used on server upgrades where it is
|
||||
* required that slaves process the latest bytes from the replication stream
|
||||
* before being turned to masters.
|
||||
*
|
||||
* This function is also internally used by Redis Cluster for the manual
|
||||
* failover procedure implemented by CLUSTER FAILOVER.
|
||||
*
|
||||
* The function always succeed, even if there is already a pause in progress.
|
||||
* In such a case, the pause is extended if the duration is more than the
|
||||
* time left for the previous duration. However if the duration is smaller
|
||||
* than the time left for the previous pause, no change is made to the
|
||||
* left duration. */
|
||||
void pauseClients(mstime_t end) {
|
||||
if (!server.clients_paused || end > server.clients_pause_end_time)
|
||||
server.clients_pause_end_time = end;
|
||||
server.clients_paused = 1;
|
||||
}
|
||||
|
||||
/* Return non-zero if clients are currently paused. As a side effect the
|
||||
* function checks if the pause time was reached and clear it. */
|
||||
int clientsArePaused(void) {
|
||||
if (server.clients_paused && server.clients_pause_end_time < server.mstime) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
redisClient *c;
|
||||
|
||||
server.clients_paused = 0;
|
||||
|
||||
/* Put all the clients in the unblocked clients queue in order to
|
||||
* force the re-processing of the input buffer if any. */
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
c = listNodeValue(ln);
|
||||
|
||||
if (c->flags & REDIS_SLAVE) continue;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
}
|
||||
return server.clients_paused;
|
||||
}
|
||||
|
||||
+10
-12
@@ -67,19 +67,17 @@ int keyspaceEventsStringToFlags(char *classes) {
|
||||
sds keyspaceEventsFlagsToString(int flags) {
|
||||
sds res;
|
||||
|
||||
if ((flags & REDIS_NOTIFY_ALL) == REDIS_NOTIFY_ALL)
|
||||
return sdsnew("A");
|
||||
res = sdsempty();
|
||||
if ((flags & REDIS_NOTIFY_ALL) == REDIS_NOTIFY_ALL) {
|
||||
res = sdscatlen(res,"A",1);
|
||||
} else {
|
||||
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_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;
|
||||
|
||||
+74
-258
@@ -29,7 +29,6 @@
|
||||
*/
|
||||
|
||||
#include "redis.h"
|
||||
#include "lzf.h" /* LZF compression library */
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
|
||||
@@ -41,49 +40,12 @@ robj *createObject(int type, void *ptr) {
|
||||
o->refcount = 1;
|
||||
|
||||
/* Set the LRU to the current lruclock (minutes resolution). */
|
||||
o->lru = LRU_CLOCK();
|
||||
o->lru = server.lruclock;
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Create a string object with encoding REDIS_ENCODING_RAW, that is a plain
|
||||
* string object where o->ptr points to a proper sds string. */
|
||||
robj *createRawStringObject(char *ptr, size_t len) {
|
||||
return createObject(REDIS_STRING,sdsnewlen(ptr,len));
|
||||
}
|
||||
|
||||
/* Create a string object with encoding REDIS_ENCODING_EMBSTR, that is
|
||||
* an object where the sds string is actually an unmodifiable string
|
||||
* allocated in the same chunk as the object itself. */
|
||||
robj *createEmbeddedStringObject(char *ptr, size_t len) {
|
||||
robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1);
|
||||
struct sdshdr *sh = (void*)(o+1);
|
||||
|
||||
o->type = REDIS_STRING;
|
||||
o->encoding = REDIS_ENCODING_EMBSTR;
|
||||
o->ptr = sh+1;
|
||||
o->refcount = 1;
|
||||
o->lru = LRU_CLOCK();
|
||||
|
||||
sh->len = len;
|
||||
sh->free = 0;
|
||||
if (ptr) {
|
||||
memcpy(sh->buf,ptr,len);
|
||||
sh->buf[len] = '\0';
|
||||
} else {
|
||||
memset(sh->buf,0,len+1);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Create a string object with EMBSTR encoding if it is smaller than
|
||||
* REIDS_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
|
||||
* used. */
|
||||
#define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 32
|
||||
robj *createStringObject(char *ptr, size_t len) {
|
||||
if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT)
|
||||
return createEmbeddedStringObject(ptr,len);
|
||||
else
|
||||
return createRawStringObject(ptr,len);
|
||||
return createObject(REDIS_STRING,sdsnewlen(ptr,len));
|
||||
}
|
||||
|
||||
robj *createStringObjectFromLongLong(long long value) {
|
||||
@@ -127,37 +89,9 @@ robj *createStringObjectFromLongDouble(long double value) {
|
||||
return createStringObject(buf,len);
|
||||
}
|
||||
|
||||
/* Duplicate a string object, with the guarantee that the returned object
|
||||
* has the same encoding as the original one.
|
||||
*
|
||||
* This function also guarantees that duplicating a small integere object
|
||||
* (or a string object that contains a representation of a small integer)
|
||||
* will always result in a fresh object that is unshared (refcount == 1).
|
||||
*
|
||||
* The resulting object always has refcount set to 1. */
|
||||
robj *dupStringObject(robj *o) {
|
||||
robj *d;
|
||||
|
||||
redisAssert(o->type == REDIS_STRING);
|
||||
|
||||
switch(o->encoding) {
|
||||
case REDIS_ENCODING_RAW:
|
||||
return createRawStringObject(o->ptr,sdslen(o->ptr));
|
||||
case REDIS_ENCODING_EMBSTR:
|
||||
return createEmbeddedStringObject(o->ptr,sdslen(o->ptr));
|
||||
case REDIS_ENCODING_INT:
|
||||
d = createObject(REDIS_STRING, NULL);
|
||||
d->encoding = REDIS_ENCODING_INT;
|
||||
d->ptr = o->ptr;
|
||||
return d;
|
||||
case REDIS_ENCODING_LZF:
|
||||
d = createObject(REDIS_STRING,sdsdup(o->ptr));
|
||||
d->encoding = REDIS_ENCODING_LZF;
|
||||
return d;
|
||||
default:
|
||||
redisPanic("Wrong encoding.");
|
||||
break;
|
||||
}
|
||||
redisAssertWithInfo(NULL,o,o->encoding == REDIS_ENCODING_RAW);
|
||||
return createStringObject(o->ptr,sdslen(o->ptr));
|
||||
}
|
||||
|
||||
robj *createListObject(void) {
|
||||
@@ -215,8 +149,7 @@ robj *createZsetZiplistObject(void) {
|
||||
}
|
||||
|
||||
void freeStringObject(robj *o) {
|
||||
if (o->encoding == REDIS_ENCODING_RAW ||
|
||||
o->encoding == REDIS_ENCODING_LZF) {
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
sdsfree(o->ptr);
|
||||
}
|
||||
}
|
||||
@@ -333,138 +266,71 @@ int checkType(redisClient *c, robj *o, int type) {
|
||||
|
||||
int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
if (intEncodedObject(o)) {
|
||||
if (o->encoding == REDIS_ENCODING_INT) {
|
||||
if (llval) *llval = (long) o->ptr;
|
||||
return REDIS_OK;
|
||||
} else if (sdsEncodedObject(o)) {
|
||||
return string2ll(o->ptr,sdslen(o->ptr),llval) ? REDIS_OK : REDIS_ERR;
|
||||
} else if (lzfEncodedObject(o)) {
|
||||
int retval;
|
||||
robj *decoded = getDecodedObject(o);
|
||||
retval = string2ll(o->ptr,sdslen(o->ptr),llval) ? REDIS_OK : REDIS_ERR;
|
||||
decrRefCount(decoded);
|
||||
return retval;
|
||||
} else {
|
||||
redisPanic("Unknown encoding.");
|
||||
return string2ll(o->ptr,sdslen(o->ptr),llval) ? REDIS_OK : REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to encode a string object in order to save space. */
|
||||
#define LZF_COMPR_STATIC_BUF (1024*32)
|
||||
/* Try to encode a string object in order to save space */
|
||||
robj *tryObjectEncoding(robj *o) {
|
||||
long value;
|
||||
sds s = o->ptr;
|
||||
size_t len;
|
||||
|
||||
/* Make sure this is a string object, the only type we encode
|
||||
* in this function. Other types use encoded memory efficient
|
||||
* representations but are handled by the commands implementing
|
||||
* the type. */
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
|
||||
/* We try some specialized encoding only for objects that are
|
||||
* RAW or EMBSTR encoded, in other words objects that are still
|
||||
* in represented by an actually array of chars. */
|
||||
if (!sdsEncodedObject(o)) return o;
|
||||
if (o->encoding != REDIS_ENCODING_RAW)
|
||||
return o; /* Already encoded */
|
||||
|
||||
/* It's not safe to encode shared objects: shared objects can be shared
|
||||
* everywhere in the "object space" of Redis and may end in places where
|
||||
* they are not handled. We handle them only as values in the keyspace. */
|
||||
* everywhere in the "object space" of Redis. Encoded objects can only
|
||||
* appear as "values" (and not, for instance, as keys) */
|
||||
if (o->refcount > 1) return o;
|
||||
|
||||
/* Check if we can represent this string as a long integer.
|
||||
* Note that we are sure that a string larger than 21 chars is not
|
||||
* representable as a 32 nor 64 bit integer. */
|
||||
/* Currently we try to encode only strings */
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
|
||||
/* Check if we can represent this string as a long integer */
|
||||
len = sdslen(s);
|
||||
if (len <= 21 && string2l(s,len,&value)) {
|
||||
/* This object is encodable as a long. Try to use a shared object.
|
||||
* Note that we avoid using shared integers when maxmemory is used
|
||||
* because every object needs to have a private LRU field for the LRU
|
||||
* algorithm to work well. */
|
||||
if (server.maxmemory == 0 &&
|
||||
value >= 0 &&
|
||||
value < REDIS_SHARED_INTEGERS)
|
||||
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)
|
||||
{
|
||||
decrRefCount(o);
|
||||
incrRefCount(shared.integers[value]);
|
||||
return shared.integers[value];
|
||||
} else {
|
||||
if (o->encoding == REDIS_ENCODING_RAW) sdsfree(o->ptr);
|
||||
o->encoding = REDIS_ENCODING_INT;
|
||||
o->ptr = (void*) value;
|
||||
return o;
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
/* Return the original object. */
|
||||
return o;
|
||||
}
|
||||
|
||||
/* If the string is small and is still RAW encoded,
|
||||
* try the EMBSTR encoding which is more efficient.
|
||||
* In this representation the object and the SDS string are allocated
|
||||
* in the same chunk of memory to save space and cache misses. */
|
||||
if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT) {
|
||||
robj *emb;
|
||||
|
||||
if (o->encoding == REDIS_ENCODING_EMBSTR) return o;
|
||||
emb = createEmbeddedStringObject(s,sdslen(s));
|
||||
/* Ok, this object can be encoded...
|
||||
*
|
||||
* Can I use a shared object? Only if the object is inside a given range
|
||||
*
|
||||
* Note that we also avoid using shared integers when maxmemory is used
|
||||
* because every object needs to have a private LRU field for the LRU
|
||||
* algorithm to work well. */
|
||||
if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS) {
|
||||
decrRefCount(o);
|
||||
return emb;
|
||||
incrRefCount(shared.integers[value]);
|
||||
return shared.integers[value];
|
||||
} else {
|
||||
o->encoding = REDIS_ENCODING_INT;
|
||||
sdsfree(o->ptr);
|
||||
o->ptr = (void*) value;
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Try LZF compression for objects up to server.mem_compression_max_size
|
||||
* and greater than REDIS_ENCODING_EMBSTR_SIZE_LIMIT. */
|
||||
if (server.mem_compression && len <= server.mem_compression_max_size) {
|
||||
/* Allocate four more bytes in our buffer since we need to store
|
||||
* the size of the compressed string as header. */
|
||||
unsigned char compr_static[LZF_COMPR_STATIC_BUF];
|
||||
unsigned char *compr = compr_static;
|
||||
size_t comprlen, outlen;
|
||||
|
||||
/* Save want to save at least 25% of memory for this to make sense. */
|
||||
outlen = len-4-(len/4);
|
||||
|
||||
/* Use an heap allocated buffer if the output is too big for our
|
||||
* static buffer. We use the trick to directly allocating an empty
|
||||
* SDS string so we can use it directly to create the object later. */
|
||||
if (outlen+4 > LZF_COMPR_STATIC_BUF)
|
||||
compr = (unsigned char*) sdsnewlen(NULL,outlen+4);
|
||||
comprlen = lzf_compress(s,len,compr+4,outlen);
|
||||
if (comprlen != 0) {
|
||||
/* Object successfully compressed within the required space. */
|
||||
compr[0] = len & 0xff;
|
||||
compr[1] = (len >> 8) & 0xff;
|
||||
compr[2] = (len >> 16) & 0xff;
|
||||
compr[3] = (len >> 24) & 0xff;
|
||||
if (o->encoding == REDIS_ENCODING_RAW) sdsfree(o->ptr);
|
||||
o->encoding = REDIS_ENCODING_LZF;
|
||||
if (compr == compr_static) {
|
||||
/* When compressing to the static buffer we have to
|
||||
* generate the SDS string here. */
|
||||
o->ptr = sdsnewlen(compr,comprlen+4);
|
||||
} else {
|
||||
/* Already an SDS, use it. */
|
||||
o->ptr = compr;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
if (compr != compr_static) sdsfree((char*)compr);
|
||||
}
|
||||
|
||||
/* 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 only for relatively large strings as this branch
|
||||
* is only entered if the length of the string is greater than
|
||||
* REDIS_ENCODING_EMBSTR_SIZE_LIMIT. */
|
||||
if (o->encoding == REDIS_ENCODING_RAW &&
|
||||
sdsavail(s) > len/10)
|
||||
{
|
||||
o->ptr = sdsRemoveFreeSpace(o->ptr);
|
||||
}
|
||||
|
||||
/* Return the original object. */
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Get a decoded version of an encoded object (returned as a new object).
|
||||
@@ -472,24 +338,16 @@ robj *tryObjectEncoding(robj *o) {
|
||||
robj *getDecodedObject(robj *o) {
|
||||
robj *dec;
|
||||
|
||||
if (sdsEncodedObject(o)) {
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
incrRefCount(o);
|
||||
return o;
|
||||
}
|
||||
if (o->type == REDIS_STRING && intEncodedObject(o)) {
|
||||
if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_INT) {
|
||||
char buf[32];
|
||||
|
||||
ll2string(buf,32,(long)o->ptr);
|
||||
dec = createStringObject(buf,strlen(buf));
|
||||
return dec;
|
||||
} else if (o->type == REDIS_STRING && lzfEncodedObject(o)) {
|
||||
int origlen = stringObjectLen(o);
|
||||
sds orig = sdsnewlen(NULL,origlen);
|
||||
unsigned char *p = o->ptr;
|
||||
|
||||
if (lzf_decompress(p+4,sdslen(o->ptr)-4,orig,origlen) == 0)
|
||||
redisPanic("LZF error during object decoding.");
|
||||
return createObject(REDIS_STRING,orig);
|
||||
} else {
|
||||
redisPanic("Unknown encoding type");
|
||||
}
|
||||
@@ -510,28 +368,21 @@ 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;
|
||||
size_t alen, blen, minlen;
|
||||
int decr = 0;
|
||||
|
||||
if (a == b) return 0;
|
||||
|
||||
if (lzfEncodedObject(a) || lzfEncodedObject(b)) {
|
||||
a = getDecodedObject(a);
|
||||
b = getDecodedObject(b);
|
||||
decr = 1;
|
||||
}
|
||||
if (sdsEncodedObject(a)) {
|
||||
astr = a->ptr;
|
||||
alen = sdslen(astr);
|
||||
} else {
|
||||
if (a->encoding != REDIS_ENCODING_RAW) {
|
||||
alen = ll2string(bufa,sizeof(bufa),(long) a->ptr);
|
||||
astr = bufa;
|
||||
}
|
||||
if (sdsEncodedObject(b)) {
|
||||
bstr = b->ptr;
|
||||
blen = sdslen(bstr);
|
||||
} else {
|
||||
astr = a->ptr;
|
||||
alen = sdslen(astr);
|
||||
}
|
||||
if (b->encoding != REDIS_ENCODING_RAW) {
|
||||
blen = ll2string(bufb,sizeof(bufb),(long) b->ptr);
|
||||
bstr = bufb;
|
||||
} else {
|
||||
bstr = b->ptr;
|
||||
blen = sdslen(bstr);
|
||||
}
|
||||
if (flags & REDIS_COMPARE_COLL) {
|
||||
return strcoll(astr,bstr);
|
||||
@@ -543,10 +394,6 @@ int compareStringObjectsWithFlags(robj *a, robj *b, int flags) {
|
||||
if (cmp == 0) return alen-blen;
|
||||
return cmp;
|
||||
}
|
||||
if (decr) {
|
||||
decrRefCount(a);
|
||||
decrRefCount(b);
|
||||
}
|
||||
}
|
||||
|
||||
/* Wrapper for compareStringObjectsWithFlags() using binary comparison. */
|
||||
@@ -564,30 +411,20 @@ int collateStringObjects(robj *a, robj *b) {
|
||||
* this function is faster then checking for (compareStringObject(a,b) == 0)
|
||||
* because it can perform some more optimization. */
|
||||
int equalStringObjects(robj *a, robj *b) {
|
||||
if (intEncodedObject(a) && intEncodedObject(b)) {
|
||||
/* If both strings are integer encoded just check if the stored
|
||||
* long is the same. */
|
||||
if (a->encoding != REDIS_ENCODING_RAW && b->encoding != REDIS_ENCODING_RAW){
|
||||
return a->ptr == b->ptr;
|
||||
} else {
|
||||
return compareStringObjects(a,b) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the original (uncompressed) size of an LZF encoded object.
|
||||
* Only called by stringObjectLen() that should be the main interface. */
|
||||
size_t stringObjectUncompressedLen(robj *o) {
|
||||
unsigned char *p = o->ptr;
|
||||
return p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
|
||||
}
|
||||
|
||||
size_t stringObjectLen(robj *o) {
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
if (sdsEncodedObject(o)) {
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
return sdslen(o->ptr);
|
||||
} else if (o->encoding == REDIS_ENCODING_LZF) {
|
||||
return stringObjectUncompressedLen(o);
|
||||
} else {
|
||||
char buf[32];
|
||||
|
||||
return ll2string(buf,32,(long)o->ptr);
|
||||
}
|
||||
}
|
||||
@@ -600,7 +437,7 @@ int getDoubleFromObject(robj *o, double *target) {
|
||||
value = 0;
|
||||
} else {
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
if (sdsEncodedObject(o)) {
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
errno = 0;
|
||||
value = strtod(o->ptr, &eptr);
|
||||
if (isspace(((char*)o->ptr)[0]) ||
|
||||
@@ -610,14 +447,8 @@ int getDoubleFromObject(robj *o, double *target) {
|
||||
errno == EINVAL ||
|
||||
isnan(value))
|
||||
return REDIS_ERR;
|
||||
} else if (intEncodedObject(o)) {
|
||||
} else if (o->encoding == REDIS_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
} else if (lzfEncodedObject(o)) {
|
||||
int retval;
|
||||
o = getDecodedObject(o);
|
||||
retval = getDoubleFromObject(o,target);
|
||||
decrRefCount(o);
|
||||
return retval;
|
||||
} else {
|
||||
redisPanic("Unknown string encoding");
|
||||
}
|
||||
@@ -648,20 +479,14 @@ int getLongDoubleFromObject(robj *o, long double *target) {
|
||||
value = 0;
|
||||
} else {
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
if (sdsEncodedObject(o)) {
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
errno = 0;
|
||||
value = strtold(o->ptr, &eptr);
|
||||
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
|
||||
errno == ERANGE || isnan(value))
|
||||
return REDIS_ERR;
|
||||
} else if (intEncodedObject(o)) {
|
||||
} else if (o->encoding == REDIS_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
} else if (lzfEncodedObject(o)) {
|
||||
int retval;
|
||||
o = getDecodedObject(o);
|
||||
retval = getLongDoubleFromObject(o,target);
|
||||
decrRefCount(o);
|
||||
return retval;
|
||||
} else {
|
||||
redisPanic("Unknown string encoding");
|
||||
}
|
||||
@@ -692,20 +517,14 @@ int getLongLongFromObject(robj *o, long long *target) {
|
||||
value = 0;
|
||||
} else {
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
if (sdsEncodedObject(o)) {
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
errno = 0;
|
||||
value = strtoll(o->ptr, &eptr, 10);
|
||||
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
|
||||
errno == ERANGE)
|
||||
return REDIS_ERR;
|
||||
} else if (intEncodedObject(o)) {
|
||||
} else if (o->encoding == REDIS_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
} else if (lzfEncodedObject(o)) {
|
||||
int retval;
|
||||
o = getDecodedObject(o);
|
||||
retval = getLongLongFromObject(o,target);
|
||||
decrRefCount(o);
|
||||
return retval;
|
||||
} else {
|
||||
redisPanic("Unknown string encoding");
|
||||
}
|
||||
@@ -753,25 +572,22 @@ char *strEncoding(int encoding) {
|
||||
case REDIS_ENCODING_ZIPLIST: return "ziplist";
|
||||
case REDIS_ENCODING_INTSET: return "intset";
|
||||
case REDIS_ENCODING_SKIPLIST: return "skiplist";
|
||||
case REDIS_ENCODING_EMBSTR: return "embstr";
|
||||
case REDIS_ENCODING_LZF: return "lzf";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/* Given an object returns the min number of milliseconds the object was never
|
||||
/* Given an object returns the min number of seconds the object was never
|
||||
* requested, using an approximated LRU algorithm. */
|
||||
unsigned long long estimateObjectIdleTime(robj *o) {
|
||||
unsigned long long lruclock = LRU_CLOCK();
|
||||
if (lruclock >= o->lru) {
|
||||
return (lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
|
||||
unsigned long estimateObjectIdleTime(robj *o) {
|
||||
if (server.lruclock >= o->lru) {
|
||||
return (server.lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
|
||||
} else {
|
||||
return (lruclock + (REDIS_LRU_CLOCK_MAX - o->lru)) *
|
||||
return ((REDIS_LRU_CLOCK_MAX - o->lru) + server.lruclock) *
|
||||
REDIS_LRU_CLOCK_RESOLUTION;
|
||||
}
|
||||
}
|
||||
|
||||
/* This is a helper function for the OBJECT command. We need to lookup keys
|
||||
/* This is a helper function for the DEBUG command. We need to lookup keys
|
||||
* without any modification of LRU or other parameters. */
|
||||
robj *objectCommandLookup(redisClient *c, robj *key) {
|
||||
dictEntry *de;
|
||||
@@ -803,7 +619,7 @@ void objectCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
|
||||
addReplyLongLong(c,estimateObjectIdleTime(o));
|
||||
} else {
|
||||
addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime)");
|
||||
}
|
||||
|
||||
+4
-7
@@ -50,7 +50,7 @@ int listMatchPubsubPattern(void *a, void *b) {
|
||||
/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was already subscribed to that channel. */
|
||||
int pubsubSubscribeChannel(redisClient *c, robj *channel) {
|
||||
dictEntry *de;
|
||||
struct dictEntry *de;
|
||||
list *clients = NULL;
|
||||
int retval = 0;
|
||||
|
||||
@@ -80,7 +80,7 @@ int pubsubSubscribeChannel(redisClient *c, robj *channel) {
|
||||
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was not subscribed to the specified channel. */
|
||||
int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) {
|
||||
dictEntry *de;
|
||||
struct dictEntry *de;
|
||||
list *clients;
|
||||
listNode *ln;
|
||||
int retval = 0;
|
||||
@@ -218,7 +218,7 @@ int pubsubUnsubscribeAllPatterns(redisClient *c, int notify) {
|
||||
/* Publish a message */
|
||||
int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
int receivers = 0;
|
||||
dictEntry *de;
|
||||
struct dictEntry *de;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
@@ -306,10 +306,7 @@ void punsubscribeCommand(redisClient *c) {
|
||||
|
||||
void publishCommand(redisClient *c) {
|
||||
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
|
||||
if (server.cluster_enabled)
|
||||
clusterPropagatePublish(c->argv[1],c->argv[2]);
|
||||
else
|
||||
forceCommandPropagation(c,REDIS_PROPAGATE_REPL);
|
||||
forceCommandPropagation(c,REDIS_PROPAGATE_REPL);
|
||||
addReplyLongLong(c,receivers);
|
||||
}
|
||||
|
||||
|
||||
@@ -209,41 +209,11 @@ int rdbTryIntegerEncoding(char *s, size_t len, unsigned char *enc) {
|
||||
return rdbEncodeInteger(value,enc);
|
||||
}
|
||||
|
||||
/* Save an already compressed object in LZF encoding.
|
||||
*
|
||||
* On success the length of the strored object is returned, otherwise
|
||||
* 0 is returned. */
|
||||
int rdbSaveLzfStringObject(rio *rdb, unsigned char *out, size_t len, size_t comprlen) {
|
||||
int rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
|
||||
size_t comprlen, outlen;
|
||||
unsigned char byte;
|
||||
int n, nwritten = 0;
|
||||
|
||||
/* Data compressed! Let's save it on disk */
|
||||
byte = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_LZF;
|
||||
if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveLen(rdb,comprlen)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveLen(rdb,len)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
if ((n = rdbWriteRaw(rdb,out,comprlen)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
return nwritten;
|
||||
|
||||
writeerr:
|
||||
zfree(out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Try to compress the string at 's' for 'len' bytes using LZF.
|
||||
* If successful save the object with LZF encoding, otherwise
|
||||
* returns 0 if the string can't be compressed, or -1 if the
|
||||
* compressed string can't be saved.
|
||||
*
|
||||
* On success the number of bytes used is returned. */
|
||||
int rdbTrySaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
|
||||
size_t comprlen, outlen;
|
||||
void *out;
|
||||
int retval;
|
||||
|
||||
/* We require at least four bytes compression for this to be worth it */
|
||||
if (len <= 4) return 0;
|
||||
@@ -254,9 +224,26 @@ int rdbTrySaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
|
||||
zfree(out);
|
||||
return 0;
|
||||
}
|
||||
retval = rdbSaveLzfStringObject(rdb,out,len,comprlen);
|
||||
/* Data compressed! Let's save it on disk */
|
||||
byte = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_LZF;
|
||||
if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbSaveLen(rdb,comprlen)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbSaveLen(rdb,len)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbWriteRaw(rdb,out,comprlen)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
zfree(out);
|
||||
return retval;
|
||||
return nwritten;
|
||||
|
||||
writeerr:
|
||||
zfree(out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
robj *rdbLoadLzfStringObject(rio *rdb) {
|
||||
@@ -296,7 +283,7 @@ int rdbSaveRawString(rio *rdb, unsigned char *s, size_t len) {
|
||||
/* Try LZF compression - under 20 bytes it's unable to compress even
|
||||
* aaaaaaaaaaaaaaaaaa so skip it */
|
||||
if (server.rdb_compression && len > 20) {
|
||||
n = rdbTrySaveLzfStringObject(rdb,s,len);
|
||||
n = rdbSaveLzfStringObject(rdb,s,len);
|
||||
if (n == -1) return -1;
|
||||
if (n > 0) return n;
|
||||
/* Return value of 0 means data can't be compressed, save the old way */
|
||||
@@ -337,13 +324,8 @@ int rdbSaveStringObject(rio *rdb, robj *obj) {
|
||||
* object is already integer encoded. */
|
||||
if (obj->encoding == REDIS_ENCODING_INT) {
|
||||
return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);
|
||||
} else if (obj->encoding == REDIS_ENCODING_LZF) {
|
||||
/* Data is already compressed, save it with LZF encoding. */
|
||||
int len = stringObjectLen(obj);
|
||||
unsigned char *p = obj->ptr;
|
||||
return rdbSaveLzfStringObject(rdb,p+4,len,sdslen(obj->ptr)-4);
|
||||
} else {
|
||||
redisAssertWithInfo(NULL,obj,sdsEncodedObject(obj));
|
||||
redisAssertWithInfo(NULL,obj,obj->encoding == REDIS_ENCODING_RAW);
|
||||
return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr));
|
||||
}
|
||||
}
|
||||
@@ -708,9 +690,9 @@ int rdbSave(char *filename) {
|
||||
rioWrite(&rdb,&cksum,8);
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
if (fflush(fp) == EOF) goto werr;
|
||||
if (fsync(fileno(fp)) == -1) goto werr;
|
||||
if (fclose(fp) == EOF) goto werr;
|
||||
fflush(fp);
|
||||
fsync(fileno(fp));
|
||||
fclose(fp);
|
||||
|
||||
/* Use RENAME to make sure the DB file is changed atomically only
|
||||
* if the generate DB file is ok. */
|
||||
@@ -814,7 +796,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
/* If we are using a ziplist and the value is too big, convert
|
||||
* the object to a real list. */
|
||||
if (o->encoding == REDIS_ENCODING_ZIPLIST &&
|
||||
sdsEncodedObject(ele) &&
|
||||
ele->encoding == REDIS_ENCODING_RAW &&
|
||||
sdslen(ele->ptr) > server.list_max_ziplist_value)
|
||||
listTypeConvert(o,REDIS_ENCODING_LINKEDLIST);
|
||||
|
||||
@@ -860,7 +842,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
}
|
||||
|
||||
/* This will also be called when the set was just converted
|
||||
* to a regular hash table encoded set */
|
||||
* to regular hash table encoded set */
|
||||
if (o->encoding == REDIS_ENCODING_HT) {
|
||||
dictAdd((dict*)o->ptr,ele,NULL);
|
||||
} else {
|
||||
@@ -888,8 +870,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
if (rdbLoadDoubleValue(rdb,&score) == -1) return NULL;
|
||||
|
||||
/* Don't care about integer-encoded strings. */
|
||||
if (sdsEncodedObject(ele) && sdslen(ele->ptr) > maxelelen)
|
||||
maxelelen = sdslen(ele->ptr);
|
||||
if (ele->encoding == REDIS_ENCODING_RAW &&
|
||||
sdslen(ele->ptr) > maxelelen)
|
||||
maxelelen = sdslen(ele->ptr);
|
||||
|
||||
znode = zslInsert(zs->zsl,score,ele);
|
||||
dictAdd(zs->dict,ele,&znode->score);
|
||||
@@ -921,10 +904,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
/* Load raw strings */
|
||||
field = rdbLoadStringObject(rdb);
|
||||
if (field == NULL) return NULL;
|
||||
redisAssert(sdsEncodedObject(field));
|
||||
redisAssert(field->encoding == REDIS_ENCODING_RAW);
|
||||
value = rdbLoadStringObject(rdb);
|
||||
if (value == NULL) return NULL;
|
||||
redisAssert(sdsEncodedObject(value));
|
||||
redisAssert(field->encoding == REDIS_ENCODING_RAW);
|
||||
|
||||
/* Add pair to ziplist */
|
||||
o->ptr = ziplistPush(o->ptr, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL);
|
||||
@@ -1083,10 +1066,6 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t 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)
|
||||
{
|
||||
/* The DB can take some non trivial amount of time to load. Update
|
||||
* our cached time since it is used to create and update the last
|
||||
* interaction time with clients and for other important things. */
|
||||
updateCachedTime();
|
||||
if (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER)
|
||||
replicationSendNewlineToMaster();
|
||||
loadingProgress(r->processed_bytes);
|
||||
|
||||
+91
-454
@@ -58,7 +58,7 @@
|
||||
#define OUTPUT_RAW 1
|
||||
#define OUTPUT_CSV 2
|
||||
#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||
#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
|
||||
#define REDIS_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
|
||||
|
||||
static redisContext *context;
|
||||
static struct config {
|
||||
@@ -81,10 +81,6 @@ static struct config {
|
||||
int pipe_timeout;
|
||||
int getrdb_mode;
|
||||
int stat_mode;
|
||||
int scan_mode;
|
||||
int intrinsic_latency_mode;
|
||||
int intrinsic_latency_duration;
|
||||
char *pattern;
|
||||
char *rdb_filename;
|
||||
int bigkeys;
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
@@ -96,7 +92,6 @@ static struct config {
|
||||
} config;
|
||||
|
||||
static void usage();
|
||||
static void slaveMode(void);
|
||||
char *redisGitSHA1(void);
|
||||
char *redisGitDirty(void);
|
||||
|
||||
@@ -104,18 +99,14 @@ char *redisGitDirty(void);
|
||||
* Utility functions
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static long long ustime(void) {
|
||||
static long long mstime(void) {
|
||||
struct timeval tv;
|
||||
long long ust;
|
||||
long long mst;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
ust = ((long long)tv.tv_sec)*1000000;
|
||||
ust += tv.tv_usec;
|
||||
return ust;
|
||||
}
|
||||
|
||||
static long long mstime(void) {
|
||||
return ustime()/1000;
|
||||
mst = ((long long)tv.tv_sec)*1000;
|
||||
mst += tv.tv_usec/1000;
|
||||
return mst;
|
||||
}
|
||||
|
||||
static void cliRefreshPrompt(void) {
|
||||
@@ -555,7 +546,6 @@ static int cliReadReply(int output_raw_strings) {
|
||||
printf("-> Redirected to slot [%d] located at %s:%d\n",
|
||||
slot, config.hostip, config.hostport);
|
||||
config.cluster_reissue_command = 1;
|
||||
cliRefreshPrompt();
|
||||
}
|
||||
|
||||
if (output) {
|
||||
@@ -607,8 +597,6 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
|
||||
if (!strcasecmp(command,"subscribe") ||
|
||||
!strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
|
||||
if (!strcasecmp(command,"sync") ||
|
||||
!strcasecmp(command,"psync")) config.slave_mode = 1;
|
||||
|
||||
/* Setup argument length */
|
||||
argvlen = malloc(argc*sizeof(size_t));
|
||||
@@ -630,13 +618,6 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
}
|
||||
}
|
||||
|
||||
if (config.slave_mode) {
|
||||
printf("Entering slave output mode... (press Ctrl-C to quit)\n");
|
||||
slaveMode();
|
||||
config.slave_mode = 0;
|
||||
return REDIS_ERR; /* Error = slaveMode lost connection to master */
|
||||
}
|
||||
|
||||
if (cliReadReply(output_raw) != REDIS_OK) {
|
||||
free(argvlen);
|
||||
return REDIS_ERR;
|
||||
@@ -730,13 +711,6 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.slave_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--stat")) {
|
||||
config.stat_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--scan")) {
|
||||
config.scan_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--pattern") && !lastarg) {
|
||||
config.pattern = argv[++i];
|
||||
} else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
|
||||
config.intrinsic_latency_mode = 1;
|
||||
config.intrinsic_latency_duration = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--rdb") && !lastarg) {
|
||||
config.getrdb_mode = 1;
|
||||
config.rdb_filename = argv[++i];
|
||||
@@ -796,37 +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"
|
||||
" -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"
|
||||
" 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"
|
||||
" 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"
|
||||
" --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"
|
||||
" --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"
|
||||
" --scan List all keys using the SCAN command.\n"
|
||||
" --pattern <pat> Useful with --scan to specify a SCAN pattern.\n"
|
||||
" --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
|
||||
" The test will run for the specified amount of seconds.\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"
|
||||
" --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"
|
||||
@@ -834,14 +804,12 @@ static void usage() {
|
||||
" redis-cli -r 100 lpush mylist x\n"
|
||||
" redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
|
||||
" redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n"
|
||||
" redis-cli --scan --pattern '*:12345*'\n"
|
||||
"\n"
|
||||
" (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n"
|
||||
"\n"
|
||||
"When no command is given, redis-cli starts in interactive mode.\n"
|
||||
"Type \"help\" in interactive mode for information on available commands.\n"
|
||||
"\n",
|
||||
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
|
||||
version, REDIS_DEFAULT_PIPE_TIMEOUT);
|
||||
sdsfree(version);
|
||||
exit(1);
|
||||
}
|
||||
@@ -866,7 +834,6 @@ static void repl() {
|
||||
sds *argv;
|
||||
|
||||
config.interactive = 1;
|
||||
linenoiseSetMultiLine(1);
|
||||
linenoiseSetCompletionCallback(completionCallback);
|
||||
|
||||
/* Only use history when stdin is a tty. */
|
||||
@@ -962,10 +929,6 @@ static int noninteractive(int argc, char **argv) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Eval mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static int evalMode(int argc, char **argv) {
|
||||
sds script = sdsempty();
|
||||
FILE *fp;
|
||||
@@ -1004,10 +967,6 @@ static int evalMode(int argc, char **argv) {
|
||||
return cliSendCommand(argc+3-got_comma, argv2, config.repeat);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Latency and latency history modes
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
#define LATENCY_SAMPLE_RATE 10 /* milliseconds. */
|
||||
#define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */
|
||||
static void latencyMode(void) {
|
||||
@@ -1052,10 +1011,6 @@ static void latencyMode(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Slave mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* Sends SYNC and reads the number of bytes in the payload. Used both by
|
||||
* slaveMode() and getRDB(). */
|
||||
unsigned long long sendSync(int fd) {
|
||||
@@ -1095,7 +1050,6 @@ static void slaveMode(void) {
|
||||
int fd = context->fd;
|
||||
unsigned long long payload = sendSync(fd);
|
||||
char buf[1024];
|
||||
int original_output = config.output;
|
||||
|
||||
fprintf(stderr,"SYNC with master, discarding %llu "
|
||||
"bytes of bulk transfer...\n", payload);
|
||||
@@ -1116,13 +1070,8 @@ static void slaveMode(void) {
|
||||
/* Now we can use hiredis to read the incoming protocol. */
|
||||
config.output = OUTPUT_CSV;
|
||||
while (cliReadReply(0) == REDIS_OK);
|
||||
config.output = original_output;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* RDB transfer mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* This function implements --rdb, so it uses the replication protocol in order
|
||||
* to fetch the RDB file from a remote server. */
|
||||
static void getRDB(void) {
|
||||
@@ -1168,10 +1117,6 @@ static void getRDB(void) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Bulk import (pipe) mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static void pipeMode(void) {
|
||||
int fd = context->fd;
|
||||
long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
|
||||
@@ -1323,287 +1268,92 @@ static void pipeMode(void) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Find big keys
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
#define TYPE_STRING 0
|
||||
#define TYPE_LIST 1
|
||||
#define TYPE_SET 2
|
||||
#define TYPE_HASH 3
|
||||
#define TYPE_ZSET 4
|
||||
#define TYPE_NONE 5
|
||||
|
||||
static redisReply *sendScan(unsigned long long *it) {
|
||||
redisReply *reply = redisCommand(context, "SCAN %llu", *it);
|
||||
|
||||
/* Handle any error conditions */
|
||||
if(reply == NULL) {
|
||||
fprintf(stderr, "\nI/O error\n");
|
||||
exit(1);
|
||||
} else if(reply->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "SCAN error: %s\n", reply->str);
|
||||
exit(1);
|
||||
} else if(reply->type != REDIS_REPLY_ARRAY) {
|
||||
fprintf(stderr, "Non ARRAY response from SCAN!\n");
|
||||
exit(1);
|
||||
} else if(reply->elements != 2) {
|
||||
fprintf(stderr, "Invalid element count from SCAN!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Validate our types are correct */
|
||||
assert(reply->element[0]->type == REDIS_REPLY_STRING);
|
||||
assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
|
||||
|
||||
/* Update iterator */
|
||||
*it = atoi(reply->element[0]->str);
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
static int getDbSize(void) {
|
||||
redisReply *reply;
|
||||
int size;
|
||||
|
||||
reply = redisCommand(context, "DBSIZE");
|
||||
|
||||
if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
|
||||
fprintf(stderr, "Couldn't determine DBSIZE!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Grab the number of keys and free our reply */
|
||||
size = reply->integer;
|
||||
freeReplyObject(reply);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static int toIntType(char *key, char *type) {
|
||||
if(!strcmp(type, "string")) {
|
||||
return TYPE_STRING;
|
||||
} else if(!strcmp(type, "list")) {
|
||||
return TYPE_LIST;
|
||||
} else if(!strcmp(type, "set")) {
|
||||
return TYPE_SET;
|
||||
} else if(!strcmp(type, "hash")) {
|
||||
return TYPE_HASH;
|
||||
} else if(!strcmp(type, "zset")) {
|
||||
return TYPE_ZSET;
|
||||
} else if(!strcmp(type, "none")) {
|
||||
return TYPE_NONE;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void getKeyTypes(redisReply *keys, int *types) {
|
||||
redisReply *reply;
|
||||
int i;
|
||||
|
||||
/* Pipeline TYPE commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
redisAppendCommand(context, "TYPE %s", keys->element[i]->str);
|
||||
}
|
||||
|
||||
/* Retrieve types */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
|
||||
fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n",
|
||||
keys->element[i]->str, context->err, context->errstr);
|
||||
exit(1);
|
||||
} else if(reply->type != REDIS_REPLY_STATUS) {
|
||||
fprintf(stderr, "Invalid reply type (%d) for TYPE on key '%s'!\n",
|
||||
reply->type, keys->element[i]->str);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
types[i] = toIntType(keys->element[i]->str, reply->str);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void getKeySizes(redisReply *keys, int *types,
|
||||
unsigned long long *sizes)
|
||||
{
|
||||
redisReply *reply;
|
||||
char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"};
|
||||
int i;
|
||||
|
||||
/* Pipeline size commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
/* Skip keys that were deleted */
|
||||
if(types[i]==TYPE_NONE)
|
||||
continue;
|
||||
|
||||
redisAppendCommand(context, "%s %s", sizecmds[types[i]],
|
||||
keys->element[i]->str);
|
||||
}
|
||||
|
||||
/* Retreive sizes */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
/* Skip keys that dissapeared between SCAN and TYPE */
|
||||
if(types[i] == TYPE_NONE) {
|
||||
sizes[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Retreive size */
|
||||
if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
|
||||
fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n",
|
||||
keys->element[i]->str, context->err, context->errstr);
|
||||
exit(1);
|
||||
} else if(reply->type != REDIS_REPLY_INTEGER) {
|
||||
/* Theoretically the key could have been removed and
|
||||
* added as a different type between TYPE and SIZE */
|
||||
fprintf(stderr,
|
||||
"Warning: %s on '%s' failed (may have changed type)\n",
|
||||
sizecmds[types[i]], keys->element[i]->str);
|
||||
sizes[i] = 0;
|
||||
} else {
|
||||
sizes[i] = reply->integer;
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void findBigKeys(void) {
|
||||
unsigned long long biggest[5] = {0}, counts[5] = {0}, totalsize[5] = {0};
|
||||
unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0;
|
||||
sds maxkeys[5] = {0};
|
||||
char *typename[] = {"string","list","set","hash","zset"};
|
||||
unsigned long long biggest[5] = {0,0,0,0,0};
|
||||
unsigned long long samples = 0;
|
||||
redisReply *reply1, *reply2, *reply3 = NULL;
|
||||
char *sizecmd, *typename[] = {"string","list","set","hash","zset"};
|
||||
char *typeunit[] = {"bytes","items","members","fields","members"};
|
||||
redisReply *reply, *keys;
|
||||
int type, *types=NULL, arrsize=0, i;
|
||||
double pct;
|
||||
int type;
|
||||
|
||||
/* Total keys pre scanning */
|
||||
total_keys = getDbSize();
|
||||
|
||||
/* Status message */
|
||||
printf("\n# Scanning the entire keyspace to find biggest keys as well as\n");
|
||||
printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n");
|
||||
printf("# per 100 SCAN commands (not usually needed).\n\n");
|
||||
|
||||
/* New up sds strings to keep track of overall biggest per type */
|
||||
for(i=0;i<TYPE_NONE; i++) {
|
||||
maxkeys[i] = sdsempty();
|
||||
if(!maxkeys[i]) {
|
||||
fprintf(stderr, "Failed to allocate memory for largest key names!");
|
||||
printf("\n# Press ctrl+c when you have had enough of it... :)\n");
|
||||
printf("# You can use -i 0.1 to sleep 0.1 sec every 100 sampled keys\n");
|
||||
printf("# in order to reduce server load (usually not needed).\n\n");
|
||||
while(1) {
|
||||
/* Sample with RANDOMKEY */
|
||||
reply1 = redisCommand(context,"RANDOMKEY");
|
||||
if (reply1 == NULL) {
|
||||
fprintf(stderr,"\nI/O error\n");
|
||||
exit(1);
|
||||
} else if (reply1->type == REDIS_REPLY_ERROR) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/* SCAN loop */
|
||||
do {
|
||||
/* Calculate approximate percentage completion */
|
||||
pct = 100 * (double)sampled/total_keys;
|
||||
/* Get the key type */
|
||||
reply2 = redisCommand(context,"TYPE %s",reply1->str);
|
||||
assert(reply2 && reply2->type == REDIS_REPLY_STATUS);
|
||||
samples++;
|
||||
|
||||
/* Grab some keys and point to the keys array */
|
||||
reply = sendScan(&it);
|
||||
keys = reply->element[1];
|
||||
|
||||
/* Reallocate our type and size array if we need to */
|
||||
if(keys->elements > arrsize) {
|
||||
types = zrealloc(types, sizeof(int)*keys->elements);
|
||||
sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements);
|
||||
|
||||
if(!types || !sizes) {
|
||||
fprintf(stderr, "Failed to allocate storage for keys!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
arrsize = keys->elements;
|
||||
/* Get the key "size" */
|
||||
if (!strcmp(reply2->str,"string")) {
|
||||
sizecmd = "STRLEN";
|
||||
type = TYPE_STRING;
|
||||
} else if (!strcmp(reply2->str,"list")) {
|
||||
sizecmd = "LLEN";
|
||||
type = TYPE_LIST;
|
||||
} else if (!strcmp(reply2->str,"set")) {
|
||||
sizecmd = "SCARD";
|
||||
type = TYPE_SET;
|
||||
} else if (!strcmp(reply2->str,"hash")) {
|
||||
sizecmd = "HLEN";
|
||||
type = TYPE_HASH;
|
||||
} else if (!strcmp(reply2->str,"zset")) {
|
||||
sizecmd = "ZCARD";
|
||||
type = TYPE_ZSET;
|
||||
} else if (!strcmp(reply2->str,"none")) {
|
||||
freeReplyObject(reply1);
|
||||
freeReplyObject(reply2);
|
||||
continue;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown key type '%s' for key '%s'\n",
|
||||
reply2->str, reply1->str);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Retreive types and then sizes */
|
||||
getKeyTypes(keys, types);
|
||||
getKeySizes(keys, types, sizes);
|
||||
|
||||
/* Now update our stats */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
if((type = types[i]) == TYPE_NONE)
|
||||
continue;
|
||||
|
||||
totalsize[type] += sizes[i];
|
||||
counts[type]++;
|
||||
totlen += keys->element[i]->len;
|
||||
sampled++;
|
||||
|
||||
if(biggest[type]<sizes[i]) {
|
||||
printf(
|
||||
"[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
|
||||
pct, typename[type], keys->element[i]->str, sizes[i],
|
||||
typeunit[type]);
|
||||
|
||||
/* Keep track of biggest key name for this type */
|
||||
maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str);
|
||||
if(!maxkeys[type]) {
|
||||
fprintf(stderr, "Failed to allocate memory for key!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Keep track of the biggest size for this type */
|
||||
biggest[type] = sizes[i];
|
||||
}
|
||||
|
||||
/* Update overall progress */
|
||||
if(sampled % 1000000 == 0) {
|
||||
printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
|
||||
reply3 = redisCommand(context,"%s %s", sizecmd, reply1->str);
|
||||
if (reply3 && reply3->type == REDIS_REPLY_INTEGER) {
|
||||
if (biggest[type] < reply3->integer) {
|
||||
printf("Biggest %-6s found so far '%s' with %llu %s.\n",
|
||||
typename[type], reply1->str,
|
||||
(unsigned long long) reply3->integer,
|
||||
typeunit[type]);
|
||||
biggest[type] = reply3->integer;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sleep if we've been directed to do so */
|
||||
if(sampled && (sampled %100) == 0 && config.interval) {
|
||||
if ((samples % 1000000) == 0)
|
||||
printf("(%llu keys sampled)\n", samples);
|
||||
|
||||
if ((samples % 100) == 0 && config.interval)
|
||||
usleep(config.interval);
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
} while(it != 0);
|
||||
|
||||
if(types) zfree(types);
|
||||
if(sizes) zfree(sizes);
|
||||
|
||||
/* We're done */
|
||||
printf("\n-------- summary -------\n\n");
|
||||
|
||||
printf("Sampled %llu keys in the keyspace!\n", sampled);
|
||||
printf("Total key length in bytes is %llu (avg len %.2f)\n\n",
|
||||
totlen, totlen ? (double)totlen/sampled : 0);
|
||||
|
||||
/* Output the biggest keys we found, for types we did find */
|
||||
for(i=0;i<TYPE_NONE;i++) {
|
||||
if(sdslen(maxkeys[i])>0) {
|
||||
printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i],
|
||||
biggest[i], typeunit[i]);
|
||||
}
|
||||
freeReplyObject(reply1);
|
||||
freeReplyObject(reply2);
|
||||
if (reply3) freeReplyObject(reply3);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
for(i=0;i<TYPE_NONE;i++) {
|
||||
printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
|
||||
counts[i], typename[i], totalsize[i], typeunit[i],
|
||||
sampled ? 100 * (double)counts[i]/sampled : 0,
|
||||
counts[i] ? (double)totalsize[i]/counts[i] : 0);
|
||||
}
|
||||
|
||||
/* Free sds strings containing max keys */
|
||||
for(i=0;i<TYPE_NONE;i++) {
|
||||
sdsfree(maxkeys[i]);
|
||||
}
|
||||
|
||||
/* Success! */
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Stats mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* 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. */
|
||||
@@ -1743,106 +1493,6 @@ static void statMode() {
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Scan mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static void scanMode() {
|
||||
redisReply *reply;
|
||||
unsigned long long cur = 0;
|
||||
|
||||
do {
|
||||
if (config.pattern)
|
||||
reply = redisCommand(context,"SCAN %llu MATCH %s",
|
||||
cur,config.pattern);
|
||||
else
|
||||
reply = redisCommand(context,"SCAN %llu",cur);
|
||||
if (reply == NULL) {
|
||||
printf("I/O error\n");
|
||||
exit(1);
|
||||
} else if (reply->type == REDIS_REPLY_ERROR) {
|
||||
printf("ERROR: %s\n", reply->str);
|
||||
exit(1);
|
||||
} else {
|
||||
int j;
|
||||
|
||||
cur = strtoull(reply->element[0]->str,NULL,10);
|
||||
for (j = 0; j < reply->element[1]->elements; j++)
|
||||
printf("%s\n", reply->element[1]->element[j]->str);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
} while(cur != 0);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Intrisic latency mode.
|
||||
*
|
||||
* Measure max latency of a running process that does not result from
|
||||
* syscalls. Basically this software should provide an hint about how much
|
||||
* time the kernel leaves the process without a chance to run.
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* This is just some computation the compiler can't optimize out.
|
||||
* Should run in less than 100-200 microseconds even using very
|
||||
* slow hardware. Runs in less than 10 microseconds in modern HW. */
|
||||
unsigned long compute_something_fast(void) {
|
||||
unsigned char s[256], i, j, t;
|
||||
int count = 1000, k;
|
||||
unsigned long output = 0;
|
||||
|
||||
for (k = 0; k < 256; k++) s[k] = k;
|
||||
|
||||
i = 0;
|
||||
j = 0;
|
||||
while(count--) {
|
||||
i++;
|
||||
j = j + s[i];
|
||||
t = s[i];
|
||||
s[i] = s[j];
|
||||
s[j] = t;
|
||||
output += s[(s[i]+s[j])&255];
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static void intrinsicLatencyMode(void) {
|
||||
long long test_end, run_time, max_latency = 0, runs = 0;
|
||||
|
||||
run_time = config.intrinsic_latency_duration*1000000;
|
||||
test_end = ustime() + run_time;
|
||||
|
||||
while(1) {
|
||||
long long start, end, latency;
|
||||
|
||||
start = ustime();
|
||||
compute_something_fast();
|
||||
end = ustime();
|
||||
latency = end-start;
|
||||
runs++;
|
||||
if (latency <= 0) continue;
|
||||
|
||||
/* Reporting */
|
||||
if (latency > max_latency) {
|
||||
max_latency = latency;
|
||||
printf("Max latency so far: %lld microseconds.\n", max_latency);
|
||||
}
|
||||
|
||||
if (end > test_end) {
|
||||
printf("\n%lld total runs (avg %lld microseconds per run).\n",
|
||||
runs, run_time/runs);
|
||||
printf("Worst run took %.02fx times the avarege.\n",
|
||||
(double) max_latency / (run_time/runs));
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Program main()
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int firstarg;
|
||||
|
||||
@@ -1861,13 +1511,9 @@ int main(int argc, char **argv) {
|
||||
config.cluster_mode = 0;
|
||||
config.slave_mode = 0;
|
||||
config.getrdb_mode = 0;
|
||||
config.stat_mode = 0;
|
||||
config.scan_mode = 0;
|
||||
config.intrinsic_latency_mode = 0;
|
||||
config.pattern = NULL;
|
||||
config.rdb_filename = NULL;
|
||||
config.pipe_mode = 0;
|
||||
config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
|
||||
config.pipe_timeout = REDIS_DEFAULT_PIPE_TIMEOUT;
|
||||
config.bigkeys = 0;
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
@@ -1920,15 +1566,6 @@ int main(int argc, char **argv) {
|
||||
statMode();
|
||||
}
|
||||
|
||||
/* Scan mode */
|
||||
if (config.scan_mode) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
scanMode();
|
||||
}
|
||||
|
||||
/* Intrinsic latency mode */
|
||||
if (config.intrinsic_latency_mode) intrinsicLatencyMode();
|
||||
|
||||
/* 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.
|
||||
|
||||
-1075
File diff suppressed because it is too large
Load Diff
+142
-518
File diff suppressed because it is too large
Load Diff
+34
-156
@@ -71,7 +71,6 @@
|
||||
#define REDIS_MIN_HZ 1
|
||||
#define REDIS_MAX_HZ 500
|
||||
#define REDIS_SERVERPORT 6379 /* TCP port */
|
||||
#define REDIS_TCP_BACKLOG 511 /* TCP listen backlog */
|
||||
#define REDIS_MAXIDLETIME 0 /* default client timeout: infinite */
|
||||
#define REDIS_DEFAULT_DBNUM 16
|
||||
#define REDIS_CONFIGLINE_MAX 1024
|
||||
@@ -82,7 +81,7 @@
|
||||
#define REDIS_SHARED_BULKHDR_LEN 32
|
||||
#define REDIS_MAX_LOGMSG_LEN 1024 /* Default maximum length of syslog messages */
|
||||
#define REDIS_AOF_REWRITE_PERC 100
|
||||
#define REDIS_AOF_REWRITE_MIN_SIZE (64*1024*1024)
|
||||
#define REDIS_AOF_REWRITE_MIN_SIZE (1024*1024)
|
||||
#define REDIS_AOF_REWRITE_ITEMS_PER_CMD 64
|
||||
#define REDIS_SLOWLOG_LOG_SLOWER_THAN 10000
|
||||
#define REDIS_SLOWLOG_MAX_LEN 128
|
||||
@@ -107,16 +106,13 @@
|
||||
#define REDIS_DEFAULT_SYSLOG_ENABLED 0
|
||||
#define REDIS_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR 1
|
||||
#define REDIS_DEFAULT_RDB_COMPRESSION 1
|
||||
#define REDIS_DEFAULT_MEM_COMPRESSION 0
|
||||
#define REDIS_DEFAULT_MEM_COMPRESSION_MAX_SIZE (1024*64)
|
||||
#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 5
|
||||
#define REDIS_DEFAULT_AOF_FILENAME "appendonly.aof"
|
||||
#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
|
||||
@@ -125,7 +121,6 @@
|
||||
#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 REDIS_MIN_RESERVED_FDS 32
|
||||
|
||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
|
||||
@@ -142,9 +137,9 @@
|
||||
#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 + RESERVED_FDS + FDSET_INCR
|
||||
* of file descriptors we can handle are server.maxclients + FDSET_INCR
|
||||
* that is our safety margin. */
|
||||
#define REDIS_EVENTLOOP_FDSET_INCR (REDIS_MIN_RESERVED_FDS+96)
|
||||
#define REDIS_EVENTLOOP_FDSET_INCR 128
|
||||
|
||||
/* Hash table parameters */
|
||||
#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */
|
||||
@@ -163,7 +158,6 @@
|
||||
#define REDIS_CMD_LOADING 512 /* "l" flag */
|
||||
#define REDIS_CMD_STALE 1024 /* "t" flag */
|
||||
#define REDIS_CMD_SKIP_MONITOR 2048 /* "M" flag */
|
||||
#define REDIS_CMD_ASKING 4096 /* "k" flag */
|
||||
|
||||
/* Object types */
|
||||
#define REDIS_STRING 0
|
||||
@@ -174,7 +168,7 @@
|
||||
|
||||
/* Objects encoding. Some kind of objects like Strings and Hashes can be
|
||||
* internally represented in multiple ways. The 'encoding' field of the object
|
||||
* is set to one of this values. */
|
||||
* is set to one of this fields for this object. */
|
||||
#define REDIS_ENCODING_RAW 0 /* Raw representation */
|
||||
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
|
||||
#define REDIS_ENCODING_HT 2 /* Encoded as hash table */
|
||||
@@ -183,8 +177,6 @@
|
||||
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
|
||||
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
|
||||
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
|
||||
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
|
||||
#define REDIS_ENCODING_LZF 9 /* LZF compressed string. */
|
||||
|
||||
/* Defines related to the dump file format. To store 32 bits lengths for short
|
||||
* keys requires a lot of space, so we check the most significant 2 bits of
|
||||
@@ -236,14 +228,7 @@
|
||||
#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 (1<<16) /* Instance don't understand PSYNC. */
|
||||
#define REDIS_READONLY (1<<17) /* Cluster client is in read-only state. */
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if REDIS_BLOCKED flag is set. */
|
||||
#define REDIS_BLOCKED_NONE 0 /* Not blocked, no REDIS_BLOCKED flag set. */
|
||||
#define REDIS_BLOCKED_LIST 1 /* BLPOP & co. */
|
||||
#define REDIS_BLOCKED_WAIT 2 /* WAIT for synchronous replication. */
|
||||
#define REDIS_PRE_PSYNC_SLAVE (1<<16) /* Slave don't understand PSYNC. */
|
||||
|
||||
/* Client request types */
|
||||
#define REDIS_REQ_INLINE 1
|
||||
@@ -327,7 +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_NO_EVICTION
|
||||
#define REDIS_DEFAULT_MAXMEMORY_POLICY REDIS_MAXMEMORY_VOLATILE_LRU
|
||||
|
||||
/* Scripting */
|
||||
#define REDIS_LUA_TIME_LIMIT 5000 /* milliseconds */
|
||||
@@ -381,28 +366,20 @@
|
||||
* Data types
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
typedef long long mstime_t; /* millisecond time type. */
|
||||
|
||||
/* A redis object, that is a type able to hold a string / list / set */
|
||||
|
||||
/* The actual Redis Object */
|
||||
#define REDIS_LRU_BITS 24
|
||||
#define REDIS_LRU_CLOCK_MAX ((1<<REDIS_LRU_BITS)-1) /* Max value of obj->lru */
|
||||
#define REDIS_LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
|
||||
#define REDIS_LRU_CLOCK_MAX ((1<<21)-1) /* Max value of obj->lru */
|
||||
#define REDIS_LRU_CLOCK_RESOLUTION 10 /* LRU clock resolution in seconds */
|
||||
typedef struct redisObject {
|
||||
unsigned type:4;
|
||||
unsigned notused:2; /* Not used */
|
||||
unsigned encoding:4;
|
||||
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
|
||||
unsigned lru:22; /* lru time (relative to server.lruclock) */
|
||||
int refcount;
|
||||
void *ptr;
|
||||
} robj;
|
||||
|
||||
/* Macro used to obtain the current LRU clock.
|
||||
* If the current resolution is lower than the frequency we refresh the
|
||||
* LRU clock (as it should be in production servers) we return the
|
||||
* precomputed value, otherwise we need to resort to a function call. */
|
||||
#define LRU_CLOCK() ((1000/server.hz <= REDIS_LRU_CLOCK_RESOLUTION) ? server.lruclock : getLRUClock())
|
||||
|
||||
/* Macro used to initialize a Redis object allocated on the stack.
|
||||
* Note that this macro is taken near the structure definition to make sure
|
||||
* we'll update it when the structure is changed, to avoid bugs like
|
||||
@@ -414,30 +391,13 @@ typedef struct redisObject {
|
||||
_var.ptr = _ptr; \
|
||||
} while(0);
|
||||
|
||||
/* To improve the quality of the LRU approximation we take a set of keys
|
||||
* that are good candidate for eviction across freeMemoryIfNeeded() calls.
|
||||
*
|
||||
* Entries inside the eviciton pool are taken ordered by idle time, putting
|
||||
* greater idle times to the right (ascending order).
|
||||
*
|
||||
* Empty entries have the key pointer set to NULL. */
|
||||
#define REDIS_EVICTION_POOL_SIZE 16
|
||||
struct evictionPoolEntry {
|
||||
unsigned long long idle; /* Object idle time. */
|
||||
sds key; /* Key name. */
|
||||
};
|
||||
|
||||
/* Redis database representation. There are multiple databases identified
|
||||
* by integers from 0 (the default database) up to the max configured
|
||||
* database. The database number is the 'id' field in the structure. */
|
||||
typedef struct redisDb {
|
||||
dict *dict; /* The keyspace for this DB */
|
||||
dict *expires; /* Timeout of keys with a timeout set */
|
||||
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */
|
||||
dict *ready_keys; /* Blocked keys that received a PUSH */
|
||||
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
|
||||
struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */
|
||||
int id; /* Database ID */
|
||||
int id;
|
||||
long long avg_ttl; /* Average TTL, just for stats */
|
||||
} redisDb;
|
||||
|
||||
@@ -455,22 +415,13 @@ typedef struct multiState {
|
||||
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
|
||||
} multiState;
|
||||
|
||||
/* This structure holds the blocking operation state for a client.
|
||||
* The fields used depend on client->btype. */
|
||||
typedef struct blockingState {
|
||||
/* Generic fields. */
|
||||
mstime_t timeout; /* Blocking operation timeout. If UNIX current time
|
||||
* is > timeout then the operation timed out. */
|
||||
|
||||
/* REDIS_BLOCK_LIST */
|
||||
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. */
|
||||
robj *target; /* The key that should receive the element,
|
||||
* for BRPOPLPUSH. */
|
||||
|
||||
/* REDIS_BLOCK_WAIT */
|
||||
int numreplicas; /* Number of replicas we are waiting for ACK. */
|
||||
long long reploffset; /* Replication offset to reach. */
|
||||
} blockingState;
|
||||
|
||||
/* The following structure represents a node in the server.ready_keys list,
|
||||
@@ -517,16 +468,13 @@ typedef struct redisClient {
|
||||
int repldbfd; /* replication DB file descriptor */
|
||||
off_t repldboff; /* replication DB file offset */
|
||||
off_t repldbsize; /* replication DB file size */
|
||||
sds replpreamble; /* replication DB preamble. */
|
||||
long long reploff; /* replication offset if this is our master */
|
||||
long long repl_ack_off; /* replication ack offset, if this is a slave */
|
||||
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 */
|
||||
int btype; /* Type of blocking op if REDIS_BLOCKED. */
|
||||
blockingState bpop; /* blocking state */
|
||||
long long woff; /* Last write global replication offset. */
|
||||
blockingState bpop; /* blocking state */
|
||||
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
|
||||
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
|
||||
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
|
||||
@@ -614,8 +562,6 @@ typedef struct redisOpArray {
|
||||
* Global server state
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
struct clusterState;
|
||||
|
||||
struct redisServer {
|
||||
/* General */
|
||||
char *configfile; /* Absolute config file path, or NULL */
|
||||
@@ -624,7 +570,8 @@ struct redisServer {
|
||||
dict *commands; /* Command table */
|
||||
dict *orig_commands; /* Command table before command renaming. */
|
||||
aeEventLoop *el;
|
||||
unsigned lruclock:REDIS_LRU_BITS; /* Clock for LRU eviction */
|
||||
unsigned lruclock:22; /* Clock incrementing every minute, for LRU */
|
||||
unsigned lruclock_padding:10;
|
||||
int shutdown_asap; /* SHUTDOWN needed ASAP */
|
||||
int activerehashing; /* Incremental rehash in serverCron() */
|
||||
char *requirepass; /* Pass for AUTH command, or NULL */
|
||||
@@ -635,7 +582,6 @@ struct redisServer {
|
||||
int sentinel_mode; /* True if this instance is a Sentinel. */
|
||||
/* Networking */
|
||||
int port; /* TCP listening port */
|
||||
int tcp_backlog; /* TCP listen() backlog */
|
||||
char *bindaddr[REDIS_BINDADDR_MAX]; /* Addresses we should bind to */
|
||||
int bindaddr_count; /* Number of addresses in server.bindaddr[] */
|
||||
char *unixsocket; /* UNIX socket path */
|
||||
@@ -643,16 +589,11 @@ struct redisServer {
|
||||
int ipfd[REDIS_BINDADDR_MAX]; /* TCP socket file descriptors */
|
||||
int ipfd_count; /* Used slots in ipfd[] */
|
||||
int sofd; /* Unix socket file descriptor */
|
||||
int cfd[REDIS_BINDADDR_MAX];/* Cluster bus listening socket */
|
||||
int cfd_count; /* Used slots in cfd[] */
|
||||
list *clients; /* List of active clients */
|
||||
list *clients_to_close; /* Clients to close asynchronously */
|
||||
list *slaves, *monitors; /* List of slaves and MONITORs */
|
||||
redisClient *current_client; /* Current client, only used on crash report */
|
||||
int clients_paused; /* True if clients are currently paused */
|
||||
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
|
||||
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
|
||||
dict *migrate_cached_sockets;/* MIGRATE cached sockets */
|
||||
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
|
||||
/* RDB / AOF loading information */
|
||||
int loading; /* We are loading data from disk if true */
|
||||
off_t loading_total_bytes;
|
||||
@@ -680,7 +621,6 @@ struct redisServer {
|
||||
long long slowlog_entry_id; /* SLOWLOG current entry ID */
|
||||
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
|
||||
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */
|
||||
size_t resident_set_size; /* RSS sampled in serverCron(). */
|
||||
/* The following two are used to track instantaneous "load" in terms
|
||||
* of operations per second. */
|
||||
long long ops_sec_last_sample_time; /* Timestamp of last sample (in ms) */
|
||||
@@ -718,8 +658,6 @@ struct redisServer {
|
||||
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? */
|
||||
int aof_last_write_status; /* REDIS_OK or REDIS_ERR */
|
||||
int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */
|
||||
/* RDB persistence */
|
||||
long long dirty; /* Changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
|
||||
@@ -786,11 +724,8 @@ struct redisServer {
|
||||
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. */
|
||||
/* Synchronous replication. */
|
||||
list *clients_waiting_acks; /* Clients waiting in WAIT command. */
|
||||
int get_ack_from_slaves; /* If true we send REPLCONF GETACK. */
|
||||
/* Limits */
|
||||
int maxclients; /* Max number of simultaneous clients */
|
||||
unsigned int maxclients; /* Max number of simultaneous clients */
|
||||
unsigned long long maxmemory; /* Max number of memory bytes to use */
|
||||
int maxmemory_policy; /* Policy for key eviction */
|
||||
int maxmemory_samples; /* Pricision of random sampling */
|
||||
@@ -812,28 +747,20 @@ struct redisServer {
|
||||
size_t set_max_intset_entries;
|
||||
size_t zset_max_ziplist_entries;
|
||||
size_t zset_max_ziplist_value;
|
||||
int mem_compression; /* In memory LZF compression. */
|
||||
size_t mem_compression_max_size; /* Try to compress up to this size. */
|
||||
time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
long long mstime; /* Like unixtime but in milliseconds. */
|
||||
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. */
|
||||
/* Cluster */
|
||||
int cluster_enabled; /* Is cluster enabled? */
|
||||
mstime_t cluster_node_timeout; /* Cluster node timeout. */
|
||||
char *cluster_configfile; /* Cluster auto-generated config file name. */
|
||||
struct clusterState *cluster; /* State of the cluster */
|
||||
int cluster_migration_barrier; /* Cluster replicas migration barrier. */
|
||||
/* 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 */
|
||||
redisClient *lua_caller; /* The client running EVAL right now, or NULL */
|
||||
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
|
||||
mstime_t lua_time_limit; /* Script timeout in milliseconds */
|
||||
mstime_t lua_time_start; /* Start time of script, milliseconds time */
|
||||
long long lua_time_limit; /* Script timeout in seconds */
|
||||
long long lua_time_start; /* Start time of script */
|
||||
int lua_write_dirty; /* True if a write command was called during the
|
||||
execution of the current script. */
|
||||
int lua_random_dirty; /* True if a random command was called during the
|
||||
@@ -855,15 +782,14 @@ typedef struct pubsubPattern {
|
||||
} pubsubPattern;
|
||||
|
||||
typedef void redisCommandProc(redisClient *c);
|
||||
typedef int *redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
typedef int *redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, int *numkeys, int flags);
|
||||
struct redisCommand {
|
||||
char *name;
|
||||
redisCommandProc *proc;
|
||||
int arity;
|
||||
char *sflags; /* Flags as string representation, one char per flag. */
|
||||
int flags; /* The actual flags, obtained from the 'sflags' field. */
|
||||
/* Use a function to determine keys arguments in a command line.
|
||||
* Used for Redis Cluster redirect. */
|
||||
/* Use a function to determine keys arguments in a command line. */
|
||||
redisGetKeysProc *getkeys_proc;
|
||||
/* What keys should be loaded in background when calling this command? */
|
||||
int firstkey; /* The first argument that's a key (0 = no keys) */
|
||||
@@ -939,8 +865,6 @@ extern struct redisServer server;
|
||||
extern struct sharedObjectsStruct shared;
|
||||
extern dictType setDictType;
|
||||
extern dictType zsetDictType;
|
||||
extern dictType clusterNodesDictType;
|
||||
extern dictType clusterNodesBlackListDictType;
|
||||
extern dictType dbDictType;
|
||||
extern dictType shaScriptObjectDictType;
|
||||
extern double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
@@ -1004,9 +928,6 @@ int getClientLimitClassByName(char *name);
|
||||
char *getClientLimitClassName(int class);
|
||||
void flushSlavesOutputBuffers(void);
|
||||
void disconnectSlaves(void);
|
||||
int listenToPort(int port, int *fds, int *count);
|
||||
void pauseClients(mstime_t duration);
|
||||
int clientsArePaused(void);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
|
||||
@@ -1057,8 +978,6 @@ void freeZsetObject(robj *o);
|
||||
void freeHashObject(robj *o);
|
||||
robj *createObject(int type, void *ptr);
|
||||
robj *createStringObject(char *ptr, size_t len);
|
||||
robj *createRawStringObject(char *ptr, size_t len);
|
||||
robj *createEmbeddedStringObject(char *ptr, size_t len);
|
||||
robj *dupStringObject(robj *o);
|
||||
int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
|
||||
robj *tryObjectEncoding(robj *o);
|
||||
@@ -1084,11 +1003,7 @@ char *strEncoding(int encoding);
|
||||
int compareStringObjects(robj *a, robj *b);
|
||||
int collateStringObjects(robj *a, robj *b);
|
||||
int equalStringObjects(robj *a, robj *b);
|
||||
unsigned long long estimateObjectIdleTime(robj *o);
|
||||
#define rawEncodedObject(objptr) (objptr->encoding == REDIS_ENCODING_RAW)
|
||||
#define lzfEncodedObject(objptr) (objptr->encoding == REDIS_ENCODING_LZF)
|
||||
#define intEncodedObject(objptr) (objptr->encoding == REDIS_ENCODING_INT)
|
||||
#define sdsEncodedObject(objptr) (objptr->encoding == REDIS_ENCODING_RAW || objptr->encoding == REDIS_ENCODING_EMBSTR)
|
||||
unsigned long estimateObjectIdleTime(robj *o);
|
||||
|
||||
/* Synchronous I/O with timeout */
|
||||
ssize_t syncWrite(int fd, char *ptr, ssize_t size, long long timeout);
|
||||
@@ -1103,18 +1018,14 @@ void replicationCron(void);
|
||||
void replicationHandleMasterDisconnection(void);
|
||||
void replicationCacheMaster(redisClient *c);
|
||||
void resizeReplicationBacklog(long long newsize);
|
||||
void replicationSetMaster(char *ip, int port);
|
||||
void replicationUnsetMaster(void);
|
||||
void refreshGoodSlavesCount(void);
|
||||
void replicationScriptCacheInit(void);
|
||||
void replicationScriptCacheFlush(void);
|
||||
void replicationScriptCacheAdd(sds sha1);
|
||||
int replicationScriptCacheExists(sds sha1);
|
||||
void processClientsWaitingReplicas(void);
|
||||
void unblockClientWaitingReplicas(redisClient *c);
|
||||
int replicationCountAcksByOffset(long long offset);
|
||||
void replicationSetMaster(char *ip, int port);
|
||||
void replicationUnsetMaster(void);
|
||||
void replicationSendNewlineToMaster(void);
|
||||
long long replicationGetSlaveOffset(void);
|
||||
|
||||
/* Generic persistence functions */
|
||||
void startLoading(FILE *fp);
|
||||
@@ -1150,13 +1061,11 @@ zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
|
||||
unsigned char *zzlInsert(unsigned char *zl, robj *ele, double score);
|
||||
int zslDelete(zskiplist *zsl, double score, robj *obj);
|
||||
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec range);
|
||||
zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec range);
|
||||
double zzlGetScore(unsigned char *sptr);
|
||||
void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
|
||||
void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
|
||||
unsigned int zsetLength(robj *zobj);
|
||||
void zsetConvert(robj *zobj, int encoding);
|
||||
unsigned long zslGetRank(zskiplist *zsl, double score, robj *o);
|
||||
|
||||
/* Core functions */
|
||||
int freeMemoryIfNeeded(void);
|
||||
@@ -1186,9 +1095,6 @@ void populateCommandTable(void);
|
||||
void resetCommandTableStats(void);
|
||||
void adjustOpenFilesLimit(void);
|
||||
void closeListeningSockets(int unlink_unix_socket);
|
||||
void updateCachedTime(void);
|
||||
void resetServerStats(void);
|
||||
unsigned int getLRUClock(void);
|
||||
|
||||
/* Set data type */
|
||||
robj *setTypeCreate(robj *value);
|
||||
@@ -1260,32 +1166,22 @@ void setKey(redisDb *db, robj *key, robj *val);
|
||||
int dbExists(redisDb *db, robj *key);
|
||||
robj *dbRandomKey(redisDb *db);
|
||||
int dbDelete(redisDb *db, robj *key);
|
||||
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
|
||||
long long emptyDb(void(callback)(void*));
|
||||
int selectDb(redisClient *c, int id);
|
||||
void signalModifiedKey(redisDb *db, robj *key);
|
||||
void signalFlushedDb(int dbid);
|
||||
unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count);
|
||||
unsigned int countKeysInSlot(unsigned int hashslot);
|
||||
int verifyClusterConfigWithData(void);
|
||||
unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count);
|
||||
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor);
|
||||
int parseScanCursorOrReply(redisClient *c, robj *o, unsigned long *cursor);
|
||||
|
||||
/* API to get key arguments from commands */
|
||||
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
#define REDIS_GETKEYS_ALL 0
|
||||
#define REDIS_GETKEYS_PRELOAD 1
|
||||
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys, int flags);
|
||||
void getKeysFreeResult(int *result);
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys);
|
||||
int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
|
||||
/* Cluster */
|
||||
void clusterInit(void);
|
||||
unsigned short crc16(const char *buf, int len);
|
||||
unsigned int keyHashSlot(char *key, int keylen);
|
||||
void clusterCron(void);
|
||||
void clusterPropagatePublish(robj *channel, robj *message);
|
||||
void migrateCloseTimedoutSockets(void);
|
||||
void clusterBeforeSleep(void);
|
||||
int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags);
|
||||
int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags);
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags);
|
||||
|
||||
/* Sentinel */
|
||||
void initSentinelConfig(void);
|
||||
@@ -1297,13 +1193,6 @@ void sentinelIsRunning(void);
|
||||
/* Scripting */
|
||||
void scriptingInit(void);
|
||||
|
||||
/* Blocked clients */
|
||||
void processUnblockedClients(void);
|
||||
void blockClient(redisClient *c, int btype);
|
||||
void unblockClient(redisClient *c);
|
||||
void replyToBlockedClientTimedOut(redisClient *c);
|
||||
int getTimeoutFromObjectOrReply(redisClient *c, robj *object, mstime_t *timeout, int unit);
|
||||
|
||||
/* Git SHA1 */
|
||||
char *redisGitSHA1(void);
|
||||
char *redisGitDirty(void);
|
||||
@@ -1438,12 +1327,8 @@ void publishCommand(redisClient *c);
|
||||
void pubsubCommand(redisClient *c);
|
||||
void watchCommand(redisClient *c);
|
||||
void unwatchCommand(redisClient *c);
|
||||
void clusterCommand(redisClient *c);
|
||||
void restoreCommand(redisClient *c);
|
||||
void migrateCommand(redisClient *c);
|
||||
void askingCommand(redisClient *c);
|
||||
void readonlyCommand(redisClient *c);
|
||||
void readwriteCommand(redisClient *c);
|
||||
void dumpCommand(redisClient *c);
|
||||
void objectCommand(redisClient *c);
|
||||
void clientCommand(redisClient *c);
|
||||
@@ -1453,14 +1338,7 @@ void scriptCommand(redisClient *c);
|
||||
void timeCommand(redisClient *c);
|
||||
void bitopCommand(redisClient *c);
|
||||
void bitcountCommand(redisClient *c);
|
||||
void bitposCommand(redisClient *c);
|
||||
void replconfCommand(redisClient *c);
|
||||
void waitCommand(redisClient *c);
|
||||
void pfselftestCommand(redisClient *c);
|
||||
void pfaddCommand(redisClient *c);
|
||||
void pfcountCommand(redisClient *c);
|
||||
void pfmergeCommand(redisClient *c);
|
||||
void pfgetregCommand(redisClient *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
|
||||
+26
-220
@@ -39,7 +39,6 @@
|
||||
|
||||
void replicationDiscardCachedMaster(void);
|
||||
void replicationResurrectCachedMaster(int newfd);
|
||||
void replicationSendAck(void);
|
||||
|
||||
/* ---------------------------------- MASTER -------------------------------- */
|
||||
|
||||
@@ -344,7 +343,7 @@ int masterTryPartialResynchronization(redisClient *c) {
|
||||
/* Run id "?" is used by slaves that want to force a full resync. */
|
||||
if (master_runid[0] != '?') {
|
||||
redisLog(REDIS_NOTICE,"Partial resynchronization not accepted: "
|
||||
"Runid mismatch (Client asked for runid '%s', my runid is '%s')",
|
||||
"Runid mismatch (Client asked for '%s', I'm '%s')",
|
||||
master_runid, server.runid);
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,"Full resync requested by slave.");
|
||||
@@ -448,7 +447,7 @@ void syncCommand(redisClient *c) {
|
||||
return; /* No full resync needed, return. */
|
||||
} else {
|
||||
char *master_runid = c->argv[1]->ptr;
|
||||
|
||||
|
||||
/* Increment stats for failed PSYNCs, but only if the
|
||||
* runid is not "?", as this is used by slaves to force a full
|
||||
* resync on purpose when they are not albe to partially
|
||||
@@ -459,7 +458,7 @@ void syncCommand(redisClient *c) {
|
||||
/* If a slave uses SYNC, we are dealing with an old implementation
|
||||
* of the replication protocol (like redis-cli --slave). Flag the client
|
||||
* so that we don't expect to receive REPLCONF ACK feedbacks. */
|
||||
c->flags |= REDIS_PRE_PSYNC;
|
||||
c->flags |= REDIS_PRE_PSYNC_SLAVE;
|
||||
}
|
||||
|
||||
/* Full resynchronization. */
|
||||
@@ -561,11 +560,6 @@ void replconfCommand(redisClient *c) {
|
||||
c->repl_ack_time = server.unixtime;
|
||||
/* Note: this command does not reply anything! */
|
||||
return;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"getack")) {
|
||||
/* REPLCONF GETACK is used in order to request an ACK ASAP
|
||||
* to the slave. */
|
||||
if (server.masterhost && server.master) replicationSendAck();
|
||||
/* Note: this command does not reply anything! */
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unrecognized REPLCONF option: %s",
|
||||
(char*)c->argv[j]->ptr);
|
||||
@@ -582,28 +576,23 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
char buf[REDIS_IOBUF_LEN];
|
||||
ssize_t nwritten, buflen;
|
||||
|
||||
/* Before sending the RDB file, we send the preamble as configured by the
|
||||
* replication process. Currently the preamble is just the bulk count of
|
||||
* the file in the form "$<length>\r\n". */
|
||||
if (slave->replpreamble) {
|
||||
nwritten = write(fd,slave->replpreamble,sdslen(slave->replpreamble));
|
||||
if (nwritten == -1) {
|
||||
redisLog(REDIS_VERBOSE,"Write error sending RDB preamble to slave: %s",
|
||||
strerror(errno));
|
||||
if (slave->repldboff == 0) {
|
||||
/* Write the bulk write count before to transfer the DB. In theory here
|
||||
* we don't know how much room there is in the output buffer of the
|
||||
* socket, but in practice SO_SNDLOWAT (the minimum count for output
|
||||
* operations) will never be smaller than the few bytes we need. */
|
||||
sds bulkcount;
|
||||
|
||||
bulkcount = sdscatprintf(sdsempty(),"$%lld\r\n",(unsigned long long)
|
||||
slave->repldbsize);
|
||||
if (write(fd,bulkcount,sdslen(bulkcount)) != (signed)sdslen(bulkcount))
|
||||
{
|
||||
sdsfree(bulkcount);
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
sdsrange(slave->replpreamble,nwritten,-1);
|
||||
if (sdslen(slave->replpreamble) == 0) {
|
||||
sdsfree(slave->replpreamble);
|
||||
slave->replpreamble = NULL;
|
||||
/* fall through sending data. */
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
sdsfree(bulkcount);
|
||||
}
|
||||
|
||||
/* If the preamble was already transfered, send the RDB bulk data. */
|
||||
lseek(slave->repldbfd,slave->repldboff,SEEK_SET);
|
||||
buflen = read(slave->repldbfd,buf,REDIS_IOBUF_LEN);
|
||||
if (buflen <= 0) {
|
||||
@@ -613,11 +602,9 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
if ((nwritten = write(fd,buf,buflen)) == -1) {
|
||||
if (errno != EAGAIN) {
|
||||
redisLog(REDIS_WARNING,"Write error sending DB to slave: %s",
|
||||
strerror(errno));
|
||||
freeClient(slave);
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Write error sending DB to slave: %s",
|
||||
strerror(errno));
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
slave->repldboff += nwritten;
|
||||
@@ -629,7 +616,6 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
slave->repl_ack_time = server.unixtime;
|
||||
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE,
|
||||
sendReplyToClient, slave) == AE_ERR) {
|
||||
redisLog(REDIS_WARNING,"Unable to register writable event for slave bulk transfer: %s", strerror(errno));
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
@@ -673,9 +659,6 @@ void updateSlavesWaitingBgsave(int bgsaveerr) {
|
||||
slave->repldboff = 0;
|
||||
slave->repldbsize = buf.st_size;
|
||||
slave->replstate = REDIS_REPL_SEND_BULK;
|
||||
slave->replpreamble = sdscatprintf(sdsempty(),"$%lld\r\n",
|
||||
(unsigned long long) slave->repldbsize);
|
||||
|
||||
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
|
||||
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, sendBulkToSlave, slave) == AE_ERR) {
|
||||
freeClient(slave);
|
||||
@@ -846,10 +829,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
server.master->reploff = server.repl_master_initial_offset;
|
||||
memcpy(server.master->replrunid, server.repl_master_runid,
|
||||
sizeof(server.repl_master_runid));
|
||||
/* If master offset is set to -1, this master is old and is not
|
||||
* PSYNC capable, so we flag it accordingly. */
|
||||
if (server.master->reploff == -1)
|
||||
server.master->flags |= REDIS_PRE_PSYNC;
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
|
||||
/* Restart the AOF subsystem now that we finished the sync. This
|
||||
* will trigger an AOF rewrite, and when done will start appending
|
||||
@@ -1012,7 +991,7 @@ int slaveTryPartialResynchronization(int fd) {
|
||||
|
||||
/* If we reach this point we receied either an error since the master does
|
||||
* not understand PSYNC, or an unexpected reply from the master.
|
||||
* Return PSYNC_NOT_SUPPORTED to the caller in both cases. */
|
||||
* Reply with PSYNC_NOT_SUPPORTED in both cases. */
|
||||
|
||||
if (strncmp(reply,"-ERR",4)) {
|
||||
/* If it's not an error, log the unexpected event. */
|
||||
@@ -1255,7 +1234,7 @@ int cancelReplicationHandshake(void) {
|
||||
/* Set replication to the specified master address and port. */
|
||||
void replicationSetMaster(char *ip, int port) {
|
||||
sdsfree(server.masterhost);
|
||||
server.masterhost = sdsnew(ip);
|
||||
server.masterhost = sdsdup(ip);
|
||||
server.masterport = port;
|
||||
if (server.master) freeClient(server.master);
|
||||
disconnectSlaves(); /* Force our slaves to resync with us as well. */
|
||||
@@ -1263,8 +1242,6 @@ void replicationSetMaster(char *ip, int port) {
|
||||
freeReplicationBacklog(); /* Don't allow our chained slaves to PSYNC. */
|
||||
cancelReplicationHandshake();
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
server.master_repl_offset = 0;
|
||||
server.repl_down_since = 0;
|
||||
}
|
||||
|
||||
/* Cancel replication, setting the instance as a master itself. */
|
||||
@@ -1272,32 +1249,13 @@ void replicationUnsetMaster(void) {
|
||||
if (server.masterhost == NULL) return; /* Nothing to do. */
|
||||
sdsfree(server.masterhost);
|
||||
server.masterhost = NULL;
|
||||
if (server.master) {
|
||||
if (listLength(server.slaves) == 0) {
|
||||
/* If this instance is turned into a master and there are no
|
||||
* slaves, it inherits the replication offset from the master.
|
||||
* Under certain conditions this makes replicas comparable by
|
||||
* replication offset to understand what is the most updated. */
|
||||
server.master_repl_offset = server.master->reploff;
|
||||
freeReplicationBacklog();
|
||||
}
|
||||
freeClient(server.master);
|
||||
}
|
||||
if (server.master) freeClient(server.master);
|
||||
replicationDiscardCachedMaster();
|
||||
cancelReplicationHandshake();
|
||||
server.repl_state = REDIS_REPL_NONE;
|
||||
}
|
||||
|
||||
void slaveofCommand(redisClient *c) {
|
||||
/* SLAVEOF is not allowed in cluster mode as replication is automatically
|
||||
* configured using the current address of the master node. */
|
||||
if (server.cluster_enabled) {
|
||||
addReplyError(c,"SLAVEOF not allowed in cluster mode.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* The special host/port combination "NO" "ONE" turns the instance
|
||||
* into a master. Otherwise the new master address is set. */
|
||||
if (!strcasecmp(c->argv[1]->ptr,"no") &&
|
||||
!strcasecmp(c->argv[2]->ptr,"one")) {
|
||||
if (server.masterhost) {
|
||||
@@ -1546,156 +1504,7 @@ int replicationScriptCacheExists(sds sha1) {
|
||||
return dictFind(server.repl_scriptcache_dict,sha1) != NULL;
|
||||
}
|
||||
|
||||
/* ----------------------- SYNCHRONOUS REPLICATION --------------------------
|
||||
* Redis synchronous replication design can be summarized in points:
|
||||
*
|
||||
* - Redis masters have a global replication offset, used by PSYNC.
|
||||
* - Master increment the offset every time new commands are sent to slaves.
|
||||
* - Slaves ping back masters with the offset processed so far.
|
||||
*
|
||||
* So synchronous replication adds a new WAIT command in the form:
|
||||
*
|
||||
* WAIT <num_replicas> <milliseconds_timeout>
|
||||
*
|
||||
* That returns the number of replicas that processed the query when
|
||||
* we finally have at least num_replicas, or when the timeout was
|
||||
* reached.
|
||||
*
|
||||
* The command is implemented in this way:
|
||||
*
|
||||
* - Every time a client processes a command, we remember the replication
|
||||
* offset after sending that command to the slaves.
|
||||
* - When WAIT is called, we ask slaves to send an acknowledgement ASAP.
|
||||
* The client is blocked at the same time (see blocked.c).
|
||||
* - Once we receive enough ACKs for a given offset or when the timeout
|
||||
* is reached, the WAIT command is unblocked and the reply sent to the
|
||||
* client.
|
||||
*/
|
||||
|
||||
/* This just set a flag so that we broadcast a REPLCONF GETACK command
|
||||
* to all the slaves in the beforeSleep() function. Note that this way
|
||||
* we "group" all the clients that want to wait for synchronouns replication
|
||||
* in a given event loop iteration, and send a single GETACK for them all. */
|
||||
void replicationRequestAckFromSlaves(void) {
|
||||
server.get_ack_from_slaves = 1;
|
||||
}
|
||||
|
||||
/* Return the number of slaves that already acknowledged the specified
|
||||
* replication offset. */
|
||||
int replicationCountAcksByOffset(long long offset) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
int count = 0;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate != REDIS_REPL_ONLINE) continue;
|
||||
if (slave->repl_ack_off >= offset) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* WAIT for N replicas to acknowledge the processing of our latest
|
||||
* write command (and all the previous commands). */
|
||||
void waitCommand(redisClient *c) {
|
||||
mstime_t timeout;
|
||||
long numreplicas, ackreplicas;
|
||||
long long offset = c->woff;
|
||||
|
||||
/* Argument parsing. */
|
||||
if (getLongFromObjectOrReply(c,c->argv[1],&numreplicas,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[2],&timeout,UNIT_MILLISECONDS)
|
||||
!= REDIS_OK) return;
|
||||
|
||||
/* First try without blocking at all. */
|
||||
ackreplicas = replicationCountAcksByOffset(c->woff);
|
||||
if (ackreplicas >= numreplicas || c->flags & REDIS_MULTI) {
|
||||
addReplyLongLong(c,ackreplicas);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Otherwise block the client and put it into our list of clients
|
||||
* waiting for ack from slaves. */
|
||||
c->bpop.timeout = timeout;
|
||||
c->bpop.reploffset = offset;
|
||||
c->bpop.numreplicas = numreplicas;
|
||||
listAddNodeTail(server.clients_waiting_acks,c);
|
||||
blockClient(c,REDIS_BLOCKED_WAIT);
|
||||
|
||||
/* Make sure that the server will send an ACK request to all the slaves
|
||||
* before returning to the event loop. */
|
||||
replicationRequestAckFromSlaves();
|
||||
}
|
||||
|
||||
/* This is called by unblockClient() to perform the blocking op type
|
||||
* specific cleanup. We just remove the client from the list of clients
|
||||
* waiting for replica acks. Never call it directly, call unblockClient()
|
||||
* instead. */
|
||||
void unblockClientWaitingReplicas(redisClient *c) {
|
||||
listNode *ln = listSearchKey(server.clients_waiting_acks,c);
|
||||
redisAssert(ln != NULL);
|
||||
listDelNode(server.clients_waiting_acks,ln);
|
||||
}
|
||||
|
||||
/* Check if there are clients blocked in WAIT that can be unblocked since
|
||||
* we received enough ACKs from slaves. */
|
||||
void processClientsWaitingReplicas(void) {
|
||||
long long last_offset = 0;
|
||||
int last_numreplicas = 0;
|
||||
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.clients_waiting_acks,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *c = ln->value;
|
||||
|
||||
/* Every time we find a client that is satisfied for a given
|
||||
* offset and number of replicas, we remember it so the next client
|
||||
* may be unblocked without calling replicationCountAcksByOffset()
|
||||
* if the requested offset / replicas were equal or less. */
|
||||
if (last_offset && last_offset > c->bpop.reploffset &&
|
||||
last_numreplicas > c->bpop.numreplicas)
|
||||
{
|
||||
unblockClient(c);
|
||||
addReplyLongLong(c,last_numreplicas);
|
||||
} else {
|
||||
int numreplicas = replicationCountAcksByOffset(c->bpop.reploffset);
|
||||
|
||||
if (numreplicas >= c->bpop.numreplicas) {
|
||||
last_offset = c->bpop.reploffset;
|
||||
last_numreplicas = numreplicas;
|
||||
unblockClient(c);
|
||||
addReplyLongLong(c,numreplicas);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the slave replication offset for this instance, that is
|
||||
* the offset for which we already processed the master replication stream. */
|
||||
long long replicationGetSlaveOffset(void) {
|
||||
long long offset = 0;
|
||||
|
||||
if (server.masterhost != NULL) {
|
||||
if (server.master) {
|
||||
offset = server.master->reploff;
|
||||
} else if (server.cached_master) {
|
||||
offset = server.cached_master->reploff;
|
||||
}
|
||||
}
|
||||
/* offset may be -1 when the master does not support it at all, however
|
||||
* this function is designed to return an offset that can express the
|
||||
* amount of data processed by the master, so we return a positive
|
||||
* integer. */
|
||||
if (offset < 0) offset = 0;
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* --------------------------- REPLICATION CRON ---------------------------- */
|
||||
/* --------------------------- REPLICATION CRON ----------------------------- */
|
||||
|
||||
/* Replication cron funciton, called 1 time per second. */
|
||||
void replicationCron(void) {
|
||||
@@ -1734,11 +1543,8 @@ void replicationCron(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Send ACK to master from time to time.
|
||||
* Note that we do not send periodic acks to masters that don't
|
||||
* support PSYNC and replication offsets. */
|
||||
if (server.masterhost && server.master &&
|
||||
!(server.master->flags & REDIS_PRE_PSYNC))
|
||||
/* Send ACK to master from time to time. */
|
||||
if (server.masterhost && server.master)
|
||||
replicationSendAck();
|
||||
|
||||
/* If we have attached slaves, PING them from time to time.
|
||||
@@ -1782,7 +1588,7 @@ void replicationCron(void) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate != REDIS_REPL_ONLINE) continue;
|
||||
if (slave->flags & REDIS_PRE_PSYNC) continue;
|
||||
if (slave->flags & REDIS_PRE_PSYNC_SLAVE) continue;
|
||||
if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout)
|
||||
{
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
|
||||
@@ -86,7 +86,6 @@ static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
|
||||
if (r->io.file.autosync &&
|
||||
r->io.file.buffered >= r->io.file.autosync)
|
||||
{
|
||||
fflush(r->io.file.fp);
|
||||
aof_fsync(fileno(r->io.file.fp));
|
||||
r->io.file.buffered = 0;
|
||||
}
|
||||
|
||||
+2
-3
@@ -441,7 +441,7 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
REDIS_NOTUSED(ar);
|
||||
REDIS_NOTUSED(lua);
|
||||
|
||||
elapsed = mstime() - server.lua_time_start;
|
||||
elapsed = (ustime()/1000) - server.lua_time_start;
|
||||
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
|
||||
redisLog(REDIS_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
|
||||
server.lua_timedout = 1;
|
||||
@@ -900,7 +900,7 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
* We set the hook only if the time limit is enabled as the hook will
|
||||
* make the Lua script execution slower. */
|
||||
server.lua_caller = c;
|
||||
server.lua_time_start = mstime();
|
||||
server.lua_time_start = ustime()/1000;
|
||||
server.lua_kill = 0;
|
||||
if (server.lua_time_limit > 0 && server.masterhost == NULL) {
|
||||
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
|
||||
@@ -958,7 +958,6 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
rewriteClientCommandArgument(c,0,
|
||||
resetRefCount(createStringObject("EVAL",4)));
|
||||
rewriteClientCommandArgument(c,1,script);
|
||||
forceCommandPropagation(c,REDIS_PROPAGATE_REPL|REDIS_PROPAGATE_AOF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,37 +292,24 @@ sds sdscpy(sds s, const char *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 staticbuf[1024], *buf = staticbuf, *t;
|
||||
size_t buflen = strlen(fmt)*2;
|
||||
char *buf, *t;
|
||||
size_t buflen = 16;
|
||||
|
||||
/* We try to start using a static buffer for speed.
|
||||
* If not possible we revert to heap allocation. */
|
||||
if (buflen > sizeof(staticbuf)) {
|
||||
while(1) {
|
||||
buf = zmalloc(buflen);
|
||||
if (buf == NULL) return NULL;
|
||||
} else {
|
||||
buflen = sizeof(staticbuf);
|
||||
}
|
||||
|
||||
/* Try with buffers two times bigger every time we fail to
|
||||
* fit the string in the current buffer size. */
|
||||
while(1) {
|
||||
buf[buflen-2] = '\0';
|
||||
va_copy(cpy,ap);
|
||||
vsnprintf(buf, buflen, fmt, cpy);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
if (buf != staticbuf) zfree(buf);
|
||||
zfree(buf);
|
||||
buflen *= 2;
|
||||
buf = zmalloc(buflen);
|
||||
if (buf == NULL) return NULL;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Finally concat the obtained string to the SDS string and return it. */
|
||||
t = sdscat(s, buf);
|
||||
if (buf != staticbuf) zfree(buf);
|
||||
zfree(buf);
|
||||
return t;
|
||||
}
|
||||
|
||||
@@ -396,7 +383,7 @@ sds sdstrim(sds s, const char *cset) {
|
||||
* Example:
|
||||
*
|
||||
* s = sdsnew("Hello World");
|
||||
* sdsrange(s,1,-1); => "ello World"
|
||||
* sdstrim(s,1,-1); => "ello Worl"
|
||||
*/
|
||||
void sdsrange(sds s, int start, int end) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
|
||||
+149
-486
File diff suppressed because it is too large
Load Diff
+4
-6
@@ -63,17 +63,15 @@ slowlogEntry *slowlogCreateEntry(robj **argv, int argc, long long duration) {
|
||||
} else {
|
||||
/* Trim too long strings as well... */
|
||||
if (argv[j]->type == REDIS_STRING &&
|
||||
(sdsEncodedObject(argv[j]) || lzfEncodedObject(argv[j])) &&
|
||||
stringObjectLen(argv[j]) > SLOWLOG_ENTRY_MAX_STRING)
|
||||
argv[j]->encoding == REDIS_ENCODING_RAW &&
|
||||
sdslen(argv[j]->ptr) > SLOWLOG_ENTRY_MAX_STRING)
|
||||
{
|
||||
robj *o = getDecodedObject(argv[j]);
|
||||
sds s = sdsnewlen(o->ptr, SLOWLOG_ENTRY_MAX_STRING);
|
||||
sds s = sdsnewlen(argv[j]->ptr, SLOWLOG_ENTRY_MAX_STRING);
|
||||
|
||||
s = sdscatprintf(s,"... (%lu more bytes)",
|
||||
(unsigned long)
|
||||
stringObjectLen(argv[j]) - SLOWLOG_ENTRY_MAX_STRING);
|
||||
sdslen(argv[j]->ptr) - SLOWLOG_ENTRY_MAX_STRING);
|
||||
se->argv[j] = createObject(REDIS_STRING,s);
|
||||
decrRefCount(o);
|
||||
} else {
|
||||
se->argv[j] = argv[j];
|
||||
incrRefCount(argv[j]);
|
||||
|
||||
+7
-35
@@ -194,7 +194,6 @@ void sortCommand(redisClient *c) {
|
||||
int j, dontsort = 0, vectorlen;
|
||||
int getop = 0; /* GET operation counter */
|
||||
int int_convertion_error = 0;
|
||||
int syntax_error = 0;
|
||||
robj *sortval, *sortby = NULL, *storekey = NULL;
|
||||
redisSortObject *vector; /* Resulting vector to sort */
|
||||
|
||||
@@ -232,14 +231,8 @@ void sortCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"alpha")) {
|
||||
alpha = 1;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) {
|
||||
if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL)
|
||||
!= REDIS_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL)
|
||||
!= REDIS_OK))
|
||||
{
|
||||
syntax_error++;
|
||||
break;
|
||||
}
|
||||
if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return;
|
||||
j+=2;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) {
|
||||
storekey = c->argv[j+1];
|
||||
@@ -248,43 +241,22 @@ void sortCommand(redisClient *c) {
|
||||
sortby = c->argv[j+1];
|
||||
/* If the BY pattern does not contain '*', i.e. it is constant,
|
||||
* we don't need to sort nor to lookup the weight keys. */
|
||||
if (strchr(c->argv[j+1]->ptr,'*') == NULL) {
|
||||
dontsort = 1;
|
||||
} else {
|
||||
/* If BY is specified with a real patter, we can't accept
|
||||
* it in cluster mode. */
|
||||
if (server.cluster_enabled) {
|
||||
addReplyError(c,"BY option of SORT denied in Cluster mode.");
|
||||
syntax_error++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (strchr(c->argv[j+1]->ptr,'*') == NULL) dontsort = 1;
|
||||
j++;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) {
|
||||
if (server.cluster_enabled) {
|
||||
addReplyError(c,"GET option of SORT denied in Cluster mode.");
|
||||
syntax_error++;
|
||||
break;
|
||||
}
|
||||
listAddNodeTail(operations,createSortOperation(
|
||||
REDIS_SORT_GET,c->argv[j+1]));
|
||||
getop++;
|
||||
j++;
|
||||
} else {
|
||||
decrRefCount(sortval);
|
||||
listRelease(operations);
|
||||
addReply(c,shared.syntaxerr);
|
||||
syntax_error++;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
/* Handle syntax errors set during options parsing. */
|
||||
if (syntax_error) {
|
||||
decrRefCount(sortval);
|
||||
listRelease(operations);
|
||||
return;
|
||||
}
|
||||
|
||||
/* For the STORE option, or when SORT is called from a Lua script,
|
||||
* we want to force a specific ordering even when no explicit ordering
|
||||
* was asked (SORT BY nosort). This guarantees that replication / AOF
|
||||
@@ -439,7 +411,7 @@ void sortCommand(redisClient *c) {
|
||||
if (alpha) {
|
||||
if (sortby) vector[j].u.cmpobj = getDecodedObject(byval);
|
||||
} else {
|
||||
if (sdsEncodedObject(byval)) {
|
||||
if (byval->encoding == REDIS_ENCODING_RAW) {
|
||||
char *eptr;
|
||||
|
||||
vector[j].u.score = strtod(byval->ptr,&eptr);
|
||||
|
||||
+4
-1
@@ -43,7 +43,7 @@ void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
|
||||
if (o->encoding != REDIS_ENCODING_ZIPLIST) return;
|
||||
|
||||
for (i = start; i <= end; i++) {
|
||||
if (sdsEncodedObject(argv[i]) &&
|
||||
if (argv[i]->encoding == REDIS_ENCODING_RAW &&
|
||||
sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
|
||||
{
|
||||
hashTypeConvert(o, REDIS_ENCODING_HT);
|
||||
@@ -384,12 +384,15 @@ robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) {
|
||||
} else {
|
||||
dst = createStringObjectFromLongLong(vll);
|
||||
}
|
||||
|
||||
} else if (hi->encoding == REDIS_ENCODING_HT) {
|
||||
hashTypeCurrentFromHashTable(hi, what, &dst);
|
||||
incrRefCount(dst);
|
||||
|
||||
} else {
|
||||
redisPanic("Unknown hash encoding");
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
+38
-14
@@ -40,7 +40,7 @@ void signalListAsReady(redisClient *c, robj *key);
|
||||
* objects are never too long. */
|
||||
void listTypeTryConversion(robj *subject, robj *value) {
|
||||
if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;
|
||||
if (sdsEncodedObject(value) &&
|
||||
if (value->encoding == REDIS_ENCODING_RAW &&
|
||||
sdslen(value->ptr) > server.list_max_ziplist_value)
|
||||
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
|
||||
}
|
||||
@@ -234,7 +234,7 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
|
||||
int listTypeEqual(listTypeEntry *entry, robj *o) {
|
||||
listTypeIterator *li = entry->li;
|
||||
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
redisAssertWithInfo(NULL,o,sdsEncodedObject(o));
|
||||
redisAssertWithInfo(NULL,o,o->encoding == REDIS_ENCODING_RAW);
|
||||
return ziplistCompare(entry->zi,o->ptr,sdslen(o->ptr));
|
||||
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
return equalStringObjects(o,listNodeValue(entry->ln));
|
||||
@@ -778,7 +778,7 @@ void rpoplpushCommand(redisClient *c) {
|
||||
|
||||
/* Set a client in blocking mode for the specified key, with the specified
|
||||
* timeout */
|
||||
void blockForKeys(redisClient *c, robj **keys, int numkeys, mstime_t timeout, robj *target) {
|
||||
void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout, robj *target) {
|
||||
dictEntry *de;
|
||||
list *l;
|
||||
int j;
|
||||
@@ -808,11 +808,13 @@ void blockForKeys(redisClient *c, robj **keys, int numkeys, mstime_t timeout, ro
|
||||
}
|
||||
listAddNodeTail(l,c);
|
||||
}
|
||||
blockClient(c,REDIS_BLOCKED_LIST);
|
||||
|
||||
/* Mark the client as a blocked client */
|
||||
c->flags |= REDIS_BLOCKED;
|
||||
server.bpop_blocked_clients++;
|
||||
}
|
||||
|
||||
/* Unblock a client that's waiting in a blocking operation such as BLPOP.
|
||||
* You should never call this function directly, but unblockClient() instead. */
|
||||
/* Unblock a client that's waiting in a blocking operation such as BLPOP */
|
||||
void unblockClientWaitingData(redisClient *c) {
|
||||
dictEntry *de;
|
||||
dictIterator *di;
|
||||
@@ -840,6 +842,10 @@ void unblockClientWaitingData(redisClient *c) {
|
||||
decrRefCount(c->bpop.target);
|
||||
c->bpop.target = NULL;
|
||||
}
|
||||
c->flags &= ~REDIS_BLOCKED;
|
||||
c->flags |= REDIS_UNBLOCKED;
|
||||
server.bpop_blocked_clients--;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
|
||||
/* If the specified key has clients blocked waiting for list pushes, this
|
||||
@@ -994,10 +1000,10 @@ void handleClientsBlockedOnLists(void) {
|
||||
|
||||
if (value) {
|
||||
/* Protect receiver->bpop.target, that will be
|
||||
* freed by the next unblockClient()
|
||||
* freed by the next unblockClientWaitingData()
|
||||
* call. */
|
||||
if (dstkey) incrRefCount(dstkey);
|
||||
unblockClient(receiver);
|
||||
unblockClientWaitingData(receiver);
|
||||
|
||||
if (serveClientBlockedOnList(receiver,
|
||||
rl->key,dstkey,rl->db,value,
|
||||
@@ -1030,14 +1036,32 @@ void handleClientsBlockedOnLists(void) {
|
||||
}
|
||||
}
|
||||
|
||||
int getTimeoutFromObjectOrReply(redisClient *c, robj *object, time_t *timeout) {
|
||||
long tval;
|
||||
|
||||
if (getLongFromObjectOrReply(c,object,&tval,
|
||||
"timeout is not an integer or out of range") != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
|
||||
if (tval < 0) {
|
||||
addReplyError(c,"timeout is negative");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (tval > 0) tval += server.unixtime;
|
||||
*timeout = tval;
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Blocking RPOP/LPOP */
|
||||
void blockingPopGenericCommand(redisClient *c, int where) {
|
||||
robj *o;
|
||||
mstime_t timeout;
|
||||
time_t timeout;
|
||||
int j;
|
||||
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout,UNIT_SECONDS)
|
||||
!= REDIS_OK) return;
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout) != REDIS_OK)
|
||||
return;
|
||||
|
||||
for (j = 1; j < c->argc-1; j++) {
|
||||
o = lookupKeyWrite(c->db,c->argv[j]);
|
||||
@@ -1096,10 +1120,10 @@ void brpopCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
void brpoplpushCommand(redisClient *c) {
|
||||
mstime_t timeout;
|
||||
time_t timeout;
|
||||
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[3],&timeout,UNIT_SECONDS)
|
||||
!= REDIS_OK) return;
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[3],&timeout) != REDIS_OK)
|
||||
return;
|
||||
|
||||
robj *key = lookupKeyWrite(c->db, c->argv[1]);
|
||||
|
||||
|
||||
+7
-3
@@ -495,8 +495,11 @@ void srandmemberWithCountCommand(redisClient *c) {
|
||||
|
||||
if (encoding == REDIS_ENCODING_INTSET) {
|
||||
retval = dictAdd(d,createStringObjectFromLongLong(llele),NULL);
|
||||
} else {
|
||||
} else if (ele->encoding == REDIS_ENCODING_RAW) {
|
||||
retval = dictAdd(d,dupStringObject(ele),NULL);
|
||||
} else if (ele->encoding == REDIS_ENCODING_INT) {
|
||||
retval = dictAdd(d,
|
||||
createStringObjectFromLongLong((long)ele->ptr),NULL);
|
||||
}
|
||||
redisAssert(retval == DICT_OK);
|
||||
}
|
||||
@@ -524,8 +527,10 @@ void srandmemberWithCountCommand(redisClient *c) {
|
||||
encoding = setTypeRandomElement(set,&ele,&llele);
|
||||
if (encoding == REDIS_ENCODING_INTSET) {
|
||||
ele = createStringObjectFromLongLong(llele);
|
||||
} else {
|
||||
} else if (ele->encoding == REDIS_ENCODING_RAW) {
|
||||
ele = dupStringObject(ele);
|
||||
} else if (ele->encoding == REDIS_ENCODING_INT) {
|
||||
ele = createStringObjectFromLongLong((long)ele->ptr);
|
||||
}
|
||||
/* Try to add the object to the dictionary. If it already exists
|
||||
* free it, otherwise increment the number of objects we have
|
||||
@@ -820,7 +825,6 @@ void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *
|
||||
while((ele = setTypeNextObject(si)) != NULL) {
|
||||
for (j = 1; j < setnum; j++) {
|
||||
if (!sets[j]) continue; /* no key is an empty set. */
|
||||
if (sets[j] == sets[0]) break; /* same set! */
|
||||
if (setTypeIsMember(sets[j],ele)) break;
|
||||
}
|
||||
if (j == setnum) {
|
||||
|
||||
+14
-3
@@ -215,7 +215,12 @@ void setrangeCommand(redisClient *c) {
|
||||
return;
|
||||
|
||||
/* Create a copy when the object is shared or encoded. */
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbOverwrite(c->db,c->argv[1],o);
|
||||
}
|
||||
}
|
||||
|
||||
if (sdslen(value) > 0) {
|
||||
@@ -242,7 +247,6 @@ void getrangeCommand(redisClient *c) {
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
|
||||
checkType(c,o,REDIS_STRING)) return;
|
||||
|
||||
if (lzfEncodedObject(o)) o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
if (o->encoding == REDIS_ENCODING_INT) {
|
||||
str = llbuf;
|
||||
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
|
||||
@@ -429,8 +433,15 @@ void appendCommand(redisClient *c) {
|
||||
if (checkStringLength(c,totlen) != REDIS_OK)
|
||||
return;
|
||||
|
||||
/* If the object is shared or encoded, we have to make a copy */
|
||||
if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
|
||||
robj *decoded = getDecodedObject(o);
|
||||
o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
|
||||
decrRefCount(decoded);
|
||||
dbOverwrite(c->db,c->argv[1],o);
|
||||
}
|
||||
|
||||
/* Append the value */
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
|
||||
totlen = sdslen(o->ptr);
|
||||
}
|
||||
|
||||
+6
-9
@@ -646,7 +646,7 @@ unsigned char *zzlInsertAt(unsigned char *zl, unsigned char *eptr, robj *ele, do
|
||||
int scorelen;
|
||||
size_t offset;
|
||||
|
||||
redisAssertWithInfo(NULL,ele,sdsEncodedObject(ele));
|
||||
redisAssertWithInfo(NULL,ele,ele->encoding == REDIS_ENCODING_RAW);
|
||||
scorelen = d2string(scorebuf,sizeof(scorebuf),score);
|
||||
if (eptr == NULL) {
|
||||
zl = ziplistPush(zl,ele->ptr,sdslen(ele->ptr),ZIPLIST_TAIL);
|
||||
@@ -1363,7 +1363,7 @@ int zuiLongLongFromValue(zsetopval *val) {
|
||||
if (val->ele->encoding == REDIS_ENCODING_INT) {
|
||||
val->ell = (long)val->ele->ptr;
|
||||
val->flags |= OPVAL_VALID_LL;
|
||||
} else if (sdsEncodedObject(val->ele)) {
|
||||
} else if (val->ele->encoding == REDIS_ENCODING_RAW) {
|
||||
if (string2ll(val->ele->ptr,sdslen(val->ele->ptr),&val->ell))
|
||||
val->flags |= OPVAL_VALID_LL;
|
||||
} else {
|
||||
@@ -1398,7 +1398,7 @@ int zuiBufferFromValue(zsetopval *val) {
|
||||
if (val->ele->encoding == REDIS_ENCODING_INT) {
|
||||
val->elen = ll2string((char*)val->_buf,sizeof(val->_buf),(long)val->ele->ptr);
|
||||
val->estr = val->_buf;
|
||||
} else if (sdsEncodedObject(val->ele)) {
|
||||
} else if (val->ele->encoding == REDIS_ENCODING_RAW) {
|
||||
val->elen = sdslen(val->ele->ptr);
|
||||
val->estr = val->ele->ptr;
|
||||
} else {
|
||||
@@ -1623,10 +1623,9 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
dictAdd(dstzset->dict,tmp,&znode->score);
|
||||
incrRefCount(tmp); /* added to dictionary */
|
||||
|
||||
if (sdsEncodedObject(tmp)) {
|
||||
if (tmp->encoding == REDIS_ENCODING_RAW)
|
||||
if (sdslen(tmp->ptr) > maxelelen)
|
||||
maxelelen = sdslen(tmp->ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
zuiClearIterator(&src[0]);
|
||||
@@ -1670,10 +1669,9 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
dictAdd(dstzset->dict,tmp,&znode->score);
|
||||
incrRefCount(zval.ele); /* added to dictionary */
|
||||
|
||||
if (sdsEncodedObject(tmp)) {
|
||||
if (tmp->encoding == REDIS_ENCODING_RAW)
|
||||
if (sdslen(tmp->ptr) > maxelelen)
|
||||
maxelelen = sdslen(tmp->ptr);
|
||||
}
|
||||
}
|
||||
zuiClearIterator(&src[i]);
|
||||
}
|
||||
@@ -2149,8 +2147,7 @@ void zrankGenericCommand(redisClient *c, int reverse) {
|
||||
checkType(c,zobj,REDIS_ZSET)) return;
|
||||
llen = zsetLength(zobj);
|
||||
|
||||
redisAssertWithInfo(c,ele,sdsEncodedObject(ele));
|
||||
|
||||
redisAssertWithInfo(c,ele,ele->encoding == REDIS_ENCODING_RAW);
|
||||
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = zobj->ptr;
|
||||
unsigned char *eptr, *sptr;
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.9.11"
|
||||
#define REDIS_VERSION "2.8.3"
|
||||
|
||||
+2
-2
@@ -321,8 +321,8 @@ size_t zmalloc_get_rss(void) {
|
||||
#endif
|
||||
|
||||
/* Fragmentation = RSS / allocated-bytes */
|
||||
float zmalloc_get_fragmentation_ratio(size_t rss) {
|
||||
return (float)rss/zmalloc_used_memory();
|
||||
float zmalloc_get_fragmentation_ratio(void) {
|
||||
return (float)zmalloc_get_rss()/zmalloc_used_memory();
|
||||
}
|
||||
|
||||
#if defined(HAVE_PROC_SMAPS)
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ char *zstrdup(const char *s);
|
||||
size_t zmalloc_used_memory(void);
|
||||
void zmalloc_enable_thread_safeness(void);
|
||||
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
|
||||
float zmalloc_get_fragmentation_ratio(size_t rss);
|
||||
float zmalloc_get_fragmentation_ratio(void);
|
||||
size_t zmalloc_get_rss(void);
|
||||
size_t zmalloc_get_private_dirty(void);
|
||||
void zlibc_free(void *ptr);
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../sentinel-tests/includes/init-tests.tcl"
|
||||
|
||||
if {$::simulate_error} {
|
||||
test "This test will fail" {
|
||||
fail "Simulated error"
|
||||
}
|
||||
}
|
||||
|
||||
test "Basic failover works if the master is down" {
|
||||
set old_port [RI $master_id tcp_port]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
kill_instance redis $master_id
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
|
||||
}
|
||||
|
||||
test "New master [join $addr {:}] role matches" {
|
||||
assert {[RI $master_id role] eq {master}}
|
||||
}
|
||||
|
||||
test "All the other slaves now point to the new master" {
|
||||
foreach_redis_id id {
|
||||
if {$id != $master_id && $id != 0} {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI $id master_port] == [lindex $addr 1]
|
||||
} else {
|
||||
fail "Redis ID $id not configured to replicate with new master"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "The old master eventually gets reconfigured as a slave" {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 0 master_port] == [lindex $addr 1]
|
||||
} else {
|
||||
fail "Old master not reconfigured as slave of new master"
|
||||
}
|
||||
}
|
||||
|
||||
test "ODOWN is not possible without N (quorum) Sentinels reports" {
|
||||
foreach_sentinel_id id {
|
||||
S $id SENTINEL SET mymaster quorum [expr $sentinels+1]
|
||||
}
|
||||
set old_port [RI $master_id tcp_port]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
kill_instance redis $master_id
|
||||
|
||||
# Make sure failover did not happened.
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
restart_instance redis $master_id
|
||||
}
|
||||
|
||||
test "Failover is not possible without majority agreement" {
|
||||
foreach_sentinel_id id {
|
||||
S $id SENTINEL SET mymaster quorum $quorum
|
||||
}
|
||||
|
||||
# Crash majority of sentinels
|
||||
for {set id 0} {$id < $quorum} {incr id} {
|
||||
kill_instance sentinel $id
|
||||
}
|
||||
|
||||
# Kill the current master
|
||||
kill_instance redis $master_id
|
||||
|
||||
# Make sure failover did not happened.
|
||||
set addr [S $quorum SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
restart_instance redis $master_id
|
||||
|
||||
# Cleanup: restart Sentinels to monitor the master.
|
||||
for {set id 0} {$id < $quorum} {incr id} {
|
||||
restart_instance sentinel $id
|
||||
}
|
||||
}
|
||||
|
||||
test "Failover works if we configure for absolute agreement" {
|
||||
foreach_sentinel_id id {
|
||||
S $id SENTINEL SET mymaster quorum $sentinels
|
||||
}
|
||||
|
||||
# Wait for Sentinels to monitor the master again
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[dict get [S $id SENTINEL MASTER mymaster] info-refresh] < 100000
|
||||
} else {
|
||||
fail "At least one Sentinel is not monitoring the master"
|
||||
}
|
||||
}
|
||||
|
||||
kill_instance redis $master_id
|
||||
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
|
||||
|
||||
# Set the min ODOWN agreement back to strict majority.
|
||||
foreach_sentinel_id id {
|
||||
S $id SENTINEL SET mymaster quorum $quorum
|
||||
}
|
||||
}
|
||||
|
||||
test "New master [join $addr {:}] role matches" {
|
||||
assert {[RI $master_id role] eq {master}}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
# Test Sentinel configuration consistency after partitions heal.
|
||||
|
||||
source "../sentinel-tests/includes/init-tests.tcl"
|
||||
|
||||
test "We can failover with Sentinel 1 crashed" {
|
||||
set old_port [RI $master_id tcp_port]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
|
||||
# Crash Sentinel 1
|
||||
kill_instance sentinel 1
|
||||
|
||||
kill_instance redis $master_id
|
||||
foreach_sentinel_id id {
|
||||
if {$id != 1} {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "Sentinel $id did not received failover info"
|
||||
}
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
|
||||
}
|
||||
|
||||
test "After Sentinel 1 is restarted, its config gets updated" {
|
||||
restart_instance sentinel 1
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S 1 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "Restarted Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
|
||||
test "New master [join $addr {:}] role matches" {
|
||||
assert {[RI $master_id role] eq {master}}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
# Check that slaves are reconfigured at a latter time if they are partitioned.
|
||||
#
|
||||
# Here we should test:
|
||||
# 1) That slaves point to the new master after failover.
|
||||
# 2) That partitioned slaves point to new master when they are partitioned
|
||||
# away during failover and return at a latter time.
|
||||
|
||||
source "../sentinel-tests/includes/init-tests.tcl"
|
||||
|
||||
proc 03_test_slaves_replication {} {
|
||||
uplevel 1 {
|
||||
test "Check that slaves replicate from current master" {
|
||||
set master_port [RI $master_id tcp_port]
|
||||
foreach_redis_id id {
|
||||
if {$id == $master_id} continue
|
||||
if {[instance_is_killed redis $id]} continue
|
||||
wait_for_condition 1000 50 {
|
||||
[RI $id master_port] == $master_port
|
||||
} else {
|
||||
fail "Redis slave $id is replicating from wrong master"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc 03_crash_and_failover {} {
|
||||
uplevel 1 {
|
||||
test "Crash the master and force a failover" {
|
||||
set old_port [RI $master_id tcp_port]
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
assert {[lindex $addr 1] == $old_port}
|
||||
kill_instance redis $master_id
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
|
||||
} else {
|
||||
fail "At least one Sentinel did not received failover info"
|
||||
}
|
||||
}
|
||||
restart_instance redis $master_id
|
||||
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
|
||||
set master_id [get_instance_id_by_port redis [lindex $addr 1]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
03_test_slaves_replication
|
||||
03_crash_and_failover
|
||||
03_test_slaves_replication
|
||||
|
||||
test "Kill a slave instance" {
|
||||
foreach_redis_id id {
|
||||
if {$id == $master_id} continue
|
||||
set killed_slave_id $id
|
||||
kill_instance redis $id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
03_crash_and_failover
|
||||
03_test_slaves_replication
|
||||
|
||||
test "Wait for failover to end" {
|
||||
set inprogress 1
|
||||
while {$inprogress} {
|
||||
set inprogress 0
|
||||
foreach_sentinel_id id {
|
||||
if {[dict exists [S $id SENTINEL MASTER mymaster] failover-state]} {
|
||||
incr inprogress
|
||||
}
|
||||
}
|
||||
if {$inprogress} {after 100}
|
||||
}
|
||||
}
|
||||
|
||||
test "Restart killed slave and test replication of slaves again..." {
|
||||
restart_instance redis $killed_slave_id
|
||||
}
|
||||
|
||||
# Now we check if the slave rejoining the partition is reconfigured even
|
||||
# if the failover finished.
|
||||
03_test_slaves_replication
|
||||
@@ -1 +0,0 @@
|
||||
# Test runtime reconfiguration command SENTINEL SET.
|
||||
@@ -1,5 +0,0 @@
|
||||
# Test slave selection algorithm.
|
||||
#
|
||||
# This unit should test:
|
||||
# 1) That when there are no suitable slaves no failover is performed.
|
||||
# 2) That among the available slaves, the one with better offset is picked.
|
||||
@@ -1,67 +0,0 @@
|
||||
# Initialization tests -- most units will start including this.
|
||||
|
||||
test "(init) Restart killed instances" {
|
||||
foreach type {redis sentinel} {
|
||||
foreach_${type}_id id {
|
||||
if {[get_instance_attrib $type $id pid] == -1} {
|
||||
puts -nonewline "$type/$id "
|
||||
flush stdout
|
||||
restart_instance $type $id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set redis_slaves 4
|
||||
test "(init) Create a master-slaves cluster of [expr $redis_slaves+1] instances" {
|
||||
create_redis_master_slave_cluster [expr {$redis_slaves+1}]
|
||||
}
|
||||
set master_id 0
|
||||
|
||||
test "(init) Sentinels can start monitoring a master" {
|
||||
set sentinels [llength $::sentinel_instances]
|
||||
set quorum [expr {$sentinels/2+1}]
|
||||
foreach_sentinel_id id {
|
||||
catch {S $id SENTINEL REMOVE mymaster}
|
||||
S $id SENTINEL MONITOR mymaster \
|
||||
[get_instance_attrib redis $master_id host] \
|
||||
[get_instance_attrib redis $master_id port] $quorum
|
||||
}
|
||||
foreach_sentinel_id id {
|
||||
assert {[S $id sentinel master mymaster] ne {}}
|
||||
S $id SENTINEL SET mymaster down-after-milliseconds 2000
|
||||
S $id SENTINEL SET mymaster failover-timeout 20000
|
||||
S $id SENTINEL SET mymaster parallel-syncs 10
|
||||
}
|
||||
}
|
||||
|
||||
test "(init) Sentinels can talk with the master" {
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 100 50 {
|
||||
[catch {S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster}] == 0
|
||||
} else {
|
||||
fail "Sentinel $id can't talk with the master."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "(init) Sentinels are able to auto-discover other sentinels" {
|
||||
set sentinels [llength $::sentinel_instances]
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 100 50 {
|
||||
[dict get [S $id SENTINEL MASTER mymaster] num-other-sentinels] == ($sentinels-1)
|
||||
} else {
|
||||
fail "At least some sentinel can't detect some other sentinel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "(init) Sentinels are able to auto-discover slaves" {
|
||||
foreach_sentinel_id id {
|
||||
wait_for_condition 100 50 {
|
||||
[dict get [S $id SENTINEL MASTER mymaster] num-slaves] == $redis_slaves
|
||||
} else {
|
||||
fail "At least some sentinel can't detect some slave"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
redis_*
|
||||
sentinel_*
|
||||
@@ -1,396 +0,0 @@
|
||||
# Sentinel test suite. Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
|
||||
# This softare is released under the BSD License. See the COPYING file for
|
||||
# more information.
|
||||
|
||||
package require Tcl 8.5
|
||||
|
||||
set tcl_precision 17
|
||||
source tests/support/redis.tcl
|
||||
source tests/support/util.tcl
|
||||
source tests/support/server.tcl
|
||||
source tests/support/test.tcl
|
||||
|
||||
set ::verbose 0
|
||||
set ::pause_on_error 0
|
||||
set ::simulate_error 0
|
||||
set ::sentinel_instances {}
|
||||
set ::redis_instances {}
|
||||
set ::sentinel_base_port 20000
|
||||
set ::redis_base_port 30000
|
||||
set ::instances_count 5 ; # How many Sentinels / Instances we use at max
|
||||
set ::pids {} ; # We kill everything at exit
|
||||
set ::dirs {} ; # We remove all the temp dirs at exit
|
||||
set ::run_matching {} ; # If non empty, only tests matching pattern are run.
|
||||
|
||||
if {[catch {cd tests/sentinel-tmp}]} {
|
||||
puts "tests/sentinel-tmp directory not found."
|
||||
puts "Please run this test from the Redis source root."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Spawn a redis or sentinel instance, depending on 'type'.
|
||||
proc spawn_instance {type base_port count} {
|
||||
for {set j 0} {$j < $count} {incr j} {
|
||||
set port [find_available_port $base_port]
|
||||
incr base_port
|
||||
puts "Starting $type #$j at port $port"
|
||||
|
||||
# Create a directory for this Sentinel.
|
||||
set dirname "${type}_${j}"
|
||||
lappend ::dirs $dirname
|
||||
catch {exec rm -rf $dirname}
|
||||
file mkdir $dirname
|
||||
|
||||
# Write the Sentinel config file.
|
||||
set cfgfile [file join $dirname $type.conf]
|
||||
set cfg [open $cfgfile w]
|
||||
puts $cfg "port $port"
|
||||
puts $cfg "dir ./$dirname"
|
||||
puts $cfg "logfile log.txt"
|
||||
close $cfg
|
||||
|
||||
# Finally exec it and remember the pid for later cleanup.
|
||||
if {$type eq "redis"} {
|
||||
set prgname redis-server
|
||||
} else {
|
||||
set prgname redis-sentinel
|
||||
}
|
||||
set pid [exec ../../src/${prgname} $cfgfile &]
|
||||
lappend ::pids $pid
|
||||
|
||||
# Check availability
|
||||
if {[server_is_up 127.0.0.1 $port 100] == 0} {
|
||||
abort_sentinel_test "Problems starting $type #$j: ping timeout"
|
||||
}
|
||||
|
||||
# Push the instance into the right list
|
||||
lappend ::${type}_instances [list \
|
||||
pid $pid \
|
||||
host 127.0.0.1 \
|
||||
port $port \
|
||||
link [redis 127.0.0.1 $port] \
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
proc cleanup {} {
|
||||
puts "Cleaning up..."
|
||||
foreach pid $::pids {
|
||||
catch {exec kill -9 $pid}
|
||||
}
|
||||
foreach dir $::dirs {
|
||||
catch {exec rm -rf $dir}
|
||||
}
|
||||
}
|
||||
|
||||
proc abort_sentinel_test msg {
|
||||
puts "WARNING: Aborting the test."
|
||||
puts ">>>>>>>> $msg"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
proc parse_options {} {
|
||||
for {set j 0} {$j < [llength $::argv]} {incr j} {
|
||||
set opt [lindex $::argv $j]
|
||||
set val [lindex $::argv [expr $j+1]]
|
||||
if {$opt eq "--single"} {
|
||||
incr j
|
||||
set ::run_matching "*${val}*"
|
||||
} elseif {$opt eq "--pause-on-error"} {
|
||||
set ::pause_on_error 1
|
||||
} elseif {$opt eq "--fail"} {
|
||||
set ::simulate_error 1
|
||||
} elseif {$opt eq "--help"} {
|
||||
puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests."
|
||||
puts "\nOptions:"
|
||||
puts "--single <pattern> Only runs tests specified by pattern."
|
||||
puts "--pause-on-error Pause for manual inspection on error."
|
||||
puts "--fail Simulate a test failure."
|
||||
puts "--help Shows this help."
|
||||
exit 0
|
||||
} else {
|
||||
puts "Unknown option $opt"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc main {} {
|
||||
parse_options
|
||||
spawn_instance sentinel $::sentinel_base_port $::instances_count
|
||||
spawn_instance redis $::redis_base_port $::instances_count
|
||||
run_tests
|
||||
cleanup
|
||||
}
|
||||
|
||||
# If --pause-on-error option was passed at startup this function is called
|
||||
# on error in order to give the developer a chance to understand more about
|
||||
# the error condition while the instances are still running.
|
||||
proc pause_on_error {} {
|
||||
puts ""
|
||||
puts [colorstr yellow "*** Please inspect the error now ***"]
|
||||
puts "\nType \"continue\" to resume the test, \"help\" for help screen.\n"
|
||||
while 1 {
|
||||
puts -nonewline "> "
|
||||
flush stdout
|
||||
set line [gets stdin]
|
||||
set argv [split $line " "]
|
||||
set cmd [lindex $argv 0]
|
||||
if {$cmd eq {continue}} {
|
||||
break
|
||||
} elseif {$cmd eq {show-sentinel-logs}} {
|
||||
set count 10
|
||||
if {[lindex $argv 1] ne {}} {set count [lindex $argv 1]}
|
||||
foreach_sentinel_id id {
|
||||
puts "=== SENTINEL $id ===="
|
||||
puts [exec tail -$count sentinel_$id/log.txt]
|
||||
puts "---------------------\n"
|
||||
}
|
||||
} elseif {$cmd eq {ls}} {
|
||||
foreach_redis_id id {
|
||||
puts -nonewline "Redis $id"
|
||||
set errcode [catch {
|
||||
set str {}
|
||||
append str "@[RI $id tcp_port]: "
|
||||
append str "[RI $id role] "
|
||||
if {[RI $id role] eq {slave}} {
|
||||
append str "[RI $id master_host]:[RI $id master_port]"
|
||||
}
|
||||
set str
|
||||
} retval]
|
||||
if {$errcode} {
|
||||
puts " -- $retval"
|
||||
} else {
|
||||
puts $retval
|
||||
}
|
||||
}
|
||||
foreach_sentinel_id id {
|
||||
puts -nonewline "Sentinel $id"
|
||||
set errcode [catch {
|
||||
set str {}
|
||||
append str "@[SI $id tcp_port]: "
|
||||
append str "[join [S $id sentinel get-master-addr-by-name mymaster]]"
|
||||
set str
|
||||
} retval]
|
||||
if {$errcode} {
|
||||
puts " -- $retval"
|
||||
} else {
|
||||
puts $retval
|
||||
}
|
||||
}
|
||||
} elseif {$cmd eq {help}} {
|
||||
puts "ls List Sentinel and Redis instances."
|
||||
puts "show-sentinel-logs \[N\] Show latest N lines of logs."
|
||||
puts "S <id> cmd ... arg Call command in Sentinel <id>."
|
||||
puts "R <id> cmd ... arg Call command in Redis <id>."
|
||||
puts "SI <id> <field> Show Sentinel <id> INFO <field>."
|
||||
puts "RI <id> <field> Show Sentinel <id> INFO <field>."
|
||||
puts "continue Resume test."
|
||||
} else {
|
||||
set errcode [catch {eval $line} retval]
|
||||
if {$retval ne {}} {puts "$retval"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# We redefine 'test' as for Sentinel we don't use the server-client
|
||||
# architecture for the test, everything is sequential.
|
||||
proc test {descr code} {
|
||||
set ts [clock format [clock seconds] -format %H:%M:%S]
|
||||
puts -nonewline "$ts> $descr: "
|
||||
flush stdout
|
||||
|
||||
if {[catch {set retval [uplevel 1 $code]} error]} {
|
||||
if {[string match "assertion:*" $error]} {
|
||||
set msg [string range $error 10 end]
|
||||
puts [colorstr red $msg]
|
||||
if {$::pause_on_error} pause_on_error
|
||||
puts "(Jumping to next unit after error)"
|
||||
return -code continue
|
||||
} else {
|
||||
# Re-raise, let handler up the stack take care of this.
|
||||
error $error $::errorInfo
|
||||
}
|
||||
} else {
|
||||
puts [colorstr green OK]
|
||||
}
|
||||
}
|
||||
|
||||
proc run_tests {} {
|
||||
set tests [lsort [glob ../sentinel-tests/*]]
|
||||
foreach test $tests {
|
||||
if {$::run_matching ne {} && [string match $::run_matching $test] == 0} {
|
||||
continue
|
||||
}
|
||||
if {[file isdirectory $test]} continue
|
||||
puts [colorstr yellow "Testing unit: [lindex [file split $test] end]"]
|
||||
source $test
|
||||
}
|
||||
}
|
||||
|
||||
# The "S" command is used to interact with the N-th Sentinel.
|
||||
# The general form is:
|
||||
#
|
||||
# S <sentinel-id> command arg arg arg ...
|
||||
#
|
||||
# Example to ping the Sentinel 0 (first instance): S 0 PING
|
||||
proc S {n args} {
|
||||
set s [lindex $::sentinel_instances $n]
|
||||
[dict get $s link] {*}$args
|
||||
}
|
||||
|
||||
# Like R but to chat with Redis instances.
|
||||
proc R {n args} {
|
||||
set r [lindex $::redis_instances $n]
|
||||
[dict get $r link] {*}$args
|
||||
}
|
||||
|
||||
proc get_info_field {info field} {
|
||||
set fl [string length $field]
|
||||
append field :
|
||||
foreach line [split $info "\n"] {
|
||||
set line [string trim $line "\r\n "]
|
||||
if {[string range $line 0 $fl] eq $field} {
|
||||
return [string range $line [expr {$fl+1}] end]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
proc SI {n field} {
|
||||
get_info_field [S $n info] $field
|
||||
}
|
||||
|
||||
proc RI {n field} {
|
||||
get_info_field [R $n info] $field
|
||||
}
|
||||
|
||||
# Iterate over IDs of sentinel or redis instances.
|
||||
proc foreach_instance_id {instances idvar code} {
|
||||
upvar 1 $idvar id
|
||||
for {set id 0} {$id < [llength $instances]} {incr id} {
|
||||
set errcode [catch {uplevel 1 $code} result]
|
||||
if {$errcode == 1} {
|
||||
error $result $::errorInfo $::errorCode
|
||||
} elseif {$errcode == 4} {
|
||||
continue
|
||||
} elseif {$errcode == 3} {
|
||||
break
|
||||
} elseif {$errcode != 0} {
|
||||
return -code $errcode $result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc foreach_sentinel_id {idvar code} {
|
||||
set errcode [catch {uplevel 1 [list foreach_instance_id $::sentinel_instances $idvar $code]} result]
|
||||
return -code $errcode $result
|
||||
}
|
||||
|
||||
proc foreach_redis_id {idvar code} {
|
||||
set errcode [catch {uplevel 1 [list foreach_instance_id $::redis_instances $idvar $code]} result]
|
||||
return -code $errcode $result
|
||||
}
|
||||
|
||||
# Get the specific attribute of the specified instance type, id.
|
||||
proc get_instance_attrib {type id attrib} {
|
||||
dict get [lindex [set ::${type}_instances] $id] $attrib
|
||||
}
|
||||
|
||||
# Set the specific attribute of the specified instance type, id.
|
||||
proc set_instance_attrib {type id attrib newval} {
|
||||
set d [lindex [set ::${type}_instances] $id]
|
||||
dict set d $attrib $newval
|
||||
lset ::${type}_instances $id $d
|
||||
}
|
||||
|
||||
# Create a master-slave cluster of the given number of total instances.
|
||||
# The first instance "0" is the master, all others are configured as
|
||||
# slaves.
|
||||
proc create_redis_master_slave_cluster n {
|
||||
foreach_redis_id id {
|
||||
if {$id == 0} {
|
||||
# Our master.
|
||||
R $id slaveof no one
|
||||
R $id flushall
|
||||
} elseif {$id < $n} {
|
||||
R $id slaveof [get_instance_attrib redis 0 host] \
|
||||
[get_instance_attrib redis 0 port]
|
||||
} else {
|
||||
# Instances not part of the cluster.
|
||||
R $id slaveof no one
|
||||
}
|
||||
}
|
||||
# Wait for all the slaves to sync.
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 0 connected_slaves] == ($n-1)
|
||||
} else {
|
||||
fail "Unable to create a master-slaves cluster."
|
||||
}
|
||||
}
|
||||
|
||||
proc get_instance_id_by_port {type port} {
|
||||
foreach_${type}_id id {
|
||||
if {[get_instance_attrib $type $id port] == $port} {
|
||||
return $id
|
||||
}
|
||||
}
|
||||
fail "Instance $type port $port not found."
|
||||
}
|
||||
|
||||
# Kill an instance of the specified type/id with SIGKILL.
|
||||
# This function will mark the instance PID as -1 to remember that this instance
|
||||
# is no longer running and will remove its PID from the list of pids that
|
||||
# we kill at cleanup.
|
||||
#
|
||||
# The instance can be restarted with restart-instance.
|
||||
proc kill_instance {type id} {
|
||||
set pid [get_instance_attrib $type $id pid]
|
||||
if {$pid == -1} {
|
||||
error "You tried to kill $type $id twice."
|
||||
}
|
||||
exec kill -9 $pid
|
||||
set_instance_attrib $type $id pid -1
|
||||
set_instance_attrib $type $id link you_tried_to_talk_with_killed_instance
|
||||
|
||||
# Remove the PID from the list of pids to kill at exit.
|
||||
set ::pids [lsearch -all -inline -not -exact $::pids $pid]
|
||||
}
|
||||
|
||||
# Return true of the instance of the specified type/id is killed.
|
||||
proc instance_is_killed {type id} {
|
||||
set pid [get_instance_attrib $type $id pid]
|
||||
return $pid == -1
|
||||
}
|
||||
|
||||
# Restart an instance previously killed by kill_instance
|
||||
proc restart_instance {type id} {
|
||||
set dirname "${type}_${id}"
|
||||
set cfgfile [file join $dirname $type.conf]
|
||||
set port [get_instance_attrib $type $id port]
|
||||
|
||||
# Execute the instance with its old setup and append the new pid
|
||||
# file for cleanup.
|
||||
if {$type eq "redis"} {
|
||||
set prgname redis-server
|
||||
} else {
|
||||
set prgname redis-sentinel
|
||||
}
|
||||
set pid [exec ../../src/${prgname} $cfgfile &]
|
||||
set_instance_attrib $type $id pid $pid
|
||||
lappend ::pids $pid
|
||||
|
||||
# Check that the instance is running
|
||||
if {[server_is_up 127.0.0.1 $port 100] == 0} {
|
||||
abort_sentinel_test "Problems starting $type #$j: ping timeout"
|
||||
}
|
||||
|
||||
# Connect with it with a fresh link
|
||||
set_instance_attrib $type $id link [redis 127.0.0.1 $port]
|
||||
}
|
||||
|
||||
if {[catch main e]} {
|
||||
puts $::errorInfo
|
||||
cleanup
|
||||
}
|
||||
+12
-22
@@ -40,10 +40,6 @@ proc kill_server config {
|
||||
test "Check for memory leaks (pid $pid)" {
|
||||
set output {0 leaks}
|
||||
catch {exec leaks $pid} output
|
||||
if {[string match {*process does not exist*} $output]} {
|
||||
# In a few tests we kill the server process.
|
||||
set output "0 leaks"
|
||||
}
|
||||
set output
|
||||
} {*0 leaks*}
|
||||
}
|
||||
@@ -83,7 +79,7 @@ proc is_alive config {
|
||||
proc ping_server {host port} {
|
||||
set retval 0
|
||||
if {[catch {
|
||||
set fd [socket $host $port]
|
||||
set fd [socket $::host $::port]
|
||||
fconfigure $fd -translation binary
|
||||
puts $fd "PING\r\n"
|
||||
flush $fd
|
||||
@@ -105,22 +101,6 @@ proc ping_server {host port} {
|
||||
return $retval
|
||||
}
|
||||
|
||||
# Return 1 if the server at the specified addr is reachable by PING, otherwise
|
||||
# returns 0. Performs a try every 50 milliseconds for the specified number
|
||||
# of retries.
|
||||
proc server_is_up {host port retrynum} {
|
||||
after 10 ;# Use a small delay to make likely a first-try success.
|
||||
set retval 0
|
||||
while {[incr retrynum -1]} {
|
||||
if {[catch {ping_server $host $port} ping]} {
|
||||
set ping 0
|
||||
}
|
||||
if {$ping} {return 1}
|
||||
after 50
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
# doesn't really belong here, but highly coupled to code in start_server
|
||||
proc tags {tags code} {
|
||||
set ::tags [concat $::tags $tags]
|
||||
@@ -211,13 +191,23 @@ proc start_server {options {code undefined}} {
|
||||
# check that the server actually started
|
||||
# ugly but tries to be as fast as possible...
|
||||
if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
|
||||
set serverisup 0
|
||||
|
||||
if {$::verbose} {
|
||||
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
|
||||
}
|
||||
|
||||
after 10
|
||||
if {$code ne "undefined"} {
|
||||
set serverisup [server_is_up $::host $::port $retrynum]
|
||||
while {[incr retrynum -1]} {
|
||||
catch {
|
||||
if {[ping_server $::host $::port]} {
|
||||
set serverisup 1
|
||||
}
|
||||
}
|
||||
if {$serverisup} break
|
||||
after 50
|
||||
}
|
||||
} else {
|
||||
set serverisup 1
|
||||
}
|
||||
|
||||
+32
-8
@@ -53,17 +53,41 @@ proc assert_type {type key} {
|
||||
# executed.
|
||||
proc wait_for_condition {maxtries delay e _else_ elsescript} {
|
||||
while {[incr maxtries -1] >= 0} {
|
||||
set errcode [catch {uplevel 1 [list expr $e]} result]
|
||||
if {$errcode == 0} {
|
||||
if {$result} break
|
||||
} else {
|
||||
return -code $errcode $result
|
||||
}
|
||||
if {[uplevel 1 [list expr $e]]} break
|
||||
after $delay
|
||||
}
|
||||
if {$maxtries == -1} {
|
||||
set errcode [catch [uplevel 1 $elsescript] result]
|
||||
return -code $errcode $result
|
||||
uplevel 1 $elsescript
|
||||
}
|
||||
}
|
||||
|
||||
# Test if TERM looks like to support colors
|
||||
proc color_term {} {
|
||||
expr {[info exists ::env(TERM)] && [string match *xterm* $::env(TERM)]}
|
||||
}
|
||||
|
||||
proc colorstr {color str} {
|
||||
if {[color_term]} {
|
||||
set b 0
|
||||
if {[string range $color 0 4] eq {bold-}} {
|
||||
set b 1
|
||||
set color [string range $color 5 end]
|
||||
}
|
||||
switch $color {
|
||||
red {set colorcode {31}}
|
||||
green {set colorcode {32}}
|
||||
yellow {set colorcode {33}}
|
||||
blue {set colorcode {34}}
|
||||
magenta {set colorcode {35}}
|
||||
cyan {set colorcode {36}}
|
||||
white {set colorcode {37}}
|
||||
default {set colorcode {37}}
|
||||
}
|
||||
if {$colorcode ne {}} {
|
||||
return "\033\[$b;${colorcode};40m$str\033\[0m"
|
||||
}
|
||||
} else {
|
||||
return $str
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -312,48 +312,3 @@ proc csvstring s {
|
||||
proc roundFloat f {
|
||||
format "%.10g" $f
|
||||
}
|
||||
|
||||
proc find_available_port start {
|
||||
for {set j $start} {$j < $start+1024} {incr j} {
|
||||
if {[catch {
|
||||
set fd [socket 127.0.0.1 $j]
|
||||
}]} {
|
||||
return $j
|
||||
} else {
|
||||
close $fd
|
||||
}
|
||||
}
|
||||
if {$j == $start+1024} {
|
||||
error "Can't find a non busy port in the $start-[expr {$start+1023}] range."
|
||||
}
|
||||
}
|
||||
|
||||
# Test if TERM looks like to support colors
|
||||
proc color_term {} {
|
||||
expr {[info exists ::env(TERM)] && [string match *xterm* $::env(TERM)]}
|
||||
}
|
||||
|
||||
proc colorstr {color str} {
|
||||
if {[color_term]} {
|
||||
set b 0
|
||||
if {[string range $color 0 4] eq {bold-}} {
|
||||
set b 1
|
||||
set color [string range $color 5 end]
|
||||
}
|
||||
switch $color {
|
||||
red {set colorcode {31}}
|
||||
green {set colorcode {32}}
|
||||
yellow {set colorcode {33}}
|
||||
blue {set colorcode {34}}
|
||||
magenta {set colorcode {35}}
|
||||
cyan {set colorcode {36}}
|
||||
white {set colorcode {37}}
|
||||
default {set colorcode {37}}
|
||||
}
|
||||
if {$colorcode ne {}} {
|
||||
return "\033\[$b;${colorcode};40m$str\033\[0m"
|
||||
}
|
||||
} else {
|
||||
return $str
|
||||
}
|
||||
}
|
||||
|
||||
+17
-25
@@ -13,7 +13,6 @@ source tests/support/util.tcl
|
||||
|
||||
set ::all_tests {
|
||||
unit/printver
|
||||
unit/dump
|
||||
unit/auth
|
||||
unit/protocol
|
||||
unit/basic
|
||||
@@ -45,9 +44,9 @@ set ::all_tests {
|
||||
unit/introspection
|
||||
unit/limits
|
||||
unit/obuf-limits
|
||||
unit/dump
|
||||
unit/bitops
|
||||
unit/memefficiency
|
||||
unit/hyperloglog
|
||||
}
|
||||
# Index to the next test to run in the ::all_tests list.
|
||||
set ::next_test 0
|
||||
@@ -165,6 +164,21 @@ proc cleanup {} {
|
||||
if {!$::quiet} {puts "OK"}
|
||||
}
|
||||
|
||||
proc find_available_port start {
|
||||
for {set j $start} {$j < $start+1024} {incr j} {
|
||||
if {[catch {
|
||||
set fd [socket 127.0.0.1 $j]
|
||||
}]} {
|
||||
return $j
|
||||
} else {
|
||||
close $fd
|
||||
}
|
||||
}
|
||||
if {$j == $start+1024} {
|
||||
error "Can't find a non busy port in the $start-[expr {$start+1023}] range."
|
||||
}
|
||||
}
|
||||
|
||||
proc test_server_main {} {
|
||||
cleanup
|
||||
set tclsh [info nameofexecutable]
|
||||
@@ -190,7 +204,6 @@ proc test_server_main {} {
|
||||
# Setup global state for the test server
|
||||
set ::idle_clients {}
|
||||
set ::active_clients {}
|
||||
array set ::active_clients_task {}
|
||||
array set ::clients_start_time {}
|
||||
set ::clients_time_history {}
|
||||
set ::failed_tests {}
|
||||
@@ -204,12 +217,9 @@ proc test_server_main {} {
|
||||
# may be used in the future in order to detect test clients taking too much
|
||||
# time to execute the task.
|
||||
proc test_server_cron {} {
|
||||
# Do some work here.
|
||||
after 100 test_server_cron
|
||||
}
|
||||
|
||||
proc accept_test_clients {fd addr port} {
|
||||
fconfigure $fd -encoding binary
|
||||
fileevent $fd readable [list read_from_test_client $fd]
|
||||
}
|
||||
|
||||
@@ -243,17 +253,14 @@ proc read_from_test_client fd {
|
||||
puts "\[$completed_tests_count/$all_tests_count [colorstr yellow $status]\]: $data ($elapsed seconds)"
|
||||
lappend ::clients_time_history $elapsed $data
|
||||
signal_idle_client $fd
|
||||
set ::active_clients_task($fd) DONE
|
||||
} elseif {$status eq {ok}} {
|
||||
if {!$::quiet} {
|
||||
puts "\[[colorstr green $status]\]: $data"
|
||||
}
|
||||
set ::active_clients_task($fd) "(OK) $data"
|
||||
} elseif {$status eq {err}} {
|
||||
set err "\[[colorstr red $status]\]: $data"
|
||||
puts $err
|
||||
lappend ::failed_tests $err
|
||||
set ::active_clients_task($fd) "(ERR) $data"
|
||||
} elseif {$status eq {exception}} {
|
||||
puts "\[[colorstr red $status]\]: $data"
|
||||
foreach p $::clients_pids {
|
||||
@@ -261,7 +268,7 @@ proc read_from_test_client fd {
|
||||
}
|
||||
exit 1
|
||||
} elseif {$status eq {testing}} {
|
||||
set ::active_clients_task($fd) "(IN PROGRESS) $data"
|
||||
# No op
|
||||
} else {
|
||||
if {!$::quiet} {
|
||||
puts "\[$status\]: $data"
|
||||
@@ -275,24 +282,10 @@ proc signal_idle_client fd {
|
||||
# Remove this fd from the list of active clients.
|
||||
set ::active_clients \
|
||||
[lsearch -all -inline -not -exact $::active_clients $fd]
|
||||
|
||||
if 0 {
|
||||
# The following loop is only useful for debugging tests that may
|
||||
# enter an infinite loop. Commented out normally.
|
||||
foreach x $::active_clients {
|
||||
if {[info exist ::active_clients_task($x)]} {
|
||||
puts "$x => $::active_clients_task($x)"
|
||||
} else {
|
||||
puts "$x => ???"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# New unit to process?
|
||||
if {$::next_test != [llength $::all_tests]} {
|
||||
if {!$::quiet} {
|
||||
puts [colorstr bold-white "Testing [lindex $::all_tests $::next_test]"]
|
||||
set ::active_clients_task($fd) "ASSIGNED: $fd ([lindex $::all_tests $::next_test])"
|
||||
}
|
||||
set ::clients_start_time($fd) [clock seconds]
|
||||
send_data_packet $fd run [lindex $::all_tests $::next_test]
|
||||
@@ -333,7 +326,6 @@ proc the_end {} {
|
||||
# to read the command, execute, reply... all this in a loop.
|
||||
proc test_client_main server_port {
|
||||
set ::test_server_fd [socket localhost $server_port]
|
||||
fconfigure $::test_server_fd -encoding binary
|
||||
send_data_packet $::test_server_fd ready [pid]
|
||||
while 1 {
|
||||
set bytes [gets $::test_server_fd]
|
||||
|
||||
+1
-162
@@ -52,7 +52,7 @@ start_server {tags {"bitops"}} {
|
||||
}
|
||||
}
|
||||
|
||||
test {BITCOUNT fuzzing without start/end} {
|
||||
test {BITCOUNT fuzzing} {
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
set str [randstring 0 3000]
|
||||
r set str $str
|
||||
@@ -60,20 +60,6 @@ start_server {tags {"bitops"}} {
|
||||
}
|
||||
}
|
||||
|
||||
test {BITCOUNT fuzzing with start/end} {
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
set str [randstring 0 3000]
|
||||
r set str $str
|
||||
set l [string length $str]
|
||||
set start [randomInt $l]
|
||||
set end [randomInt $l]
|
||||
if {$start > $end} {
|
||||
lassign [list $end $start] start end
|
||||
}
|
||||
assert {[r bitcount str $start $end] == [count_bits [string range $str $start $end]]}
|
||||
}
|
||||
}
|
||||
|
||||
test {BITCOUNT with start, end} {
|
||||
r set s "foobar"
|
||||
assert_equal [r bitcount s 0 -1] [count_bits "foobar"]
|
||||
@@ -98,18 +84,6 @@ start_server {tags {"bitops"}} {
|
||||
}
|
||||
} {1}
|
||||
|
||||
test {BITCOUNT misaligned prefix} {
|
||||
r del str
|
||||
r set str ab
|
||||
r bitcount str 1 -1
|
||||
} {3}
|
||||
|
||||
test {BITCOUNT misaligned prefix + full words + remainder} {
|
||||
r del str
|
||||
r set str __PPxxxxxxxxxxxxxxxxRR__
|
||||
r bitcount str 2 -3
|
||||
} {74}
|
||||
|
||||
test {BITOP NOT (empty string)} {
|
||||
r set s ""
|
||||
r bitop not dest s
|
||||
@@ -203,139 +177,4 @@ start_server {tags {"bitops"}} {
|
||||
r set a "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
r bitop or x a b
|
||||
} {32}
|
||||
|
||||
test {BITPOS bit=0 with empty key returns 0} {
|
||||
r del str
|
||||
r bitpos str 0
|
||||
} {0}
|
||||
|
||||
test {BITPOS bit=1 with empty key returns -1} {
|
||||
r del str
|
||||
r bitpos str 1
|
||||
} {-1}
|
||||
|
||||
test {BITPOS bit=0 with string less than 1 word works} {
|
||||
r set str "\xff\xf0\x00"
|
||||
r bitpos str 0
|
||||
} {12}
|
||||
|
||||
test {BITPOS bit=1 with string less than 1 word works} {
|
||||
r set str "\x00\x0f\x00"
|
||||
r bitpos str 1
|
||||
} {12}
|
||||
|
||||
test {BITPOS bit=0 starting at unaligned address} {
|
||||
r set str "\xff\xf0\x00"
|
||||
r bitpos str 0 1
|
||||
} {12}
|
||||
|
||||
test {BITPOS bit=1 starting at unaligned address} {
|
||||
r set str "\x00\x0f\xff"
|
||||
r bitpos str 1 1
|
||||
} {12}
|
||||
|
||||
test {BITPOS bit=0 unaligned+full word+reminder} {
|
||||
r del str
|
||||
r set str "\xff\xff\xff" ; # Prefix
|
||||
# Followed by two (or four in 32 bit systems) full words
|
||||
r append str "\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
r append str "\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
r append str "\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
# First zero bit.
|
||||
r append str "\x0f"
|
||||
assert {[r bitpos str 0] == 216}
|
||||
assert {[r bitpos str 0 1] == 216}
|
||||
assert {[r bitpos str 0 2] == 216}
|
||||
assert {[r bitpos str 0 3] == 216}
|
||||
assert {[r bitpos str 0 4] == 216}
|
||||
assert {[r bitpos str 0 5] == 216}
|
||||
assert {[r bitpos str 0 6] == 216}
|
||||
assert {[r bitpos str 0 7] == 216}
|
||||
assert {[r bitpos str 0 8] == 216}
|
||||
}
|
||||
|
||||
test {BITPOS bit=1 unaligned+full word+reminder} {
|
||||
r del str
|
||||
r set str "\x00\x00\x00" ; # Prefix
|
||||
# Followed by two (or four in 32 bit systems) full words
|
||||
r append str "\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
r append str "\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
r append str "\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
# First zero bit.
|
||||
r append str "\xf0"
|
||||
assert {[r bitpos str 1] == 216}
|
||||
assert {[r bitpos str 1 1] == 216}
|
||||
assert {[r bitpos str 1 2] == 216}
|
||||
assert {[r bitpos str 1 3] == 216}
|
||||
assert {[r bitpos str 1 4] == 216}
|
||||
assert {[r bitpos str 1 5] == 216}
|
||||
assert {[r bitpos str 1 6] == 216}
|
||||
assert {[r bitpos str 1 7] == 216}
|
||||
assert {[r bitpos str 1 8] == 216}
|
||||
}
|
||||
|
||||
test {BITPOS bit=1 returns -1 if string is all 0 bits} {
|
||||
r set str ""
|
||||
for {set j 0} {$j < 20} {incr j} {
|
||||
assert {[r bitpos str 1] == -1}
|
||||
r append str "\x00"
|
||||
}
|
||||
}
|
||||
|
||||
test {BITPOS bit=0 works with intervals} {
|
||||
r set str "\x00\xff\x00"
|
||||
assert {[r bitpos str 0 0 -1] == 0}
|
||||
assert {[r bitpos str 0 1 -1] == 16}
|
||||
assert {[r bitpos str 0 2 -1] == 16}
|
||||
assert {[r bitpos str 0 2 200] == 16}
|
||||
assert {[r bitpos str 0 1 1] == -1}
|
||||
}
|
||||
|
||||
test {BITPOS bit=1 works with intervals} {
|
||||
r set str "\x00\xff\x00"
|
||||
assert {[r bitpos str 1 0 -1] == 8}
|
||||
assert {[r bitpos str 1 1 -1] == 8}
|
||||
assert {[r bitpos str 1 2 -1] == -1}
|
||||
assert {[r bitpos str 1 2 200] == -1}
|
||||
assert {[r bitpos str 1 1 1] == 8}
|
||||
}
|
||||
|
||||
test {BITPOS bit=0 changes behavior if end is given} {
|
||||
r set str "\xff\xff\xff"
|
||||
assert {[r bitpos str 0] == 24}
|
||||
assert {[r bitpos str 0 0] == 24}
|
||||
assert {[r bitpos str 0 0 -1] == -1}
|
||||
}
|
||||
|
||||
test {BITPOS bit=1 fuzzy testing using SETBIT} {
|
||||
r del str
|
||||
set max 524288; # 64k
|
||||
set first_one_pos -1
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
assert {[r bitpos str 1] == $first_one_pos}
|
||||
set pos [randomInt $max]
|
||||
r setbit str $pos 1
|
||||
if {$first_one_pos == -1 || $first_one_pos > $pos} {
|
||||
# Update the position of the first 1 bit in the array
|
||||
# if the bit we set is on the left of the previous one.
|
||||
set first_one_pos $pos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {BITPOS bit=0 fuzzy testing using SETBIT} {
|
||||
set max 524288; # 64k
|
||||
set first_zero_pos $max
|
||||
r set str [string repeat "\xff" [expr $max/8]]
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
assert {[r bitpos str 0] == $first_zero_pos}
|
||||
set pos [randomInt $max]
|
||||
r setbit str $pos 0
|
||||
if {$first_zero_pos > $pos} {
|
||||
# Update the position of the first 0 bit in the array
|
||||
# if the bit we clear is on the left of the previous one.
|
||||
set first_zero_pos $pos
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-90
@@ -16,16 +16,6 @@ start_server {tags {"dump"}} {
|
||||
r get foo
|
||||
} {bar}
|
||||
|
||||
test {RESTORE can set an expire that overflows a 32 bit integer} {
|
||||
r set foo bar
|
||||
set encoded [r dump foo]
|
||||
r del foo
|
||||
r restore foo 2569591501 $encoded
|
||||
set ttl [r pttl foo]
|
||||
assert {$ttl >= (2569591501-3000) && $ttl <= 2569591501}
|
||||
r get foo
|
||||
} {bar}
|
||||
|
||||
test {RESTORE returns an error of the key already exists} {
|
||||
r set foo bar
|
||||
set e {}
|
||||
@@ -33,47 +23,10 @@ start_server {tags {"dump"}} {
|
||||
set e
|
||||
} {*is busy*}
|
||||
|
||||
test {RESTORE can overwrite an existing key with REPLACE} {
|
||||
r set foo bar1
|
||||
set encoded1 [r dump foo]
|
||||
r set foo bar2
|
||||
set encoded2 [r dump foo]
|
||||
r del foo
|
||||
r restore foo 0 $encoded1
|
||||
r restore foo 0 $encoded2 replace
|
||||
r get foo
|
||||
} {bar2}
|
||||
|
||||
test {RESTORE can detect a syntax error for unrecongized options} {
|
||||
catch {r restore foo 0 "..." invalid-option} e
|
||||
set e
|
||||
} {*syntax*}
|
||||
|
||||
test {DUMP of non existing key returns nil} {
|
||||
r dump nonexisting_key
|
||||
} {}
|
||||
|
||||
test {MIGRATE is caching connections} {
|
||||
# Note, we run this as first test so that the connection cache
|
||||
# is empty.
|
||||
set first [srv 0 client]
|
||||
r set key "Some Value"
|
||||
start_server {tags {"repl"}} {
|
||||
set second [srv 0 client]
|
||||
set second_host [srv 0 host]
|
||||
set second_port [srv 0 port]
|
||||
|
||||
assert_match {*migrate_cached_sockets:0*} [r -1 info]
|
||||
r -1 migrate $second_host $second_port key 9 1000
|
||||
assert_match {*migrate_cached_sockets:1*} [r -1 info]
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE cached connections are released after some time} {
|
||||
after 15000
|
||||
assert_match {*migrate_cached_sockets:0*} [r info]
|
||||
}
|
||||
|
||||
test {MIGRATE is able to migrate a key between two instances} {
|
||||
set first [srv 0 client]
|
||||
r set key "Some Value"
|
||||
@@ -93,47 +46,6 @@ start_server {tags {"dump"}} {
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE is able to copy a key between two instances} {
|
||||
set first [srv 0 client]
|
||||
r del list
|
||||
r lpush list a b c d
|
||||
start_server {tags {"repl"}} {
|
||||
set second [srv 0 client]
|
||||
set second_host [srv 0 host]
|
||||
set second_port [srv 0 port]
|
||||
|
||||
assert {[$first exists list] == 1}
|
||||
assert {[$second exists list] == 0}
|
||||
set ret [r -1 migrate $second_host $second_port list 9 5000 copy]
|
||||
assert {$ret eq {OK}}
|
||||
assert {[$first exists list] == 1}
|
||||
assert {[$second exists list] == 1}
|
||||
assert {[$first lrange list 0 -1] eq [$second lrange list 0 -1]}
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE will not overwrite existing keys, unless REPLACE is used} {
|
||||
set first [srv 0 client]
|
||||
r del list
|
||||
r lpush list a b c d
|
||||
start_server {tags {"repl"}} {
|
||||
set second [srv 0 client]
|
||||
set second_host [srv 0 host]
|
||||
set second_port [srv 0 port]
|
||||
|
||||
assert {[$first exists list] == 1}
|
||||
assert {[$second exists list] == 0}
|
||||
$second set list somevalue
|
||||
catch {r -1 migrate $second_host $second_port list 9 5000 copy} e
|
||||
assert_match {ERR*} $e
|
||||
set res [r -1 migrate $second_host $second_port list 9 5000 copy replace]
|
||||
assert {$ret eq {OK}}
|
||||
assert {[$first exists list] == 1}
|
||||
assert {[$second exists list] == 1}
|
||||
assert {[$first lrange list 0 -1] eq [$second lrange list 0 -1]}
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE propagates TTL correctly} {
|
||||
set first [srv 0 client]
|
||||
r set key "Some Value"
|
||||
@@ -211,9 +123,9 @@ start_server {tags {"dump"}} {
|
||||
assert {[$second exists key] == 0}
|
||||
|
||||
set rd [redis_deferring_client]
|
||||
$rd debug sleep 1.0 ; # Make second server unable to reply.
|
||||
$rd debug sleep 5.0 ; # Make second server unable to reply.
|
||||
set e {}
|
||||
catch {r -1 migrate $second_host $second_port key 9 500} e
|
||||
catch {r -1 migrate $second_host $second_port key 9 1000} e
|
||||
assert_match {IOERR*} $e
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
start_server {tags {"hll"}} {
|
||||
test {HyperLogLog self test passes} {
|
||||
catch {r pfselftest} e
|
||||
set e
|
||||
} {OK}
|
||||
|
||||
test {PFADD without arguments creates an HLL value} {
|
||||
r pfadd hll
|
||||
r exists hll
|
||||
} {1}
|
||||
|
||||
test {Approximated cardinality after creation is zero} {
|
||||
r pfcount hll
|
||||
} {0}
|
||||
|
||||
test {PFADD returns 1 when at least 1 reg was modified} {
|
||||
r pfadd hll a b c
|
||||
} {1}
|
||||
|
||||
test {PFADD returns 0 when no reg was modified} {
|
||||
r pfadd hll a b c
|
||||
} {0}
|
||||
|
||||
test {PFADD works with empty string (regression)} {
|
||||
r pfadd hll ""
|
||||
}
|
||||
|
||||
# Note that the self test stresses much better the
|
||||
# cardinality estimation error. We are testing just the
|
||||
# command implementation itself here.
|
||||
test {PFCOUNT returns approximated cardinality of set} {
|
||||
r del hll
|
||||
set res {}
|
||||
r pfadd hll 1 2 3 4 5
|
||||
lappend res [r pfcount hll]
|
||||
# Call it again to test cached value invalidation.
|
||||
r pfadd hll 6 7 8 8 9 10
|
||||
lappend res [r pfcount hll]
|
||||
set res
|
||||
} {5 10}
|
||||
|
||||
test {PFADD, PFCOUNT, PFMERGE type checking works} {
|
||||
r set foo bar
|
||||
catch {r pfadd foo 1} e
|
||||
assert_match {*WRONGTYPE*} $e
|
||||
catch {r pfcount foo} e
|
||||
assert_match {*WRONGTYPE*} $e
|
||||
catch {r pfmerge bar foo} e
|
||||
assert_match {*WRONGTYPE*} $e
|
||||
catch {r pfmerge foo bar} e
|
||||
assert_match {*WRONGTYPE*} $e
|
||||
}
|
||||
|
||||
test {PFMERGE results on the cardinality of union of sets} {
|
||||
r del hll hll1 hll2 hll3
|
||||
r pfadd hll1 a b c
|
||||
r pfadd hll2 b c d
|
||||
r pfadd hll3 c d e
|
||||
r pfmerge hll hll1 hll2 hll3
|
||||
r pfcount hll
|
||||
} {5}
|
||||
|
||||
test {PFGETREG returns the HyperLogLog raw registers} {
|
||||
r del hll
|
||||
r pfadd hll 1 2 3
|
||||
llength [r pfgetreg hll]
|
||||
} {16384}
|
||||
}
|
||||
@@ -356,15 +356,4 @@ start_server {tags {"pubsub"}} {
|
||||
r config set maxmemory 0
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "Keyspace notifications: test CONFIG GET/SET of event flags" {
|
||||
r config set notify-keyspace-events gKE
|
||||
assert_equal {gKE} [lindex [r config get notify-keyspace-events] 1]
|
||||
r config set notify-keyspace-events {$lshzxeKE}
|
||||
assert_equal {$lshzxeKE} [lindex [r config get notify-keyspace-events] 1]
|
||||
r config set notify-keyspace-events KA
|
||||
assert_equal {AK} [lindex [r config get notify-keyspace-events] 1]
|
||||
r config set notify-keyspace-events EA
|
||||
assert_equal {AE} [lindex [r config get notify-keyspace-events] 1]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,17 +417,5 @@ start_server {tags {"scripting repl"}} {
|
||||
}
|
||||
set res
|
||||
} {a 1}
|
||||
|
||||
test {EVALSHA replication when first call is readonly} {
|
||||
r del x
|
||||
r eval {if tonumber(KEYS[1]) > 0 then redis.call('incr', 'x') end} 1 0
|
||||
r evalsha 38fe3ddf5284a1d48f37f824b4c4e826879f3cb9 1 0
|
||||
r evalsha 38fe3ddf5284a1d48f37f824b4c4e826879f3cb9 1 1
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get x] eq {1}
|
||||
} else {
|
||||
fail "Expected 1 in x, but value is '[r -1 get x]'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,12 +214,6 @@ start_server {
|
||||
r sdiff set1 set2 set3
|
||||
} {}
|
||||
|
||||
test "SDIFF with same set two times" {
|
||||
r del set1
|
||||
r sadd set1 a b c 1 2 3 4 5 6
|
||||
r sdiff set1 set1
|
||||
} {}
|
||||
|
||||
test "SDIFF fuzzing" {
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
unset -nocomplain s
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
*.txt
|
||||
@@ -1,27 +0,0 @@
|
||||
# hll-err.rb - Copyright (C) 2014 Salvatore Sanfilippo
|
||||
# BSD license, See the COPYING file for more information.
|
||||
#
|
||||
# Check error of HyperLogLog Redis implementation for different set sizes.
|
||||
|
||||
require 'rubygems'
|
||||
require 'redis'
|
||||
require 'digest/sha1'
|
||||
|
||||
r = Redis.new
|
||||
r.del('hll')
|
||||
i = 0
|
||||
while true do
|
||||
100.times {
|
||||
elements = []
|
||||
1000.times {
|
||||
ele = Digest::SHA1.hexdigest(i.to_s)
|
||||
elements << ele
|
||||
i += 1
|
||||
}
|
||||
r.pfadd('hll',*elements)
|
||||
}
|
||||
approx = r.pfcount('hll')
|
||||
abs_err = (approx-i).abs
|
||||
rel_err = 100.to_f*abs_err/i
|
||||
puts "#{i} vs #{approx}: #{rel_err}%"
|
||||
end
|
||||
@@ -1,88 +0,0 @@
|
||||
# hll-err.rb - Copyright (C) 2014 Salvatore Sanfilippo
|
||||
# BSD license, See the COPYING file for more information.
|
||||
#
|
||||
# This program is suited to output average and maximum errors of
|
||||
# the Redis HyperLogLog implementation in a format suitable to print
|
||||
# graphs using gnuplot.
|
||||
|
||||
require 'rubygems'
|
||||
require 'redis'
|
||||
require 'digest/sha1'
|
||||
|
||||
# Generate an array of [cardinality,relative_error] pairs
|
||||
# in the 0 - max range, with the specified step.
|
||||
#
|
||||
# 'r' is the Redis object used to perform the queries.
|
||||
# 'seed' must be different every time you want a test performed
|
||||
# with a different set. The function guarantees that if 'seed' is the
|
||||
# same, exactly the same dataset is used, and when it is different,
|
||||
# a totally unrelated different data set is used (without any common
|
||||
# element in practice).
|
||||
def run_experiment(r,seed,max,step)
|
||||
r.del('hll')
|
||||
i = 0
|
||||
samples = []
|
||||
step = 1000 if step > 1000
|
||||
while i < max do
|
||||
elements = []
|
||||
step.times {
|
||||
ele = Digest::SHA1.hexdigest(i.to_s+seed.to_s)
|
||||
elements << ele
|
||||
i += 1
|
||||
}
|
||||
r.pfadd('hll',*elements)
|
||||
approx = r.pfcount('hll')
|
||||
err = approx-i
|
||||
rel_err = 100.to_f*err/i
|
||||
samples << [i,rel_err]
|
||||
end
|
||||
samples
|
||||
end
|
||||
|
||||
def filter_samples(numsets,max,step,filter)
|
||||
r = Redis.new
|
||||
dataset = {}
|
||||
(0...numsets).each{|i|
|
||||
dataset[i] = run_experiment(r,i,max,step)
|
||||
STDERR.puts "Set #{i}"
|
||||
}
|
||||
dataset[0].each_with_index{|ele,index|
|
||||
if filter == :max
|
||||
card=ele[0]
|
||||
err=ele[1].abs
|
||||
(1...numsets).each{|i|
|
||||
err = dataset[i][index][1] if err < dataset[i][index][1]
|
||||
}
|
||||
puts "#{card} #{err}"
|
||||
elsif filter == :avg
|
||||
card=ele[0]
|
||||
err = 0
|
||||
(0...numsets).each{|i|
|
||||
err += dataset[i][index][1]
|
||||
}
|
||||
err /= numsets
|
||||
puts "#{card} #{err}"
|
||||
elsif filter == :absavg
|
||||
card=ele[0]
|
||||
err = 0
|
||||
(0...numsets).each{|i|
|
||||
err += dataset[i][index][1].abs
|
||||
}
|
||||
err /= numsets
|
||||
puts "#{card} #{err}"
|
||||
elsif filter == :all
|
||||
(0...numsets).each{|i|
|
||||
card,err = dataset[i][index]
|
||||
puts "#{card} #{err}"
|
||||
}
|
||||
else
|
||||
raise "Unknown filter #{filter}"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
if ARGV.length != 4
|
||||
puts "Usage: hll-gnuplot-graph <samples> <max> <step> (max|avg|absavg|all)"
|
||||
exit 1
|
||||
end
|
||||
filter_samples(ARGV[0].to_i,ARGV[1].to_i,ARGV[2].to_i,ARGV[3].to_sym)
|
||||
+60
-115
@@ -30,48 +30,46 @@
|
||||
# this scripts should be run as root
|
||||
|
||||
die () {
|
||||
echo "ERROR: $1. Aborting!"
|
||||
echo "ERROR: $1. Aborting!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
#Absolute path to this script
|
||||
SCRIPT=$(readlink -f $0)
|
||||
#Absolute path this script is in
|
||||
SCRIPTPATH=$(dirname $SCRIPT)
|
||||
|
||||
#Initial defaults
|
||||
_REDIS_PORT=6379
|
||||
|
||||
echo "Welcome to the redis service installer"
|
||||
echo "This script will help you easily set up a running redis server"
|
||||
echo
|
||||
echo "This script will help you easily set up a running redis server
|
||||
|
||||
#check for root user
|
||||
if [ "$(id -u)" -ne 0 ] ; then
|
||||
"
|
||||
|
||||
#check for root user TODO: replace this with a call to "id"
|
||||
if [ `whoami` != "root" ] ; then
|
||||
echo "You must run this script as root. Sorry!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
#Read the redis port
|
||||
read -p "Please select the redis port for this instance: [$_REDIS_PORT] " REDIS_PORT
|
||||
if ! echo $REDIS_PORT | egrep -q '^[0-9]+$' ; then
|
||||
read -p "Please select the redis port for this instance: [$_REDIS_PORT] " REDIS_PORT
|
||||
if [ ! `echo $REDIS_PORT | egrep "^[0-9]+\$"` ] ; then
|
||||
echo "Selecting default: $_REDIS_PORT"
|
||||
REDIS_PORT=$_REDIS_PORT
|
||||
REDIS_PORT=$_REDIS_PORT
|
||||
fi
|
||||
|
||||
#read the redis config file
|
||||
_REDIS_CONFIG_FILE="/etc/redis/$REDIS_PORT.conf"
|
||||
read -p "Please select the redis config file name [$_REDIS_CONFIG_FILE] " REDIS_CONFIG_FILE
|
||||
if [ -z "$REDIS_CONFIG_FILE" ] ; then
|
||||
if [ !"$REDIS_CONFIG_FILE" ] ; then
|
||||
REDIS_CONFIG_FILE=$_REDIS_CONFIG_FILE
|
||||
echo "Selected default - $REDIS_CONFIG_FILE"
|
||||
fi
|
||||
#try and create it
|
||||
mkdir -p `dirname "$REDIS_CONFIG_FILE"` || die "Could not create redis config directory"
|
||||
|
||||
#read the redis log file path
|
||||
_REDIS_LOG_FILE="/var/log/redis_$REDIS_PORT.log"
|
||||
read -p "Please select the redis log file name [$_REDIS_LOG_FILE] " REDIS_LOG_FILE
|
||||
if [ -z "$REDIS_LOG_FILE" ] ; then
|
||||
if [ !"$REDIS_LOG_FILE" ] ; then
|
||||
REDIS_LOG_FILE=$_REDIS_LOG_FILE
|
||||
echo "Selected default - $REDIS_LOG_FILE"
|
||||
fi
|
||||
@@ -80,71 +78,55 @@ fi
|
||||
#get the redis data directory
|
||||
_REDIS_DATA_DIR="/var/lib/redis/$REDIS_PORT"
|
||||
read -p "Please select the data directory for this instance [$_REDIS_DATA_DIR] " REDIS_DATA_DIR
|
||||
if [ -z "$REDIS_DATA_DIR" ] ; then
|
||||
if [ !"$REDIS_DATA_DIR" ] ; then
|
||||
REDIS_DATA_DIR=$_REDIS_DATA_DIR
|
||||
echo "Selected default - $REDIS_DATA_DIR"
|
||||
fi
|
||||
mkdir -p $REDIS_DATA_DIR || die "Could not create redis data directory"
|
||||
|
||||
#get the redis executable path
|
||||
_REDIS_EXECUTABLE=`command -v redis-server`
|
||||
_REDIS_EXECUTABLE=`which redis-server`
|
||||
read -p "Please select the redis executable path [$_REDIS_EXECUTABLE] " REDIS_EXECUTABLE
|
||||
if [ ! -x "$REDIS_EXECUTABLE" ] ; then
|
||||
if [ ! -f "$REDIS_EXECUTABLE" ] ; then
|
||||
REDIS_EXECUTABLE=$_REDIS_EXECUTABLE
|
||||
|
||||
if [ ! -x "$REDIS_EXECUTABLE" ] ; then
|
||||
|
||||
if [ ! -f "$REDIS_EXECUTABLE" ] ; then
|
||||
echo "Mmmmm... it seems like you don't have a redis executable. Did you run make install yet?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
|
||||
#render the tmplates
|
||||
TMP_FILE="/tmp/$REDIS_PORT.conf"
|
||||
DEFAULT_CONFIG="../redis.conf"
|
||||
INIT_TPL_FILE="./redis_init_script.tpl"
|
||||
INIT_SCRIPT_DEST="/etc/init.d/redis_$REDIS_PORT"
|
||||
PIDFILE="/var/run/redis_$REDIS_PORT.pid"
|
||||
|
||||
|
||||
|
||||
#check the default for redis cli
|
||||
CLI_EXEC=`command -v redis-cli`
|
||||
if [ -z "$CLI_EXEC" ] ; then
|
||||
CLI_EXEC=`which redis-cli`
|
||||
if [ ! "$CLI_EXEC" ] ; then
|
||||
CLI_EXEC=`dirname $REDIS_EXECUTABLE`"/redis-cli"
|
||||
fi
|
||||
|
||||
echo "Selected config:"
|
||||
|
||||
echo "Port : $REDIS_PORT"
|
||||
echo "Config file : $REDIS_CONFIG_FILE"
|
||||
echo "Log file : $REDIS_LOG_FILE"
|
||||
echo "Data dir : $REDIS_DATA_DIR"
|
||||
echo "Executable : $REDIS_EXECUTABLE"
|
||||
echo "Cli Executable : $CLI_EXEC"
|
||||
|
||||
read -p "Is this ok? Then press ENTER to go on or Ctrl-C to abort." _UNUSED_
|
||||
|
||||
mkdir -p `dirname "$REDIS_CONFIG_FILE"` || die "Could not create redis config directory"
|
||||
mkdir -p `dirname "$REDIS_LOG_FILE"` || die "Could not create redis log dir"
|
||||
mkdir -p "$REDIS_DATA_DIR" || die "Could not create redis data directory"
|
||||
|
||||
#render the templates
|
||||
TMP_FILE="/tmp/${REDIS_PORT}.conf"
|
||||
DEFAULT_CONFIG="${SCRIPTPATH}/../redis.conf"
|
||||
INIT_TPL_FILE="${SCRIPTPATH}/redis_init_script.tpl"
|
||||
INIT_SCRIPT_DEST="/etc/init.d/redis_${REDIS_PORT}"
|
||||
PIDFILE="/var/run/redis_${REDIS_PORT}.pid"
|
||||
|
||||
if [ ! -f "$DEFAULT_CONFIG" ]; then
|
||||
echo "Mmmmm... the default config is missing. Did you switch to the utils directory?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#Generate config file from the default config file as template
|
||||
#changing only the stuff we're controlling from this script
|
||||
echo "## Generated by install_server.sh ##" > $TMP_FILE
|
||||
|
||||
read -r SED_EXPR <<-EOF
|
||||
s#^port [0-9]{4}\$#port ${REDIS_PORT}#; \
|
||||
s#^logfile .+\$#logfile ${REDIS_LOG_FILE}#; \
|
||||
s#^dir .+\$#dir ${REDIS_DATA_DIR}#; \
|
||||
s#^pidfile .+\$#pidfile ${PIDFILE}#; \
|
||||
s#^daemonize no\$#daemonize yes#;
|
||||
EOF
|
||||
SED_EXPR="s#^port [0-9]{4}\$#port ${REDIS_PORT}#;\
|
||||
s#^logfile .+\$#logfile ${REDIS_LOG_FILE}#;\
|
||||
s#^dir .+\$#dir ${REDIS_DATA_DIR}#;\
|
||||
s#^pidfile .+\$#pidfile ${PIDFILE}#;\
|
||||
s#^daemonize no\$#daemonize yes#;"
|
||||
echo $SED_EXPR
|
||||
sed -r "$SED_EXPR" $DEFAULT_CONFIG >> $TMP_FILE
|
||||
|
||||
#cat $TPL_FILE | while read line; do eval "echo \"$line\"" >> $TMP_FILE; done
|
||||
cp $TMP_FILE $REDIS_CONFIG_FILE || die "Could not write redis config file $REDIS_CONFIG_FILE"
|
||||
cp -f $TMP_FILE $REDIS_CONFIG_FILE || exit 1
|
||||
|
||||
#Generate sample script from template file
|
||||
rm -f $TMP_FILE
|
||||
@@ -156,7 +138,7 @@ REDIS_INIT_HEADER=\
|
||||
#Configurations injected by install_server below....\n\n
|
||||
EXEC=$REDIS_EXECUTABLE\n
|
||||
CLIEXEC=$CLI_EXEC\n
|
||||
PIDFILE=\"$PIDFILE\"\n
|
||||
PIDFILE=$PIDFILE\n
|
||||
CONF=\"$REDIS_CONFIG_FILE\"\n\n
|
||||
REDISPORT=\"$REDIS_PORT\"\n\n
|
||||
###############\n\n"
|
||||
@@ -164,82 +146,45 @@ REDISPORT=\"$REDIS_PORT\"\n\n
|
||||
REDIS_CHKCONFIG_INFO=\
|
||||
"# REDHAT chkconfig header\n\n
|
||||
# chkconfig: - 58 74\n
|
||||
# description: redis_${REDIS_PORT} is the redis daemon.\n
|
||||
# description: redis_6379 is the redis daemon.\n
|
||||
### BEGIN INIT INFO\n
|
||||
# Provides: redis_6379\n
|
||||
# Required-Start: \$network \$local_fs \$remote_fs\n
|
||||
# Required-Stop: \$network \$local_fs \$remote_fs\n
|
||||
# Required-Start: $network $local_fs $remote_fs\n
|
||||
# Required-Stop: $network $local_fs $remote_fs\n
|
||||
# Default-Start: 2 3 4 5\n
|
||||
# Default-Stop: 0 1 6\n
|
||||
# Should-Start: \$syslog \$named\n
|
||||
# Should-Stop: \$syslog \$named\n
|
||||
# Short-Description: start and stop redis_${REDIS_PORT}\n
|
||||
# Should-Start: $syslog $named\n
|
||||
# Should-Stop: $syslog $named\n
|
||||
# Short-Description: start and stop redis_6379\n
|
||||
# Description: Redis daemon\n
|
||||
### END INIT INFO\n\n"
|
||||
|
||||
if command -v chkconfig >/dev/null; then
|
||||
#if we're a box with chkconfig on it we want to include info for chkconfig
|
||||
echo "$REDIS_INIT_HEADER" "$REDIS_CHKCONFIG_INFO" > $TMP_FILE && cat $INIT_TPL_FILE >> $TMP_FILE || die "Could not write init script to $TMP_FILE"
|
||||
else
|
||||
if [ !`which chkconfig` ] ; then
|
||||
#combine the header and the template (which is actually a static footer)
|
||||
echo "$REDIS_INIT_HEADER" > $TMP_FILE && cat $INIT_TPL_FILE >> $TMP_FILE || die "Could not write init script to $TMP_FILE"
|
||||
echo $REDIS_INIT_HEADER > $TMP_FILE && cat $INIT_TPL_FILE >> $TMP_FILE || die "Could not write init script to $TMP_FILE"
|
||||
else
|
||||
#if we're a box with chkconfig on it we want to include info for chkconfig
|
||||
echo -e $REDIS_INIT_HEADER $REDIS_CHKCONFIG_INFO > $TMP_FILE && cat $INIT_TPL_FILE >> $TMP_FILE || die "Could not write init script to $TMP_FILE"
|
||||
fi
|
||||
|
||||
###
|
||||
# Generate sample script from template file
|
||||
# - No need to check which system we are on. The init info are comments and
|
||||
# do not interfere with update_rc.d systems. Additionally:
|
||||
# Ubuntu/debian by default does not come with chkconfig, but does issue a
|
||||
# warning if init info is not available.
|
||||
|
||||
cat > ${TMP_FILE} <<EOT
|
||||
#/bin/sh
|
||||
#Configurations injected by install_server below....
|
||||
|
||||
EXEC=$REDIS_EXECUTABLE
|
||||
CLIEXEC=$CLI_EXEC
|
||||
PIDFILE=$PIDFILE
|
||||
CONF="$REDIS_CONFIG_FILE"
|
||||
REDISPORT="$REDIS_PORT"
|
||||
###############
|
||||
# SysV Init Information
|
||||
# chkconfig: - 58 74
|
||||
# description: redis_${REDIS_PORT} is the redis daemon.
|
||||
### BEGIN INIT INFO
|
||||
# Provides: redis_${REDIS_PORT}
|
||||
# Required-Start: \$network \$local_fs \$remote_fs
|
||||
# Required-Stop: \$network \$local_fs \$remote_fs
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Should-Start: \$syslog \$named
|
||||
# Should-Stop: \$syslog \$named
|
||||
# Short-Description: start and stop redis_${REDIS_PORT}
|
||||
# Description: Redis daemon
|
||||
### END INIT INFO
|
||||
|
||||
EOT
|
||||
cat ${INIT_TPL_FILE} >> ${TMP_FILE}
|
||||
|
||||
#copy to /etc/init.d
|
||||
cp $TMP_FILE $INIT_SCRIPT_DEST && \
|
||||
chmod +x $INIT_SCRIPT_DEST || die "Could not copy redis init script to $INIT_SCRIPT_DEST"
|
||||
cp -f $TMP_FILE $INIT_SCRIPT_DEST && chmod +x $INIT_SCRIPT_DEST || die "Could not copy redis init script to $INIT_SCRIPT_DEST"
|
||||
echo "Copied $TMP_FILE => $INIT_SCRIPT_DEST"
|
||||
|
||||
#Install the service
|
||||
echo "Installing service..."
|
||||
if command -v chkconfig >/dev/null 2>&1; then
|
||||
# we're chkconfig, so lets add to chkconfig and put in runlevel 345
|
||||
chkconfig --add redis_${REDIS_PORT} && echo "Successfully added to chkconfig!"
|
||||
chkconfig --level 345 redis_${REDIS_PORT} on && echo "Successfully added to runlevels 345!"
|
||||
elif command -v update-rc.d >/dev/null 2>&1; then
|
||||
if [ !`which chkconfig` ] ; then
|
||||
#if we're not a chkconfig box assume we're able to use update-rc.d
|
||||
update-rc.d redis_${REDIS_PORT} defaults && echo "Success!"
|
||||
update-rc.d redis_$REDIS_PORT defaults && echo "Success!"
|
||||
else
|
||||
echo "No supported init tool found."
|
||||
# we're chkconfig, so lets add to chkconfig and put in runlevel 345
|
||||
chkconfig --add redis_$REDIS_PORT && echo "Successfully added to chkconfig!"
|
||||
chkconfig --level 345 redis_$REDIS_PORT on && echo "Successfully added to runlevels 345!"
|
||||
fi
|
||||
|
||||
|
||||
/etc/init.d/redis_$REDIS_PORT start || die "Failed starting service..."
|
||||
|
||||
#tada
|
||||
echo "Installation successful!"
|
||||
exit 0
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
The test-lru.rb program can be used in order to check the behavior of the
|
||||
Redis approximated LRU algorithm against the theoretical output of true
|
||||
LRU algorithm.
|
||||
|
||||
In order to use the program you need to recompile Redis setting the define
|
||||
REDIS_LRU_CLOCK_RESOLUTION to 1, by editing redis.h.
|
||||
This allows to execute the program in a fast way since the 1 ms resolution
|
||||
is enough for all the objects to have a different enough time stamp during
|
||||
the test.
|
||||
|
||||
The program is executed like this:
|
||||
|
||||
ruby test-lru.rb > /tmp/lru.html
|
||||
@@ -1,112 +0,0 @@
|
||||
require 'rubygems'
|
||||
require 'redis'
|
||||
|
||||
r = Redis.new
|
||||
r.config("SET","maxmemory","2000000")
|
||||
r.config("SET","maxmemory-policy","allkeys-lru")
|
||||
r.config("SET","maxmemory-samples",5)
|
||||
r.config("RESETSTAT")
|
||||
r.flushall
|
||||
|
||||
puts <<EOF
|
||||
<html>
|
||||
<body>
|
||||
<style>
|
||||
.box {
|
||||
width:5px;
|
||||
height:5px;
|
||||
float:left;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
.old {
|
||||
border: 1px black solid;
|
||||
}
|
||||
|
||||
.new {
|
||||
border: 1px green solid;
|
||||
}
|
||||
|
||||
.ex {
|
||||
background-color: #666;
|
||||
}
|
||||
</style>
|
||||
<pre>
|
||||
EOF
|
||||
|
||||
# Fill
|
||||
oldsize = r.dbsize
|
||||
id = 0
|
||||
while true
|
||||
id += 1
|
||||
r.set(id,"foo")
|
||||
newsize = r.dbsize
|
||||
break if newsize == oldsize
|
||||
oldsize = newsize
|
||||
end
|
||||
|
||||
inserted = r.dbsize
|
||||
first_set_max_id = id
|
||||
puts "#{r.dbsize} keys inserted"
|
||||
|
||||
# Access keys sequencially
|
||||
|
||||
puts "Access keys sequencially"
|
||||
(1..first_set_max_id).each{|id|
|
||||
r.get(id)
|
||||
# sleep 0.001
|
||||
}
|
||||
|
||||
# Insert more 50% keys. We expect that the new keys
|
||||
half = inserted/2
|
||||
puts "Insert enough keys to evict half the keys we inserted"
|
||||
add = 0
|
||||
while true
|
||||
add += 1
|
||||
id += 1
|
||||
r.set(id,"foo")
|
||||
break if r.info['evicted_keys'].to_i >= half
|
||||
end
|
||||
|
||||
puts "#{add} additional keys added."
|
||||
puts "#{r.dbsize} keys in DB"
|
||||
|
||||
# Check if evicted keys respect LRU
|
||||
# We consider errors from 1 to N progressively more serious as they violate
|
||||
# more the access pattern.
|
||||
|
||||
errors = 0
|
||||
e = 1
|
||||
edecr = 1.0/(first_set_max_id/2)
|
||||
(1..(first_set_max_id/2)).each{|id|
|
||||
e -= edecr if e > 0
|
||||
e = 0 if e < 0
|
||||
if r.exists(id)
|
||||
errors += e
|
||||
end
|
||||
}
|
||||
|
||||
puts "#{errors} errors!"
|
||||
puts "</pre>"
|
||||
|
||||
# Generate the graphical representation
|
||||
(1..id).each{|id|
|
||||
# Mark first set and added items in a different way.
|
||||
c = "box"
|
||||
if id <= first_set_max_id
|
||||
c << " old"
|
||||
else
|
||||
c << " new"
|
||||
end
|
||||
|
||||
# Add class if exists
|
||||
c << " ex" if r.exists(id)
|
||||
puts "<div class=\"#{c}\"></div>"
|
||||
}
|
||||
|
||||
# Close HTML page
|
||||
|
||||
puts <<EOF
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
+14
-26
@@ -3,41 +3,29 @@ case "$1" in
|
||||
start)
|
||||
if [ -f $PIDFILE ]
|
||||
then
|
||||
echo "$PIDFILE exists, process is already running or crashed"
|
||||
echo "$PIDFILE exists, process is already running or crashed"
|
||||
else
|
||||
echo "Starting Redis server..."
|
||||
$EXEC $CONF
|
||||
echo "Starting Redis server..."
|
||||
$EXEC $CONF
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if [ ! -f $PIDFILE ]
|
||||
then
|
||||
echo "$PIDFILE does not exist, process is not running"
|
||||
echo "$PIDFILE does not exist, process is not running"
|
||||
else
|
||||
PID=$(cat $PIDFILE)
|
||||
echo "Stopping ..."
|
||||
$CLIEXEC -p $REDISPORT shutdown
|
||||
while [ -x /proc/${PID} ]
|
||||
do
|
||||
echo "Waiting for Redis to shutdown ..."
|
||||
sleep 1
|
||||
done
|
||||
echo "Redis stopped"
|
||||
PID=$(cat $PIDFILE)
|
||||
echo "Stopping ..."
|
||||
$CLIEXEC -p $REDISPORT shutdown
|
||||
while [ -x /proc/${PID} ]
|
||||
do
|
||||
echo "Waiting for Redis to shutdown ..."
|
||||
sleep 1
|
||||
done
|
||||
echo "Redis stopped"
|
||||
fi
|
||||
;;
|
||||
status)
|
||||
if [ ! -f $PIDFILE ]
|
||||
then
|
||||
echo 'Redis is not running'
|
||||
else
|
||||
echo "Redis is running ($(<$PIDFILE))"
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
$0 start
|
||||
;;
|
||||
*)
|
||||
echo "Please use start, stop, restart or status as first argument"
|
||||
echo "Please use start or stop as first argument"
|
||||
;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user