Compare commits
1006
Commits
3.0.0-beta5
..
2.8.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71f7eb2590 | ||
|
|
df00b6ac05 | ||
|
|
b8db8a0c48 | ||
|
|
8ed0b49525 | ||
|
|
560e548dc6 | ||
|
|
0ca750d94a | ||
|
|
1f9580c125 | ||
|
|
bb207007ca | ||
|
|
b7dc3204a6 | ||
|
|
2e0ba7bb5b | ||
|
|
52cf0975c7 | ||
|
|
6bcc370c9d | ||
|
|
ea2bffa030 | ||
|
|
bca65866ff | ||
|
|
e4c51759f5 | ||
|
|
51f71e2d8e | ||
|
|
05d219a6fa | ||
|
|
2a1a31ca9d | ||
|
|
418d3d358a | ||
|
|
0a1a236e3e | ||
|
|
27d06111db | ||
|
|
5b7c16137d | ||
|
|
d07d4a876c | ||
|
|
9d1997706c | ||
|
|
4ad219adc8 | ||
|
|
c6db326d1d | ||
|
|
4456ee1173 | ||
|
|
5fa937d956 | ||
|
|
9ba9d78ada | ||
|
|
d33f3689d7 | ||
|
|
fb8a480f54 | ||
|
|
d62bbfda16 | ||
|
|
090bcc946f | ||
|
|
ddd529bc3b | ||
|
|
673c42bb4e | ||
|
|
33f6f35f34 | ||
|
|
8eb1cb3b52 | ||
|
|
993e0ede76 | ||
|
|
2507e366b2 | ||
|
|
f9e9448a5b | ||
|
|
45c60a903a | ||
|
|
5e75e681fe | ||
|
|
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 |
+152
-169
@@ -1,7 +1,9 @@
|
||||
Redis 3.0 release notes
|
||||
Redis 2.8 release notes
|
||||
=======================
|
||||
|
||||
WARNING: Redis 3.0 is currently a BETA not suitable for production environments.
|
||||
** IMPORTANT ** Check the 'Migrating from 2.6 to 2.8' section at the end of
|
||||
this file for information about what changed between 2.6 and
|
||||
2.8 and how this may affect your application.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Upgrade urgency levels:
|
||||
@@ -12,220 +14,201 @@ HIGH: There is a critical bug that may affect a subset of users. Upgrade!
|
||||
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--[ Redis 3.0.0 Beta 5 (version 2.9.54) ] Release date: 26 may 2014
|
||||
--[ Redis 2.8.4 ] Release date: 13 Jan 2014
|
||||
|
||||
This is the 5th beta of Redis 3.0.0. It does not include any real
|
||||
worthwhile change (just three days passed since the previous beta), but
|
||||
fixes two stupid bugs preventing cluster tests to pass.
|
||||
# UPGRADE URGENCY: MODERATE for Redis and Sentinel.
|
||||
|
||||
--[ Redis 3.0.0 Beta 4 (version 2.9.53) ] Release date: 23 may 2014
|
||||
* [FIX] Makefile compatibility with non common make variants improved.
|
||||
* [FIX] SDIFF crash in very unlikely to trigger state fixed.
|
||||
* [FIX] Config rewriting fixed: don't wipe options unknown to the rewrite
|
||||
process.
|
||||
* [FIX] Set TCP port to 0 works again to disable TCP networking.
|
||||
* [FIX] Fixed replication with old Redis instances as masters by not
|
||||
sending REPLCONF ACK to them.
|
||||
* [FIX] Fix keyspace notifications rewrite and CONFIG GET output.
|
||||
* [FIX] Fix RESTORE TTL handling in 32 bit systems (32 bit overflow).
|
||||
|
||||
This is the fourth beta of Redis 3.0.0.
|
||||
* [NEW] Sentinel now has a run time configuration API.
|
||||
* [NEW] Log when we lost connection with master or slave.
|
||||
* [NEW] When instance is turned from slave to master now inherits the
|
||||
old master replication offset when possible. This improves the
|
||||
Sentinel failover procedure.
|
||||
|
||||
>> General changes
|
||||
--[ Redis 2.8.3 ] Release date: 11 Dec 2013
|
||||
|
||||
* [NEW] Scripting engine performances improvements.
|
||||
* [NEW] Log format slightly changed to report current node role.
|
||||
# UPGRADE URGENCY: MODERATE for Redis, HIGH for Sentinel.
|
||||
|
||||
* [FIX] Correct the HyperLogLog stale cache flag to prevent unnecessary
|
||||
computation.
|
||||
* [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.
|
||||
|
||||
>> Cluster changes
|
||||
--[ Redis 2.8.2 ] Release date: 2 Dec 2013
|
||||
|
||||
* [NEW] redis-trib: ability to import data from standalone Redis instances.
|
||||
* [NEW] redis-trib: "fix" subcommand much better at fixing errors.
|
||||
* [NEW] CLUSTER FAILOVER FORCE implemented.
|
||||
* [NEW] CLUSTER RESET implemented, it is now possible to completely reset
|
||||
nodes and create a new cluster without restarting anything.
|
||||
* [NEW] Slave validity factor (max estimated data age to still failover)
|
||||
is now under the control of the user, and can be configured via
|
||||
redis.conf or CONFIG SET. Option name cluster-slave-validity-factor.
|
||||
* [NEW] Cluster test: failure detection and failover initial tests.
|
||||
* [NEW] CLUSTER MEET: better error messages when address is invalid.
|
||||
* [NEW] Bulk-accept new Cluster nodes in the Cluster bus instead of
|
||||
performing just a single accept() per event fired.
|
||||
# UPGRADE URGENCY: MODERATE for both Redis and Sentinel.
|
||||
|
||||
* [FIX] Bypass data_age check for manual failovers.
|
||||
* [FIX] Fixed data_age computation / check integer overflow.
|
||||
* [FIX] Various fixes to Tcl client.tcl Redis Cluster client used in tests.
|
||||
* [FIX] Better handling of stolen slots.
|
||||
* [FIX] Don't accept cluster bus connections during startup.
|
||||
* [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.
|
||||
|
||||
>> Sentinel changes
|
||||
--[ Redis 2.8.1 ] Release date: 25 Nov 2013
|
||||
|
||||
* [NEW] Generate +config-update-from event when a new config is received.
|
||||
* [NEW] Log when a failover will be re-attempted again.
|
||||
# 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] Sentinel: Add "dir /tmp" directive in example sentinel.conf.
|
||||
* [FIX] Fixed a bug in "new Sentinel" config propagation.
|
||||
* [FIX] Fixed a false positive in Redis tests.
|
||||
|
||||
--[ Redis 3.0.0 Beta 3 (version 2.9.52) ] Release date: 5 may 2014
|
||||
--[ Redis 2.8.0 ] Release date: 22 Nov 2013
|
||||
|
||||
This is the third beta of Redis 3.0.0.
|
||||
# UPGRADE URGENCY: LOW, unless you want to upgrade to new Sentinel code.
|
||||
|
||||
>> General changes
|
||||
* [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.
|
||||
|
||||
* [NEW] New data structure: the HyperLogLog (see 2.8 release notes).
|
||||
* [NEW] Lexicograhical range queries in sorted sets (see 2.8 release notes).
|
||||
* [NEW] LRU algorithm precision greatly improved.
|
||||
--[ Redis 2.8 Release Candidate 6 (2.7.106) ] Release date: 6 Nov 2013
|
||||
|
||||
* [FIX] Redis is now much more responsive to reply with LOADING / BUSY errors.
|
||||
This is the 6th release candidate of Redis 2.8 (official version is 2.7.106).
|
||||
|
||||
>> Cluster changes
|
||||
# UPGRADE URGENCY: LOW, only new features back ported, no fixes.
|
||||
|
||||
* [NEW] Cluster testing framework and initial tests.
|
||||
* [NEW] Cluster epochs collision resolution (make Redis Cluster more resilient
|
||||
to admin and programming errors).
|
||||
* [NEW] Persist / fsync some global state to ensure correct crash-recovery
|
||||
semantics.
|
||||
* [NEW] New command introduced: CLUSTER SET-CONFIG-EPOCH, still not used
|
||||
by redis-trib. Will be used to speedup the assignment of unique
|
||||
epochs to different nodes at cluster creation time. For now this is
|
||||
handled as a side effect of the cluster epochs collision resolution.
|
||||
* [NEW] SCAN, SSCAN, HSCAN, ZSCAN commands.
|
||||
|
||||
* [FIX] Different fixes to redis-trib cluster creation.
|
||||
* [FIX] Fix an error in the CLUSTER NODES output for nodes slots.
|
||||
--[ Redis 2.8 Release Candidate 5 (2.7.105) ] Release date: 9 Oct 2013
|
||||
|
||||
>> Sentinel changes
|
||||
This is the 5th release candidate of Redis 2.8 (official version is 2.7.105).
|
||||
Important bugs fixed inside.
|
||||
|
||||
* [NEW] Sentinels are now able to send update messages in a peer-to-peer
|
||||
fashion even if no Redis instances are available. Now the Sentinel
|
||||
liveness property that the most updated configuration in a given
|
||||
partition is propagated to all the Sentinels is extended to partitions
|
||||
without reachable instances.
|
||||
* [NEW] Sentinel safety properties are now ensured in a crash-recovery system
|
||||
model since some state is persisted on disk before replying to other
|
||||
nodes, and reloaded at startup.
|
||||
* [NEW] Sentinel now uses CLIENT SETNAME so that it is easy to identify
|
||||
Sentinels using CLIENT LIST among other clients.
|
||||
* [NEW] Sentinel failure detection and reconnection code improved.
|
||||
# UPGRADE URGENCY: HIGH because of many non critical replication bugs fixed.
|
||||
|
||||
--[ Redis 3.0.0 Beta 2 (version 2.9.51) ] Release date: 11 mar 2014
|
||||
* [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.
|
||||
|
||||
This is the second beta of Redis 3.0.0.
|
||||
--[ Redis 2.8 Release Candidate 4 (2.7.104) ] Release date: 30 Aug 2013
|
||||
|
||||
>> General changes
|
||||
This is the fourth release candidate of Redis 2.8 (official version is 2.7.104).
|
||||
Important bugs fixed inside.
|
||||
|
||||
* [FIX] Sometimes the absolute config file path was obtained in a wrong way.
|
||||
This happened when there was a "dir" directive inside the config file
|
||||
and at the same time the configuration file was given as a relative
|
||||
path to redis-server or redis-sentinel executables.
|
||||
* [FIX] redis-cli: Automatically enter --slave mode when SYNC or PSYNC are
|
||||
called during an interactive session.
|
||||
* [FIX] BITCOUNT: fixed unaligned access causing issues in sparc and other
|
||||
archs not capable of dealing with unaligned accesses. This also makes
|
||||
the code faster in archs where unaligned accesses are allowed.
|
||||
* [FIX] Force INFO used_memory_peak to match peak memory. This generated some
|
||||
confusion among users even if it was not an actual bug.
|
||||
* [FIX] Fixed an critical EVALSHA script cache bug: scripts executed may not
|
||||
propagate to AOF / Slaves correctly under certain conditions.
|
||||
See issue #1549 at Github for more information.
|
||||
* [FIX] Fixed multiple bugs resulting into closing the link with master or slave
|
||||
during replication without good reasons. This will result in useless
|
||||
resynchronizations, or infinite loops where the replication link can't
|
||||
be established.
|
||||
* [FIX] Don't count the time needed to populate the buffers of clients waiting
|
||||
in MONITOR mode when populating the Slow Log entries.
|
||||
# UPGRADE URGENCY: HIGH because of the EVAL memory leak.
|
||||
|
||||
* [NEW] Redis-cli updated to use SCAN instead of random sampling via
|
||||
RANDOMKEY in order to implement --bigkeys feature. Moreover the
|
||||
implementation now supports pipelining and reports more information
|
||||
at the end of the scan. Much faster, much better. A special thank
|
||||
you to Michael Grunder for this improvement.
|
||||
* [NEW] redis-cli now supports a new --intrinsic-latency mode that is able
|
||||
to meter the latency of a system due to kernel / hypervisor.
|
||||
How to use it is explained at http://redis.io/topics/latency.
|
||||
* [NEW] New command BITPOS: find first bit set or clear in a bitmap.
|
||||
* [NEW] CONFIG REWRITE calls are now logged.
|
||||
* [NEW] AOF write errors (like no space on device) no longer abort Redis if the
|
||||
fsync policy is none or every second. The database enters a read-only
|
||||
mode where every write is refused with an error. Normal operations are
|
||||
restored as soon as Redis is able to append again data to the AOF file.
|
||||
* [NEW] Sentinel now accepts SHUTDOWN command.
|
||||
* [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
|
||||
|
||||
>> Cluster changes
|
||||
This is the third release candidate of Redis 2.8 (official version is 2.7.103).
|
||||
Important bugs fixed inside.
|
||||
|
||||
* [FIX] Bind the first interface listed in the "bind" configuration directive
|
||||
if any, in order to perform outgoing connections. This fixes Cluster
|
||||
usage when an address is bound but there are multiple interfaces that
|
||||
may be used to connect with other nodes.
|
||||
* [FIX] When an "Importing" slot is closed via CLUSTER SETSLOT NODE ...
|
||||
increment the configEpoch in the special case it is zero.
|
||||
* [FIX] Current transaction is invalidated on redirection errors.
|
||||
* [FIX] Abort if port does not allow for a valid cluster bus port that is
|
||||
always at fixed +10000 offset.
|
||||
* [FIX] Keys extraction algorithm fixed for ZUNIONSTORE/ZINTERSTORE and SORT.
|
||||
* [FIX] Better failover timeout and retry times: failover should now work
|
||||
reliabily when node-timeout is very small (a few milliseconds).
|
||||
* [FIX] Don't allow SORT GET/BY options in Cluster mode.
|
||||
* [FIX] Clear importing/migrating state when turning from master to slave role.
|
||||
* [FIX] Set slot error if we receive an update for a busy slot.
|
||||
* [FIX] Update node configEpoch on UPDATE messages.
|
||||
# UPGRADE URGENCY: HIGH
|
||||
|
||||
* [NEW] Support multi-key operations as long as keys resolve to the same
|
||||
hash slot, and the slot is not migrating, or it is migrating but all
|
||||
the mentioned keys are available.
|
||||
* [NEW] New DEBUG command CMDKEYS available to debug / test keys identification
|
||||
in Redis commands.
|
||||
* [NEW] redis-trib: create subcommand is now able to assign spare slaves.
|
||||
* [NEW] redis-trib: new subcommand 'call'. Exec command in all nodes.
|
||||
* [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
|
||||
|
||||
>> Sentinel changes
|
||||
This is the second release candidate of Redis 2.8 (official version is 2.7.102).
|
||||
Important bugs fixed inside.
|
||||
|
||||
* [FIX] Sentinel "IDONTKNOW" error removed as it does not made sense with the
|
||||
new Sentinel design. This error was actually a fix for a design error
|
||||
in the first implementation of Sentinel.
|
||||
* [FIX] Sentinel: added a missing exit() call to abort after config file
|
||||
checks at startup. This error was introduced with an improvement in
|
||||
a previous 2.8 release.
|
||||
* [FIX] Sentinel: better nodes fail over start time desynchronization to avoid
|
||||
split-brain during the voting process needed to get authorization to
|
||||
fail over. This means the system is less likely to need to retry
|
||||
and will fail over faster. No changes in behavior / correctness.
|
||||
# UPGRADE URGENCY: HIGH
|
||||
|
||||
* [NEW] Sentinel unit tests and framework. More tests needed and units must
|
||||
be improved in order to have less false positives, but it is a start
|
||||
and features a debugging console that is useful to fix tests or to
|
||||
inspect bugs causing tests failures.
|
||||
* [NEW] New Sentinel events: +/-monitor and +set used to monitor when an
|
||||
instance to monitor is added or removed, or when a configuration
|
||||
is modified via SENTINEL SET.
|
||||
* [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 3.0.0 Beta 1 (version 2.9.50) ] Release date: 11 Feb 2014
|
||||
--[ Redis 2.8 Release Candidate 1 (2.7.101) ] Release date: 18 Jul 2013
|
||||
|
||||
This is the first beta of Redis 3.0.0.
|
||||
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 3.0, compared to Redis 2.8.
|
||||
The following is a list of improvements in Redis 2.8, compared to Redis 2.6.
|
||||
|
||||
* [NEW] Redis Cluster: a distributed implementation of a subset of Redis.
|
||||
* [NEW] New "embedded string" object encoding resulting in less cache
|
||||
misses. Big speed gain under certain work loads.
|
||||
* [NEW] WAIT command to block waiting for a write to be transmitted to
|
||||
the specified number of slaves.
|
||||
* [NEW] MIGRATE connection caching. Much faster keys migraitons.
|
||||
* [NEW] MIGARTE new options COPY and REPLACE.
|
||||
* [NEW] CLIENT PAUSE command: stop processing client requests for a
|
||||
specified amount of time.
|
||||
* [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.8 to 3.0
|
||||
Migrating from 2.6 to 2.8
|
||||
=========================
|
||||
|
||||
Redis 3.0 is mostly a strict subset of 2.8, you should not have any problem
|
||||
upgrading your application from 2.8 to 3.0. However this is a list of small
|
||||
non-backward compatible changes introduced in the 3.0 release:
|
||||
Redis 2.6 is mostly a strict subset of 2.8. However there are a few things
|
||||
that you should be aware of:
|
||||
|
||||
* The log format was modified. The prefix of each line included the pid
|
||||
in the following format [1234]. Now instead it is 1234:? Where
|
||||
'?' is actually the role of the instance. M for master, S for slave, C
|
||||
if this process is a saving child (for RDB/AOF), and X for Sentinel.
|
||||
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 Pivotal for making all this possible.
|
||||
Also many thanks to all the other contributors and the amazing community
|
||||
we have.
|
||||
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.
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
+6
-121
@@ -44,15 +44,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
|
||||
@@ -416,18 +407,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 ###############################
|
||||
|
||||
@@ -541,95 +529,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
|
||||
|
||||
# A slave of a failing master will avoid to start a failover if its data
|
||||
# looks too old.
|
||||
#
|
||||
# There is no simple way for a slave to actually have a exact measure of
|
||||
# its "data age", so the following two checks are performed:
|
||||
#
|
||||
# 1) If there are multiple slaves able to failover, they exchange messages
|
||||
# in order to try to give an advantage to the slave with the best
|
||||
# replication offset (more data from the master processed).
|
||||
# Slaves will try to get their rank by offset, and apply to the start
|
||||
# of the failover a delay proportional to their rank.
|
||||
#
|
||||
# 2) Every single slave computes the time of the last interaction with
|
||||
# its master. This can be the last ping or command received (if the master
|
||||
# is still in the "connected" state), or the time that elapsed since the
|
||||
# disconnection with the master (if the replication link is currently down).
|
||||
# If the last interaction is too old, the slave will not try to failover
|
||||
# at all.
|
||||
#
|
||||
# The point "2" can be tuned by user. Specifically a slave will not perform
|
||||
# the failover if, since the last interaction with the master, the time
|
||||
# elapsed is greater than:
|
||||
#
|
||||
# (node-timeout * slave-validity-factor) + repl-ping-slave-period
|
||||
#
|
||||
# So for example if node-timeout is 30 seconds, and the slave-validity-factor
|
||||
# is 10, and assuming a default repl-ping-slave-period of 10 seconds, the
|
||||
# slave will not try to failover if it was not able to talk with the master
|
||||
# for longer than 310 seconds.
|
||||
#
|
||||
# A large slave-validity-factor may allow slaves with too old data to failover
|
||||
# a master, while a too small value may prevent the cluster from being able to
|
||||
# elect a slave at all.
|
||||
#
|
||||
# For maximum availability, it is possible to set the slave-validity-factor
|
||||
# to a value of 0, which means, that slaves will always try to failover the
|
||||
# master regardless of the last time they interacted with the master.
|
||||
# (However they'll always try to apply a delay proportional to their
|
||||
# offset rank).
|
||||
#
|
||||
# Zero is the only value able to guarantee that when all the partitions heal
|
||||
# the cluster will always be able to continue.
|
||||
#
|
||||
# cluster-slave-validity-factor 10
|
||||
|
||||
# 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
|
||||
@@ -727,20 +626,6 @@ set-max-intset-entries 512
|
||||
zset-max-ziplist-entries 128
|
||||
zset-max-ziplist-value 64
|
||||
|
||||
# HyperLogLog sparse representation bytes limit. The limit includes the
|
||||
# 16 bytes header. When an HyperLogLog using the sparse representation crosses
|
||||
# this limit, it is converted into the dense representation.
|
||||
#
|
||||
# A value greater than 16000 is totally useless, since at that point the
|
||||
# dense representation is more memory efficient.
|
||||
#
|
||||
# The suggested value is ~ 3000 in order to have the benefits of
|
||||
# the space efficient encoding without slowing down too much PFADD,
|
||||
# which is O(N) with the sparse encoding. The value can be raised to
|
||||
# ~ 10000 when CPU is not a concern, but space is, and the data set is
|
||||
# composed of many HyperLogLogs with cardinality in the 0 - 15000 range.
|
||||
hll-sparse-max-bytes 3000
|
||||
|
||||
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation Redis uses (see dict.c)
|
||||
|
||||
@@ -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/cluster/run.tcl $*
|
||||
@@ -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/run.tcl $*
|
||||
+2
-15
@@ -4,13 +4,6 @@
|
||||
# The port that this sentinel instance will run on
|
||||
port 26379
|
||||
|
||||
# dir <working-directory>
|
||||
# Every long running process should have a well-defined working directory.
|
||||
# For Redis Sentinel to chdir to /tmp at startup is the simplest thing
|
||||
# for the process to don't interferer with administrative tasks such as
|
||||
# unmounting filesystems.
|
||||
dir /tmp
|
||||
|
||||
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
|
||||
#
|
||||
# Tells Sentinel to monitor this master, and to consider it in O_DOWN
|
||||
@@ -20,12 +13,6 @@ dir /tmp
|
||||
# be elected by the majority of the known Sentinels in order to
|
||||
# start a failover, so no failover can be performed in minority.
|
||||
#
|
||||
# Slaves are auto-discovered, so you don't need to specify slaves in
|
||||
# any way. Sentinel itself will rewrite this configuration file adding
|
||||
# the slaves using additional configuration options.
|
||||
# Also note that the configuration file is rewritten when a
|
||||
# slave is promoted to master.
|
||||
#
|
||||
# Note: master name should not include special characters or spaces.
|
||||
# The valid charset is A-z 0-9 and the three characters ".-_".
|
||||
sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
@@ -99,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
|
||||
|
||||
+1
-4
@@ -107,7 +107,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 +204,6 @@ distclean: clean
|
||||
test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
@(cd ..; ./runtest)
|
||||
|
||||
test-sentinel: $(REDIS_SENTINEL_NAME)
|
||||
@(cd ..; ./runtest-sentinel)
|
||||
|
||||
check: test
|
||||
|
||||
lcov:
|
||||
|
||||
+63
-74
@@ -1,126 +1,115 @@
|
||||
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_select.c ae_evport.c ae_epoll.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
|
||||
../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
|
||||
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
|
||||
../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 dict.h adlist.h \
|
||||
zmalloc.h anet.h ziplist.h intset.h version.h rdb.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/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
|
||||
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
|
||||
../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
|
||||
config.h redisassert.h
|
||||
zipmap.o: zipmap.c zmalloc.h endianconv.h config.h
|
||||
zmalloc.o: zmalloc.c config.h zmalloc.h
|
||||
|
||||
+19
-40
@@ -234,12 +234,11 @@ 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;
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
|
||||
snprintf(portstr,sizeof(portstr),"%d",port);
|
||||
memset(&hints,0,sizeof(hints));
|
||||
@@ -259,24 +258,6 @@ static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
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. */
|
||||
@@ -306,17 +287,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 +361,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 +389,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 +411,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 +426,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 +447,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);
|
||||
@@ -496,7 +475,7 @@ int anetTcpAccept(char *err, int s, char *ip, size_t ip_len, int *port) {
|
||||
int fd;
|
||||
struct sockaddr_storage sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == -1)
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
if (sa.ss_family == AF_INET) {
|
||||
@@ -515,7 +494,7 @@ int anetUnixAccept(char *err, int s) {
|
||||
int fd;
|
||||
struct sockaddr_un sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == -1)
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
return fd;
|
||||
|
||||
+3
-4
@@ -45,15 +45,14 @@
|
||||
|
||||
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;
|
||||
@@ -507,7 +456,6 @@ struct redisClient *createFakeClient(void) {
|
||||
c->reply_bytes = 0;
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
c->watched_keys = listCreate();
|
||||
c->peerid = NULL;
|
||||
listSetFreeMethod(c->reply,decrRefCountVoid);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
initClientMultiState(c);
|
||||
@@ -561,7 +509,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
/* Serve the clients from time to time */
|
||||
if (!(loops++ % 1000)) {
|
||||
loadingProgress(ftello(fp));
|
||||
processEventsWhileBlocked();
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
}
|
||||
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
@@ -643,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");
|
||||
@@ -970,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
-189
@@ -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,12 +174,12 @@ void getbitCommand(redisClient *c) {
|
||||
|
||||
byte = bitoffset >> 3;
|
||||
bit = 7 - (bitoffset & 0x7);
|
||||
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);
|
||||
@@ -497,90 +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 (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().");
|
||||
}
|
||||
}
|
||||
|
||||
-4432
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_DEFAULT_SLAVE_VALIDITY 10 /* Slave max data age factor. */
|
||||
#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_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 */
|
||||
+17
-88
@@ -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;
|
||||
@@ -391,8 +383,6 @@ void loadServerConfigFromString(char *config) {
|
||||
server.zset_max_ziplist_entries = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"zset-max-ziplist-value") && argc == 2) {
|
||||
server.zset_max_ziplist_value = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"hll-sparse-max-bytes") && argc == 2) {
|
||||
server.hll_sparse_max_bytes = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"rename-command") && argc == 3) {
|
||||
struct redisCommand *cmd = lookupCommand(argv[1]);
|
||||
int retval;
|
||||
@@ -417,34 +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 zero or positive";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-slave-validity-factor")
|
||||
&& argc == 2)
|
||||
{
|
||||
server.cluster_slave_validity_factor = atoi(argv[1]);
|
||||
if (server.cluster_slave_validity_factor < 0) {
|
||||
err = "cluster slave validity factor must be zero or 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") &&
|
||||
@@ -515,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;
|
||||
|
||||
@@ -579,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")) {
|
||||
@@ -610,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;
|
||||
@@ -775,9 +728,6 @@ void configSetCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-value")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.zset_max_ziplist_value = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"hll-sparse-max-bytes")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.hll_sparse_max_bytes = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"lua-time-limit")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.lua_time_limit = ll;
|
||||
@@ -897,18 +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 if (!strcasecmp(c->argv[2]->ptr,"cluster-slave-validity-factor")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.cluster_slave_validity_factor = ll;
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||
(char*)c->argv[2]->ptr);
|
||||
@@ -958,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);
|
||||
@@ -991,15 +929,12 @@ void configGetCommand(redisClient *c) {
|
||||
server.zset_max_ziplist_entries);
|
||||
config_get_numerical_field("zset-max-ziplist-value",
|
||||
server.zset_max_ziplist_value);
|
||||
config_get_numerical_field("hll-sparse-max-bytes",
|
||||
server.hll_sparse_max_bytes);
|
||||
config_get_numerical_field("lua-time-limit",server.lua_time_limit);
|
||||
config_get_numerical_field("slowlog-log-slower-than",
|
||||
server.slowlog_log_slower_than);
|
||||
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);
|
||||
@@ -1011,9 +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);
|
||||
config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor);
|
||||
|
||||
/* Bool (yes/no) values */
|
||||
config_get_bool_field("no-appendfsync-on-rewrite",
|
||||
@@ -1436,7 +1368,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*);
|
||||
@@ -1448,7 +1380,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);
|
||||
@@ -1481,7 +1413,7 @@ 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. */
|
||||
@@ -1505,9 +1437,8 @@ 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) {
|
||||
* in the file to be removed. */
|
||||
if (server.masterhost == NULL) {
|
||||
rewriteConfigMarkAsProcessed(state,"slaveof");
|
||||
return;
|
||||
}
|
||||
@@ -1721,7 +1652,6 @@ int rewriteConfig(char *path) {
|
||||
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);
|
||||
@@ -1779,11 +1709,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,"cluster-slave-validity-factor",server.cluster_slave_validity_factor,REDIS_CLUSTER_DEFAULT_SLAVE_VALIDITY);
|
||||
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);
|
||||
@@ -1794,7 +1719,6 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,REDIS_SET_MAX_INTSET_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,REDIS_ZSET_MAX_ZIPLIST_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,REDIS_ZSET_MAX_ZIPLIST_VALUE);
|
||||
rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",server.hll_sparse_max_bytes,REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES);
|
||||
rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,REDIS_DEFAULT_ACTIVE_REHASHING);
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
rewriteConfigNumericalOption(state,"hz",server.hz,REDIS_DEFAULT_HZ);
|
||||
@@ -1829,7 +1753,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")) {
|
||||
@@ -1839,10 +1770,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 {
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ void setproctitle(const char *fmt, ...);
|
||||
|
||||
#if (__i386 || __amd64) && __GNUC__
|
||||
#define GNUC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
|
||||
#if (GNUC_VERSION >= 40100) || defined(__clang__)
|
||||
#if GNUC_VERSION >= 40100
|
||||
#define HAVE_ATOMIC
|
||||
#endif
|
||||
#endif
|
||||
|
||||
-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);
|
||||
}
|
||||
|
||||
@@ -280,7 +236,6 @@ void delCommand(redisClient *c) {
|
||||
int deleted = 0, j;
|
||||
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
expireIfNeeded(c->db,c->argv[j]);
|
||||
if (dbDelete(c->db,c->argv[j])) {
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
|
||||
@@ -308,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 {
|
||||
@@ -537,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,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.");
|
||||
@@ -707,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;
|
||||
@@ -811,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.
|
||||
@@ -833,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++;
|
||||
@@ -969,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);
|
||||
@@ -990,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
|
||||
@@ -1057,148 +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;
|
||||
}
|
||||
|
||||
/* Remove all the keys in the specified hash slot.
|
||||
* The number of removed items is returned. */
|
||||
unsigned int delKeysInSlot(unsigned int hashslot) {
|
||||
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) {
|
||||
robj *key = n->obj;
|
||||
n = n->level[0].forward; /* Go to the next item before freeing it. */
|
||||
incrRefCount(key); /* Protect the object while freeing it. */
|
||||
dbDelete(&server.db[0],key);
|
||||
decrRefCount(key);
|
||||
j++;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
+8
-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,7 +308,7 @@ void debugCommand(redisClient *c) {
|
||||
val = dictGetVal(de);
|
||||
key = dictGetKey(de);
|
||||
|
||||
if (val->type != REDIS_STRING || !sdsEncodedObject(val)) {
|
||||
if (val->type != REDIS_STRING || val->encoding != REDIS_ENCODING_RAW) {
|
||||
addReplyError(c,"Not an sds encoded string.");
|
||||
} else {
|
||||
addReplyStatusFormat(c,
|
||||
@@ -326,7 +326,6 @@ void debugCommand(redisClient *c) {
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != REDIS_OK)
|
||||
return;
|
||||
dictExpand(c->db->dict,keys);
|
||||
for (j = 0; j < keys; j++) {
|
||||
snprintf(buf,sizeof(buf),"key:%lu",j);
|
||||
key = createStringObject(buf,strlen(buf));
|
||||
@@ -364,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);
|
||||
@@ -422,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",
|
||||
@@ -438,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));
|
||||
@@ -702,7 +678,7 @@ void logCurrentClient(void) {
|
||||
int j;
|
||||
|
||||
redisLog(REDIS_WARNING, "--- CURRENT CLIENT INFO");
|
||||
client = catClientInfoString(sdsempty(),cc);
|
||||
client = getClientInfoString(cc);
|
||||
redisLog(REDIS_WARNING,"client: %s", client);
|
||||
sdsfree(client);
|
||||
for (j = 0; j < cc->argc; j++) {
|
||||
@@ -947,7 +923,7 @@ void enableWatchdog(int period) {
|
||||
/* Watchdog was actually disabled, so we have to setup the signal
|
||||
* handler. */
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
||||
act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_SIGINFO;
|
||||
act.sa_sigaction = watchdogSignalHandler;
|
||||
sigaction(SIGALRM, &act, NULL);
|
||||
}
|
||||
|
||||
+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
|
||||
|
||||
+2
-73
@@ -14,8 +14,7 @@ static char *commandGroups[] = {
|
||||
"transactions",
|
||||
"connection",
|
||||
"server",
|
||||
"scripting",
|
||||
"hyperloglog"
|
||||
"scripting"
|
||||
};
|
||||
|
||||
struct commandHelp {
|
||||
@@ -55,11 +54,6 @@ struct commandHelp {
|
||||
"Perform bitwise operations between strings",
|
||||
1,
|
||||
"2.6.0" },
|
||||
{ "BITPOS",
|
||||
"key bit [start] [end]",
|
||||
"Find first bit set or clear in a string",
|
||||
1,
|
||||
"2.8.7" },
|
||||
{ "BLPOP",
|
||||
"key [key ...] timeout",
|
||||
"Remove and get the first element in a list, or block until one is available",
|
||||
@@ -90,11 +84,6 @@ struct commandHelp {
|
||||
"Get the list of client connections",
|
||||
9,
|
||||
"2.4.0" },
|
||||
{ "CLIENT PAUSE",
|
||||
"timeout",
|
||||
"Stop processing commands from clients for some time",
|
||||
9,
|
||||
"2.9.50" },
|
||||
{ "CLIENT SETNAME",
|
||||
"connection-name",
|
||||
"Set the current connection name",
|
||||
@@ -110,11 +99,6 @@ struct commandHelp {
|
||||
"Reset the stats returned by INFO",
|
||||
9,
|
||||
"2.0.0" },
|
||||
{ "CONFIG REWRITE",
|
||||
"-",
|
||||
"Rewrite the configuration file with the in memory configuration",
|
||||
9,
|
||||
"2.8.0" },
|
||||
{ "CONFIG SET",
|
||||
"parameter value",
|
||||
"Set a configuration parameter to the given value",
|
||||
@@ -275,11 +259,6 @@ struct commandHelp {
|
||||
"Set multiple hash fields to multiple values",
|
||||
5,
|
||||
"2.0.0" },
|
||||
{ "HSCAN",
|
||||
"key cursor [MATCH pattern] [COUNT count]",
|
||||
"Incrementally iterate hash fields and associated values",
|
||||
5,
|
||||
"2.8.0" },
|
||||
{ "HSET",
|
||||
"key field value",
|
||||
"Set the string value of a hash field",
|
||||
@@ -381,7 +360,7 @@ struct commandHelp {
|
||||
1,
|
||||
"1.0.0" },
|
||||
{ "MIGRATE",
|
||||
"host port key destination-db timeout [COPY] [REPLACE]",
|
||||
"host port key destination-db timeout",
|
||||
"Atomically transfer a key from a Redis instance to another one.",
|
||||
0,
|
||||
"2.6.0" },
|
||||
@@ -430,21 +409,6 @@ struct commandHelp {
|
||||
"Set the expiration for a key as a UNIX timestamp specified in milliseconds",
|
||||
0,
|
||||
"2.6.0" },
|
||||
{ "PFADD",
|
||||
"key element [element ...]",
|
||||
"Adds the specified elements to the specified HyperLogLog.",
|
||||
11,
|
||||
"2.8.9" },
|
||||
{ "PFCOUNT",
|
||||
"key [key ...]",
|
||||
"Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).",
|
||||
11,
|
||||
"2.8.9" },
|
||||
{ "PFMERGE",
|
||||
"destkey sourcekey [sourcekey ...]",
|
||||
"Merge N different HyperLogLogs into a single one.",
|
||||
11,
|
||||
"2.8.9" },
|
||||
{ "PING",
|
||||
"-",
|
||||
"Ping the server",
|
||||
@@ -470,11 +434,6 @@ struct commandHelp {
|
||||
"Post a message to a channel",
|
||||
6,
|
||||
"2.0.0" },
|
||||
{ "PUBSUB",
|
||||
"subcommand [argument [argument ...]]",
|
||||
"Inspect the state of the Pub/Sub subsystem",
|
||||
6,
|
||||
"2.8.0" },
|
||||
{ "PUNSUBSCRIBE",
|
||||
"[pattern [pattern ...]]",
|
||||
"Stop listening for messages posted to channels matching the given patterns",
|
||||
@@ -535,11 +494,6 @@ struct commandHelp {
|
||||
"Synchronously save the dataset to disk",
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "SCAN",
|
||||
"cursor [MATCH pattern] [COUNT count]",
|
||||
"Incrementally iterate the keys space",
|
||||
0,
|
||||
"2.8.0" },
|
||||
{ "SCARD",
|
||||
"key",
|
||||
"Get the number of members in a set",
|
||||
@@ -665,11 +619,6 @@ struct commandHelp {
|
||||
"Remove one or more members from a set",
|
||||
3,
|
||||
"1.0.0" },
|
||||
{ "SSCAN",
|
||||
"key cursor [MATCH pattern] [COUNT count]",
|
||||
"Incrementally iterate Set elements",
|
||||
3,
|
||||
"2.8.0" },
|
||||
{ "STRLEN",
|
||||
"key",
|
||||
"Get the length of the value stored in a key",
|
||||
@@ -750,21 +699,11 @@ struct commandHelp {
|
||||
"Intersect multiple sorted sets and store the resulting sorted set in a new key",
|
||||
4,
|
||||
"2.0.0" },
|
||||
{ "ZLEXCOUNT",
|
||||
"key min max",
|
||||
"Count the number of members in a sorted set between a given lexicographical range",
|
||||
4,
|
||||
"2.8.9" },
|
||||
{ "ZRANGE",
|
||||
"key start stop [WITHSCORES]",
|
||||
"Return a range of members in a sorted set, by index",
|
||||
4,
|
||||
"1.2.0" },
|
||||
{ "ZRANGEBYLEX",
|
||||
"key min max [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by lexicographical range",
|
||||
4,
|
||||
"2.8.9" },
|
||||
{ "ZRANGEBYSCORE",
|
||||
"key min max [WITHSCORES] [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by score",
|
||||
@@ -780,11 +719,6 @@ struct commandHelp {
|
||||
"Remove one or more members from a sorted set",
|
||||
4,
|
||||
"1.2.0" },
|
||||
{ "ZREMRANGEBYLEX",
|
||||
"key min max",
|
||||
"Remove all members in a sorted set between the given lexicographical range",
|
||||
4,
|
||||
"2.8.9" },
|
||||
{ "ZREMRANGEBYRANK",
|
||||
"key start stop",
|
||||
"Remove all members in a sorted set within the given indexes",
|
||||
@@ -810,11 +744,6 @@ struct commandHelp {
|
||||
"Determine the index of a member in a sorted set, with scores ordered from high to low",
|
||||
4,
|
||||
"2.0.0" },
|
||||
{ "ZSCAN",
|
||||
"key cursor [MATCH pattern] [COUNT count]",
|
||||
"Incrementally iterate sorted sets elements and associated scores",
|
||||
4,
|
||||
"2.8.0" },
|
||||
{ "ZSCORE",
|
||||
"key member",
|
||||
"Get the score associated with the given member in a sorted set",
|
||||
|
||||
-1548
File diff suppressed because it is too large
Load Diff
+247
@@ -0,0 +1,247 @@
|
||||
#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 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 (getLongLongFromObjectOrReply(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;
|
||||
}
|
||||
+61
-186
@@ -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,17 +97,12 @@ 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();
|
||||
c->peerid = NULL;
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||
if (fd != -1) listAddNodeTail(server.clients,c);
|
||||
@@ -198,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);
|
||||
@@ -214,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);
|
||||
@@ -237,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);
|
||||
@@ -262,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);
|
||||
@@ -278,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);
|
||||
@@ -299,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) {
|
||||
@@ -411,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);
|
||||
@@ -419,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);
|
||||
@@ -487,7 +469,7 @@ 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 {
|
||||
long n = (long)obj->ptr;
|
||||
@@ -552,7 +534,6 @@ void copyClientOutputBuffer(redisClient *dst, redisClient *src) {
|
||||
dst->reply_bytes = src->reply_bytes;
|
||||
}
|
||||
|
||||
#define MAX_ACCEPTS_PER_CALL 1000
|
||||
static void acceptCommonHandler(int fd, int flags) {
|
||||
redisClient *c;
|
||||
if ((c = createClient(fd)) == NULL) {
|
||||
@@ -582,44 +563,37 @@ static void acceptCommonHandler(int fd, int flags) {
|
||||
}
|
||||
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
|
||||
int cport, cfd;
|
||||
char cip[REDIS_IP_STR_LEN];
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
while(max--) {
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (errno != EWOULDBLOCK)
|
||||
redisLog(REDIS_WARNING,
|
||||
"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
acceptCommonHandler(cfd,0);
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == AE_ERR) {
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
acceptCommonHandler(cfd,0);
|
||||
}
|
||||
|
||||
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cfd, max = MAX_ACCEPTS_PER_CALL;
|
||||
int cfd;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
while(max--) {
|
||||
cfd = anetUnixAccept(server.neterr, fd);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (errno != EWOULDBLOCK)
|
||||
redisLog(REDIS_WARNING,
|
||||
"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket);
|
||||
acceptCommonHandler(cfd,REDIS_UNIX_SOCKET);
|
||||
cfd = anetUnixAccept(server.neterr, fd);
|
||||
if (cfd == AE_ERR) {
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket);
|
||||
acceptCommonHandler(cfd,REDIS_UNIX_SOCKET);
|
||||
}
|
||||
|
||||
|
||||
static void freeClientArgv(redisClient *c) {
|
||||
int j;
|
||||
for (j = 0; j < c->argc; j++)
|
||||
@@ -676,7 +650,7 @@ void freeClient(redisClient *c) {
|
||||
}
|
||||
|
||||
/* Log link disconnection with slave */
|
||||
if ((c->flags & REDIS_SLAVE) && !(c->flags & REDIS_MONITOR)) {
|
||||
if (c->flags & REDIS_SLAVE) {
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
|
||||
if (anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1) {
|
||||
@@ -690,7 +664,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 */
|
||||
@@ -731,10 +706,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);
|
||||
@@ -764,7 +737,6 @@ void freeClient(redisClient *c) {
|
||||
if (c->name) decrRefCount(c->name);
|
||||
zfree(c->argv);
|
||||
freeClientMultiState(c);
|
||||
sdsfree(c->peerid);
|
||||
zfree(c);
|
||||
}
|
||||
|
||||
@@ -813,11 +785,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;
|
||||
}
|
||||
|
||||
@@ -873,16 +844,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) {
|
||||
@@ -948,7 +915,7 @@ int processInlineBuffer(redisClient *c) {
|
||||
* multi bulk requests idempotent. */
|
||||
static void setProtocolError(redisClient *c, int pos) {
|
||||
if (server.verbosity >= REDIS_VERBOSE) {
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
sds client = getClientInfoString(c);
|
||||
redisLog(REDIS_VERBOSE,
|
||||
"Protocol error from client: %s", client);
|
||||
sdsfree(client);
|
||||
@@ -1010,10 +977,8 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
newline = strchr(c->querybuf+pos,'\r');
|
||||
if (newline == NULL) {
|
||||
if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {
|
||||
addReplyError(c,
|
||||
"Protocol error: too big bulk count string");
|
||||
addReplyError(c,"Protocol error: too big bulk count string");
|
||||
setProtocolError(c,0);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1098,9 +1063,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;
|
||||
|
||||
@@ -1186,7 +1148,7 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
if (sdslen(c->querybuf) > server.client_max_querybuf_len) {
|
||||
sds ci = catClientInfoString(sdsempty(),c), bytes = sdsempty();
|
||||
sds ci = getClientInfoString(c), bytes = sdsempty();
|
||||
|
||||
bytes = sdscatrepr(bytes,c->querybuf,64);
|
||||
redisLog(REDIS_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes);
|
||||
@@ -1217,7 +1179,7 @@ void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
*biggest_input_buffer = bib;
|
||||
}
|
||||
|
||||
/* This is a helper function for genClientPeerId().
|
||||
/* This is a helper function for getClientPeerId().
|
||||
* It writes the specified ip/port to "peerid" as a null termiated string
|
||||
* in the form ip:port if ip does not contain ":" itself, otherwise
|
||||
* [ip]:port format is used (for IPv6 addresses basically). */
|
||||
@@ -1241,7 +1203,7 @@ void formatPeerId(char *peerid, size_t peerid_len, char *ip, int port) {
|
||||
* On failure the function still populates 'peerid' with the "?:0" string
|
||||
* in case you want to relax error checking or need to display something
|
||||
* anyway (see anetPeerToString implementation for more info). */
|
||||
int genClientPeerId(redisClient *client, char *peerid, size_t peerid_len) {
|
||||
int getClientPeerId(redisClient *client, char *peerid, size_t peerid_len) {
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
int port;
|
||||
|
||||
@@ -1257,26 +1219,12 @@ int genClientPeerId(redisClient *client, char *peerid, size_t peerid_len) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This function returns the client peer id, by creating and caching it
|
||||
* if client->perrid is NULL, otherwise returning the cached value.
|
||||
* The Peer ID never changes during the life of the client, however it
|
||||
* is expensive to compute. */
|
||||
char *getClientPeerId(redisClient *c) {
|
||||
char peerid[REDIS_PEER_ID_LEN];
|
||||
|
||||
if (c->peerid == NULL) {
|
||||
genClientPeerId(c,peerid,sizeof(peerid));
|
||||
c->peerid = sdsnew(peerid);
|
||||
}
|
||||
return c->peerid;
|
||||
}
|
||||
|
||||
/* Concatenate a string representing the state of a client in an human
|
||||
* readable format, into the sds string 's'. */
|
||||
sds catClientInfoString(sds s, redisClient *client) {
|
||||
char flags[16], events[3], *p;
|
||||
/* Turn a Redis client into an sds string representing its state. */
|
||||
sds getClientInfoString(redisClient *client) {
|
||||
char peerid[REDIS_PEER_ID_LEN], flags[16], events[3], *p;
|
||||
int emask;
|
||||
|
||||
getClientPeerId(client,peerid,sizeof(peerid));
|
||||
p = flags;
|
||||
if (client->flags & REDIS_SLAVE) {
|
||||
if (client->flags & REDIS_MONITOR)
|
||||
@@ -1292,7 +1240,6 @@ sds catClientInfoString(sds s, 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';
|
||||
|
||||
@@ -1301,23 +1248,23 @@ sds catClientInfoString(sds s, redisClient *client) {
|
||||
if (emask & AE_READABLE) *p++ = 'r';
|
||||
if (emask & AE_WRITABLE) *p++ = 'w';
|
||||
*p = '\0';
|
||||
return sdscatfmt(s,
|
||||
"addr=%s fd=%i name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U obl=%U oll=%U omem=%U events=%s cmd=%s",
|
||||
getClientPeerId(client),
|
||||
return sdscatprintf(sdsempty(),
|
||||
"addr=%s fd=%d name=%s age=%ld idle=%ld flags=%s db=%d sub=%d psub=%d multi=%d qbuf=%lu qbuf-free=%lu obl=%lu oll=%lu omem=%lu events=%s cmd=%s",
|
||||
peerid,
|
||||
client->fd,
|
||||
client->name ? (char*)client->name->ptr : "",
|
||||
(long long)(server.unixtime - client->ctime),
|
||||
(long long)(server.unixtime - client->lastinteraction),
|
||||
(long)(server.unixtime - client->ctime),
|
||||
(long)(server.unixtime - client->lastinteraction),
|
||||
flags,
|
||||
client->db->id,
|
||||
(int) dictSize(client->pubsub_channels),
|
||||
(int) listLength(client->pubsub_patterns),
|
||||
(client->flags & REDIS_MULTI) ? client->mstate.count : -1,
|
||||
(unsigned long long) sdslen(client->querybuf),
|
||||
(unsigned long long) sdsavail(client->querybuf),
|
||||
(unsigned long long) client->bufpos,
|
||||
(unsigned long long) listLength(client->reply),
|
||||
(unsigned long long) getClientOutputBufferMemoryUsage(client),
|
||||
(unsigned long) sdslen(client->querybuf),
|
||||
(unsigned long) sdsavail(client->querybuf),
|
||||
(unsigned long) client->bufpos,
|
||||
(unsigned long) listLength(client->reply),
|
||||
getClientOutputBufferMemoryUsage(client),
|
||||
events,
|
||||
client->lastcmd ? client->lastcmd->name : "NULL");
|
||||
}
|
||||
@@ -1328,11 +1275,14 @@ sds getAllClientsInfoString(void) {
|
||||
redisClient *client;
|
||||
sds o = sdsempty();
|
||||
|
||||
o = sdsMakeRoomFor(o,200*listLength(server.clients));
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds cs;
|
||||
|
||||
client = listNodeValue(ln);
|
||||
o = catClientInfoString(o,client);
|
||||
cs = getClientInfoString(client);
|
||||
o = sdscatsds(o,cs);
|
||||
sdsfree(cs);
|
||||
o = sdscatlen(o,"\n",1);
|
||||
}
|
||||
return o;
|
||||
@@ -1350,10 +1300,11 @@ void clientCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"kill") && c->argc == 3) {
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
char *peerid;
|
||||
char peerid[REDIS_PEER_ID_LEN];
|
||||
|
||||
client = listNodeValue(ln);
|
||||
peerid = getClientPeerId(client);
|
||||
if (getClientPeerId(client,peerid,sizeof(peerid)) == REDIS_ERR)
|
||||
continue;
|
||||
if (strcmp(peerid,c->argv[2]->ptr) == 0) {
|
||||
addReply(c,shared.ok);
|
||||
if (c == client) {
|
||||
@@ -1398,13 +1349,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)");
|
||||
}
|
||||
@@ -1559,7 +1503,7 @@ void asyncCloseClientOnOutputBufferLimitReached(redisClient *c) {
|
||||
redisAssert(c->reply_bytes < ULONG_MAX-(1024*64));
|
||||
if (c->reply_bytes == 0 || c->flags & REDIS_CLOSE_ASAP) return;
|
||||
if (checkClientOutputBufferLimits(c)) {
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
sds client = getClientInfoString(c);
|
||||
|
||||
freeClientAsync(c);
|
||||
redisLog(REDIS_WARNING,"Client %s scheduled to be closed ASAP for overcoming of output buffer limits.", client);
|
||||
@@ -1587,72 +1531,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;
|
||||
}
|
||||
|
||||
/* This function is called by Redis in order to process a few events from
|
||||
* time to time while blocked into some not interruptible operation.
|
||||
* This allows to reply to clients with the -LOADING error while loading the
|
||||
* data set at startup or after a full resynchronization with the master
|
||||
* and so forth.
|
||||
*
|
||||
* It calls the event loop in order to process a few events. Specifically we
|
||||
* try to call the event loop for times as long as we receive acknowledge that
|
||||
* some event was processed, in order to go forward with the accept, read,
|
||||
* write, close sequence needed to serve a client.
|
||||
*
|
||||
* The function returns the total number of events processed. */
|
||||
int processEventsWhileBlocked(void) {
|
||||
int iterations = 4; /* See the function top-comment. */
|
||||
int count = 0;
|
||||
while (iterations--) {
|
||||
int events = aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
if (!events) break;
|
||||
count += events;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
+65
-153
@@ -40,52 +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.
|
||||
*
|
||||
* The current limit of 39 is chosen so that the biggest string object
|
||||
* we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
|
||||
#define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 39
|
||||
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) {
|
||||
@@ -129,33 +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;
|
||||
default:
|
||||
redisPanic("Wrong encoding.");
|
||||
break;
|
||||
}
|
||||
redisAssertWithInfo(NULL,o,o->encoding == REDIS_ENCODING_RAW);
|
||||
return createStringObject(o->ptr,sdslen(o->ptr));
|
||||
}
|
||||
|
||||
robj *createListObject(void) {
|
||||
@@ -344,76 +280,57 @@ robj *tryObjectEncoding(robj *o) {
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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).
|
||||
@@ -421,7 +338,7 @@ robj *tryObjectEncoding(robj *o) {
|
||||
robj *getDecodedObject(robj *o) {
|
||||
robj *dec;
|
||||
|
||||
if (sdsEncodedObject(o)) {
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
incrRefCount(o);
|
||||
return o;
|
||||
}
|
||||
@@ -453,19 +370,19 @@ int compareStringObjectsWithFlags(robj *a, robj *b, int flags) {
|
||||
size_t alen, blen, minlen;
|
||||
|
||||
if (a == b) return 0;
|
||||
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);
|
||||
@@ -494,10 +411,7 @@ 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 (a->encoding == REDIS_ENCODING_INT &&
|
||||
b->encoding == REDIS_ENCODING_INT){
|
||||
/* 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;
|
||||
@@ -506,7 +420,7 @@ int equalStringObjects(robj *a, robj *b) {
|
||||
|
||||
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 {
|
||||
char buf[32];
|
||||
@@ -523,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]) ||
|
||||
@@ -565,7 +479,7 @@ 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' ||
|
||||
@@ -603,7 +517,7 @@ 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' ||
|
||||
@@ -658,24 +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";
|
||||
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;
|
||||
@@ -707,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -325,7 +325,7 @@ int rdbSaveStringObject(rio *rdb, robj *obj) {
|
||||
if (obj->encoding == REDIS_ENCODING_INT) {
|
||||
return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);
|
||||
} else {
|
||||
redisAssertWithInfo(NULL,obj,sdsEncodedObject(obj));
|
||||
redisAssertWithInfo(NULL,obj,obj->encoding == REDIS_ENCODING_RAW);
|
||||
return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr));
|
||||
}
|
||||
}
|
||||
@@ -398,7 +398,7 @@ int rdbSaveDoubleValue(rio *rdb, double val) {
|
||||
double min = -4503599627370495; /* (2^52)-1 */
|
||||
double max = 4503599627370496; /* -(2^52) */
|
||||
if (val > min && val < max && val == ((double)((long long)val)))
|
||||
ll2string((char*)buf+1,sizeof(buf)-1,(long long)val);
|
||||
ll2string((char*)buf+1,sizeof(buf),(long long)val);
|
||||
else
|
||||
#endif
|
||||
snprintf((char*)buf+1,sizeof(buf)-1,"%.17g",val);
|
||||
@@ -410,7 +410,7 @@ int rdbSaveDoubleValue(rio *rdb, double val) {
|
||||
|
||||
/* For information about double serialization check rdbSaveDoubleValue() */
|
||||
int rdbLoadDoubleValue(rio *rdb, double *val) {
|
||||
char buf[256];
|
||||
char buf[128];
|
||||
unsigned char len;
|
||||
|
||||
if (rioRead(rdb,&len,1) == 0) return -1;
|
||||
@@ -690,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. */
|
||||
@@ -796,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);
|
||||
|
||||
@@ -842,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 {
|
||||
@@ -870,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);
|
||||
@@ -903,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);
|
||||
@@ -1065,14 +1066,10 @@ 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);
|
||||
processEventsWhileBlocked();
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+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.
|
||||
|
||||
-1291
File diff suppressed because it is too large
Load Diff
+145
-554
File diff suppressed because it is too large
Load Diff
+37
-169
@@ -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
|
||||
@@ -113,7 +112,7 @@
|
||||
#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_MAXMEMORY_SAMPLES 3
|
||||
#define REDIS_DEFAULT_AOF_FILENAME "appendonly.aof"
|
||||
#define REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE 0
|
||||
#define REDIS_DEFAULT_ACTIVE_REHASHING 1
|
||||
@@ -123,7 +122,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 */
|
||||
@@ -140,9 +138,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% */
|
||||
@@ -161,7 +159,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
|
||||
@@ -181,7 +178,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 */
|
||||
|
||||
/* 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
|
||||
@@ -234,13 +230,6 @@
|
||||
#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. */
|
||||
|
||||
/* Client request types */
|
||||
#define REDIS_REQ_INLINE 1
|
||||
@@ -312,9 +301,6 @@
|
||||
#define REDIS_ZSET_MAX_ZIPLIST_ENTRIES 128
|
||||
#define REDIS_ZSET_MAX_ZIPLIST_VALUE 64
|
||||
|
||||
/* HyperLogLog defines */
|
||||
#define REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES 3000
|
||||
|
||||
/* Sets operations codes */
|
||||
#define REDIS_OP_UNION 0
|
||||
#define REDIS_OP_DIFF 1
|
||||
@@ -327,7 +313,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 +367,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 +392,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 +416,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,20 +469,16 @@ 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) */
|
||||
sds peerid; /* Cached peer ID. */
|
||||
|
||||
/* Response buffer */
|
||||
int bufpos;
|
||||
@@ -548,9 +496,9 @@ struct sharedObjectsStruct {
|
||||
*emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
|
||||
*outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
|
||||
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
|
||||
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop,
|
||||
*lpush, *emptyscan, *minstring, *maxstring,
|
||||
*lpush, *emptyscan,
|
||||
*select[REDIS_SHARED_SELECT_CMDS],
|
||||
*integers[REDIS_SHARED_INTEGERS],
|
||||
*mbulkhdr[REDIS_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
|
||||
@@ -615,18 +563,16 @@ typedef struct redisOpArray {
|
||||
* Global server state
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
struct clusterState;
|
||||
|
||||
struct redisServer {
|
||||
/* General */
|
||||
pid_t pid; /* Main process pid. */
|
||||
char *configfile; /* Absolute config file path, or NULL */
|
||||
int hz; /* serverCron() calls frequency in hertz */
|
||||
redisDb *db;
|
||||
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 */
|
||||
@@ -637,7 +583,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 */
|
||||
@@ -645,16 +590,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;
|
||||
@@ -682,7 +622,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) */
|
||||
@@ -720,8 +659,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 */
|
||||
@@ -788,11 +725,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 */
|
||||
@@ -814,7 +748,6 @@ struct redisServer {
|
||||
size_t set_max_intset_entries;
|
||||
size_t zset_max_ziplist_entries;
|
||||
size_t zset_max_ziplist_value;
|
||||
size_t hll_sparse_max_bytes;
|
||||
time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
|
||||
/* Pubsub */
|
||||
@@ -822,20 +755,13 @@ struct redisServer {
|
||||
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. */
|
||||
int cluster_slave_validity_factor; /* Slave max data age for failover. */
|
||||
/* 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
|
||||
@@ -857,15 +783,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) */
|
||||
@@ -941,8 +866,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;
|
||||
@@ -994,8 +917,8 @@ void *dupClientReplyValue(void *o);
|
||||
void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
unsigned long *biggest_input_buffer);
|
||||
void formatPeerId(char *peerid, size_t peerid_len, char *ip, int port);
|
||||
char *getClientPeerId(redisClient *client);
|
||||
sds catClientInfoString(sds s, redisClient *client);
|
||||
int getClientPeerId(redisClient *client, char *peerid, size_t peerid_len);
|
||||
sds getClientInfoString(redisClient *client);
|
||||
sds getAllClientsInfoString(void);
|
||||
void rewriteClientCommandVector(redisClient *c, int argc, ...);
|
||||
void rewriteClientCommandArgument(redisClient *c, int i, robj *newval);
|
||||
@@ -1006,10 +929,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);
|
||||
int processEventsWhileBlocked(void);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
|
||||
@@ -1060,8 +979,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);
|
||||
@@ -1087,8 +1004,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 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 +1019,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);
|
||||
@@ -1138,31 +1050,23 @@ unsigned long aofRewriteBufferSize(void);
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
/* Struct to hold a inclusive/exclusive range spec by score comparison. */
|
||||
/* Struct to hold a inclusive/exclusive range spec. */
|
||||
typedef struct {
|
||||
double min, max;
|
||||
int minex, maxex; /* are min or max exclusive? */
|
||||
} zrangespec;
|
||||
|
||||
/* Struct to hold an inclusive/exclusive range spec by lexicographic comparison. */
|
||||
typedef struct {
|
||||
robj *min, *max; /* May be set to shared.(minstring|maxstring) */
|
||||
int minex, maxex; /* are min or max exclusive? */
|
||||
} zlexrangespec;
|
||||
|
||||
zskiplist *zslCreate(void);
|
||||
void zslFree(zskiplist *zsl);
|
||||
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);
|
||||
zskiplistNode *zslFirstInRange(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);
|
||||
@@ -1192,9 +1096,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);
|
||||
@@ -1266,33 +1167,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);
|
||||
unsigned int delKeysInSlot(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);
|
||||
@@ -1304,13 +1194,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);
|
||||
@@ -1402,16 +1285,12 @@ void zincrbyCommand(redisClient *c);
|
||||
void zrangeCommand(redisClient *c);
|
||||
void zrangebyscoreCommand(redisClient *c);
|
||||
void zrevrangebyscoreCommand(redisClient *c);
|
||||
void zrangebylexCommand(redisClient *c);
|
||||
void zrevrangebylexCommand(redisClient *c);
|
||||
void zcountCommand(redisClient *c);
|
||||
void zlexcountCommand(redisClient *c);
|
||||
void zrevrangeCommand(redisClient *c);
|
||||
void zcardCommand(redisClient *c);
|
||||
void zremCommand(redisClient *c);
|
||||
void zscoreCommand(redisClient *c);
|
||||
void zremrangebyscoreCommand(redisClient *c);
|
||||
void zremrangebylexCommand(redisClient *c);
|
||||
void multiCommand(redisClient *c);
|
||||
void execCommand(redisClient *c);
|
||||
void discardCommand(redisClient *c);
|
||||
@@ -1449,12 +1328,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);
|
||||
@@ -1464,14 +1339,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 pfdebugCommand(redisClient *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
|
||||
+23
-203
@@ -39,7 +39,6 @@
|
||||
|
||||
void replicationDiscardCachedMaster(void);
|
||||
void replicationResurrectCachedMaster(int newfd);
|
||||
void replicationSendAck(void);
|
||||
|
||||
/* ---------------------------------- MASTER -------------------------------- */
|
||||
|
||||
@@ -239,6 +238,7 @@ void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **
|
||||
int j;
|
||||
sds cmdrepr = sdsnew("+");
|
||||
robj *cmdobj;
|
||||
char peerid[REDIS_PEER_ID_LEN];
|
||||
struct timeval tv;
|
||||
|
||||
gettimeofday(&tv,NULL);
|
||||
@@ -248,7 +248,8 @@ void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **
|
||||
} else if (c->flags & REDIS_UNIX_SOCKET) {
|
||||
cmdrepr = sdscatprintf(cmdrepr,"[%d unix:%s] ",dictid,server.unixsocket);
|
||||
} else {
|
||||
cmdrepr = sdscatprintf(cmdrepr,"[%d %s] ",dictid,getClientPeerId(c));
|
||||
getClientPeerId(c,peerid,sizeof(peerid));
|
||||
cmdrepr = sdscatprintf(cmdrepr,"[%d %s] ",dictid,peerid);
|
||||
}
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
@@ -342,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.");
|
||||
@@ -446,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
|
||||
@@ -559,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);
|
||||
@@ -580,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) {
|
||||
@@ -611,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;
|
||||
@@ -627,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;
|
||||
}
|
||||
@@ -671,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);
|
||||
@@ -1253,7 +1238,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. */
|
||||
@@ -1262,7 +1247,6 @@ void replicationSetMaster(char *ip, int port) {
|
||||
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. */
|
||||
@@ -1287,15 +1271,6 @@ void replicationUnsetMaster(void) {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1385,12 +1360,6 @@ void replicationCacheMaster(redisClient *c) {
|
||||
/* Set fd to -1 so that we can safely call freeClient(c) later. */
|
||||
c->fd = -1;
|
||||
|
||||
/* Invalidate the Peer ID cache. */
|
||||
if (c->peerid) {
|
||||
sdsfree(c->peerid);
|
||||
c->peerid = NULL;
|
||||
}
|
||||
|
||||
/* Caching the master happens instead of the actual freeClient() call,
|
||||
* so make sure to adjust the replication state. This function will
|
||||
* also set server.master to NULL. */
|
||||
@@ -1550,156 +1519,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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+27
-106
@@ -200,20 +200,13 @@ void luaSortArray(lua_State *lua) {
|
||||
lua_pop(lua,1); /* Stack: array (sorted) */
|
||||
}
|
||||
|
||||
#define LUA_CMD_OBJCACHE_SIZE 32
|
||||
#define LUA_CMD_OBJCACHE_MAX_LEN 64
|
||||
int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
int j, argc = lua_gettop(lua);
|
||||
struct redisCommand *cmd;
|
||||
robj **argv;
|
||||
redisClient *c = server.lua_client;
|
||||
sds reply;
|
||||
|
||||
/* Cached across calls. */
|
||||
static robj **argv = NULL;
|
||||
static int argv_size = 0;
|
||||
static robj *cached_objects[LUA_CMD_OBJCACHE_SIZE];
|
||||
static int cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
|
||||
|
||||
/* Require at least one argument */
|
||||
if (argc == 0) {
|
||||
luaPushError(lua,
|
||||
@@ -222,37 +215,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
}
|
||||
|
||||
/* Build the arguments vector */
|
||||
if (!argv) {
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
} else if (argv_size < argc) {
|
||||
argv = zrealloc(argv,sizeof(robj*)*argc);
|
||||
argv_size = argc;
|
||||
}
|
||||
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
char *obj_s;
|
||||
size_t obj_len;
|
||||
|
||||
obj_s = (char*)lua_tolstring(lua,j+1,&obj_len);
|
||||
if (obj_s == NULL) break; /* Not a string. */
|
||||
|
||||
/* Try to use a cached object. */
|
||||
if (j < LUA_CMD_OBJCACHE_SIZE && cached_objects[j] &&
|
||||
cached_objects_len[j] >= obj_len)
|
||||
{
|
||||
char *s = cached_objects[j]->ptr;
|
||||
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
|
||||
|
||||
argv[j] = cached_objects[j];
|
||||
cached_objects[j] = NULL;
|
||||
memcpy(s,obj_s,obj_len+1);
|
||||
sh->free += sh->len - obj_len;
|
||||
sh->len = obj_len;
|
||||
} else {
|
||||
argv[j] = createStringObject(obj_s, obj_len);
|
||||
}
|
||||
if (!lua_isstring(lua,j+1)) break;
|
||||
argv[j] = createStringObject((char*)lua_tostring(lua,j+1),
|
||||
lua_strlen(lua,j+1));
|
||||
}
|
||||
|
||||
|
||||
/* Check if one of the arguments passed by the Lua script
|
||||
* is not a string or an integer (lua_isstring() return true for
|
||||
* integers as well). */
|
||||
@@ -262,6 +231,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
decrRefCount(argv[j]);
|
||||
j--;
|
||||
}
|
||||
zfree(argv);
|
||||
luaPushError(lua,
|
||||
"Lua redis() command arguments must be strings or integers");
|
||||
return 1;
|
||||
@@ -336,22 +306,16 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
/* Convert the result of the Redis command into a suitable Lua type.
|
||||
* The first thing we need is to create a single string from the client
|
||||
* output buffers. */
|
||||
if (listLength(c->reply) == 0 && c->bufpos < REDIS_REPLY_CHUNK_BYTES) {
|
||||
/* This is a fast path for the common case of a reply inside the
|
||||
* client static buffer. Don't create an SDS string but just use
|
||||
* the client buffer directly. */
|
||||
c->buf[c->bufpos] = '\0';
|
||||
reply = c->buf;
|
||||
reply = sdsempty();
|
||||
if (c->bufpos) {
|
||||
reply = sdscatlen(reply,c->buf,c->bufpos);
|
||||
c->bufpos = 0;
|
||||
} else {
|
||||
reply = sdsnewlen(c->buf,c->bufpos);
|
||||
c->bufpos = 0;
|
||||
while(listLength(c->reply)) {
|
||||
robj *o = listNodeValue(listFirst(c->reply));
|
||||
}
|
||||
while(listLength(c->reply)) {
|
||||
robj *o = listNodeValue(listFirst(c->reply));
|
||||
|
||||
reply = sdscatlen(reply,o->ptr,sdslen(o->ptr));
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
}
|
||||
reply = sdscatlen(reply,o->ptr,sdslen(o->ptr));
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
}
|
||||
if (raise_error && reply[0] != '-') raise_error = 0;
|
||||
redisProtocolToLuaType(lua,reply);
|
||||
@@ -361,38 +325,15 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
(reply[0] == '*' && reply[1] != '-')) {
|
||||
luaSortArray(lua);
|
||||
}
|
||||
if (reply != c->buf) sdsfree(reply);
|
||||
sdsfree(reply);
|
||||
c->reply_bytes = 0;
|
||||
|
||||
cleanup:
|
||||
/* Clean up. Command code may have changed argv/argc so we use the
|
||||
* argv/argc of the client instead of the local variables. */
|
||||
for (j = 0; j < c->argc; j++) {
|
||||
robj *o = c->argv[j];
|
||||
|
||||
/* Try to cache the object in the cached_objects array.
|
||||
* The object must be small, SDS-encoded, and with refcount = 1
|
||||
* (we must be the only owner) for us to cache it. */
|
||||
if (j < LUA_CMD_OBJCACHE_SIZE &&
|
||||
o->refcount == 1 &&
|
||||
(o->encoding == REDIS_ENCODING_RAW ||
|
||||
o->encoding == REDIS_ENCODING_EMBSTR) &&
|
||||
sdslen(o->ptr) <= LUA_CMD_OBJCACHE_MAX_LEN)
|
||||
{
|
||||
struct sdshdr *sh = (void*)(((char*)(o->ptr))-(sizeof(struct sdshdr)));
|
||||
|
||||
if (cached_objects[j]) decrRefCount(cached_objects[j]);
|
||||
cached_objects[j] = o;
|
||||
cached_objects_len[j] = sh->free + sh->len;
|
||||
} else {
|
||||
decrRefCount(o);
|
||||
}
|
||||
}
|
||||
|
||||
if (c->argv != argv) {
|
||||
zfree(c->argv);
|
||||
argv = NULL;
|
||||
}
|
||||
for (j = 0; j < c->argc; j++)
|
||||
decrRefCount(c->argv[j]);
|
||||
zfree(c->argv);
|
||||
|
||||
if (raise_error) {
|
||||
/* If we are here we should have an error in the stack, in the
|
||||
@@ -500,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;
|
||||
@@ -511,7 +452,8 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
* here when the EVAL command will return. */
|
||||
aeDeleteFileEvent(server.el, server.lua_caller->fd, AE_READABLE);
|
||||
}
|
||||
if (server.lua_timedout) processEventsWhileBlocked();
|
||||
if (server.lua_timedout)
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
if (server.lua_kill) {
|
||||
redisLog(REDIS_WARNING,"Lua script killed by user with SCRIPT KILL.");
|
||||
lua_pushstring(lua,"Script killed by user with SCRIPT KILL...");
|
||||
@@ -534,7 +476,7 @@ void luaLoadLibraries(lua_State *lua) {
|
||||
luaLoadLib(lua, LUA_TABLIBNAME, luaopen_table);
|
||||
luaLoadLib(lua, LUA_STRLIBNAME, luaopen_string);
|
||||
luaLoadLib(lua, LUA_MATHLIBNAME, luaopen_math);
|
||||
luaLoadLib(lua, LUA_DBLIBNAME, luaopen_debug);
|
||||
luaLoadLib(lua, LUA_DBLIBNAME, luaopen_debug);
|
||||
luaLoadLib(lua, "cjson", luaopen_cjson);
|
||||
luaLoadLib(lua, "struct", luaopen_struct);
|
||||
luaLoadLib(lua, "cmsgpack", luaopen_cmsgpack);
|
||||
@@ -914,12 +856,8 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
int j;
|
||||
char *sha = c->argv[1]->ptr;
|
||||
|
||||
/* Convert to lowercase. We don't use tolower since the function
|
||||
* managed to always show up in the profiler output consuming
|
||||
* a non trivial amount of time. */
|
||||
for (j = 0; j < 40; j++)
|
||||
funcname[j+2] = (sha[j] >= 'A' && sha[j] <= 'Z') ?
|
||||
sha[j]+('a'-'A') : sha[j];
|
||||
funcname[j+2] = tolower(sha[j]);
|
||||
funcname[42] = '\0';
|
||||
}
|
||||
|
||||
@@ -956,13 +894,13 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
|
||||
/* Select the right DB in the context of the Lua client */
|
||||
selectDb(server.lua_client,c->db->id);
|
||||
|
||||
|
||||
/* Set a hook in order to be able to stop the script execution if it
|
||||
* is running for too much time.
|
||||
* 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);
|
||||
@@ -985,23 +923,7 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
}
|
||||
server.lua_caller = NULL;
|
||||
selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */
|
||||
|
||||
/* Call the Lua garbage collector from time to time to avoid a
|
||||
* full cycle performed by Lua, which adds too latency.
|
||||
*
|
||||
* The call is performed every LUA_GC_CYCLE_PERIOD executed commands
|
||||
* (and for LUA_GC_CYCLE_PERIOD collection steps) because calling it
|
||||
* for every command uses too much CPU. */
|
||||
#define LUA_GC_CYCLE_PERIOD 50
|
||||
{
|
||||
static long gc_count = 0;
|
||||
|
||||
gc_count++;
|
||||
if (gc_count == LUA_GC_CYCLE_PERIOD) {
|
||||
lua_gc(lua,LUA_GCSTEP,LUA_GC_CYCLE_PERIOD);
|
||||
gc_count = 0;
|
||||
}
|
||||
}
|
||||
lua_gc(lua,LUA_GCSTEP,1);
|
||||
|
||||
if (err) {
|
||||
addReplyErrorFormat(c,"Error running script (call to %s): %s\n",
|
||||
@@ -1036,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,118 +289,27 @@ sds sdscpy(sds s, const char *t) {
|
||||
return sdscpylen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
/* Helper for sdscatlonglong() doing the actual number -> string
|
||||
* conversion. 's' must point to a string with room for at least
|
||||
* SDS_LLSTR_SIZE bytes.
|
||||
*
|
||||
* The function returns the lenght of the null-terminated string
|
||||
* representation stored at 's'. */
|
||||
#define SDS_LLSTR_SIZE 21
|
||||
int sdsll2str(char *s, long long value) {
|
||||
char *p, aux;
|
||||
unsigned long long v;
|
||||
size_t l;
|
||||
|
||||
/* Generate the string representation, this method produces
|
||||
* an reversed string. */
|
||||
v = (value < 0) ? -value : value;
|
||||
p = s;
|
||||
do {
|
||||
*p++ = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
if (value < 0) *p++ = '-';
|
||||
|
||||
/* Compute length and add null term. */
|
||||
l = p-s;
|
||||
*p = '\0';
|
||||
|
||||
/* Reverse the string. */
|
||||
p--;
|
||||
while(s < p) {
|
||||
aux = *s;
|
||||
*s = *p;
|
||||
*p = aux;
|
||||
s++;
|
||||
p--;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Identical sdsll2str(), but for unsigned long long type. */
|
||||
int sdsull2str(char *s, unsigned long long v) {
|
||||
char *p, aux;
|
||||
size_t l;
|
||||
|
||||
/* Generate the string representation, this method produces
|
||||
* an reversed string. */
|
||||
p = s;
|
||||
do {
|
||||
*p++ = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
|
||||
/* Compute length and add null term. */
|
||||
l = p-s;
|
||||
*p = '\0';
|
||||
|
||||
/* Reverse the string. */
|
||||
p--;
|
||||
while(s < p) {
|
||||
aux = *s;
|
||||
*s = *p;
|
||||
*p = aux;
|
||||
s++;
|
||||
p--;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Create an sds string from a long long value. It is much faster than:
|
||||
*
|
||||
* sdscatprintf(sdsempty(),"%lld\n", value);
|
||||
*/
|
||||
sds sdsfromlonglong(long long value) {
|
||||
char buf[SDS_LLSTR_SIZE];
|
||||
int len = sdsll2str(buf,value);
|
||||
|
||||
return sdsnewlen(buf,len);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
@@ -429,122 +338,6 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
return t;
|
||||
}
|
||||
|
||||
/* This function is similar to sdscatprintf, but much faster as it does
|
||||
* not rely on sprintf() family functions implemented by the libc that
|
||||
* are often very slow. Moreover directly handling the sds string as
|
||||
* new data is concatenated provides a performance improvement.
|
||||
*
|
||||
* However this function only handles an incompatible subset of printf-alike
|
||||
* format specifiers:
|
||||
*
|
||||
* %s - C String
|
||||
* %S - SDS string
|
||||
* %i - signed int
|
||||
* %I - 64 bit signed integer (long long, int64_t)
|
||||
* %u - unsigned int
|
||||
* %U - 64 bit unsigned integer (unsigned long long, uint64_t)
|
||||
* %% - Verbatim "%" character.
|
||||
*/
|
||||
sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t initlen = sdslen(s);
|
||||
const char *f = fmt;
|
||||
int i;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap,fmt);
|
||||
f = fmt; /* Next format specifier byte to process. */
|
||||
i = initlen; /* Position of the next byte to write to dest str. */
|
||||
while(*f) {
|
||||
char next, *str;
|
||||
size_t l;
|
||||
long long num;
|
||||
unsigned long long unum;
|
||||
|
||||
/* Make sure there is always space for at least 1 char. */
|
||||
if (sh->free == 0) {
|
||||
s = sdsMakeRoomFor(s,1);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
}
|
||||
|
||||
switch(*f) {
|
||||
case '%':
|
||||
next = *(f+1);
|
||||
f++;
|
||||
switch(next) {
|
||||
case 's':
|
||||
case 'S':
|
||||
str = va_arg(ap,char*);
|
||||
l = (next == 's') ? strlen(str) : sdslen(str);
|
||||
if (sh->free < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
}
|
||||
memcpy(s+i,str,l);
|
||||
sh->len += l;
|
||||
sh->free -= l;
|
||||
i += l;
|
||||
break;
|
||||
case 'i':
|
||||
case 'I':
|
||||
if (next == 'i')
|
||||
num = va_arg(ap,int);
|
||||
else
|
||||
num = va_arg(ap,long long);
|
||||
{
|
||||
char buf[SDS_LLSTR_SIZE];
|
||||
l = sdsll2str(buf,num);
|
||||
if (sh->free < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
}
|
||||
memcpy(s+i,buf,l);
|
||||
sh->len += l;
|
||||
sh->free -= l;
|
||||
i += l;
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
case 'U':
|
||||
if (next == 'u')
|
||||
unum = va_arg(ap,unsigned int);
|
||||
else
|
||||
unum = va_arg(ap,unsigned long long);
|
||||
{
|
||||
char buf[SDS_LLSTR_SIZE];
|
||||
l = sdsull2str(buf,unum);
|
||||
if (sh->free < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
}
|
||||
memcpy(s+i,buf,l);
|
||||
sh->len += l;
|
||||
sh->free -= l;
|
||||
i += l;
|
||||
}
|
||||
break;
|
||||
default: /* Handle %% and generally %<unknown>. */
|
||||
s[i++] = next;
|
||||
sh->len += 1;
|
||||
sh->free -= 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
s[i++] = *f;
|
||||
sh->len += 1;
|
||||
sh->free -= 1;
|
||||
break;
|
||||
}
|
||||
f++;
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
/* Add null-term */
|
||||
s[i] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Remove the part of the string from left and from right composed just of
|
||||
* contiguous characters found in 'cset', that is a null terminted C string.
|
||||
*
|
||||
@@ -732,6 +525,25 @@ void sdsfreesplitres(sds *tokens, int count) {
|
||||
zfree(tokens);
|
||||
}
|
||||
|
||||
/* Create an sds string from a long long value. It is much faster than:
|
||||
*
|
||||
* sdscatprintf(sdsempty(),"%lld\n", value);
|
||||
*/
|
||||
sds sdsfromlonglong(long long value) {
|
||||
char buf[32], *p;
|
||||
unsigned long long v;
|
||||
|
||||
v = (value < 0) ? -value : value;
|
||||
p = buf+31; /* point to the last character */
|
||||
do {
|
||||
*p-- = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
if (value < 0) *p-- = '-';
|
||||
p++;
|
||||
return sdsnewlen(p,32-(p-buf));
|
||||
}
|
||||
|
||||
/* Append to the sds string "s" an escaped string representation where
|
||||
* all the non-printable characters (tested with isprint()) are turned into
|
||||
* escapes in the form "\n\r\a...." or "\x<hex-number>".
|
||||
@@ -962,7 +774,6 @@ sds sdsjoin(char **argv, int argc, char *sep) {
|
||||
#ifdef SDS_TEST_MAIN
|
||||
#include <stdio.h>
|
||||
#include "testhelp.h"
|
||||
#include "limits.h"
|
||||
|
||||
int main(void) {
|
||||
{
|
||||
@@ -993,61 +804,39 @@ int main(void) {
|
||||
sdsfree(x);
|
||||
x = sdscatprintf(sdsempty(),"%d",123);
|
||||
test_cond("sdscatprintf() seems working in the base case",
|
||||
sdslen(x) == 3 && memcmp(x,"123\0",4) == 0)
|
||||
sdslen(x) == 3 && memcmp(x,"123\0",4) ==0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnew("--");
|
||||
x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX);
|
||||
test_cond("sdscatfmt() seems working in the base case",
|
||||
sdslen(x) == 60 &&
|
||||
memcmp(x,"--Hello Hi! World -9223372036854775808,"
|
||||
"9223372036854775807--",60) == 0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnew("--");
|
||||
x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX);
|
||||
test_cond("sdscatfmt() seems working with unsigned numbers",
|
||||
sdslen(x) == 35 &&
|
||||
memcmp(x,"--4294967295,18446744073709551615--",35) == 0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnew("xxciaoyyy");
|
||||
sdstrim(x,"xy");
|
||||
x = sdstrim(sdsnew("xxciaoyyy"),"xy");
|
||||
test_cond("sdstrim() correctly trims characters",
|
||||
sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
|
||||
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,1,1);
|
||||
y = sdsrange(sdsdup(x),1,1);
|
||||
test_cond("sdsrange(...,1,1)",
|
||||
sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,1,-1);
|
||||
y = sdsrange(sdsdup(x),1,-1);
|
||||
test_cond("sdsrange(...,1,-1)",
|
||||
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,-2,-1);
|
||||
y = sdsrange(sdsdup(x),-2,-1);
|
||||
test_cond("sdsrange(...,-2,-1)",
|
||||
sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,2,1);
|
||||
y = sdsrange(sdsdup(x),2,1);
|
||||
test_cond("sdsrange(...,2,1)",
|
||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,1,100);
|
||||
y = sdsrange(sdsdup(x),1,100);
|
||||
test_cond("sdsrange(...,1,100)",
|
||||
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,100,100);
|
||||
y = sdsrange(sdsdup(x),100,100);
|
||||
test_cond("sdsrange(...,100,100)",
|
||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
|
||||
|
||||
@@ -1069,13 +858,6 @@ int main(void) {
|
||||
y = sdsnew("bar");
|
||||
test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
|
||||
|
||||
sdsfree(y);
|
||||
sdsfree(x);
|
||||
x = sdsnewlen("\a\n\0foo\r",7);
|
||||
y = sdscatrepr(sdsempty(),x,sdslen(x));
|
||||
test_cond("sdscatrepr(...data...)",
|
||||
memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
|
||||
|
||||
{
|
||||
int oldfree;
|
||||
|
||||
|
||||
@@ -76,7 +76,6 @@ sds sdscatprintf(sds s, const char *fmt, ...)
|
||||
sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
sds sdscatfmt(sds s, char const *fmt, ...);
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
void sdsrange(sds s, int start, int end);
|
||||
void sdsupdatelen(sds s);
|
||||
|
||||
+153
-359
@@ -44,6 +44,8 @@ extern char **environ;
|
||||
|
||||
/* ======================== Sentinel global state =========================== */
|
||||
|
||||
typedef long long mstime_t; /* millisecond time type. */
|
||||
|
||||
/* Address object, used to describe an ip:port pair. */
|
||||
typedef struct sentinelAddr {
|
||||
char *ip;
|
||||
@@ -78,13 +80,12 @@ typedef struct sentinelAddr {
|
||||
#define SENTINEL_TILT_TRIGGER 2000
|
||||
#define SENTINEL_TILT_PERIOD (SENTINEL_PING_PERIOD*30)
|
||||
#define SENTINEL_DEFAULT_SLAVE_PRIORITY 100
|
||||
#define SENTINEL_SLAVE_RECONF_TIMEOUT 10000
|
||||
#define SENTINEL_SLAVE_RECONF_RETRY_PERIOD 10000
|
||||
#define SENTINEL_DEFAULT_PARALLEL_SYNCS 1
|
||||
#define SENTINEL_MIN_LINK_RECONNECT_PERIOD 15000
|
||||
#define SENTINEL_DEFAULT_FAILOVER_TIMEOUT (60*3*1000)
|
||||
#define SENTINEL_MAX_PENDING_COMMANDS 100
|
||||
#define SENTINEL_ELECTION_TIMEOUT 10000
|
||||
#define SENTINEL_MAX_DESYNC 1000
|
||||
|
||||
/* Failover machine different states. */
|
||||
#define SENTINEL_FAILOVER_STATE_NONE 0 /* No failover in progress. */
|
||||
@@ -129,10 +130,6 @@ typedef struct sentinelRedisInstance {
|
||||
mstime_t pc_last_activity; /* Last time we received any message. */
|
||||
mstime_t last_avail_time; /* Last time the instance replied to ping with
|
||||
a reply we consider valid. */
|
||||
mstime_t last_ping_time; /* Last time a pending ping was sent in the
|
||||
context of the current command connection
|
||||
with the instance. 0 if still not sent or
|
||||
if pong already received. */
|
||||
mstime_t last_pong_time; /* Last time the instance replied to ping,
|
||||
whatever the reply was. That's used to check
|
||||
if the link is idle and must be reconnected. */
|
||||
@@ -183,8 +180,6 @@ typedef struct sentinelRedisInstance {
|
||||
mstime_t failover_state_change_time;
|
||||
mstime_t failover_start_time; /* Last failover attempt start time. */
|
||||
mstime_t failover_timeout; /* Max time to refresh failover state. */
|
||||
mstime_t failover_delay_logged; /* For what failover_start_time value we
|
||||
logged the failover delay. */
|
||||
struct sentinelRedisInstance *promoted_slave; /* Promoted slave instance. */
|
||||
/* Scripts executed to notify admin or reconfigure clients: when they
|
||||
* are set to NULL no script is executed. */
|
||||
@@ -334,8 +329,6 @@ void sentinelDiscardReplyCallback(redisAsyncContext *c, void *reply, void *privd
|
||||
int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port);
|
||||
char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char *req_runid, uint64_t *leader_epoch);
|
||||
void sentinelFlushConfig(void);
|
||||
void sentinelGenerateInitialMonitorEvents(void);
|
||||
int sentinelSendPing(sentinelRedisInstance *ri);
|
||||
|
||||
/* ========================= Dictionary types =============================== */
|
||||
|
||||
@@ -378,7 +371,6 @@ dictType leaderVotesDictType = {
|
||||
void sentinelCommand(redisClient *c);
|
||||
void sentinelInfoCommand(redisClient *c);
|
||||
void sentinelSetCommand(redisClient *c);
|
||||
void sentinelPublishCommand(redisClient *c);
|
||||
|
||||
struct redisCommand sentinelcmds[] = {
|
||||
{"ping",pingCommand,1,"",0,NULL,0,0,0,0,0},
|
||||
@@ -387,9 +379,7 @@ struct redisCommand sentinelcmds[] = {
|
||||
{"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0},
|
||||
{"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
|
||||
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
|
||||
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0}
|
||||
};
|
||||
|
||||
/* This function overwrites a few normal Redis config default with Sentinel
|
||||
@@ -428,20 +418,10 @@ void initSentinel(void) {
|
||||
void sentinelIsRunning(void) {
|
||||
redisLog(REDIS_WARNING,"Sentinel runid is %s", server.runid);
|
||||
|
||||
if (server.configfile == NULL) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Sentinel started without a config file. Exiting...");
|
||||
exit(1);
|
||||
} else if (access(server.configfile,W_OK) == -1) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Sentinel config file %s is not writable: %s. Exiting...",
|
||||
server.configfile,strerror(errno));
|
||||
if (server.configfile == NULL || access(server.configfile,W_OK) == -1) {
|
||||
redisLog(REDIS_WARNING,"Sentinel started without a config file, or config file not writable. Exiting...");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* We want to generate a +monitor event for every configured master
|
||||
* at startup. */
|
||||
sentinelGenerateInitialMonitorEvents();
|
||||
}
|
||||
|
||||
/* ============================== sentinelAddr ============================== */
|
||||
@@ -493,7 +473,7 @@ int sentinelAddrIsEqual(sentinelAddr *a, sentinelAddr *b) {
|
||||
/* =========================== Events notification ========================== */
|
||||
|
||||
/* Send an event to log, pub/sub, user notification script.
|
||||
*
|
||||
*
|
||||
* 'level' is the log level for logging. Only REDIS_WARNING events will trigger
|
||||
* the execution of the user notification script.
|
||||
*
|
||||
@@ -573,22 +553,6 @@ void sentinelEvent(int level, char *type, sentinelRedisInstance *ri,
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called only at startup and is used to generate a
|
||||
* +monitor event for every configured master. The same events are also
|
||||
* generated when a master to monitor is added at runtime via the
|
||||
* SENTINEL MONITOR command. */
|
||||
void sentinelGenerateInitialMonitorEvents(void) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
di = dictGetIterator(sentinel.masters);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *ri = dictGetVal(de);
|
||||
sentinelEvent(REDIS_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* ============================ script execution ============================ */
|
||||
|
||||
/* Release a script job structure and all the associated data. */
|
||||
@@ -616,7 +580,7 @@ void sentinelScheduleScriptExecution(char *path, ...) {
|
||||
}
|
||||
va_end(ap);
|
||||
argv[0] = sdsnew(path);
|
||||
|
||||
|
||||
sj = zmalloc(sizeof(*sj));
|
||||
sj->flags = SENTINEL_SCRIPT_NONE;
|
||||
sj->retry_num = 0;
|
||||
@@ -742,7 +706,7 @@ void sentinelCollectTerminatedScripts(void) {
|
||||
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
|
||||
sentinelEvent(REDIS_DEBUG,"-script-child",NULL,"%ld %d %d",
|
||||
(long)pid, exitcode, bysignal);
|
||||
|
||||
|
||||
ln = sentinelGetScriptListNodeByPid(pid);
|
||||
if (ln == NULL) {
|
||||
redisLog(REDIS_WARNING,"wait3() returned a pid (%ld) we can't find in our scripts execution queue!", (long)pid);
|
||||
@@ -932,11 +896,6 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
ri->cc_conn_time = 0;
|
||||
ri->pc_conn_time = 0;
|
||||
ri->pc_last_activity = 0;
|
||||
/* We set the last_ping_time to "now" even if we actually don't have yet
|
||||
* a connection with the node, nor we sent a ping.
|
||||
* This is useful to detect a timeout in case we'll not be able to connect
|
||||
* with the node at all. */
|
||||
ri->last_ping_time = mstime();
|
||||
ri->last_avail_time = mstime();
|
||||
ri->last_pong_time = mstime();
|
||||
ri->last_pub_time = mstime();
|
||||
@@ -969,7 +928,6 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
ri->failover_state_change_time = 0;
|
||||
ri->failover_start_time = 0;
|
||||
ri->failover_timeout = SENTINEL_DEFAULT_FAILOVER_TIMEOUT;
|
||||
ri->failover_delay_logged = 0;
|
||||
ri->promoted_slave = NULL;
|
||||
ri->notification_script = NULL;
|
||||
ri->client_reconfig_script = NULL;
|
||||
@@ -1020,7 +978,7 @@ sentinelRedisInstance *sentinelRedisInstanceLookupSlave(
|
||||
{
|
||||
sds key;
|
||||
sentinelRedisInstance *slave;
|
||||
|
||||
|
||||
redisAssert(ri->flags & SRI_MASTER);
|
||||
key = sdscatprintf(sdsempty(),
|
||||
strchr(ip,':') ? "[%s]:%d" : "%s:%d",
|
||||
@@ -1040,7 +998,7 @@ const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri) {
|
||||
|
||||
/* This function removes all the instances found in the dictionary of
|
||||
* sentinels in the specified 'master', having either:
|
||||
*
|
||||
*
|
||||
* 1) The same ip/port as specified.
|
||||
* 2) The same runid.
|
||||
*
|
||||
@@ -1174,7 +1132,6 @@ void sentinelResetMaster(sentinelRedisInstance *ri, int flags) {
|
||||
sdsfree(ri->slave_master_host);
|
||||
ri->runid = NULL;
|
||||
ri->slave_master_host = NULL;
|
||||
ri->last_ping_time = mstime();
|
||||
ri->last_avail_time = mstime();
|
||||
ri->last_pong_time = mstime();
|
||||
ri->role_reported_time = mstime();
|
||||
@@ -1234,7 +1191,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip,
|
||||
slave->addr->port);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
|
||||
/* If we are switching to a different address, include the old address
|
||||
* as a slave as well, so that we'll be able to sense / reconfigure
|
||||
* the old master. */
|
||||
@@ -1301,24 +1258,6 @@ sentinelAddr *sentinelGetCurrentMasterAddress(sentinelRedisInstance *master) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This function sets the down_after_period field value in 'master' to all
|
||||
* the slaves and sentinel instances connected to this master. */
|
||||
void sentinelPropagateDownAfterPeriod(sentinelRedisInstance *master) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
int j;
|
||||
dict *d[] = {master->slaves, master->sentinels, NULL};
|
||||
|
||||
for (j = 0; d[j]; j++) {
|
||||
di = dictGetIterator(d[j]);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *ri = dictGetVal(de);
|
||||
ri->down_after_period = master->down_after_period;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================ Config handling ============================= */
|
||||
char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
sentinelRedisInstance *ri;
|
||||
@@ -1344,7 +1283,6 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
ri->down_after_period = atoi(argv[2]);
|
||||
if (ri->down_after_period <= 0)
|
||||
return "negative or zero time parameter.";
|
||||
sentinelPropagateDownAfterPeriod(ri);
|
||||
} else if (!strcasecmp(argv[0],"failover-timeout") && argc == 3) {
|
||||
/* failover-timeout <name> <milliseconds> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
@@ -1377,26 +1315,13 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->auth_pass = sdsnew(argv[2]);
|
||||
} else if (!strcasecmp(argv[0],"current-epoch") && argc == 2) {
|
||||
/* current-epoch <epoch> */
|
||||
unsigned long long current_epoch = strtoull(argv[1],NULL,10);
|
||||
if (current_epoch > sentinel.current_epoch)
|
||||
sentinel.current_epoch = current_epoch;
|
||||
} else if (!strcasecmp(argv[0],"config-epoch") && argc == 3) {
|
||||
/* config-epoch <name> <epoch> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->config_epoch = strtoull(argv[2],NULL,10);
|
||||
/* The following update of current_epoch is not really useful as
|
||||
* now the current epoch is persisted on the config file, but
|
||||
* we leave this check here for redundancy. */
|
||||
if (ri->config_epoch > sentinel.current_epoch)
|
||||
sentinel.current_epoch = ri->config_epoch;
|
||||
} else if (!strcasecmp(argv[0],"leader-epoch") && argc == 3) {
|
||||
/* leader-epoch <name> <epoch> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->leader_epoch = strtoull(argv[2],NULL,10);
|
||||
} else if (!strcasecmp(argv[0],"known-slave") && argc == 4) {
|
||||
sentinelRedisInstance *slave;
|
||||
|
||||
@@ -1435,13 +1360,13 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
dictIterator *di, *di2;
|
||||
dictEntry *de;
|
||||
sds line;
|
||||
|
||||
/* For every master emit a "sentinel monitor" config entry. */
|
||||
di = dictGetIterator(sentinel.masters);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *master, *ri;
|
||||
sentinelAddr *master_addr;
|
||||
sds line;
|
||||
|
||||
/* sentinel monitor */
|
||||
master = dictGetVal(de);
|
||||
@@ -1505,12 +1430,6 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
master->name, (unsigned long long) master->config_epoch);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
|
||||
/* sentinel leader-epoch */
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel leader-epoch %s %llu",
|
||||
master->name, (unsigned long long) master->leader_epoch);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
|
||||
/* sentinel known-slave */
|
||||
di2 = dictGetIterator(master->slaves);
|
||||
while((de = dictNext(di2)) != NULL) {
|
||||
@@ -1546,12 +1465,6 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
}
|
||||
dictReleaseIterator(di2);
|
||||
}
|
||||
|
||||
/* sentinel current-epoch is a global state valid for all the masters. */
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel current-epoch %llu", (unsigned long long) sentinel.current_epoch);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
@@ -1563,23 +1476,21 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
*
|
||||
* On failure the function logs a warning on the Redis log. */
|
||||
void sentinelFlushConfig(void) {
|
||||
int fd = -1;
|
||||
int fd;
|
||||
int saved_hz = server.hz;
|
||||
int rewrite_status;
|
||||
|
||||
server.hz = REDIS_DEFAULT_HZ;
|
||||
rewrite_status = rewriteConfig(server.configfile);
|
||||
if (rewriteConfig(server.configfile) != -1) {
|
||||
/* Rewrite succeded, fsync it. */
|
||||
if ((fd = open(server.configfile,O_RDONLY)) != -1) {
|
||||
fsync(fd);
|
||||
close(fd);
|
||||
}
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"WARNING: Senitnel was not able to save the new configuration on disk!!!: %s", strerror(errno));
|
||||
}
|
||||
server.hz = saved_hz;
|
||||
|
||||
if (rewrite_status == -1) goto werr;
|
||||
if ((fd = open(server.configfile,O_RDONLY)) == -1) goto werr;
|
||||
if (fsync(fd) == -1) goto werr;
|
||||
if (close(fd) == EOF) goto werr;
|
||||
return;
|
||||
|
||||
werr:
|
||||
if (fd != -1) close(fd);
|
||||
redisLog(REDIS_WARNING,"WARNING: Sentinel was not able to save the new configuration on disk!!!: %s", strerror(errno));
|
||||
}
|
||||
|
||||
/* ====================== hiredis connection handling ======================= */
|
||||
@@ -1650,23 +1561,6 @@ void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Use CLIENT SETNAME to name the connection in the Redis instance as
|
||||
* sentinel-<first_8_chars_of_runid>-<connection_type>
|
||||
* The connection type is "cmd" or "pubsub" as specified by 'type'.
|
||||
*
|
||||
* This makes it possible to list all the sentinel instances connected
|
||||
* to a Redis servewr with CLIENT LIST, grepping for a specific name format. */
|
||||
void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char *type) {
|
||||
char name[64];
|
||||
|
||||
snprintf(name,sizeof(name),"sentinel-%.8s-%s",server.runid,type);
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, NULL,
|
||||
"CLIENT SETNAME %s", name) == REDIS_OK)
|
||||
{
|
||||
ri->pending_commands++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create the async connections for the specified instance if the instance
|
||||
* is disconnected. Note that the SRI_DISCONNECTED flag is set even if just
|
||||
* one of the two links (commands and pub/sub) is missing. */
|
||||
@@ -1689,10 +1583,6 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
redisAsyncSetDisconnectCallback(ri->cc,
|
||||
sentinelDisconnectCallback);
|
||||
sentinelSendAuthIfNeeded(ri,ri->cc);
|
||||
sentinelSetClientName(ri,ri->cc,"cmd");
|
||||
|
||||
/* Send a PING ASAP when reconnecting. */
|
||||
sentinelSendPing(ri);
|
||||
}
|
||||
}
|
||||
/* Pub / Sub */
|
||||
@@ -1713,7 +1603,6 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
||||
redisAsyncSetDisconnectCallback(ri->pc,
|
||||
sentinelDisconnectCallback);
|
||||
sentinelSendAuthIfNeeded(ri,ri->pc);
|
||||
sentinelSetClientName(ri,ri->pc,"pubsub");
|
||||
/* Now we subscribe to the Sentinels "Hello" channel. */
|
||||
retval = redisAsyncCommand(ri->pc,
|
||||
sentinelReceiveHelloMessages, NULL, "SUBSCRIBE %s",
|
||||
@@ -1847,7 +1736,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
ri->slave_conf_change_time = mstime();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* master_link_status:<status> */
|
||||
if (sdslen(l) >= 19 && !memcmp(l,"master_link_status:",19)) {
|
||||
ri->slave_master_link_status =
|
||||
@@ -1877,20 +1766,8 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
ri->role_reported_time = mstime();
|
||||
ri->role_reported = role;
|
||||
if (role == SRI_SLAVE) ri->slave_conf_change_time = mstime();
|
||||
/* Log the event with +role-change if the new role is coherent or
|
||||
* with -role-change if there is a mismatch with the current config. */
|
||||
sentinelEvent(REDIS_VERBOSE,
|
||||
((ri->flags & (SRI_MASTER|SRI_SLAVE)) == role) ?
|
||||
"+role-change" : "-role-change",
|
||||
ri, "%@ new reported role is %s",
|
||||
role == SRI_MASTER ? "master" : "slave",
|
||||
ri->flags & SRI_MASTER ? "master" : "slave");
|
||||
}
|
||||
|
||||
/* None of the following conditions are processed when in tilt mode, so
|
||||
* return asap. */
|
||||
if (sentinel.tilt) return;
|
||||
|
||||
/* Handle master -> slave role switch. */
|
||||
if ((ri->flags & SRI_MASTER) && role == SRI_SLAVE) {
|
||||
/* Nothing to do, but masters claiming to be slaves are
|
||||
@@ -1902,7 +1779,8 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
if ((ri->flags & SRI_SLAVE) && role == SRI_MASTER) {
|
||||
/* If this is a promoted slave we can change state to the
|
||||
* failover state machine. */
|
||||
if ((ri->master->flags & SRI_FAILOVER_IN_PROGRESS) &&
|
||||
if (!sentinel.tilt &&
|
||||
(ri->master->flags & SRI_FAILOVER_IN_PROGRESS) &&
|
||||
(ri->master->failover_state ==
|
||||
SENTINEL_FAILOVER_STATE_WAIT_PROMOTION))
|
||||
{
|
||||
@@ -1920,7 +1798,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
ri->master,"%@");
|
||||
sentinelCallClientReconfScript(ri->master,SENTINEL_LEADER,
|
||||
"start",ri->master->addr,ri->addr);
|
||||
} else {
|
||||
} else if (!sentinel.tilt) {
|
||||
/* A slave turned into a master. We want to force our view and
|
||||
* reconfigure as slave. Wait some time after the change before
|
||||
* going forward, to receive new configs if any. */
|
||||
@@ -1940,7 +1818,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
}
|
||||
|
||||
/* Handle slaves replicating to a different master address. */
|
||||
if ((ri->flags & SRI_SLAVE) &&
|
||||
if ((ri->flags & SRI_SLAVE) && !sentinel.tilt &&
|
||||
role == SRI_SLAVE &&
|
||||
(ri->slave_master_port != ri->master->addr->port ||
|
||||
strcasecmp(ri->slave_master_host,ri->master->addr->ip)))
|
||||
@@ -1961,6 +1839,10 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
}
|
||||
}
|
||||
|
||||
/* None of the following conditions are processed when in tilt mode, so
|
||||
* return asap. */
|
||||
if (sentinel.tilt) return;
|
||||
|
||||
/* Detect if the slave that is in the process of being reconfigured
|
||||
* changed state. */
|
||||
if ((ri->flags & SRI_SLAVE) && role == SRI_SLAVE &&
|
||||
@@ -2027,7 +1909,6 @@ void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata
|
||||
strncmp(r->str,"MASTERDOWN",10) == 0)
|
||||
{
|
||||
ri->last_avail_time = mstime();
|
||||
ri->last_ping_time = 0; /* Flag the pong as received. */
|
||||
} else {
|
||||
/* Send a SCRIPT KILL command if the instance appears to be
|
||||
* down because of a busy script. */
|
||||
@@ -2062,113 +1943,22 @@ void sentinelPublishReplyCallback(redisAsyncContext *c, void *reply, void *privd
|
||||
ri->last_pub_time = mstime();
|
||||
}
|
||||
|
||||
/* Process an hello message received via Pub/Sub in master or slave instance,
|
||||
* or sent directly to this sentinel via the (fake) PUBLISH command of Sentinel.
|
||||
*
|
||||
* If the master name specified in the message is not known, the message is
|
||||
* discareded. */
|
||||
void sentinelProcessHelloMessage(char *hello, int hello_len) {
|
||||
/* Format is composed of 8 tokens:
|
||||
* 0=ip,1=port,2=runid,3=current_epoch,4=master_name,
|
||||
* 5=master_ip,6=master_port,7=master_config_epoch. */
|
||||
int numtokens, port, removed, master_port;
|
||||
uint64_t current_epoch, master_config_epoch;
|
||||
char **token = sdssplitlen(hello, hello_len, ",", 1, &numtokens);
|
||||
sentinelRedisInstance *si, *master;
|
||||
|
||||
if (numtokens == 8) {
|
||||
/* Obtain a reference to the master this hello message is about */
|
||||
master = sentinelGetMasterByName(token[4]);
|
||||
if (!master) goto cleanup; /* Unknown master, skip the message. */
|
||||
|
||||
/* First, try to see if we already have this sentinel. */
|
||||
port = atoi(token[1]);
|
||||
master_port = atoi(token[6]);
|
||||
si = getSentinelRedisInstanceByAddrAndRunID(
|
||||
master->sentinels,token[0],port,token[2]);
|
||||
current_epoch = strtoull(token[3],NULL,10);
|
||||
master_config_epoch = strtoull(token[7],NULL,10);
|
||||
|
||||
if (!si) {
|
||||
/* If not, remove all the sentinels that have the same runid
|
||||
* OR the same ip/port, because it's either a restart or a
|
||||
* network topology change. */
|
||||
removed = removeMatchingSentinelsFromMaster(master,token[0],port,
|
||||
token[2]);
|
||||
if (removed) {
|
||||
sentinelEvent(REDIS_NOTICE,"-dup-sentinel",master,
|
||||
"%@ #duplicate of %s:%d or %s",
|
||||
token[0],port,token[2]);
|
||||
}
|
||||
|
||||
/* Add the new sentinel. */
|
||||
si = createSentinelRedisInstance(NULL,SRI_SENTINEL,
|
||||
token[0],port,master->quorum,master);
|
||||
if (si) {
|
||||
sentinelEvent(REDIS_NOTICE,"+sentinel",si,"%@");
|
||||
/* The runid is NULL after a new instance creation and
|
||||
* for Sentinels we don't have a later chance to fill it,
|
||||
* so do it now. */
|
||||
si->runid = sdsnew(token[2]);
|
||||
sentinelFlushConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/* Update local current_epoch if received current_epoch is greater.*/
|
||||
if (current_epoch > sentinel.current_epoch) {
|
||||
sentinel.current_epoch = current_epoch;
|
||||
sentinelFlushConfig();
|
||||
sentinelEvent(REDIS_WARNING,"+new-epoch",master,"%llu",
|
||||
(unsigned long long) sentinel.current_epoch);
|
||||
}
|
||||
|
||||
/* Update master info if received configuration is newer. */
|
||||
if (master->config_epoch < master_config_epoch) {
|
||||
master->config_epoch = master_config_epoch;
|
||||
if (master_port != master->addr->port ||
|
||||
strcmp(master->addr->ip, token[5]))
|
||||
{
|
||||
sentinelAddr *old_addr;
|
||||
|
||||
sentinelEvent(REDIS_WARNING,"+config-update-from",si,"%@");
|
||||
sentinelEvent(REDIS_WARNING,"+switch-master",
|
||||
master,"%s %s %d %s %d",
|
||||
master->name,
|
||||
master->addr->ip, master->addr->port,
|
||||
token[5], master_port);
|
||||
|
||||
old_addr = dupSentinelAddr(master->addr);
|
||||
sentinelResetMasterAndChangeAddress(master, token[5], master_port);
|
||||
sentinelCallClientReconfScript(master,
|
||||
SENTINEL_OBSERVER,"start",
|
||||
old_addr,master->addr);
|
||||
releaseSentinelAddr(old_addr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the state of the Sentinel. */
|
||||
if (si) si->last_hello_time = mstime();
|
||||
}
|
||||
|
||||
cleanup:
|
||||
sdsfreesplitres(token,numtokens);
|
||||
}
|
||||
|
||||
|
||||
/* This is our Pub/Sub callback for the Hello channel. It's useful in order
|
||||
* to discover other sentinels attached at the same master. */
|
||||
void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privdata) {
|
||||
sentinelRedisInstance *ri = c->data;
|
||||
sentinelRedisInstance *ri = c->data, *master;
|
||||
redisReply *r;
|
||||
|
||||
if (!reply || !ri) return;
|
||||
r = reply;
|
||||
|
||||
master = (ri->flags & SRI_MASTER) ? ri : ri->master;
|
||||
|
||||
/* Update the last activity in the pubsub channel. Note that since we
|
||||
* receive our messages as well this timestamp can be used to detect
|
||||
* if the link is probably disconnected even if it seems otherwise. */
|
||||
ri->pc_last_activity = mstime();
|
||||
|
||||
|
||||
/* Sanity check in the reply we expect, so that the code that follows
|
||||
* can avoid to check for details. */
|
||||
if (r->type != REDIS_REPLY_ARRAY ||
|
||||
@@ -2181,7 +1971,90 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd
|
||||
/* We are not interested in meeting ourselves */
|
||||
if (strstr(r->element[2]->str,server.runid) != NULL) return;
|
||||
|
||||
sentinelProcessHelloMessage(r->element[2]->str, r->element[2]->len);
|
||||
{
|
||||
/* Format is composed of 8 tokens:
|
||||
* 0=ip,1=port,2=runid,3=current_epoch,4=master_name,
|
||||
* 5=master_ip,6=master_port,7=master_config_epoch. */
|
||||
int numtokens, port, removed, master_port;
|
||||
uint64_t current_epoch, master_config_epoch;
|
||||
char **token = sdssplitlen(r->element[2]->str,
|
||||
r->element[2]->len,
|
||||
",",1,&numtokens);
|
||||
sentinelRedisInstance *si;
|
||||
|
||||
if (numtokens == 8) {
|
||||
/* First, try to see if we already have this sentinel. */
|
||||
port = atoi(token[1]);
|
||||
master_port = atoi(token[6]);
|
||||
si = getSentinelRedisInstanceByAddrAndRunID(
|
||||
master->sentinels,token[0],port,token[2]);
|
||||
current_epoch = strtoull(token[3],NULL,10);
|
||||
master_config_epoch = strtoull(token[7],NULL,10);
|
||||
sentinelRedisInstance *msgmaster;
|
||||
|
||||
if (!si) {
|
||||
/* If not, remove all the sentinels that have the same runid
|
||||
* OR the same ip/port, because it's either a restart or a
|
||||
* network topology change. */
|
||||
removed = removeMatchingSentinelsFromMaster(master,token[0],port,
|
||||
token[2]);
|
||||
if (removed) {
|
||||
sentinelEvent(REDIS_NOTICE,"-dup-sentinel",master,
|
||||
"%@ #duplicate of %s:%d or %s",
|
||||
token[0],port,token[2]);
|
||||
}
|
||||
|
||||
/* Add the new sentinel. */
|
||||
si = createSentinelRedisInstance(NULL,SRI_SENTINEL,
|
||||
token[0],port,master->quorum,master);
|
||||
if (si) {
|
||||
sentinelEvent(REDIS_NOTICE,"+sentinel",si,"%@");
|
||||
/* The runid is NULL after a new instance creation and
|
||||
* for Sentinels we don't have a later chance to fill it,
|
||||
* so do it now. */
|
||||
si->runid = sdsnew(token[2]);
|
||||
sentinelFlushConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/* Update local current_epoch if received current_epoch is greater.*/
|
||||
if (current_epoch > sentinel.current_epoch) {
|
||||
sentinel.current_epoch = current_epoch;
|
||||
sentinelEvent(REDIS_WARNING,"+new-epoch",ri,"%llu",
|
||||
(unsigned long long) sentinel.current_epoch);
|
||||
}
|
||||
|
||||
/* Update master info if received configuration is newer. */
|
||||
if ((msgmaster = sentinelGetMasterByName(token[4])) != NULL) {
|
||||
if (msgmaster->config_epoch < master_config_epoch) {
|
||||
msgmaster->config_epoch = master_config_epoch;
|
||||
if (master_port != msgmaster->addr->port ||
|
||||
strcmp(msgmaster->addr->ip, token[5]))
|
||||
{
|
||||
sentinelAddr *old_addr;
|
||||
|
||||
sentinelEvent(REDIS_WARNING,"+switch-master",
|
||||
msgmaster,"%s %s %d %s %d",
|
||||
msgmaster->name,
|
||||
msgmaster->addr->ip, msgmaster->addr->port,
|
||||
token[5], master_port);
|
||||
|
||||
old_addr = dupSentinelAddr(msgmaster->addr);
|
||||
sentinelResetMasterAndChangeAddress(msgmaster,
|
||||
token[5], master_port);
|
||||
sentinelCallClientReconfScript(msgmaster,
|
||||
SENTINEL_OBSERVER,"start",
|
||||
old_addr,msgmaster->addr);
|
||||
releaseSentinelAddr(old_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the state of the Sentinel. */
|
||||
if (si) si->last_hello_time = mstime();
|
||||
}
|
||||
sdsfreesplitres(token,numtokens);
|
||||
}
|
||||
}
|
||||
|
||||
/* Send an "Hello" message via Pub/Sub to the specified 'ri' Redis
|
||||
@@ -2223,31 +2096,11 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Send a PING to the specified instance and refresh the last_ping_time
|
||||
* if it is zero (that is, if we received a pong for the previous ping).
|
||||
*
|
||||
* On error zero is returned, and we can't consider the PING command
|
||||
* queued in the connection. */
|
||||
int sentinelSendPing(sentinelRedisInstance *ri) {
|
||||
int retval = redisAsyncCommand(ri->cc,
|
||||
sentinelPingReplyCallback, NULL, "PING");
|
||||
if (retval == REDIS_OK) {
|
||||
ri->pending_commands++;
|
||||
/* We update the ping time only if we received the pong for
|
||||
* the previous ping, otherwise we are technically waiting
|
||||
* since the first ping that did not received a reply. */
|
||||
if (ri->last_ping_time == 0) ri->last_ping_time = mstime();
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Send periodic PING, INFO, and PUBLISH to the Hello channel to
|
||||
* the specified master or slave instance. */
|
||||
void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
|
||||
void sentinelPingInstance(sentinelRedisInstance *ri) {
|
||||
mstime_t now = mstime();
|
||||
mstime_t info_period, ping_period;
|
||||
mstime_t info_period;
|
||||
int retval;
|
||||
|
||||
/* Return ASAP if we have already a PING or INFO already pending, or
|
||||
@@ -2273,12 +2126,6 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
|
||||
info_period = SENTINEL_INFO_PERIOD;
|
||||
}
|
||||
|
||||
/* We ping instances every time the last received pong is older than
|
||||
* the configured 'down-after-milliseconds' time, but every second
|
||||
* anyway if 'down-after-milliseconds' is greater than 1 second. */
|
||||
ping_period = ri->down_after_period;
|
||||
if (ping_period > SENTINEL_PING_PERIOD) ping_period = SENTINEL_PING_PERIOD;
|
||||
|
||||
if ((ri->flags & SRI_SENTINEL) == 0 &&
|
||||
(ri->info_refresh == 0 ||
|
||||
(now - ri->info_refresh) > info_period))
|
||||
@@ -2286,12 +2133,18 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
|
||||
/* Send INFO to masters and slaves, not sentinels. */
|
||||
retval = redisAsyncCommand(ri->cc,
|
||||
sentinelInfoReplyCallback, NULL, "INFO");
|
||||
if (retval == REDIS_OK) ri->pending_commands++;
|
||||
} else if ((now - ri->last_pong_time) > ping_period) {
|
||||
if (retval != REDIS_OK) return;
|
||||
ri->pending_commands++;
|
||||
} else if ((now - ri->last_pong_time) > SENTINEL_PING_PERIOD) {
|
||||
/* Send PING to all the three kinds of instances. */
|
||||
sentinelSendPing(ri);
|
||||
} else if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
|
||||
/* PUBLISH hello messages to all the three kinds of instances. */
|
||||
retval = redisAsyncCommand(ri->cc,
|
||||
sentinelPingReplyCallback, NULL, "PING");
|
||||
if (retval != REDIS_OK) return;
|
||||
ri->pending_commands++;
|
||||
} else if ((ri->flags & SRI_SENTINEL) == 0 &&
|
||||
(now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD)
|
||||
{
|
||||
/* PUBLISH hello messages to masters and slaves. */
|
||||
sentinelSendHello(ri);
|
||||
}
|
||||
}
|
||||
@@ -2365,11 +2218,6 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) {
|
||||
fields++;
|
||||
}
|
||||
|
||||
addReplyBulkCString(c,"last-ping-sent");
|
||||
addReplyBulkLongLong(c,
|
||||
ri->last_ping_time ? (mstime() - ri->last_ping_time) : 0);
|
||||
fields++;
|
||||
|
||||
addReplyBulkCString(c,"last-ok-ping-reply");
|
||||
addReplyBulkLongLong(c,mstime() - ri->last_avail_time);
|
||||
fields++;
|
||||
@@ -2390,10 +2238,6 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) {
|
||||
fields++;
|
||||
}
|
||||
|
||||
addReplyBulkCString(c,"down-after-milliseconds");
|
||||
addReplyBulkLongLong(c,ri->down_after_period);
|
||||
fields++;
|
||||
|
||||
/* Masters and Slaves */
|
||||
if (ri->flags & (SRI_MASTER|SRI_SLAVE)) {
|
||||
addReplyBulkCString(c,"info-refresh");
|
||||
@@ -2428,6 +2272,10 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) {
|
||||
addReplyBulkLongLong(c,ri->quorum);
|
||||
fields++;
|
||||
|
||||
addReplyBulkCString(c,"down-after-milliseconds");
|
||||
addReplyBulkLongLong(c,ri->down_after_period);
|
||||
fields++;
|
||||
|
||||
addReplyBulkCString(c,"failover-timeout");
|
||||
addReplyBulkLongLong(c,ri->failover_timeout);
|
||||
fields++;
|
||||
@@ -2538,7 +2386,6 @@ void sentinelCommand(redisClient *c) {
|
||||
/* SENTINEL MASTER <name> */
|
||||
sentinelRedisInstance *ri;
|
||||
|
||||
if (c->argc != 3) goto numargserr;
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
addReplySentinelRedisInstance(c,ri);
|
||||
@@ -2608,6 +2455,8 @@ void sentinelCommand(redisClient *c) {
|
||||
ri = sentinelGetMasterByName(c->argv[2]->ptr);
|
||||
if (ri == NULL) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
} else if (ri->info_refresh == 0) {
|
||||
addReplySds(c,sdsnew("-IDONTKNOW I have not enough information to reply. Please ask another Sentinel.\r\n"));
|
||||
} else {
|
||||
sentinelAddr *addr = sentinelGetCurrentMasterAddress(ri);
|
||||
|
||||
@@ -2642,7 +2491,6 @@ void sentinelCommand(redisClient *c) {
|
||||
sentinelPendingScriptsCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"monitor")) {
|
||||
/* SENTINEL MONITOR <name> <ip> <port> <quorum> */
|
||||
sentinelRedisInstance *ri;
|
||||
long quorum, port;
|
||||
char buf[32];
|
||||
|
||||
@@ -2658,11 +2506,9 @@ void sentinelCommand(redisClient *c) {
|
||||
addReplyError(c,"Invalid IP address specified");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Parameters are valid. Try to create the master instance. */
|
||||
ri = createSentinelRedisInstance(c->argv[2]->ptr,SRI_MASTER,
|
||||
c->argv[3]->ptr,port,quorum,NULL);
|
||||
if (ri == NULL) {
|
||||
if (createSentinelRedisInstance(c->argv[2]->ptr,SRI_MASTER,
|
||||
c->argv[3]->ptr,port,quorum,NULL) == NULL)
|
||||
{
|
||||
switch(errno) {
|
||||
case EBUSY:
|
||||
addReplyError(c,"Duplicated master name");
|
||||
@@ -2676,7 +2522,6 @@ void sentinelCommand(redisClient *c) {
|
||||
}
|
||||
} else {
|
||||
sentinelFlushConfig();
|
||||
sentinelEvent(REDIS_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"remove")) {
|
||||
@@ -2685,7 +2530,6 @@ void sentinelCommand(redisClient *c) {
|
||||
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
sentinelEvent(REDIS_WARNING,"-monitor",ri,"%@");
|
||||
dictDelete(sentinel.masters,c->argv[2]->ptr);
|
||||
sentinelFlushConfig();
|
||||
addReply(c,shared.ok);
|
||||
@@ -2784,7 +2628,6 @@ void sentinelSetCommand(redisClient *c) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0)
|
||||
goto badfmt;
|
||||
ri->down_after_period = ll;
|
||||
sentinelPropagateDownAfterPeriod(ri);
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"failover-timeout")) {
|
||||
/* failover-timeout <milliseconds> */
|
||||
@@ -2826,19 +2669,12 @@ void sentinelSetCommand(redisClient *c) {
|
||||
sdsfree(ri->auth_pass);
|
||||
ri->auth_pass = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"quorum")) {
|
||||
/* quorum <count> */
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0)
|
||||
goto badfmt;
|
||||
ri->quorum = ll;
|
||||
changes++;
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unknown option '%s' for SENTINEL SET",
|
||||
option);
|
||||
if (changes) sentinelFlushConfig();
|
||||
return;
|
||||
}
|
||||
sentinelEvent(REDIS_WARNING,"+set",ri,"%@ %s %s",option,value);
|
||||
}
|
||||
|
||||
if (changes) sentinelFlushConfig();
|
||||
@@ -2851,42 +2687,20 @@ badfmt: /* Bad format errors */
|
||||
value, option);
|
||||
}
|
||||
|
||||
/* Our fake PUBLISH command: it is actually useful only to receive hello messages
|
||||
* from the other sentinel instances, and publishing to a channel other than
|
||||
* SENTINEL_HELLO_CHANNEL is forbidden.
|
||||
*
|
||||
* Because we have a Sentinel PUBLISH, the code to send hello messages is the same
|
||||
* for all the three kind of instances: masters, slaves, sentinels. */
|
||||
void sentinelPublishCommand(redisClient *c) {
|
||||
if (strcmp(c->argv[1]->ptr,SENTINEL_HELLO_CHANNEL)) {
|
||||
addReplyError(c, "Only HELLO messages are accepted by Sentinel instances.");
|
||||
return;
|
||||
}
|
||||
sentinelProcessHelloMessage(c->argv[2]->ptr,sdslen(c->argv[2]->ptr));
|
||||
addReplyLongLong(c,1);
|
||||
}
|
||||
|
||||
/* ===================== SENTINEL availability checks ======================= */
|
||||
|
||||
/* Is this instance down from our point of view? */
|
||||
void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
|
||||
mstime_t elapsed = 0;
|
||||
|
||||
if (ri->last_ping_time)
|
||||
elapsed = mstime() - ri->last_ping_time;
|
||||
mstime_t elapsed = mstime() - ri->last_avail_time;
|
||||
|
||||
/* Check if we are in need for a reconnection of one of the
|
||||
* links, because we are detecting low activity.
|
||||
*
|
||||
* 1) Check if the command link seems connected, was connected not less
|
||||
* than SENTINEL_MIN_LINK_RECONNECT_PERIOD, but still we have a
|
||||
* pending ping for more than half the timeout. */
|
||||
* than SENTINEL_MIN_LINK_RECONNECT_PERIOD, but still we have an
|
||||
* idle time that is greater than down_after_period / 2 seconds. */
|
||||
if (ri->cc &&
|
||||
(mstime() - ri->cc_conn_time) > SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
|
||||
ri->last_ping_time != 0 && /* Ther is a pending ping... */
|
||||
/* The pending ping is delayed, and we did not received
|
||||
* error replies as well. */
|
||||
(mstime() - ri->last_ping_time) > (ri->down_after_period/2) &&
|
||||
(mstime() - ri->last_pong_time) > (ri->down_after_period/2))
|
||||
{
|
||||
sentinelKillLink(ri,ri->cc);
|
||||
@@ -3069,7 +2883,6 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f
|
||||
char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char *req_runid, uint64_t *leader_epoch) {
|
||||
if (req_epoch > sentinel.current_epoch) {
|
||||
sentinel.current_epoch = req_epoch;
|
||||
sentinelFlushConfig();
|
||||
sentinelEvent(REDIS_WARNING,"+new-epoch",master,"%llu",
|
||||
(unsigned long long) sentinel.current_epoch);
|
||||
}
|
||||
@@ -3079,14 +2892,13 @@ char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char
|
||||
sdsfree(master->leader);
|
||||
master->leader = sdsnew(req_runid);
|
||||
master->leader_epoch = sentinel.current_epoch;
|
||||
sentinelFlushConfig();
|
||||
sentinelEvent(REDIS_WARNING,"+vote-for-leader",master,"%s %llu",
|
||||
master->leader, (unsigned long long) master->leader_epoch);
|
||||
/* If we did not voted for ourselves, set the master failover start
|
||||
* time to now, in order to force a delay before we can start a
|
||||
* failover for the same master. */
|
||||
if (strcasecmp(master->leader,server.runid))
|
||||
master->failover_start_time = mstime()+rand()%SENTINEL_MAX_DESYNC;
|
||||
master->failover_start_time = mstime();
|
||||
}
|
||||
|
||||
*leader_epoch = master->leader_epoch;
|
||||
@@ -3231,7 +3043,7 @@ void sentinelStartFailover(sentinelRedisInstance *master) {
|
||||
sentinelEvent(REDIS_WARNING,"+new-epoch",master,"%llu",
|
||||
(unsigned long long) sentinel.current_epoch);
|
||||
sentinelEvent(REDIS_WARNING,"+try-failover",master,"%@");
|
||||
master->failover_start_time = mstime()+rand()%SENTINEL_MAX_DESYNC;
|
||||
master->failover_start_time = mstime();
|
||||
master->failover_state_change_time = mstime();
|
||||
}
|
||||
|
||||
@@ -3241,7 +3053,7 @@ void sentinelStartFailover(sentinelRedisInstance *master) {
|
||||
* 1) Master must be in ODOWN condition.
|
||||
* 2) No failover already in progress.
|
||||
* 3) No failover already attempted recently.
|
||||
*
|
||||
*
|
||||
* We still don't know if we'll win the election so it is possible that we
|
||||
* start the failover but that we'll not be able to act.
|
||||
*
|
||||
@@ -3255,22 +3067,7 @@ int sentinelStartFailoverIfNeeded(sentinelRedisInstance *master) {
|
||||
|
||||
/* Last failover attempt started too little time ago? */
|
||||
if (mstime() - master->failover_start_time <
|
||||
master->failover_timeout*2)
|
||||
{
|
||||
if (master->failover_delay_logged != master->failover_start_time) {
|
||||
time_t clock = (master->failover_start_time +
|
||||
master->failover_timeout*2) / 1000;
|
||||
char ctimebuf[26];
|
||||
|
||||
ctime_r(&clock,ctimebuf);
|
||||
ctimebuf[24] = '\0'; /* Remove newline. */
|
||||
master->failover_delay_logged = master->failover_start_time;
|
||||
redisLog(REDIS_WARNING,
|
||||
"Next failover delay: I will not start a failover before %s",
|
||||
ctimebuf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
master->failover_timeout*2) return 0;
|
||||
|
||||
sentinelStartFailover(master);
|
||||
return 1;
|
||||
@@ -3556,17 +3353,14 @@ void sentinelFailoverReconfNextSlave(sentinelRedisInstance *master) {
|
||||
/* Skip the promoted slave, and already configured slaves. */
|
||||
if (slave->flags & (SRI_PROMOTED|SRI_RECONF_DONE)) continue;
|
||||
|
||||
/* If too much time elapsed without the slave moving forward to
|
||||
* the next state, consider it reconfigured even if it is not.
|
||||
* Sentinels will detect the slave as misconfigured and fix its
|
||||
* configuration later. */
|
||||
/* Clear the SRI_RECONF_SENT flag if too much time elapsed without
|
||||
* the slave moving forward to the next state. */
|
||||
if ((slave->flags & SRI_RECONF_SENT) &&
|
||||
(mstime() - slave->slave_reconf_sent_time) >
|
||||
SENTINEL_SLAVE_RECONF_TIMEOUT)
|
||||
SENTINEL_SLAVE_RECONF_RETRY_PERIOD)
|
||||
{
|
||||
sentinelEvent(REDIS_NOTICE,"-slave-reconf-sent-timeout",slave,"%@");
|
||||
slave->flags &= ~SRI_RECONF_SENT;
|
||||
slave->flags |= SRI_RECONF_DONE;
|
||||
}
|
||||
|
||||
/* Nothing to do for instances that are disconnected or already
|
||||
@@ -3657,7 +3451,7 @@ void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
|
||||
/* ========== MONITORING HALF ============ */
|
||||
/* Every kind of instance */
|
||||
sentinelReconnectInstance(ri);
|
||||
sentinelSendPeriodicCommands(ri);
|
||||
sentinelPingInstance(ri);
|
||||
|
||||
/* ============== ACTING HALF ============= */
|
||||
/* We don't proceed with the acting half if we are in TILT mode.
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ 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]) &&
|
||||
argv[j]->encoding == REDIS_ENCODING_RAW &&
|
||||
sdslen(argv[j]->ptr) > SLOWLOG_ENTRY_MAX_STRING)
|
||||
{
|
||||
sds s = sdsnewlen(argv[j]->ptr, SLOWLOG_ENTRY_MAX_STRING);
|
||||
|
||||
+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
-2
@@ -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
|
||||
|
||||
+14
-2
@@ -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) {
|
||||
@@ -428,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);
|
||||
}
|
||||
|
||||
+101
-715
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -402,7 +402,7 @@ void getRandomHexChars(char *p, unsigned int len) {
|
||||
/* Turn it into hex digits taking just 4 bits out of 8 for every byte. */
|
||||
for (j = 0; j < len; j++)
|
||||
p[j] = charset[p[j] & 0x0F];
|
||||
if (fp) fclose(fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/* Given the filename, return the absolute path as an SDS string, or NULL
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.9.54"
|
||||
#define REDIS_VERSION "2.8.4"
|
||||
|
||||
+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,130 +0,0 @@
|
||||
# Cluster-specific test functions.
|
||||
#
|
||||
# Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
|
||||
# This softare is released under the BSD License. See the COPYING file for
|
||||
# more information.
|
||||
|
||||
# Returns a parsed CLUSTER NODES output as a list of dictionaries.
|
||||
proc get_cluster_nodes id {
|
||||
set lines [split [R $id cluster nodes] "\r\n"]
|
||||
set nodes {}
|
||||
foreach l $lines {
|
||||
set l [string trim $l]
|
||||
if {$l eq {}} continue
|
||||
set args [split $l]
|
||||
set node [dict create \
|
||||
id [lindex $args 0] \
|
||||
addr [lindex $args 1] \
|
||||
flags [split [lindex $args 2] ,] \
|
||||
slaveof [lindex $args 3] \
|
||||
ping_sent [lindex $args 4] \
|
||||
pong_recv [lindex $args 5] \
|
||||
config_epoch [lindex $args 6] \
|
||||
linkstate [lindex $args 7] \
|
||||
slots [lrange $args 8 -1] \
|
||||
]
|
||||
lappend nodes $node
|
||||
}
|
||||
return $nodes
|
||||
}
|
||||
|
||||
# Test node for flag.
|
||||
proc has_flag {node flag} {
|
||||
expr {[lsearch -exact [dict get $node flags] $flag] != -1}
|
||||
}
|
||||
|
||||
# Returns the parsed myself node entry as a dictionary.
|
||||
proc get_myself id {
|
||||
set nodes [get_cluster_nodes $id]
|
||||
foreach n $nodes {
|
||||
if {[has_flag $n myself]} {return $n}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
# Return the value of the specified CLUSTER INFO field.
|
||||
proc CI {n field} {
|
||||
get_info_field [R $n cluster info] $field
|
||||
}
|
||||
|
||||
# Assuming nodes are reest, this function performs slots allocation.
|
||||
# Only the first 'n' nodes are used.
|
||||
proc cluster_allocate_slots {n} {
|
||||
set slot 16383
|
||||
while {$slot >= 0} {
|
||||
# Allocate successive slots to random nodes.
|
||||
set node [randomInt $n]
|
||||
lappend slots_$node $slot
|
||||
incr slot -1
|
||||
}
|
||||
for {set j 0} {$j < $n} {incr j} {
|
||||
R $j cluster addslots {*}[set slots_${j}]
|
||||
}
|
||||
}
|
||||
|
||||
# Check that cluster nodes agree about "state", or raise an error.
|
||||
proc assert_cluster_state {state} {
|
||||
foreach_redis_id id {
|
||||
if {[instance_is_killed redis $id]} continue
|
||||
wait_for_condition 1000 50 {
|
||||
[CI $id cluster_state] eq $state
|
||||
} else {
|
||||
fail "Cluster node $id cluster_state:[CI $id cluster_state]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Search the first node starting from ID $first that is not
|
||||
# already configured as a slave.
|
||||
proc cluster_find_available_slave {first} {
|
||||
foreach_redis_id id {
|
||||
if {$id < $first} continue
|
||||
if {[instance_is_killed redis $id]} continue
|
||||
set me [get_myself $id]
|
||||
if {[dict get $me slaveof] eq {-}} {return $id}
|
||||
}
|
||||
fail "No available slaves"
|
||||
}
|
||||
|
||||
# Add 'slaves' slaves to a cluster composed of 'masters' masters.
|
||||
# It assumes that masters are allocated sequentially from instance ID 0
|
||||
# to N-1.
|
||||
proc cluster_allocate_slaves {masters slaves} {
|
||||
for {set j 0} {$j < $slaves} {incr j} {
|
||||
set master_id [expr {$j % $masters}]
|
||||
set slave_id [cluster_find_available_slave $masters]
|
||||
set master_myself [get_myself $master_id]
|
||||
R $slave_id cluster replicate [dict get $master_myself id]
|
||||
}
|
||||
}
|
||||
|
||||
# Create a cluster composed of the specified number of masters and slaves.
|
||||
proc create_cluster {masters slaves} {
|
||||
cluster_allocate_slots $masters
|
||||
if {$slaves} {
|
||||
cluster_allocate_slaves $masters $slaves
|
||||
}
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
# Set the cluster node-timeout to all the reachalbe nodes.
|
||||
proc set_cluster_node_timeout {to} {
|
||||
foreach_redis_id id {
|
||||
catch {R $id CONFIG SET cluster-node-timeout $to}
|
||||
}
|
||||
}
|
||||
|
||||
# Check if the cluster is writable and readable. Use node "id"
|
||||
# as a starting point to talk with the cluster.
|
||||
proc cluster_write_test {id} {
|
||||
set prefix [randstring 20 20 alpha]
|
||||
set port [get_instance_attrib redis $id port]
|
||||
set cluster [redis_cluster 127.0.0.1:$port]
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
$cluster set key.$j $prefix.$j
|
||||
}
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
assert {[$cluster get key.$j] eq "$prefix.$j"}
|
||||
}
|
||||
$cluster close
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
# Cluster 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.
|
||||
|
||||
cd tests/cluster
|
||||
source cluster.tcl
|
||||
source ../instances.tcl
|
||||
source ../../support/cluster.tcl ; # Redis Cluster client.
|
||||
|
||||
set ::instances_count 20 ; # How many instances we use at max.
|
||||
|
||||
proc main {} {
|
||||
parse_options
|
||||
spawn_instance redis $::redis_base_port $::instances_count {
|
||||
"cluster-enabled yes"
|
||||
"appendonly yes"
|
||||
}
|
||||
run_tests
|
||||
cleanup
|
||||
}
|
||||
|
||||
if {[catch main e]} {
|
||||
puts $::errorInfo
|
||||
cleanup
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
if {$::simulate_error} {
|
||||
test "This test will fail" {
|
||||
fail "Simulated error"
|
||||
}
|
||||
}
|
||||
|
||||
test "Different nodes have different IDs" {
|
||||
set ids {}
|
||||
set numnodes 0
|
||||
foreach_redis_id id {
|
||||
incr numnodes
|
||||
# Every node should just know itself.
|
||||
set nodeid [dict get [get_myself $id] id]
|
||||
assert {$nodeid ne {}}
|
||||
lappend ids $nodeid
|
||||
}
|
||||
set numids [llength [lsort -unique $ids]]
|
||||
assert {$numids == $numnodes}
|
||||
}
|
||||
|
||||
test "It is possible to perform slot allocation" {
|
||||
cluster_allocate_slots 5
|
||||
}
|
||||
|
||||
test "After the join, every node gets a different config epoch" {
|
||||
set trynum 60
|
||||
while {[incr trynum -1] != 0} {
|
||||
# We check that this condition is true for *all* the nodes.
|
||||
set ok 1 ; # Will be set to 0 every time a node is not ok.
|
||||
foreach_redis_id id {
|
||||
set epochs {}
|
||||
foreach n [get_cluster_nodes $id] {
|
||||
lappend epochs [dict get $n config_epoch]
|
||||
}
|
||||
if {[lsort $epochs] != [lsort -unique $epochs]} {
|
||||
set ok 0 ; # At least one collision!
|
||||
}
|
||||
}
|
||||
if {$ok} break
|
||||
after 1000
|
||||
puts -nonewline .
|
||||
flush stdout
|
||||
}
|
||||
if {$trynum == 0} {
|
||||
fail "Config epoch conflict resolution is not working."
|
||||
}
|
||||
}
|
||||
|
||||
test "Nodes should report cluster_state is ok now" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "It is possible to write and read from the cluster" {
|
||||
cluster_write_test 0
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
test "Create a 5 nodes cluster" {
|
||||
create_cluster 5 5
|
||||
}
|
||||
|
||||
test "Cluster should start ok" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Killing two slave nodes" {
|
||||
kill_instance redis 5
|
||||
kill_instance redis 6
|
||||
}
|
||||
|
||||
test "Cluster should be still up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Killing one master node" {
|
||||
kill_instance redis 0
|
||||
}
|
||||
|
||||
# Note: the only slave of instance 0 is already down so no
|
||||
# failover is possible, that would change the state back to ok.
|
||||
test "Cluster should be down now" {
|
||||
assert_cluster_state fail
|
||||
}
|
||||
|
||||
test "Restarting master node" {
|
||||
restart_instance redis 0
|
||||
}
|
||||
|
||||
test "Cluster should be up again" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
test "Create a 5 nodes cluster" {
|
||||
create_cluster 5 5
|
||||
}
|
||||
|
||||
test "Cluster is up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 0
|
||||
}
|
||||
|
||||
test "Instance #5 is a slave" {
|
||||
assert {[RI 5 role] eq {slave}}
|
||||
}
|
||||
|
||||
test "Killing one master node" {
|
||||
kill_instance redis 0
|
||||
}
|
||||
|
||||
test "Cluster should eventually be up again" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 1
|
||||
}
|
||||
|
||||
test "Instance #5 is now a master" {
|
||||
assert {[RI 5 role] eq {master}}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
# Initialization tests -- most units will start including this.
|
||||
|
||||
test "(init) Restart killed instances" {
|
||||
foreach type {redis} {
|
||||
foreach_${type}_id id {
|
||||
if {[get_instance_attrib $type $id pid] == -1} {
|
||||
puts -nonewline "$type/$id "
|
||||
flush stdout
|
||||
restart_instance $type $id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster nodes are reachable" {
|
||||
foreach_redis_id id {
|
||||
# Every node should just know itself.
|
||||
assert {[R $id ping] eq {PONG}}
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster nodes hard reset" {
|
||||
foreach_redis_id id {
|
||||
catch {R $id flushall} ; # May fail for readonly slaves.
|
||||
R $id cluster reset hard
|
||||
R $id config set cluster-node-timeout 3000
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster Join and auto-discovery test" {
|
||||
# Join node 0 with 1, 1 with 2, ... and so forth.
|
||||
# If auto-discovery works all nodes will know every other node
|
||||
# eventually.
|
||||
set ids {}
|
||||
foreach_redis_id id {lappend ids $id}
|
||||
for {set j 0} {$j < [expr [llength $ids]-1]} {incr j} {
|
||||
set a [lindex $ids $j]
|
||||
set b [lindex $ids [expr $j+1]]
|
||||
set b_port [get_instance_attrib redis $b port]
|
||||
R $a cluster meet 127.0.0.1 $b_port
|
||||
}
|
||||
|
||||
foreach_redis_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[llength [get_cluster_nodes $id]] == [llength $ids]
|
||||
} else {
|
||||
fail "Cluster failed to join into a full mesh."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "Before slots allocation, all nodes report cluster failure" {
|
||||
assert_cluster_state fail
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
redis_*
|
||||
sentinel_*
|
||||
@@ -1,394 +0,0 @@
|
||||
# Multi-instance test framework.
|
||||
# This is used in order to test Sentinel and Redis Cluster, and provides
|
||||
# basic capabilities for spawning and handling N parallel Redis / Sentinel
|
||||
# instances.
|
||||
#
|
||||
# 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 ../support/redis.tcl
|
||||
source ../support/util.tcl
|
||||
source ../support/server.tcl
|
||||
source ../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 ::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 tmp}]} {
|
||||
puts "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 {conf {}}} {
|
||||
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 instance.
|
||||
set dirname "${type}_${j}"
|
||||
lappend ::dirs $dirname
|
||||
catch {exec rm -rf $dirname}
|
||||
file mkdir $dirname
|
||||
|
||||
# Write the instance 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"
|
||||
# Add additional config files
|
||||
foreach directive $conf {
|
||||
puts $cfg $directive
|
||||
}
|
||||
close $cfg
|
||||
|
||||
# Finally exec it and remember the pid for later cleanup.
|
||||
if {$type eq "redis"} {
|
||||
set prgname redis-server
|
||||
} elseif {$type eq "sentinel"} {
|
||||
set prgname redis-sentinel
|
||||
} else {
|
||||
error "Unknown instance type."
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 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 ../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]
|
||||
expr {$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]
|
||||
}
|
||||
|
||||
@@ -91,13 +91,8 @@ tags {"aof"} {
|
||||
assert_equal 1 [is_alive $srv]
|
||||
}
|
||||
|
||||
test "Fixed AOF: Keyspace should contain values that were parsable" {
|
||||
test "Fixed AOF: Keyspace should contain values that were parsable" {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
wait_for_condition 50 100 {
|
||||
[catch {$client ping} e] == 0
|
||||
} else {
|
||||
fail "Loading DB is taking too much time."
|
||||
}
|
||||
assert_equal "hello" [$client get foo]
|
||||
assert_equal "" [$client get bar]
|
||||
}
|
||||
@@ -117,11 +112,6 @@ tags {"aof"} {
|
||||
|
||||
test "AOF+SPOP: Set should have 1 member" {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
wait_for_condition 50 100 {
|
||||
[catch {$client ping} e] == 0
|
||||
} else {
|
||||
fail "Loading DB is taking too much time."
|
||||
}
|
||||
assert_equal 1 [$client scard set]
|
||||
}
|
||||
}
|
||||
@@ -140,11 +130,6 @@ tags {"aof"} {
|
||||
|
||||
test "AOF+EXPIRE: List should be empty" {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
wait_for_condition 50 100 {
|
||||
[catch {$client ping} e] == 0
|
||||
} else {
|
||||
fail "Loading DB is taking too much time."
|
||||
}
|
||||
assert_equal 0 [$client llen list]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +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.
|
||||
|
||||
cd tests/sentinel
|
||||
source ../instances.tcl
|
||||
|
||||
set ::instances_count 5 ; # How many instances we use at max.
|
||||
|
||||
proc main {} {
|
||||
parse_options
|
||||
spawn_instance sentinel $::sentinel_base_port $::instances_count
|
||||
spawn_instance redis $::redis_base_port $::instances_count
|
||||
run_tests
|
||||
cleanup
|
||||
}
|
||||
|
||||
if {[catch main e]} {
|
||||
puts $::errorInfo
|
||||
cleanup
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../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 "../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 "../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,303 +0,0 @@
|
||||
# Tcl redis cluster client as a wrapper of redis.rb.
|
||||
# Copyright (C) 2014 Salvatore Sanfilippo
|
||||
# Released under the BSD license like Redis itself
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# set c [redis_cluster 127.0.0.1 6379 127.0.0.1 6380]
|
||||
# $c set foo
|
||||
# $c get foo
|
||||
# $c close
|
||||
|
||||
package require Tcl 8.5
|
||||
package provide redis_cluster 0.1
|
||||
|
||||
namespace eval redis_cluster {}
|
||||
set ::redis_cluster::id 0
|
||||
array set ::redis_cluster::startup_nodes {}
|
||||
array set ::redis_cluster::nodes {}
|
||||
array set ::redis_cluster::slots {}
|
||||
|
||||
# List of "plain" commands, which are commands where the sole key is always
|
||||
# the first argument.
|
||||
set ::redis_cluster::plain_commands {
|
||||
get set setnx setex psetex append strlen exists setbit getbit
|
||||
setrange getrange substr incr decr rpush lpush rpushx lpushx
|
||||
linsert rpop lpop brpop llen lindex lset lrange ltrim lrem
|
||||
sadd srem sismember scard spop srandmember smembers sscan zadd
|
||||
zincrby zrem zremrangebyscore zremrangebyrank zremrangebylex zrange
|
||||
zrangebyscore zrevrangebyscore zrangebylex zrevrangebylex zcount
|
||||
zlexcount zrevrange zcard zscore zrank zrevrank zscan hset hsetnx
|
||||
hget hmset hmget hincrby hincrbyfloat hdel hlen hkeys hvals
|
||||
hgetall hexists hscan incrby decrby incrbyfloat getset move
|
||||
expire expireat pexpire pexpireat type ttl pttl persist restore
|
||||
dump bitcount bitpos pfadd pfcount
|
||||
}
|
||||
|
||||
proc redis_cluster {nodes} {
|
||||
set id [incr ::redis_cluster::id]
|
||||
set ::redis_cluster::startup_nodes($id) $nodes
|
||||
set ::redis_cluster::nodes($id) {}
|
||||
set ::redis_cluster::slots($id) {}
|
||||
set handle [interp alias {} ::redis_cluster::instance$id {} ::redis_cluster::__dispatch__ $id]
|
||||
$handle refresh_nodes_map
|
||||
return $handle
|
||||
}
|
||||
|
||||
# Totally reset the slots / nodes state for the client, calls
|
||||
# CLUSTER NODES in the first startup node available, populates the
|
||||
# list of nodes ::redis_cluster::nodes($id) with an hash mapping node
|
||||
# ip:port to a representation of the node (another hash), and finally
|
||||
# maps ::redis_cluster::slots($id) with an hash mapping slot numbers
|
||||
# to node IDs.
|
||||
#
|
||||
# This function is called when a new Redis Cluster client is initialized
|
||||
# and every time we get a -MOVED redirection error.
|
||||
proc ::redis_cluster::__method__refresh_nodes_map {id} {
|
||||
# Contact the first responding startup node.
|
||||
set idx 0; # Index of the node that will respond.
|
||||
set errmsg {}
|
||||
foreach start_node $::redis_cluster::startup_nodes($id) {
|
||||
lassign [split $start_node :] start_host start_port
|
||||
if {[catch {
|
||||
set r {}
|
||||
set r [redis $start_host $start_port]
|
||||
set nodes_descr [$r cluster nodes]
|
||||
$r close
|
||||
} e]} {
|
||||
if {$r ne {}} {catch {$r close}}
|
||||
incr idx
|
||||
if {[string length $errmsg] < 200} {
|
||||
append errmsg " $start_node: $e"
|
||||
}
|
||||
continue ; # Try next.
|
||||
} else {
|
||||
break; # Good node found.
|
||||
}
|
||||
}
|
||||
|
||||
if {$idx == [llength $::redis_cluster::startup_nodes($id)]} {
|
||||
error "No good startup node found. $errmsg"
|
||||
}
|
||||
|
||||
# Put the node that responded as first in the list if it is not
|
||||
# already the first.
|
||||
if {$idx != 0} {
|
||||
set l $::redis_cluster::startup_nodes($id)
|
||||
set left [lrange $l 0 [expr {$idx-1}]]
|
||||
set right [lrange $l [expr {$idx+1}] end]
|
||||
set l [concat [lindex $l $idx] $left $right]
|
||||
set ::redis_cluster::startup_nodes($id) $l
|
||||
}
|
||||
|
||||
# Parse CLUSTER NODES output to populate the nodes description.
|
||||
set nodes {} ; # addr -> node description hash.
|
||||
foreach line [split $nodes_descr "\n"] {
|
||||
set line [string trim $line]
|
||||
if {$line eq {}} continue
|
||||
set args [split $line " "]
|
||||
lassign $args nodeid addr flags slaveof pingsent pongrecv configepoch linkstate
|
||||
set slots [lrange $args 8 end]
|
||||
if {$addr eq {:0}} {
|
||||
set addr $start_host:$start_port
|
||||
}
|
||||
lassign [split $addr :] host port
|
||||
|
||||
# Connect to the node
|
||||
set link {}
|
||||
catch {set link [redis $host $port]}
|
||||
|
||||
# Build this node description as an hash.
|
||||
set node [dict create \
|
||||
id $nodeid \
|
||||
addr $addr \
|
||||
host $host \
|
||||
port $port \
|
||||
flags $flags \
|
||||
slaveof $slaveof \
|
||||
slots $slots \
|
||||
link $link \
|
||||
]
|
||||
dict set nodes $addr $node
|
||||
lappend ::redis_cluster::startup_nodes($id) $addr
|
||||
}
|
||||
|
||||
# Close all the existing links in the old nodes map, and set the new
|
||||
# map as current.
|
||||
foreach n $::redis_cluster::nodes($id) {
|
||||
catch {
|
||||
[dict get $n link] close
|
||||
}
|
||||
}
|
||||
set ::redis_cluster::nodes($id) $nodes
|
||||
|
||||
# Populates the slots -> nodes map.
|
||||
dict for {addr node} $nodes {
|
||||
foreach slotrange [dict get $node slots] {
|
||||
lassign [split $slotrange -] start end
|
||||
if {$end == {}} {set end $start}
|
||||
for {set j $start} {$j <= $end} {incr j} {
|
||||
dict set ::redis_cluster::slots($id) $j $addr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Only retain unique entries in the startup nodes list
|
||||
set ::redis_cluster::startup_nodes($id) [lsort -unique $::redis_cluster::startup_nodes($id)]
|
||||
}
|
||||
|
||||
# Free a redis_cluster handle.
|
||||
proc ::redis_cluster::__method__close {id} {
|
||||
catch {
|
||||
set nodes $::redis_cluster::nodes($id)
|
||||
dict for {addr node} $nodes {
|
||||
catch {
|
||||
[dict get $node link] close
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {unset ::redis_cluster::startup_nodes($id)}
|
||||
catch {unset ::redis_cluster::nodes($id)}
|
||||
catch {unset ::redis_cluster::slots($id)}
|
||||
catch {interp alias {} ::redis_cluster::instance$id {}}
|
||||
}
|
||||
|
||||
proc ::redis_cluster::__dispatch__ {id method args} {
|
||||
if {[info command ::redis_cluster::__method__$method] eq {}} {
|
||||
# Get the keys from the command.
|
||||
set keys [::redis_cluster::get_keys_from_command $method $args]
|
||||
if {$keys eq {}} {
|
||||
error "Redis command '$method' is not supported by redis_cluster."
|
||||
}
|
||||
|
||||
# Resolve the keys in the corresponding hash slot they hash to.
|
||||
set slot [::redis_cluster::get_slot_from_keys $keys]
|
||||
if {$slot eq {}} {
|
||||
error "Invalid command: multiple keys not hashing to the same slot."
|
||||
}
|
||||
|
||||
# Get the node mapped to this slot.
|
||||
set node_addr [dict get $::redis_cluster::slots($id) $slot]
|
||||
if {$node_addr eq {}} {
|
||||
error "No mapped node for slot $slot."
|
||||
}
|
||||
|
||||
# Execute the command in the node we think is the slot owner.
|
||||
set retry 100
|
||||
while {[incr retry -1]} {
|
||||
if {$retry < 5} {after 100}
|
||||
set node [dict get $::redis_cluster::nodes($id) $node_addr]
|
||||
set link [dict get $node link]
|
||||
if {[catch {$link $method {*}$args} e]} {
|
||||
if {$link eq {} || \
|
||||
[string range $e 0 4] eq {MOVED} || \
|
||||
[string range $e 0 2] eq {I/O} \
|
||||
} {
|
||||
# MOVED redirection.
|
||||
::redis_cluster::__method__refresh_nodes_map $id
|
||||
set node_addr [dict get $::redis_cluster::slots($id) $slot]
|
||||
continue
|
||||
} elseif {[string range $e 0 2] eq {ASK}} {
|
||||
# ASK redirection.
|
||||
set node_addr [lindex $e 2]
|
||||
continue
|
||||
} else {
|
||||
# Non redirecting error.
|
||||
error $e $::errorInfo $::errorCode
|
||||
}
|
||||
} else {
|
||||
# OK query went fine
|
||||
return $e
|
||||
}
|
||||
}
|
||||
error "Too many redirections or failures contacting Redis Cluster."
|
||||
} else {
|
||||
uplevel 1 [list ::redis_cluster::__method__$method $id] $args
|
||||
}
|
||||
}
|
||||
|
||||
proc ::redis_cluster::get_keys_from_command {cmd argv} {
|
||||
set cmd [string tolower $cmd]
|
||||
# Most Redis commands get just one key as first argument.
|
||||
if {[lsearch -exact $::redis_cluster::plain_commands $cmd] != -1} {
|
||||
return [list [lindex $argv 0]]
|
||||
}
|
||||
|
||||
# Special handling for other commands
|
||||
switch -exact $cmd {
|
||||
mget {return $argv}
|
||||
}
|
||||
|
||||
# All the remaining commands are not handled.
|
||||
return {}
|
||||
}
|
||||
|
||||
# Returns the CRC16 of the specified string.
|
||||
# The CRC parameters are described in the Redis Cluster specification.
|
||||
set ::redis_cluster::XMODEMCRC16Lookup {
|
||||
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
|
||||
}
|
||||
|
||||
proc ::redis_cluster::crc16 {s} {
|
||||
set s [encoding convertto ascii $s]
|
||||
set crc 0
|
||||
foreach char [split $s {}] {
|
||||
scan $char %c byte
|
||||
set crc [expr {(($crc<<8)&0xffff) ^ [lindex $::redis_cluster::XMODEMCRC16Lookup [expr {(($crc>>8)^$byte) & 0xff}]]}]
|
||||
}
|
||||
return $crc
|
||||
}
|
||||
|
||||
# Hash a single key returning the slot it belongs to, Implemented hash
|
||||
# tags as described in the Redis Cluster specification.
|
||||
proc ::redis_cluster::hash {key} {
|
||||
# TODO: Handle hash slots.
|
||||
expr {[::redis_cluster::crc16 $key] & 16383}
|
||||
}
|
||||
|
||||
# Return the slot the specified keys hash to.
|
||||
# If the keys hash to multiple slots, an empty string is returned to
|
||||
# signal that the command can't be run in Redis Cluster.
|
||||
proc ::redis_cluster::get_slot_from_keys {keys} {
|
||||
set slot {}
|
||||
foreach k $keys {
|
||||
set s [::redis_cluster::hash $k]
|
||||
if {$slot eq {}} {
|
||||
set slot $s
|
||||
} elseif {$slot != $s} {
|
||||
return {} ; # Error
|
||||
}
|
||||
}
|
||||
return $slot
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
# Tcl client library - used by the Redis test
|
||||
# Copyright (C) 2009-2014 Salvatore Sanfilippo
|
||||
# Tcl clinet library - used by test-redis.tcl script for now
|
||||
# Copyright (C) 2009 Salvatore Sanfilippo
|
||||
# Released under the BSD license like Redis itself
|
||||
#
|
||||
# Example usage:
|
||||
@@ -170,10 +170,7 @@ proc ::redis::redis_read_reply fd {
|
||||
- {return -code error [redis_read_line $fd]}
|
||||
$ {redis_bulk_read $fd}
|
||||
* {redis_multi_bulk_read $fd}
|
||||
default {
|
||||
if {$type eq {}} {return -code error "I/O error reading reply"}
|
||||
return -code error "Bad protocol, '$type' as reply type byte"
|
||||
}
|
||||
default {return -code error "Bad protocol, '$type' as reply type byte"}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-24
@@ -40,11 +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] ||
|
||||
[string match {*cannot examine*} $output]} {
|
||||
# In a few tests we kill the server process.
|
||||
set output "0 leaks"
|
||||
}
|
||||
set output
|
||||
} {*0 leaks*}
|
||||
}
|
||||
@@ -84,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
|
||||
@@ -106,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]
|
||||
@@ -212,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
|
||||
}
|
||||
@@ -236,7 +225,7 @@ proc start_server {options {code undefined}} {
|
||||
|
||||
# find out the pid
|
||||
while {![info exists pid]} {
|
||||
regexp {PID:\s(\d+)} [exec cat $stdout] _ pid
|
||||
regexp {\[(\d+)\]} [exec cat $stdout] _ pid
|
||||
after 100
|
||||
}
|
||||
|
||||
|
||||
+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]
|
||||
|
||||
@@ -261,14 +261,6 @@ start_server {tags {"basic"}} {
|
||||
assert_equal 20 [r get x]
|
||||
}
|
||||
|
||||
test "DEL against expired key" {
|
||||
r debug set-active-expire 0
|
||||
r setex keyExpire 1 valExpire
|
||||
after 1100
|
||||
assert_equal 0 [r del keyExpire]
|
||||
r debug set-active-expire 1
|
||||
}
|
||||
|
||||
test {EXISTS} {
|
||||
set res {}
|
||||
r set newkey test
|
||||
|
||||
+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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-81
@@ -31,49 +31,12 @@ start_server {tags {"dump"}} {
|
||||
set e {}
|
||||
catch {r restore foo 0 "..."} e
|
||||
set e
|
||||
} {*BUSYKEY*}
|
||||
|
||||
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*}
|
||||
} {*is busy*}
|
||||
|
||||
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 +56,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 +133,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,170 +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 {HyperLogLogs are promote from sparse to dense} {
|
||||
r del hll
|
||||
r config set hll-sparse-max-bytes 3000
|
||||
set n 0
|
||||
while {$n < 100000} {
|
||||
set elements {}
|
||||
for {set j 0} {$j < 100} {incr j} {lappend elements [expr rand()]}
|
||||
incr n 100
|
||||
r pfadd hll {*}$elements
|
||||
set card [r pfcount hll]
|
||||
set err [expr {abs($card-$n)}]
|
||||
assert {$err < (double($card)/100)*5}
|
||||
if {$n < 1000} {
|
||||
assert {[r pfdebug encoding hll] eq {sparse}}
|
||||
} elseif {$n > 10000} {
|
||||
assert {[r pfdebug encoding hll] eq {dense}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {HyperLogLog sparse encoding stress test} {
|
||||
for {set x 0} {$x < 1000} {incr x} {
|
||||
r del hll1 hll2
|
||||
set numele [randomInt 100]
|
||||
set elements {}
|
||||
for {set j 0} {$j < $numele} {incr j} {
|
||||
lappend elements [expr rand()]
|
||||
}
|
||||
# Force dense representation of hll2
|
||||
r pfadd hll2
|
||||
r pfdebug todense hll2
|
||||
r pfadd hll1 {*}$elements
|
||||
r pfadd hll2 {*}$elements
|
||||
assert {[r pfdebug encoding hll1] eq {sparse}}
|
||||
assert {[r pfdebug encoding hll2] eq {dense}}
|
||||
# Cardinality estimated should match exactly.
|
||||
assert {[r pfcount hll1] eq [r pfcount hll2]}
|
||||
}
|
||||
}
|
||||
|
||||
test {Corrupted sparse HyperLogLogs are detected: Additionl at tail} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r append hll "hello"
|
||||
set e {}
|
||||
catch {r pfcount hll} e
|
||||
set e
|
||||
} {*INVALIDOBJ*}
|
||||
|
||||
test {Corrupted sparse HyperLogLogs are detected: Broken magic} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r setrange hll 0 "0123"
|
||||
set e {}
|
||||
catch {r pfcount hll} e
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
test {Corrupted sparse HyperLogLogs are detected: Invalid encoding} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r setrange hll 4 "x"
|
||||
set e {}
|
||||
catch {r pfcount hll} e
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
test {Corrupted dense HyperLogLogs are detected: Wrong length} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r setrange hll 4 "\x00"
|
||||
set e {}
|
||||
catch {r pfcount hll} e
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
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 {PFCOUNT multiple-keys merge returns cardinality of union} {
|
||||
r del hll1 hll2 hll3
|
||||
for {set x 1} {$x < 10000} {incr x} {
|
||||
# Force dense representation of hll2
|
||||
r pfadd hll1 "foo-$x"
|
||||
r pfadd hll2 "bar-$x"
|
||||
r pfadd hll3 "zap-$x"
|
||||
|
||||
set card [r pfcount hll1 hll2 hll3]
|
||||
set realcard [expr {$x*3}]
|
||||
set err [expr {abs($card-$realcard)}]
|
||||
assert {$err < (double($card)/100)*5}
|
||||
}
|
||||
}
|
||||
|
||||
test {PFDEBUG GETREG returns the HyperLogLog raw registers} {
|
||||
r del hll
|
||||
r pfadd hll 1 2 3
|
||||
llength [r pfdebug getreg hll]
|
||||
} {16384}
|
||||
|
||||
test {PFADD / PFCOUNT cache invalidation works} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r pfcount hll
|
||||
assert {[r getrange hll 15 15] eq "\x00"}
|
||||
r pfadd hll a b c
|
||||
assert {[r getrange hll 15 15] eq "\x00"}
|
||||
r pfadd hll 1 2 3
|
||||
assert {[r getrange hll 15 15] eq "\x80"}
|
||||
}
|
||||
}
|
||||
@@ -311,34 +311,8 @@ start_server {tags {"scripting"}} {
|
||||
r config set slave-read-only yes
|
||||
r slaveof 127.0.0.1 0
|
||||
r debug loadaof
|
||||
set res [r get foo]
|
||||
r slaveof no one
|
||||
set res
|
||||
r get foo
|
||||
} {102}
|
||||
|
||||
test {We can call scripts rewriting client->argv from Lua} {
|
||||
r del myset
|
||||
r sadd myset a b c
|
||||
r mset a 1 b 2 c 3 d 4
|
||||
assert {[r spop myset] ne {}}
|
||||
assert {[r spop myset] ne {}}
|
||||
assert {[r spop myset] ne {}}
|
||||
assert {[r mget a b c d] eq {1 2 3 4}}
|
||||
assert {[r spop myset] eq {}}
|
||||
}
|
||||
|
||||
test {Call Redis command with many args from Lua (issue #1764)} {
|
||||
r eval {
|
||||
local i
|
||||
local x={}
|
||||
redis.call('del','mylist')
|
||||
for i=1,100 do
|
||||
table.insert(x,i)
|
||||
end
|
||||
redis.call('rpush','mylist',unpack(x))
|
||||
return redis.call('lrange','mylist',0,-1)
|
||||
} 0
|
||||
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100}
|
||||
}
|
||||
|
||||
# Start a new server since the last test in this stanza will kill the
|
||||
@@ -352,7 +326,6 @@ start_server {tags {"scripting"}} {
|
||||
catch {r ping} e
|
||||
assert_match {BUSY*} $e
|
||||
r script kill
|
||||
after 200 ; # Give some time to Lua to call the hook again...
|
||||
assert_equal [r ping] "PONG"
|
||||
}
|
||||
|
||||
@@ -444,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]'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,62 +296,6 @@ start_server {tags {"zset"}} {
|
||||
assert_error "*not*float*" {r zrangebyscore fooz 1 NaN}
|
||||
}
|
||||
|
||||
proc create_default_lex_zset {} {
|
||||
create_zset zset {0 alpha 0 bar 0 cool 0 down
|
||||
0 elephant 0 foo 0 great 0 hill
|
||||
0 omega}
|
||||
}
|
||||
|
||||
test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZCOUNT basics" {
|
||||
create_default_lex_zset
|
||||
|
||||
# inclusive range
|
||||
assert_equal {alpha bar cool} [r zrangebylex zset - \[cool]
|
||||
assert_equal {bar cool down} [r zrangebylex zset \[bar \[down]
|
||||
assert_equal {great hill omega} [r zrangebylex zset \[g +]
|
||||
assert_equal {cool bar alpha} [r zrevrangebylex zset \[cool -]
|
||||
assert_equal {down cool bar} [r zrevrangebylex zset \[down \[bar]
|
||||
assert_equal {omega hill great foo elephant down} [r zrevrangebylex zset + \[d]
|
||||
assert_equal 3 [r zlexcount zset \[ele \[h]
|
||||
|
||||
# exclusive range
|
||||
assert_equal {alpha bar} [r zrangebylex zset - (cool]
|
||||
assert_equal {cool} [r zrangebylex zset (bar (down]
|
||||
assert_equal {hill omega} [r zrangebylex zset (great +]
|
||||
assert_equal {bar alpha} [r zrevrangebylex zset (cool -]
|
||||
assert_equal {cool} [r zrevrangebylex zset (down (bar]
|
||||
assert_equal {omega hill} [r zrevrangebylex zset + (great]
|
||||
assert_equal 2 [r zlexcount zset (ele (great]
|
||||
|
||||
# inclusive and exclusive
|
||||
assert_equal {} [r zrangebylex zset (az (b]
|
||||
assert_equal {} [r zrangebylex zset (z +]
|
||||
assert_equal {} [r zrangebylex zset - \[aaaa]
|
||||
assert_equal {} [r zrevrangebylex zset \[elez \[elex]
|
||||
assert_equal {} [r zrevrangebylex zset (hill (omega]
|
||||
}
|
||||
|
||||
test "ZRANGEBYSLEX with LIMIT" {
|
||||
create_default_lex_zset
|
||||
assert_equal {alpha bar} [r zrangebylex zset - \[cool LIMIT 0 2]
|
||||
assert_equal {bar cool} [r zrangebylex zset - \[cool LIMIT 1 2]
|
||||
assert_equal {} [r zrangebylex zset \[bar \[down LIMIT 0 0]
|
||||
assert_equal {} [r zrangebylex zset \[bar \[down LIMIT 2 0]
|
||||
assert_equal {bar} [r zrangebylex zset \[bar \[down LIMIT 0 1]
|
||||
assert_equal {cool} [r zrangebylex zset \[bar \[down LIMIT 1 1]
|
||||
assert_equal {bar cool down} [r zrangebylex zset \[bar \[down LIMIT 0 100]
|
||||
assert_equal {omega hill great foo elephant} [r zrevrangebylex zset + \[d LIMIT 0 5]
|
||||
assert_equal {omega hill great foo} [r zrevrangebylex zset + \[d LIMIT 0 4]
|
||||
}
|
||||
|
||||
test "ZRANGEBYLEX with invalid lex range specifiers" {
|
||||
assert_error "*not*string*" {r zrangebylex fooz foo bar}
|
||||
assert_error "*not*string*" {r zrangebylex fooz \[foo bar}
|
||||
assert_error "*not*string*" {r zrangebylex fooz foo \[bar}
|
||||
assert_error "*not*string*" {r zrangebylex fooz +x \[bar}
|
||||
assert_error "*not*string*" {r zrangebylex fooz -x \[bar}
|
||||
}
|
||||
|
||||
test "ZREMRANGEBYSCORE basics" {
|
||||
proc remrangebyscore {min max} {
|
||||
create_zset zset {1 a 2 b 3 c 4 d 5 e}
|
||||
@@ -764,111 +708,6 @@ start_server {tags {"zset"}} {
|
||||
assert_equal {} $err
|
||||
}
|
||||
|
||||
test "ZRANGEBYLEX fuzzy test, 100 ranges in $elements element sorted set - $encoding" {
|
||||
set lexset {}
|
||||
r del zset
|
||||
for {set j 0} {$j < $elements} {incr j} {
|
||||
set e [randstring 0 30 alpha]
|
||||
lappend lexset $e
|
||||
r zadd zset 0 $e
|
||||
}
|
||||
set lexset [lsort -unique $lexset]
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
set min [randstring 0 30 alpha]
|
||||
set max [randstring 0 30 alpha]
|
||||
set mininc [randomInt 2]
|
||||
set maxinc [randomInt 2]
|
||||
if {$mininc} {set cmin "\[$min"} else {set cmin "($min"}
|
||||
if {$maxinc} {set cmax "\[$max"} else {set cmax "($max"}
|
||||
set rev [randomInt 2]
|
||||
if {$rev} {
|
||||
set cmd zrevrangebylex
|
||||
} else {
|
||||
set cmd zrangebylex
|
||||
}
|
||||
|
||||
# Make sure data is the same in both sides
|
||||
assert {[r zrange zset 0 -1] eq $lexset}
|
||||
|
||||
# Get the Redis output
|
||||
set output [r $cmd zset $cmin $cmax]
|
||||
if {$rev} {
|
||||
set outlen [r zlexcount zset $cmax $cmin]
|
||||
} else {
|
||||
set outlen [r zlexcount zset $cmin $cmax]
|
||||
}
|
||||
|
||||
# Compute the same output via Tcl
|
||||
set o {}
|
||||
set copy $lexset
|
||||
if {(!$rev && [string compare $min $max] > 0) ||
|
||||
($rev && [string compare $max $min] > 0)} {
|
||||
# Empty output when ranges are inverted.
|
||||
} else {
|
||||
if {$rev} {
|
||||
# Invert the Tcl array using Redis itself.
|
||||
set copy [r zrevrange zset 0 -1]
|
||||
# Invert min / max as well
|
||||
lassign [list $min $max $mininc $maxinc] \
|
||||
max min maxinc mininc
|
||||
}
|
||||
foreach e $copy {
|
||||
set mincmp [string compare $e $min]
|
||||
set maxcmp [string compare $e $max]
|
||||
if {
|
||||
($mininc && $mincmp >= 0 || !$mininc && $mincmp > 0)
|
||||
&&
|
||||
($maxinc && $maxcmp <= 0 || !$maxinc && $maxcmp < 0)
|
||||
} {
|
||||
lappend o $e
|
||||
}
|
||||
}
|
||||
}
|
||||
assert {$o eq $output}
|
||||
assert {$outlen eq [llength $output]}
|
||||
}
|
||||
}
|
||||
|
||||
test "ZREMRANGEBYLEX fuzzy test, 100 ranges in $elements element sorted set - $encoding" {
|
||||
set lexset {}
|
||||
r del zset zsetcopy
|
||||
for {set j 0} {$j < $elements} {incr j} {
|
||||
set e [randstring 0 30 alpha]
|
||||
lappend lexset $e
|
||||
r zadd zset 0 $e
|
||||
}
|
||||
set lexset [lsort -unique $lexset]
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
# Copy...
|
||||
r zunionstore zsetcopy 1 zset
|
||||
set lexsetcopy $lexset
|
||||
|
||||
set min [randstring 0 30 alpha]
|
||||
set max [randstring 0 30 alpha]
|
||||
set mininc [randomInt 2]
|
||||
set maxinc [randomInt 2]
|
||||
if {$mininc} {set cmin "\[$min"} else {set cmin "($min"}
|
||||
if {$maxinc} {set cmax "\[$max"} else {set cmax "($max"}
|
||||
|
||||
# Make sure data is the same in both sides
|
||||
assert {[r zrange zset 0 -1] eq $lexset}
|
||||
|
||||
# Get the range we are going to remove
|
||||
set torem [r zrangebylex zset $cmin $cmax]
|
||||
set toremlen [r zlexcount zset $cmin $cmax]
|
||||
r zremrangebylex zsetcopy $cmin $cmax
|
||||
set output [r zrange zsetcopy 0 -1]
|
||||
|
||||
# Remove the range with Tcl from the original list
|
||||
if {$toremlen} {
|
||||
set first [lsearch -exact $lexsetcopy [lindex $torem 0]]
|
||||
set last [expr {$first+$toremlen-1}]
|
||||
set lexsetcopy [lreplace $lexsetcopy $first $last]
|
||||
}
|
||||
assert {$lexsetcopy eq $output}
|
||||
}
|
||||
}
|
||||
|
||||
test "ZSETs skiplist implementation backlink consistency test - $encoding" {
|
||||
set diff 0
|
||||
for {set j 0} {$j < $elements} {incr j} {
|
||||
|
||||
@@ -11,8 +11,7 @@ GROUPS = [
|
||||
"transactions",
|
||||
"connection",
|
||||
"server",
|
||||
"scripting",
|
||||
"hyperloglog"
|
||||
"scripting"
|
||||
].freeze
|
||||
|
||||
GROUPS_BY_NAME = Hash[*
|
||||
|
||||
@@ -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