Compare commits
859
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01671edcca | ||
|
|
5f185413df | ||
|
|
2a11e94fdd | ||
|
|
7ea331d601 | ||
|
|
110f0464e0 | ||
|
|
3ef0876b95 | ||
|
|
bb7fea0d5c | ||
|
|
f5c6ebbfe3 | ||
|
|
5a526c22cc | ||
|
|
a076743079 | ||
|
|
2c42b645bc | ||
|
|
0014966c1e | ||
|
|
daca1edb6e | ||
|
|
bbf1af2da3 | ||
|
|
e10c5444e7 | ||
|
|
6fbaeddf3f | ||
|
|
9ec22d9223 | ||
|
|
4b8f4b90b9 | ||
|
|
775cc30a98 | ||
|
|
8a416ca46e | ||
|
|
a27befc495 | ||
|
|
3b9a97984a | ||
|
|
707352439c | ||
|
|
c4dbc7cdec | ||
|
|
18de5395b2 | ||
|
|
d6797d34c0 | ||
|
|
ebb3bd53c2 | ||
|
|
b50e3215d2 | ||
|
|
d4f6a1711d | ||
|
|
2309f15d89 | ||
|
|
456003af25 | ||
|
|
fd112f52dc | ||
|
|
525c488f63 | ||
|
|
74f90c6123 | ||
|
|
10aafdad56 | ||
|
|
b1337b15b6 | ||
|
|
4b16263bd9 | ||
|
|
25a3d9965e | ||
|
|
0c5a06f6bb | ||
|
|
80f7f63b64 | ||
|
|
f70369335d | ||
|
|
5ee2ccf48e | ||
|
|
43ae606430 | ||
|
|
42951ab301 | ||
|
|
5f8360eb21 | ||
|
|
e9e007555e | ||
|
|
3730d118a3 | ||
|
|
7a1e0d9898 | ||
|
|
fbe7545545 | ||
|
|
93eed9ae01 | ||
|
|
2a436aaeab | ||
|
|
1cd0d26c63 | ||
|
|
75f0cd6520 | ||
|
|
850ea57c37 | ||
|
|
29db3227ab | ||
|
|
16546f5aca | ||
|
|
5ba47b50ae | ||
|
|
da838544ae | ||
|
|
591b69c745 | ||
|
|
5f6950caa8 | ||
|
|
36e34a656a | ||
|
|
908be1dbeb | ||
|
|
5b47783d77 | ||
|
|
2df8341c75 | ||
|
|
8beb98574a | ||
|
|
034ca98678 | ||
|
|
edb3987a06 | ||
|
|
e4b0c8ec50 | ||
|
|
3c6f9ac37c | ||
|
|
389ec305b3 | ||
|
|
16559b4615 | ||
|
|
be0061635e | ||
|
|
c3d252d995 | ||
|
|
82154ffab5 | ||
|
|
fa8786f17a | ||
|
|
678df67501 | ||
|
|
a63b9c24f0 | ||
|
|
38a5db6c9a | ||
|
|
ef5fc599b4 | ||
|
|
9aba616866 | ||
|
|
cf737ff1c8 | ||
|
|
1cedebb799 | ||
|
|
09cb281bc3 | ||
|
|
28343966a4 | ||
|
|
12d0195b30 | ||
|
|
fc8f7ec765 | ||
|
|
39be4bf496 | ||
|
|
d9462d2780 | ||
|
|
f62f00e062 | ||
|
|
3a82b8ac64 | ||
|
|
9f98b29cef | ||
|
|
4d2042212c | ||
|
|
45409d3d8b | ||
|
|
0d434a446a | ||
|
|
aa628446f2 | ||
|
|
a2583466e4 | ||
|
|
0cb874819b | ||
|
|
6e0a280cb5 | ||
|
|
74aeb27a26 | ||
|
|
df484c7a2c | ||
|
|
de10b8d9cc | ||
|
|
d4222e6bee | ||
|
|
35a8302b7c | ||
|
|
d61e820faf | ||
|
|
5807558c98 | ||
|
|
f3107559be | ||
|
|
eba1e652f4 | ||
|
|
07bd52113e | ||
|
|
44acd50a11 | ||
|
|
8fe3ecff09 | ||
|
|
56c1f8805e | ||
|
|
747b57cfb6 | ||
|
|
6d53b2f34b | ||
|
|
6d699ea401 | ||
|
|
cceb58d233 | ||
|
|
2374496799 | ||
|
|
c89afc8e5d | ||
|
|
a2c740ea93 | ||
|
|
878c089ec9 | ||
|
|
0ba8fe1a43 | ||
|
|
046a00a03d | ||
|
|
b82d650afa | ||
|
|
3064e9bd4b | ||
|
|
f5efa9bbad | ||
|
|
f4be6f16f2 | ||
|
|
12b56a969f | ||
|
|
99871803c8 | ||
|
|
acfc19633d | ||
|
|
b892ea70ae | ||
|
|
9f40c25a08 | ||
|
|
31f79a46ff | ||
|
|
de663966c6 | ||
|
|
132550efc3 | ||
|
|
7b2e5ff9f9 | ||
|
|
0ab04287aa | ||
|
|
d9f0094154 | ||
|
|
0a6cbabb26 | ||
|
|
c9437fe5a3 | ||
|
|
cd576a1aab | ||
|
|
3d939266be | ||
|
|
0019479f86 | ||
|
|
b20df972ed | ||
|
|
f0e306f4a0 | ||
|
|
67e414c7b8 | ||
|
|
a23502e5e8 | ||
|
|
86cde329d1 | ||
|
|
91e6b4d74e | ||
|
|
72aa797c89 | ||
|
|
064d5c96ac | ||
|
|
293348d0de | ||
|
|
26744da0d1 | ||
|
|
8bbc1897c4 | ||
|
|
119a7000b5 | ||
|
|
8eeb1802ec | ||
|
|
209f266cc5 | ||
|
|
1f8a6d806c | ||
|
|
6b099c7de6 | ||
|
|
aa404fc11b | ||
|
|
b34a8c3b75 | ||
|
|
7e63dd23f3 | ||
|
|
c38884ceac | ||
|
|
ef57f94df0 | ||
|
|
5afe1e37c7 | ||
|
|
681de88df4 | ||
|
|
60c448b584 | ||
|
|
879e18b7ec | ||
|
|
82bac1b729 | ||
|
|
a6edfceaa8 | ||
|
|
18ca831830 | ||
|
|
100c3315be | ||
|
|
0a98b21f65 | ||
|
|
233d24a737 | ||
|
|
d786fb6e94 | ||
|
|
6a7a32a806 | ||
|
|
c951c3ee5a | ||
|
|
edca2b14d2 | ||
|
|
7e3ccddc6d | ||
|
|
68db7b1f56 | ||
|
|
cf85b5ba81 | ||
|
|
3426866e0a | ||
|
|
951eefae3c | ||
|
|
b57a4d07a2 | ||
|
|
ded57795ff | ||
|
|
de5465baf7 | ||
|
|
da0132638f | ||
|
|
dec58464d8 | ||
|
|
af62d9a687 | ||
|
|
354119e4c5 | ||
|
|
498ad7482b | ||
|
|
25791550e0 | ||
|
|
4bb6844e43 | ||
|
|
7e9f24d694 | ||
|
|
a8d3e930bc | ||
|
|
f510cfb4ef | ||
|
|
7a2450a7e5 | ||
|
|
23f08510d5 | ||
|
|
8db020e2a1 | ||
|
|
6df1251baa | ||
|
|
67c4fbed82 | ||
|
|
9b4efe6cf4 | ||
|
|
c49378fe3e | ||
|
|
7bb25f8a46 | ||
|
|
d99a7246f8 | ||
|
|
c82e0b701f | ||
|
|
87815ab5ba | ||
|
|
53fdfda9e3 | ||
|
|
f17f8521f0 | ||
|
|
1a5e5b6bd4 | ||
|
|
05676c5d16 | ||
|
|
5b3930655f | ||
|
|
9756f61fda | ||
|
|
009cabf103 | ||
|
|
426553164d | ||
|
|
0042fb0eb0 | ||
|
|
bbc1cd0bd9 | ||
|
|
78a012d81a | ||
|
|
a75a574141 | ||
|
|
c90af7cd14 | ||
|
|
f231edc672 | ||
|
|
ddd9fd417e | ||
|
|
75b92fe7c4 | ||
|
|
d76c663dfe | ||
|
|
0f8df3df41 | ||
|
|
351ad518b3 | ||
|
|
628c5641c0 | ||
|
|
0d35b46761 | ||
|
|
ad87ec4fb7 | ||
|
|
49c817c250 | ||
|
|
c22fc1ac6f | ||
|
|
0d9bcb1c12 | ||
|
|
7eb47ae290 | ||
|
|
0ce352c19f | ||
|
|
0adf4482f0 | ||
|
|
b9bb7e2d4a | ||
|
|
d74e422b5e | ||
|
|
119813e968 | ||
|
|
d1cb6a0fc4 | ||
|
|
89af463124 | ||
|
|
10088d5087 | ||
|
|
54f1c682e6 | ||
|
|
15336ffe86 | ||
|
|
73a809b159 | ||
|
|
8d4ff87b26 | ||
|
|
509c888dfa | ||
|
|
420584a46d | ||
|
|
6b9b958e00 | ||
|
|
b3c042cdb2 | ||
|
|
27839e5ecb | ||
|
|
59cf0824d9 | ||
|
|
c7822bf382 | ||
|
|
9e7e0cd613 | ||
|
|
ce8a68b1fd | ||
|
|
6382936ebc | ||
|
|
3bf72d0d5f | ||
|
|
a94aa39c86 | ||
|
|
0dcc754f68 | ||
|
|
79d2f70f5c | ||
|
|
e01195e90d | ||
|
|
067e365042 | ||
|
|
fda2b30346 | ||
|
|
924dc9731f | ||
|
|
51e01c0450 | ||
|
|
7fb90a670e | ||
|
|
57e931debc | ||
|
|
2a232dfa9a | ||
|
|
19853db892 | ||
|
|
b2604dc58c | ||
|
|
f8934657b2 | ||
|
|
2de5bab368 | ||
|
|
895409ca78 | ||
|
|
93cd75bbe0 | ||
|
|
ac33c049c6 | ||
|
|
20a0959964 | ||
|
|
cc24447a1a | ||
|
|
62f9d34c56 | ||
|
|
30c42f12b0 | ||
|
|
75ef41058d | ||
|
|
2fa9fb647f | ||
|
|
57852ff207 | ||
|
|
a99766fda1 | ||
|
|
f0f9d583ad | ||
|
|
1f665d6e82 | ||
|
|
e3c0125b70 | ||
|
|
a887af34e1 | ||
|
|
53ae687d59 | ||
|
|
a53c734094 | ||
|
|
aa16f87b87 | ||
|
|
6f20482a86 | ||
|
|
9d4d810861 | ||
|
|
1766d91697 | ||
|
|
e173f7a0e3 | ||
|
|
cc4df5a6b8 | ||
|
|
ed4980243a | ||
|
|
b809676a9e | ||
|
|
de88bc63d5 | ||
|
|
f35abe2ff5 | ||
|
|
83beaa886c | ||
|
|
753b707d2a | ||
|
|
551bee86b4 | ||
|
|
8612e6de88 | ||
|
|
d7a07a2012 | ||
|
|
0afb7a48c0 | ||
|
|
54157bc49e | ||
|
|
612bc784fc | ||
|
|
683f41adf2 | ||
|
|
885b6fc577 | ||
|
|
a92ae77740 | ||
|
|
7fd0149d34 | ||
|
|
9bf6921f3d | ||
|
|
183458f76a | ||
|
|
1f0c0df4a8 | ||
|
|
95b1979c32 | ||
|
|
97f1fc65cf | ||
|
|
42231117a7 | ||
|
|
e7887e6060 | ||
|
|
f86798ba6b | ||
|
|
a3e7a665ad | ||
|
|
a953c88381 | ||
|
|
f9bca13a1a | ||
|
|
9be3ee8283 | ||
|
|
75c57d53ea | ||
|
|
a6fe4ca321 | ||
|
|
e14829de30 | ||
|
|
f29b12d0bf | ||
|
|
46319094db | ||
|
|
e21e0ba3dc | ||
|
|
e3bae84606 | ||
|
|
54c46bafa3 | ||
|
|
be8f4d49d4 | ||
|
|
5c7286efbd | ||
|
|
5cd83ef539 | ||
|
|
d830dcb12d | ||
|
|
1506c64c8b | ||
|
|
28f32c99d0 | ||
|
|
ef897a41e8 | ||
|
|
af7647b20d | ||
|
|
1206bdf13f | ||
|
|
72980c7005 | ||
|
|
fb2f637c4a | ||
|
|
7970d53997 | ||
|
|
fceef8e0dd | ||
|
|
fe596d67e3 | ||
|
|
22d17bc14f | ||
|
|
f62dfa0f50 | ||
|
|
d06d8d6ffa | ||
|
|
41f12ac988 | ||
|
|
f16ad11c71 | ||
|
|
94bc467328 | ||
|
|
1c343ec29f | ||
|
|
5260ce1260 | ||
|
|
670d164fbe | ||
|
|
9162b5f8f9 | ||
|
|
36bbfeef7b | ||
|
|
f7d9827330 | ||
|
|
3f8865bcdc | ||
|
|
9f8dcfe69a | ||
|
|
3a482689c5 | ||
|
|
20c2a38ad0 | ||
|
|
2c17591224 | ||
|
|
bb2011d992 | ||
|
|
e06b3819ea | ||
|
|
e7affd266c | ||
|
|
f26f79ea37 | ||
|
|
56d26c2380 | ||
|
|
aa19fd612b | ||
|
|
96e0fe6232 | ||
|
|
38a06e86cf | ||
|
|
1d5b12c54a | ||
|
|
73fefd0bc0 | ||
|
|
3ac1dad77a | ||
|
|
76efe1225f | ||
|
|
8ef79e72ac | ||
|
|
58799718be | ||
|
|
605303d9c6 | ||
|
|
61eb0eae83 | ||
|
|
d5d92deb6c | ||
|
|
8204ab0098 | ||
|
|
64e6d7af47 | ||
|
|
4fd35a28d3 | ||
|
|
92dcae8fe4 | ||
|
|
1617c36c35 | ||
|
|
2ed450c322 | ||
|
|
9865ce0e64 | ||
|
|
6785b39eba | ||
|
|
9b3bc82c1a | ||
|
|
08c7363647 | ||
|
|
c7f93143f6 | ||
|
|
6a13193d8f | ||
|
|
d34c2fa3bb | ||
|
|
32d0a79f78 | ||
|
|
a2c2ef7de5 | ||
|
|
a2403227c7 | ||
|
|
113be48221 | ||
|
|
e8ebd7d0e0 | ||
|
|
1e221d101c | ||
|
|
14fb0ac649 | ||
|
|
134fd9eaf4 | ||
|
|
3758f27bc1 | ||
|
|
3307db49bd | ||
|
|
072982d83c | ||
|
|
dc8584696a | ||
|
|
8a588ac14d | ||
|
|
b187c591e3 | ||
|
|
efbe173962 | ||
|
|
3e0aef1e2e | ||
|
|
7a0c5fdf12 | ||
|
|
88c2307535 | ||
|
|
3495caf4fb | ||
|
|
69fa133ec2 | ||
|
|
6c16ecaaaa | ||
|
|
d0566daeaf | ||
|
|
aa5dfb3c2c | ||
|
|
a700bc74a8 | ||
|
|
94e3bb568a | ||
|
|
b239a32aae | ||
|
|
d98fa718e0 | ||
|
|
39603a7e31 | ||
|
|
ce37488919 | ||
|
|
17b05afda9 | ||
|
|
762b1ae2be | ||
|
|
c68c78719f | ||
|
|
33f943b4cd | ||
|
|
56161ca0a4 | ||
|
|
7f772355f4 | ||
|
|
ce2b2f22d9 | ||
|
|
ce7c47265b | ||
|
|
4ddc77041f | ||
|
|
01e3f9ba1d | ||
|
|
ea0e2524aa | ||
|
|
a9e62ab9fa | ||
|
|
04d901aed5 | ||
|
|
194b78050e | ||
|
|
a7da78e472 | ||
|
|
4c04744734 | ||
|
|
ba52cd06c8 | ||
|
|
67133d2f48 | ||
|
|
8c6e92c3bc | ||
|
|
7b87cda70e | ||
|
|
796f4ae9f7 | ||
|
|
8b9d5ecbd1 | ||
|
|
0b7aa26164 | ||
|
|
60e5d1724c | ||
|
|
6baac558d8 | ||
|
|
27ca133d35 | ||
|
|
cdf2271c5b | ||
|
|
bae30479fb | ||
|
|
832a298005 | ||
|
|
7c4decb101 | ||
|
|
3e0e51dd9f | ||
|
|
2692339138 | ||
|
|
005f564eb3 | ||
|
|
b252fab06c | ||
|
|
433e835d3e | ||
|
|
658ad301cc | ||
|
|
63d1f9e570 | ||
|
|
715a6d3a78 | ||
|
|
939c586ef7 | ||
|
|
5c78f87666 | ||
|
|
2a48bd4a37 | ||
|
|
71d0e7e0ea | ||
|
|
74435aba47 | ||
|
|
72ff03346f | ||
|
|
8a170c817d | ||
|
|
3625b52791 | ||
|
|
2102778606 | ||
|
|
931beae9b0 | ||
|
|
0b0f872f3f | ||
|
|
76c31d425e | ||
|
|
088b9eadc4 | ||
|
|
4f686555ce | ||
|
|
1e4ba6e7e6 | ||
|
|
76fda9f8e1 | ||
|
|
48c49c4851 | ||
|
|
3318b74705 | ||
|
|
c49955fd77 | ||
|
|
0ef4f44c5a | ||
|
|
8226be61ec | ||
|
|
40abeb1f40 | ||
|
|
1c130c6b03 | ||
|
|
3bc119c155 | ||
|
|
fcd2065f8e | ||
|
|
5344357f80 | ||
|
|
8b7e23bdde | ||
|
|
bc8ea04a7d | ||
|
|
e8357d0f85 | ||
|
|
1db45ba58c | ||
|
|
11d9ecb71d | ||
|
|
7b5ce1ffb1 | ||
|
|
4a3db25504 | ||
|
|
e8631a6991 | ||
|
|
9e422f74a6 | ||
|
|
2c55622333 | ||
|
|
2555b2f4bd | ||
|
|
e1b129811a | ||
|
|
0bcc7cb4bf | ||
|
|
f9a4a80f49 | ||
|
|
2d76736a2e | ||
|
|
53575c4708 | ||
|
|
93e7a130fc | ||
|
|
e29d330724 | ||
|
|
3a3458ee7b | ||
|
|
cac4bae11a | ||
|
|
c3f85c0107 | ||
|
|
7d9b45b4a1 | ||
|
|
e3cf812c9e | ||
|
|
db06108bc1 | ||
|
|
897adc1c8c | ||
|
|
e6b6813656 | ||
|
|
32c917964e | ||
|
|
12e435d2a4 | ||
|
|
7dd4432798 | ||
|
|
e0918a332d | ||
|
|
cb4e2ee9e7 | ||
|
|
a69178fdd2 | ||
|
|
20c040d364 | ||
|
|
9eeb03e5f7 | ||
|
|
fdaab02347 | ||
|
|
9caa1ae96a | ||
|
|
ab3afe2f4d | ||
|
|
85a2f2354e | ||
|
|
5eb7ac0c92 | ||
|
|
192a213274 | ||
|
|
0feb2aabca | ||
|
|
fcd2155b6f | ||
|
|
78954ca3a2 | ||
|
|
8827dc4eec | ||
|
|
95098b7230 | ||
|
|
c0ccd4da7d | ||
|
|
5c48432340 | ||
|
|
bcab07f7fc | ||
|
|
8b5e0b213e | ||
|
|
cffeafe391 | ||
|
|
8e8f8189eb | ||
|
|
0bbdaca6a0 | ||
|
|
402110f9fd | ||
|
|
d541f65d66 | ||
|
|
dde8dff73f | ||
|
|
54f0156e8c | ||
|
|
848d0461f9 | ||
|
|
81ceef7d22 | ||
|
|
9df77fc0c4 | ||
|
|
3bc35f9ce9 | ||
|
|
ba0afb4566 | ||
|
|
e9cd51c7eb | ||
|
|
681bf7468b | ||
|
|
db40da0a47 | ||
|
|
4e0a99ba51 | ||
|
|
5532b5308a | ||
|
|
837ca39081 | ||
|
|
142d133c8a | ||
|
|
1ee18db922 | ||
|
|
82c31f750d | ||
|
|
3b20003503 | ||
|
|
b7571b7453 | ||
|
|
f9dc3cb04d | ||
|
|
261da523e8 | ||
|
|
e8e717e145 | ||
|
|
2067644a8c | ||
|
|
80140fa006 | ||
|
|
3c3c16561a | ||
|
|
a9e057e095 | ||
|
|
0b7d08efb9 | ||
|
|
d9314079ca | ||
|
|
f5c03044a6 | ||
|
|
b5659cb0a6 | ||
|
|
1ccb661569 | ||
|
|
a79386b1af | ||
|
|
1fc04a6221 | ||
|
|
c756936b1d | ||
|
|
8ea5b46d30 | ||
|
|
1efc1e052d | ||
|
|
d55474e558 | ||
|
|
88530804e8 | ||
|
|
83d2830372 | ||
|
|
9c037ba85f | ||
|
|
da2fbcf93d | ||
|
|
67bb2c46b2 | ||
|
|
4930d903fc | ||
|
|
3a6a1e42f1 | ||
|
|
d5be696db8 | ||
|
|
1c12bcbcfb | ||
|
|
433ce7f85c | ||
|
|
352208ff68 | ||
|
|
d2ca4bb62d | ||
|
|
349c978189 | ||
|
|
b612affba3 | ||
|
|
ce637b2fef | ||
|
|
aaaed66c56 | ||
|
|
9682295f68 | ||
|
|
be9860d0e9 | ||
|
|
cf34507b87 | ||
|
|
096b5e921e | ||
|
|
cef505d739 | ||
|
|
bf066c875f | ||
|
|
f0868e080d | ||
|
|
4ab162a559 | ||
|
|
5afcca34ce | ||
|
|
ba4e20835a | ||
|
|
e887c62e45 | ||
|
|
f1b7608128 | ||
|
|
0c9f06a237 | ||
|
|
ec1ee66256 | ||
|
|
f2277475b2 | ||
|
|
4ab45183fc | ||
|
|
8aeb0c196a | ||
|
|
69a93194fd | ||
|
|
a8fb1a328d | ||
|
|
60e60f4ee0 | ||
|
|
7f9d289e10 | ||
|
|
307a189900 | ||
|
|
543ede03f2 | ||
|
|
aaf6db459b | ||
|
|
4628ac0065 | ||
|
|
5317a582cf | ||
|
|
3ed947fb30 | ||
|
|
28dce36f76 | ||
|
|
ac8fbe8829 | ||
|
|
fdf81b2d35 | ||
|
|
f29123364b | ||
|
|
f90a4af3d7 | ||
|
|
ded86076b3 | ||
|
|
156929ee97 | ||
|
|
5660ff1cc1 | ||
|
|
e3234116ad | ||
|
|
e73839e7d5 | ||
|
|
f22397dd7f | ||
|
|
1c88c5941b | ||
|
|
552eb5407a | ||
|
|
0609380603 | ||
|
|
8f52173b2c | ||
|
|
7fb14b73ba | ||
|
|
6dd2dbbd36 | ||
|
|
3cf6f1f54f | ||
|
|
80d4c52cdf | ||
|
|
eb746ec408 | ||
|
|
0064b1a583 | ||
|
|
6c527a89a0 | ||
|
|
c1041c570f | ||
|
|
6540e9eeaa | ||
|
|
9e0b9f12b2 | ||
|
|
6e33c908dd | ||
|
|
72c5ebcba4 | ||
|
|
35667d75c3 | ||
|
|
fc0fb0bad3 | ||
|
|
78782ed59f | ||
|
|
7f1c3607ec | ||
|
|
4290455145 | ||
|
|
b47b343fab | ||
|
|
906c4d77c0 | ||
|
|
8e6625e6ae | ||
|
|
a006fcb8a7 | ||
|
|
386a46946b | ||
|
|
3b54ee6ea4 | ||
|
|
896e15f3e3 | ||
|
|
68a3af5f9c | ||
|
|
e942f3ce0f | ||
|
|
df4bdbf688 | ||
|
|
90b844212d | ||
|
|
4a25983f8f | ||
|
|
491532a713 | ||
|
|
c138631cd1 | ||
|
|
93253c2762 | ||
|
|
30639c8ca9 | ||
|
|
d3efe04c47 | ||
|
|
a9caca0424 | ||
|
|
6972f18cbd | ||
|
|
4d2e8fa189 | ||
|
|
5fa3248bad | ||
|
|
4fdd7a0546 | ||
|
|
a98369929e | ||
|
|
f4da796c53 | ||
|
|
c641b670c3 | ||
|
|
82b53c650c | ||
|
|
5317f5e99a | ||
|
|
22c9cfaf57 | ||
|
|
6d5790d682 | ||
|
|
ad6b0f70b2 | ||
|
|
1faf82663f | ||
|
|
d77e231682 | ||
|
|
fe30847016 | ||
|
|
e150ec7d0c | ||
|
|
67ed5f00aa | ||
|
|
b9e90a70fa | ||
|
|
258d377d8e | ||
|
|
58f104e2ee | ||
|
|
2586ea7637 | ||
|
|
218cc5fc39 | ||
|
|
bb6d850160 | ||
|
|
ae0b7680b3 | ||
|
|
3a2ff55617 | ||
|
|
2209d077d3 | ||
|
|
c65b75e740 | ||
|
|
584052ee6b | ||
|
|
ed813863f0 | ||
|
|
365094028b | ||
|
|
9dfe426fc8 | ||
|
|
133fccb03f | ||
|
|
429aff4ef4 | ||
|
|
cc11d103c0 | ||
|
|
df32eb6827 | ||
|
|
2e5c394fa8 | ||
|
|
e26f4486b0 | ||
|
|
a2ff90919f | ||
|
|
1ed0ad77f0 | ||
|
|
8287945ff8 | ||
|
|
2e8e0ad44e | ||
|
|
8eae54aa1e | ||
|
|
2705306ba1 | ||
|
|
5b864617bc | ||
|
|
c4ef1d6494 | ||
|
|
3e1d772677 | ||
|
|
04cf02e8dc | ||
|
|
21765c8588 | ||
|
|
03344196f3 | ||
|
|
8caecc9ab4 | ||
|
|
ef5e7fbaa2 | ||
|
|
c0e818ab08 | ||
|
|
caf7b9b425 | ||
|
|
787b297046 | ||
|
|
55b88e0044 | ||
|
|
aca6cb529b | ||
|
|
c1a7d3e61f | ||
|
|
442b06db54 | ||
|
|
ed8c55237b | ||
|
|
3e8a92ef8d | ||
|
|
3b0edb80ec | ||
|
|
0f1f25784f | ||
|
|
f0782a6e86 | ||
|
|
0f2597092f | ||
|
|
6984692060 | ||
|
|
36676c2318 | ||
|
|
bbf39b7a3a | ||
|
|
b74c899da3 | ||
|
|
e8bae92e54 | ||
|
|
385c25f70f | ||
|
|
d2040ab9b1 | ||
|
|
59cf0b1902 | ||
|
|
54e99fb226 | ||
|
|
9b401819c0 | ||
|
|
5f5118bdad | ||
|
|
e5b1e7be64 | ||
|
|
8d011492a0 | ||
|
|
47750998a6 | ||
|
|
08da025f56 | ||
|
|
efb092baa6 | ||
|
|
7d97a4c99b | ||
|
|
3072a1e781 | ||
|
|
dd8d883c9c | ||
|
|
138695d990 | ||
|
|
c0f8665414 | ||
|
|
4b9ac6edd0 | ||
|
|
c5edd91716 | ||
|
|
35e8bc305d | ||
|
|
1734f7aedb | ||
|
|
db9e718c8a | ||
|
|
69cc3b692f | ||
|
|
e41a3edfab | ||
|
|
12a88d575d | ||
|
|
0c1bb1313c | ||
|
|
68d6d1597f | ||
|
|
8dea2029a4 | ||
|
|
f1c9a203b2 | ||
|
|
a89c8bb87c | ||
|
|
806788d009 | ||
|
|
8b8c1cd4c2 | ||
|
|
b21f4d63de | ||
|
|
76a6e82d89 | ||
|
|
0e31eaa27f | ||
|
|
38c620b3b5 | ||
|
|
addd4de9c1 | ||
|
|
8d95a47408 | ||
|
|
2a7847a3b5 | ||
|
|
746ce35f5f | ||
|
|
5edc7f76ed | ||
|
|
d769cad4bf | ||
|
|
55c059e270 | ||
|
|
fe9a489b29 | ||
|
|
55e36e1132 | ||
|
|
339322fe58 | ||
|
|
013a4ce242 | ||
|
|
fef88681f2 | ||
|
|
a2c76ffb1c | ||
|
|
ba993cc685 | ||
|
|
5580350a7b | ||
|
|
c1d67ea9b4 | ||
|
|
dcac007b81 | ||
|
|
386467acfb | ||
|
|
a9360c62e8 | ||
|
|
044b627549 | ||
|
|
630fb3539f | ||
|
|
d3a3ef0bc1 | ||
|
|
b15411df98 | ||
|
|
29f4df8018 | ||
|
|
6b373edb77 | ||
|
|
3b7a757468 | ||
|
|
25cebf7285 | ||
|
|
540536c055 | ||
|
|
09dec3613e | ||
|
|
afd3db17a0 | ||
|
|
a929867cca | ||
|
|
e163332858 | ||
|
|
b1c1386374 | ||
|
|
8c254415f7 | ||
|
|
1d7d1d9b1f | ||
|
|
2c273e3591 | ||
|
|
d7da507683 | ||
|
|
5765444454 | ||
|
|
7d7b3810e7 | ||
|
|
ce68caea37 | ||
|
|
b20ae393f1 | ||
|
|
e087d8a20d | ||
|
|
a88a057a1f | ||
|
|
2a08c7e5ac | ||
|
|
136537dcb0 | ||
|
|
8e553ec67c | ||
|
|
c7b7439528 | ||
|
|
c4fbc1d336 | ||
|
|
19b863c7fa | ||
|
|
141bac4c79 | ||
|
|
7cec9e48ce | ||
|
|
18b8bad53c | ||
|
|
af788b5852 | ||
|
|
34c404e069 | ||
|
|
a1dca2efab | ||
|
|
ede33fb912 | ||
|
|
e1b77b61f3 | ||
|
|
51bd9da1fd | ||
|
|
7e8abcf693 | ||
|
|
f2bdf601be | ||
|
|
21e6b0fbe9 | ||
|
|
fc08c8599f | ||
|
|
fe8352540f | ||
|
|
db6d628c3e | ||
|
|
5e0e03be41 | ||
|
|
8251d2d150 | ||
|
|
4a64286c36 | ||
|
|
72f7abf6a2 | ||
|
|
44f7afe28a | ||
|
|
a1349728ea | ||
|
|
6dc26795aa | ||
|
|
218358bbbd | ||
|
|
3107e7ca60 | ||
|
|
1ae50a9b1d | ||
|
|
59e03a8f35 | ||
|
|
bf670e0745 | ||
|
|
a3755ae9ee | ||
|
|
6fc53e16ad | ||
|
|
be0bb19fd3 | ||
|
|
1a73c992a3 | ||
|
|
32563b4a5f | ||
|
|
21648473aa | ||
|
|
5b2082ead3 | ||
|
|
f106a79309 | ||
|
|
f885fa8bac | ||
|
|
344a065d51 | ||
|
|
7bf7b7350c | ||
|
|
dced9c0619 | ||
|
|
007e1c7cb2 | ||
|
|
100cd7b21e |
+11
-786
@@ -1,791 +1,16 @@
|
||||
Redis 3.0 release notes
|
||||
=======================
|
||||
Hello! This file is just a placeholder, since this is the "unstable" branch
|
||||
of Redis, the place where all the development happens.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
Upgrade urgency levels:
|
||||
There is no release notes for this branch, it gets forked into another branch
|
||||
every time there is a partial feature freeze in order to eventually create
|
||||
a new stable release.
|
||||
|
||||
LOW: No need to upgrade unless there are new features you want to use.
|
||||
MODERATE: Program an upgrade of the server, but it's not urgent.
|
||||
HIGH: There is a critical bug that may affect a subset of users. Upgrade!
|
||||
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
--------------------------------------------------------------------------------
|
||||
Usually "unstable" is stable enough for you to use it in development environments
|
||||
however you should never use it in production environments. It is possible
|
||||
to download the latest stable release here:
|
||||
|
||||
--[ Redis 3.0.7 ] Release date: 25 jan 2016
|
||||
http://download.redis.io/releases/redis-stable.tar.gz
|
||||
|
||||
Upgrade urgency MODERATE: this release fixes important Redis Cluster bugs.
|
||||
More information is available at http://redis.io
|
||||
|
||||
* [FIX] Many fixes to MIGRATE multiple keys implementation. The command
|
||||
could handle errors in a faulty way leading to crashes or other
|
||||
unexpected behaviors. MIGRATE command refactoring.
|
||||
(The analysis of the faulty conditions was conducted by
|
||||
Kevin McGehee. The fix was developed by Salvatore Sanfilippo)
|
||||
* [FIX] A Redis Cluster node crash was fixed because of wrong handling of
|
||||
node->slaveof pointers.
|
||||
(Reported by JackyWoo, fixed by Salvatore Sanfilippo)
|
||||
* [FIX] Fix redis-trib rebalance when nodes need to be left empty because
|
||||
the specified weight is zero.
|
||||
(Reported by Shahar Mor, fixed by Salvatore Sanfilippo)
|
||||
* [FIX] MIGRATE: Never send -ASK redirections for MIGRATE when there are
|
||||
open slots. Redis-trib and other cluster management utility must
|
||||
always be free to move keys between nodes about open slots, in order
|
||||
to reshard, fix the cluster configuration, and so forth.
|
||||
(Salvatore Sanfilippo)
|
||||
* [FIX] Lua debugger crash when printing too deeply nested objects.
|
||||
(Reported by Paul Kulchenko, fixed by Salvatore Sanfilippo)
|
||||
* [FIX] Redis-cli implementation of Lua debugging now allows to use the
|
||||
SCRIPT DEBUG command directly, switching to debugging mode as needed.
|
||||
(Reported by Paul Kulchenko, fixed by Salvatore Sanfilippo)
|
||||
* [FIX] Redis-trib is now able to fix more errors. A new CLUSTER subcommand
|
||||
called BUMPEPOCH was introduced in order to support new modes
|
||||
for the "fix" subcommand. (Salvatore Sanfilippo)
|
||||
* [NEW] Redis proctected mode: this feature improves Redis security and makes
|
||||
harder to run Redis in a configuration that is unsecure because no
|
||||
firewalling was used in order to protect Redis from external accesses.
|
||||
See this Reddit post for more info: https://www.reddit.com/r/redis/comments/3zv85m/new_security_feature_redis_protected_mode/ (Salvatore Sanfilippo)
|
||||
* [NEW] Cluster/Sentinel tests now use OSX leak to perform leak detection
|
||||
at the end of every unit. (Salvatore Sanfilippo)
|
||||
* [NEW] Detect and show server crashes during Cluster/Sentinel tests.
|
||||
(Salvatore Sanfilippo)
|
||||
* [NEW] More reliable Cluster/Sentinel test becuase of timing errors and
|
||||
-LOADING errors. (Salvatore Sanfilippo)
|
||||
|
||||
--[ Redis 3.0.6 ] Release date: 18 Dec 2015
|
||||
|
||||
Upgrade urgency: MODERATE. We fixed a crash that happens very rarely, so
|
||||
updating does not hurt, but most users are unlikely to
|
||||
experience this condition because it requires some odd
|
||||
timing. However if you are a Redis Cluster user, upgrading
|
||||
is strongly adviced since this release includes very
|
||||
important improvements to Redis Cluster.
|
||||
|
||||
* [FIX] lua_struct.c/getnum security issue fixed. (Luca Bruno discovered it,
|
||||
patched by Sun He and Chris Lamb)
|
||||
* [FIX] Redis Cluster replica migration fixed. See issue #2924 for details.
|
||||
(Salvatore Sanfilippo)
|
||||
* [FIX] Fix a race condition in processCommand() because of interactions
|
||||
with freeMemoryIfNeeded(). Details in issue #2948 and especially
|
||||
in the commit message d999f5a. (Race found analytically by
|
||||
Oran Agra, patch by Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] Backported from the upcoming Redis 3.2:
|
||||
MIGRATE now supports an extended multiple-keys pipelined mode, which
|
||||
is an order of magnitude faster. Redis Cluster now uses this mode
|
||||
in order to perform reshardings and rebalancings. (Salvatore Sanfilippo)
|
||||
* [NEW] Backported from the upcoming Redis 3.2:
|
||||
Redis Cluster has now support for rebalancing via the redis-trib
|
||||
rebalance command. Demo here:
|
||||
https://asciinema.org/a/0tw2e5740kouda0yhkqrm5790
|
||||
Official documentation will be available ASAP. (Salvatore Sanfilippo)
|
||||
* [NEW] Redis Cluster redis-trib.rb new "info" subcommand.
|
||||
* [NEW] Redis Cluster tests improved. (Salvatore Sanfilippo)
|
||||
* [NEW] Log offending memory access address on SIGSEGV/SIGBUS (Salvatore
|
||||
Sanfilippo)
|
||||
|
||||
--[ Redis 3.0.5 ] Release date: 15 Oct 2015
|
||||
|
||||
Upgrade urgency: MODERATE, the most important thing is a fix in the replication
|
||||
code that may make the slave hanging forever if the master
|
||||
remains with an open socket even if it is no longer able to
|
||||
reply.
|
||||
|
||||
* [FIX] MOVE now moves the TTL as well. A bug lasting forever... finally
|
||||
fixed thanks to Andy Grunwald that reported it.
|
||||
(reported by Andy Grunwald, fixed by Salvatore Sanfilippo)
|
||||
* [FIX] Fix a false positive in HSTRLEN test.
|
||||
* [FIX] Fix a bug in redis-cli --pipe mode that was not able to read back
|
||||
replies from the server incrementally. Now a mass import will use
|
||||
a lot less memory, and you can use --pipe to do incremental streaming.
|
||||
(reported by Twitter user @fsaintjacques, fixed by Salvatore
|
||||
Sanfilippo)
|
||||
* [FIX] Slave detection of master timeout. (fixed by Kevin McGehee, refactoring
|
||||
and regression test by Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] Cluster: redis-trib fix can fix an additional case for opens lots.
|
||||
(Salvatore Sanfilippo)
|
||||
* [NEW] Cluster: redis-trib import support for --copy and --replace options
|
||||
(David Thomson)
|
||||
|
||||
--[ Redis 3.0.4 ] Release date: 8 Sep 2015
|
||||
|
||||
Upgrade urgency: HIGH for Redis and Sentinel. However note that in order to
|
||||
fix certain replication bugs, the replication internals were
|
||||
modified in a very heavy way. So while this release is
|
||||
conceptually saner, it may contain regressions. For this
|
||||
reason, before the release, QA activities were performed by
|
||||
me (antirez) and Redis Labs and no evident bug was found.
|
||||
|
||||
* [FIX] A number of bugs related to replication PSYNC and the (yet experimental)
|
||||
diskless replication feature were fixed. The bugs could lead to
|
||||
inconsistency between masters and slaves. (Salvatore Sanfilippo, Oran
|
||||
Agra fixed the issue found by Yuval Inbar)
|
||||
* [FIX] A replication bug in the context of PSYNC partial resynchonization was
|
||||
found and fixed. This bug happens even when diskless replication is off
|
||||
in the case different slaves connect at different times while the master
|
||||
is creating an RDB file, and later a partial resynchronization is
|
||||
attempted by a slave that connected not as the first one. (Salvatore
|
||||
Sanfilippo, Oran Agra)
|
||||
* [FIX] Chained replication and PSYNC interactions leading to potential stale
|
||||
chained slaves data set, see issue #2694. (Salvatore Sanfilippo fixed
|
||||
an issue reported by "GeorgeBJ" user at Github)
|
||||
* [FIX] redis-cli --scan iteration fixed when returned cursor overflows
|
||||
32 bit signed integer. (Ofir Luzon, Yuval Inbar)
|
||||
* [FIX] Sentinel: fixed a bug during the master switch process, where for a
|
||||
failed conditional check, the new configuration is rewritten, during
|
||||
a small window of time, in a corrupted way where the master is
|
||||
also reported to be one of the slaves. This bug is rare to trigger
|
||||
but apparently it happens in the wild, and the effect is to see
|
||||
a replication loop where the master will try to replicate with itself.
|
||||
A detailed explanation of the bug and its effects can be found in
|
||||
the commit message here: https://github.com/antirez/redis/commit/c20218eb5770b2cafb12bc7092313b8358fedc0a.
|
||||
The bug was found by Jan-Erik Rediger using a static analyzer and
|
||||
fixed by Salvatore Sanfilippo.
|
||||
* [FIX] Sentinel lack of arity checks for certain commands.
|
||||
(Rogerio Goncalves, Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] Replication internals rewritten in order to be more resistant to bugs.
|
||||
The replication handshake in the slave side was rewritten as a non
|
||||
blocking state machine. (Salvatore Sanfilippo, Oran Agra)
|
||||
* [NEW] New "replication capabilities" feature introduced in order to signal
|
||||
from the master to the slave what are the features supported, so that
|
||||
the master can choose the kind of replication to start (diskless or
|
||||
not) when master and slave are of different versions. (Oran Agra,
|
||||
Salvatore Sanfilippo)
|
||||
* [NEW] Log clients details when SLAVEOF command is received. (Salvatore
|
||||
Sanfilippo with inputs from Nick Craver and Marc Gravell).
|
||||
|
||||
--[ Redis 3.0.3 ] Release date: 17 Jul 2015
|
||||
|
||||
Upgrade urgency: LOW for Redis and Sentinel.
|
||||
|
||||
* [FIX] Fix blocking operations timeout precision when HZ is at its default
|
||||
value (not increased) and there are thousands of clients connected
|
||||
at the same time. This bug affected Sidekiq users that experienced
|
||||
a very long delay for BLPOP and similar commands to return for
|
||||
timeout. Check commit b029ff1 for more info. (Salvatore Sanfilippo)
|
||||
* [FIX] MIGRATE "creating socket: Invalid argument" error fix. Check
|
||||
issues #2609 and #2612 for more info. (Salvatore Sanfilippo)
|
||||
* [FIX] Be able to connect to the master even when the slave is bound to
|
||||
just the loopback interface and has no valid public address in the
|
||||
network the master is reacahble. (Salvatore Sanfilippo)
|
||||
* [FIX] ZADD with options encoding promotion fixed. (linfangrong)
|
||||
* [FIX] Reset aof_delayed_fsync on CONFIG RESETSTATS. (Tom Kiemes)
|
||||
* [FIX] PFCOUNT key parsing in cluster fixed. (MOON_CLJ)
|
||||
* [FIX] Fix Solaris compilation of Redis 3.0. (Jan-Erik Rediger)
|
||||
|
||||
* [NEW] Variadic EXISTS command. Now the command accepts multiple arguments
|
||||
and returns the total count of existing keys.
|
||||
|
||||
--[ Redis 3.0.2 ] Release date: 4 Jun 2015
|
||||
|
||||
Upgrade urgency: HIGH for Redis because of a security issue.
|
||||
LOW for Sentinel.
|
||||
|
||||
* [FIX] Critical security issue fix by Ben Murphy: http://t.co/LpGTyZmfS7
|
||||
* [FIX] SMOVE reply fixed when src and dst keys are the same. (Glenn Nethercutt)
|
||||
* [FIX] Lua cmsgpack lib updated to support str8 type. (Sebastian Waisbrot)
|
||||
|
||||
* [NEW] ZADD support for options: NX, XX, CH. See new doc at redis.io.
|
||||
(Salvatore Sanfilippo)
|
||||
* [NEW] Senitnel: CKQUORUM and FLUSHCONFIG commands back ported.
|
||||
(Salvatore Sanfilippo and Bill Anderson)
|
||||
|
||||
--[ Redis 3.0.1 ] Release date: 5 May 2015
|
||||
|
||||
Upgrade urgency: LOW for Redis and Cluster, MODERATE for Sentinel.
|
||||
|
||||
* [FIX] Sentinel memory leak due to hiredis fixed. (Salvatore Sanfilippo)
|
||||
* [FIX] Sentinel memory leak on duplicated instance. (Charsyam)
|
||||
* [FIX] Redis crash on Lua reaching output buffer limits. (Yossi Gottlieb)
|
||||
* [FIX] Sentinel flushes config on +slave events. (Bill Anderson)
|
||||
|
||||
--[ Redis 3.0.0 ] Release date: 1 Apr 2015
|
||||
|
||||
>> What's new in Redis 3.0 compared to Redis 2.8?
|
||||
|
||||
* Redis Cluster: a distributed implementation of a subset of Redis.
|
||||
* New "embedded string" object encoding resulting in less cache
|
||||
misses. Big speed gain under certain work loads.
|
||||
* AOF child -> parent final data transmission to minimize latency due
|
||||
to "last write" during AOF rewrites.
|
||||
* Much improved LRU approximation algorithm for keys eviction.
|
||||
* WAIT command to block waiting for a write to be transmitted to
|
||||
the specified number of slaves.
|
||||
* MIGRATE connection caching. Much faster keys migraitons.
|
||||
* MIGRATE new options COPY and REPLACE.
|
||||
* CLIENT PAUSE command: stop processing client requests for a
|
||||
specified amount of time.
|
||||
* BITCOUNT performance improvements.
|
||||
* CONFIG SET accepts memory values in different units (for example
|
||||
you can use "CONFIG SET maxmemory 1gb").
|
||||
* Redis log format slightly changed reporting in each line the role of the
|
||||
instance (master/slave) or if it's a saving child log.
|
||||
* INCR performance improvements.
|
||||
|
||||
>> Refactoring changes (no new features nor bug fixes)
|
||||
|
||||
* Blocking operations full refactoring (blocked.c)
|
||||
* Client output buffer memory tracking refactored.
|
||||
|
||||
Changes between RC6 and 3.0.0 stable:
|
||||
|
||||
>> General changes
|
||||
|
||||
* Fixes to diskless replication. (Oran Agra)
|
||||
* Test for BLPOP replication on role change. (Salvatore Sanfilippo)
|
||||
* prepareClientToWrite() error handling improvements. (Salvatore Sanfilippo)
|
||||
* Remove dict.c no longer used function. (Salvatore Sanfilippo)
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
None
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
None
|
||||
|
||||
--[ Redis 3.0.0 RC6 (version 2.9.106) ] Release date: 24 mar 2015
|
||||
|
||||
Upgrade urgency: HIGH because of bugs related to Redis Custer and replication.
|
||||
|
||||
This is the 6th release candidate of Redis 3.0.0. This release fixes important
|
||||
issues discovered during stress testing, and implements safest behavior
|
||||
for blocking operations during clients reshardings, and a new much needed
|
||||
functionality of Redis Cluster manual failovers.
|
||||
|
||||
In order to fix certain bugs quite a bit of refactoring was needed which
|
||||
is usually non advisabble in a Release Candidate, but needed in order to
|
||||
end with a clean fix.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [FIX] Redis (non clustered & clustered) replication bug involving blocking
|
||||
operations: see issue #2473. (Salvatore Sanfilippo)
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [FIX] clientsArePaused() fix crashing the old master during manual failover.
|
||||
(Salvatore Sanfilippo)
|
||||
* [FIX] Lua scripts replication in Redis Cluster was totally broken.
|
||||
(Salvatore Sanfilippo)
|
||||
* [FIX] Redirect clients blocked into list operations when the hash slot
|
||||
they are blocked into is migrated to another instance or the cluster
|
||||
state turns into "fail". (Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] TAKEOVER option for CLUSTER FAILOVER implemented. It is now possible
|
||||
to fix a cluster manually in the minority side of the partition, for
|
||||
example in order to allow for multi DC setups & recovery.
|
||||
(Salvatore Sanfilippo)
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
No changes in Sentinel.
|
||||
|
||||
--[ Redis 3.0.0 RC5 (version 2.9.105) ] Release date: 20 mar 2015
|
||||
|
||||
Upgrade urgency: Moderate for Redis Cluster users, low otherwise.
|
||||
|
||||
This is the 5th release candidate of Redis 3.0.0, released in order to fix
|
||||
a moderate bug in Redis Cluster. This RC does not shift in the future the
|
||||
Redis 3.0.0 final release which is scheduled in a few days (we are in the
|
||||
process of finishing the documentation for Redis Cluster).
|
||||
|
||||
>> General changes
|
||||
|
||||
* [FIX] Fix LATENCY command crash. (Salvatore Sanfilippo, thx to Ingmar)
|
||||
* [FIX] Config: missing activerehashing option support in CONFIG SET added.
|
||||
(Salvatore Sanfilippo, thx to Bill Anderson)
|
||||
* [FIX] Fix for backtrace generation issue. (Mariano Pérez Rodríguez, Matt Stancliff, Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] Redis-cli --latency-dist backported from unstable.
|
||||
(Salvatore Sanfilippo)
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [FIX] Avoid redundant SELECT in MIGRATE. (Tommy Wang, Salvatore Sanfilippo)
|
||||
* [FIX] More robust slave check in CLUSTER REPLICATE. (Salvatore Sanfilippo)
|
||||
* [FIX] Fixed possible Redis Cluster node crash due to wrong separation of
|
||||
concerns between getNodeByQuery() and Cluster global state update
|
||||
fnuction. (Salvatore Sanfilippo, thx to Ingmar)
|
||||
|
||||
* [NEW] Add command CLUSTER MYID to easily featch instance ID. (Michel Martens)
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
* [NEW] Support for CLIENT command added. It was missing in the command table.
|
||||
(Leandro López)
|
||||
|
||||
--[ Redis 3.0.0 RC4 (version 2.9.104) ] Release date: 13 feb 2015
|
||||
|
||||
Upgrade urgency: High for Redis if you use LRU eviction, low otherwise.
|
||||
|
||||
This is the 4th release candidate of Redis 3.0.0, it fixes problems with
|
||||
LRU eviction that are not present in older release (2.8.x is not affected)
|
||||
and adds new tools to inspect latency and load-test LRU.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [FIX] redis-cli CSV output NIL spurious newline removed. (Matt Collier)
|
||||
* [FIX] Memory efficiency test in unit test is now much faster: it affacted
|
||||
the total "make test" execution time in a bad way. (Salvatore
|
||||
Sanfilippo)
|
||||
* [FIX] Fixes and improvements to dict.c and LRU eviction. Redis 3.0.0 new
|
||||
LRU eviction had bugs creating high latency spikes when LRU was
|
||||
happening during the keys dictionary rehashing. This bug is not
|
||||
present into 2.8, was 3.0 specific. As a side effect of this issue
|
||||
dict.c is now improved, and LRU algorithm is more precise (better
|
||||
approximates true LRU). This was a joint effort, see issue
|
||||
#2306 for details. (Oran Agra, Sun He, Salvatore Sanfilippo).
|
||||
Thanks to Charsyam for spotting an integer overflow.
|
||||
|
||||
* [NEW] New latency tool: redis-cli --latency-dist is able to show an
|
||||
xterm-256 based spectrum of latencies over time. (Salvatore Sanfilippo)
|
||||
* [NEW] redis-cli --lru-test implemented (cache workload simulator). (Salvatore
|
||||
Sanfilippo)
|
||||
* [NEW] redis-cli --stat now shows LOAD when Redis is loading data.
|
||||
* [NEW] Support "1G" etc. units in CONFIG SET. (Chris Lamb, Salvatore
|
||||
Sanfilippo)
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* None.
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
* None.
|
||||
|
||||
--[ Redis 3.0.0 RC3 (version 2.9.103) ] Release date: 30 jan 2015
|
||||
|
||||
Upgrade urgency: High for Redis Cluster users, low otherwise.
|
||||
|
||||
This is the third release candidate for Redis 3.0.0, the new RC fixes
|
||||
several critical issues with Redis Cluster.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [FIX] AOF bug unlikely to happen in practice and mostly harmless: child
|
||||
process segfaults when parent is not reachable via pipe. (Sun He)
|
||||
* [FIX] Scripting engine now reports an error when misused with Lua debug
|
||||
hooks, instead of crashing. (Salvatore Sanfilippo)
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [FIX] Several issues with Redis Cluster internal nodes objects handling.
|
||||
(Matt Stancliff & Salvatore Sanfilippo)
|
||||
* [FIX] Improvements in the Cluster test.
|
||||
(Matt Stancliff & Salvatore Sanfilippo).
|
||||
* [FIX] Cluster memory leaks / double frees (Matt Stancliff).
|
||||
* [FIX] /dev/urandom surrogate for generation of unique IDs in a more
|
||||
cheap way. (Salvatore Sanfilippo)
|
||||
* [FIX] Fixes and improvements to PING / PONG packets gossip sections
|
||||
in order to improve (and fix) failure detection and speedup
|
||||
cluster info propagation. (Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] CLUSTER count-failure-reports command added. (Salvatore Sanfilippo)
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
No changes for Sentinel.
|
||||
|
||||
--[ Redis 3.0.0 RC2 (version 2.9.102) ] Release date: 13 jan 2015
|
||||
|
||||
Upgrade urgency: LOW.
|
||||
|
||||
This is the second release candidate of Redis 3.0.0. The major changes
|
||||
are back porting of things implemented into the unstable branch while
|
||||
this was still possible (with the new development model adopted only
|
||||
bug fixes will be merged in the future).
|
||||
|
||||
RC2 also fixes a few Redis Cluster non critical bugs.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [FIX] A number of minor bug fixes.
|
||||
|
||||
* [NEW] Diskless replication backportede.
|
||||
* [NEW] Lua bitops and updated cmsgpack backported.
|
||||
* [NEW] Transparent Huge Pages warnings and reporting backported.
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [FIX] Fix PUBLISH cluster bus message count field.
|
||||
* [FIX] It is no longer possible to write outside node hash slots using Lua.
|
||||
* [FIX] Valgrind warnings (no actual bugs).
|
||||
* [FIX] Less strict in acceptiong myself->ip if it's not populated.
|
||||
|
||||
* [NEW] Better testing of Lua scripts.
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
No changes to Sentinel.
|
||||
|
||||
--[ Redis 3.0.0 RC1 (version 2.9.101) ] Release date: 9 oct 2014
|
||||
|
||||
This is the first release candidate of Redis 3.0.0.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [FIX] An very large number of small fixes, old and new, merged in the
|
||||
context of a the issue #1906. Please see the issue page here
|
||||
for exact credits: https://github.com/antirez/redis/pull/1906
|
||||
of each commit. (Matt Stancliff and many others).
|
||||
* [FIX] SAVE is no longer propagated to AOF / slaves.
|
||||
* [FIX] GETRANGE test no longer fails for 32 bit builds (Matt Stancliff).
|
||||
* [FIX] Limit SCAN latency when the hash table is in an odd state (very few
|
||||
populted buckets because rehashing is in progress). (Xiaost and
|
||||
Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] Redis is now able to load truncated AOF files without requiring a
|
||||
redis-check-aof utility run. The default now is to load truncated
|
||||
(but apparently not corrupted) AOFs, you can change this in redis.conf.
|
||||
(Salvatore Sanfilippo).
|
||||
* [NEW] DEBUG POPULATE two args form implemented. It is now possible to
|
||||
call it with DEBUG POPULATE <count> <prefix>. Default prefix
|
||||
is "key:" as usually.
|
||||
* [NEW] INCR: Modify incremented object in-place when possible. This results
|
||||
in speed improvements + possibly better memory locality.
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [FIX] Cluster: claim ping_sent time even if we can't connect.
|
||||
* [FIX] redis-trib should not abort easily on connection issues.
|
||||
* [FIX] Cluster test: less console-spammy resharding test.
|
||||
* [FIX] Fix logic to detect we are among a minority.
|
||||
* [FIX] Process gossip section only for known nodes.
|
||||
|
||||
* [NEW] Redis Cluster is stable and tested enough, there is a clear MVP,
|
||||
so it was promoted from beta to stable.
|
||||
* [NEW] New unit 09, Pub/Sub across the cluster.
|
||||
* [NEW] New unit 08, update messages.
|
||||
* [NEW] New cluster option to work with partial slots coverage.
|
||||
* [NEW] More chatty cluster slaves when failover is stalled. They log reason
|
||||
with rate limiting, only when reason changes or a given time
|
||||
has elapsed.
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
* [FIX] Sentinel critical bug fixed: the absolute majority was computed in a
|
||||
wrong way because of a programming error. Now the implementation does
|
||||
what the specification says and the majority to authorize a failover
|
||||
(that should not be confused with the ODOWN quorum) is the majority of
|
||||
*all* the Sentinels ever seen for a given master, regardless of their
|
||||
current state.
|
||||
* [FIX] Resolved a memory leak in the hiredis library causing a memory leak
|
||||
in Redis Sentinel when a monitored instance or another Sentinel is
|
||||
unavailable. Every reconnection attempt will leak a small amount of
|
||||
memory, but in the long run the process can reach a considerable size.
|
||||
|
||||
* [NEW] Sentinel: ability to announce itself with an arbitrary IP/port to work
|
||||
in the context of natted networks. However this is probably still
|
||||
not enough since there is no equivalent mechanism for slaves listed
|
||||
in the master INFO output. (Dara Kong and Salvatore Sanfilippo)
|
||||
|
||||
--[ Redis 3.0.0 Beta 8 (version 2.9.57) ] Release date: 29 jul 2014
|
||||
|
||||
This is the 8th beta of Redis 3.0.0.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [FIX] Solaris compilation issues. (Matt Stancliff, Salvatore Sanfilippo)
|
||||
* [FIX] Allow shared integer objects if maxmemory policy is not LRU based.
|
||||
(Salvatore Sanfilippo)
|
||||
* [FIX] PFSELFTEST: less false positives. (Salvatore Sanfilippo)
|
||||
* [FIX] Fail SYNC if background save child aborted due to a signal. (Yossi
|
||||
Gottlieb)
|
||||
|
||||
* [NEW] Latency framework backported from unstable branch. (Salvatore
|
||||
Sanfilippo)
|
||||
* [NEW] AOF rewrite improved for latency. (Salvatore Sanfilippo)
|
||||
* [NEW] Pub/Sub PING. (Salvatore Sanfilippo)
|
||||
* [NEW] Much faster ZUNIONSTORE. (Kyle Hubert, Salvatore Sanfilippo)
|
||||
* [NEW] Faster ll2string() implementation. (Salvatore Sanfilippo)
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [FIX] CLUSTER RESET: Flush slave dataset on reset. (Salvatore Sanfilippo)
|
||||
* [FIX] Replica migration: don't migrate to masters that never had slaves
|
||||
in the past, but only to masters that remained orphaned after
|
||||
failure events. (Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] redis-trib: allow to reshard in non-interactive way. (Salvatore
|
||||
Sanfilippo)
|
||||
* [NEW] Cluster test: unit 04, check consistency during resharding. (Salvatore
|
||||
Sanfilippo)
|
||||
* [NEW] Cluster test: unit 05, slave selection. (Salvatore Sanfilippo)
|
||||
* [NEW] Cluster test: unit 06, slaves with stale data can't failover. (Salvatore
|
||||
Sanfilippo)
|
||||
* [NEW] Cluster test: unit 07, replicas migration. (Salvatore Sanfilippo)
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
* No Sentinel changes in this release.
|
||||
|
||||
--[ Redis 3.0.0 Beta 7 (version 2.9.56) ] Release date: 30 jun 2014
|
||||
|
||||
This is the 7th beta of Redis 3.0.0.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [FIX] Scripting fixes backported from unstable, see Redis 2.8.12 changelog
|
||||
for more info. (Salvatore Sanfilippo)
|
||||
* [FIX] Cancel SHUTDOWN if initial AOF is being written. (Matt Stancliff)
|
||||
|
||||
* [NEW] New command: COMMAND, for commands introspection (Matt Stancliff &
|
||||
Salvatore Sanfilippo)
|
||||
* [NEW] hiredis: Update to latest version. (Matt Stancliff)
|
||||
* [NEW] Jemalloc updated to 3.6.0. (Salvatore Sanfilippo)
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [FIX] Cluster: clear NOADDR flag when updating node address.
|
||||
(Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] New CLUSTER SLOTS command to simplify Cluster clients operations.
|
||||
(Matt Stancliff)
|
||||
* [NEW] More Cluster tests. (Salvatore Sanfilippo)
|
||||
* [NEW] Log when failover authorization are granted / denied.
|
||||
(Salvatore Sanfilippo)
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
* [FIX] A few Sentinel bugs fixed and improvements, see Redis 2.8.12
|
||||
changelog for more info. (Salvatore Sanfilippo & Matt Stancliff)
|
||||
* [NEW] New Sentinel-Client handshake protocol, ROLE command, CLIENT KILL,
|
||||
all backported to 3.0 branch. (Salvatore Sanfilippo)
|
||||
|
||||
--[ Redis 3.0.0 Beta 6 (version 2.9.55) ] Release date: 9 jun 2014
|
||||
|
||||
This is the 6th beta of Redis 3.0.0.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [FIX] Fix software watchdog signal handler crash due to re-entering.
|
||||
* [FIX] Better Lua number -> string conversion for Lua scripts.
|
||||
* [FIX] Serious replication bug when min-slaves-* feature is used in slaves
|
||||
configuration fixed.
|
||||
* [FIX] Blocking pop on lists now works when the list is created by commands
|
||||
other than *PUSH* (for example SORT STORE).
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [FIX] CRITICAL: For a bug in the implementation of CLUSTER SET-CONFIG-EPOCH
|
||||
introduced with beta-3 (especially beta-4 where the command
|
||||
is actually used by redis-trib), a configEpoch for a node could
|
||||
jump backward, breaking the eventual consistency property of the
|
||||
slots -> nodes mapping in the cluster.
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
* No changes for Sentinel in this release.
|
||||
|
||||
--[ Redis 3.0.0 Beta 5 (version 2.9.54) ] Release date: 26 may 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.
|
||||
|
||||
--[ Redis 3.0.0 Beta 4 (version 2.9.53) ] Release date: 23 may 2014
|
||||
|
||||
This is the fourth beta of Redis 3.0.0.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [NEW] Scripting engine performances improvements.
|
||||
* [NEW] Log format slightly changed to report current node role.
|
||||
|
||||
* [FIX] Correct the HyperLogLog stale cache flag to prevent unnecessary
|
||||
computation.
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [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.
|
||||
|
||||
* [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.
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
* [NEW] Generate +config-update-from event when a new config is received.
|
||||
* [NEW] Log when a failover will be re-attempted again.
|
||||
|
||||
* [FIX] Sentinel: Add "dir /tmp" directive in example sentinel.conf.
|
||||
|
||||
--[ Redis 3.0.0 Beta 3 (version 2.9.52) ] Release date: 5 may 2014
|
||||
|
||||
This is the third beta of Redis 3.0.0.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [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.
|
||||
|
||||
* [FIX] Redis is now much more responsive to reply with LOADING / BUSY errors.
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [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.
|
||||
|
||||
* [FIX] Different fixes to redis-trib cluster creation.
|
||||
* [FIX] Fix an error in the CLUSTER NODES output for nodes slots.
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
* [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.
|
||||
|
||||
--[ Redis 3.0.0 Beta 2 (version 2.9.51) ] Release date: 11 mar 2014
|
||||
|
||||
This is the second beta of Redis 3.0.0.
|
||||
|
||||
>> General changes
|
||||
|
||||
* [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.
|
||||
|
||||
* [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.
|
||||
|
||||
|
||||
>> Cluster changes
|
||||
|
||||
* [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.
|
||||
|
||||
* [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.
|
||||
|
||||
|
||||
>> Sentinel changes
|
||||
|
||||
* [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.
|
||||
|
||||
* [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.
|
||||
|
||||
--[ Redis 3.0.0 Beta 1 (version 2.9.50) ] Release date: 11 Feb 2014
|
||||
|
||||
This is the first beta of Redis 3.0.0.
|
||||
|
||||
Migrating from 2.8 to 3.0
|
||||
=========================
|
||||
|
||||
Redis 2.8 is mostly a strict subset of 3.0, 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:
|
||||
|
||||
* 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 default maxmemory policy in Redis 3.0 is no longer "volatile-lru" as
|
||||
it used to be in 2.8, but "noeviction". The policies behavior is the same
|
||||
(but LRU eviction is much more precise in 3.0), so only the default value
|
||||
changed. Just make sure to specify in your redis.conf what you mean.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
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.
|
||||
|
||||
See commit messages for more credits.
|
||||
|
||||
Cheers,
|
||||
Salvatore
|
||||
Happy hacking!
|
||||
|
||||
+5
-9
@@ -12,17 +12,15 @@ each source file that you contribute.
|
||||
|
||||
PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected
|
||||
bugs in the Github issues system. We'll be very happy to help you and provide
|
||||
all the support Reddit sub:
|
||||
all the support in the Redis Google Group.
|
||||
|
||||
http://reddit.com/r/redis
|
||||
|
||||
There is also an active community of Redis users at Stack Overflow:
|
||||
|
||||
http://stackoverflow.com/questions/tagged/redis
|
||||
Redis Google Group address:
|
||||
|
||||
https://groups.google.com/forum/?fromgroups#!forum/redis-db
|
||||
|
||||
# How to provide a patch for a new feature
|
||||
|
||||
1. If it is a major feature or a semantical change, please post it as a new submission in r/redis on Reddit at http://reddit.com/r/redis. Try to be passionate about why the feature is needed, make users upvote your proposal to gain traction and so forth. Read feedbacks about the community. But in this first step **please don't write code yet**.
|
||||
1. Drop a message to the Redis Google Group with a proposal of semantics/API.
|
||||
|
||||
2. If in step 1 you get an acknowledge from the project leaders, use the
|
||||
following procedure to submit a patch:
|
||||
@@ -33,6 +31,4 @@ each source file that you contribute.
|
||||
d. Initiate a pull request on github ( http://help.github.com/send-pull-requests/ )
|
||||
e. Done :)
|
||||
|
||||
For minor fixes just open a pull request on Github.
|
||||
|
||||
Thanks!
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2006-2015, Salvatore Sanfilippo
|
||||
Copyright (c) 2006-2014, 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:
|
||||
|
||||
@@ -26,25 +26,6 @@ After building Redis is a good idea to test it, using:
|
||||
|
||||
% make test
|
||||
|
||||
Fixing build problems with dependencies or cached build options
|
||||
—--------
|
||||
Redis has some dependencies which are included into the "deps" directory.
|
||||
"make" does not rebuild dependencies automatically, even if something in the
|
||||
source code of dependencies is changes.
|
||||
|
||||
When you update the source code with `git pull` or when code inside the
|
||||
dependencies tree is modified in any other way, make sure to use the following
|
||||
command in order to really clean everything and rebuild from scratch:
|
||||
|
||||
make distclean
|
||||
|
||||
This will clean: jemalloc, lua, hiredis, linenoise.
|
||||
|
||||
Also if you force certain build options like 32bit target, no C compiler
|
||||
optimizations (for debugging purposes), and other similar build time options,
|
||||
those options are cached indefinitely until you issue a "make distclean"
|
||||
command.
|
||||
|
||||
Fixing problems building 32 bit binaries
|
||||
---------
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -58,7 +58,7 @@ ifeq ($(uname_S),SunOS)
|
||||
LUA_CFLAGS= -D__C99FEATURES__=1
|
||||
endif
|
||||
|
||||
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL $(CFLAGS)
|
||||
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI $(CFLAGS)
|
||||
LUA_LDFLAGS+= $(LDFLAGS)
|
||||
# lua's Makefile defines AR="ar rcu", which is unusual, and makes it more
|
||||
# challenging to cross-compile lua (and redis). These defines make it easier
|
||||
|
||||
Vendored
-1
@@ -443,7 +443,6 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {
|
||||
c->err = REDIS_ERR_OTHER;
|
||||
snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str);
|
||||
c->reader->fn->freeObject(reply);
|
||||
__redisAsyncDisconnect(ac);
|
||||
return;
|
||||
}
|
||||
|
||||
Vendored
+2
-3
@@ -25,10 +25,9 @@ PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris
|
||||
LUA_A= liblua.a
|
||||
CORE_O= lapi.o lcode.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o \
|
||||
lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o \
|
||||
lundump.o lvm.o lzio.o strbuf.o fpconv.o
|
||||
lundump.o lvm.o lzio.o strbuf.o
|
||||
LIB_O= lauxlib.o lbaselib.o ldblib.o liolib.o lmathlib.o loslib.o ltablib.o \
|
||||
lstrlib.o loadlib.o linit.o lua_cjson.o lua_struct.o lua_cmsgpack.o \
|
||||
lua_bit.o
|
||||
lstrlib.o loadlib.o linit.o lua_cjson.o lua_struct.o lua_cmsgpack.o
|
||||
|
||||
LUA_T= lua
|
||||
LUA_O= lua.o
|
||||
|
||||
Vendored
-205
@@ -1,205 +0,0 @@
|
||||
/* fpconv - Floating point conversion routines
|
||||
*
|
||||
* Copyright (c) 2011-2012 Mark Pulford <mark@kyne.com.au>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* JSON uses a '.' decimal separator. strtod() / sprintf() under C libraries
|
||||
* with locale support will break when the decimal separator is a comma.
|
||||
*
|
||||
* fpconv_* will around these issues with a translation buffer if required.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fpconv.h"
|
||||
|
||||
/* Lua CJSON assumes the locale is the same for all threads within a
|
||||
* process and doesn't change after initialisation.
|
||||
*
|
||||
* This avoids the need for per thread storage or expensive checks
|
||||
* for call. */
|
||||
static char locale_decimal_point = '.';
|
||||
|
||||
/* In theory multibyte decimal_points are possible, but
|
||||
* Lua CJSON only supports UTF-8 and known locales only have
|
||||
* single byte decimal points ([.,]).
|
||||
*
|
||||
* localconv() may not be thread safe (=>crash), and nl_langinfo() is
|
||||
* not supported on some platforms. Use sprintf() instead - if the
|
||||
* locale does change, at least Lua CJSON won't crash. */
|
||||
static void fpconv_update_locale()
|
||||
{
|
||||
char buf[8];
|
||||
|
||||
snprintf(buf, sizeof(buf), "%g", 0.5);
|
||||
|
||||
/* Failing this test might imply the platform has a buggy dtoa
|
||||
* implementation or wide characters */
|
||||
if (buf[0] != '0' || buf[2] != '5' || buf[3] != 0) {
|
||||
fprintf(stderr, "Error: wide characters found or printf() bug.");
|
||||
abort();
|
||||
}
|
||||
|
||||
locale_decimal_point = buf[1];
|
||||
}
|
||||
|
||||
/* Check for a valid number character: [-+0-9a-yA-Y.]
|
||||
* Eg: -0.6e+5, infinity, 0xF0.F0pF0
|
||||
*
|
||||
* Used to find the probable end of a number. It doesn't matter if
|
||||
* invalid characters are counted - strtod() will find the valid
|
||||
* number if it exists. The risk is that slightly more memory might
|
||||
* be allocated before a parse error occurs. */
|
||||
static inline int valid_number_character(char ch)
|
||||
{
|
||||
char lower_ch;
|
||||
|
||||
if ('0' <= ch && ch <= '9')
|
||||
return 1;
|
||||
if (ch == '-' || ch == '+' || ch == '.')
|
||||
return 1;
|
||||
|
||||
/* Hex digits, exponent (e), base (p), "infinity",.. */
|
||||
lower_ch = ch | 0x20;
|
||||
if ('a' <= lower_ch && lower_ch <= 'y')
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Calculate the size of the buffer required for a strtod locale
|
||||
* conversion. */
|
||||
static int strtod_buffer_size(const char *s)
|
||||
{
|
||||
const char *p = s;
|
||||
|
||||
while (valid_number_character(*p))
|
||||
p++;
|
||||
|
||||
return p - s;
|
||||
}
|
||||
|
||||
/* Similar to strtod(), but must be passed the current locale's decimal point
|
||||
* character. Guaranteed to be called at the start of any valid number in a string */
|
||||
double fpconv_strtod(const char *nptr, char **endptr)
|
||||
{
|
||||
char localbuf[FPCONV_G_FMT_BUFSIZE];
|
||||
char *buf, *endbuf, *dp;
|
||||
int buflen;
|
||||
double value;
|
||||
|
||||
/* System strtod() is fine when decimal point is '.' */
|
||||
if (locale_decimal_point == '.')
|
||||
return strtod(nptr, endptr);
|
||||
|
||||
buflen = strtod_buffer_size(nptr);
|
||||
if (!buflen) {
|
||||
/* No valid characters found, standard strtod() return */
|
||||
*endptr = (char *)nptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Duplicate number into buffer */
|
||||
if (buflen >= FPCONV_G_FMT_BUFSIZE) {
|
||||
/* Handle unusually large numbers */
|
||||
buf = malloc(buflen + 1);
|
||||
if (!buf) {
|
||||
fprintf(stderr, "Out of memory");
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
/* This is the common case.. */
|
||||
buf = localbuf;
|
||||
}
|
||||
memcpy(buf, nptr, buflen);
|
||||
buf[buflen] = 0;
|
||||
|
||||
/* Update decimal point character if found */
|
||||
dp = strchr(buf, '.');
|
||||
if (dp)
|
||||
*dp = locale_decimal_point;
|
||||
|
||||
value = strtod(buf, &endbuf);
|
||||
*endptr = (char *)&nptr[endbuf - buf];
|
||||
if (buflen >= FPCONV_G_FMT_BUFSIZE)
|
||||
free(buf);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* "fmt" must point to a buffer of at least 6 characters */
|
||||
static void set_number_format(char *fmt, int precision)
|
||||
{
|
||||
int d1, d2, i;
|
||||
|
||||
assert(1 <= precision && precision <= 14);
|
||||
|
||||
/* Create printf format (%.14g) from precision */
|
||||
d1 = precision / 10;
|
||||
d2 = precision % 10;
|
||||
fmt[0] = '%';
|
||||
fmt[1] = '.';
|
||||
i = 2;
|
||||
if (d1) {
|
||||
fmt[i++] = '0' + d1;
|
||||
}
|
||||
fmt[i++] = '0' + d2;
|
||||
fmt[i++] = 'g';
|
||||
fmt[i] = 0;
|
||||
}
|
||||
|
||||
/* Assumes there is always at least 32 characters available in the target buffer */
|
||||
int fpconv_g_fmt(char *str, double num, int precision)
|
||||
{
|
||||
char buf[FPCONV_G_FMT_BUFSIZE];
|
||||
char fmt[6];
|
||||
int len;
|
||||
char *b;
|
||||
|
||||
set_number_format(fmt, precision);
|
||||
|
||||
/* Pass through when decimal point character is dot. */
|
||||
if (locale_decimal_point == '.')
|
||||
return snprintf(str, FPCONV_G_FMT_BUFSIZE, fmt, num);
|
||||
|
||||
/* snprintf() to a buffer then translate for other decimal point characters */
|
||||
len = snprintf(buf, FPCONV_G_FMT_BUFSIZE, fmt, num);
|
||||
|
||||
/* Copy into target location. Translate decimal point if required */
|
||||
b = buf;
|
||||
do {
|
||||
*str++ = (*b == locale_decimal_point ? '.' : *b);
|
||||
} while(*b++);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void fpconv_init()
|
||||
{
|
||||
fpconv_update_locale();
|
||||
}
|
||||
|
||||
/* vi:ai et sw=4 ts=4:
|
||||
*/
|
||||
Vendored
-22
@@ -1,22 +0,0 @@
|
||||
/* Lua CJSON floating point conversion routines */
|
||||
|
||||
/* Buffer required to store the largest string representation of a double.
|
||||
*
|
||||
* Longest double printed with %.14g is 21 characters long:
|
||||
* -1.7976931348623e+308 */
|
||||
# define FPCONV_G_FMT_BUFSIZE 32
|
||||
|
||||
#ifdef USE_INTERNAL_FPCONV
|
||||
static inline void fpconv_init()
|
||||
{
|
||||
/* Do nothing - not required */
|
||||
}
|
||||
#else
|
||||
extern void fpconv_init();
|
||||
#endif
|
||||
|
||||
extern int fpconv_g_fmt(char*, double, int);
|
||||
extern double fpconv_strtod(const char*, char**);
|
||||
|
||||
/* vi:ai et sw=4 ts=4:
|
||||
*/
|
||||
Vendored
+1
-1
@@ -495,7 +495,7 @@ static void f_parser (lua_State *L, void *ud) {
|
||||
struct SParser *p = cast(struct SParser *, ud);
|
||||
int c = luaZ_lookahead(p->z);
|
||||
luaC_checkGC(L);
|
||||
tf = (luaY_parser)(L, p->z,
|
||||
tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z,
|
||||
&p->buff, p->name);
|
||||
cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));
|
||||
cl->l.p = tf;
|
||||
|
||||
Vendored
-189
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
** Lua BitOp -- a bit operations library for Lua 5.1/5.2.
|
||||
** http://bitop.luajit.org/
|
||||
**
|
||||
** Copyright (C) 2008-2012 Mike Pall. All rights reserved.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining
|
||||
** a copy of this software and associated documentation files (the
|
||||
** "Software"), to deal in the Software without restriction, including
|
||||
** without limitation the rights to use, copy, modify, merge, publish,
|
||||
** distribute, sublicense, and/or sell copies of the Software, and to
|
||||
** permit persons to whom the Software is furnished to do so, subject to
|
||||
** the following conditions:
|
||||
**
|
||||
** The above copyright notice and this permission notice shall be
|
||||
** included in all copies or substantial portions of the Software.
|
||||
**
|
||||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
**
|
||||
** [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
|
||||
*/
|
||||
|
||||
#define LUA_BITOP_VERSION "1.0.2"
|
||||
|
||||
#define LUA_LIB
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/* MSVC is stuck in the last century and doesn't have C99's stdint.h. */
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
typedef int32_t SBits;
|
||||
typedef uint32_t UBits;
|
||||
|
||||
typedef union {
|
||||
lua_Number n;
|
||||
#ifdef LUA_NUMBER_DOUBLE
|
||||
uint64_t b;
|
||||
#else
|
||||
UBits b;
|
||||
#endif
|
||||
} BitNum;
|
||||
|
||||
/* Convert argument to bit type. */
|
||||
static UBits barg(lua_State *L, int idx)
|
||||
{
|
||||
BitNum bn;
|
||||
UBits b;
|
||||
#if LUA_VERSION_NUM < 502
|
||||
bn.n = lua_tonumber(L, idx);
|
||||
#else
|
||||
bn.n = luaL_checknumber(L, idx);
|
||||
#endif
|
||||
#if defined(LUA_NUMBER_DOUBLE)
|
||||
bn.n += 6755399441055744.0; /* 2^52+2^51 */
|
||||
#ifdef SWAPPED_DOUBLE
|
||||
b = (UBits)(bn.b >> 32);
|
||||
#else
|
||||
b = (UBits)bn.b;
|
||||
#endif
|
||||
#elif defined(LUA_NUMBER_INT) || defined(LUA_NUMBER_LONG) || \
|
||||
defined(LUA_NUMBER_LONGLONG) || defined(LUA_NUMBER_LONG_LONG) || \
|
||||
defined(LUA_NUMBER_LLONG)
|
||||
if (sizeof(UBits) == sizeof(lua_Number))
|
||||
b = bn.b;
|
||||
else
|
||||
b = (UBits)(SBits)bn.n;
|
||||
#elif defined(LUA_NUMBER_FLOAT)
|
||||
#error "A 'float' lua_Number type is incompatible with this library"
|
||||
#else
|
||||
#error "Unknown number type, check LUA_NUMBER_* in luaconf.h"
|
||||
#endif
|
||||
#if LUA_VERSION_NUM < 502
|
||||
if (b == 0 && !lua_isnumber(L, idx)) {
|
||||
luaL_typerror(L, idx, "number");
|
||||
}
|
||||
#endif
|
||||
return b;
|
||||
}
|
||||
|
||||
/* Return bit type. */
|
||||
#define BRET(b) lua_pushnumber(L, (lua_Number)(SBits)(b)); return 1;
|
||||
|
||||
static int bit_tobit(lua_State *L) { BRET(barg(L, 1)) }
|
||||
static int bit_bnot(lua_State *L) { BRET(~barg(L, 1)) }
|
||||
|
||||
#define BIT_OP(func, opr) \
|
||||
static int func(lua_State *L) { int i; UBits b = barg(L, 1); \
|
||||
for (i = lua_gettop(L); i > 1; i--) b opr barg(L, i); BRET(b) }
|
||||
BIT_OP(bit_band, &=)
|
||||
BIT_OP(bit_bor, |=)
|
||||
BIT_OP(bit_bxor, ^=)
|
||||
|
||||
#define bshl(b, n) (b << n)
|
||||
#define bshr(b, n) (b >> n)
|
||||
#define bsar(b, n) ((SBits)b >> n)
|
||||
#define brol(b, n) ((b << n) | (b >> (32-n)))
|
||||
#define bror(b, n) ((b << (32-n)) | (b >> n))
|
||||
#define BIT_SH(func, fn) \
|
||||
static int func(lua_State *L) { \
|
||||
UBits b = barg(L, 1); UBits n = barg(L, 2) & 31; BRET(fn(b, n)) }
|
||||
BIT_SH(bit_lshift, bshl)
|
||||
BIT_SH(bit_rshift, bshr)
|
||||
BIT_SH(bit_arshift, bsar)
|
||||
BIT_SH(bit_rol, brol)
|
||||
BIT_SH(bit_ror, bror)
|
||||
|
||||
static int bit_bswap(lua_State *L)
|
||||
{
|
||||
UBits b = barg(L, 1);
|
||||
b = (b >> 24) | ((b >> 8) & 0xff00) | ((b & 0xff00) << 8) | (b << 24);
|
||||
BRET(b)
|
||||
}
|
||||
|
||||
static int bit_tohex(lua_State *L)
|
||||
{
|
||||
UBits b = barg(L, 1);
|
||||
SBits n = lua_isnone(L, 2) ? 8 : (SBits)barg(L, 2);
|
||||
const char *hexdigits = "0123456789abcdef";
|
||||
char buf[8];
|
||||
int i;
|
||||
if (n < 0) { n = -n; hexdigits = "0123456789ABCDEF"; }
|
||||
if (n > 8) n = 8;
|
||||
for (i = (int)n; --i >= 0; ) { buf[i] = hexdigits[b & 15]; b >>= 4; }
|
||||
lua_pushlstring(L, buf, (size_t)n);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct luaL_Reg bit_funcs[] = {
|
||||
{ "tobit", bit_tobit },
|
||||
{ "bnot", bit_bnot },
|
||||
{ "band", bit_band },
|
||||
{ "bor", bit_bor },
|
||||
{ "bxor", bit_bxor },
|
||||
{ "lshift", bit_lshift },
|
||||
{ "rshift", bit_rshift },
|
||||
{ "arshift", bit_arshift },
|
||||
{ "rol", bit_rol },
|
||||
{ "ror", bit_ror },
|
||||
{ "bswap", bit_bswap },
|
||||
{ "tohex", bit_tohex },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
/* Signed right-shifts are implementation-defined per C89/C99.
|
||||
** But the de facto standard are arithmetic right-shifts on two's
|
||||
** complement CPUs. This behaviour is required here, so test for it.
|
||||
*/
|
||||
#define BAD_SAR (bsar(-8, 2) != (SBits)-2)
|
||||
|
||||
LUALIB_API int luaopen_bit(lua_State *L)
|
||||
{
|
||||
UBits b;
|
||||
lua_pushnumber(L, (lua_Number)1437217655L);
|
||||
b = barg(L, -1);
|
||||
if (b != (UBits)1437217655L || BAD_SAR) { /* Perform a simple self-test. */
|
||||
const char *msg = "compiled with incompatible luaconf.h";
|
||||
#ifdef LUA_NUMBER_DOUBLE
|
||||
#ifdef _WIN32
|
||||
if (b == (UBits)1610612736L)
|
||||
msg = "use D3DCREATE_FPU_PRESERVE with DirectX";
|
||||
#endif
|
||||
if (b == (UBits)1127743488L)
|
||||
msg = "not compiled with SWAPPED_DOUBLE";
|
||||
#endif
|
||||
if (BAD_SAR)
|
||||
msg = "arithmetic right-shift broken";
|
||||
luaL_error(L, "bit library self-test failed (%s)", msg);
|
||||
}
|
||||
#if LUA_VERSION_NUM < 502
|
||||
luaL_register(L, "bit", bit_funcs);
|
||||
#else
|
||||
luaL_newlib(L, bit_funcs);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
Vendored
+310
-438
File diff suppressed because it is too large
Load Diff
Vendored
+127
-368
@@ -7,38 +7,14 @@
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
|
||||
#define LUACMSGPACK_NAME "cmsgpack"
|
||||
#define LUACMSGPACK_SAFE_NAME "cmsgpack_safe"
|
||||
#define LUACMSGPACK_VERSION "lua-cmsgpack 0.4.0"
|
||||
#define LUACMSGPACK_VERSION "lua-cmsgpack 0.3.0"
|
||||
#define LUACMSGPACK_COPYRIGHT "Copyright (C) 2012, Salvatore Sanfilippo"
|
||||
#define LUACMSGPACK_DESCRIPTION "MessagePack C implementation for Lua"
|
||||
|
||||
/* Allows a preprocessor directive to override MAX_NESTING */
|
||||
#ifndef LUACMSGPACK_MAX_NESTING
|
||||
#define LUACMSGPACK_MAX_NESTING 16 /* Max tables nesting. */
|
||||
#endif
|
||||
#define LUACMSGPACK_MAX_NESTING 16 /* Max tables nesting. */
|
||||
|
||||
/* Check if float or double can be an integer without loss of precision */
|
||||
#define IS_INT_TYPE_EQUIVALENT(x, T) (!isinf(x) && (T)(x) == (x))
|
||||
|
||||
#define IS_INT64_EQUIVALENT(x) IS_INT_TYPE_EQUIVALENT(x, int64_t)
|
||||
#define IS_INT_EQUIVALENT(x) IS_INT_TYPE_EQUIVALENT(x, int)
|
||||
|
||||
/* If size of pointer is equal to a 4 byte integer, we're on 32 bits. */
|
||||
#if UINTPTR_MAX == UINT_MAX
|
||||
#define BITS_32 1
|
||||
#else
|
||||
#define BITS_32 0
|
||||
#endif
|
||||
|
||||
#if BITS_32
|
||||
#define lua_pushunsigned(L, n) lua_pushnumber(L, n)
|
||||
#else
|
||||
#define lua_pushunsigned(L, n) lua_pushinteger(L, n)
|
||||
#endif
|
||||
|
||||
/* =============================================================================
|
||||
* MessagePack implementation and bindings for Lua 5.1/5.2.
|
||||
/* ==============================================================================
|
||||
* MessagePack implementation and bindings for Lua 5.1.
|
||||
* Copyright(C) 2012 Salvatore Sanfilippo <antirez@gmail.com>
|
||||
*
|
||||
* http://github.com/antirez/lua-cmsgpack
|
||||
@@ -53,27 +29,23 @@
|
||||
* 20-Feb-2012 (ver 0.2.0): Tables encoding improved.
|
||||
* 20-Feb-2012 (ver 0.2.1): Minor bug fixing.
|
||||
* 20-Feb-2012 (ver 0.3.0): Module renamed lua-cmsgpack (was lua-msgpack).
|
||||
* 04-Apr-2014 (ver 0.3.1): Lua 5.2 support and minor bug fix.
|
||||
* 07-Apr-2014 (ver 0.4.0): Multiple pack/unpack, lua allocator, efficiency.
|
||||
* ========================================================================== */
|
||||
* ============================================================================ */
|
||||
|
||||
/* -------------------------- Endian conversion --------------------------------
|
||||
* We use it only for floats and doubles, all the other conversions performed
|
||||
/* --------------------------- Endian conversion --------------------------------
|
||||
* We use it only for floats and doubles, all the other conversions are performed
|
||||
* in an endian independent fashion. So the only thing we need is a function
|
||||
* that swaps a binary string if arch is little endian (and left it untouched
|
||||
* that swaps a binary string if the arch is little endian (and left it untouched
|
||||
* otherwise). */
|
||||
|
||||
/* Reverse memory bytes if arch is little endian. Given the conceptual
|
||||
* simplicity of the Lua build system we prefer check for endianess at runtime.
|
||||
* simplicity of the Lua build system we prefer to check for endianess at runtime.
|
||||
* The performance difference should be acceptable. */
|
||||
void memrevifle(void *ptr, size_t len) {
|
||||
unsigned char *p = (unsigned char *)ptr,
|
||||
*e = (unsigned char *)p+len-1,
|
||||
aux;
|
||||
static void memrevifle(void *ptr, size_t len) {
|
||||
unsigned char *p = ptr, *e = p+len-1, aux;
|
||||
int test = 1;
|
||||
unsigned char *testp = (unsigned char*) &test;
|
||||
|
||||
if (testp[0] == 0) return; /* Big endian, nothing to do. */
|
||||
if (testp[0] == 0) return; /* Big endian, nothign to do. */
|
||||
len /= 2;
|
||||
while(len--) {
|
||||
aux = *p;
|
||||
@@ -84,34 +56,20 @@ void memrevifle(void *ptr, size_t len) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------- String buffer ----------------------------------
|
||||
* This is a simple implementation of string buffers. The only operation
|
||||
/* ----------------------------- String buffer ----------------------------------
|
||||
* This is a simple implementation of string buffers. The only opereation
|
||||
* supported is creating empty buffers and appending bytes to it.
|
||||
* The string buffer uses 2x preallocation on every realloc for O(N) append
|
||||
* behavior. */
|
||||
|
||||
typedef struct mp_buf {
|
||||
lua_State *L;
|
||||
unsigned char *b;
|
||||
size_t len, free;
|
||||
} mp_buf;
|
||||
|
||||
void *mp_realloc(lua_State *L, void *target, size_t osize,size_t nsize) {
|
||||
void *(*local_realloc) (void *, void *, size_t osize, size_t nsize) = NULL;
|
||||
void *ud;
|
||||
|
||||
local_realloc = lua_getallocf(L, &ud);
|
||||
|
||||
return local_realloc(ud, target, osize, nsize);
|
||||
}
|
||||
|
||||
mp_buf *mp_buf_new(lua_State *L) {
|
||||
mp_buf *buf = NULL;
|
||||
|
||||
/* Old size = 0; new size = sizeof(*buf) */
|
||||
buf = (mp_buf*)mp_realloc(L, NULL, 0, sizeof(*buf));
|
||||
|
||||
buf->L = L;
|
||||
static mp_buf *mp_buf_new(void) {
|
||||
mp_buf *buf = malloc(sizeof(*buf));
|
||||
|
||||
buf->b = NULL;
|
||||
buf->len = buf->free = 0;
|
||||
return buf;
|
||||
@@ -121,7 +79,7 @@ void mp_buf_append(mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
if (buf->free < len) {
|
||||
size_t newlen = buf->len+len;
|
||||
|
||||
buf->b = (unsigned char*)mp_realloc(buf->L, buf->b, buf->len, newlen*2);
|
||||
buf->b = realloc(buf->b,newlen*2);
|
||||
buf->free = newlen;
|
||||
}
|
||||
memcpy(buf->b+buf->len,s,len);
|
||||
@@ -130,11 +88,11 @@ void mp_buf_append(mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
}
|
||||
|
||||
void mp_buf_free(mp_buf *buf) {
|
||||
mp_realloc(buf->L, buf->b, buf->len, 0); /* realloc to 0 = free */
|
||||
mp_realloc(buf->L, buf, sizeof(*buf), 0);
|
||||
free(buf->b);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* ---------------------------- String cursor ----------------------------------
|
||||
/* ------------------------------ String cursor ----------------------------------
|
||||
* This simple data structure is used for parsing. Basically you create a cursor
|
||||
* using a string pointer and a length, then it is possible to access the
|
||||
* current string position with cursor->p, check the remaining length
|
||||
@@ -144,7 +102,7 @@ void mp_buf_free(mp_buf *buf) {
|
||||
* be used to report errors. */
|
||||
|
||||
#define MP_CUR_ERROR_NONE 0
|
||||
#define MP_CUR_ERROR_EOF 1 /* Not enough data to complete operation. */
|
||||
#define MP_CUR_ERROR_EOF 1 /* Not enough data to complete the opereation. */
|
||||
#define MP_CUR_ERROR_BADFMT 2 /* Bad data format */
|
||||
|
||||
typedef struct mp_cur {
|
||||
@@ -153,15 +111,22 @@ typedef struct mp_cur {
|
||||
int err;
|
||||
} mp_cur;
|
||||
|
||||
void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) {
|
||||
static mp_cur *mp_cur_new(const unsigned char *s, size_t len) {
|
||||
mp_cur *cursor = malloc(sizeof(*cursor));
|
||||
|
||||
cursor->p = s;
|
||||
cursor->left = len;
|
||||
cursor->err = MP_CUR_ERROR_NONE;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
static void mp_cur_free(mp_cur *cursor) {
|
||||
free(cursor);
|
||||
}
|
||||
|
||||
#define mp_cur_consume(_c,_len) do { _c->p += _len; _c->left -= _len; } while(0)
|
||||
|
||||
/* When there is not enough room we set an error in the cursor and return. This
|
||||
/* When there is not enough room we set an error in the cursor and return, this
|
||||
* is very common across the code so we have a macro to make the code look
|
||||
* a bit simpler. */
|
||||
#define mp_cur_need(_c,_len) do { \
|
||||
@@ -171,19 +136,15 @@ void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) {
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* ------------------------- Low level MP encoding -------------------------- */
|
||||
/* --------------------------- Low level MP encoding -------------------------- */
|
||||
|
||||
void mp_encode_bytes(mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
static void mp_encode_bytes(mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
unsigned char hdr[5];
|
||||
int hdrlen;
|
||||
|
||||
if (len < 32) {
|
||||
hdr[0] = 0xa0 | (len&0xff); /* fix raw */
|
||||
hdrlen = 1;
|
||||
} else if (len <= 0xff) {
|
||||
hdr[0] = 0xd9;
|
||||
hdr[1] = len;
|
||||
hdrlen = 2;
|
||||
} else if (len <= 0xffff) {
|
||||
hdr[0] = 0xda;
|
||||
hdr[1] = (len&0xff00)>>8;
|
||||
@@ -202,7 +163,7 @@ void mp_encode_bytes(mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
}
|
||||
|
||||
/* we assume IEEE 754 internal format for single and double precision floats. */
|
||||
void mp_encode_double(mp_buf *buf, double d) {
|
||||
static void mp_encode_double(mp_buf *buf, double d) {
|
||||
unsigned char b[9];
|
||||
float f = d;
|
||||
|
||||
@@ -220,7 +181,7 @@ void mp_encode_double(mp_buf *buf, double d) {
|
||||
}
|
||||
}
|
||||
|
||||
void mp_encode_int(mp_buf *buf, int64_t n) {
|
||||
static void mp_encode_int(mp_buf *buf, int64_t n) {
|
||||
unsigned char b[9];
|
||||
int enclen;
|
||||
|
||||
@@ -258,7 +219,7 @@ void mp_encode_int(mp_buf *buf, int64_t n) {
|
||||
}
|
||||
} else {
|
||||
if (n >= -32) {
|
||||
b[0] = ((signed char)n); /* negative fixnum */
|
||||
b[0] = ((char)n); /* negative fixnum */
|
||||
enclen = 1;
|
||||
} else if (n >= -128) {
|
||||
b[0] = 0xd0; /* int 8 */
|
||||
@@ -292,7 +253,7 @@ void mp_encode_int(mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(buf,b,enclen);
|
||||
}
|
||||
|
||||
void mp_encode_array(mp_buf *buf, int64_t n) {
|
||||
static void mp_encode_array(mp_buf *buf, int64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
|
||||
@@ -315,7 +276,7 @@ void mp_encode_array(mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(buf,b,enclen);
|
||||
}
|
||||
|
||||
void mp_encode_map(mp_buf *buf, int64_t n) {
|
||||
static void mp_encode_map(mp_buf *buf, int64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
|
||||
@@ -338,9 +299,9 @@ void mp_encode_map(mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(buf,b,enclen);
|
||||
}
|
||||
|
||||
/* --------------------------- Lua types encoding --------------------------- */
|
||||
/* ----------------------------- Lua types encoding --------------------------- */
|
||||
|
||||
void mp_encode_lua_string(lua_State *L, mp_buf *buf) {
|
||||
static void mp_encode_lua_string(lua_State *L, mp_buf *buf) {
|
||||
size_t len;
|
||||
const char *s;
|
||||
|
||||
@@ -348,43 +309,26 @@ void mp_encode_lua_string(lua_State *L, mp_buf *buf) {
|
||||
mp_encode_bytes(buf,(const unsigned char*)s,len);
|
||||
}
|
||||
|
||||
void mp_encode_lua_bool(lua_State *L, mp_buf *buf) {
|
||||
static void mp_encode_lua_bool(lua_State *L, mp_buf *buf) {
|
||||
unsigned char b = lua_toboolean(L,-1) ? 0xc3 : 0xc2;
|
||||
mp_buf_append(buf,&b,1);
|
||||
}
|
||||
|
||||
/* Lua 5.3 has a built in 64-bit integer type */
|
||||
void mp_encode_lua_integer(lua_State *L, mp_buf *buf) {
|
||||
#if (LUA_VERSION_NUM < 503) && BITS_32
|
||||
lua_Number i = lua_tonumber(L,-1);
|
||||
#else
|
||||
lua_Integer i = lua_tointeger(L,-1);
|
||||
#endif
|
||||
mp_encode_int(buf, (int64_t)i);
|
||||
}
|
||||
|
||||
/* Lua 5.2 and lower only has 64-bit doubles, so we need to
|
||||
* detect if the double may be representable as an int
|
||||
* for Lua < 5.3 */
|
||||
void mp_encode_lua_number(lua_State *L, mp_buf *buf) {
|
||||
static void mp_encode_lua_number(lua_State *L, mp_buf *buf) {
|
||||
lua_Number n = lua_tonumber(L,-1);
|
||||
|
||||
if (IS_INT64_EQUIVALENT(n)) {
|
||||
mp_encode_lua_integer(L, buf);
|
||||
} else {
|
||||
if (floor(n) != n) {
|
||||
mp_encode_double(buf,(double)n);
|
||||
} else {
|
||||
mp_encode_int(buf,(int64_t)n);
|
||||
}
|
||||
}
|
||||
|
||||
void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level);
|
||||
static void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level);
|
||||
|
||||
/* Convert a lua table into a message pack list. */
|
||||
void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) {
|
||||
#if LUA_VERSION_NUM < 502
|
||||
static void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) {
|
||||
size_t len = lua_objlen(L,-1), j;
|
||||
#else
|
||||
size_t len = lua_rawlen(L,-1), j;
|
||||
#endif
|
||||
|
||||
mp_encode_array(buf,len);
|
||||
for (j = 1; j <= len; j++) {
|
||||
@@ -395,13 +339,13 @@ void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) {
|
||||
}
|
||||
|
||||
/* Convert a lua table into a message pack key-value map. */
|
||||
void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) {
|
||||
static void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) {
|
||||
size_t len = 0;
|
||||
|
||||
/* First step: count keys into table. No other way to do it with the
|
||||
* Lua API, we need to iterate a first time. Note that an alternative
|
||||
* would be to do a single run, and then hack the buffer to insert the
|
||||
* map opcodes for message pack. Too hackish for this lib. */
|
||||
* map opcodes for message pack. Too hachish for this lib. */
|
||||
lua_pushnil(L);
|
||||
while(lua_next(L,-2)) {
|
||||
lua_pop(L,1); /* remove value, keep key for next iteration. */
|
||||
@@ -422,131 +366,80 @@ void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) {
|
||||
/* Returns true if the Lua table on top of the stack is exclusively composed
|
||||
* of keys from numerical keys from 1 up to N, with N being the total number
|
||||
* of elements, without any hole in the middle. */
|
||||
int table_is_an_array(lua_State *L) {
|
||||
int count = 0, max = 0;
|
||||
#if LUA_VERSION_NUM < 503
|
||||
static int table_is_an_array(lua_State *L) {
|
||||
long count = 0, max = 0, idx = 0;
|
||||
lua_Number n;
|
||||
#else
|
||||
lua_Integer n;
|
||||
#endif
|
||||
|
||||
/* Stack top on function entry */
|
||||
int stacktop;
|
||||
|
||||
stacktop = lua_gettop(L);
|
||||
|
||||
lua_pushnil(L);
|
||||
while(lua_next(L,-2)) {
|
||||
/* Stack: ... key value */
|
||||
lua_pop(L,1); /* Stack: ... key */
|
||||
/* The <= 0 check is valid here because we're comparing indexes. */
|
||||
#if LUA_VERSION_NUM < 503
|
||||
if ((LUA_TNUMBER != lua_type(L,-1)) || (n = lua_tonumber(L, -1)) <= 0 ||
|
||||
!IS_INT_EQUIVALENT(n))
|
||||
#else
|
||||
if (!lua_isinteger(L,-1) || (n = lua_tointeger(L, -1)) <= 0)
|
||||
#endif
|
||||
{
|
||||
lua_settop(L, stacktop);
|
||||
return 0;
|
||||
}
|
||||
max = (n > max ? n : max);
|
||||
if (lua_type(L,-1) != LUA_TNUMBER) goto not_array;
|
||||
n = lua_tonumber(L,-1);
|
||||
idx = n;
|
||||
if (idx != n || idx < 1) goto not_array;
|
||||
count++;
|
||||
max = idx;
|
||||
}
|
||||
/* We have the total number of elements in "count". Also we have
|
||||
* the max index encountered in "max". We can't reach this code
|
||||
* the max index encountered in "idx". We can't reach this code
|
||||
* if there are indexes <= 0. If you also note that there can not be
|
||||
* repeated keys into a table, you have that if max==count you are sure
|
||||
* repeated keys into a table, you have that if idx==count you are sure
|
||||
* that there are all the keys form 1 to count (both included). */
|
||||
lua_settop(L, stacktop);
|
||||
return max == count;
|
||||
return idx == count;
|
||||
|
||||
not_array:
|
||||
lua_pop(L,1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If the length operator returns non-zero, that is, there is at least
|
||||
* an object at key '1', we serialize to message pack list. Otherwise
|
||||
* we use a map. */
|
||||
void mp_encode_lua_table(lua_State *L, mp_buf *buf, int level) {
|
||||
static void mp_encode_lua_table(lua_State *L, mp_buf *buf, int level) {
|
||||
if (table_is_an_array(L))
|
||||
mp_encode_lua_table_as_array(L,buf,level);
|
||||
else
|
||||
mp_encode_lua_table_as_map(L,buf,level);
|
||||
}
|
||||
|
||||
void mp_encode_lua_null(lua_State *L, mp_buf *buf) {
|
||||
static void mp_encode_lua_null(lua_State *L, mp_buf *buf) {
|
||||
unsigned char b[1];
|
||||
(void)L;
|
||||
|
||||
b[0] = 0xc0;
|
||||
mp_buf_append(buf,b,1);
|
||||
}
|
||||
|
||||
void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level) {
|
||||
static void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level) {
|
||||
int t = lua_type(L,-1);
|
||||
|
||||
/* Limit the encoding of nested tables to a specified maximum depth, so that
|
||||
/* Limit the encoding of nested tables to a specfiied maximum depth, so that
|
||||
* we survive when called against circular references in tables. */
|
||||
if (t == LUA_TTABLE && level == LUACMSGPACK_MAX_NESTING) t = LUA_TNIL;
|
||||
switch(t) {
|
||||
case LUA_TSTRING: mp_encode_lua_string(L,buf); break;
|
||||
case LUA_TBOOLEAN: mp_encode_lua_bool(L,buf); break;
|
||||
case LUA_TNUMBER:
|
||||
#if LUA_VERSION_NUM < 503
|
||||
mp_encode_lua_number(L,buf); break;
|
||||
#else
|
||||
if (lua_isinteger(L, -1)) {
|
||||
mp_encode_lua_integer(L, buf);
|
||||
} else {
|
||||
mp_encode_lua_number(L, buf);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case LUA_TNUMBER: mp_encode_lua_number(L,buf); break;
|
||||
case LUA_TTABLE: mp_encode_lua_table(L,buf,level); break;
|
||||
default: mp_encode_lua_null(L,buf); break;
|
||||
}
|
||||
lua_pop(L,1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Packs all arguments as a stream for multiple upacking later.
|
||||
* Returns error if no arguments provided.
|
||||
*/
|
||||
int mp_pack(lua_State *L) {
|
||||
int nargs = lua_gettop(L);
|
||||
int i;
|
||||
mp_buf *buf;
|
||||
static int mp_pack(lua_State *L) {
|
||||
mp_buf *buf = mp_buf_new();
|
||||
|
||||
if (nargs == 0)
|
||||
return luaL_argerror(L, 0, "MessagePack pack needs input.");
|
||||
|
||||
buf = mp_buf_new(L);
|
||||
for(i = 1; i <= nargs; i++) {
|
||||
/* Copy argument i to top of stack for _encode processing;
|
||||
* the encode function pops it from the stack when complete. */
|
||||
lua_pushvalue(L, i);
|
||||
|
||||
mp_encode_lua_type(L,buf,0);
|
||||
|
||||
lua_pushlstring(L,(char*)buf->b,buf->len);
|
||||
|
||||
/* Reuse the buffer for the next operation by
|
||||
* setting its free count to the total buffer size
|
||||
* and the current position to zero. */
|
||||
buf->free += buf->len;
|
||||
buf->len = 0;
|
||||
}
|
||||
mp_encode_lua_type(L,buf,0);
|
||||
lua_pushlstring(L,(char*)buf->b,buf->len);
|
||||
mp_buf_free(buf);
|
||||
|
||||
/* Concatenate all nargs buffers together */
|
||||
lua_concat(L, nargs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ------------------------------- Decoding --------------------------------- */
|
||||
/* --------------------------------- Decoding --------------------------------- */
|
||||
|
||||
void mp_decode_to_lua_type(lua_State *L, mp_cur *c);
|
||||
|
||||
void mp_decode_to_lua_array(lua_State *L, mp_cur *c, size_t len) {
|
||||
assert(len <= UINT_MAX);
|
||||
int index = 1;
|
||||
|
||||
lua_newtable(L);
|
||||
@@ -559,7 +452,6 @@ void mp_decode_to_lua_array(lua_State *L, mp_cur *c, size_t len) {
|
||||
}
|
||||
|
||||
void mp_decode_to_lua_hash(lua_State *L, mp_cur *c, size_t len) {
|
||||
assert(len <= UINT_MAX);
|
||||
lua_newtable(L);
|
||||
while(len--) {
|
||||
mp_decode_to_lua_type(L,c); /* key */
|
||||
@@ -574,44 +466,34 @@ void mp_decode_to_lua_hash(lua_State *L, mp_cur *c, size_t len) {
|
||||
* a Lua type, that is left as the only result on the stack. */
|
||||
void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
mp_cur_need(c,1);
|
||||
|
||||
/* If we return more than 18 elements, we must resize the stack to
|
||||
* fit all our return values. But, there is no way to
|
||||
* determine how many objects a msgpack will unpack to up front, so
|
||||
* we request a +1 larger stack on each iteration (noop if stack is
|
||||
* big enough, and when stack does require resize it doubles in size) */
|
||||
luaL_checkstack(L, 1,
|
||||
"too many return values at once; "
|
||||
"use unpack_one or unpack_limit instead.");
|
||||
|
||||
switch(c->p[0]) {
|
||||
case 0xcc: /* uint 8 */
|
||||
mp_cur_need(c,2);
|
||||
lua_pushunsigned(L,c->p[1]);
|
||||
lua_pushnumber(L,c->p[1]);
|
||||
mp_cur_consume(c,2);
|
||||
break;
|
||||
case 0xd0: /* int 8 */
|
||||
mp_cur_need(c,2);
|
||||
lua_pushinteger(L,(signed char)c->p[1]);
|
||||
lua_pushnumber(L,(char)c->p[1]);
|
||||
mp_cur_consume(c,2);
|
||||
break;
|
||||
case 0xcd: /* uint 16 */
|
||||
mp_cur_need(c,3);
|
||||
lua_pushunsigned(L,
|
||||
lua_pushnumber(L,
|
||||
(c->p[1] << 8) |
|
||||
c->p[2]);
|
||||
mp_cur_consume(c,3);
|
||||
break;
|
||||
case 0xd1: /* int 16 */
|
||||
mp_cur_need(c,3);
|
||||
lua_pushinteger(L,(int16_t)
|
||||
lua_pushnumber(L,(int16_t)
|
||||
(c->p[1] << 8) |
|
||||
c->p[2]);
|
||||
mp_cur_consume(c,3);
|
||||
break;
|
||||
case 0xce: /* uint 32 */
|
||||
mp_cur_need(c,5);
|
||||
lua_pushunsigned(L,
|
||||
lua_pushnumber(L,
|
||||
((uint32_t)c->p[1] << 24) |
|
||||
((uint32_t)c->p[2] << 16) |
|
||||
((uint32_t)c->p[3] << 8) |
|
||||
@@ -620,7 +502,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
break;
|
||||
case 0xd2: /* int 32 */
|
||||
mp_cur_need(c,5);
|
||||
lua_pushinteger(L,
|
||||
lua_pushnumber(L,
|
||||
((int32_t)c->p[1] << 24) |
|
||||
((int32_t)c->p[2] << 16) |
|
||||
((int32_t)c->p[3] << 8) |
|
||||
@@ -629,7 +511,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
break;
|
||||
case 0xcf: /* uint 64 */
|
||||
mp_cur_need(c,9);
|
||||
lua_pushunsigned(L,
|
||||
lua_pushnumber(L,
|
||||
((uint64_t)c->p[1] << 56) |
|
||||
((uint64_t)c->p[2] << 48) |
|
||||
((uint64_t)c->p[3] << 40) |
|
||||
@@ -642,11 +524,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
break;
|
||||
case 0xd3: /* int 64 */
|
||||
mp_cur_need(c,9);
|
||||
#if LUA_VERSION_NUM < 503
|
||||
lua_pushnumber(L,
|
||||
#else
|
||||
lua_pushinteger(L,
|
||||
#endif
|
||||
((int64_t)c->p[1] << 56) |
|
||||
((int64_t)c->p[2] << 48) |
|
||||
((int64_t)c->p[3] << 40) |
|
||||
@@ -691,15 +569,6 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
mp_cur_consume(c,9);
|
||||
}
|
||||
break;
|
||||
case 0xd9: /* raw 8 */
|
||||
mp_cur_need(c,2);
|
||||
{
|
||||
size_t l = c->p[1];
|
||||
mp_cur_need(c,2+l);
|
||||
lua_pushlstring(L,(char*)c->p+2,l);
|
||||
mp_cur_consume(c,2+l);
|
||||
}
|
||||
break;
|
||||
case 0xda: /* raw 16 */
|
||||
mp_cur_need(c,3);
|
||||
{
|
||||
@@ -712,14 +581,13 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
case 0xdb: /* raw 32 */
|
||||
mp_cur_need(c,5);
|
||||
{
|
||||
size_t l = ((size_t)c->p[1] << 24) |
|
||||
((size_t)c->p[2] << 16) |
|
||||
((size_t)c->p[3] << 8) |
|
||||
(size_t)c->p[4];
|
||||
mp_cur_consume(c,5);
|
||||
mp_cur_need(c,l);
|
||||
lua_pushlstring(L,(char*)c->p,l);
|
||||
mp_cur_consume(c,l);
|
||||
size_t l = (c->p[1] << 24) |
|
||||
(c->p[2] << 16) |
|
||||
(c->p[3] << 8) |
|
||||
c->p[4];
|
||||
mp_cur_need(c,5+l);
|
||||
lua_pushlstring(L,(char*)c->p+5,l);
|
||||
mp_cur_consume(c,5+l);
|
||||
}
|
||||
break;
|
||||
case 0xdc: /* array 16 */
|
||||
@@ -733,10 +601,10 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
case 0xdd: /* array 32 */
|
||||
mp_cur_need(c,5);
|
||||
{
|
||||
size_t l = ((size_t)c->p[1] << 24) |
|
||||
((size_t)c->p[2] << 16) |
|
||||
((size_t)c->p[3] << 8) |
|
||||
(size_t)c->p[4];
|
||||
size_t l = (c->p[1] << 24) |
|
||||
(c->p[2] << 16) |
|
||||
(c->p[3] << 8) |
|
||||
c->p[4];
|
||||
mp_cur_consume(c,5);
|
||||
mp_decode_to_lua_array(L,c,l);
|
||||
}
|
||||
@@ -752,20 +620,20 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
case 0xdf: /* map 32 */
|
||||
mp_cur_need(c,5);
|
||||
{
|
||||
size_t l = ((size_t)c->p[1] << 24) |
|
||||
((size_t)c->p[2] << 16) |
|
||||
((size_t)c->p[3] << 8) |
|
||||
(size_t)c->p[4];
|
||||
size_t l = (c->p[1] << 24) |
|
||||
(c->p[2] << 16) |
|
||||
(c->p[3] << 8) |
|
||||
c->p[4];
|
||||
mp_cur_consume(c,5);
|
||||
mp_decode_to_lua_hash(L,c,l);
|
||||
}
|
||||
break;
|
||||
default: /* types that can't be idenitified by first byte value. */
|
||||
if ((c->p[0] & 0x80) == 0) { /* positive fixnum */
|
||||
lua_pushunsigned(L,c->p[0]);
|
||||
lua_pushnumber(L,c->p[0]);
|
||||
mp_cur_consume(c,1);
|
||||
} else if ((c->p[0] & 0xe0) == 0xe0) { /* negative fixnum */
|
||||
lua_pushinteger(L,(signed char)c->p[0]);
|
||||
lua_pushnumber(L,(signed char)c->p[0]);
|
||||
mp_cur_consume(c,1);
|
||||
} else if ((c->p[0] & 0xe0) == 0xa0) { /* fix raw */
|
||||
size_t l = c->p[0] & 0x1f;
|
||||
@@ -786,163 +654,54 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
}
|
||||
}
|
||||
|
||||
int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
static int mp_unpack(lua_State *L) {
|
||||
size_t len;
|
||||
const char *s;
|
||||
mp_cur c;
|
||||
int cnt; /* Number of objects unpacked */
|
||||
int decode_all = (!limit && !offset);
|
||||
const unsigned char *s;
|
||||
mp_cur *c;
|
||||
|
||||
s = luaL_checklstring(L,1,&len); /* if no match, exits */
|
||||
|
||||
if (offset < 0 || limit < 0) /* requesting negative off or lim is invalid */
|
||||
return luaL_error(L,
|
||||
"Invalid request to unpack with offset of %d and limit of %d.",
|
||||
offset, len);
|
||||
else if (offset > len)
|
||||
return luaL_error(L,
|
||||
"Start offset %d greater than input length %d.", offset, len);
|
||||
|
||||
if (decode_all) limit = INT_MAX;
|
||||
|
||||
mp_cur_init(&c,(const unsigned char *)s+offset,len-offset);
|
||||
|
||||
/* We loop over the decode because this could be a stream
|
||||
* of multiple top-level values serialized together */
|
||||
for(cnt = 0; c.left > 0 && cnt < limit; cnt++) {
|
||||
mp_decode_to_lua_type(L,&c);
|
||||
|
||||
if (c.err == MP_CUR_ERROR_EOF) {
|
||||
return luaL_error(L,"Missing bytes in input.");
|
||||
} else if (c.err == MP_CUR_ERROR_BADFMT) {
|
||||
return luaL_error(L,"Bad data format in input.");
|
||||
}
|
||||
if (!lua_isstring(L,-1)) {
|
||||
lua_pushstring(L,"MessagePack decoding needs a string as input.");
|
||||
lua_error(L);
|
||||
}
|
||||
|
||||
if (!decode_all) {
|
||||
/* c->left is the remaining size of the input buffer.
|
||||
* subtract the entire buffer size from the unprocessed size
|
||||
* to get our next start offset */
|
||||
int offset = len - c.left;
|
||||
/* Return offset -1 when we have have processed the entire buffer. */
|
||||
lua_pushinteger(L, c.left == 0 ? -1 : offset);
|
||||
/* Results are returned with the arg elements still
|
||||
* in place. Lua takes care of only returning
|
||||
* elements above the args for us.
|
||||
* In this case, we have one arg on the stack
|
||||
* for this function, so we insert our first return
|
||||
* value at position 2. */
|
||||
lua_insert(L, 2);
|
||||
cnt += 1; /* increase return count by one to make room for offset */
|
||||
s = (const unsigned char*) lua_tolstring(L,-1,&len);
|
||||
c = mp_cur_new(s,len);
|
||||
mp_decode_to_lua_type(L,c);
|
||||
|
||||
if (c->err == MP_CUR_ERROR_EOF) {
|
||||
mp_cur_free(c);
|
||||
lua_pushstring(L,"Missing bytes in input.");
|
||||
lua_error(L);
|
||||
} else if (c->err == MP_CUR_ERROR_BADFMT) {
|
||||
mp_cur_free(c);
|
||||
lua_pushstring(L,"Bad data format in input.");
|
||||
lua_error(L);
|
||||
} else if (c->left != 0) {
|
||||
mp_cur_free(c);
|
||||
lua_pushstring(L,"Extra bytes in input.");
|
||||
lua_error(L);
|
||||
}
|
||||
|
||||
return cnt;
|
||||
mp_cur_free(c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int mp_unpack(lua_State *L) {
|
||||
return mp_unpack_full(L, 0, 0);
|
||||
}
|
||||
/* ---------------------------------------------------------------------------- */
|
||||
|
||||
int mp_unpack_one(lua_State *L) {
|
||||
int offset = luaL_optinteger(L, 2, 0);
|
||||
/* Variable pop because offset may not exist */
|
||||
lua_pop(L, lua_gettop(L)-1);
|
||||
return mp_unpack_full(L, 1, offset);
|
||||
}
|
||||
|
||||
int mp_unpack_limit(lua_State *L) {
|
||||
int limit = luaL_checkinteger(L, 2);
|
||||
int offset = luaL_optinteger(L, 3, 0);
|
||||
/* Variable pop because offset may not exist */
|
||||
lua_pop(L, lua_gettop(L)-1);
|
||||
|
||||
return mp_unpack_full(L, limit, offset);
|
||||
}
|
||||
|
||||
int mp_safe(lua_State *L) {
|
||||
int argc, err, total_results;
|
||||
|
||||
argc = lua_gettop(L);
|
||||
|
||||
/* This adds our function to the bottom of the stack
|
||||
* (the "call this function" position) */
|
||||
lua_pushvalue(L, lua_upvalueindex(1));
|
||||
lua_insert(L, 1);
|
||||
|
||||
err = lua_pcall(L, argc, LUA_MULTRET, 0);
|
||||
total_results = lua_gettop(L);
|
||||
|
||||
if (!err) {
|
||||
return total_results;
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
lua_insert(L,-2);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
const struct luaL_Reg cmds[] = {
|
||||
static const struct luaL_reg thislib[] = {
|
||||
{"pack", mp_pack},
|
||||
{"unpack", mp_unpack},
|
||||
{"unpack_one", mp_unpack_one},
|
||||
{"unpack_limit", mp_unpack_limit},
|
||||
{0}
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int luaopen_create(lua_State *L) {
|
||||
int i;
|
||||
/* Manually construct our module table instead of
|
||||
* relying on _register or _newlib */
|
||||
lua_newtable(L);
|
||||
LUALIB_API int luaopen_cmsgpack (lua_State *L) {
|
||||
luaL_register(L, "cmsgpack", thislib);
|
||||
|
||||
for (i = 0; i < (sizeof(cmds)/sizeof(*cmds) - 1); i++) {
|
||||
lua_pushcfunction(L, cmds[i].func);
|
||||
lua_setfield(L, -2, cmds[i].name);
|
||||
}
|
||||
|
||||
/* Add metadata */
|
||||
lua_pushliteral(L, LUACMSGPACK_NAME);
|
||||
lua_setfield(L, -2, "_NAME");
|
||||
lua_pushliteral(L, LUACMSGPACK_VERSION);
|
||||
lua_setfield(L, -2, "_VERSION");
|
||||
lua_pushliteral(L, LUACMSGPACK_COPYRIGHT);
|
||||
lua_setfield(L, -2, "_COPYRIGHT");
|
||||
lua_pushliteral(L, LUACMSGPACK_DESCRIPTION);
|
||||
lua_setfield(L, -2, "_DESCRIPTION");
|
||||
return 1;
|
||||
}
|
||||
|
||||
LUALIB_API int luaopen_cmsgpack(lua_State *L) {
|
||||
luaopen_create(L);
|
||||
|
||||
#if LUA_VERSION_NUM < 502
|
||||
/* Register name globally for 5.1 */
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setglobal(L, LUACMSGPACK_NAME);
|
||||
#endif
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
LUALIB_API int luaopen_cmsgpack_safe(lua_State *L) {
|
||||
int i;
|
||||
|
||||
luaopen_cmsgpack(L);
|
||||
|
||||
/* Wrap all functions in the safe handler */
|
||||
for (i = 0; i < (sizeof(cmds)/sizeof(*cmds) - 1); i++) {
|
||||
lua_getfield(L, -1, cmds[i].name);
|
||||
lua_pushcclosure(L, mp_safe, 1);
|
||||
lua_setfield(L, -2, cmds[i].name);
|
||||
}
|
||||
|
||||
#if LUA_VERSION_NUM < 502
|
||||
/* Register name globally for 5.1 */
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setglobal(L, LUACMSGPACK_SAFE_NAME);
|
||||
#endif
|
||||
|
||||
lua_setfield(L, -2, "_DESCRIPTION");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+4
-6
@@ -89,14 +89,12 @@ typedef struct Header {
|
||||
} Header;
|
||||
|
||||
|
||||
static int getnum (lua_State *L, const char **fmt, int df) {
|
||||
static int getnum (const char **fmt, int df) {
|
||||
if (!isdigit(**fmt)) /* no number? */
|
||||
return df; /* return default value */
|
||||
else {
|
||||
int a = 0;
|
||||
do {
|
||||
if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0')))
|
||||
luaL_error(L, "integral size overflow");
|
||||
a = a*10 + *((*fmt)++) - '0';
|
||||
} while (isdigit(**fmt));
|
||||
return a;
|
||||
@@ -117,9 +115,9 @@ static size_t optsize (lua_State *L, char opt, const char **fmt) {
|
||||
case 'f': return sizeof(float);
|
||||
case 'd': return sizeof(double);
|
||||
case 'x': return 1;
|
||||
case 'c': return getnum(L, fmt, 1);
|
||||
case 'c': return getnum(fmt, 1);
|
||||
case 'i': case 'I': {
|
||||
int sz = getnum(L, fmt, sizeof(int));
|
||||
int sz = getnum(fmt, sizeof(int));
|
||||
if (sz > MAXINTSIZE)
|
||||
luaL_error(L, "integral size %d is larger than limit of %d",
|
||||
sz, MAXINTSIZE);
|
||||
@@ -152,7 +150,7 @@ static void controloptions (lua_State *L, int opt, const char **fmt,
|
||||
case '>': h->endian = BIG; return;
|
||||
case '<': h->endian = LITTLE; return;
|
||||
case '!': {
|
||||
int a = getnum(L, fmt, MAXALIGN);
|
||||
int a = getnum(fmt, MAXALIGN);
|
||||
if (!isp2(a))
|
||||
luaL_error(L, "alignment %d is not a power of 2", a);
|
||||
h->align = a;
|
||||
|
||||
Vendored
+3
-3
@@ -1,6 +1,6 @@
|
||||
/* strbuf - String buffer routines
|
||||
/* strbuf - string buffer routines
|
||||
*
|
||||
* Copyright (c) 2010-2012 Mark Pulford <mark@kyne.com.au>
|
||||
* Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
#include "strbuf.h"
|
||||
|
||||
static void die(const char *fmt, ...)
|
||||
void die(const char *fmt, ...)
|
||||
{
|
||||
va_list arg;
|
||||
|
||||
|
||||
Vendored
+2
-14
@@ -1,6 +1,6 @@
|
||||
/* strbuf - String buffer routines
|
||||
*
|
||||
* Copyright (c) 2010-2012 Mark Pulford <mark@kyne.com.au>
|
||||
* Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
@@ -62,9 +62,7 @@ extern void strbuf_resize(strbuf_t *s, int len);
|
||||
static int strbuf_empty_length(strbuf_t *s);
|
||||
static int strbuf_length(strbuf_t *s);
|
||||
static char *strbuf_string(strbuf_t *s, int *len);
|
||||
static void strbuf_ensure_empty_length(strbuf_t *s, int len);
|
||||
static char *strbuf_empty_ptr(strbuf_t *s);
|
||||
static void strbuf_extend_length(strbuf_t *s, int len);
|
||||
static void strbuf_ensure_empty_length(strbuf_t *s, int len);
|
||||
|
||||
/* Update */
|
||||
extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...);
|
||||
@@ -98,16 +96,6 @@ static inline void strbuf_ensure_empty_length(strbuf_t *s, int len)
|
||||
strbuf_resize(s, s->length + len);
|
||||
}
|
||||
|
||||
static inline char *strbuf_empty_ptr(strbuf_t *s)
|
||||
{
|
||||
return s->buf + s->length;
|
||||
}
|
||||
|
||||
static inline void strbuf_extend_length(strbuf_t *s, int len)
|
||||
{
|
||||
s->length += len;
|
||||
}
|
||||
|
||||
static inline int strbuf_length(strbuf_t *s)
|
||||
{
|
||||
return s->length;
|
||||
|
||||
+4
-19
@@ -1,9 +1,4 @@
|
||||
# Redis configuration file example.
|
||||
#
|
||||
# Note that in order to read the configuration file, Redis must be
|
||||
# started with the file path as first argument:
|
||||
#
|
||||
# ./redis-server /path/to/redis.conf
|
||||
# Redis configuration file example
|
||||
|
||||
# Note on units: when memory size is needed, it is possible to specify
|
||||
# it in the usual form of 1k 5GB 4M and so forth:
|
||||
@@ -247,10 +242,6 @@ slave-read-only yes
|
||||
|
||||
# Replication SYNC strategy: disk or socket.
|
||||
#
|
||||
# -------------------------------------------------------
|
||||
# WARNING: DISKLESS REPLICATION IS EXPERIMENTAL CURRENTLY
|
||||
# -------------------------------------------------------
|
||||
#
|
||||
# New slaves and reconnecting slaves that are not able to continue the replication
|
||||
# process just receiving differences, need to do what is called a "full
|
||||
# synchronization". An RDB file is transmitted from the master to the slaves.
|
||||
@@ -277,7 +268,7 @@ slave-read-only yes
|
||||
repl-diskless-sync no
|
||||
|
||||
# When diskless replication is enabled, it is possible to configure the delay
|
||||
# the server waits in order to spawn the child that transfers the RDB via socket
|
||||
# the server waits in order to spawn the child that trnasfers the RDB via socket
|
||||
# to the slaves.
|
||||
#
|
||||
# This is important since once the transfer starts, it is not possible to serve
|
||||
@@ -624,12 +615,6 @@ lua-time-limit 5000
|
||||
|
||||
################################ REDIS CLUSTER ###############################
|
||||
#
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# WARNING EXPERIMENTAL: Redis Cluster is considered to be stable code, however
|
||||
# in order to mark it as "mature" we need to wait for a non trivial percentage
|
||||
# of users to deploy it in production.
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
#
|
||||
# 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:
|
||||
@@ -771,11 +756,11 @@ slowlog-max-len 128
|
||||
# By default latency monitoring is disabled since it is mostly not needed
|
||||
# if you don't have latency issues, and collecting data has a performance
|
||||
# impact, that while very small, can be measured under big load. Latency
|
||||
# monitoring can easily be enabled at runtime using the command
|
||||
# monitoring can easily be enalbed at runtime using the command
|
||||
# "CONFIG SET latency-monitor-threshold <milliseconds>" if needed.
|
||||
latency-monitor-threshold 0
|
||||
|
||||
############################# EVENT NOTIFICATION ##############################
|
||||
############################# Event notification ##############################
|
||||
|
||||
# Redis can notify Pub/Sub clients about events happening in the key space.
|
||||
# This feature is documented at http://redis.io/topics/notifications
|
||||
|
||||
+1
-2
@@ -58,7 +58,7 @@ ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
INSTALL=cp -pf
|
||||
FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lresolv -lpthread -lrt
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lresolv -lpthread
|
||||
else
|
||||
ifeq ($(uname_S),Darwin)
|
||||
# Darwin (nothing to do)
|
||||
@@ -251,4 +251,3 @@ install: all
|
||||
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_DUMP_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN)
|
||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
|
||||
|
||||
+5
-23
@@ -264,7 +264,6 @@ static int anetCreateSocket(char *err, int domain) {
|
||||
|
||||
#define ANET_CONNECT_NONE 0
|
||||
#define ANET_CONNECT_NONBLOCK 1
|
||||
#define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */
|
||||
static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
char *source_addr, int flags)
|
||||
{
|
||||
@@ -296,7 +295,7 @@ static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
if ((rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)) != 0)
|
||||
{
|
||||
anetSetError(err, "%s", gai_strerror(rv));
|
||||
goto error;
|
||||
goto end;
|
||||
}
|
||||
for (b = bservinfo; b != NULL; b = b->ai_next) {
|
||||
if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
|
||||
@@ -307,7 +306,7 @@ static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
freeaddrinfo(bservinfo);
|
||||
if (!bound) {
|
||||
anetSetError(err, "bind: %s", strerror(errno));
|
||||
goto error;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
|
||||
@@ -332,17 +331,9 @@ error:
|
||||
close(s);
|
||||
s = ANET_ERR;
|
||||
}
|
||||
|
||||
end:
|
||||
freeaddrinfo(servinfo);
|
||||
|
||||
/* Handle best effort binding: if a binding address was used, but it is
|
||||
* not possible to create a socket, try again without a binding address. */
|
||||
if (s == ANET_ERR && source_addr && (flags & ANET_CONNECT_BE_BINDING)) {
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,flags);
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port)
|
||||
@@ -355,18 +346,9 @@ 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)
|
||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,
|
||||
ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port,
|
||||
char *source_addr)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,
|
||||
ANET_CONNECT_NONBLOCK|ANET_CONNECT_BE_BINDING);
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetUnixGenericConnect(char *err, char *path, int flags)
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
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 anetTcpNonBlockBestEffortBindConnect(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);
|
||||
|
||||
@@ -1105,7 +1105,6 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
di = NULL;
|
||||
}
|
||||
|
||||
/* Do an initial slow fsync here while the parent is still sending
|
||||
@@ -1169,9 +1168,9 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
return REDIS_OK;
|
||||
|
||||
werr:
|
||||
redisLog(REDIS_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
||||
fclose(fp);
|
||||
unlink(tmpfile);
|
||||
redisLog(REDIS_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
||||
if (di) dictReleaseIterator(di);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
+9
-21
@@ -70,19 +70,16 @@ size_t redisPopcount(void *s, long count) {
|
||||
count--;
|
||||
}
|
||||
|
||||
/* Count bits 28 bytes at a time */
|
||||
/* Count bits 16 bytes at a time */
|
||||
p4 = (uint32_t*)p;
|
||||
while(count>=28) {
|
||||
uint32_t aux1, aux2, aux3, aux4, aux5, aux6, aux7;
|
||||
while(count>=16) {
|
||||
uint32_t aux1, aux2, aux3, aux4;
|
||||
|
||||
aux1 = *p4++;
|
||||
aux2 = *p4++;
|
||||
aux3 = *p4++;
|
||||
aux4 = *p4++;
|
||||
aux5 = *p4++;
|
||||
aux6 = *p4++;
|
||||
aux7 = *p4++;
|
||||
count -= 28;
|
||||
count -= 16;
|
||||
|
||||
aux1 = aux1 - ((aux1 >> 1) & 0x55555555);
|
||||
aux1 = (aux1 & 0x33333333) + ((aux1 >> 2) & 0x33333333);
|
||||
@@ -92,19 +89,10 @@ size_t redisPopcount(void *s, long count) {
|
||||
aux3 = (aux3 & 0x33333333) + ((aux3 >> 2) & 0x33333333);
|
||||
aux4 = aux4 - ((aux4 >> 1) & 0x55555555);
|
||||
aux4 = (aux4 & 0x33333333) + ((aux4 >> 2) & 0x33333333);
|
||||
aux5 = aux5 - ((aux5 >> 1) & 0x55555555);
|
||||
aux5 = (aux5 & 0x33333333) + ((aux5 >> 2) & 0x33333333);
|
||||
aux6 = aux6 - ((aux6 >> 1) & 0x55555555);
|
||||
aux6 = (aux6 & 0x33333333) + ((aux6 >> 2) & 0x33333333);
|
||||
aux7 = aux7 - ((aux7 >> 1) & 0x55555555);
|
||||
aux7 = (aux7 & 0x33333333) + ((aux7 >> 2) & 0x33333333);
|
||||
bits += ((((aux1 + (aux1 >> 4)) & 0x0F0F0F0F) +
|
||||
((aux2 + (aux2 >> 4)) & 0x0F0F0F0F) +
|
||||
((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) +
|
||||
((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) +
|
||||
((aux5 + (aux5 >> 4)) & 0x0F0F0F0F) +
|
||||
((aux6 + (aux6 >> 4)) & 0x0F0F0F0F) +
|
||||
((aux7 + (aux7 >> 4)) & 0x0F0F0F0F))* 0x01010101) >> 24;
|
||||
bits += ((((aux1 + (aux1 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +
|
||||
((((aux2 + (aux2 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +
|
||||
((((aux3 + (aux3 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24) +
|
||||
((((aux4 + (aux4 >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24);
|
||||
}
|
||||
/* Count the remaining bytes. */
|
||||
p = (unsigned char*)p4;
|
||||
@@ -360,7 +348,7 @@ void bitopCommand(redisClient *c) {
|
||||
* can take a fast path that performs much better than the
|
||||
* vanilla algorithm. */
|
||||
j = 0;
|
||||
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
|
||||
if (minlen && numkeys <= 16) {
|
||||
unsigned long *lp[16];
|
||||
unsigned long *lres = (unsigned long*) res;
|
||||
|
||||
|
||||
+1
-26
@@ -59,8 +59,6 @@
|
||||
* 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.
|
||||
* If the blocking operation waits for certain keys to change state, the
|
||||
* clusterRedirectBlockedClientIfNeeded() function should also be updated.
|
||||
*/
|
||||
|
||||
#include "redis.h"
|
||||
@@ -116,6 +114,7 @@ void processUnblockedClients(void) {
|
||||
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) {
|
||||
@@ -157,27 +156,3 @@ void replyToBlockedClientTimedOut(redisClient *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Mass-unblock clients because something changed in the instance that makes
|
||||
* blocking no longer safe. For example clients blocked in list operations
|
||||
* in an instance which turns from master to slave is unsafe, so this function
|
||||
* is called when a master turns into a slave.
|
||||
*
|
||||
* The semantics is to send an -UNBLOCKED error to the client, disconnecting
|
||||
* it at the same time. */
|
||||
void disconnectAllBlockedClients(void) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
listRewind(server.clients,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *c = listNodeValue(ln);
|
||||
|
||||
if (c->flags & REDIS_BLOCKED) {
|
||||
addReplySds(c,sdsnew(
|
||||
"-UNBLOCKED force unblock from blocking operation, "
|
||||
"instance state changed (master -> slave?)\r\n"));
|
||||
unblockClient(c);
|
||||
c->flags |= REDIS_CLOSE_AFTER_REPLY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+299
-738
File diff suppressed because it is too large
Load Diff
+9
-23
@@ -23,16 +23,13 @@
|
||||
#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. */
|
||||
#define REDIS_CLUSTER_SLAVE_MIGRATION_DELAY 5000 /* Delay for slave migration */
|
||||
|
||||
/* Redirection errors returned by getNodeByQuery(). */
|
||||
#define REDIS_CLUSTER_REDIR_NONE 0 /* Node can serve the request. */
|
||||
#define REDIS_CLUSTER_REDIR_CROSS_SLOT 1 /* -CROSSSLOT request. */
|
||||
#define REDIS_CLUSTER_REDIR_UNSTABLE 2 /* -TRYAGAIN redirection required */
|
||||
#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. */
|
||||
#define REDIS_CLUSTER_REDIR_DOWN_STATE 5 /* -CLUSTERDOWN, global state. */
|
||||
#define REDIS_CLUSTER_REDIR_DOWN_UNBOUND 6 /* -CLUSTERDOWN, unbound slot. */
|
||||
|
||||
struct clusterNode;
|
||||
|
||||
@@ -54,7 +51,7 @@ typedef struct clusterLink {
|
||||
#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_MIGRATE_TO 256 /* Master elegible for replica migration. */
|
||||
#define REDIS_NODE_PROMOTED 256 /* Master was a slave promoted 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)
|
||||
@@ -88,16 +85,12 @@ typedef struct clusterNode {
|
||||
int numslots; /* Number of slots handled by this node */
|
||||
int numslaves; /* Number of slave nodes, if this is a master */
|
||||
struct clusterNode **slaves; /* pointers to slave nodes */
|
||||
struct clusterNode *slaveof; /* pointer to the master node. Note that it
|
||||
may be NULL even if the node is a slave
|
||||
if we don't have the master node in our
|
||||
tables. */
|
||||
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 */
|
||||
mstime_t orphaned_time; /* Starting time of orphaned master condition */
|
||||
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 */
|
||||
@@ -170,11 +163,10 @@ 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; /* node->flags copy */
|
||||
uint16_t notused1; /* Some room for future improvements. */
|
||||
uint32_t notused2;
|
||||
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 {
|
||||
@@ -184,10 +176,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
uint32_t channel_len;
|
||||
uint32_t message_len;
|
||||
/* We can't reclare bulk_data as bulk_data[] since this structure is
|
||||
* nested. The 8 bytes are removed from the count during the message
|
||||
* length computation. */
|
||||
unsigned char bulk_data[8];
|
||||
unsigned char bulk_data[8]; /* defined as 8 just for alignment concerns. */
|
||||
} clusterMsgDataPublish;
|
||||
|
||||
typedef struct {
|
||||
@@ -219,7 +208,6 @@ union clusterMsgData {
|
||||
} update;
|
||||
};
|
||||
|
||||
#define CLUSTER_PROTO_VER 0 /* Cluster bus protocol version. */
|
||||
|
||||
typedef struct {
|
||||
char sig[4]; /* Siganture "RCmb" (Redis Cluster message bus). */
|
||||
@@ -255,7 +243,5 @@ typedef struct {
|
||||
|
||||
/* ---------------------- API exported outside cluster.c -------------------- */
|
||||
clusterNode *getNodeByQuery(redisClient *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *ask);
|
||||
int clusterRedirectBlockedClientIfNeeded(redisClient *c);
|
||||
void clusterRedirectClient(redisClient *c, clusterNode *n, int hashslot, int error_code);
|
||||
|
||||
#endif /* __REDIS_CLUSTER_H */
|
||||
|
||||
+19
-12
@@ -226,6 +226,11 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) {
|
||||
server.maxmemory = memtoll(argv[1],NULL);
|
||||
server.maxmemory_enforced = (double) server.maxmemory / server.maxmemory_frag_guess;
|
||||
} else if (!strcasecmp(argv[0],"rss-aware-maxmemory") && argc==2) {
|
||||
if ((server.rss_aware_maxmemory = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"maxmemory-policy") && argc == 2) {
|
||||
if (!strcasecmp(argv[1],"volatile-lru")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
|
||||
@@ -609,7 +614,6 @@ void loadServerConfig(char *filename, char *options) {
|
||||
void configSetCommand(redisClient *c) {
|
||||
robj *o;
|
||||
long long ll;
|
||||
int err;
|
||||
redisAssertWithInfo(c,c->argv[2],sdsEncodedObject(c->argv[2]));
|
||||
redisAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3]));
|
||||
o = c->argv[3];
|
||||
@@ -629,15 +633,21 @@ void configSetCommand(redisClient *c) {
|
||||
zfree(server.masterauth);
|
||||
server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) {
|
||||
ll = memtoll(o->ptr,&err);
|
||||
if (err || ll < 0) goto badfmt;
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.maxmemory = ll;
|
||||
server.maxmemory_enforced = (double) server.maxmemory / server.maxmemory_frag_guess;
|
||||
if (server.maxmemory) {
|
||||
if (server.maxmemory < zmalloc_used_memory()) {
|
||||
redisLog(REDIS_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in keys eviction and/or inability to accept new write commands depending on the maxmemory-policy.");
|
||||
}
|
||||
freeMemoryIfNeeded();
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"rss-aware-maxmemory")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.rss_aware_maxmemory = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxclients")) {
|
||||
int orig_value = server.maxclients;
|
||||
|
||||
@@ -785,11 +795,6 @@ void configSetCommand(redisClient *c) {
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.repl_slave_ro = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"activerehashing")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.activerehashing = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"dir")) {
|
||||
if (chdir((char*)o->ptr) == -1) {
|
||||
addReplyErrorFormat(c,"Changing directory: %s", strerror(errno));
|
||||
@@ -857,6 +862,7 @@ void configSetCommand(redisClient *c) {
|
||||
* whole configuration string or accept it all, even if a single
|
||||
* error in a single client class is present. */
|
||||
for (j = 0; j < vlen; j++) {
|
||||
char *eptr;
|
||||
long val;
|
||||
|
||||
if ((j % 4) == 0) {
|
||||
@@ -865,8 +871,8 @@ void configSetCommand(redisClient *c) {
|
||||
goto badfmt;
|
||||
}
|
||||
} else {
|
||||
val = memtoll(v[j], &err);
|
||||
if (err || val < 0) {
|
||||
val = strtoll(v[j], &eptr, 10);
|
||||
if (eptr[0] != '\0' || val < 0) {
|
||||
sdsfreesplitres(v,vlen);
|
||||
goto badfmt;
|
||||
}
|
||||
@@ -900,8 +906,7 @@ void configSetCommand(redisClient *c) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
|
||||
server.repl_timeout = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-size")) {
|
||||
ll = memtoll(o->ptr,&err);
|
||||
if (err || ll < 0) goto badfmt;
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
|
||||
resizeReplicationBacklog(ll);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-ttl")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
@@ -1099,6 +1104,8 @@ void configGetCommand(redisClient *c) {
|
||||
server.aof_rewrite_incremental_fsync);
|
||||
config_get_bool_field("aof-load-truncated",
|
||||
server.aof_load_truncated);
|
||||
config_get_bool_field("rss-aware-maxmemory",
|
||||
server.rss_aware_maxmemory);
|
||||
|
||||
/* Everything we can't handle with macros follows. */
|
||||
|
||||
|
||||
+6
-8
@@ -34,11 +34,6 @@
|
||||
#include <AvailabilityMacros.h>
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/version.h>
|
||||
#include <features.h>
|
||||
#endif
|
||||
|
||||
/* Define redis_fstat to fstat or fstat64() */
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
|
||||
#define redis_fstat fstat64
|
||||
@@ -53,16 +48,17 @@
|
||||
#define HAVE_PROC_STAT 1
|
||||
#define HAVE_PROC_MAPS 1
|
||||
#define HAVE_PROC_SMAPS 1
|
||||
#define HAVE_PROC_SOMAXCONN 1
|
||||
#define HAVE_RSS_REPORTING 1
|
||||
#endif
|
||||
|
||||
/* Test for task_info() */
|
||||
#if defined(__APPLE__)
|
||||
#define HAVE_TASKINFO 1
|
||||
#define HAVE_RSS_REPORTING 1
|
||||
#endif
|
||||
|
||||
/* Test for backtrace() */
|
||||
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__))
|
||||
#if defined(__APPLE__) || defined(__linux__)
|
||||
#define HAVE_BACKTRACE 1
|
||||
#endif
|
||||
|
||||
@@ -92,6 +88,8 @@
|
||||
/* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use
|
||||
* the plain fsync() call. */
|
||||
#ifdef __linux__
|
||||
#include <linux/version.h>
|
||||
#include <features.h>
|
||||
#if defined(__GLIBC__) && defined(__GLIBC_PREREQ)
|
||||
#if (LINUX_VERSION_CODE >= 0x020611 && __GLIBC_PREREQ(2, 6))
|
||||
#define HAVE_SYNC_FILE_RANGE 1
|
||||
@@ -116,7 +114,7 @@
|
||||
#define USE_SETPROCTITLE
|
||||
#endif
|
||||
|
||||
#if ((defined __linux && defined(__GLIBC__)) || defined __APPLE__)
|
||||
#if (defined __linux || defined __APPLE__)
|
||||
#define USE_SETPROCTITLE
|
||||
#define INIT_SETPROCTITLE_REPLACEMENT
|
||||
void spt_init(int argc, char *argv[]);
|
||||
|
||||
@@ -293,17 +293,13 @@ void delCommand(redisClient *c) {
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
|
||||
/* EXISTS key1 key2 ... key_N.
|
||||
* Return value is the number of keys existing. */
|
||||
void existsCommand(redisClient *c) {
|
||||
long long count = 0;
|
||||
int j;
|
||||
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
expireIfNeeded(c->db,c->argv[j]);
|
||||
if (dbExists(c->db,c->argv[j])) count++;
|
||||
expireIfNeeded(c->db,c->argv[1]);
|
||||
if (dbExists(c->db,c->argv[1])) {
|
||||
addReply(c, shared.cone);
|
||||
} else {
|
||||
addReply(c, shared.czero);
|
||||
}
|
||||
addReplyLongLong(c,count);
|
||||
}
|
||||
|
||||
void selectCommand(redisClient *c) {
|
||||
@@ -385,7 +381,7 @@ void scanCallback(void *privdata, const dictEntry *de) {
|
||||
} else if (o->type == REDIS_ZSET) {
|
||||
key = dictGetKey(de);
|
||||
incrRefCount(key);
|
||||
val = createStringObjectFromLongDouble(*(double*)dictGetVal(de),0);
|
||||
val = createStringObjectFromLongDouble(*(double*)dictGetVal(de));
|
||||
} else {
|
||||
redisPanic("Type not handled in SCAN callback.");
|
||||
}
|
||||
@@ -714,7 +710,7 @@ void moveCommand(redisClient *c) {
|
||||
robj *o;
|
||||
redisDb *src, *dst;
|
||||
int srcid;
|
||||
long long dbid, expire;
|
||||
long long dbid;
|
||||
|
||||
if (server.cluster_enabled) {
|
||||
addReplyError(c,"MOVE is not allowed in cluster mode");
|
||||
@@ -748,7 +744,6 @@ void moveCommand(redisClient *c) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
expire = getExpire(c->db,c->argv[1]);
|
||||
|
||||
/* Return zero if the key already exists in the target DB */
|
||||
if (lookupKeyWrite(dst,c->argv[1]) != NULL) {
|
||||
@@ -756,7 +751,6 @@ void moveCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
dbAdd(dst,c->argv[1],o);
|
||||
if (expire != -1) setExpire(dst,c->argv[1],expire);
|
||||
incrRefCount(o);
|
||||
|
||||
/* OK! key moved, free the entry in the source DB */
|
||||
@@ -1131,33 +1125,6 @@ int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
return keys;
|
||||
}
|
||||
|
||||
int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, num, first, *keys;
|
||||
REDIS_NOTUSED(cmd);
|
||||
|
||||
/* Assume the obvious form. */
|
||||
first = 3;
|
||||
num = 1;
|
||||
|
||||
/* But check for the extended one with the KEYS option. */
|
||||
if (argc > 6) {
|
||||
for (i = 6; i < argc; i++) {
|
||||
if (!strcasecmp(argv[i]->ptr,"keys") &&
|
||||
sdslen(argv[3]->ptr) == 0)
|
||||
{
|
||||
first = i+1;
|
||||
num = argc-first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
for (i = 0; i < num; i++) keys[i] = first+i;
|
||||
*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. */
|
||||
|
||||
+2
-6
@@ -301,7 +301,7 @@ void debugCommand(redisClient *c) {
|
||||
"lru:%d lru_seconds_idle:%llu",
|
||||
(void*)val, val->refcount,
|
||||
strenc, (long long) rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val)/1000);
|
||||
val->lru, estimateObjectIdleTime(val));
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) {
|
||||
dictEntry *de;
|
||||
robj *val;
|
||||
@@ -415,7 +415,7 @@ void _redisAssertPrintClientInfo(redisClient *c) {
|
||||
if (c->argv[j]->type == REDIS_STRING && sdsEncodedObject(c->argv[j])) {
|
||||
arg = (char*) c->argv[j]->ptr;
|
||||
} else {
|
||||
snprintf(buf,sizeof(buf),"Object type: %u, encoding: %u",
|
||||
snprintf(buf,sizeof(buf),"Object type: %d, encoding: %d",
|
||||
c->argv[j]->type, c->argv[j]->encoding);
|
||||
arg = buf;
|
||||
}
|
||||
@@ -811,10 +811,6 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
bugReportStart();
|
||||
redisLog(REDIS_WARNING,
|
||||
" Redis %s crashed by signal: %d", REDIS_VERSION, sig);
|
||||
if (sig == SIGSEGV) {
|
||||
redisLog(REDIS_WARNING,
|
||||
" SIGSEGV caused by address: %p", (void*)info->si_addr);
|
||||
}
|
||||
redisLog(REDIS_WARNING,
|
||||
" Failed assertion: %s (%s:%d)", server.assert_failed,
|
||||
server.assert_file, server.assert_line);
|
||||
|
||||
+35
-90
@@ -211,9 +211,6 @@ int dictExpand(dict *d, unsigned long size)
|
||||
if (dictIsRehashing(d) || d->ht[0].used > size)
|
||||
return DICT_ERR;
|
||||
|
||||
/* Rehashing to the same table size is not useful. */
|
||||
if (realsize == d->ht[0].size) return DICT_ERR;
|
||||
|
||||
/* Allocate the new hash table and initialize all pointers to NULL */
|
||||
n.size = realsize;
|
||||
n.sizemask = realsize-1;
|
||||
@@ -235,27 +232,27 @@ 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, however
|
||||
* since part of the hash table may be composed of empty spaces, it is not
|
||||
* guaranteed that this function will rehash even a single bucket, since it
|
||||
* will visit at max N*10 empty buckets in total, otherwise the amount of
|
||||
* work it does would be unbound and the function may block for a long time. */
|
||||
* than one key as we use chaining) from the old to the new hash table. */
|
||||
int dictRehash(dict *d, int n) {
|
||||
int empty_visits = n*10; /* Max number of empty buckets to visit. */
|
||||
if (!dictIsRehashing(d)) return 0;
|
||||
|
||||
while(n-- && d->ht[0].used != 0) {
|
||||
while(n--) {
|
||||
dictEntry *de, *nextde;
|
||||
|
||||
/* Check if we already rehashed the whole table... */
|
||||
if (d->ht[0].used == 0) {
|
||||
zfree(d->ht[0].table);
|
||||
d->ht[0] = d->ht[1];
|
||||
_dictReset(&d->ht[1]);
|
||||
d->rehashidx = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Note that rehashidx can't overflow as we are sure there are more
|
||||
* elements because ht[0].used != 0 */
|
||||
assert(d->ht[0].size > (unsigned long)d->rehashidx);
|
||||
while(d->ht[0].table[d->rehashidx] == NULL) {
|
||||
d->rehashidx++;
|
||||
if (--empty_visits == 0) return 1;
|
||||
}
|
||||
while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
|
||||
de = d->ht[0].table[d->rehashidx];
|
||||
/* Move all the keys in this bucket from the old to the new hash HT */
|
||||
while(de) {
|
||||
@@ -273,17 +270,6 @@ int dictRehash(dict *d, int n) {
|
||||
d->ht[0].table[d->rehashidx] = NULL;
|
||||
d->rehashidx++;
|
||||
}
|
||||
|
||||
/* Check if we already rehashed the whole table... */
|
||||
if (d->ht[0].used == 0) {
|
||||
zfree(d->ht[0].table);
|
||||
d->ht[0] = d->ht[1];
|
||||
_dictReset(&d->ht[1]);
|
||||
d->rehashidx = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* More to rehash... */
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -630,11 +616,7 @@ dictEntry *dictGetRandomKey(dict *d)
|
||||
if (dictIsRehashing(d)) _dictRehashStep(d);
|
||||
if (dictIsRehashing(d)) {
|
||||
do {
|
||||
/* We are sure there are no elements in indexes from 0
|
||||
* to rehashidx-1 */
|
||||
h = d->rehashidx + (random() % (d->ht[0].size +
|
||||
d->ht[1].size -
|
||||
d->rehashidx));
|
||||
h = random() % (d->ht[0].size+d->ht[1].size);
|
||||
he = (h >= d->ht[0].size) ? d->ht[1].table[h - d->ht[0].size] :
|
||||
d->ht[0].table[h];
|
||||
} while(he == NULL);
|
||||
@@ -661,12 +643,9 @@ dictEntry *dictGetRandomKey(dict *d)
|
||||
return he;
|
||||
}
|
||||
|
||||
/* This function samples the dictionary to return a few keys from random
|
||||
* locations.
|
||||
*
|
||||
* It does not guarantee to return all the keys specified in 'count', nor
|
||||
* it does guarantee to return non-duplicated elements, however it will make
|
||||
* some effort to do both things.
|
||||
/* 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
|
||||
@@ -675,65 +654,28 @@ dictEntry *dictGetRandomKey(dict *d)
|
||||
*
|
||||
* 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, or if not enough elements were found in a reasonable amount of
|
||||
* steps.
|
||||
* 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. */
|
||||
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
unsigned int j; /* internal hash table id, 0 or 1. */
|
||||
unsigned int tables; /* 1 or 2 tables? */
|
||||
unsigned int stored = 0, maxsizemask;
|
||||
unsigned int maxsteps;
|
||||
* at producing N elements, and the elements are guaranteed to be non
|
||||
* repeating. */
|
||||
unsigned int dictGetRandomKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
int j; /* internal hash table id, 0 or 1. */
|
||||
unsigned int stored = 0;
|
||||
|
||||
if (dictSize(d) < count) count = dictSize(d);
|
||||
maxsteps = count*10;
|
||||
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;
|
||||
|
||||
/* Try to do a rehashing work proportional to 'count'. */
|
||||
for (j = 0; j < count; j++) {
|
||||
if (dictIsRehashing(d))
|
||||
_dictRehashStep(d);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
tables = dictIsRehashing(d) ? 2 : 1;
|
||||
maxsizemask = d->ht[0].sizemask;
|
||||
if (tables > 1 && maxsizemask < d->ht[1].sizemask)
|
||||
maxsizemask = d->ht[1].sizemask;
|
||||
|
||||
/* Pick a random point inside the larger table. */
|
||||
unsigned int i = random() & maxsizemask;
|
||||
unsigned int emptylen = 0; /* Continuous empty entries so far. */
|
||||
while(stored < count && maxsteps--) {
|
||||
for (j = 0; j < tables; j++) {
|
||||
/* Invariant of the dict.c rehashing: up to the indexes already
|
||||
* visited in ht[0] during the rehashing, there are no populated
|
||||
* buckets, so we can skip ht[0] for indexes between 0 and idx-1. */
|
||||
if (tables == 2 && j == 0 && i < (unsigned int) d->rehashidx) {
|
||||
/* Moreover, if we are currently out of range in the second
|
||||
* table, there will be no elements in both tables up to
|
||||
* the current rehashing index, so we jump if possible.
|
||||
* (this happens when going from big to small table). */
|
||||
if (i >= d->ht[1].size) i = d->rehashidx;
|
||||
continue;
|
||||
}
|
||||
if (i >= d->ht[j].size) continue; /* Out of range for this table. */
|
||||
dictEntry *he = d->ht[j].table[i];
|
||||
|
||||
/* Count contiguous empty buckets, and jump to other
|
||||
* locations if they reach 'count' (with a minimum of 5). */
|
||||
if (he == NULL) {
|
||||
emptylen++;
|
||||
if (emptylen >= 5 && emptylen > count) {
|
||||
i = random() & maxsizemask;
|
||||
emptylen = 0;
|
||||
}
|
||||
} else {
|
||||
emptylen = 0;
|
||||
/* 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. */
|
||||
@@ -743,11 +685,14 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
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);
|
||||
}
|
||||
i = (i+1) & maxsizemask;
|
||||
}
|
||||
return stored;
|
||||
return stored; /* Never reached. */
|
||||
}
|
||||
|
||||
/* Function to reverse bits. Algorithm from:
|
||||
|
||||
+1
-1
@@ -164,7 +164,7 @@ dictIterator *dictGetSafeIterator(dict *d);
|
||||
dictEntry *dictNext(dictIterator *iter);
|
||||
void dictReleaseIterator(dictIterator *iter);
|
||||
dictEntry *dictGetRandomKey(dict *d);
|
||||
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
|
||||
unsigned int dictGetRandomKeys(dict *d, dictEntry **des, unsigned int count);
|
||||
void dictPrintStats(dict *d);
|
||||
unsigned int dictGenHashFunction(const void *key, int len);
|
||||
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
|
||||
#if defined(__linux__)
|
||||
#define _GNU_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(_AIX)
|
||||
|
||||
+8
-180
@@ -1,4 +1,4 @@
|
||||
/* Automatically generated by generate-command-help.rb, do not edit. */
|
||||
/* Automatically generated by utils/generate-command-help.rb, do not edit. */
|
||||
|
||||
#ifndef __REDIS_HELP_H
|
||||
#define __REDIS_HELP_H
|
||||
@@ -15,9 +15,7 @@ static char *commandGroups[] = {
|
||||
"connection",
|
||||
"server",
|
||||
"scripting",
|
||||
"hyperloglog",
|
||||
"cluster",
|
||||
"geo"
|
||||
"hyperloglog"
|
||||
};
|
||||
|
||||
struct commandHelp {
|
||||
@@ -48,7 +46,7 @@ struct commandHelp {
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "BITCOUNT",
|
||||
"key [start end]",
|
||||
"key [start] [end]",
|
||||
"Count set bits in a string",
|
||||
1,
|
||||
"2.6.0" },
|
||||
@@ -83,7 +81,7 @@ struct commandHelp {
|
||||
9,
|
||||
"2.6.9" },
|
||||
{ "CLIENT KILL",
|
||||
"[ip:port] [ID client-id] [TYPE normal|slave|pubsub] [ADDR ip:port] [SKIPME yes/no]",
|
||||
"ip:port",
|
||||
"Kill the connection of a client",
|
||||
9,
|
||||
"2.4.0" },
|
||||
@@ -102,116 +100,6 @@ struct commandHelp {
|
||||
"Set the current connection name",
|
||||
9,
|
||||
"2.6.9" },
|
||||
{ "CLUSTER ADDSLOTS",
|
||||
"slot [slot ...]",
|
||||
"Assign new hash slots to receiving node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER COUNT-FAILURE-REPORTS",
|
||||
"node-id",
|
||||
"Return the number of failure reports active for a given node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER COUNTKEYSINSLOT",
|
||||
"slot",
|
||||
"Return the number of local keys in the specified hash slot",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER DELSLOTS",
|
||||
"slot [slot ...]",
|
||||
"Set hash slots as unbound in receiving node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER FAILOVER",
|
||||
"[FORCE|TAKEOVER]",
|
||||
"Forces a slave to perform a manual failover of its master.",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER FORGET",
|
||||
"node-id",
|
||||
"Remove a node from the nodes table",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER GETKEYSINSLOT",
|
||||
"slot count",
|
||||
"Return local key names in the specified hash slot",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER INFO",
|
||||
"-",
|
||||
"Provides info about Redis Cluster node state",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER KEYSLOT",
|
||||
"key",
|
||||
"Returns the hash slot of the specified key",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER MEET",
|
||||
"ip port",
|
||||
"Force a node cluster to handshake with another node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER NODES",
|
||||
"-",
|
||||
"Get Cluster config for the node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER REPLICATE",
|
||||
"node-id",
|
||||
"Reconfigure a node as a slave of the specified master node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER RESET",
|
||||
"[HARD|SOFT]",
|
||||
"Reset a Redis Cluster node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER SAVECONFIG",
|
||||
"-",
|
||||
"Forces the node to save cluster state on disk",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER SET-CONFIG-EPOCH",
|
||||
"config-epoch",
|
||||
"Set the configuration epoch in a new node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER SETSLOT",
|
||||
"slot IMPORTING|MIGRATING|STABLE|NODE [node-id]",
|
||||
"Bind an hash slot to a specific node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER SLAVES",
|
||||
"node-id",
|
||||
"List slave nodes of the specified master node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER SLOTS",
|
||||
"-",
|
||||
"Get array of Cluster slot to node mappings",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "COMMAND",
|
||||
"-",
|
||||
"Get array of Redis command details",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "COMMAND COUNT",
|
||||
"-",
|
||||
"Get total number of Redis commands",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "COMMAND GETKEYS",
|
||||
"-",
|
||||
"Extract keys given a full Redis command",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "COMMAND INFO",
|
||||
"command-name [command-name ...]",
|
||||
"Get array of specific Redis command details",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "CONFIG GET",
|
||||
"parameter",
|
||||
"Get the value of a configuration parameter",
|
||||
@@ -293,7 +181,7 @@ struct commandHelp {
|
||||
7,
|
||||
"1.2.0" },
|
||||
{ "EXISTS",
|
||||
"key [key ...]",
|
||||
"key",
|
||||
"Determine if a key exists",
|
||||
0,
|
||||
"1.0.0" },
|
||||
@@ -317,36 +205,6 @@ struct commandHelp {
|
||||
"Remove all keys from the current database",
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "GEOADD",
|
||||
"key longitude latitude member [longitude latitude member ...]",
|
||||
"Add one or more geospatial items in the geospatial index represented using a sorted set",
|
||||
13,
|
||||
"" },
|
||||
{ "GEODIST",
|
||||
"key member1 member2 [unit]",
|
||||
"Returns the distance between two members of a geospatial index",
|
||||
13,
|
||||
"" },
|
||||
{ "GEOHASH",
|
||||
"key member [member ...]",
|
||||
"Returns members of a geospatial index as standard geohash strings",
|
||||
13,
|
||||
"" },
|
||||
{ "GEOPOS",
|
||||
"key member [member ...]",
|
||||
"Returns longitude and latitude of members of a geospatial index",
|
||||
13,
|
||||
"" },
|
||||
{ "GEORADIUS",
|
||||
"key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]",
|
||||
"Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point",
|
||||
13,
|
||||
"" },
|
||||
{ "GEORADIUSBYMEMBER",
|
||||
"key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]",
|
||||
"Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member",
|
||||
13,
|
||||
"" },
|
||||
{ "GET",
|
||||
"key",
|
||||
"Get the value of a key",
|
||||
@@ -432,11 +290,6 @@ struct commandHelp {
|
||||
"Set the value of a hash field, only if the field does not exist",
|
||||
5,
|
||||
"2.0.0" },
|
||||
{ "HSTRLEN",
|
||||
"key field",
|
||||
"Get the length of the value of a hash field",
|
||||
5,
|
||||
"3.2.0" },
|
||||
{ "HVALS",
|
||||
"key",
|
||||
"Get all the values in a hash",
|
||||
@@ -637,16 +490,6 @@ struct commandHelp {
|
||||
"Return a random key from the keyspace",
|
||||
0,
|
||||
"1.0.0" },
|
||||
{ "READONLY",
|
||||
"-",
|
||||
"Enables read queries for a connection to a cluster slave node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "READWRITE",
|
||||
"-",
|
||||
"Disables read queries for a connection to a cluster slave node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "RENAME",
|
||||
"key newkey",
|
||||
"Rename a key",
|
||||
@@ -658,15 +501,10 @@ struct commandHelp {
|
||||
0,
|
||||
"1.0.0" },
|
||||
{ "RESTORE",
|
||||
"key ttl serialized-value [REPLACE]",
|
||||
"key ttl serialized-value",
|
||||
"Create a key using the provided serialized value, previously obtained using DUMP.",
|
||||
0,
|
||||
"2.6.0" },
|
||||
{ "ROLE",
|
||||
"-",
|
||||
"Return the role of the instance in the context of replication",
|
||||
9,
|
||||
"2.8.12" },
|
||||
{ "RPOP",
|
||||
"key",
|
||||
"Remove and get the last element in a list",
|
||||
@@ -674,7 +512,7 @@ struct commandHelp {
|
||||
"1.0.0" },
|
||||
{ "RPOPLPUSH",
|
||||
"source destination",
|
||||
"Remove the last element in a list, prepend it to another list and return it",
|
||||
"Remove the last element in a list, append it to another list and return it",
|
||||
2,
|
||||
"1.2.0" },
|
||||
{ "RPUSH",
|
||||
@@ -882,18 +720,13 @@ struct commandHelp {
|
||||
"Forget about all watched keys",
|
||||
7,
|
||||
"2.2.0" },
|
||||
{ "WAIT",
|
||||
"numslaves timeout",
|
||||
"Wait for the synchronous replication of all the write commands sent in the context of the current connection",
|
||||
0,
|
||||
"3.0.0" },
|
||||
{ "WATCH",
|
||||
"key [key ...]",
|
||||
"Watch the given keys to determine execution of the MULTI/EXEC block",
|
||||
7,
|
||||
"2.2.0" },
|
||||
{ "ZADD",
|
||||
"key [NX|XX] [CH] [INCR] score member [score member ...]",
|
||||
"key score member [score member ...]",
|
||||
"Add one or more members to a sorted set, or update its score if it already exists",
|
||||
4,
|
||||
"1.2.0" },
|
||||
@@ -967,11 +800,6 @@ struct commandHelp {
|
||||
"Return a range of members in a sorted set, by index, with scores ordered from high to low",
|
||||
4,
|
||||
"1.2.0" },
|
||||
{ "ZREVRANGEBYLEX",
|
||||
"key max min [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by lexicographical range, ordered from higher to lower strings.",
|
||||
4,
|
||||
"2.8.9" },
|
||||
{ "ZREVRANGEBYSCORE",
|
||||
"key max min [WITHSCORES] [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by score, with scores ordered from high to low",
|
||||
|
||||
+1
-1
@@ -1213,7 +1213,7 @@ void pfcountCommand(redisClient *c) {
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
/* Check type and size. */
|
||||
robj *o = lookupKeyRead(c->db,c->argv[j]);
|
||||
if (o == NULL) continue; /* Assume empty HLL for non existing var.*/
|
||||
if (o == NULL) continue; /* Assume empty HLL for non existing var. */
|
||||
if (isHLLObjectOrReply(c,o) != REDIS_OK) return;
|
||||
|
||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
||||
|
||||
+6
-14
@@ -73,7 +73,6 @@ int THPIsEnabled(void) {
|
||||
fclose(fp);
|
||||
return (strstr(buf,"[never]") == NULL) ? 1 : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Report the amount of AnonHugePages in smap, in bytes. If the return
|
||||
* value of the function is non-zero, the process is being targeted by
|
||||
@@ -81,6 +80,7 @@ int THPIsEnabled(void) {
|
||||
int THPGetAnonHugePagesSize(void) {
|
||||
return zmalloc_get_smap_bytes_by_field("AnonHugePages:");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ---------------------------- Latency API --------------------------------- */
|
||||
|
||||
@@ -228,7 +228,6 @@ sds createLatencyReport(void) {
|
||||
int advise_write_load_info = 0; /* Print info about AOF and write load. */
|
||||
int advise_hz = 0; /* Use higher HZ. */
|
||||
int advise_large_objects = 0; /* Deletion of large objects. */
|
||||
int advise_mass_eviction = 0; /* Avoid mass eviction of keys. */
|
||||
int advise_relax_fsync_policy = 0; /* appendfsync always is slow. */
|
||||
int advise_disable_thp = 0; /* AnonHugePages detected. */
|
||||
int advices = 0;
|
||||
@@ -248,7 +247,7 @@ sds createLatencyReport(void) {
|
||||
dictEntry *de;
|
||||
int eventnum = 0;
|
||||
|
||||
di = dictGetSafeIterator(server.latency_events);
|
||||
di = dictGetIterator(server.latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = dictGetKey(de);
|
||||
struct latencyTimeSeries *ts = dictGetVal(de);
|
||||
@@ -365,13 +364,8 @@ sds createLatencyReport(void) {
|
||||
}
|
||||
|
||||
/* Eviction cycle. */
|
||||
if (!strcasecmp(event,"eviction-del")) {
|
||||
advise_large_objects = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"eviction-cycle")) {
|
||||
advise_mass_eviction = 1;
|
||||
advise_large_objects = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
@@ -458,10 +452,6 @@ sds createLatencyReport(void) {
|
||||
report = sdscat(report,"- Deleting, expiring or evicting (because of maxmemory policy) large objects is a blocking operation. If you have very large objects that are often deleted, expired, or evicted, try to fragment those objects into multiple smaller objects.\n");
|
||||
}
|
||||
|
||||
if (advise_mass_eviction) {
|
||||
report = sdscat(report,"- Sudden changes to the 'maxmemory' setting via 'CONFIG SET', or allocation of large objects via sets or sorted sets intersections, STORE option of SORT, Redis Cluster large keys migrations (RESTORE command), may create sudden memory pressure forcing the server to block trying to evict keys. \n");
|
||||
}
|
||||
|
||||
if (advise_disable_thp) {
|
||||
report = sdscat(report,"- I detected a non zero amount of anonymous huge pages used by your process. This creates very serious latency events in different conditions, especially when Redis is persisting on disk. To disable THP support use the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled', make sure to also add it into /etc/rc.local so that the command will be executed again after a reboot. Note that even if you have already disabled THP, you still need to restart the Redis process to get rid of the huge pages already created.\n");
|
||||
}
|
||||
@@ -522,6 +512,7 @@ sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
|
||||
for (j = 0; j < LATENCY_TS_LEN; j++) {
|
||||
int i = (ts->idx + j) % LATENCY_TS_LEN;
|
||||
int elapsed;
|
||||
char *label;
|
||||
char buf[64];
|
||||
|
||||
if (ts->samples[i].time == 0) continue;
|
||||
@@ -543,7 +534,8 @@ sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
|
||||
snprintf(buf,sizeof(buf),"%dh",elapsed/3600);
|
||||
else
|
||||
snprintf(buf,sizeof(buf),"%dd",elapsed/(3600*24));
|
||||
sparklineSequenceAddSample(seq,ts->samples[i].latency,buf);
|
||||
label = zstrdup(buf);
|
||||
sparklineSequenceAddSample(seq,ts->samples[i].latency,label);
|
||||
}
|
||||
|
||||
graph = sdscatprintf(graph,
|
||||
|
||||
@@ -86,8 +86,4 @@ int THPIsEnabled(void);
|
||||
(var) >= server.latency_monitor_threshold) \
|
||||
latencyAddSample((event),(var));
|
||||
|
||||
/* Remove time from a nested event. */
|
||||
#define latencyRemoveNestedEvent(event_var,nested_var) \
|
||||
event_var += nested_var;
|
||||
|
||||
#endif /* __LATENCY_H */
|
||||
|
||||
@@ -35,9 +35,6 @@
|
||||
#include <errno.h>
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
#if defined(__sun)
|
||||
#include <stropts.h>
|
||||
#endif
|
||||
#include "config.h"
|
||||
|
||||
#if (ULONG_MAX == 4294967295UL)
|
||||
|
||||
+30
-81
@@ -105,7 +105,6 @@ redisClient *createClient(int fd) {
|
||||
c->repl_ack_off = 0;
|
||||
c->repl_ack_time = 0;
|
||||
c->slave_listening_port = 0;
|
||||
c->slave_capa = SLAVE_CAPA_NONE;
|
||||
c->reply = listCreate();
|
||||
c->reply_bytes = 0;
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
@@ -136,49 +135,23 @@ redisClient *createClient(int fd) {
|
||||
* returns REDIS_OK, and make sure to install the write handler in our event
|
||||
* loop so that when the socket is writable new data gets written.
|
||||
*
|
||||
* If the client should not receive new data, because it is a fake client
|
||||
* (used to load AOF in memory), a master or because the setup of the write
|
||||
* handler failed, the function returns REDIS_ERR.
|
||||
*
|
||||
* The function may return REDIS_OK without actually installing the write
|
||||
* event handler in the following cases:
|
||||
*
|
||||
* 1) The event handler should already be installed since the output buffer
|
||||
* already contained something.
|
||||
* 2) The client is a slave but not yet online, so we want to just accumulate
|
||||
* writes in the buffer but not actually sending them yet.
|
||||
* If the client should not receive new data, because it is a fake client,
|
||||
* a master, a slave not yet online, or because the setup of the write handler
|
||||
* failed, the function returns REDIS_ERR.
|
||||
*
|
||||
* Typically gets called every time a reply is built, before adding more
|
||||
* data to the clients output buffers. If the function returns REDIS_ERR no
|
||||
* data should be appended to the output buffers. */
|
||||
int prepareClientToWrite(redisClient *c) {
|
||||
/* If it's the Lua client we always return ok without installing any
|
||||
* handler since there is no socket at all. */
|
||||
if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK;
|
||||
|
||||
/* Masters don't receive replies, unless REDIS_MASTER_FORCE_REPLY flag
|
||||
* is set. */
|
||||
if ((c->flags & REDIS_MASTER) &&
|
||||
!(c->flags & REDIS_MASTER_FORCE_REPLY)) return REDIS_ERR;
|
||||
|
||||
if (c->fd <= 0) return REDIS_ERR; /* Fake client for AOF loading. */
|
||||
|
||||
/* Only install the handler if not already installed and, in case of
|
||||
* slaves, if the client can actually receive writes. */
|
||||
if (c->fd <= 0) return REDIS_ERR; /* Fake client */
|
||||
if (c->bufpos == 0 && listLength(c->reply) == 0 &&
|
||||
(c->replstate == REDIS_REPL_NONE ||
|
||||
(c->replstate == REDIS_REPL_ONLINE && !c->repl_put_online_on_ack)))
|
||||
{
|
||||
/* Try to install the write handler. */
|
||||
if (aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
|
||||
sendReplyToClient, c) == AE_ERR)
|
||||
{
|
||||
freeClientAsync(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* Authorize the caller to queue in the output buffer of this client. */
|
||||
c->replstate == REDIS_REPL_ONLINE) &&
|
||||
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
|
||||
sendReplyToClient, c) == AE_ERR) return REDIS_ERR;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -667,6 +640,20 @@ void disconnectSlaves(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called when the slave lose the connection with the
|
||||
* master into an unexpected way. */
|
||||
void replicationHandleMasterDisconnection(void) {
|
||||
server.master = NULL;
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
server.repl_down_since = server.unixtime;
|
||||
/* We lost connection with our master, force our slaves to resync
|
||||
* with us as well to load the new data set.
|
||||
*
|
||||
* If server.masterhost is NULL the user called SLAVEOF NO ONE so
|
||||
* slave resync is not needed. */
|
||||
if (server.masterhost != NULL) disconnectSlaves();
|
||||
}
|
||||
|
||||
void freeClient(redisClient *c) {
|
||||
listNode *ln;
|
||||
|
||||
@@ -784,7 +771,7 @@ void freeClient(redisClient *c) {
|
||||
* a context where calling freeClient() is not possible, because the client
|
||||
* should be valid for the continuation of the flow of the program. */
|
||||
void freeClientAsync(redisClient *c) {
|
||||
if (c->flags & REDIS_CLOSE_ASAP || c->flags & REDIS_LUA_CLIENT) return;
|
||||
if (c->flags & REDIS_CLOSE_ASAP) return;
|
||||
c->flags |= REDIS_CLOSE_ASAP;
|
||||
listAddNodeTail(server.clients_to_close,c);
|
||||
}
|
||||
@@ -852,7 +839,6 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
*
|
||||
* However if we are over the maxmemory limit we ignore that and
|
||||
* just deliver as much data as it is possible to deliver. */
|
||||
server.stat_net_output_bytes += totwritten;
|
||||
if (totwritten > REDIS_MAX_WRITE_PER_EVENT &&
|
||||
(server.maxmemory == 0 ||
|
||||
zmalloc_used_memory() < server.maxmemory)) break;
|
||||
@@ -940,10 +926,8 @@ int processInlineBuffer(redisClient *c) {
|
||||
sdsrange(c->querybuf,querylen+2,-1);
|
||||
|
||||
/* Setup argv array on client structure */
|
||||
if (argc) {
|
||||
if (c->argv) zfree(c->argv);
|
||||
c->argv = zmalloc(sizeof(robj*)*argc);
|
||||
}
|
||||
if (c->argv) zfree(c->argv);
|
||||
c->argv = zmalloc(sizeof(robj*)*argc);
|
||||
|
||||
/* Create redis objects for all arguments. */
|
||||
for (c->argc = 0, j = 0; j < argc; j++) {
|
||||
@@ -961,7 +945,7 @@ int processInlineBuffer(redisClient *c) {
|
||||
/* Helper function. Trims query buffer to make the function that processes
|
||||
* multi bulk requests idempotent. */
|
||||
static void setProtocolError(redisClient *c, int pos) {
|
||||
if (server.verbosity <= REDIS_VERBOSE) {
|
||||
if (server.verbosity >= REDIS_VERBOSE) {
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
redisLog(REDIS_VERBOSE,
|
||||
"Protocol error from client: %s", client);
|
||||
@@ -1195,7 +1179,6 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
sdsIncrLen(c->querybuf,nread);
|
||||
c->lastinteraction = server.unixtime;
|
||||
if (c->flags & REDIS_MASTER) c->reploff += nread;
|
||||
server.stat_net_input_bytes += nread;
|
||||
} else {
|
||||
server.current_client = NULL;
|
||||
return;
|
||||
@@ -1527,39 +1510,16 @@ void rewriteClientCommandVector(redisClient *c, int argc, ...) {
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* Completely replace the client command vector with the provided one. */
|
||||
void replaceClientCommandVector(redisClient *c, int argc, robj **argv) {
|
||||
freeClientArgv(c);
|
||||
zfree(c->argv);
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
c->cmd = lookupCommandOrOriginal(c->argv[0]->ptr);
|
||||
redisAssertWithInfo(c,NULL,c->cmd != NULL);
|
||||
}
|
||||
|
||||
/* Rewrite a single item in the command vector.
|
||||
* The new val ref count is incremented, and the old decremented.
|
||||
*
|
||||
* It is possible to specify an argument over the current size of the
|
||||
* argument vector: in this case the array of objects gets reallocated
|
||||
* and c->argc set to the max value. However it's up to the caller to
|
||||
*
|
||||
* 1. Make sure there are no "holes" and all the arguments are set.
|
||||
* 2. If the original argument vector was longer than the one we
|
||||
* want to end with, it's up to the caller to set c->argc and
|
||||
* free the no longer used objects on c->argv. */
|
||||
* The new val ref count is incremented, and the old decremented. */
|
||||
void rewriteClientCommandArgument(redisClient *c, int i, robj *newval) {
|
||||
robj *oldval;
|
||||
|
||||
if (i >= c->argc) {
|
||||
c->argv = zrealloc(c->argv,sizeof(robj*)*(i+1));
|
||||
c->argc = i+1;
|
||||
c->argv[i] = NULL;
|
||||
}
|
||||
redisAssertWithInfo(c,NULL,i < c->argc);
|
||||
oldval = c->argv[i];
|
||||
c->argv[i] = newval;
|
||||
incrRefCount(newval);
|
||||
if (oldval) decrRefCount(oldval);
|
||||
decrRefCount(oldval);
|
||||
|
||||
/* If this is the command name make sure to fix c->cmd. */
|
||||
if (i == 0) {
|
||||
@@ -1689,12 +1649,6 @@ void flushSlavesOutputBuffers(void) {
|
||||
redisClient *slave = listNodeValue(ln);
|
||||
int events;
|
||||
|
||||
/* Note that the following will not flush output buffers of slaves
|
||||
* in STATE_ONLINE but having put_online_on_ack set to true: in this
|
||||
* case the writable event is never installed, since the purpose
|
||||
* of put_online_on_ack is to postpone the moment it is installed.
|
||||
* This is what we want since slaves in this state should not receive
|
||||
* writes before the first ACK. */
|
||||
events = aeGetFileEvents(server.el,slave->fd);
|
||||
if (events & AE_WRITABLE &&
|
||||
slave->replstate == REDIS_REPL_ONLINE &&
|
||||
@@ -1731,9 +1685,7 @@ void pauseClients(mstime_t end) {
|
||||
/* 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)
|
||||
{
|
||||
if (server.clients_paused && server.clients_pause_end_time < server.mstime) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
redisClient *c;
|
||||
@@ -1746,10 +1698,7 @@ int clientsArePaused(void) {
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
c = listNodeValue(ln);
|
||||
|
||||
/* Don't touch slaves and blocked clients. The latter pending
|
||||
* requests be processed when unblocked. */
|
||||
if (c->flags & (REDIS_SLAVE|REDIS_BLOCKED)) continue;
|
||||
c->flags |= REDIS_UNBLOCKED;
|
||||
if (c->flags & REDIS_SLAVE) continue;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-34
@@ -109,44 +109,26 @@ robj *createStringObjectFromLongLong(long long value) {
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Create a string object from a long double. If humanfriendly is non-zero
|
||||
* it does not use exponential format and trims trailing zeroes at the end,
|
||||
* however this results in loss of precision. Otherwise exp format is used
|
||||
* and the output of snprintf() is not modified.
|
||||
*
|
||||
* The 'humanfriendly' option is used for INCRBYFLOAT and HINCRBYFLOAT. */
|
||||
robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {
|
||||
/* Note: this function is defined into object.c since here it is where it
|
||||
* belongs but it is actually designed to be used just for INCRBYFLOAT */
|
||||
robj *createStringObjectFromLongDouble(long double value) {
|
||||
char buf[256];
|
||||
int len;
|
||||
|
||||
if (isinf(value)) {
|
||||
/* Libc in odd systems (Hi Solaris!) will format infinite in a
|
||||
* different way, so better to handle it in an explicit way. */
|
||||
if (value > 0) {
|
||||
memcpy(buf,"inf",3);
|
||||
len = 3;
|
||||
} else {
|
||||
memcpy(buf,"-inf",4);
|
||||
len = 4;
|
||||
/* We use 17 digits precision since with 128 bit floats that precision
|
||||
* after rounding is able to represent most small decimal numbers in a way
|
||||
* that is "non surprising" for the user (that is, most small decimal
|
||||
* numbers will be represented in a way that when converted back into
|
||||
* a string are exactly the same as what the user typed.) */
|
||||
len = snprintf(buf,sizeof(buf),"%.17Lf", value);
|
||||
/* Now remove trailing zeroes after the '.' */
|
||||
if (strchr(buf,'.') != NULL) {
|
||||
char *p = buf+len-1;
|
||||
while(*p == '0') {
|
||||
p--;
|
||||
len--;
|
||||
}
|
||||
} else if (humanfriendly) {
|
||||
/* We use 17 digits precision since with 128 bit floats that precision
|
||||
* after rounding is able to represent most small decimal numbers in a
|
||||
* way that is "non surprising" for the user (that is, most small
|
||||
* decimal numbers will be represented in a way that when converted
|
||||
* back into a string are exactly the same as what the user typed.) */
|
||||
len = snprintf(buf,sizeof(buf),"%.17Lf", value);
|
||||
/* Now remove trailing zeroes after the '.' */
|
||||
if (strchr(buf,'.') != NULL) {
|
||||
char *p = buf+len-1;
|
||||
while(*p == '0') {
|
||||
p--;
|
||||
len--;
|
||||
}
|
||||
if (*p == '.') len--;
|
||||
}
|
||||
} else {
|
||||
len = snprintf(buf,sizeof(buf),"%.17Lg", value);
|
||||
if (*p == '.') len--;
|
||||
}
|
||||
return createStringObject(buf,len);
|
||||
}
|
||||
|
||||
@@ -755,9 +755,9 @@ int rdbSave(char *filename) {
|
||||
return REDIS_OK;
|
||||
|
||||
werr:
|
||||
redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
|
||||
fclose(fp);
|
||||
unlink(tmpfile);
|
||||
redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
@@ -1087,9 +1087,8 @@ void startLoading(FILE *fp) {
|
||||
/* Load the DB */
|
||||
server.loading = 1;
|
||||
server.loading_start_time = time(NULL);
|
||||
server.loading_loaded_bytes = 0;
|
||||
if (fstat(fileno(fp), &sb) == -1) {
|
||||
server.loading_total_bytes = 0;
|
||||
server.loading_total_bytes = 1; /* just to avoid division by zero */
|
||||
} else {
|
||||
server.loading_total_bytes = sb.st_size;
|
||||
}
|
||||
@@ -1420,7 +1419,7 @@ int rdbSaveToSlavesSockets(void) {
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
|
||||
clientids[numfds] = slave->id;
|
||||
fds[numfds++] = slave->fd;
|
||||
replicationSetupSlaveForFullResync(slave,getPsyncInitialOffset());
|
||||
slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
/* Put the socket in non-blocking mode to simplify RDB transfer.
|
||||
* We'll restore it when the children returns (since duped socket
|
||||
* will share the O_NONBLOCK attribute with the parent). */
|
||||
@@ -1492,49 +1491,31 @@ int rdbSaveToSlavesSockets(void) {
|
||||
{
|
||||
retval = REDIS_ERR;
|
||||
}
|
||||
zfree(msg);
|
||||
}
|
||||
zfree(clientids);
|
||||
exitFromChild((retval == REDIS_OK) ? 0 : 1);
|
||||
} else {
|
||||
/* Parent */
|
||||
zfree(clientids); /* Not used by parent. Free ASAP. */
|
||||
server.stat_fork_time = ustime()-start;
|
||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
||||
if (childpid == -1) {
|
||||
redisLog(REDIS_WARNING,"Can't save in background: fork: %s",
|
||||
strerror(errno));
|
||||
|
||||
/* Undo the state change. The caller will perform cleanup on
|
||||
* all the slaves in BGSAVE_START state, but an early call to
|
||||
* replicationSetupSlaveForFullResync() turned it into BGSAVE_END */
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
int j;
|
||||
|
||||
for (j = 0; j < numfds; j++) {
|
||||
if (slave->id == clientids[j]) {
|
||||
slave->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
zfree(fds);
|
||||
close(pipefds[0]);
|
||||
close(pipefds[1]);
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,"Background RDB transfer started by pid %d",
|
||||
childpid);
|
||||
server.rdb_save_time_start = time(NULL);
|
||||
server.rdb_child_pid = childpid;
|
||||
server.rdb_child_type = REDIS_RDB_CHILD_TYPE_SOCKET;
|
||||
updateDictResizePolicy();
|
||||
return REDIS_ERR;
|
||||
}
|
||||
zfree(clientids);
|
||||
redisLog(REDIS_NOTICE,"Background RDB transfer started by pid %d",childpid);
|
||||
server.rdb_save_time_start = time(NULL);
|
||||
server.rdb_child_pid = childpid;
|
||||
server.rdb_child_type = REDIS_RDB_CHILD_TYPE_SOCKET;
|
||||
updateDictResizePolicy();
|
||||
zfree(fds);
|
||||
return (childpid == -1) ? REDIS_ERR : REDIS_OK;
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_OK; /* Unreached. */
|
||||
return REDIS_OK; /* unreached */
|
||||
}
|
||||
|
||||
void saveCommand(redisClient *c) {
|
||||
|
||||
+34
-41
@@ -90,10 +90,9 @@ typedef struct _client {
|
||||
long long start; /* Start time of a request */
|
||||
long long latency; /* Request latency */
|
||||
int pending; /* Number of pending requests (replies to consume) */
|
||||
int prefix_pending; /* If non-zero, number of pending prefix commands. Commands
|
||||
such as auth and select are prefixed to the pipeline of
|
||||
benchmark commands and discarded after the first send. */
|
||||
int prefixlen; /* Size in bytes of the pending prefix commands */
|
||||
int selectlen; /* If non-zero, a SELECT of 'selectlen' bytes is currently
|
||||
used as a prefix of the pipline of commands. This gets
|
||||
discarded the first time it's sent. */
|
||||
} *client;
|
||||
|
||||
/* Prototypes */
|
||||
@@ -213,21 +212,20 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
/* This is an OK for prefix commands such as auth and select.*/
|
||||
if (c->prefix_pending > 0) {
|
||||
c->prefix_pending--;
|
||||
|
||||
if (c->selectlen) {
|
||||
size_t j;
|
||||
|
||||
/* This is the OK from SELECT. Just discard the SELECT
|
||||
* from the buffer. */
|
||||
c->pending--;
|
||||
/* Discard prefix commands on first response.*/
|
||||
if (c->prefixlen > 0) {
|
||||
size_t j;
|
||||
sdsrange(c->obuf, c->prefixlen, -1);
|
||||
/* We also need to fix the pointers to the strings
|
||||
* we need to randomize. */
|
||||
for (j = 0; j < c->randlen; j++)
|
||||
c->randptr[j] -= c->prefixlen;
|
||||
c->prefixlen = 0;
|
||||
}
|
||||
continue;
|
||||
sdsrange(c->obuf,c->selectlen,-1);
|
||||
/* We also need to fix the pointers to the strings
|
||||
* we need to randomize. */
|
||||
for (j = 0; j < c->randlen; j++)
|
||||
c->randptr[j] -= c->selectlen;
|
||||
c->selectlen = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.requests_finished < config.requests)
|
||||
@@ -301,7 +299,8 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* 2) The offsets of the __rand_int__ elements inside the command line, used
|
||||
* for arguments randomization.
|
||||
*
|
||||
* Even when cloning another client, prefix commands are applied if needed.*/
|
||||
* Even when cloning another client, the SELECT command is automatically prefixed
|
||||
* if needed. */
|
||||
static client createClient(char *cmd, size_t len, client from) {
|
||||
int j;
|
||||
client c = zmalloc(sizeof(struct _client));
|
||||
@@ -326,16 +325,12 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
* Queue N requests accordingly to the pipeline size, or simply clone
|
||||
* the example client buffer. */
|
||||
c->obuf = sdsempty();
|
||||
/* Prefix the request buffer with AUTH and/or SELECT commands, if applicable.
|
||||
* These commands are discarded after the first response, so if the client is
|
||||
* reused the commands will not be used again. */
|
||||
c->prefix_pending = 0;
|
||||
|
||||
if (config.auth) {
|
||||
char *buf = NULL;
|
||||
int len = redisFormatCommand(&buf, "AUTH %s", config.auth);
|
||||
c->obuf = sdscatlen(c->obuf, buf, len);
|
||||
free(buf);
|
||||
c->prefix_pending++;
|
||||
}
|
||||
|
||||
/* If a DB number different than zero is selected, prefix our request
|
||||
@@ -345,23 +340,26 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
if (config.dbnum != 0) {
|
||||
c->obuf = sdscatprintf(c->obuf,"*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n",
|
||||
(int)sdslen(config.dbnumstr),config.dbnumstr);
|
||||
c->prefix_pending++;
|
||||
c->selectlen = sdslen(c->obuf);
|
||||
} else {
|
||||
c->selectlen = 0;
|
||||
}
|
||||
c->prefixlen = sdslen(c->obuf);
|
||||
|
||||
/* Append the request itself. */
|
||||
if (from) {
|
||||
c->obuf = sdscatlen(c->obuf,
|
||||
from->obuf+from->prefixlen,
|
||||
sdslen(from->obuf)-from->prefixlen);
|
||||
from->obuf+from->selectlen,
|
||||
sdslen(from->obuf)-from->selectlen);
|
||||
} else {
|
||||
for (j = 0; j < config.pipeline; j++)
|
||||
c->obuf = sdscatlen(c->obuf,cmd,len);
|
||||
}
|
||||
|
||||
c->written = 0;
|
||||
c->pending = config.pipeline+c->prefix_pending;
|
||||
c->pending = config.pipeline;
|
||||
c->randptr = NULL;
|
||||
c->randlen = 0;
|
||||
if (c->selectlen) c->pending++;
|
||||
|
||||
/* Find substrings in the output buffer that need to be randomized. */
|
||||
if (config.randomkeys) {
|
||||
@@ -373,7 +371,7 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
for (j = 0; j < (int)c->randlen; j++) {
|
||||
c->randptr[j] = c->obuf + (from->randptr[j]-from->obuf);
|
||||
/* Adjust for the different select prefix length. */
|
||||
c->randptr[j] += c->prefixlen - from->prefixlen;
|
||||
c->randptr[j] += c->selectlen - from->selectlen;
|
||||
}
|
||||
} else {
|
||||
char *p = c->obuf;
|
||||
@@ -392,8 +390,7 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (config.idlemode == 0)
|
||||
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
|
||||
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
|
||||
listAddNodeTail(config.clients,c);
|
||||
config.liveclients++;
|
||||
return c;
|
||||
@@ -558,7 +555,7 @@ usage:
|
||||
" -s <socket> Server socket (overrides host and port)\n"
|
||||
" -a <password> Password for Redis Auth\n"
|
||||
" -c <clients> Number of parallel connections (default 50)\n"
|
||||
" -n <requests> Total number of requests (default 100000)\n"
|
||||
" -n <requests> Total number of requests (default 10000)\n"
|
||||
" -d <size> Data size of SET/GET value in bytes (default 2)\n"
|
||||
" -dbnum <db> SELECT the specified db number (default 0)\n"
|
||||
" -k <boolean> 1=keep alive 0=reconnect (default 1)\n"
|
||||
@@ -602,13 +599,9 @@ int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData
|
||||
if (config.liveclients == 0) {
|
||||
fprintf(stderr,"All clients disconnected... aborting.\n");
|
||||
exit(1);
|
||||
}
|
||||
if (config.csv) return 250;
|
||||
if (config.idlemode == 1) {
|
||||
printf("clients: %d\r", config.liveclients);
|
||||
fflush(stdout);
|
||||
return 250;
|
||||
}
|
||||
|
||||
if (config.csv) return 250;
|
||||
float dt = (float)(mstime()-config.start)/1000.0;
|
||||
float rps = (float)config.requests_finished/dt;
|
||||
printf("%s: %.2f\r", config.title, rps);
|
||||
@@ -642,7 +635,7 @@ int main(int argc, const char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
config.numclients = 50;
|
||||
config.requests = 100000;
|
||||
config.requests = 10000;
|
||||
config.liveclients = 0;
|
||||
config.el = aeCreateEventLoop(1024*10);
|
||||
aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL);
|
||||
@@ -700,8 +693,8 @@ int main(int argc, const char **argv) {
|
||||
}
|
||||
|
||||
/* Run default benchmark suite. */
|
||||
data = zmalloc(config.datasize+1);
|
||||
do {
|
||||
data = zmalloc(config.datasize+1);
|
||||
memset(data,'x',config.datasize);
|
||||
data[config.datasize] = '\0';
|
||||
|
||||
|
||||
+44
-366
@@ -44,7 +44,6 @@
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "sds.h"
|
||||
@@ -61,19 +60,6 @@
|
||||
#define OUTPUT_CSV 2
|
||||
#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||
#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
|
||||
#define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE"
|
||||
#define REDIS_CLI_HISTFILE_DEFAULT ".rediscli_history"
|
||||
|
||||
/* --latency-dist palettes. */
|
||||
int spectrum_palette_color_size = 19;
|
||||
int spectrum_palette_color[] = {0,233,234,235,237,239,241,243,245,247,144,143,142,184,226,214,208,202,196};
|
||||
|
||||
int spectrum_palette_mono_size = 13;
|
||||
int spectrum_palette_mono[] = {0,233,234,235,237,239,241,243,245,247,249,251,253};
|
||||
|
||||
/* The actual palette in use. */
|
||||
int *spectrum_palette;
|
||||
int spectrum_palette_size;
|
||||
|
||||
static redisContext *context;
|
||||
static struct config {
|
||||
@@ -88,10 +74,7 @@ static struct config {
|
||||
int monitor_mode;
|
||||
int pubsub_mode;
|
||||
int latency_mode;
|
||||
int latency_dist_mode;
|
||||
int latency_history;
|
||||
int lru_test_mode;
|
||||
long long lru_test_sample_size;
|
||||
int cluster_mode;
|
||||
int cluster_reissue_command;
|
||||
int slave_mode;
|
||||
@@ -155,30 +138,6 @@ static void cliRefreshPrompt(void) {
|
||||
snprintf(config.prompt+len,sizeof(config.prompt)-len,"> ");
|
||||
}
|
||||
|
||||
static sds getHistoryPath() {
|
||||
char *path = NULL;
|
||||
sds historyPath = NULL;
|
||||
|
||||
/* check the env for a histfile override */
|
||||
path = getenv(REDIS_CLI_HISTFILE_ENV);
|
||||
if (path != NULL && *path != '\0') {
|
||||
if (!strcmp("/dev/null", path)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* if the env is set, return it */
|
||||
historyPath = sdscatprintf(sdsempty(), "%s", path);
|
||||
} else {
|
||||
char *home = getenv("HOME");
|
||||
if (home != NULL && *home != '\0') {
|
||||
/* otherwise, return the default */
|
||||
historyPath = sdscatprintf(sdsempty(), "%s/%s", home, REDIS_CLI_HISTFILE_DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
return historyPath;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Help functions
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -343,7 +302,7 @@ static void completionCallback(const char *buf, linenoiseCompletions *lc) {
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* Send AUTH command to the server */
|
||||
static int cliAuth(void) {
|
||||
static int cliAuth() {
|
||||
redisReply *reply;
|
||||
if (config.auth == NULL) return REDIS_OK;
|
||||
|
||||
@@ -356,7 +315,7 @@ static int cliAuth(void) {
|
||||
}
|
||||
|
||||
/* Send SELECT dbnum to the server */
|
||||
static int cliSelect(void) {
|
||||
static int cliSelect() {
|
||||
redisReply *reply;
|
||||
if (config.dbnum == 0) return REDIS_OK;
|
||||
|
||||
@@ -459,7 +418,7 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
|
||||
_prefix = sdscat(sdsnew(prefix),_prefixlen);
|
||||
|
||||
/* Setup prefix format for every entry */
|
||||
snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud) ",idxlen);
|
||||
snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen);
|
||||
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
/* Don't use the prefix for the first element, as the parent
|
||||
@@ -534,7 +493,7 @@ static sds cliFormatReplyCSV(redisReply *r) {
|
||||
out = sdscatrepr(out,r->str,r->len);
|
||||
break;
|
||||
case REDIS_REPLY_NIL:
|
||||
out = sdscat(out,"NIL");
|
||||
out = sdscat(out,"NIL\n");
|
||||
break;
|
||||
case REDIS_REPLY_ARRAY:
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
@@ -713,17 +672,16 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Send a command reconnecting the link if needed. */
|
||||
static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ...) {
|
||||
/* Send the INFO command, reconnecting the link if needed. */
|
||||
static redisReply *reconnectingInfo(void) {
|
||||
redisContext *c = context;
|
||||
redisReply *reply = NULL;
|
||||
int tries = 0;
|
||||
va_list ap;
|
||||
|
||||
assert(!c->err);
|
||||
while(reply == NULL) {
|
||||
while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
|
||||
printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
|
||||
printf("Reconnecting... %d\r", ++tries);
|
||||
printf("Reconnecting (%d)...\r", ++tries);
|
||||
fflush(stdout);
|
||||
|
||||
redisFree(c);
|
||||
@@ -731,15 +689,12 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ..
|
||||
usleep(1000000);
|
||||
}
|
||||
|
||||
va_start(ap,fmt);
|
||||
reply = redisvCommand(c,fmt,ap);
|
||||
va_end(ap);
|
||||
|
||||
reply = redisCommand(c,"INFO");
|
||||
if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
|
||||
fprintf(stderr, "Error: %s\n", c->errstr);
|
||||
exit(1);
|
||||
} else if (tries > 0) {
|
||||
printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -787,17 +742,9 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.output = OUTPUT_CSV;
|
||||
} else if (!strcmp(argv[i],"--latency")) {
|
||||
config.latency_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--latency-dist")) {
|
||||
config.latency_dist_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--mono")) {
|
||||
spectrum_palette = spectrum_palette_mono;
|
||||
spectrum_palette_size = spectrum_palette_mono_size;
|
||||
} else if (!strcmp(argv[i],"--latency-history")) {
|
||||
config.latency_mode = 1;
|
||||
config.latency_history = 1;
|
||||
} else if (!strcmp(argv[i],"--lru-test") && !lastarg) {
|
||||
config.lru_test_mode = 1;
|
||||
config.lru_test_sample_size = strtoll(argv[++i],NULL,10);
|
||||
} else if (!strcmp(argv[i],"--slave")) {
|
||||
config.slave_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--stat")) {
|
||||
@@ -883,13 +830,9 @@ static void usage(void) {
|
||||
" not a tty).\n"
|
||||
" --no-raw Force formatted output even when STDOUT is not a tty.\n"
|
||||
" --csv Output in CSV format.\n"
|
||||
" --stat Print rolling stats about server: mem, clients, ...\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"
|
||||
" --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n"
|
||||
" Default time interval is 1 sec. Change it using -i.\n"
|
||||
" --lru-test <keys> Simulate a cache workload with an 80-20 distribution.\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"
|
||||
@@ -934,33 +877,6 @@ static char **convertToSds(int count, char** args) {
|
||||
return sds;
|
||||
}
|
||||
|
||||
static int issueCommandRepeat(int argc, char **argv, long repeat) {
|
||||
while (1) {
|
||||
config.cluster_reissue_command = 0;
|
||||
if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
|
||||
cliConnect(1);
|
||||
|
||||
/* If we still cannot send the command print error.
|
||||
* We'll try to reconnect the next time. */
|
||||
if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
|
||||
cliPrintContextError();
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
/* Issue the command again if we got redirected in cluster mode */
|
||||
if (config.cluster_mode && config.cluster_reissue_command) {
|
||||
cliConnect(1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int issueCommand(int argc, char **argv) {
|
||||
return issueCommandRepeat(argc, argv, config.repeat);
|
||||
}
|
||||
|
||||
static void repl(void) {
|
||||
sds historyfile = NULL;
|
||||
int history = 0;
|
||||
@@ -974,9 +890,10 @@ static void repl(void) {
|
||||
|
||||
/* Only use history when stdin is a tty. */
|
||||
if (isatty(fileno(stdin))) {
|
||||
historyfile = getHistoryPath();
|
||||
if (historyfile != NULL) {
|
||||
history = 1;
|
||||
history = 1;
|
||||
|
||||
if (getenv("HOME") != NULL) {
|
||||
historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME"));
|
||||
linenoiseHistoryLoad(historyfile);
|
||||
}
|
||||
}
|
||||
@@ -1016,8 +933,26 @@ static void repl(void) {
|
||||
repeat = 1;
|
||||
}
|
||||
|
||||
issueCommandRepeat(argc-skipargs, argv+skipargs, repeat);
|
||||
while (1) {
|
||||
config.cluster_reissue_command = 0;
|
||||
if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
|
||||
!= REDIS_OK)
|
||||
{
|
||||
cliConnect(1);
|
||||
|
||||
/* If we still cannot send the command print error.
|
||||
* We'll try to reconnect the next time. */
|
||||
if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
|
||||
!= REDIS_OK)
|
||||
cliPrintContextError();
|
||||
}
|
||||
/* Issue the command again if we got redirected in cluster mode */
|
||||
if (config.cluster_mode && config.cluster_reissue_command) {
|
||||
cliConnect(1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
elapsed = mstime()-start_time;
|
||||
if (elapsed >= 500) {
|
||||
printf("(%.2fs)\n",(double)elapsed/1000);
|
||||
@@ -1038,9 +973,10 @@ static int noninteractive(int argc, char **argv) {
|
||||
if (config.stdinarg) {
|
||||
argv = zrealloc(argv, (argc+1)*sizeof(char*));
|
||||
argv[argc] = readArgFromStdin();
|
||||
retval = issueCommand(argc+1, argv);
|
||||
retval = cliSendCommand(argc+1, argv, config.repeat);
|
||||
} else {
|
||||
retval = issueCommand(argc, argv);
|
||||
/* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */
|
||||
retval = cliSendCommand(argc, argv, config.repeat);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
@@ -1084,7 +1020,7 @@ static int evalMode(int argc, char **argv) {
|
||||
argv2[2] = sdscatprintf(sdsempty(),"%d",keys);
|
||||
|
||||
/* Call it */
|
||||
return issueCommand(argc+3-got_comma, argv2);
|
||||
return cliSendCommand(argc+3-got_comma, argv2, config.repeat);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
@@ -1105,7 +1041,7 @@ static void latencyMode(void) {
|
||||
if (!context) exit(1);
|
||||
while(1) {
|
||||
start = mstime();
|
||||
reply = reconnectingRedisCommand(context,"PING");
|
||||
reply = redisCommand(context,"PING");
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr,"\nI/O error\n");
|
||||
exit(1);
|
||||
@@ -1135,148 +1071,6 @@ static void latencyMode(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Latency distribution mode -- requires 256 colors xterm
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
#define LATENCY_DIST_DEFAULT_INTERVAL 1000 /* milliseconds. */
|
||||
|
||||
/* Structure to store samples distribution. */
|
||||
struct distsamples {
|
||||
long long max; /* Max latency to fit into this interval (usec). */
|
||||
long long count; /* Number of samples in this interval. */
|
||||
int character; /* Associated character in visualization. */
|
||||
};
|
||||
|
||||
/* Helper function for latencyDistMode(). Performs the spectrum visualization
|
||||
* of the collected samples targeting an xterm 256 terminal.
|
||||
*
|
||||
* Takes an array of distsamples structures, ordered from smaller to bigger
|
||||
* 'max' value. Last sample max must be 0, to mean that it olds all the
|
||||
* samples greater than the previous one, and is also the stop sentinel.
|
||||
*
|
||||
* "tot' is the total number of samples in the different buckets, so it
|
||||
* is the SUM(samples[i].conut) for i to 0 up to the max sample.
|
||||
*
|
||||
* As a side effect the function sets all the buckets count to 0. */
|
||||
void showLatencyDistSamples(struct distsamples *samples, long long tot) {
|
||||
int j;
|
||||
|
||||
/* We convert samples into a index inside the palette
|
||||
* proportional to the percentage a given bucket represents.
|
||||
* This way intensity of the different parts of the spectrum
|
||||
* don't change relative to the number of requests, which avoids to
|
||||
* pollute the visualization with non-latency related info. */
|
||||
printf("\033[38;5;0m"); /* Set foreground color to black. */
|
||||
for (j = 0; ; j++) {
|
||||
int coloridx =
|
||||
ceil((float) samples[j].count / tot * (spectrum_palette_size-1));
|
||||
int color = spectrum_palette[coloridx];
|
||||
printf("\033[48;5;%dm%c", (int)color, samples[j].character);
|
||||
samples[j].count = 0;
|
||||
if (samples[j].max == 0) break; /* Last sample. */
|
||||
}
|
||||
printf("\033[0m\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* Show the legend: different buckets values and colors meaning, so
|
||||
* that the spectrum is more easily readable. */
|
||||
void showLatencyDistLegend(void) {
|
||||
int j;
|
||||
|
||||
printf("---------------------------------------------\n");
|
||||
printf(". - * # .01 .125 .25 .5 milliseconds\n");
|
||||
printf("1,2,3,...,9 from 1 to 9 milliseconds\n");
|
||||
printf("A,B,C,D,E 10,20,30,40,50 milliseconds\n");
|
||||
printf("F,G,H,I,J .1,.2,.3,.4,.5 seconds\n");
|
||||
printf("K,L,M,N,O,P,Q,? 1,2,4,8,16,30,60,>60 seconds\n");
|
||||
printf("From 0 to 100%%: ");
|
||||
for (j = 0; j < spectrum_palette_size; j++) {
|
||||
printf("\033[48;5;%dm ", spectrum_palette[j]);
|
||||
}
|
||||
printf("\033[0m\n");
|
||||
printf("---------------------------------------------\n");
|
||||
}
|
||||
|
||||
static void latencyDistMode(void) {
|
||||
redisReply *reply;
|
||||
long long start, latency, count = 0;
|
||||
long long history_interval =
|
||||
config.interval ? config.interval/1000 :
|
||||
LATENCY_DIST_DEFAULT_INTERVAL;
|
||||
long long history_start = ustime();
|
||||
int j, outputs = 0;
|
||||
|
||||
struct distsamples samples[] = {
|
||||
/* We use a mostly logarithmic scale, with certain linear intervals
|
||||
* which are more interesting than others, like 1-10 milliseconds
|
||||
* range. */
|
||||
{10,0,'.'}, /* 0.01 ms */
|
||||
{125,0,'-'}, /* 0.125 ms */
|
||||
{250,0,'*'}, /* 0.25 ms */
|
||||
{500,0,'#'}, /* 0.5 ms */
|
||||
{1000,0,'1'}, /* 1 ms */
|
||||
{2000,0,'2'}, /* 2 ms */
|
||||
{3000,0,'3'}, /* 3 ms */
|
||||
{4000,0,'4'}, /* 4 ms */
|
||||
{5000,0,'5'}, /* 5 ms */
|
||||
{6000,0,'6'}, /* 6 ms */
|
||||
{7000,0,'7'}, /* 7 ms */
|
||||
{8000,0,'8'}, /* 8 ms */
|
||||
{9000,0,'9'}, /* 9 ms */
|
||||
{10000,0,'A'}, /* 10 ms */
|
||||
{20000,0,'B'}, /* 20 ms */
|
||||
{30000,0,'C'}, /* 30 ms */
|
||||
{40000,0,'D'}, /* 40 ms */
|
||||
{50000,0,'E'}, /* 50 ms */
|
||||
{100000,0,'F'}, /* 0.1 s */
|
||||
{200000,0,'G'}, /* 0.2 s */
|
||||
{300000,0,'H'}, /* 0.3 s */
|
||||
{400000,0,'I'}, /* 0.4 s */
|
||||
{500000,0,'J'}, /* 0.5 s */
|
||||
{1000000,0,'K'}, /* 1 s */
|
||||
{2000000,0,'L'}, /* 2 s */
|
||||
{4000000,0,'M'}, /* 4 s */
|
||||
{8000000,0,'N'}, /* 8 s */
|
||||
{16000000,0,'O'}, /* 16 s */
|
||||
{30000000,0,'P'}, /* 30 s */
|
||||
{60000000,0,'Q'}, /* 1 minute */
|
||||
{0,0,'?'}, /* > 1 minute */
|
||||
};
|
||||
|
||||
if (!context) exit(1);
|
||||
while(1) {
|
||||
start = ustime();
|
||||
reply = reconnectingRedisCommand(context,"PING");
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr,"\nI/O error\n");
|
||||
exit(1);
|
||||
}
|
||||
latency = ustime()-start;
|
||||
freeReplyObject(reply);
|
||||
count++;
|
||||
|
||||
/* Populate the relevant bucket. */
|
||||
for (j = 0; ; j++) {
|
||||
if (samples[j].max == 0 || latency <= samples[j].max) {
|
||||
samples[j].count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* From time to time show the spectrum. */
|
||||
if (count && (ustime()-history_start)/1000 > history_interval) {
|
||||
if ((outputs++ % 20) == 0)
|
||||
showLatencyDistLegend();
|
||||
showLatencyDistSamples(samples,count);
|
||||
history_start = ustime();
|
||||
count = 0;
|
||||
}
|
||||
usleep(LATENCY_SAMPLE_RATE * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Slave mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -1397,7 +1191,6 @@ static void getRDB(void) {
|
||||
* Bulk import (pipe) mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
#define PIPEMODE_WRITE_LOOP_MAX_BYTES (128*1024)
|
||||
static void pipeMode(void) {
|
||||
int fd = context->fd;
|
||||
long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
|
||||
@@ -1474,8 +1267,6 @@ static void pipeMode(void) {
|
||||
|
||||
/* Handle the writable state: we can send protocol to the server. */
|
||||
if (mask & AE_WRITABLE) {
|
||||
ssize_t loop_nwritten = 0;
|
||||
|
||||
while(1) {
|
||||
/* Transfer current buffer to server. */
|
||||
if (obuf_len != 0) {
|
||||
@@ -1492,7 +1283,6 @@ static void pipeMode(void) {
|
||||
}
|
||||
obuf_len -= nwritten;
|
||||
obuf_pos += nwritten;
|
||||
loop_nwritten += nwritten;
|
||||
if (obuf_len != 0) break; /* Can't accept more data. */
|
||||
}
|
||||
/* If buffer is empty, load from stdin. */
|
||||
@@ -1528,8 +1318,7 @@ static void pipeMode(void) {
|
||||
obuf_pos = 0;
|
||||
}
|
||||
}
|
||||
if ((obuf_len == 0 && eof) ||
|
||||
loop_nwritten > PIPEMODE_WRITE_LOOP_MAX_BYTES) break;
|
||||
if (obuf_len == 0 && eof) break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1587,7 +1376,7 @@ static redisReply *sendScan(unsigned long long *it) {
|
||||
assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
|
||||
|
||||
/* Update iterator */
|
||||
*it = strtoull(reply->element[0]->str, NULL, 10);
|
||||
*it = atoi(reply->element[0]->str);
|
||||
|
||||
return reply;
|
||||
}
|
||||
@@ -1878,7 +1667,7 @@ void bytesToHuman(char *s, long long n) {
|
||||
}
|
||||
if (n < 1024) {
|
||||
/* Bytes */
|
||||
sprintf(s,"%lldB",n);
|
||||
sprintf(s,"%lluB",n);
|
||||
return;
|
||||
} else if (n < (1024*1024)) {
|
||||
d = (double)n/(1024);
|
||||
@@ -1901,7 +1690,7 @@ static void statMode(void) {
|
||||
char buf[64];
|
||||
int j;
|
||||
|
||||
reply = reconnectingRedisCommand(context,"INFO");
|
||||
reply = reconnectingInfo();
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
printf("ERROR: %s\n", reply->str);
|
||||
exit(1);
|
||||
@@ -1955,7 +1744,6 @@ static void statMode(void) {
|
||||
/* Children */
|
||||
aux = getLongInfoField(reply->str,"bgsave_in_progress");
|
||||
aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1;
|
||||
aux |= getLongInfoField(reply->str,"loading") << 2;
|
||||
switch(aux) {
|
||||
case 0: break;
|
||||
case 1:
|
||||
@@ -1967,9 +1755,6 @@ static void statMode(void) {
|
||||
case 3:
|
||||
printf("SAVE+AOF");
|
||||
break;
|
||||
case 4:
|
||||
printf("LOAD");
|
||||
break;
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
@@ -2011,94 +1796,6 @@ static void scanMode(void) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* LRU test mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* Return an integer from min to max (both inclusive) using a power-law
|
||||
* distribution, depending on the value of alpha: the greater the alpha
|
||||
* the more bias towards lower values.
|
||||
*
|
||||
* With alpha = 6.2 the output follows the 80-20 rule where 20% of
|
||||
* the returned numbers will account for 80% of the frequency. */
|
||||
long long powerLawRand(long long min, long long max, double alpha) {
|
||||
double pl, r;
|
||||
|
||||
max += 1;
|
||||
r = ((double)rand()) / RAND_MAX;
|
||||
pl = pow(
|
||||
((pow(max,alpha+1) - pow(min,alpha+1))*r + pow(min,alpha+1)),
|
||||
(1.0/(alpha+1)));
|
||||
return (max-1-(long long)pl)+min;
|
||||
}
|
||||
|
||||
/* Generates a key name among a set of lru_test_sample_size keys, using
|
||||
* an 80-20 distribution. */
|
||||
void LRUTestGenKey(char *buf, size_t buflen) {
|
||||
snprintf(buf, buflen, "lru:%lld\n",
|
||||
powerLawRand(1, config.lru_test_sample_size, 6.2));
|
||||
}
|
||||
|
||||
#define LRU_CYCLE_PERIOD 1000 /* 1000 milliseconds. */
|
||||
#define LRU_CYCLE_PIPELINE_SIZE 250
|
||||
static void LRUTestMode(void) {
|
||||
redisReply *reply;
|
||||
char key[128];
|
||||
long long start_cycle;
|
||||
int j;
|
||||
|
||||
srand(time(NULL)^getpid());
|
||||
while(1) {
|
||||
/* Perform cycles of 1 second with 50% writes and 50% reads.
|
||||
* We use pipelining batching writes / reads N times per cycle in order
|
||||
* to fill the target instance easily. */
|
||||
start_cycle = mstime();
|
||||
long long hits = 0, misses = 0;
|
||||
while(mstime() - start_cycle < 1000) {
|
||||
/* Write cycle. */
|
||||
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
|
||||
LRUTestGenKey(key,sizeof(key));
|
||||
redisAppendCommand(context, "SET %s val",key);
|
||||
}
|
||||
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++)
|
||||
redisGetReply(context, (void**)&reply);
|
||||
|
||||
/* Read cycle. */
|
||||
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
|
||||
LRUTestGenKey(key,sizeof(key));
|
||||
redisAppendCommand(context, "GET %s",key);
|
||||
}
|
||||
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
|
||||
if (redisGetReply(context, (void**)&reply) == REDIS_OK) {
|
||||
switch(reply->type) {
|
||||
case REDIS_REPLY_ERROR:
|
||||
printf("%s\n", reply->str);
|
||||
break;
|
||||
case REDIS_REPLY_NIL:
|
||||
misses++;
|
||||
break;
|
||||
default:
|
||||
hits++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context->err) {
|
||||
fprintf(stderr,"I/O error during LRU test\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
/* Print stats. */
|
||||
printf(
|
||||
"%lld Gets/sec | Hits: %lld (%.2f%%) | Misses: %lld (%.2f%%)\n",
|
||||
hits+misses,
|
||||
hits, (double)hits/(hits+misses)*100,
|
||||
misses, (double)misses/(hits+misses)*100);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Intrisic latency mode.
|
||||
*
|
||||
@@ -2190,10 +1887,7 @@ int main(int argc, char **argv) {
|
||||
config.monitor_mode = 0;
|
||||
config.pubsub_mode = 0;
|
||||
config.latency_mode = 0;
|
||||
config.latency_dist_mode = 0;
|
||||
config.latency_history = 0;
|
||||
config.lru_test_mode = 0;
|
||||
config.lru_test_sample_size = 0;
|
||||
config.cluster_mode = 0;
|
||||
config.slave_mode = 0;
|
||||
config.getrdb_mode = 0;
|
||||
@@ -2210,9 +1904,6 @@ int main(int argc, char **argv) {
|
||||
config.eval = NULL;
|
||||
config.last_cmd_type = -1;
|
||||
|
||||
spectrum_palette = spectrum_palette_color;
|
||||
spectrum_palette_size = spectrum_palette_color_size;
|
||||
|
||||
if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL))
|
||||
config.output = OUTPUT_RAW;
|
||||
else
|
||||
@@ -2224,18 +1915,14 @@ int main(int argc, char **argv) {
|
||||
argc -= firstarg;
|
||||
argv += firstarg;
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
/* Latency mode */
|
||||
if (config.latency_mode) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
latencyMode();
|
||||
}
|
||||
|
||||
/* Latency distribution mode */
|
||||
if (config.latency_dist_mode) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
latencyDistMode();
|
||||
}
|
||||
|
||||
/* Slave mode */
|
||||
if (config.slave_mode) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
@@ -2273,20 +1960,11 @@ int main(int argc, char **argv) {
|
||||
scanMode();
|
||||
}
|
||||
|
||||
/* LRU test mode */
|
||||
if (config.lru_test_mode) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
LRUTestMode();
|
||||
}
|
||||
|
||||
/* Intrinsic latency mode */
|
||||
if (config.intrinsic_latency_mode) intrinsicLatencyMode();
|
||||
|
||||
/* Start interactive mode when no command is provided */
|
||||
if (argc == 0 && !config.eval) {
|
||||
/* Ignore SIGPIPE in interactive mode to force a reconnect */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
/* Note that in repl mode we don't abort on connection error.
|
||||
* A new attempt will be performed for every command send. */
|
||||
cliConnect(0);
|
||||
|
||||
+92
-440
@@ -25,11 +25,6 @@ require 'rubygems'
|
||||
require 'redis'
|
||||
|
||||
ClusterHashSlots = 16384
|
||||
MigrateDefaultTimeout = 60000
|
||||
MigrateDefaultPipeline = 10
|
||||
RebalanceDefaultThreshold = 2
|
||||
|
||||
$verbose = false
|
||||
|
||||
def xputs(s)
|
||||
case s[0..2]
|
||||
@@ -37,8 +32,6 @@ def xputs(s)
|
||||
color="29;1"
|
||||
when "[ER"
|
||||
color="31;1"
|
||||
when "[WA"
|
||||
color="31;1"
|
||||
when "[OK"
|
||||
color="32"
|
||||
when "[FA","***"
|
||||
@@ -79,7 +72,7 @@ class ClusterNode
|
||||
@friends
|
||||
end
|
||||
|
||||
def slots
|
||||
def slots
|
||||
@info[:slots]
|
||||
end
|
||||
|
||||
@@ -93,7 +86,7 @@ class ClusterNode
|
||||
|
||||
def connect(o={})
|
||||
return if @r
|
||||
print "Connecting to node #{self}: " if $verbose
|
||||
print "Connecting to node #{self}: "
|
||||
STDOUT.flush
|
||||
begin
|
||||
@r = Redis.new(:host => @info[:host], :port => @info[:port], :timeout => 60)
|
||||
@@ -103,7 +96,7 @@ class ClusterNode
|
||||
exit 1 if o[:abort]
|
||||
@r = nil
|
||||
end
|
||||
xputs "OK" if $verbose
|
||||
xputs "OK"
|
||||
end
|
||||
|
||||
def assert_cluster
|
||||
@@ -161,7 +154,7 @@ class ClusterNode
|
||||
end
|
||||
} if slots
|
||||
@dirty = false
|
||||
@r.cluster("info").split("\n").each{|e|
|
||||
@r.cluster("info").split("\n").each{|e|
|
||||
k,v=e.split(":")
|
||||
k = k.to_sym
|
||||
v.chop!
|
||||
@@ -220,7 +213,7 @@ class ClusterNode
|
||||
#
|
||||
# Note: this could be easily written without side effects,
|
||||
# we use 'slots' just to split the computation into steps.
|
||||
|
||||
|
||||
# First step: we want an increasing array of integers
|
||||
# for instance: [1,2,3,4,5,8,9,20,21,22,23,24,25,30]
|
||||
slots = @info[:slots].keys.sort
|
||||
@@ -280,7 +273,7 @@ class ClusterNode
|
||||
def info
|
||||
@info
|
||||
end
|
||||
|
||||
|
||||
def is_dirty?
|
||||
@dirty
|
||||
end
|
||||
@@ -295,7 +288,6 @@ class RedisTrib
|
||||
@nodes = []
|
||||
@fix = false
|
||||
@errors = []
|
||||
@timeout = MigrateDefaultTimeout
|
||||
end
|
||||
|
||||
def check_arity(req_args, num_args)
|
||||
@@ -310,16 +302,11 @@ class RedisTrib
|
||||
@nodes << node
|
||||
end
|
||||
|
||||
def reset_nodes
|
||||
@nodes = []
|
||||
end
|
||||
|
||||
def cluster_error(msg)
|
||||
@errors << msg
|
||||
xputs msg
|
||||
end
|
||||
|
||||
# Return the node with the specified ID or Nil.
|
||||
def get_node_by_name(name)
|
||||
@nodes.each{|n|
|
||||
return n if n.info[:name] == name.downcase
|
||||
@@ -327,21 +314,6 @@ class RedisTrib
|
||||
return nil
|
||||
end
|
||||
|
||||
# Like get_node_by_name but the specified name can be just the first
|
||||
# part of the node ID as long as the prefix in unique across the
|
||||
# cluster.
|
||||
def get_node_by_abbreviated_name(name)
|
||||
l = name.length
|
||||
candidates = []
|
||||
@nodes.each{|n|
|
||||
if n.info[:name][0...l] == name.downcase
|
||||
candidates << n
|
||||
end
|
||||
}
|
||||
return nil if candidates.length != 1
|
||||
candidates[0]
|
||||
end
|
||||
|
||||
# This function returns the master that has the least number of replicas
|
||||
# in the cluster. If there are multiple masters with the same smaller
|
||||
# number of replicas, one at random is returned.
|
||||
@@ -353,30 +325,14 @@ class RedisTrib
|
||||
sorted[0]
|
||||
end
|
||||
|
||||
def check_cluster(opt={})
|
||||
def check_cluster
|
||||
xputs ">>> Performing Cluster Check (using node #{@nodes[0]})"
|
||||
show_nodes if !opt[:quiet]
|
||||
show_nodes
|
||||
check_config_consistency
|
||||
check_open_slots
|
||||
check_slots_coverage
|
||||
end
|
||||
|
||||
def show_cluster_info
|
||||
masters = 0
|
||||
keys = 0
|
||||
@nodes.each{|n|
|
||||
if n.has_flag?("master")
|
||||
puts "#{n} (#{n.info[:name][0...8]}...) -> #{n.r.dbsize} keys | #{n.slots.length} slots | "+
|
||||
"#{n.info[:replicas].length} slaves."
|
||||
masters += 1
|
||||
keys += n.r.dbsize
|
||||
end
|
||||
}
|
||||
xputs "[OK] #{keys} keys in #{masters} masters."
|
||||
keys_per_slot = sprintf("%.2f",keys/16384.0)
|
||||
puts "#{keys_per_slot} keys per slot on average."
|
||||
end
|
||||
|
||||
# Merge slots of every known node. If the resulting slots are equal
|
||||
# to ClusterHashSlots, then all slots are served.
|
||||
def covered_slots
|
||||
@@ -425,7 +381,6 @@ class RedisTrib
|
||||
def nodes_with_keys_in_slot(slot)
|
||||
nodes = []
|
||||
@nodes.each{|n|
|
||||
next if n.has_flag?("slave")
|
||||
nodes << n if n.r.cluster("getkeysinslot",slot,1).length > 0
|
||||
}
|
||||
nodes
|
||||
@@ -444,7 +399,7 @@ class RedisTrib
|
||||
not_covered.each{|slot|
|
||||
nodes = nodes_with_keys_in_slot(slot)
|
||||
slots[slot] = nodes
|
||||
xputs "Slot #{slot} has keys in #{nodes.length} nodes: #{nodes.join(", ")}"
|
||||
xputs "Slot #{slot} has keys in #{nodes.length} nodes: #{nodes.join}"
|
||||
}
|
||||
|
||||
none = slots.select {|k,v| v.length == 0}
|
||||
@@ -480,50 +435,26 @@ class RedisTrib
|
||||
xputs multi.keys.join(",")
|
||||
yes_or_die "Fix these slots by moving keys into a single node?"
|
||||
multi.each{|slot,nodes|
|
||||
target = get_node_with_most_keys_in_slot(nodes,slot)
|
||||
xputs ">>> Covering slot #{slot} moving keys to #{target}"
|
||||
|
||||
target.r.cluster('addslots',slot)
|
||||
target.r.cluster('setslot',slot,'stable')
|
||||
nodes.each{|src|
|
||||
next if src == target
|
||||
# Set the source node in 'importing' state (even if we will
|
||||
# actually migrate keys away) in order to avoid receiving
|
||||
# redirections for MIGRATE.
|
||||
src.r.cluster('setslot',slot,'importing',target.info[:name])
|
||||
move_slot(src,target,slot,:dots=>true,:fix=>true,:cold=>true)
|
||||
src.r.cluster('setslot',slot,'stable')
|
||||
}
|
||||
xputs ">>> Covering slot #{slot} moving keys to #{nodes[0]}"
|
||||
# TODO
|
||||
# 1) Set all nodes as "MIGRATING" for this slot, so that we
|
||||
# can access keys in the hash slot using ASKING.
|
||||
# 2) Move everything to node[0]
|
||||
# 3) Clear MIGRATING from nodes, and ADDSLOTS the slot to
|
||||
# node[0].
|
||||
raise "TODO: Work in progress"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Return the owner of the specified slot
|
||||
def get_slot_owners(slot)
|
||||
owners = []
|
||||
def get_slot_owner(slot)
|
||||
@nodes.each{|n|
|
||||
next if n.has_flag?("slave")
|
||||
n.slots.each{|s,_|
|
||||
owners << n if s == slot
|
||||
return n if s == slot
|
||||
}
|
||||
}
|
||||
owners
|
||||
end
|
||||
|
||||
# Return the node, among 'nodes' with the greatest number of keys
|
||||
# in the specified slot.
|
||||
def get_node_with_most_keys_in_slot(nodes,slot)
|
||||
best = nil
|
||||
best_numkeys = 0
|
||||
@nodes.each{|n|
|
||||
next if n.has_flag?("slave")
|
||||
numkeys = n.r.cluster("countkeysinslot",slot)
|
||||
if numkeys > best_numkeys || best == nil
|
||||
best = n
|
||||
best_numkeys = numkeys
|
||||
end
|
||||
}
|
||||
return best
|
||||
nil
|
||||
end
|
||||
|
||||
# Slot 'slot' was found to be in importing or migrating state in one or
|
||||
@@ -534,8 +465,16 @@ class RedisTrib
|
||||
|
||||
# Try to obtain the current slot owner, according to the current
|
||||
# nodes configuration.
|
||||
owners = get_slot_owners(slot)
|
||||
owner = owners[0] if owners.length == 1
|
||||
owner = get_slot_owner(slot)
|
||||
|
||||
# If there is no slot owner, set as owner the slot with the biggest
|
||||
# number of keys, among the set of migrating / importing nodes.
|
||||
if !owner
|
||||
xputs "*** Fix me, some work to do here."
|
||||
# Select owner...
|
||||
# Use ADDSLOTS to assign the slot.
|
||||
exit 1
|
||||
end
|
||||
|
||||
migrating = []
|
||||
importing = []
|
||||
@@ -553,77 +492,20 @@ class RedisTrib
|
||||
puts "Set as migrating in: #{migrating.join(",")}"
|
||||
puts "Set as importing in: #{importing.join(",")}"
|
||||
|
||||
# If there is no slot owner, set as owner the slot with the biggest
|
||||
# number of keys, among the set of migrating / importing nodes.
|
||||
if !owner
|
||||
xputs ">>> Nobody claims ownership, selecting an owner..."
|
||||
owner = get_node_with_most_keys_in_slot(@nodes,slot)
|
||||
|
||||
# If we still don't have an owner, we can't fix it.
|
||||
if !owner
|
||||
xputs "[ERR] Can't select a slot owner. Impossible to fix."
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Use ADDSLOTS to assign the slot.
|
||||
puts "*** Configuring #{owner} as the slot owner"
|
||||
n.r.cluster("setslot",slot,"stable")
|
||||
n.r.cluster("addslot",slot)
|
||||
# Make sure this information will propagate. Not strictly needed
|
||||
# since there is no past owner, so all the other nodes will accept
|
||||
# whatever epoch this node will claim the slot with.
|
||||
n.r.cluster("bumpepoch")
|
||||
|
||||
# Remove the owner from the list of migrating/importing
|
||||
# nodes.
|
||||
migrating.delete(n)
|
||||
importing.delete(n)
|
||||
end
|
||||
|
||||
# If there are multiple owners of the slot, we need to fix it
|
||||
# so that a single node is the owner and all the other nodes
|
||||
# are in importing state. Later the fix can be handled by one
|
||||
# of the base cases above.
|
||||
#
|
||||
# Note that this case also covers multiple nodes having the slot
|
||||
# in migrating state, since migrating is a valid state only for
|
||||
# slot owners.
|
||||
if owners.length > 1
|
||||
owner = get_node_with_most_keys_in_slot(owners,slot)
|
||||
owners.each{|n|
|
||||
next if n == owner
|
||||
n.r.cluster('delslots',slot)
|
||||
n.r.cluster('setslot',slot,'importing',owner.info[:name])
|
||||
importing.delete(n) # Avoid duplciates
|
||||
importing << n
|
||||
}
|
||||
owner.r.cluster('bumpepoch')
|
||||
end
|
||||
|
||||
# Case 1: The slot is in migrating state in one slot, and in
|
||||
# importing state in 1 slot. That's trivial to address.
|
||||
if migrating.length == 1 && importing.length == 1
|
||||
move_slot(migrating[0],importing[0],slot,:dots=>true,:fix=>true)
|
||||
# Case 2: There are multiple nodes that claim the slot as importing,
|
||||
# they probably got keys about the slot after a restart so opened
|
||||
# the slot. In this case we just move all the keys to the owner
|
||||
# according to the configuration.
|
||||
move_slot(migrating[0],importing[0],slot,:verbose=>true,:fix=>true)
|
||||
elsif migrating.length == 0 && importing.length > 0
|
||||
xputs ">>> Moving all the #{slot} slot keys to its owner #{owner}"
|
||||
importing.each {|node|
|
||||
next if node == owner
|
||||
move_slot(node,owner,slot,:dots=>true,:fix=>true,:cold=>true)
|
||||
move_slot(node,owner,slot,:verbose=>true,:fix=>true,:cold=>true)
|
||||
xputs ">>> Setting #{slot} as STABLE in #{node}"
|
||||
node.r.cluster("setslot",slot,"stable")
|
||||
}
|
||||
# Case 3: There are no slots claiming to be in importing state, but
|
||||
# there is a migrating node that actually don't have any key. We
|
||||
# can just close the slot, probably a reshard interrupted in the middle.
|
||||
elsif importing.length == 0 && migrating.length == 1 &&
|
||||
migrating[0].r.cluster("getkeysinslot",slot,10).length == 0
|
||||
migrating[0].r.cluster("setslot",slot,"stable")
|
||||
else
|
||||
xputs "[ERR] Sorry, Redis-trib can't fix this slot yet (work in progress). Slot is set as migrating in #{migrating.join(",")}, as importing in #{importing.join(",")}, owner is #{owner}"
|
||||
xputs "[ERR] Sorry, Redis-trib can't fix this slot yet (work in progress)"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -658,6 +540,7 @@ class RedisTrib
|
||||
nodes_count = @nodes.length
|
||||
masters_count = @nodes.length / (@replicas+1)
|
||||
masters = []
|
||||
slaves = []
|
||||
|
||||
# The first step is to split instances by IP. This is useful as
|
||||
# we'll try to allocate master nodes in different physical machines
|
||||
@@ -675,31 +558,16 @@ class RedisTrib
|
||||
|
||||
# Select master instances
|
||||
puts "Using #{masters_count} masters:"
|
||||
interleaved = []
|
||||
stop = false
|
||||
while not stop do
|
||||
# Take one node from each IP until we run out of nodes
|
||||
# across every IP.
|
||||
ips.each do |ip,nodes|
|
||||
if nodes.empty?
|
||||
# if this IP has no remaining nodes, check for termination
|
||||
if interleaved.length == nodes_count
|
||||
# stop when 'interleaved' has accumulated all nodes
|
||||
stop = true
|
||||
next
|
||||
end
|
||||
else
|
||||
# else, move one node from this IP to 'interleaved'
|
||||
interleaved.push nodes.shift
|
||||
end
|
||||
end
|
||||
while masters.length < masters_count
|
||||
ips.each{|ip,nodes_list|
|
||||
next if nodes_list.length == 0
|
||||
masters << nodes_list.shift
|
||||
puts masters[-1]
|
||||
nodes_count -= 1
|
||||
break if masters.length == masters_count
|
||||
}
|
||||
end
|
||||
|
||||
masters = interleaved.slice!(0, masters_count)
|
||||
nodes_count -= masters.length
|
||||
|
||||
masters.each{|m| puts m}
|
||||
|
||||
# Alloc slots on masters
|
||||
slots_per_node = ClusterHashSlots.to_f / masters_count
|
||||
first = 0
|
||||
@@ -726,8 +594,8 @@ class RedisTrib
|
||||
# all nodes will be used.
|
||||
assignment_verbose = false
|
||||
|
||||
[:requested,:unused].each do |assign|
|
||||
masters.each do |m|
|
||||
[:requested,:unused].each{|assign|
|
||||
masters.each{|m|
|
||||
assigned_replicas = 0
|
||||
while assigned_replicas < @replicas
|
||||
break if nodes_count == 0
|
||||
@@ -741,33 +609,21 @@ class RedisTrib
|
||||
"role too (#{nodes_count} remaining)."
|
||||
end
|
||||
end
|
||||
|
||||
# Return the first node not matching our current master
|
||||
node = interleaved.find{|n| n.info[:host] != m.info[:host]}
|
||||
|
||||
# If we found a node, use it as a best-first match.
|
||||
# Otherwise, we didn't find a node on a different IP, so we
|
||||
# go ahead and use a same-IP replica.
|
||||
if node
|
||||
slave = node
|
||||
interleaved.delete node
|
||||
else
|
||||
slave = interleaved.shift
|
||||
end
|
||||
slave.set_as_replica(m.info[:name])
|
||||
nodes_count -= 1
|
||||
assigned_replicas += 1
|
||||
puts "Adding replica #{slave} to #{m}"
|
||||
|
||||
# If we are in the "assign extra nodes" loop,
|
||||
# we want to assign one extra replica to each
|
||||
# master before repeating masters.
|
||||
# This break lets us assign extra replicas to masters
|
||||
# in a round-robin way.
|
||||
break if assign == :unused
|
||||
ips.each{|ip,nodes_list|
|
||||
next if nodes_list.length == 0
|
||||
# Skip instances with the same IP as the master if we
|
||||
# have some more IPs available.
|
||||
next if ip == m.info[:host] && nodes_count > nodes_list.length
|
||||
slave = nodes_list.shift
|
||||
slave.set_as_replica(m.info[:name])
|
||||
nodes_count -= 1
|
||||
assigned_replicas += 1
|
||||
puts "Adding replica #{slave} to #{m}"
|
||||
break
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def flush_nodes_config
|
||||
@@ -907,241 +763,65 @@ class RedisTrib
|
||||
|
||||
# Move slots between source and target nodes using MIGRATE.
|
||||
#
|
||||
# Options:
|
||||
# Options:
|
||||
# :verbose -- Print a dot for every moved key.
|
||||
# :fix -- We are moving in the context of a fix. Use REPLACE.
|
||||
# :cold -- Move keys without opening slots / reconfiguring the nodes.
|
||||
# :update -- Update nodes.info[:slots] for source/target nodes.
|
||||
# :quiet -- Don't print info messages.
|
||||
# :cold -- Move keys without opening / reconfiguring the nodes.
|
||||
def move_slot(source,target,slot,o={})
|
||||
o = {:pipeline => MigrateDefaultPipeline}.merge(o)
|
||||
|
||||
# We start marking the slot as importing in the destination node,
|
||||
# and the slot as migrating in the target host. Note that the order of
|
||||
# the operations is important, as otherwise a client may be redirected
|
||||
# to the target node that does not yet know it is importing this slot.
|
||||
if !o[:quiet]
|
||||
print "Moving slot #{slot} from #{source} to #{target}: "
|
||||
STDOUT.flush
|
||||
end
|
||||
|
||||
print "Moving slot #{slot} from #{source} to #{target}: "; STDOUT.flush
|
||||
if !o[:cold]
|
||||
target.r.cluster("setslot",slot,"importing",source.info[:name])
|
||||
source.r.cluster("setslot",slot,"migrating",target.info[:name])
|
||||
end
|
||||
# Migrate all the keys from source to target using the MIGRATE command
|
||||
while true
|
||||
keys = source.r.cluster("getkeysinslot",slot,o[:pipeline])
|
||||
keys = source.r.cluster("getkeysinslot",slot,10)
|
||||
break if keys.length == 0
|
||||
begin
|
||||
source.r.client.call(["migrate",target.info[:host],target.info[:port],"",0,@timeout,:keys,*keys])
|
||||
rescue => e
|
||||
if o[:fix] && e.to_s =~ /BUSYKEY/
|
||||
xputs "*** Target key exists. Replacing it for FIX."
|
||||
source.r.client.call(["migrate",target.info[:host],target.info[:port],"",0,@timeout,:replace,:keys,*keys])
|
||||
else
|
||||
puts ""
|
||||
xputs "[ERR] #{e}"
|
||||
exit 1
|
||||
keys.each{|key|
|
||||
begin
|
||||
source.r.client.call(["migrate",target.info[:host],target.info[:port],key,0,15000])
|
||||
rescue => e
|
||||
if o[:fix] && e.to_s =~ /BUSYKEY/
|
||||
xputs "*** Target key #{key} exists. Replace it for FIX."
|
||||
source.r.client.call(["migrate",target.info[:host],target.info[:port],key,0,15000,:replace])
|
||||
else
|
||||
puts ""
|
||||
xputs "[ERR] #{e}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
end
|
||||
print "."*keys.length if o[:dots]
|
||||
STDOUT.flush
|
||||
end
|
||||
|
||||
puts if !o[:quiet]
|
||||
# Set the new node as the owner of the slot in all the known nodes.
|
||||
if !o[:cold]
|
||||
@nodes.each{|n|
|
||||
next if n.has_flag?("slave")
|
||||
n.r.cluster("setslot",slot,"node",target.info[:name])
|
||||
print "." if o[:verbose]
|
||||
STDOUT.flush
|
||||
}
|
||||
end
|
||||
|
||||
# Update the node logical config
|
||||
if o[:update] then
|
||||
source.info[:slots].delete(slot)
|
||||
target.info[:slots][slot] = true
|
||||
puts
|
||||
# Set the new node as the owner of the slot in all the known nodes.
|
||||
if !o[:cold]
|
||||
@nodes.each{|n|
|
||||
n.r.cluster("setslot",slot,"node",target.info[:name])
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# redis-trib subcommands implementations.
|
||||
# redis-trib subcommands implementations
|
||||
|
||||
def check_cluster_cmd(argv,opt)
|
||||
load_cluster_info_from_node(argv[0])
|
||||
check_cluster
|
||||
end
|
||||
|
||||
def info_cluster_cmd(argv,opt)
|
||||
load_cluster_info_from_node(argv[0])
|
||||
show_cluster_info
|
||||
end
|
||||
|
||||
def rebalance_cluster_cmd(argv,opt)
|
||||
opt = {
|
||||
'pipeline' => MigrateDefaultPipeline,
|
||||
'threshold' => RebalanceDefaultThreshold
|
||||
}.merge(opt)
|
||||
|
||||
# Load nodes info before parsing options, otherwise we can't
|
||||
# handle --weight.
|
||||
load_cluster_info_from_node(argv[0])
|
||||
|
||||
# Options parsing
|
||||
threshold = opt['threshold'].to_i
|
||||
autoweights = opt['auto-weights']
|
||||
weights = {}
|
||||
opt['weight'].each{|w|
|
||||
fields = w.split("=")
|
||||
node = get_node_by_abbreviated_name(fields[0])
|
||||
if !node || !node.has_flag?("master")
|
||||
puts "*** No such master node #{fields[0]}"
|
||||
exit 1
|
||||
end
|
||||
weights[node.info[:name]] = fields[1].to_f
|
||||
} if opt['weight']
|
||||
useempty = opt['use-empty-masters']
|
||||
|
||||
# Assign a weight to each node, and compute the total cluster weight.
|
||||
total_weight = 0
|
||||
nodes_involved = 0
|
||||
@nodes.each{|n|
|
||||
if n.has_flag?("master")
|
||||
next if !useempty && n.slots.length == 0
|
||||
n.info[:w] = weights[n.info[:name]] ? weights[n.info[:name]] : 1
|
||||
total_weight += n.info[:w]
|
||||
nodes_involved += 1
|
||||
end
|
||||
}
|
||||
|
||||
# Check cluster, only proceed if it looks sane.
|
||||
check_cluster(:quiet => true)
|
||||
if @errors.length != 0
|
||||
puts "*** Please fix your cluster problems before rebalancing"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Calculate the slots balance for each node. It's the number of
|
||||
# slots the node should lose (if positive) or gain (if negative)
|
||||
# in order to be balanced.
|
||||
threshold = opt['threshold'].to_f
|
||||
threshold_reached = false
|
||||
@nodes.each{|n|
|
||||
if n.has_flag?("master")
|
||||
next if !n.info[:w]
|
||||
expected = ((ClusterHashSlots.to_f / total_weight) *
|
||||
n.info[:w]).to_i
|
||||
n.info[:balance] = n.slots.length - expected
|
||||
# Compute the percentage of difference between the
|
||||
# expected number of slots and the real one, to see
|
||||
# if it's over the threshold specified by the user.
|
||||
over_threshold = false
|
||||
if threshold > 0
|
||||
if n.slots.length > 0
|
||||
err_perc = (100-(100.0*expected/n.slots.length)).abs
|
||||
over_threshold = true if err_perc > threshold
|
||||
elsif expected > 0
|
||||
over_threshold = true
|
||||
end
|
||||
end
|
||||
threshold_reached = true if over_threshold
|
||||
end
|
||||
}
|
||||
if !threshold_reached
|
||||
xputs "*** No rebalancing needed! All nodes are within the #{threshold}% threshold."
|
||||
return
|
||||
end
|
||||
|
||||
# Only consider nodes we want to change
|
||||
sn = @nodes.select{|n|
|
||||
n.has_flag?("master") && n.info[:w]
|
||||
}
|
||||
|
||||
# Because of rounding, it is possible that the balance of all nodes
|
||||
# summed does not give 0. Make sure that nodes that have to provide
|
||||
# slots are always matched by nodes receiving slots.
|
||||
total_balance = sn.map{|x| x.info[:balance]}.reduce{|a,b| a+b}
|
||||
while total_balance > 0
|
||||
sn.each{|n|
|
||||
if n.info[:balance] < 0 && total_balance > 0
|
||||
n.info[:balance] -= 1
|
||||
total_balance -= 1
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# Sort nodes by their slots balance.
|
||||
sn = sn.sort{|a,b|
|
||||
a.info[:balance] <=> b.info[:balance]
|
||||
}
|
||||
|
||||
xputs ">>> Rebalancing across #{nodes_involved} nodes. Total weight = #{total_weight}"
|
||||
|
||||
if $verbose
|
||||
sn.each{|n|
|
||||
puts "#{n} balance is #{n.info[:balance]} slots"
|
||||
}
|
||||
end
|
||||
|
||||
# Now we have at the start of the 'sn' array nodes that should get
|
||||
# slots, at the end nodes that must give slots.
|
||||
# We take two indexes, one at the start, and one at the end,
|
||||
# incrementing or decrementing the indexes accordingly til we
|
||||
# find nodes that need to get/provide slots.
|
||||
dst_idx = 0
|
||||
src_idx = sn.length - 1
|
||||
|
||||
while dst_idx < src_idx
|
||||
dst = sn[dst_idx]
|
||||
src = sn[src_idx]
|
||||
numslots = [dst.info[:balance],src.info[:balance]].map{|n|
|
||||
n.abs
|
||||
}.min
|
||||
|
||||
if numslots > 0
|
||||
puts "Moving #{numslots} slots from #{src} to #{dst}"
|
||||
|
||||
# Actaully move the slots.
|
||||
reshard_table = compute_reshard_table([src],numslots)
|
||||
if reshard_table.length != numslots
|
||||
xputs "*** Assertio failed: Reshard table != number of slots"
|
||||
exit 1
|
||||
end
|
||||
if opt['simulate']
|
||||
print "#"*reshard_table.length
|
||||
else
|
||||
reshard_table.each{|e|
|
||||
move_slot(e[:source],dst,e[:slot],
|
||||
:quiet=>true,
|
||||
:dots=>false,
|
||||
:update=>true,
|
||||
:pipeline=>opt['pipeline'])
|
||||
print "#"
|
||||
STDOUT.flush
|
||||
}
|
||||
end
|
||||
puts
|
||||
end
|
||||
|
||||
# Update nodes balance.
|
||||
dst.info[:balance] += numslots
|
||||
src.info[:balance] -= numslots
|
||||
dst_idx += 1 if dst.info[:balance] == 0
|
||||
src_idx -= 1 if src.info[:balance] == 0
|
||||
end
|
||||
end
|
||||
|
||||
def fix_cluster_cmd(argv,opt)
|
||||
@fix = true
|
||||
@timeout = opt['timeout'].to_i if opt['timeout']
|
||||
|
||||
load_cluster_info_from_node(argv[0])
|
||||
check_cluster
|
||||
end
|
||||
|
||||
def reshard_cluster_cmd(argv,opt)
|
||||
opt = {'pipeline' => MigrateDefaultPipeline}.merge(opt)
|
||||
|
||||
load_cluster_info_from_node(argv[0])
|
||||
check_cluster
|
||||
if @errors.length != 0
|
||||
@@ -1149,8 +829,6 @@ class RedisTrib
|
||||
exit 1
|
||||
end
|
||||
|
||||
@timeout = opt['timeout'].to_i if opt['timeout'].to_i
|
||||
|
||||
# Get number of slots
|
||||
if opt['slots']
|
||||
numslots = opt['slots'].to_i
|
||||
@@ -1254,9 +932,7 @@ class RedisTrib
|
||||
exit(1) if (yesno != "yes")
|
||||
end
|
||||
reshard_table.each{|e|
|
||||
move_slot(e[:source],target,e[:slot],
|
||||
:dots=>true,
|
||||
:pipeline=>opt['pipeline'])
|
||||
move_slot(e[:source],target,e[:slot],:verbose=>true)
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1437,9 +1113,7 @@ class RedisTrib
|
||||
def import_cluster_cmd(argv,opt)
|
||||
source_addr = opt['from']
|
||||
xputs ">>> Importing data from #{source_addr} to cluster #{argv[1]}"
|
||||
use_copy = opt['copy']
|
||||
use_replace = opt['replace']
|
||||
|
||||
|
||||
# Check the existing cluster.
|
||||
load_cluster_info_from_node(argv[0])
|
||||
check_cluster
|
||||
@@ -1465,7 +1139,7 @@ class RedisTrib
|
||||
# right node as needed.
|
||||
cursor = nil
|
||||
while cursor != 0
|
||||
cursor,keys = source.scan(cursor, :count => 1000)
|
||||
cursor,keys = source.scan(cursor,:count,1000)
|
||||
cursor = cursor.to_i
|
||||
keys.each{|k|
|
||||
# Migrate keys using the MIGRATE command.
|
||||
@@ -1474,10 +1148,7 @@ class RedisTrib
|
||||
print "Migrating #{k} to #{target}: "
|
||||
STDOUT.flush
|
||||
begin
|
||||
cmd = ["migrate",target.info[:host],target.info[:port],k,0,@timeout]
|
||||
cmd << :copy if use_copy
|
||||
cmd << :replace if use_replace
|
||||
source.client.call(cmd)
|
||||
source.client.call(["migrate",target.info[:host],target.info[:port],k,0,15000])
|
||||
rescue => e
|
||||
puts e
|
||||
else
|
||||
@@ -1502,32 +1173,17 @@ class RedisTrib
|
||||
if ARGV[idx][0..1] == "--"
|
||||
option = ARGV[idx][2..-1]
|
||||
idx += 1
|
||||
|
||||
# --verbose is a global option
|
||||
if option == "verbose"
|
||||
$verbose = true
|
||||
next
|
||||
end
|
||||
|
||||
if ALLOWED_OPTIONS[cmd] == nil || ALLOWED_OPTIONS[cmd][option] == nil
|
||||
puts "Unknown option '#{option}' for command '#{cmd}'"
|
||||
exit 1
|
||||
end
|
||||
if ALLOWED_OPTIONS[cmd][option] != false
|
||||
if ALLOWED_OPTIONS[cmd][option]
|
||||
value = ARGV[idx]
|
||||
idx += 1
|
||||
else
|
||||
value = true
|
||||
end
|
||||
|
||||
# If the option is set to [], it's a multiple arguments
|
||||
# option. We just queue every new value into an array.
|
||||
if ALLOWED_OPTIONS[cmd][option] == []
|
||||
options[option] = [] if !options[option]
|
||||
options[option] << value
|
||||
else
|
||||
options[option] = value
|
||||
end
|
||||
options[option] = value
|
||||
else
|
||||
# Remaining arguments are not options.
|
||||
break
|
||||
@@ -1550,7 +1206,7 @@ end
|
||||
|
||||
#################################################################################
|
||||
# Libraries
|
||||
#
|
||||
#
|
||||
# We try to don't depend on external libs since this is a critical part
|
||||
# of Redis Cluster.
|
||||
#################################################################################
|
||||
@@ -1639,10 +1295,8 @@ end
|
||||
COMMANDS={
|
||||
"create" => ["create_cluster_cmd", -2, "host1:port1 ... hostN:portN"],
|
||||
"check" => ["check_cluster_cmd", 2, "host:port"],
|
||||
"info" => ["info_cluster_cmd", 2, "host:port"],
|
||||
"fix" => ["fix_cluster_cmd", 2, "host:port"],
|
||||
"reshard" => ["reshard_cluster_cmd", 2, "host:port"],
|
||||
"rebalance" => ["rebalance_cluster_cmd", -2, "host:port"],
|
||||
"add-node" => ["addnode_cluster_cmd", 3, "new_host:new_port existing_host:existing_port"],
|
||||
"del-node" => ["delnode_cluster_cmd", 3, "host:port node_id"],
|
||||
"set-timeout" => ["set_timeout_cluster_cmd", 3, "host:port milliseconds"],
|
||||
@@ -1654,10 +1308,8 @@ COMMANDS={
|
||||
ALLOWED_OPTIONS={
|
||||
"create" => {"replicas" => true},
|
||||
"add-node" => {"slave" => false, "master-id" => true},
|
||||
"import" => {"from" => :required, "copy" => false, "replace" => false},
|
||||
"reshard" => {"from" => true, "to" => true, "slots" => true, "yes" => false, "timeout" => true, "pipeline" => true},
|
||||
"rebalance" => {"weight" => [], "auto-weights" => false, "use-empty-masters" => false, "timeout" => true, "simulate" => false, "pipeline" => true, "threshold" => true},
|
||||
"fix" => {"timeout" => MigrateDefaultTimeout},
|
||||
"import" => {"from" => :required},
|
||||
"reshard" => {"from" => true, "to" => true, "slots" => true, "yes" => false}
|
||||
}
|
||||
|
||||
def show_help
|
||||
|
||||
+161
-168
@@ -129,7 +129,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"strlen",strlenCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0},
|
||||
{"exists",existsCommand,-2,"rF",0,NULL,1,-1,1,0,0},
|
||||
{"exists",existsCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"getbit",getbitCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
@@ -247,7 +247,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"pttl",pttlCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"persist",persistCommand,2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"slaveof",slaveofCommand,3,"ast",0,NULL,0,0,0,0,0},
|
||||
{"role",roleCommand,1,"lst",0,NULL,0,0,0,0,0},
|
||||
{"role",roleCommand,1,"last",0,NULL,0,0,0,0,0},
|
||||
{"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
{"config",configCommand,-2,"art",0,NULL,0,0,0,0,0},
|
||||
{"subscribe",subscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0},
|
||||
@@ -259,19 +259,19 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"watch",watchCommand,-2,"rsF",0,NULL,1,-1,1,0,0},
|
||||
{"unwatch",unwatchCommand,1,"rsF",0,NULL,0,0,0,0,0},
|
||||
{"cluster",clusterCommand,-2,"ar",0,NULL,0,0,0,0,0},
|
||||
{"restore",restoreCommand,-4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"restore-asking",restoreCommand,-4,"wmk",0,NULL,1,1,1,0,0},
|
||||
{"migrate",migrateCommand,-6,"w",0,migrateGetKeys,0,0,0,0,0},
|
||||
{"restore",restoreCommand,-4,"awm",0,NULL,1,1,1,0,0},
|
||||
{"restore-asking",restoreCommand,-4,"awmk",0,NULL,1,1,1,0,0},
|
||||
{"migrate",migrateCommand,-6,"aw",0,NULL,0,0,0,0,0},
|
||||
{"asking",askingCommand,1,"r",0,NULL,0,0,0,0,0},
|
||||
{"readonly",readonlyCommand,1,"rF",0,NULL,0,0,0,0,0},
|
||||
{"readwrite",readwriteCommand,1,"rF",0,NULL,0,0,0,0,0},
|
||||
{"dump",dumpCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"dump",dumpCommand,2,"ar",0,NULL,1,1,1,0,0},
|
||||
{"object",objectCommand,3,"r",0,NULL,2,2,2,0,0},
|
||||
{"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"ars",0,NULL,0,0,0,0,0},
|
||||
{"eval",evalCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
{"evalsha",evalShaCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
|
||||
{"slowlog",slowlogCommand,-2,"r",0,NULL,0,0,0,0,0},
|
||||
{"script",scriptCommand,-2,"rs",0,NULL,0,0,0,0,0},
|
||||
{"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0},
|
||||
{"time",timeCommand,1,"rRF",0,NULL,0,0,0,0,0},
|
||||
{"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
|
||||
{"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0},
|
||||
@@ -280,7 +280,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"command",commandCommand,0,"rlt",0,NULL,0,0,0,0,0},
|
||||
{"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0},
|
||||
{"pfadd",pfaddCommand,-2,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"pfcount",pfcountCommand,-2,"r",0,NULL,1,-1,1,0,0},
|
||||
{"pfcount",pfcountCommand,-2,"w",0,NULL,1,1,1,0,0},
|
||||
{"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0},
|
||||
{"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0},
|
||||
{"latency",latencyCommand,-2,"arslt",0,NULL,0,0,0,0,0}
|
||||
@@ -876,38 +876,32 @@ unsigned int getLRUClock(void) {
|
||||
}
|
||||
|
||||
/* Add a sample to the operations per second array of samples. */
|
||||
void trackInstantaneousMetric(int metric, long long current_reading) {
|
||||
long long t = mstime() - server.inst_metric[metric].last_sample_time;
|
||||
long long ops = current_reading -
|
||||
server.inst_metric[metric].last_sample_count;
|
||||
void trackOperationsPerSecond(void) {
|
||||
long long t = mstime() - server.ops_sec_last_sample_time;
|
||||
long long ops = server.stat_numcommands - server.ops_sec_last_sample_ops;
|
||||
long long ops_sec;
|
||||
|
||||
ops_sec = t > 0 ? (ops*1000/t) : 0;
|
||||
|
||||
server.inst_metric[metric].samples[server.inst_metric[metric].idx] =
|
||||
ops_sec;
|
||||
server.inst_metric[metric].idx++;
|
||||
server.inst_metric[metric].idx %= REDIS_METRIC_SAMPLES;
|
||||
server.inst_metric[metric].last_sample_time = mstime();
|
||||
server.inst_metric[metric].last_sample_count = current_reading;
|
||||
server.ops_sec_samples[server.ops_sec_idx] = ops_sec;
|
||||
server.ops_sec_idx = (server.ops_sec_idx+1) % REDIS_OPS_SEC_SAMPLES;
|
||||
server.ops_sec_last_sample_time = mstime();
|
||||
server.ops_sec_last_sample_ops = server.stat_numcommands;
|
||||
}
|
||||
|
||||
/* Return the mean of all the samples. */
|
||||
long long getInstantaneousMetric(int metric) {
|
||||
long long getOperationsPerSecond(void) {
|
||||
int j;
|
||||
long long sum = 0;
|
||||
|
||||
for (j = 0; j < REDIS_METRIC_SAMPLES; j++)
|
||||
sum += server.inst_metric[metric].samples[j];
|
||||
return sum / REDIS_METRIC_SAMPLES;
|
||||
for (j = 0; j < REDIS_OPS_SEC_SAMPLES; j++)
|
||||
sum += server.ops_sec_samples[j];
|
||||
return sum / REDIS_OPS_SEC_SAMPLES;
|
||||
}
|
||||
|
||||
/* Check for timeouts. Returns non-zero if the client was terminated.
|
||||
* The function gets the current time in milliseconds as argument since
|
||||
* it gets called multiple times in a loop, so calling gettimeofday() for
|
||||
* each iteration would be costly without any actual gain. */
|
||||
int clientsCronHandleTimeout(redisClient *c, mstime_t now_ms) {
|
||||
time_t now = now_ms/1000;
|
||||
/* Check for timeouts. Returns non-zero if the client was terminated */
|
||||
int clientsCronHandleTimeout(redisClient *c) {
|
||||
time_t now = server.unixtime;
|
||||
|
||||
if (server.maxidletime &&
|
||||
!(c->flags & REDIS_SLAVE) && /* no timeout for slaves */
|
||||
@@ -923,16 +917,11 @@ int clientsCronHandleTimeout(redisClient *c, mstime_t now_ms) {
|
||||
/* Blocked OPS timeout is handled with milliseconds resolution.
|
||||
* However note that the actual resolution is limited by
|
||||
* server.hz. */
|
||||
mstime_t now_ms = mstime();
|
||||
|
||||
if (c->bpop.timeout != 0 && c->bpop.timeout < now_ms) {
|
||||
/* Handle blocking operation specific timeout. */
|
||||
replyToBlockedClientTimedOut(c);
|
||||
unblockClient(c);
|
||||
} else if (server.cluster_enabled) {
|
||||
/* Cluster: handle unblock & redirect of clients blocked
|
||||
* into keys no longer served by this server. */
|
||||
if (clusterRedirectBlockedClientIfNeeded(c))
|
||||
unblockClient(c);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@@ -964,23 +953,17 @@ int clientsCronResizeQueryBuffer(redisClient *c) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CLIENTS_CRON_MIN_ITERATIONS 5
|
||||
void clientsCron(void) {
|
||||
/* Make sure to process at least numclients/server.hz of clients
|
||||
* per call. Since this function is called server.hz times per second
|
||||
* we are sure that in the worst case we process all the clients in 1
|
||||
* second. */
|
||||
/* Make sure to process at least 1/(server.hz*10) of clients per call.
|
||||
* Since this function is called server.hz times per second we are sure that
|
||||
* in the worst case we process all the clients in 10 seconds.
|
||||
* In normal conditions (a reasonable number of clients) we process
|
||||
* all the clients in a shorter time. */
|
||||
int numclients = listLength(server.clients);
|
||||
int iterations = numclients/server.hz;
|
||||
mstime_t now = mstime();
|
||||
|
||||
/* Process at least a few clients while we are at it, even if we need
|
||||
* to process less than CLIENTS_CRON_MIN_ITERATIONS to meet our contract
|
||||
* of processing each client once per second. */
|
||||
if (iterations < CLIENTS_CRON_MIN_ITERATIONS)
|
||||
iterations = (numclients < CLIENTS_CRON_MIN_ITERATIONS) ?
|
||||
numclients : CLIENTS_CRON_MIN_ITERATIONS;
|
||||
int iterations = numclients/(server.hz*10);
|
||||
|
||||
if (iterations < 50)
|
||||
iterations = (numclients < 50) ? numclients : 50;
|
||||
while(listLength(server.clients) && iterations--) {
|
||||
redisClient *c;
|
||||
listNode *head;
|
||||
@@ -994,7 +977,7 @@ void clientsCron(void) {
|
||||
/* The following functions do different service checks on the client.
|
||||
* The protocol is that they return non-zero if the client was
|
||||
* terminated. */
|
||||
if (clientsCronHandleTimeout(c,now)) continue;
|
||||
if (clientsCronHandleTimeout(c)) continue;
|
||||
if (clientsCronResizeQueryBuffer(c)) continue;
|
||||
}
|
||||
}
|
||||
@@ -1085,13 +1068,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
/* Update the time cache. */
|
||||
updateCachedTime();
|
||||
|
||||
run_with_period(100) {
|
||||
trackInstantaneousMetric(REDIS_METRIC_COMMAND,server.stat_numcommands);
|
||||
trackInstantaneousMetric(REDIS_METRIC_NET_INPUT,
|
||||
server.stat_net_input_bytes);
|
||||
trackInstantaneousMetric(REDIS_METRIC_NET_OUTPUT,
|
||||
server.stat_net_output_bytes);
|
||||
}
|
||||
run_with_period(100) trackOperationsPerSecond();
|
||||
|
||||
/* We have just REDIS_LRU_BITS bits per object for LRU information.
|
||||
* So we use an (eventually wrapping) LRU clock.
|
||||
@@ -1172,13 +1149,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
|
||||
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
|
||||
|
||||
if (pid == -1) {
|
||||
redisLog(LOG_WARNING,"wait3() returned an error: %s. "
|
||||
"rdb_child_pid = %d, aof_child_pid = %d",
|
||||
strerror(errno),
|
||||
(int) server.rdb_child_pid,
|
||||
(int) server.aof_child_pid);
|
||||
} else if (pid == server.rdb_child_pid) {
|
||||
if (pid == server.rdb_child_pid) {
|
||||
backgroundSaveDoneHandler(exitcode,bysignal);
|
||||
} else if (pid == server.aof_child_pid) {
|
||||
backgroundRewriteDoneHandler(exitcode,bysignal);
|
||||
@@ -1277,12 +1248,6 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
REDIS_NOTUSED(eventLoop);
|
||||
|
||||
/* Call the Redis Cluster before sleep function. Note that this function
|
||||
* may change the state of Redis Cluster (from ok to fail or vice versa),
|
||||
* so it's a good idea to call it before serving the unblocked clients
|
||||
* later in this function. */
|
||||
if (server.cluster_enabled) clusterBeforeSleep();
|
||||
|
||||
/* Run a fast expire cycle (the called function will return
|
||||
* ASAP if a fast cycle is not needed). */
|
||||
if (server.active_expire_enabled && server.masterhost == NULL)
|
||||
@@ -1314,6 +1279,9 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
|
||||
/* Write the AOF buffer on disk */
|
||||
flushAppendOnlyFile(0);
|
||||
|
||||
/* Call the Redis Cluster before sleep function. */
|
||||
if (server.cluster_enabled) clusterBeforeSleep();
|
||||
}
|
||||
|
||||
/* =========================== Server initialization ======================== */
|
||||
@@ -1465,8 +1433,11 @@ void initServerConfig(void) {
|
||||
server.maxclients = REDIS_MAX_CLIENTS;
|
||||
server.bpop_blocked_clients = 0;
|
||||
server.maxmemory = REDIS_DEFAULT_MAXMEMORY;
|
||||
server.maxmemory_frag_guess = REDIS_DEFAULT_MAXMEMORY_FRAG_GUESS;
|
||||
server.maxmemory_enforced = (double) REDIS_DEFAULT_MAXMEMORY / server.maxmemory_frag_guess;
|
||||
server.maxmemory_policy = REDIS_DEFAULT_MAXMEMORY_POLICY;
|
||||
server.maxmemory_samples = REDIS_DEFAULT_MAXMEMORY_SAMPLES;
|
||||
server.rss_aware_maxmemory = REDIS_DEFAULT_RSS_AWARE_MAXMEMORY;
|
||||
server.hash_max_ziplist_entries = REDIS_HASH_MAX_ZIPLIST_ENTRIES;
|
||||
server.hash_max_ziplist_value = REDIS_HASH_MAX_ZIPLIST_VALUE;
|
||||
server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES;
|
||||
@@ -1586,33 +1557,33 @@ void adjustOpenFilesLimit(void) {
|
||||
/* Set the max number of files if the current limit is not enough
|
||||
* for our needs. */
|
||||
if (oldlimit < maxfiles) {
|
||||
rlim_t bestlimit;
|
||||
rlim_t f;
|
||||
int setrlimit_error = 0;
|
||||
|
||||
/* Try to set the file limit to match 'maxfiles' or at least
|
||||
* to the higher value supported less than maxfiles. */
|
||||
bestlimit = maxfiles;
|
||||
while(bestlimit > oldlimit) {
|
||||
f = maxfiles;
|
||||
while(f > oldlimit) {
|
||||
rlim_t decr_step = 16;
|
||||
|
||||
limit.rlim_cur = bestlimit;
|
||||
limit.rlim_max = bestlimit;
|
||||
limit.rlim_cur = f;
|
||||
limit.rlim_max = f;
|
||||
if (setrlimit(RLIMIT_NOFILE,&limit) != -1) break;
|
||||
setrlimit_error = errno;
|
||||
|
||||
/* We failed to set file limit to 'bestlimit'. Try with a
|
||||
/* We failed to set file limit to 'f'. Try with a
|
||||
* smaller limit decrementing by a few FDs per iteration. */
|
||||
if (bestlimit < decr_step) break;
|
||||
bestlimit -= decr_step;
|
||||
if (f < decr_step) break;
|
||||
f -= decr_step;
|
||||
}
|
||||
|
||||
/* Assume that the limit we get initially is still valid if
|
||||
* our last try was even lower. */
|
||||
if (bestlimit < oldlimit) bestlimit = oldlimit;
|
||||
if (f < oldlimit) f = oldlimit;
|
||||
|
||||
if (bestlimit < maxfiles) {
|
||||
if (f != maxfiles) {
|
||||
int old_maxclients = server.maxclients;
|
||||
server.maxclients = bestlimit-REDIS_MIN_RESERVED_FDS;
|
||||
server.maxclients = f-REDIS_MIN_RESERVED_FDS;
|
||||
if (server.maxclients < 1) {
|
||||
redisLog(REDIS_WARNING,"Your current 'ulimit -n' "
|
||||
"of %llu is not enough for Redis to start. "
|
||||
@@ -1633,7 +1604,7 @@ void adjustOpenFilesLimit(void) {
|
||||
"maxclients has been reduced to %d to compensate for "
|
||||
"low ulimit. "
|
||||
"If you need higher maxclients increase 'ulimit -n'.",
|
||||
(unsigned long long) bestlimit, server.maxclients);
|
||||
(unsigned long long) oldlimit, server.maxclients);
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,"Increased maximum number of open files "
|
||||
"to %llu (it was originally set to %llu).",
|
||||
@@ -1644,23 +1615,6 @@ void adjustOpenFilesLimit(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Check that server.tcp_backlog can be actually enforced in Linux according
|
||||
* to the value of /proc/sys/net/core/somaxconn, or warn about it. */
|
||||
void checkTcpBacklogSettings(void) {
|
||||
#ifdef HAVE_PROC_SOMAXCONN
|
||||
FILE *fp = fopen("/proc/sys/net/core/somaxconn","r");
|
||||
char buf[1024];
|
||||
if (!fp) return;
|
||||
if (fgets(buf,sizeof(buf),fp) != NULL) {
|
||||
int somaxconn = atoi(buf);
|
||||
if (somaxconn > 0 && somaxconn < server.tcp_backlog) {
|
||||
redisLog(REDIS_WARNING,"WARNING: The TCP backlog setting of %d cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of %d.", server.tcp_backlog, somaxconn);
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Initialize a set of file descriptors to listen to the specified 'port'
|
||||
* binding the addresses specified in the Redis server configuration.
|
||||
*
|
||||
@@ -1731,8 +1685,6 @@ int listenToPort(int port, int *fds, int *count) {
|
||||
* to reset via CONFIG RESETSTAT. The function is also used in order to
|
||||
* initialize these fields in initServer() at server startup. */
|
||||
void resetServerStats(void) {
|
||||
int j;
|
||||
|
||||
server.stat_numcommands = 0;
|
||||
server.stat_numconnections = 0;
|
||||
server.stat_expiredkeys = 0;
|
||||
@@ -1745,16 +1697,10 @@ void resetServerStats(void) {
|
||||
server.stat_sync_full = 0;
|
||||
server.stat_sync_partial_ok = 0;
|
||||
server.stat_sync_partial_err = 0;
|
||||
for (j = 0; j < REDIS_METRIC_COUNT; j++) {
|
||||
server.inst_metric[j].idx = 0;
|
||||
server.inst_metric[j].last_sample_time = mstime();
|
||||
server.inst_metric[j].last_sample_count = 0;
|
||||
memset(server.inst_metric[j].samples,0,
|
||||
sizeof(server.inst_metric[j].samples));
|
||||
}
|
||||
server.stat_net_input_bytes = 0;
|
||||
server.stat_net_output_bytes = 0;
|
||||
server.aof_delayed_fsync = 0;
|
||||
memset(server.ops_sec_samples,0,sizeof(server.ops_sec_samples));
|
||||
server.ops_sec_idx = 0;
|
||||
server.ops_sec_last_sample_time = mstime();
|
||||
server.ops_sec_last_sample_ops = 0;
|
||||
}
|
||||
|
||||
void initServer(void) {
|
||||
@@ -1798,7 +1744,7 @@ void initServer(void) {
|
||||
server.sofd = anetUnixServer(server.neterr,server.unixsocket,
|
||||
server.unixsocketperm, server.tcp_backlog);
|
||||
if (server.sofd == ANET_ERR) {
|
||||
redisLog(REDIS_WARNING, "Opening Unix socket: %s", server.neterr);
|
||||
redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);
|
||||
exit(1);
|
||||
}
|
||||
anetNonBlock(NULL,server.sofd);
|
||||
@@ -2055,7 +2001,7 @@ void call(redisClient *c, int flags) {
|
||||
* not generated from reading an AOF. */
|
||||
if (listLength(server.monitors) &&
|
||||
!server.loading &&
|
||||
!(c->cmd->flags & (REDIS_CMD_SKIP_MONITOR|REDIS_CMD_ADMIN)))
|
||||
!(c->cmd->flags & REDIS_CMD_SKIP_MONITOR))
|
||||
{
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
}
|
||||
@@ -2179,22 +2125,36 @@ int processCommand(redisClient *c) {
|
||||
* 2) The command has no key arguments. */
|
||||
if (server.cluster_enabled &&
|
||||
!(c->flags & REDIS_MASTER) &&
|
||||
!(c->flags & REDIS_LUA_CLIENT &&
|
||||
server.lua_caller->flags & REDIS_MASTER) &&
|
||||
!(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0))
|
||||
{
|
||||
int hashslot;
|
||||
|
||||
if (server.cluster->state != REDIS_CLUSTER_OK) {
|
||||
flagTransaction(c);
|
||||
clusterRedirectClient(c,NULL,0,REDIS_CLUSTER_REDIR_DOWN_STATE);
|
||||
addReplySds(c,sdsnew("-CLUSTERDOWN The cluster is down. Use CLUSTER INFO for more information\r\n"));
|
||||
return REDIS_OK;
|
||||
} else {
|
||||
int error_code;
|
||||
clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,&hashslot,&error_code);
|
||||
if (n == NULL || n != server.cluster->myself) {
|
||||
if (n == NULL) {
|
||||
flagTransaction(c);
|
||||
clusterRedirectClient(c,n,hashslot,error_code);
|
||||
if (error_code == REDIS_CLUSTER_REDIR_CROSS_SLOT) {
|
||||
addReplySds(c,sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n"));
|
||||
} else if (error_code == REDIS_CLUSTER_REDIR_UNSTABLE) {
|
||||
/* The request spawns mutliple keys in the same slot,
|
||||
* but the slot is not "stable" currently as there is
|
||||
* a migration or import in progress. */
|
||||
addReplySds(c,sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n"));
|
||||
} else {
|
||||
redisPanic("getNodeByQuery() unknown error.");
|
||||
}
|
||||
return REDIS_OK;
|
||||
} else if (n != server.cluster->myself) {
|
||||
flagTransaction(c);
|
||||
addReplySds(c,sdscatprintf(sdsempty(),
|
||||
"-%s %d %s:%d\r\n",
|
||||
(error_code == REDIS_CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
|
||||
hashslot,n->ip,n->port));
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
@@ -2207,12 +2167,6 @@ int processCommand(redisClient *c) {
|
||||
* is returning an error. */
|
||||
if (server.maxmemory) {
|
||||
int retval = freeMemoryIfNeeded();
|
||||
/* freeMemoryIfNeeded may flush slave output buffers. This may result
|
||||
* into a slave, that may be the active client, to be freed. */
|
||||
if (server.current_client == NULL) return REDIS_ERR;
|
||||
|
||||
/* It was impossible to free enough memory, and the command the client
|
||||
* is trying to execute is denied during OOM conditions? Error. */
|
||||
if ((c->cmd->flags & REDIS_CMD_DENYOOM) && retval == REDIS_ERR) {
|
||||
flagTransaction(c);
|
||||
addReply(c, shared.oomerr);
|
||||
@@ -2271,7 +2225,7 @@ int processCommand(redisClient *c) {
|
||||
c->cmd->proc != unsubscribeCommand &&
|
||||
c->cmd->proc != psubscribeCommand &&
|
||||
c->cmd->proc != punsubscribeCommand) {
|
||||
addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context");
|
||||
addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context");
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -2806,14 +2760,14 @@ sds genRedisInfoString(char *section) {
|
||||
server.loading_loaded_bytes;
|
||||
|
||||
perc = ((double)server.loading_loaded_bytes /
|
||||
(server.loading_total_bytes+1)) * 100;
|
||||
server.loading_total_bytes) * 100;
|
||||
|
||||
elapsed = time(NULL)-server.loading_start_time;
|
||||
elapsed = server.unixtime-server.loading_start_time;
|
||||
if (elapsed == 0) {
|
||||
eta = 1; /* A fake 1 second figure if we don't have
|
||||
enough info */
|
||||
} else {
|
||||
eta = (elapsed*remaining_bytes)/(server.loading_loaded_bytes+1);
|
||||
eta = (elapsed*remaining_bytes)/server.loading_loaded_bytes;
|
||||
}
|
||||
|
||||
info = sdscatprintf(info,
|
||||
@@ -2839,10 +2793,6 @@ sds genRedisInfoString(char *section) {
|
||||
"total_connections_received:%lld\r\n"
|
||||
"total_commands_processed:%lld\r\n"
|
||||
"instantaneous_ops_per_sec:%lld\r\n"
|
||||
"total_net_input_bytes:%lld\r\n"
|
||||
"total_net_output_bytes:%lld\r\n"
|
||||
"instantaneous_input_kbps:%.2f\r\n"
|
||||
"instantaneous_output_kbps:%.2f\r\n"
|
||||
"rejected_connections:%lld\r\n"
|
||||
"sync_full:%lld\r\n"
|
||||
"sync_partial_ok:%lld\r\n"
|
||||
@@ -2857,11 +2807,7 @@ sds genRedisInfoString(char *section) {
|
||||
"migrate_cached_sockets:%ld\r\n",
|
||||
server.stat_numconnections,
|
||||
server.stat_numcommands,
|
||||
getInstantaneousMetric(REDIS_METRIC_COMMAND),
|
||||
server.stat_net_input_bytes,
|
||||
server.stat_net_output_bytes,
|
||||
(float)getInstantaneousMetric(REDIS_METRIC_NET_INPUT)/1024,
|
||||
(float)getInstantaneousMetric(REDIS_METRIC_NET_OUTPUT)/1024,
|
||||
getOperationsPerSecond(),
|
||||
server.stat_rejected_conn,
|
||||
server.stat_sync_full,
|
||||
server.stat_sync_partial_ok,
|
||||
@@ -3152,7 +3098,13 @@ void evictionPoolPopulate(dict *sampledict, dict *keydict, struct evictionPoolEn
|
||||
samples = zmalloc(sizeof(samples[0])*server.maxmemory_samples);
|
||||
}
|
||||
|
||||
count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples);
|
||||
#if 1 /* Use bulk get by default. */
|
||||
count = dictGetRandomKeys(sampledict,samples,server.maxmemory_samples);
|
||||
#else
|
||||
count = server.maxmemory_samples;
|
||||
for (j = 0; j < count; j++) samples[j] = dictGetRandomKey(sampledict);
|
||||
#endif
|
||||
|
||||
for (j = 0; j < count; j++) {
|
||||
unsigned long long idle;
|
||||
sds key;
|
||||
@@ -3205,9 +3157,9 @@ void evictionPoolPopulate(dict *sampledict, dict *keydict, struct evictionPoolEn
|
||||
}
|
||||
|
||||
int freeMemoryIfNeeded(void) {
|
||||
size_t mem_used, mem_tofree, mem_freed;
|
||||
size_t mem_used, mem_tofree, mem_freed, mem_target = server.maxmemory;
|
||||
int slaves = listLength(server.slaves);
|
||||
mstime_t latency, eviction_latency;
|
||||
mstime_t latency;
|
||||
|
||||
/* Remove the size of slaves output buffers and AOF buffer from the
|
||||
* count of used memory. */
|
||||
@@ -3231,14 +3183,72 @@ int freeMemoryIfNeeded(void) {
|
||||
mem_used -= aofRewriteBufferSize();
|
||||
}
|
||||
|
||||
/* If we use RSS aware maxmemory, update the target memory using
|
||||
* the current fragmentation figure. */
|
||||
#ifdef HAVE_RSS_REPORTING
|
||||
if (server.rss_aware_maxmemory &&
|
||||
server.maxmemory_policy != REDIS_MAXMEMORY_NO_EVICTION)
|
||||
{
|
||||
static unsigned long iterations = 0;
|
||||
static unsigned long sampling_stage = 1;
|
||||
static float last_observed_frag = 0;
|
||||
|
||||
/* For some time, we analyze what happens during memory pressure, when
|
||||
* objects are evicted and reallocated. */
|
||||
if (mem_used > server.maxmemory_enforced) {
|
||||
unsigned long sample_cycles = 1000000;
|
||||
|
||||
/* Every sample_cycle cycles we sample the fragmentation, and
|
||||
* compare it with the previos one. If it is no longer raising,
|
||||
* we take it as a guess of the fragmentation with this workload. */
|
||||
if (sampling_stage && iterations < sample_cycles) {
|
||||
iterations++;
|
||||
if (iterations == sample_cycles) {
|
||||
float current_frag = zmalloc_get_fragmentation_ratio(server.resident_set_size);
|
||||
if (last_observed_frag == 0) {
|
||||
/* First sample we get. */
|
||||
last_observed_frag = current_frag;
|
||||
} else {
|
||||
if (current_frag <= last_observed_frag) {
|
||||
size_t enforced_new;
|
||||
|
||||
sampling_stage = 0;
|
||||
server.maxmemory_frag_guess = current_frag;
|
||||
/* Update the global fragmentation guess and use
|
||||
* it (also used it for successive
|
||||
* "CONFIG SET maxmemory" commands). */
|
||||
if (server.maxmemory_frag_guess < 1)
|
||||
server.maxmemory_frag_guess = 1;
|
||||
else if (server.maxmemory_frag_guess > 2)
|
||||
server.maxmemory_frag_guess = 2;
|
||||
|
||||
/* Only set the new limit if it is higher than our
|
||||
* initial guess, otherwise it is futile: RSS will
|
||||
* not go backward anyway. */
|
||||
enforced_new = (double) server.maxmemory /
|
||||
server.maxmemory_frag_guess;
|
||||
if (enforced_new > server.maxmemory_enforced)
|
||||
server.maxmemory_enforced = enforced_new;
|
||||
redisLog(REDIS_NOTICE,"RSS aware maxmemory, fragmentation looks stable at: %f", server.maxmemory_frag_guess);
|
||||
}
|
||||
last_observed_frag = current_frag;
|
||||
}
|
||||
iterations = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
mem_target = server.maxmemory_enforced;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Check if we are over the memory limit. */
|
||||
if (mem_used <= server.maxmemory) return REDIS_OK;
|
||||
if (mem_used <= mem_target) return REDIS_OK;
|
||||
|
||||
if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION)
|
||||
return REDIS_ERR; /* We need to free memory, but policy forbids. */
|
||||
|
||||
/* Compute how much memory we need to free. */
|
||||
mem_tofree = mem_used - server.maxmemory;
|
||||
mem_tofree = mem_used - mem_target;
|
||||
mem_freed = 0;
|
||||
latencyStartMonitor(latency);
|
||||
while (mem_freed < mem_tofree) {
|
||||
@@ -3338,11 +3348,7 @@ int freeMemoryIfNeeded(void) {
|
||||
* AOF and Output buffer memory will be freed eventually so
|
||||
* we only care about memory used by the key space. */
|
||||
delta = (long long) zmalloc_used_memory();
|
||||
latencyStartMonitor(eviction_latency);
|
||||
dbDelete(db,keyobj);
|
||||
latencyEndMonitor(eviction_latency);
|
||||
latencyAddSampleIfNeeded("eviction-del",eviction_latency);
|
||||
latencyRemoveNestedEvent(latency,eviction_latency);
|
||||
delta -= (long long) zmalloc_used_memory();
|
||||
mem_freed += delta;
|
||||
server.stat_evictedkeys++;
|
||||
@@ -3459,27 +3465,15 @@ void redisAsciiArt(void) {
|
||||
else if (server.sentinel_mode) mode = "sentinel";
|
||||
else mode = "standalone";
|
||||
|
||||
if (server.syslog_enabled) {
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Redis %s (%s/%d) %s bit, %s mode, port %d, pid %ld ready to start.",
|
||||
REDIS_VERSION,
|
||||
redisGitSHA1(),
|
||||
strtol(redisGitDirty(),NULL,10) > 0,
|
||||
(sizeof(long) == 8) ? "64" : "32",
|
||||
mode, server.port,
|
||||
(long) getpid()
|
||||
);
|
||||
} else {
|
||||
snprintf(buf,1024*16,ascii_logo,
|
||||
REDIS_VERSION,
|
||||
redisGitSHA1(),
|
||||
strtol(redisGitDirty(),NULL,10) > 0,
|
||||
(sizeof(long) == 8) ? "64" : "32",
|
||||
mode, server.port,
|
||||
(long) getpid()
|
||||
);
|
||||
redisLogRaw(REDIS_NOTICE|REDIS_LOG_RAW,buf);
|
||||
}
|
||||
snprintf(buf,1024*16,ascii_logo,
|
||||
REDIS_VERSION,
|
||||
redisGitSHA1(),
|
||||
strtol(redisGitDirty(),NULL,10) > 0,
|
||||
(sizeof(long) == 8) ? "64" : "32",
|
||||
mode, server.port,
|
||||
(long) getpid()
|
||||
);
|
||||
redisLogRaw(REDIS_NOTICE|REDIS_LOG_RAW,buf);
|
||||
zfree(buf);
|
||||
}
|
||||
|
||||
@@ -3672,7 +3666,6 @@ int main(int argc, char **argv) {
|
||||
if (server.daemonize) createPidFile();
|
||||
redisSetProcTitle(argv[0]);
|
||||
redisAsciiArt();
|
||||
checkTcpBacklogSettings();
|
||||
|
||||
if (!server.sentinel_mode) {
|
||||
/* Things not needed when running in Sentinel mode. */
|
||||
|
||||
+26
-51
@@ -32,7 +32,10 @@
|
||||
|
||||
#include "fmacros.h"
|
||||
#include "config.h"
|
||||
|
||||
#if defined(__sun)
|
||||
#include "solarisfixes.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -94,6 +97,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDIS_REPL_PING_SLAVE_PERIOD 10
|
||||
#define REDIS_RUN_ID_SIZE 40
|
||||
#define REDIS_EOF_MARK_SIZE 40
|
||||
#define REDIS_OPS_SEC_SAMPLES 16
|
||||
#define REDIS_DEFAULT_REPL_BACKLOG_SIZE (1024*1024) /* 1mb */
|
||||
#define REDIS_DEFAULT_REPL_BACKLOG_TIME_LIMIT (60*60) /* 1 hour */
|
||||
#define REDIS_REPL_BACKLOG_MIN_SIZE (1024*16) /* 16k */
|
||||
@@ -117,6 +121,8 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY 0
|
||||
#define REDIS_DEFAULT_MAXMEMORY 0
|
||||
#define REDIS_DEFAULT_MAXMEMORY_SAMPLES 5
|
||||
#define REDIS_DEFAULT_MAXMEMORY_FRAG_GUESS 1.4
|
||||
#define REDIS_DEFAULT_RSS_AWARE_MAXMEMORY 0
|
||||
#define REDIS_DEFAULT_AOF_FILENAME "appendonly.aof"
|
||||
#define REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE 0
|
||||
#define REDIS_DEFAULT_AOF_LOAD_TRUNCATED 1
|
||||
@@ -124,7 +130,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1
|
||||
#define REDIS_DEFAULT_MIN_SLAVES_TO_WRITE 0
|
||||
#define REDIS_DEFAULT_MIN_SLAVES_MAX_LAG 10
|
||||
#define REDIS_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */
|
||||
#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
|
||||
@@ -136,13 +142,6 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_SLOW 0
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST 1
|
||||
|
||||
/* Instantaneous metrics tracking. */
|
||||
#define REDIS_METRIC_SAMPLES 16 /* Number of samples per metric. */
|
||||
#define REDIS_METRIC_COMMAND 0 /* Number of commands executed. */
|
||||
#define REDIS_METRIC_NET_INPUT 1 /* Bytes read to network .*/
|
||||
#define REDIS_METRIC_NET_OUTPUT 2 /* Bytes written to network. */
|
||||
#define REDIS_METRIC_COUNT 3
|
||||
|
||||
/* Protocol and I/O related defines */
|
||||
#define REDIS_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */
|
||||
#define REDIS_IOBUF_LEN (1024*16) /* Generic I/O buffer size */
|
||||
@@ -267,37 +266,22 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDIS_CLIENT_TYPE_PUBSUB 2 /* Clients subscribed to PubSub channels. */
|
||||
#define REDIS_CLIENT_TYPE_COUNT 3
|
||||
|
||||
/* Slave replication state. Used in server.repl_state for slaves to remember
|
||||
* what to do next. */
|
||||
/* Slave replication state - from the point of view of the slave. */
|
||||
#define REDIS_REPL_NONE 0 /* No active replication */
|
||||
#define REDIS_REPL_CONNECT 1 /* Must connect to master */
|
||||
#define REDIS_REPL_CONNECTING 2 /* Connecting to master */
|
||||
/* --- Handshake states, must be ordered --- */
|
||||
#define REDIS_REPL_RECEIVE_PONG 3 /* Wait for PING reply */
|
||||
#define REDIS_REPL_SEND_AUTH 4 /* Send AUTH to master */
|
||||
#define REDIS_REPL_RECEIVE_AUTH 5 /* Wait for AUTH reply */
|
||||
#define REDIS_REPL_SEND_PORT 6 /* Send REPLCONF listening-port */
|
||||
#define REDIS_REPL_RECEIVE_PORT 7 /* Wait for REPLCONF reply */
|
||||
#define REDIS_REPL_SEND_CAPA 8 /* Send REPLCONF capa */
|
||||
#define REDIS_REPL_RECEIVE_CAPA 9 /* Wait for REPLCONF reply */
|
||||
#define REDIS_REPL_SEND_PSYNC 10 /* Send PSYNC */
|
||||
#define REDIS_REPL_RECEIVE_PSYNC 11 /* Wait for PSYNC reply */
|
||||
/* --- End of handshake states --- */
|
||||
#define REDIS_REPL_TRANSFER 12 /* Receiving .rdb from master */
|
||||
#define REDIS_REPL_CONNECTED 13 /* Connected to master */
|
||||
#define REDIS_REPL_TRANSFER 4 /* Receiving .rdb from master */
|
||||
#define REDIS_REPL_CONNECTED 5 /* Connected to master */
|
||||
|
||||
/* State of slaves from the POV of the master. Used in client->replstate.
|
||||
/* Slave replication state - from the point of view of the master.
|
||||
* In SEND_BULK and ONLINE state the slave receives new updates
|
||||
* in its output queue. In the WAIT_BGSAVE state instead the server is waiting
|
||||
* to start the next background saving in order to send updates to it. */
|
||||
#define REDIS_REPL_WAIT_BGSAVE_START 14 /* We need to produce a new RDB file. */
|
||||
#define REDIS_REPL_WAIT_BGSAVE_END 15 /* Waiting RDB file creation to finish. */
|
||||
#define REDIS_REPL_SEND_BULK 16 /* Sending RDB file to slave. */
|
||||
#define REDIS_REPL_ONLINE 17 /* RDB file transmitted, sending just updates. */
|
||||
|
||||
/* Slave capabilities. */
|
||||
#define SLAVE_CAPA_NONE 0
|
||||
#define SLAVE_CAPA_EOF (1<<0) /* Can parse the RDB EOF streaming format. */
|
||||
#define REDIS_REPL_WAIT_BGSAVE_START 6 /* We need to produce a new RDB file. */
|
||||
#define REDIS_REPL_WAIT_BGSAVE_END 7 /* Waiting RDB file creation to finish. */
|
||||
#define REDIS_REPL_SEND_BULK 8 /* Sending RDB file to slave. */
|
||||
#define REDIS_REPL_ONLINE 9 /* RDB file transmitted, sending just updates. */
|
||||
|
||||
/* Synchronous read timeout - slave side */
|
||||
#define REDIS_REPL_SYNCIO_TIMEOUT 5
|
||||
@@ -558,12 +542,8 @@ typedef struct redisClient {
|
||||
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 */
|
||||
long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
|
||||
copying this slave output buffer
|
||||
should use. */
|
||||
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 */
|
||||
int slave_capa; /* Slave capabilities: SLAVE_CAPA_* bitwise OR. */
|
||||
multiState mstate; /* MULTI/EXEC state */
|
||||
int btype; /* Type of blocking op if REDIS_BLOCKED. */
|
||||
blockingState bpop; /* blocking state */
|
||||
@@ -732,16 +712,12 @@ struct redisServer {
|
||||
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(). */
|
||||
long long stat_net_input_bytes; /* Bytes read from network. */
|
||||
long long stat_net_output_bytes; /* Bytes written to network. */
|
||||
/* The following two are used to track instantaneous metrics, like
|
||||
* number of operations per second, network traffic. */
|
||||
struct {
|
||||
long long last_sample_time; /* Timestamp of last sample in ms */
|
||||
long long last_sample_count;/* Count in last sample */
|
||||
long long samples[REDIS_METRIC_SAMPLES];
|
||||
int idx;
|
||||
} inst_metric[REDIS_METRIC_COUNT];
|
||||
/* 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) */
|
||||
long long ops_sec_last_sample_ops; /* numcommands in last sample */
|
||||
long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES];
|
||||
int ops_sec_idx;
|
||||
/* Configuration */
|
||||
int verbosity; /* Loglevel in redis.conf */
|
||||
int maxidletime; /* Client timeout in seconds */
|
||||
@@ -865,6 +841,10 @@ struct redisServer {
|
||||
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 */
|
||||
/* RSS aware maxmemory additional state. */
|
||||
int rss_aware_maxmemory; /* Non zero if enabled. */
|
||||
unsigned long long maxmemory_enforced; /* Currently enforced maxmemory. */
|
||||
float maxmemory_frag_guess; /* Guessed fragmentation. */
|
||||
/* Blocked clients */
|
||||
unsigned int bpop_blocked_clients; /* Number of clients blocked by lists */
|
||||
list *unblocked_clients; /* list of clients to unblock before next loop */
|
||||
@@ -1071,7 +1051,6 @@ sds catClientInfoString(sds s, redisClient *client);
|
||||
sds getAllClientsInfoString(void);
|
||||
void rewriteClientCommandVector(redisClient *c, int argc, ...);
|
||||
void rewriteClientCommandArgument(redisClient *c, int i, robj *newval);
|
||||
void replaceClientCommandVector(redisClient *c, int argc, robj **argv);
|
||||
unsigned long getClientOutputBufferMemoryUsage(redisClient *c);
|
||||
void freeClientsInAsyncFreeQueue(void);
|
||||
void asyncCloseClientOnOutputBufferLimitReached(redisClient *c);
|
||||
@@ -1143,7 +1122,7 @@ robj *tryObjectEncoding(robj *o);
|
||||
robj *getDecodedObject(robj *o);
|
||||
size_t stringObjectLen(robj *o);
|
||||
robj *createStringObjectFromLongLong(long long value);
|
||||
robj *createStringObjectFromLongDouble(long double value, int humanfriendly);
|
||||
robj *createStringObjectFromLongDouble(long double value);
|
||||
robj *createListObject(void);
|
||||
robj *createZiplistObject(void);
|
||||
robj *createSetObject(void);
|
||||
@@ -1191,8 +1170,6 @@ int replicationCountAcksByOffset(long long offset);
|
||||
void replicationSendNewlineToMaster(void);
|
||||
long long replicationGetSlaveOffset(void);
|
||||
char *replicationGetSlaveName(redisClient *c);
|
||||
long long getPsyncInitialOffset(void);
|
||||
int replicationSetupSlaveForFullResync(redisClient *slave, long long offset);
|
||||
|
||||
/* Generic persistence functions */
|
||||
void startLoading(FILE *fp);
|
||||
@@ -1362,7 +1339,6 @@ 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);
|
||||
int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
|
||||
/* Cluster */
|
||||
void clusterInit(void);
|
||||
@@ -1389,7 +1365,6 @@ 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);
|
||||
void disconnectAllBlockedClients(void);
|
||||
|
||||
/* Git SHA1 */
|
||||
char *redisGitSHA1(void);
|
||||
|
||||
+181
-432
@@ -201,7 +201,6 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
|
||||
listRewind(slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) continue;
|
||||
addReply(slave,selectcmd);
|
||||
}
|
||||
|
||||
@@ -350,58 +349,6 @@ long long addReplyReplicationBacklog(redisClient *c, long long offset) {
|
||||
return server.repl_backlog_histlen - skip;
|
||||
}
|
||||
|
||||
/* Return the offset to provide as reply to the PSYNC command received
|
||||
* from the slave. The returned value is only valid immediately after
|
||||
* the BGSAVE process started and before executing any other command
|
||||
* from clients. */
|
||||
long long getPsyncInitialOffset(void) {
|
||||
long long psync_offset = server.master_repl_offset;
|
||||
/* Add 1 to psync_offset if it the replication backlog does not exists
|
||||
* as when it will be created later we'll increment the offset by one. */
|
||||
if (server.repl_backlog == NULL) psync_offset++;
|
||||
return psync_offset;
|
||||
}
|
||||
|
||||
/* Send a FULLRESYNC reply in the specific case of a full resynchronization,
|
||||
* as a side effect setup the slave for a full sync in different ways:
|
||||
*
|
||||
* 1) Remember, into the slave client structure, the offset we sent
|
||||
* here, so that if new slaves will later attach to the same
|
||||
* background RDB saving process (by duplicating this client output
|
||||
* buffer), we can get the right offset from this slave.
|
||||
* 2) Set the replication state of the slave to WAIT_BGSAVE_END so that
|
||||
* we start accumulating differences from this point.
|
||||
* 3) Force the replication stream to re-emit a SELECT statement so
|
||||
* the new slave incremental differences will start selecting the
|
||||
* right database number.
|
||||
*
|
||||
* Normally this function should be called immediately after a successful
|
||||
* BGSAVE for replication was started, or when there is one already in
|
||||
* progress that we attached our slave to. */
|
||||
int replicationSetupSlaveForFullResync(redisClient *slave, long long offset) {
|
||||
char buf[128];
|
||||
int buflen;
|
||||
|
||||
slave->psync_initial_offset = offset;
|
||||
slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
/* We are going to accumulate the incremental changes for this
|
||||
* slave as well. Set slaveseldb to -1 in order to force to re-emit
|
||||
* a SLEECT statement in the replication stream. */
|
||||
server.slaveseldb = -1;
|
||||
|
||||
/* Don't send this reply to slaves that approached us with
|
||||
* the old SYNC command. */
|
||||
if (!(slave->flags & REDIS_PRE_PSYNC)) {
|
||||
buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n",
|
||||
server.runid,offset);
|
||||
if (write(slave->fd,buf,buflen) != buflen) {
|
||||
freeClientAsync(slave);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* This function handles the PSYNC command from the point of view of a
|
||||
* master receiving a request for partial resynchronization.
|
||||
*
|
||||
@@ -475,10 +422,18 @@ int masterTryPartialResynchronization(redisClient *c) {
|
||||
return REDIS_OK; /* The caller can return, no full resync needed. */
|
||||
|
||||
need_full_resync:
|
||||
/* We need a full resync for some reason... Note that we can't
|
||||
* reply to PSYNC right now if a full SYNC is needed. The reply
|
||||
* must include the master offset at the time the RDB file we transfer
|
||||
* is generated, so we need to delay the reply to that moment. */
|
||||
/* We need a full resync for some reason... notify the client. */
|
||||
psync_offset = server.master_repl_offset;
|
||||
/* Add 1 to psync_offset if it the replication backlog does not exists
|
||||
* as when it will be created later we'll increment the offset by one. */
|
||||
if (server.repl_backlog == NULL) psync_offset++;
|
||||
/* Again, we can't use the connection buffers (see above). */
|
||||
buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n",
|
||||
server.runid,psync_offset);
|
||||
if (write(c->fd,buf,buflen) != buflen) {
|
||||
freeClientAsync(c);
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
@@ -486,68 +441,18 @@ need_full_resync:
|
||||
* socket target depending on the configuration, and making sure that
|
||||
* the script cache is flushed before to start.
|
||||
*
|
||||
* The mincapa argument is the bitwise AND among all the slaves capabilities
|
||||
* of the slaves waiting for this BGSAVE, so represents the slave capabilities
|
||||
* all the slaves support. Can be tested via SLAVE_CAPA_* macros.
|
||||
*
|
||||
* Side effects, other than starting a BGSAVE:
|
||||
*
|
||||
* 1) Handle the slaves in WAIT_START state, by preparing them for a full
|
||||
* sync if the BGSAVE was succesfully started, or sending them an error
|
||||
* and dropping them from the list of slaves.
|
||||
*
|
||||
* 2) Flush the Lua scripting script cache if the BGSAVE was actually
|
||||
* started.
|
||||
*
|
||||
* Returns REDIS_OK on success or REDIS_ERR otherwise. */
|
||||
int startBgsaveForReplication(int mincapa) {
|
||||
int startBgsaveForReplication(void) {
|
||||
int retval;
|
||||
int socket_target = server.repl_diskless_sync && (mincapa & SLAVE_CAPA_EOF);
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC with target: %s",
|
||||
socket_target ? "slaves sockets" : "disk");
|
||||
server.repl_diskless_sync ? "slaves sockets" : "disk");
|
||||
|
||||
if (socket_target)
|
||||
if (server.repl_diskless_sync)
|
||||
retval = rdbSaveToSlavesSockets();
|
||||
else
|
||||
retval = rdbSaveBackground(server.rdb_filename);
|
||||
|
||||
/* If we failed to BGSAVE, remove the slaves waiting for a full
|
||||
* resynchorinization from the list of salves, inform them with
|
||||
* an error about what happened, close the connection ASAP. */
|
||||
if (retval == REDIS_ERR) {
|
||||
redisLog(REDIS_WARNING,"BGSAVE for replication failed");
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
|
||||
slave->flags &= ~REDIS_SLAVE;
|
||||
listDelNode(server.slaves,ln);
|
||||
addReplyError(slave,
|
||||
"BGSAVE failed, replication can't continue");
|
||||
slave->flags |= REDIS_CLOSE_AFTER_REPLY;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* If the target is socket, rdbSaveToSlavesSockets() already setup
|
||||
* the salves for a full resync. Otherwise for disk target do it now.*/
|
||||
if (!socket_target) {
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
|
||||
replicationSetupSlaveForFullResync(slave,
|
||||
getPsyncInitialOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Flush the script cache, since we need that slave differences are
|
||||
* accumulated without requiring slaves to match our cached scripts. */
|
||||
if (retval == REDIS_OK) replicationScriptCacheFlush();
|
||||
@@ -610,16 +515,8 @@ void syncCommand(redisClient *c) {
|
||||
/* Full resynchronization. */
|
||||
server.stat_sync_full++;
|
||||
|
||||
/* Setup the slave as one waiting for BGSAVE to start. The following code
|
||||
* paths will change the state if we handle the slave differently. */
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
if (server.repl_disable_tcp_nodelay)
|
||||
anetDisableTcpNoDelay(NULL, c->fd); /* Non critical if it fails. */
|
||||
c->repldbfd = -1;
|
||||
c->flags |= REDIS_SLAVE;
|
||||
listAddNodeTail(server.slaves,c);
|
||||
|
||||
/* CASE 1: BGSAVE is in progress, with disk target. */
|
||||
/* Here we need to check if there is a background saving operation
|
||||
* in progress, or if it is required to start one */
|
||||
if (server.rdb_child_pid != -1 &&
|
||||
server.rdb_child_type == REDIS_RDB_CHILD_TYPE_DISK)
|
||||
{
|
||||
@@ -635,45 +532,51 @@ void syncCommand(redisClient *c) {
|
||||
slave = ln->value;
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) break;
|
||||
}
|
||||
/* To attach this slave, we check that it has at least all the
|
||||
* capabilities of the slave that triggered the current BGSAVE. */
|
||||
if (ln && ((c->slave_capa & slave->slave_capa) == slave->slave_capa)) {
|
||||
if (ln) {
|
||||
/* Perfect, the server is already registering differences for
|
||||
* another slave. Set the right state, and copy the buffer. */
|
||||
copyClientOutputBuffer(c,slave);
|
||||
replicationSetupSlaveForFullResync(c,slave->psync_initial_offset);
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
redisLog(REDIS_NOTICE,"Waiting for end of BGSAVE for SYNC");
|
||||
} else {
|
||||
/* No way, we need to wait for the next BGSAVE in order to
|
||||
* register differences. */
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
redisLog(REDIS_NOTICE,"Waiting for next BGSAVE for SYNC");
|
||||
}
|
||||
|
||||
/* CASE 2: BGSAVE is in progress, with socket target. */
|
||||
} else if (server.rdb_child_pid != -1 &&
|
||||
server.rdb_child_type == REDIS_RDB_CHILD_TYPE_SOCKET)
|
||||
{
|
||||
/* There is an RDB child process but it is writing directly to
|
||||
* children sockets. We need to wait for the next BGSAVE
|
||||
* in order to synchronize. */
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
redisLog(REDIS_NOTICE,"Waiting for next BGSAVE for SYNC");
|
||||
|
||||
/* CASE 3: There is no BGSAVE is progress. */
|
||||
} else {
|
||||
if (server.repl_diskless_sync && (c->slave_capa & SLAVE_CAPA_EOF)) {
|
||||
if (server.repl_diskless_sync) {
|
||||
/* Diskless replication RDB child is created inside
|
||||
* replicationCron() since we want to delay its start a
|
||||
* few seconds to wait for more slaves to arrive. */
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
if (server.repl_diskless_sync_delay)
|
||||
redisLog(REDIS_NOTICE,"Delay next BGSAVE for SYNC");
|
||||
} else {
|
||||
/* Target is disk (or the slave is not capable of supporting
|
||||
* diskless replication) and we don't have a BGSAVE in progress,
|
||||
* let's start one. */
|
||||
if (startBgsaveForReplication(c->slave_capa) != REDIS_OK) return;
|
||||
/* Ok we don't have a BGSAVE in progress, let's start one. */
|
||||
if (startBgsaveForReplication() != REDIS_OK) {
|
||||
redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE");
|
||||
addReplyError(c,"Unable to perform background save");
|
||||
return;
|
||||
}
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
}
|
||||
}
|
||||
|
||||
if (server.repl_disable_tcp_nodelay)
|
||||
anetDisableTcpNoDelay(NULL, c->fd); /* Non critical if it fails. */
|
||||
c->repldbfd = -1;
|
||||
c->flags |= REDIS_SLAVE;
|
||||
server.slaveseldb = -1; /* Force to re-emit the SELECT command. */
|
||||
listAddNodeTail(server.slaves,c);
|
||||
if (listLength(server.slaves) == 1 && server.repl_backlog == NULL)
|
||||
createReplicationBacklog();
|
||||
return;
|
||||
@@ -710,10 +613,6 @@ void replconfCommand(redisClient *c) {
|
||||
&port,NULL) != REDIS_OK))
|
||||
return;
|
||||
c->slave_listening_port = port;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"capa")) {
|
||||
/* Ignore capabilities not understood by this master. */
|
||||
if (!strcasecmp(c->argv[j+1]->ptr,"eof"))
|
||||
c->slave_capa |= SLAVE_CAPA_EOF;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"ack")) {
|
||||
/* REPLCONF ACK is used by slave to inform the master the amount
|
||||
* of replication stream that it processed so far. It is an
|
||||
@@ -753,8 +652,7 @@ void replconfCommand(redisClient *c) {
|
||||
*
|
||||
* It does a few things:
|
||||
*
|
||||
* 1) Put the slave in ONLINE state (useless when the function is called
|
||||
* because state is already ONLINE but repl_put_online_on_ack is true).
|
||||
* 1) Put the slave in ONLINE state.
|
||||
* 2) Make sure the writable event is re-installed, since calling the SYNC
|
||||
* command disables it, so that we can accumulate output buffer without
|
||||
* sending it to the slave.
|
||||
@@ -762,7 +660,7 @@ void replconfCommand(redisClient *c) {
|
||||
void putSlaveOnline(redisClient *slave) {
|
||||
slave->replstate = REDIS_REPL_ONLINE;
|
||||
slave->repl_put_online_on_ack = 0;
|
||||
slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */
|
||||
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));
|
||||
@@ -792,7 +690,6 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
server.stat_net_output_bytes += nwritten;
|
||||
sdsrange(slave->replpreamble,nwritten,-1);
|
||||
if (sdslen(slave->replpreamble) == 0) {
|
||||
sdsfree(slave->replpreamble);
|
||||
@@ -821,7 +718,6 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
slave->repldboff += nwritten;
|
||||
server.stat_net_output_bytes += nwritten;
|
||||
if (slave->repldboff == slave->repldbsize) {
|
||||
close(slave->repldbfd);
|
||||
slave->repldbfd = -1;
|
||||
@@ -847,7 +743,6 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
listNode *ln;
|
||||
int startbgsave = 0;
|
||||
int mincapa = -1;
|
||||
listIter li;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
@@ -856,8 +751,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
|
||||
startbgsave = 1;
|
||||
mincapa = (mincapa == -1) ? slave->slave_capa :
|
||||
(mincapa & slave->slave_capa);
|
||||
slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
} else if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) {
|
||||
struct redis_stat buf;
|
||||
|
||||
@@ -877,7 +771,6 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
* is technically online now. */
|
||||
slave->replstate = REDIS_REPL_ONLINE;
|
||||
slave->repl_put_online_on_ack = 1;
|
||||
slave->repl_ack_time = server.unixtime; /* Timeout otherwise. */
|
||||
} else {
|
||||
if (bgsaveerr != REDIS_OK) {
|
||||
freeClient(slave);
|
||||
@@ -904,18 +797,24 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (startbgsave) startBgsaveForReplication(mincapa);
|
||||
if (startbgsave) {
|
||||
if (startBgsaveForReplication() != REDIS_OK) {
|
||||
listIter li;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
redisLog(REDIS_WARNING,"SYNC failed. BGSAVE failed");
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START)
|
||||
freeClient(slave);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------- SLAVE -------------------------------- */
|
||||
|
||||
/* Returns 1 if the given replication state is a handshake state,
|
||||
* 0 otherwise. */
|
||||
int slaveIsInHandshakeState(void) {
|
||||
return server.repl_state >= REDIS_REPL_RECEIVE_PONG &&
|
||||
server.repl_state <= REDIS_REPL_RECEIVE_PSYNC;
|
||||
}
|
||||
|
||||
/* Abort the async download of the bulk dataset while SYNC-ing with master */
|
||||
void replicationAbortSyncTransfer(void) {
|
||||
redisAssert(server.repl_state == REDIS_REPL_TRANSFER);
|
||||
@@ -1039,7 +938,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
replicationAbortSyncTransfer();
|
||||
return;
|
||||
}
|
||||
server.stat_net_input_bytes += nread;
|
||||
|
||||
/* When a mark is used, we want to detect EOF asap in order to avoid
|
||||
* writing the EOF mark into the file... */
|
||||
@@ -1159,54 +1057,38 @@ error:
|
||||
* The command returns an sds string representing the result of the
|
||||
* operation. On error the first byte is a "-".
|
||||
*/
|
||||
#define SYNC_CMD_READ (1<<0)
|
||||
#define SYNC_CMD_WRITE (1<<1)
|
||||
#define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE)
|
||||
char *sendSynchronousCommand(int flags, int fd, ...) {
|
||||
char *sendSynchronousCommand(int fd, ...) {
|
||||
va_list ap;
|
||||
sds cmd = sdsempty();
|
||||
char *arg, buf[256];
|
||||
|
||||
/* Create the command to send to the master, we use simple inline
|
||||
* protocol for simplicity as currently we only send simple strings. */
|
||||
if (flags & SYNC_CMD_WRITE) {
|
||||
char *arg;
|
||||
va_list ap;
|
||||
sds cmd = sdsempty();
|
||||
va_start(ap,fd);
|
||||
va_start(ap,fd);
|
||||
while(1) {
|
||||
arg = va_arg(ap, char*);
|
||||
if (arg == NULL) break;
|
||||
|
||||
while(1) {
|
||||
arg = va_arg(ap, char*);
|
||||
if (arg == NULL) break;
|
||||
|
||||
if (sdslen(cmd) != 0) cmd = sdscatlen(cmd," ",1);
|
||||
cmd = sdscat(cmd,arg);
|
||||
}
|
||||
cmd = sdscatlen(cmd,"\r\n",2);
|
||||
|
||||
/* Transfer command to the server. */
|
||||
if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
|
||||
== -1)
|
||||
{
|
||||
sdsfree(cmd);
|
||||
return sdscatprintf(sdsempty(),"-Writing to master: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
sdsfree(cmd);
|
||||
va_end(ap);
|
||||
if (sdslen(cmd) != 0) cmd = sdscatlen(cmd," ",1);
|
||||
cmd = sdscat(cmd,arg);
|
||||
}
|
||||
cmd = sdscatlen(cmd,"\r\n",2);
|
||||
|
||||
/* Transfer command to the server. */
|
||||
if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000) == -1) {
|
||||
sdsfree(cmd);
|
||||
return sdscatprintf(sdsempty(),"-Writing to master: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
sdsfree(cmd);
|
||||
|
||||
/* Read the reply from the server. */
|
||||
if (flags & SYNC_CMD_READ) {
|
||||
char buf[256];
|
||||
|
||||
if (syncReadLine(fd,buf,sizeof(buf),server.repl_syncio_timeout*1000)
|
||||
== -1)
|
||||
{
|
||||
return sdscatprintf(sdsempty(),"-Reading from master: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
server.repl_transfer_lastio = server.unixtime;
|
||||
return sdsnew(buf);
|
||||
if (syncReadLine(fd,buf,sizeof(buf),server.repl_syncio_timeout*1000) == -1)
|
||||
{
|
||||
return sdscatprintf(sdsempty(),"-Reading from master: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
return NULL;
|
||||
return sdsnew(buf);
|
||||
}
|
||||
|
||||
/* Try a partial resynchronization with the master if we are about to reconnect.
|
||||
@@ -1223,19 +1105,6 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
|
||||
* of successful partial resynchronization, the function will reuse
|
||||
* 'fd' as file descriptor of the server.master client structure.
|
||||
*
|
||||
* The function is split in two halves: if read_reply is 0, the function
|
||||
* writes the PSYNC command on the socket, and a new function call is
|
||||
* needed, with read_reply set to 1, in order to read the reply of the
|
||||
* command. This is useful in order to support non blocking operations, so
|
||||
* that we write, return into the event loop, and read when there are data.
|
||||
*
|
||||
* When read_reply is 0 the function returns PSYNC_WRITE_ERR if there
|
||||
* was a write error, or PSYNC_WAIT_REPLY to signal we need another call
|
||||
* with read_reply set to 1. However even when read_reply is set to 1
|
||||
* the function may return PSYNC_WAIT_REPLY again to signal there were
|
||||
* insufficient data to read to complete its work. We should re-enter
|
||||
* into the event loop and wait in such a case.
|
||||
*
|
||||
* The function returns:
|
||||
*
|
||||
* PSYNC_CONTINUE: If the PSYNC command succeded and we can continue.
|
||||
@@ -1244,68 +1113,35 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
|
||||
* offset is saved.
|
||||
* PSYNC_NOT_SUPPORTED: If the server does not understand PSYNC at all and
|
||||
* the caller should fall back to SYNC.
|
||||
* PSYNC_WRITE_ERR: There was an error writing the command to the socket.
|
||||
* PSYNC_WAIT_REPLY: Call again the function with read_reply set to 1.
|
||||
*
|
||||
* Notable side effects:
|
||||
*
|
||||
* 1) As a side effect of the function call the function removes the readable
|
||||
* event handler from "fd", unless the return value is PSYNC_WAIT_REPLY.
|
||||
* 2) server.repl_master_initial_offset is set to the right value according
|
||||
* to the master reply. This will be used to populate the 'server.master'
|
||||
* structure replication offset.
|
||||
*/
|
||||
|
||||
#define PSYNC_WRITE_ERROR 0
|
||||
#define PSYNC_WAIT_REPLY 1
|
||||
#define PSYNC_CONTINUE 2
|
||||
#define PSYNC_FULLRESYNC 3
|
||||
#define PSYNC_NOT_SUPPORTED 4
|
||||
int slaveTryPartialResynchronization(int fd, int read_reply) {
|
||||
#define PSYNC_CONTINUE 0
|
||||
#define PSYNC_FULLRESYNC 1
|
||||
#define PSYNC_NOT_SUPPORTED 2
|
||||
int slaveTryPartialResynchronization(int fd) {
|
||||
char *psync_runid;
|
||||
char psync_offset[32];
|
||||
sds reply;
|
||||
|
||||
/* Writing half */
|
||||
if (!read_reply) {
|
||||
/* Initially set repl_master_initial_offset to -1 to mark the current
|
||||
* master run_id and offset as not valid. Later if we'll be able to do
|
||||
* a FULL resync using the PSYNC command we'll set the offset at the
|
||||
* right value, so that this information will be propagated to the
|
||||
* client structure representing the master into server.master. */
|
||||
server.repl_master_initial_offset = -1;
|
||||
/* Initially set repl_master_initial_offset to -1 to mark the current
|
||||
* master run_id and offset as not valid. Later if we'll be able to do
|
||||
* a FULL resync using the PSYNC command we'll set the offset at the
|
||||
* right value, so that this information will be propagated to the
|
||||
* client structure representing the master into server.master. */
|
||||
server.repl_master_initial_offset = -1;
|
||||
|
||||
if (server.cached_master) {
|
||||
psync_runid = server.cached_master->replrunid;
|
||||
snprintf(psync_offset,sizeof(psync_offset),"%lld", server.cached_master->reploff+1);
|
||||
redisLog(REDIS_NOTICE,"Trying a partial resynchronization (request %s:%s).", psync_runid, psync_offset);
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,"Partial resynchronization not possible (no cached master)");
|
||||
psync_runid = "?";
|
||||
memcpy(psync_offset,"-1",3);
|
||||
}
|
||||
|
||||
/* Issue the PSYNC command */
|
||||
reply = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PSYNC",psync_runid,psync_offset,NULL);
|
||||
if (reply != NULL) {
|
||||
redisLog(REDIS_WARNING,"Unable to send PSYNC to master: %s",reply);
|
||||
sdsfree(reply);
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE);
|
||||
return PSYNC_WRITE_ERROR;
|
||||
}
|
||||
return PSYNC_WAIT_REPLY;
|
||||
if (server.cached_master) {
|
||||
psync_runid = server.cached_master->replrunid;
|
||||
snprintf(psync_offset,sizeof(psync_offset),"%lld", server.cached_master->reploff+1);
|
||||
redisLog(REDIS_NOTICE,"Trying a partial resynchronization (request %s:%s).", psync_runid, psync_offset);
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,"Partial resynchronization not possible (no cached master)");
|
||||
psync_runid = "?";
|
||||
memcpy(psync_offset,"-1",3);
|
||||
}
|
||||
|
||||
/* Reading half */
|
||||
reply = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
||||
if (sdslen(reply) == 0) {
|
||||
/* The master may send empty newlines after it receives PSYNC
|
||||
* and before to reply, just to keep the connection alive. */
|
||||
sdsfree(reply);
|
||||
return PSYNC_WAIT_REPLY;
|
||||
}
|
||||
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE);
|
||||
/* Issue the PSYNC command */
|
||||
reply = sendSynchronousCommand(fd,"PSYNC",psync_runid,psync_offset,NULL);
|
||||
|
||||
if (!strncmp(reply,"+FULLRESYNC",11)) {
|
||||
char *runid = NULL, *offset = NULL;
|
||||
@@ -1349,7 +1185,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
|
||||
return PSYNC_CONTINUE;
|
||||
}
|
||||
|
||||
/* If we reach this point we received either an error since the master does
|
||||
/* If we reach this point we receied either an error since the master does
|
||||
* not understand PSYNC, or an unexpected reply from the master.
|
||||
* Return PSYNC_NOT_SUPPORTED to the caller in both cases. */
|
||||
|
||||
@@ -1368,7 +1204,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
|
||||
}
|
||||
|
||||
void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
char tmpfile[256], *err = NULL;
|
||||
char tmpfile[256], *err;
|
||||
int dfd, maxtries = 5;
|
||||
int sockerr = 0, psync_result;
|
||||
socklen_t errlen = sizeof(sockerr);
|
||||
@@ -1387,12 +1223,16 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
|
||||
sockerr = errno;
|
||||
if (sockerr) {
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
|
||||
redisLog(REDIS_WARNING,"Error condition on socket for SYNC: %s",
|
||||
strerror(sockerr));
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Send a PING to check the master is able to reply without errors. */
|
||||
/* If we were connecting, it's time to send a non blocking PING, we want to
|
||||
* make sure the master is able to reply before going into the actual
|
||||
* replication process where we have long timeouts in the order of
|
||||
* seconds (in the meantime the slave would block). */
|
||||
if (server.repl_state == REDIS_REPL_CONNECTING) {
|
||||
redisLog(REDIS_NOTICE,"Non blocking connect for SYNC fired the event.");
|
||||
/* Delete the writable event so that the readable event remains
|
||||
@@ -1401,109 +1241,70 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
server.repl_state = REDIS_REPL_RECEIVE_PONG;
|
||||
/* Send the PING, don't check for errors at all, we have the timeout
|
||||
* that will take care about this. */
|
||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PING",NULL);
|
||||
if (err) goto write_error;
|
||||
syncWrite(fd,"PING\r\n",6,100);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Receive the PONG command. */
|
||||
if (server.repl_state == REDIS_REPL_RECEIVE_PONG) {
|
||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
||||
char buf[1024];
|
||||
|
||||
/* Delete the readable event, we no longer need it now that there is
|
||||
* the PING reply to read. */
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE);
|
||||
|
||||
/* Read the reply with explicit timeout. */
|
||||
buf[0] = '\0';
|
||||
if (syncReadLine(fd,buf,sizeof(buf),
|
||||
server.repl_syncio_timeout*1000) == -1)
|
||||
{
|
||||
redisLog(REDIS_WARNING,
|
||||
"I/O error reading PING reply from master: %s",
|
||||
strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* We accept only two replies as valid, a positive +PONG reply
|
||||
* (we just check for "+") or an authentication error.
|
||||
* Note that older versions of Redis replied with "operation not
|
||||
* permitted" instead of using a proper error code, so we test
|
||||
* both. */
|
||||
if (err[0] != '+' &&
|
||||
strncmp(err,"-NOAUTH",7) != 0 &&
|
||||
strncmp(err,"-ERR operation not permitted",28) != 0)
|
||||
if (buf[0] != '+' &&
|
||||
strncmp(buf,"-NOAUTH",7) != 0 &&
|
||||
strncmp(buf,"-ERR operation not permitted",28) != 0)
|
||||
{
|
||||
redisLog(REDIS_WARNING,"Error reply to PING from master: '%s'",err);
|
||||
sdsfree(err);
|
||||
redisLog(REDIS_WARNING,"Error reply to PING from master: '%s'",buf);
|
||||
goto error;
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Master replied to PING, replication can continue...");
|
||||
}
|
||||
sdsfree(err);
|
||||
server.repl_state = REDIS_REPL_SEND_AUTH;
|
||||
}
|
||||
|
||||
/* AUTH with the master if required. */
|
||||
if (server.repl_state == REDIS_REPL_SEND_AUTH) {
|
||||
if (server.masterauth) {
|
||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH",server.masterauth,NULL);
|
||||
if (err) goto write_error;
|
||||
server.repl_state = REDIS_REPL_RECEIVE_AUTH;
|
||||
return;
|
||||
} else {
|
||||
server.repl_state = REDIS_REPL_SEND_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
/* Receive AUTH reply. */
|
||||
if (server.repl_state == REDIS_REPL_RECEIVE_AUTH) {
|
||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
||||
if(server.masterauth) {
|
||||
err = sendSynchronousCommand(fd,"AUTH",server.masterauth,NULL);
|
||||
if (err[0] == '-') {
|
||||
redisLog(REDIS_WARNING,"Unable to AUTH to MASTER: %s",err);
|
||||
sdsfree(err);
|
||||
goto error;
|
||||
}
|
||||
sdsfree(err);
|
||||
server.repl_state = REDIS_REPL_SEND_PORT;
|
||||
}
|
||||
|
||||
/* Set the slave port, so that Master's INFO command can list the
|
||||
* slave listening port correctly. */
|
||||
if (server.repl_state == REDIS_REPL_SEND_PORT) {
|
||||
{
|
||||
sds port = sdsfromlonglong(server.port);
|
||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
|
||||
"listening-port",port, NULL);
|
||||
err = sendSynchronousCommand(fd,"REPLCONF","listening-port",port,
|
||||
NULL);
|
||||
sdsfree(port);
|
||||
if (err) goto write_error;
|
||||
sdsfree(err);
|
||||
server.repl_state = REDIS_REPL_RECEIVE_PORT;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Receive REPLCONF listening-port reply. */
|
||||
if (server.repl_state == REDIS_REPL_RECEIVE_PORT) {
|
||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
||||
/* Ignore the error if any, not all the Redis versions support
|
||||
* REPLCONF listening-port. */
|
||||
if (err[0] == '-') {
|
||||
redisLog(REDIS_NOTICE,"(Non critical) Master does not understand "
|
||||
"REPLCONF listening-port: %s", err);
|
||||
redisLog(REDIS_NOTICE,"(Non critical) Master does not understand REPLCONF listening-port: %s", err);
|
||||
}
|
||||
sdsfree(err);
|
||||
server.repl_state = REDIS_REPL_SEND_CAPA;
|
||||
}
|
||||
|
||||
/* Inform the master of our capabilities. While we currently send
|
||||
* just one capability, it is possible to chain new capabilities here
|
||||
* in the form of REPLCONF capa X capa Y capa Z ...
|
||||
* The master will ignore capabilities it does not understand. */
|
||||
if (server.repl_state == REDIS_REPL_SEND_CAPA) {
|
||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
|
||||
"capa","eof",NULL);
|
||||
if (err) goto write_error;
|
||||
sdsfree(err);
|
||||
server.repl_state = REDIS_REPL_RECEIVE_CAPA;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Receive CAPA reply. */
|
||||
if (server.repl_state == REDIS_REPL_RECEIVE_CAPA) {
|
||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
||||
/* Ignore the error if any, not all the Redis versions support
|
||||
* REPLCONF capa. */
|
||||
if (err[0] == '-') {
|
||||
redisLog(REDIS_NOTICE,"(Non critical) Master does not understand "
|
||||
"REPLCONF capa: %s", err);
|
||||
}
|
||||
sdsfree(err);
|
||||
server.repl_state = REDIS_REPL_SEND_PSYNC;
|
||||
}
|
||||
|
||||
/* Try a partial resynchonization. If we don't have a cached master
|
||||
@@ -1511,41 +1312,12 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* to start a full resynchronization so that we get the master run id
|
||||
* and the global offset, to try a partial resync at the next
|
||||
* reconnection attempt. */
|
||||
if (server.repl_state == REDIS_REPL_SEND_PSYNC) {
|
||||
if (slaveTryPartialResynchronization(fd,0) == PSYNC_WRITE_ERROR) {
|
||||
err = sdsnew("Write error sending the PSYNC command.");
|
||||
goto write_error;
|
||||
}
|
||||
server.repl_state = REDIS_REPL_RECEIVE_PSYNC;
|
||||
return;
|
||||
}
|
||||
|
||||
/* If reached this point, we should be in REDIS_REPL_RECEIVE_PSYNC. */
|
||||
if (server.repl_state != REDIS_REPL_RECEIVE_PSYNC) {
|
||||
redisLog(REDIS_WARNING,"syncWithMaster(): state machine error, "
|
||||
"state should be RECEIVE_PSYNC but is %d",
|
||||
server.repl_state);
|
||||
goto error;
|
||||
}
|
||||
|
||||
psync_result = slaveTryPartialResynchronization(fd,1);
|
||||
if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */
|
||||
|
||||
/* Note: if PSYNC does not return WAIT_REPLY, it will take care of
|
||||
* uninstalling the read handler from the file descriptor. */
|
||||
|
||||
psync_result = slaveTryPartialResynchronization(fd);
|
||||
if (psync_result == PSYNC_CONTINUE) {
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Master accepted a Partial Resynchronization.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* PSYNC failed or is not supported: we want our slaves to resync with us
|
||||
* as well, if we have any (chained replication case). The mater may
|
||||
* transfer us an entirely different data set and we have no way to
|
||||
* incrementally feed our slaves after that. */
|
||||
disconnectSlaves(); /* Force our slaves to resync with us as well. */
|
||||
freeReplicationBacklog(); /* Don't allow our chained slaves to PSYNC. */
|
||||
|
||||
/* Fall back to SYNC if needed. Otherwise psync_result == PSYNC_FULLRESYNC
|
||||
* and the server.repl_master_runid and repl_master_initial_offset are
|
||||
* already populated. */
|
||||
@@ -1591,22 +1363,16 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
|
||||
error:
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
|
||||
close(fd);
|
||||
server.repl_transfer_s = -1;
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
return;
|
||||
|
||||
write_error: /* Handle sendSynchronousCommand(SYNC_CMD_WRITE) errors. */
|
||||
redisLog(REDIS_WARNING,"Sending command to master in replication handshake: %s", err);
|
||||
sdsfree(err);
|
||||
goto error;
|
||||
}
|
||||
|
||||
int connectWithMaster(void) {
|
||||
int fd;
|
||||
|
||||
fd = anetTcpNonBlockBestEffortBindConnect(NULL,
|
||||
fd = anetTcpNonBlockBindConnect(NULL,
|
||||
server.masterhost,server.masterport,REDIS_BIND_ADDR);
|
||||
if (fd == -1) {
|
||||
redisLog(REDIS_WARNING,"Unable to connect to MASTER: %s",
|
||||
@@ -1634,7 +1400,7 @@ void undoConnectWithMaster(void) {
|
||||
int fd = server.repl_transfer_s;
|
||||
|
||||
redisAssert(server.repl_state == REDIS_REPL_CONNECTING ||
|
||||
slaveIsInHandshakeState());
|
||||
server.repl_state == REDIS_REPL_RECEIVE_PONG);
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
|
||||
close(fd);
|
||||
server.repl_transfer_s = -1;
|
||||
@@ -1653,7 +1419,7 @@ int cancelReplicationHandshake(void) {
|
||||
if (server.repl_state == REDIS_REPL_TRANSFER) {
|
||||
replicationAbortSyncTransfer();
|
||||
} else if (server.repl_state == REDIS_REPL_CONNECTING ||
|
||||
slaveIsInHandshakeState())
|
||||
server.repl_state == REDIS_REPL_RECEIVE_PONG)
|
||||
{
|
||||
undoConnectWithMaster();
|
||||
} else {
|
||||
@@ -1668,7 +1434,6 @@ void replicationSetMaster(char *ip, int port) {
|
||||
server.masterhost = sdsnew(ip);
|
||||
server.masterport = port;
|
||||
if (server.master) freeClient(server.master);
|
||||
disconnectAllBlockedClients(); /* Clients blocked in master, now slave. */
|
||||
disconnectSlaves(); /* Force our slaves to resync with us as well. */
|
||||
replicationDiscardCachedMaster(); /* Don't try a PSYNC. */
|
||||
freeReplicationBacklog(); /* Don't allow our chained slaves to PSYNC. */
|
||||
@@ -1699,17 +1464,6 @@ void replicationUnsetMaster(void) {
|
||||
server.repl_state = REDIS_REPL_NONE;
|
||||
}
|
||||
|
||||
/* This function is called when the slave lose the connection with the
|
||||
* master into an unexpected way. */
|
||||
void replicationHandleMasterDisconnection(void) {
|
||||
server.master = NULL;
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
server.repl_down_since = server.unixtime;
|
||||
/* We lost connection with our master, don't disconnect slaves yet,
|
||||
* maybe we'll be able to PSYNC with our master later. We'll disconnect
|
||||
* the slaves only if we'll have to do a full resync with our master. */
|
||||
}
|
||||
|
||||
void slaveofCommand(redisClient *c) {
|
||||
/* SLAVEOF is not allowed in cluster mode as replication is automatically
|
||||
* configured using the current address of the master node. */
|
||||
@@ -1724,10 +1478,7 @@ void slaveofCommand(redisClient *c) {
|
||||
!strcasecmp(c->argv[2]->ptr,"one")) {
|
||||
if (server.masterhost) {
|
||||
replicationUnsetMaster();
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
redisLog(REDIS_NOTICE,
|
||||
"MASTER MODE enabled (user request from '%s')",client);
|
||||
sdsfree(client);
|
||||
redisLog(REDIS_NOTICE,"MASTER MODE enabled (user request)");
|
||||
}
|
||||
} else {
|
||||
long port;
|
||||
@@ -1745,10 +1496,8 @@ void slaveofCommand(redisClient *c) {
|
||||
/* There was no previous master or the user specified a different one,
|
||||
* we can continue. */
|
||||
replicationSetMaster(c->argv[1]->ptr, port);
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
redisLog(REDIS_NOTICE,"SLAVE OF %s:%d enabled (user request from '%s')",
|
||||
server.masterhost, server.masterport, client);
|
||||
sdsfree(client);
|
||||
redisLog(REDIS_NOTICE,"SLAVE OF %s:%d enabled (user request)",
|
||||
server.masterhost, server.masterport);
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
@@ -1788,17 +1537,14 @@ void roleCommand(redisClient *c) {
|
||||
addReplyBulkCBuffer(c,"slave",5);
|
||||
addReplyBulkCString(c,server.masterhost);
|
||||
addReplyLongLong(c,server.masterport);
|
||||
if (slaveIsInHandshakeState()) {
|
||||
slavestate = "handshake";
|
||||
} else {
|
||||
switch(server.repl_state) {
|
||||
case REDIS_REPL_NONE: slavestate = "none"; break;
|
||||
case REDIS_REPL_CONNECT: slavestate = "connect"; break;
|
||||
case REDIS_REPL_CONNECTING: slavestate = "connecting"; break;
|
||||
case REDIS_REPL_TRANSFER: slavestate = "sync"; break;
|
||||
case REDIS_REPL_CONNECTED: slavestate = "connected"; break;
|
||||
default: slavestate = "unknown"; break;
|
||||
}
|
||||
switch(server.repl_state) {
|
||||
case REDIS_REPL_NONE: slavestate = "none"; break;
|
||||
case REDIS_REPL_CONNECT: slavestate = "connect"; break;
|
||||
case REDIS_REPL_CONNECTING: slavestate = "connecting"; break;
|
||||
case REDIS_REPL_RECEIVE_PONG: /* see next */
|
||||
case REDIS_REPL_TRANSFER: slavestate = "sync"; break;
|
||||
case REDIS_REPL_CONNECTED: slavestate = "connected"; break;
|
||||
default: slavestate = "unknown"; break;
|
||||
}
|
||||
addReplyBulkCString(c,slavestate);
|
||||
addReplyLongLong(c,server.master ? server.master->reploff : -1);
|
||||
@@ -2184,13 +1930,11 @@ long long replicationGetSlaveOffset(void) {
|
||||
|
||||
/* Replication cron function, called 1 time per second. */
|
||||
void replicationCron(void) {
|
||||
static long long replication_cron_loops = 0;
|
||||
|
||||
/* Non blocking connection timeout? */
|
||||
if (server.masterhost &&
|
||||
(server.repl_state == REDIS_REPL_CONNECTING ||
|
||||
slaveIsInHandshakeState()) &&
|
||||
(time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
|
||||
server.repl_state == REDIS_REPL_RECEIVE_PONG) &&
|
||||
(time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
|
||||
{
|
||||
redisLog(REDIS_WARNING,"Timeout connecting to the MASTER...");
|
||||
undoConnectWithMaster();
|
||||
@@ -2232,34 +1976,31 @@ void replicationCron(void) {
|
||||
* So slaves can implement an explicit timeout to masters, and will
|
||||
* be able to detect a link disconnection even if the TCP connection
|
||||
* will not actually go down. */
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
robj *ping_argv[1];
|
||||
if (!(server.cronloops % (server.repl_ping_slave_period * server.hz))) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
robj *ping_argv[1];
|
||||
|
||||
/* First, send PING according to ping_slave_period. */
|
||||
if ((replication_cron_loops % server.repl_ping_slave_period) == 0) {
|
||||
/* First, send PING */
|
||||
ping_argv[0] = createStringObject("PING",4);
|
||||
replicationFeedSlaves(server.slaves, server.slaveseldb,
|
||||
ping_argv, 1);
|
||||
replicationFeedSlaves(server.slaves, server.slaveseldb, ping_argv, 1);
|
||||
decrRefCount(ping_argv[0]);
|
||||
}
|
||||
|
||||
/* Second, send a newline to all the slaves in pre-synchronization
|
||||
* stage, that is, slaves waiting for the master to create the RDB file.
|
||||
* The newline will be ignored by the slave but will refresh the
|
||||
* last-io timer preventing a timeout. In this case we ignore the
|
||||
* ping period and refresh the connection once per second since certain
|
||||
* timeouts are set at a few seconds (example: PSYNC response). */
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
/* Second, send a newline to all the slaves in pre-synchronization
|
||||
* stage, that is, slaves waiting for the master to create the RDB file.
|
||||
* The newline will be ignored by the slave but will refresh the
|
||||
* last-io timer preventing a timeout. */
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START ||
|
||||
(slave->replstate == REDIS_REPL_WAIT_BGSAVE_END &&
|
||||
server.rdb_child_type != REDIS_RDB_CHILD_TYPE_SOCKET))
|
||||
{
|
||||
if (write(slave->fd, "\n", 1) == -1) {
|
||||
/* Don't worry, it's just a ping. */
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START ||
|
||||
(slave->replstate == REDIS_REPL_WAIT_BGSAVE_END &&
|
||||
server.rdb_child_type != REDIS_RDB_CHILD_TYPE_SOCKET))
|
||||
{
|
||||
if (write(slave->fd, "\n", 1) == -1) {
|
||||
/* Don't worry, it's just a ping. */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2320,7 +2061,6 @@ void replicationCron(void) {
|
||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
|
||||
time_t idle, max_idle = 0;
|
||||
int slaves_waiting = 0;
|
||||
int mincapa = -1;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
@@ -2331,19 +2071,28 @@ void replicationCron(void) {
|
||||
idle = server.unixtime - slave->lastinteraction;
|
||||
if (idle > max_idle) max_idle = idle;
|
||||
slaves_waiting++;
|
||||
mincapa = (mincapa == -1) ? slave->slave_capa :
|
||||
(mincapa & slave->slave_capa);
|
||||
}
|
||||
}
|
||||
|
||||
if (slaves_waiting && max_idle > server.repl_diskless_sync_delay) {
|
||||
/* Start a BGSAVE. Usually with socket target, or with disk target
|
||||
* if there was a recent socket -> disk config change. */
|
||||
startBgsaveForReplication(mincapa);
|
||||
if (startBgsaveForReplication() == REDIS_OK) {
|
||||
/* It started! We need to change the state of slaves
|
||||
* from WAIT_BGSAVE_START to WAIT_BGSAVE_END in case
|
||||
* the current target is disk. Otherwise it was already done
|
||||
* by rdbSaveToSlavesSockets() which is called by
|
||||
* startBgsaveForReplication(). */
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START)
|
||||
slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Refresh the number of slaves with lag <= min-slaves-max-lag. */
|
||||
refreshGoodSlavesCount();
|
||||
replication_cron_loops++; /* Incremented with frequency 1 HZ. */
|
||||
}
|
||||
|
||||
+8
-50
@@ -30,7 +30,6 @@
|
||||
#include "redis.h"
|
||||
#include "sha1.h"
|
||||
#include "rand.h"
|
||||
#include "cluster.h"
|
||||
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
@@ -214,27 +213,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
static int argv_size = 0;
|
||||
static robj *cached_objects[LUA_CMD_OBJCACHE_SIZE];
|
||||
static size_t cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
|
||||
static int inuse = 0; /* Recursive calls detection. */
|
||||
|
||||
/* By using Lua debug hooks it is possible to trigger a recursive call
|
||||
* to luaRedisGenericCommand(), which normally should never happen.
|
||||
* To make this function reentrant is futile and makes it slower, but
|
||||
* we should at least detect such a misuse, and abort. */
|
||||
if (inuse) {
|
||||
char *recursion_warning =
|
||||
"luaRedisGenericCommand() recursive call detected. "
|
||||
"Are you doing funny stuff with Lua debug hooks?";
|
||||
redisLog(REDIS_WARNING,"%s",recursion_warning);
|
||||
luaPushError(lua,recursion_warning);
|
||||
return 1;
|
||||
}
|
||||
inuse++;
|
||||
|
||||
/* Require at least one argument */
|
||||
if (argc == 0) {
|
||||
luaPushError(lua,
|
||||
"Please specify at least one argument for redis.call()");
|
||||
inuse--;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -289,7 +272,6 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
}
|
||||
luaPushError(lua,
|
||||
"Lua redis() command arguments must be strings or integers");
|
||||
inuse--;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -309,7 +291,6 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
luaPushError(lua,"Unknown Redis command called from Lua script");
|
||||
goto cleanup;
|
||||
}
|
||||
c->cmd = cmd;
|
||||
|
||||
/* There are commands that are not allowed inside scripts. */
|
||||
if (cmd->flags & REDIS_CMD_NOSCRIPT) {
|
||||
@@ -356,24 +337,8 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
if (cmd->flags & REDIS_CMD_RANDOM) server.lua_random_dirty = 1;
|
||||
if (cmd->flags & REDIS_CMD_WRITE) server.lua_write_dirty = 1;
|
||||
|
||||
/* If this is a Redis Cluster node, we need to make sure Lua is not
|
||||
* trying to access non-local keys, with the exception of commands
|
||||
* received from our master. */
|
||||
if (server.cluster_enabled && !(server.lua_caller->flags & REDIS_MASTER)) {
|
||||
/* Duplicate relevant flags in the lua client. */
|
||||
c->flags &= ~(REDIS_READONLY|REDIS_ASKING);
|
||||
c->flags |= server.lua_caller->flags & (REDIS_READONLY|REDIS_ASKING);
|
||||
if (getNodeByQuery(c,c->cmd,c->argv,c->argc,NULL,NULL) !=
|
||||
server.cluster->myself)
|
||||
{
|
||||
luaPushError(lua,
|
||||
"Lua script attempted to access a non local key in a "
|
||||
"cluster node");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* Run the command */
|
||||
c->cmd = cmd;
|
||||
call(c,REDIS_CALL_SLOWLOG | REDIS_CALL_STATS);
|
||||
|
||||
/* Convert the result of the Redis command into a suitable Lua type.
|
||||
@@ -444,10 +409,8 @@ cleanup:
|
||||
* return the plain error. */
|
||||
lua_pushstring(lua,"err");
|
||||
lua_gettable(lua,-2);
|
||||
inuse--;
|
||||
return lua_error(lua);
|
||||
}
|
||||
inuse--;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -574,7 +537,6 @@ void luaLoadLib(lua_State *lua, const char *libname, lua_CFunction luafunc) {
|
||||
LUALIB_API int (luaopen_cjson) (lua_State *L);
|
||||
LUALIB_API int (luaopen_struct) (lua_State *L);
|
||||
LUALIB_API int (luaopen_cmsgpack) (lua_State *L);
|
||||
LUALIB_API int (luaopen_bit) (lua_State *L);
|
||||
|
||||
void luaLoadLibraries(lua_State *lua) {
|
||||
luaLoadLib(lua, "", luaopen_base);
|
||||
@@ -585,7 +547,6 @@ void luaLoadLibraries(lua_State *lua) {
|
||||
luaLoadLib(lua, "cjson", luaopen_cjson);
|
||||
luaLoadLib(lua, "struct", luaopen_struct);
|
||||
luaLoadLib(lua, "cmsgpack", luaopen_cmsgpack);
|
||||
luaLoadLib(lua, "bit", luaopen_bit);
|
||||
|
||||
#if 0 /* Stuff that we don't load currently, for sandboxing concerns. */
|
||||
luaLoadLib(lua, LUA_LOADLIBNAME, luaopen_package);
|
||||
@@ -612,12 +573,11 @@ void scriptingEnableGlobalsProtection(lua_State *lua) {
|
||||
|
||||
/* strict.lua from: http://metalua.luaforge.net/src/lib/strict.lua.html.
|
||||
* Modified to be adapted to Redis. */
|
||||
s[j++]="local dbg=debug\n";
|
||||
s[j++]="local mt = {}\n";
|
||||
s[j++]="setmetatable(_G, mt)\n";
|
||||
s[j++]="mt.__newindex = function (t, n, v)\n";
|
||||
s[j++]=" if dbg.getinfo(2) then\n";
|
||||
s[j++]=" local w = dbg.getinfo(2, \"S\").what\n";
|
||||
s[j++]=" if debug.getinfo(2) then\n";
|
||||
s[j++]=" local w = debug.getinfo(2, \"S\").what\n";
|
||||
s[j++]=" if w ~= \"main\" and w ~= \"C\" then\n";
|
||||
s[j++]=" error(\"Script attempted to create global variable '\"..tostring(n)..\"'\", 2)\n";
|
||||
s[j++]=" end\n";
|
||||
@@ -625,12 +585,11 @@ void scriptingEnableGlobalsProtection(lua_State *lua) {
|
||||
s[j++]=" rawset(t, n, v)\n";
|
||||
s[j++]="end\n";
|
||||
s[j++]="mt.__index = function (t, n)\n";
|
||||
s[j++]=" if dbg.getinfo(2) and dbg.getinfo(2, \"S\").what ~= \"C\" then\n";
|
||||
s[j++]=" if debug.getinfo(2) and debug.getinfo(2, \"S\").what ~= \"C\" then\n";
|
||||
s[j++]=" error(\"Script attempted to access unexisting global variable '\"..tostring(n)..\"'\", 2)\n";
|
||||
s[j++]=" end\n";
|
||||
s[j++]=" return rawget(t, n)\n";
|
||||
s[j++]="end\n";
|
||||
s[j++]="debug = nil\n";
|
||||
s[j++]=NULL;
|
||||
|
||||
for (j = 0; s[j] != NULL; j++) code = sdscatlen(code,s[j],strlen(s[j]));
|
||||
@@ -734,11 +693,10 @@ void scriptingInit(void) {
|
||||
* information about the caller, that's what makes sense from the point
|
||||
* of view of the user debugging a script. */
|
||||
{
|
||||
char *errh_func = "local dbg = debug\n"
|
||||
"function __redis__err__handler(err)\n"
|
||||
" local i = dbg.getinfo(2,'nSl')\n"
|
||||
char *errh_func = "function __redis__err__handler(err)\n"
|
||||
" local i = debug.getinfo(2,'nSl')\n"
|
||||
" if i and i.what == 'C' then\n"
|
||||
" i = dbg.getinfo(3,'nSl')\n"
|
||||
" i = debug.getinfo(3,'nSl')\n"
|
||||
" end\n"
|
||||
" if i then\n"
|
||||
" return i.source .. ':' .. i.currentline .. ': ' .. err\n"
|
||||
@@ -895,7 +853,7 @@ int luaCreateFunction(redisClient *c, lua_State *lua, char *funcname, robj *body
|
||||
funcdef = sdscatlen(funcdef,funcname,42);
|
||||
funcdef = sdscatlen(funcdef,"() ",3);
|
||||
funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr));
|
||||
funcdef = sdscatlen(funcdef,"\nend",4);
|
||||
funcdef = sdscatlen(funcdef," end",4);
|
||||
|
||||
if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) {
|
||||
addReplyErrorFormat(c,"Error compiling script (new function): %s\n",
|
||||
|
||||
@@ -71,7 +71,7 @@ sds sdsempty(void) {
|
||||
return sdsnewlen("",0);
|
||||
}
|
||||
|
||||
/* Create a new sds string starting from a null terminated C string. */
|
||||
/* Create a new sds string starting from a null termined C string. */
|
||||
sds sdsnew(const char *init) {
|
||||
size_t initlen = (init == NULL) ? 0 : strlen(init);
|
||||
return sdsnewlen(init, initlen);
|
||||
@@ -295,7 +295,7 @@ sds sdscpy(sds s, const char *t) {
|
||||
* conversion. 's' must point to a string with room for at least
|
||||
* SDS_LLSTR_SIZE bytes.
|
||||
*
|
||||
* The function returns the length of the null-terminated string
|
||||
* 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) {
|
||||
@@ -369,7 +369,7 @@ sds sdsfromlonglong(long long value) {
|
||||
return sdsnewlen(buf,len);
|
||||
}
|
||||
|
||||
/* Like sdscatprintf() but gets va_list instead of being variadic. */
|
||||
/* 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;
|
||||
@@ -390,7 +390,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
buf[buflen-2] = '\0';
|
||||
va_copy(cpy,ap);
|
||||
vsnprintf(buf, buflen, fmt, cpy);
|
||||
va_end(cpy);
|
||||
va_end(ap);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
if (buf != staticbuf) zfree(buf);
|
||||
buflen *= 2;
|
||||
@@ -415,7 +415,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* s = sdsnew("Sum is: ");
|
||||
* s = sdsempty("Sum is: ");
|
||||
* s = sdscatprintf(s,"%d+%d = %d",a,b,a+b).
|
||||
*
|
||||
* Often you need to create a string from scratch with the printf-alike
|
||||
@@ -557,7 +557,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
* Example:
|
||||
*
|
||||
* s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
|
||||
* s = sdstrim(s,"Aa. :");
|
||||
* s = sdstrim(s,"A. :");
|
||||
* printf("%s\n", s);
|
||||
*
|
||||
* Output will be just "Hello World".
|
||||
@@ -643,8 +643,8 @@ void sdstoupper(sds s) {
|
||||
*
|
||||
* Return value:
|
||||
*
|
||||
* positive if s1 > s2.
|
||||
* negative if s1 < s2.
|
||||
* 1 if s1 > s2.
|
||||
* -1 if s1 < s2.
|
||||
* 0 if s1 and s2 are exactly the same binary string.
|
||||
*
|
||||
* If two strings share exactly the same prefix, but one of the two has
|
||||
@@ -1083,7 +1083,6 @@ int main(void) {
|
||||
int oldfree;
|
||||
|
||||
sdsfree(x);
|
||||
sdsfree(y);
|
||||
x = sdsnew("0");
|
||||
sh = (void*) (x-(sizeof(struct sdshdr)));
|
||||
test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0);
|
||||
@@ -1096,8 +1095,6 @@ int main(void) {
|
||||
test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1');
|
||||
test_cond("sdsIncrLen() -- len", sh->len == 2);
|
||||
test_cond("sdsIncrLen() -- free", sh->free == oldfree-1);
|
||||
|
||||
sdsfree(x);
|
||||
}
|
||||
}
|
||||
test_report()
|
||||
|
||||
+7
-67
@@ -397,7 +397,6 @@ struct redisCommand sentinelcmds[] = {
|
||||
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
|
||||
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
|
||||
{"role",sentinelRoleCommand,1,"l",0,NULL,0,0,0,0,0},
|
||||
{"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
|
||||
};
|
||||
|
||||
@@ -923,7 +922,6 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
else if (flags & SRI_SENTINEL) table = master->sentinels;
|
||||
sdsname = sdsnew(name);
|
||||
if (dictFind(table,sdsname)) {
|
||||
releaseSentinelAddr(addr);
|
||||
sdsfree(sdsname);
|
||||
errno = EBUSY;
|
||||
return NULL;
|
||||
@@ -1157,9 +1155,7 @@ void sentinelDelFlagsToDictOfRedisInstances(dict *instances, int flags) {
|
||||
* 1) Remove all slaves.
|
||||
* 2) Remove all sentinels.
|
||||
* 3) Remove most of the flags resulting from runtime operations.
|
||||
* 4) Reset timers to their default value. For example after a reset it will be
|
||||
* possible to failover again the same master ASAP, without waiting the
|
||||
* failover timeout delay.
|
||||
* 4) Reset timers to their default value.
|
||||
* 5) In the process of doing this undo the failover if in progress.
|
||||
* 6) Disconnect the connections with the master (will reconnect automatically).
|
||||
*/
|
||||
@@ -1182,7 +1178,7 @@ void sentinelResetMaster(sentinelRedisInstance *ri, int flags) {
|
||||
}
|
||||
ri->failover_state = SENTINEL_FAILOVER_STATE_NONE;
|
||||
ri->failover_state_change_time = 0;
|
||||
ri->failover_start_time = 0; /* We can failover again ASAP. */
|
||||
ri->failover_start_time = 0;
|
||||
ri->promoted_slave = NULL;
|
||||
sdsfree(ri->runid);
|
||||
sdsfree(ri->slave_master_host);
|
||||
@@ -1272,7 +1268,10 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip,
|
||||
slave = createSentinelRedisInstance(NULL,SRI_SLAVE,slaves[j]->ip,
|
||||
slaves[j]->port, master->quorum, master);
|
||||
releaseSentinelAddr(slaves[j]);
|
||||
if (slave) sentinelEvent(REDIS_NOTICE,"+slave",slave,"%@");
|
||||
if (slave) {
|
||||
sentinelEvent(REDIS_NOTICE,"+slave",slave,"%@");
|
||||
sentinelFlushConfig();
|
||||
}
|
||||
}
|
||||
zfree(slaves);
|
||||
|
||||
@@ -1546,7 +1545,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
slave_addr = master->addr;
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel known-slave %s %s %d",
|
||||
master->name, slave_addr->ip, slave_addr->port);
|
||||
master->name, ri->addr->ip, ri->addr->port);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
dictReleaseIterator(di2);
|
||||
@@ -1844,7 +1843,6 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
atoi(port), ri->quorum, ri)) != NULL)
|
||||
{
|
||||
sentinelEvent(REDIS_NOTICE,"+slave",slave,"%@");
|
||||
sentinelFlushConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2616,31 +2614,6 @@ sentinelRedisInstance *sentinelGetMasterByNameOrReplyError(redisClient *c,
|
||||
return ri;
|
||||
}
|
||||
|
||||
#define SENTINEL_ISQR_OK 0
|
||||
#define SENTINEL_ISQR_NOQUORUM (1<<0)
|
||||
#define SENTINEL_ISQR_NOAUTH (1<<1)
|
||||
int sentinelIsQuorumReachable(sentinelRedisInstance *master, int *usableptr) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
int usable = 1; /* Number of usable Sentinels. Init to 1 to count myself. */
|
||||
int result = SENTINEL_ISQR_OK;
|
||||
int voters = dictSize(master->sentinels)+1; /* Known Sentinels + myself. */
|
||||
|
||||
di = dictGetIterator(master->sentinels);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *ri = dictGetVal(de);
|
||||
|
||||
if (ri->flags & (SRI_S_DOWN|SRI_O_DOWN)) continue;
|
||||
usable++;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
if (usable < (int)master->quorum) result |= SENTINEL_ISQR_NOQUORUM;
|
||||
if (usable < voters/2+1) result |= SENTINEL_ISQR_NOAUTH;
|
||||
if (usableptr) *usableptr = usable;
|
||||
return result;
|
||||
}
|
||||
|
||||
void sentinelCommand(redisClient *c) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"masters")) {
|
||||
/* SENTINEL MASTERS */
|
||||
@@ -2791,49 +2764,16 @@ void sentinelCommand(redisClient *c) {
|
||||
sentinelEvent(REDIS_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"flushconfig")) {
|
||||
if (c->argc != 2) goto numargserr;
|
||||
sentinelFlushConfig();
|
||||
addReply(c,shared.ok);
|
||||
return;
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"remove")) {
|
||||
/* SENTINEL REMOVE <name> */
|
||||
sentinelRedisInstance *ri;
|
||||
|
||||
if (c->argc != 3) goto numargserr;
|
||||
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);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"ckquorum")) {
|
||||
/* SENTINEL CKQUORUM <name> */
|
||||
sentinelRedisInstance *ri;
|
||||
int usable;
|
||||
|
||||
if (c->argc != 3) goto numargserr;
|
||||
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
|
||||
== NULL) return;
|
||||
int result = sentinelIsQuorumReachable(ri,&usable);
|
||||
if (result == SENTINEL_ISQR_OK) {
|
||||
addReplySds(c, sdscatfmt(sdsempty(),
|
||||
"+OK %i usable Sentinels. Quorum and failover authorization "
|
||||
"can be reached\r\n",usable));
|
||||
} else {
|
||||
sds e = sdscatfmt(sdsempty(),
|
||||
"-NOQUORUM %i usable Sentinels. ",usable);
|
||||
if (result & SENTINEL_ISQR_NOQUORUM)
|
||||
e = sdscat(e,"Not enough available Sentinels to reach the"
|
||||
" specified quorum for this master");
|
||||
if (result & SENTINEL_ISQR_NOAUTH) {
|
||||
if (result & SENTINEL_ISQR_NOQUORUM) e = sdscat(e,". ");
|
||||
e = sdscat(e, "Not enough available Sentinels to reach the"
|
||||
" majority and authorize a failover");
|
||||
}
|
||||
e = sdscat(e,"\r\n");
|
||||
addReplySds(c,e);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"set")) {
|
||||
if (c->argc < 3 || c->argc % 2 == 0) goto numargserr;
|
||||
sentinelSetCommand(c);
|
||||
|
||||
+9
-7
@@ -23,8 +23,10 @@ A million repetitions of "a"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h> /* for u_int*_t */
|
||||
#if defined(__sun)
|
||||
#include "solarisfixes.h"
|
||||
#endif
|
||||
#include "sha1.h"
|
||||
#include "config.h"
|
||||
|
||||
@@ -53,12 +55,12 @@ A million repetitions of "a"
|
||||
|
||||
/* Hash a single 512-bit block. This is the core of the algorithm. */
|
||||
|
||||
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
|
||||
void SHA1Transform(u_int32_t state[5], const unsigned char buffer[64])
|
||||
{
|
||||
uint32_t a, b, c, d, e;
|
||||
u_int32_t a, b, c, d, e;
|
||||
typedef union {
|
||||
unsigned char c[64];
|
||||
uint32_t l[16];
|
||||
u_int32_t l[16];
|
||||
} CHAR64LONG16;
|
||||
#ifdef SHA1HANDSOFF
|
||||
CHAR64LONG16 block[1]; /* use array to appear as a pointer */
|
||||
@@ -128,9 +130,9 @@ void SHA1Init(SHA1_CTX* context)
|
||||
|
||||
/* Run your data through this. */
|
||||
|
||||
void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len)
|
||||
void SHA1Update(SHA1_CTX* context, const unsigned char* data, u_int32_t len)
|
||||
{
|
||||
uint32_t i, j;
|
||||
u_int32_t i, j;
|
||||
|
||||
j = context->count[0];
|
||||
if ((context->count[0] += len << 3) < j)
|
||||
@@ -168,7 +170,7 @@ void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
uint32_t t = context->count[i];
|
||||
u_int32_t t = context->count[i];
|
||||
int j;
|
||||
|
||||
for (j = 0; j < 4; t >>= 8, j++)
|
||||
|
||||
+4
-4
@@ -6,12 +6,12 @@ By Steve Reid <steve@edmweb.com>
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
uint32_t state[5];
|
||||
uint32_t count[2];
|
||||
u_int32_t state[5];
|
||||
u_int32_t count[2];
|
||||
unsigned char buffer[64];
|
||||
} SHA1_CTX;
|
||||
|
||||
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
|
||||
void SHA1Transform(u_int32_t state[5], const unsigned char buffer[64]);
|
||||
void SHA1Init(SHA1_CTX* context);
|
||||
void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len);
|
||||
void SHA1Update(SHA1_CTX* context, const unsigned char* data, u_int32_t len);
|
||||
void SHA1Final(unsigned char digest[20], SHA1_CTX* context);
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#if defined(__sun)
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#include <math.h>
|
||||
#undef isnan
|
||||
@@ -50,5 +48,3 @@
|
||||
#define u_int uint
|
||||
#define u_int32_t uint32_t
|
||||
#endif /* __GNUC__ */
|
||||
|
||||
#endif /* __sun */
|
||||
|
||||
+10
-9
@@ -209,7 +209,7 @@ void sortCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
/* Create a list of operations to perform for every sorted element.
|
||||
* Operations can be GET */
|
||||
* Operations can be GET/DEL/INCR/DECR */
|
||||
operations = listCreate();
|
||||
listSetFreeMethod(operations,zfree);
|
||||
j = 2; /* options start at argv[2] */
|
||||
@@ -285,15 +285,16 @@ void sortCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* When sorting a set with no sort specified, we must sort the output
|
||||
* so the result is consistent across scripting and replication.
|
||||
/* 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
|
||||
* is deterministic.
|
||||
*
|
||||
* The other types (list, sorted set) will retain their native order
|
||||
* even if no sort order is requested, so they remain stable across
|
||||
* scripting and replication. */
|
||||
if (dontsort &&
|
||||
sortval->type == REDIS_SET &&
|
||||
(storekey || c->flags & REDIS_LUA_CLIENT))
|
||||
* However in the case 'dontsort' is true, but the type to sort is a
|
||||
* sorted set, we don't need to do anything as ordering is guaranteed
|
||||
* in this special case. */
|
||||
if ((storekey || c->flags & REDIS_LUA_CLIENT) &&
|
||||
(dontsort && sortval->type != REDIS_ZSET))
|
||||
{
|
||||
/* Force ALPHA sorting */
|
||||
dontsort = 0;
|
||||
|
||||
+1
-2
@@ -49,7 +49,7 @@ static int label_margin_top = 1;
|
||||
* sparklineSequenceAddSample(seq, 10, NULL);
|
||||
* sparklineSequenceAddSample(seq, 20, NULL);
|
||||
* sparklineSequenceAddSample(seq, 30, "last sample label");
|
||||
* sds output = sparklineRender(sdsempty(), seq, 80, 4, SPARKLINE_FILL);
|
||||
* sds output = sparklineRender(seq, 80, 4);
|
||||
* freeSparklineSequence(seq);
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
@@ -63,7 +63,6 @@ struct sequence *createSparklineSequence(void) {
|
||||
|
||||
/* Add a new sample into a sequence. */
|
||||
void sparklineSequenceAddSample(struct sequence *seq, double value, char *label) {
|
||||
label = (label == NULL || label[0] == '\0') ? NULL : zstrdup(label);
|
||||
if (seq->length == 0) {
|
||||
seq->min = seq->max = value;
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -565,7 +565,7 @@ void hincrbyfloatCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
value += incr;
|
||||
new = createStringObjectFromLongDouble(value,1);
|
||||
new = createStringObjectFromLongDouble(value);
|
||||
hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
|
||||
hashTypeSet(o,c->argv[2],new);
|
||||
addReplyBulk(c,new);
|
||||
|
||||
+1
-1
@@ -321,7 +321,7 @@ void smoveCommand(redisClient *c) {
|
||||
|
||||
/* If srcset and dstset are equal, SMOVE is a no-op */
|
||||
if (srcset == dstset) {
|
||||
addReply(c,setTypeIsMember(srcset,ele) ? shared.cone : shared.czero);
|
||||
addReply(c,shared.cone);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -397,7 +397,7 @@ void incrbyfloatCommand(redisClient *c) {
|
||||
addReplyError(c,"increment would produce NaN or Infinity");
|
||||
return;
|
||||
}
|
||||
new = createStringObjectFromLongDouble(value,1);
|
||||
new = createStringObjectFromLongDouble(value);
|
||||
if (o)
|
||||
dbOverwrite(c->db,c->argv[1],new);
|
||||
else
|
||||
|
||||
+16
-78
@@ -1171,84 +1171,35 @@ void zsetConvert(robj *zobj, int encoding) {
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* This generic command implements both ZADD and ZINCRBY. */
|
||||
#define ZADD_NONE 0
|
||||
#define ZADD_INCR (1<<0) /* Increment the score instead of setting it. */
|
||||
#define ZADD_NX (1<<1) /* Don't touch elements not already existing. */
|
||||
#define ZADD_XX (1<<2) /* Only touch elements already exisitng. */
|
||||
#define ZADD_CH (1<<3) /* Return num of elements added or updated. */
|
||||
void zaddGenericCommand(redisClient *c, int flags) {
|
||||
void zaddGenericCommand(redisClient *c, int incr) {
|
||||
static char *nanerr = "resulting score is not a number (NaN)";
|
||||
robj *key = c->argv[1];
|
||||
robj *ele;
|
||||
robj *zobj;
|
||||
robj *curobj;
|
||||
double score = 0, *scores = NULL, curscore = 0.0;
|
||||
int j, elements;
|
||||
int scoreidx = 0;
|
||||
/* The following vars are used in order to track what the command actually
|
||||
* did during the execution, to reply to the client and to trigger the
|
||||
* notification of keyspace change. */
|
||||
int added = 0; /* Number of new elements added. */
|
||||
int updated = 0; /* Number of elements with updated score. */
|
||||
int processed = 0; /* Number of elements processed, may remain zero with
|
||||
options like XX. */
|
||||
int j, elements = (c->argc-2)/2;
|
||||
int added = 0, updated = 0;
|
||||
|
||||
/* Parse options. At the end 'scoreidx' is set to the argument position
|
||||
* of the score of the first score-element pair. */
|
||||
scoreidx = 2;
|
||||
while(scoreidx < c->argc) {
|
||||
char *opt = c->argv[scoreidx]->ptr;
|
||||
if (!strcasecmp(opt,"nx")) flags |= ZADD_NX;
|
||||
else if (!strcasecmp(opt,"xx")) flags |= ZADD_XX;
|
||||
else if (!strcasecmp(opt,"ch")) flags |= ZADD_CH;
|
||||
else if (!strcasecmp(opt,"incr")) flags |= ZADD_INCR;
|
||||
else break;
|
||||
scoreidx++;
|
||||
}
|
||||
|
||||
/* Turn options into simple to check vars. */
|
||||
int incr = (flags & ZADD_INCR) != 0;
|
||||
int nx = (flags & ZADD_NX) != 0;
|
||||
int xx = (flags & ZADD_XX) != 0;
|
||||
int ch = (flags & ZADD_CH) != 0;
|
||||
|
||||
/* After the options, we expect to have an even number of args, since
|
||||
* we expect any number of score-element pairs. */
|
||||
elements = c->argc-scoreidx;
|
||||
if (elements % 2) {
|
||||
if (c->argc % 2) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
elements /= 2; /* Now this holds the number of score-element pairs. */
|
||||
|
||||
/* Check for incompatible options. */
|
||||
if (nx && xx) {
|
||||
addReplyError(c,
|
||||
"XX and NX options at the same time are not compatible");
|
||||
return;
|
||||
}
|
||||
|
||||
if (incr && elements > 1) {
|
||||
addReplyError(c,
|
||||
"INCR option supports a single increment-element pair");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Start parsing all the scores, we need to emit any syntax error
|
||||
* before executing additions to the sorted set, as the command should
|
||||
* either execute fully or nothing at all. */
|
||||
scores = zmalloc(sizeof(double)*elements);
|
||||
for (j = 0; j < elements; j++) {
|
||||
if (getDoubleFromObjectOrReply(c,c->argv[scoreidx+j*2],&scores[j],NULL)
|
||||
if (getDoubleFromObjectOrReply(c,c->argv[2+j*2],&scores[j],NULL)
|
||||
!= REDIS_OK) goto cleanup;
|
||||
}
|
||||
|
||||
/* Lookup the key and create the sorted set if does not exist. */
|
||||
zobj = lookupKeyWrite(c->db,key);
|
||||
if (zobj == NULL) {
|
||||
if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */
|
||||
if (server.zset_max_ziplist_entries == 0 ||
|
||||
server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
|
||||
server.zset_max_ziplist_value < sdslen(c->argv[3]->ptr))
|
||||
{
|
||||
zobj = createZsetObject();
|
||||
} else {
|
||||
@@ -1269,9 +1220,8 @@ void zaddGenericCommand(redisClient *c, int flags) {
|
||||
unsigned char *eptr;
|
||||
|
||||
/* Prefer non-encoded element when dealing with ziplists. */
|
||||
ele = c->argv[scoreidx+1+j*2];
|
||||
ele = c->argv[3+j*2];
|
||||
if ((eptr = zzlFind(zobj->ptr,ele,&curscore)) != NULL) {
|
||||
if (nx) continue;
|
||||
if (incr) {
|
||||
score += curscore;
|
||||
if (isnan(score)) {
|
||||
@@ -1287,8 +1237,7 @@ void zaddGenericCommand(redisClient *c, int flags) {
|
||||
server.dirty++;
|
||||
updated++;
|
||||
}
|
||||
processed++;
|
||||
} else if (!xx) {
|
||||
} else {
|
||||
/* Optimize: check if the element is too large or the list
|
||||
* becomes too long *before* executing zzlInsert. */
|
||||
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
|
||||
@@ -1298,18 +1247,15 @@ void zaddGenericCommand(redisClient *c, int flags) {
|
||||
zsetConvert(zobj,REDIS_ENCODING_SKIPLIST);
|
||||
server.dirty++;
|
||||
added++;
|
||||
processed++;
|
||||
}
|
||||
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
|
||||
zset *zs = zobj->ptr;
|
||||
zskiplistNode *znode;
|
||||
dictEntry *de;
|
||||
|
||||
ele = c->argv[scoreidx+1+j*2] =
|
||||
tryObjectEncoding(c->argv[scoreidx+1+j*2]);
|
||||
ele = c->argv[3+j*2] = tryObjectEncoding(c->argv[3+j*2]);
|
||||
de = dictFind(zs->dict,ele);
|
||||
if (de != NULL) {
|
||||
if (nx) continue;
|
||||
curobj = dictGetKey(de);
|
||||
curscore = *(double*)dictGetVal(de);
|
||||
|
||||
@@ -1334,30 +1280,22 @@ void zaddGenericCommand(redisClient *c, int flags) {
|
||||
server.dirty++;
|
||||
updated++;
|
||||
}
|
||||
processed++;
|
||||
} else if (!xx) {
|
||||
} else {
|
||||
znode = zslInsert(zs->zsl,score,ele);
|
||||
incrRefCount(ele); /* Inserted in skiplist. */
|
||||
redisAssertWithInfo(c,NULL,dictAdd(zs->dict,ele,&znode->score) == DICT_OK);
|
||||
incrRefCount(ele); /* Added to dictionary. */
|
||||
server.dirty++;
|
||||
added++;
|
||||
processed++;
|
||||
}
|
||||
} else {
|
||||
redisPanic("Unknown sorted set encoding");
|
||||
}
|
||||
}
|
||||
|
||||
reply_to_client:
|
||||
if (incr) { /* ZINCRBY or INCR option. */
|
||||
if (processed)
|
||||
addReplyDouble(c,score);
|
||||
else
|
||||
addReply(c,shared.nullbulk);
|
||||
} else { /* ZADD. */
|
||||
addReplyLongLong(c,ch ? added+updated : added);
|
||||
}
|
||||
if (incr) /* ZINCRBY */
|
||||
addReplyDouble(c,score);
|
||||
else /* ZADD */
|
||||
addReplyLongLong(c,added);
|
||||
|
||||
cleanup:
|
||||
zfree(scores);
|
||||
@@ -1369,11 +1307,11 @@ cleanup:
|
||||
}
|
||||
|
||||
void zaddCommand(redisClient *c) {
|
||||
zaddGenericCommand(c,ZADD_NONE);
|
||||
zaddGenericCommand(c,0);
|
||||
}
|
||||
|
||||
void zincrbyCommand(redisClient *c) {
|
||||
zaddGenericCommand(c,ZADD_INCR);
|
||||
zaddGenericCommand(c,1);
|
||||
}
|
||||
|
||||
void zremCommand(redisClient *c) {
|
||||
|
||||
+13
-58
@@ -38,10 +38,8 @@
|
||||
#include <sys/time.h>
|
||||
#include <float.h>
|
||||
#include <stdint.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "sha1.h"
|
||||
|
||||
/* Glob-style pattern matching. */
|
||||
int stringmatchlen(const char *pattern, int patternLen,
|
||||
@@ -171,12 +169,11 @@ int stringmatch(const char *pattern, const char *string, int nocase) {
|
||||
}
|
||||
|
||||
/* Convert a string representing an amount of memory into the number of
|
||||
* bytes, so for instance memtoll("1Gb") will return 1073741824 that is
|
||||
* bytes, so for instance memtoll("1Gi") will return 1073741824 that is
|
||||
* (1024*1024*1024).
|
||||
*
|
||||
* On parsing error, if *err is not NULL, it's set to 1, otherwise it's
|
||||
* set to 0. On error the function return value is 0, regardless of the
|
||||
* fact 'err' is NULL or not. */
|
||||
* set to 0 */
|
||||
long long memtoll(const char *p, int *err) {
|
||||
const char *u;
|
||||
char buf[128];
|
||||
@@ -185,7 +182,6 @@ long long memtoll(const char *p, int *err) {
|
||||
unsigned int digits;
|
||||
|
||||
if (err) *err = 0;
|
||||
|
||||
/* Search the first non digit character. */
|
||||
u = p;
|
||||
if (*u == '-') u++;
|
||||
@@ -206,26 +202,16 @@ long long memtoll(const char *p, int *err) {
|
||||
mul = 1024L*1024*1024;
|
||||
} else {
|
||||
if (err) *err = 1;
|
||||
return 0;
|
||||
mul = 1;
|
||||
}
|
||||
|
||||
/* Copy the digits into a buffer, we'll use strtoll() to convert
|
||||
* the digit (without the unit) into a number. */
|
||||
digits = u-p;
|
||||
if (digits >= sizeof(buf)) {
|
||||
if (err) *err = 1;
|
||||
return 0;
|
||||
return LLONG_MAX;
|
||||
}
|
||||
memcpy(buf,p,digits);
|
||||
buf[digits] = '\0';
|
||||
|
||||
char *endptr;
|
||||
errno = 0;
|
||||
val = strtoll(buf,&endptr,10);
|
||||
if ((val == 0 && errno == EINVAL) || *endptr != '\0') {
|
||||
if (err) *err = 1;
|
||||
return 0;
|
||||
}
|
||||
val = strtoll(buf,NULL,10);
|
||||
return val*mul;
|
||||
}
|
||||
|
||||
@@ -442,44 +428,11 @@ int d2string(char *buf, size_t len, double value) {
|
||||
* having run_id == A, and you reconnect and it has run_id == B, you can be
|
||||
* sure that it is either a different instance or it was restarted. */
|
||||
void getRandomHexChars(char *p, unsigned int len) {
|
||||
FILE *fp = fopen("/dev/urandom","r");
|
||||
char *charset = "0123456789abcdef";
|
||||
unsigned int j;
|
||||
|
||||
/* Global state. */
|
||||
static int seed_initialized = 0;
|
||||
static unsigned char seed[20]; /* The SHA1 seed, from /dev/urandom. */
|
||||
static uint64_t counter = 0; /* The counter we hash with the seed. */
|
||||
|
||||
if (!seed_initialized) {
|
||||
/* Initialize a seed and use SHA1 in counter mode, where we hash
|
||||
* the same seed with a progressive counter. For the goals of this
|
||||
* function we just need non-colliding strings, there are no
|
||||
* cryptographic security needs. */
|
||||
FILE *fp = fopen("/dev/urandom","r");
|
||||
if (fp && fread(seed,sizeof(seed),1,fp) == 1)
|
||||
seed_initialized = 1;
|
||||
if (fp) fclose(fp);
|
||||
}
|
||||
|
||||
if (seed_initialized) {
|
||||
while(len) {
|
||||
unsigned char digest[20];
|
||||
SHA1_CTX ctx;
|
||||
unsigned int copylen = len > 20 ? 20 : len;
|
||||
|
||||
SHA1Init(&ctx);
|
||||
SHA1Update(&ctx, seed, sizeof(seed));
|
||||
SHA1Update(&ctx, (unsigned char*)&counter,sizeof(counter));
|
||||
SHA1Final(digest, &ctx);
|
||||
counter++;
|
||||
|
||||
memcpy(p,digest,copylen);
|
||||
/* Convert to hex digits. */
|
||||
for (j = 0; j < copylen; j++) p[j] = charset[p[j] & 0x0F];
|
||||
len -= copylen;
|
||||
p += copylen;
|
||||
}
|
||||
} else {
|
||||
if (fp == NULL || fread(p,len,1,fp) == 0) {
|
||||
/* If we can't read from /dev/urandom, do some reasonable effort
|
||||
* in order to create some entropy, since this function is used to
|
||||
* generate run_id and cluster instance IDs */
|
||||
@@ -506,12 +459,14 @@ void getRandomHexChars(char *p, unsigned int len) {
|
||||
x += sizeof(pid);
|
||||
}
|
||||
/* Finally xor it with rand() output, that was already seeded with
|
||||
* time() at startup, and convert to hex digits. */
|
||||
for (j = 0; j < len; j++) {
|
||||
* time() at startup. */
|
||||
for (j = 0; j < len; j++)
|
||||
p[j] ^= rand();
|
||||
p[j] = charset[p[j] & 0x0F];
|
||||
}
|
||||
}
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* Given the filename, return the absolute path as an SDS string, or NULL
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "3.0.7"
|
||||
#define REDIS_VERSION "2.9.999"
|
||||
|
||||
+3
-2
@@ -51,9 +51,10 @@
|
||||
* <len> is the length of the following string (key or value).
|
||||
* <len> lengths are encoded in a single value or in a 5 bytes value.
|
||||
* If the first byte value (as an unsigned 8 bit value) is between 0 and
|
||||
* 253, it's a single-byte length. If it is 254 then a four bytes unsigned
|
||||
* 252, it's a single-byte length. If it is 253 then a four bytes unsigned
|
||||
* integer follows (in the host byte ordering). A value of 255 is used to
|
||||
* signal the end of the hash.
|
||||
* signal the end of the hash. The special value 254 is used to mark
|
||||
* empty space that can be used to add new key/value pairs.
|
||||
*
|
||||
* <free> is the number of free unused bytes after the string, resulting
|
||||
* from modification of values associated to a key. For instance if "foo"
|
||||
|
||||
+1
-1
@@ -356,7 +356,7 @@ size_t zmalloc_get_smap_bytes_by_field(char *field) {
|
||||
}
|
||||
#else
|
||||
size_t zmalloc_get_smap_bytes_by_field(char *field) {
|
||||
((void) field);
|
||||
REDIS_NOTUSED(field);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -17,12 +17,10 @@ proc main {} {
|
||||
}
|
||||
run_tests
|
||||
cleanup
|
||||
end_tests
|
||||
}
|
||||
|
||||
if {[catch main e]} {
|
||||
puts $::errorInfo
|
||||
if {$::pause_on_error} pause_on_error
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -13,24 +13,6 @@ test "Cluster is up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Enable AOF in all the instances" {
|
||||
foreach_redis_id id {
|
||||
R $id config set appendonly yes
|
||||
# We use "appendfsync no" because it's fast but also guarantees that
|
||||
# write(2) is performed before replying to client.
|
||||
R $id config set appendfsync no
|
||||
}
|
||||
|
||||
foreach_redis_id id {
|
||||
wait_for_condition 1000 500 {
|
||||
[RI $id aof_rewrite_in_progress] == 0 &&
|
||||
[RI $id aof_enabled] == 1
|
||||
} else {
|
||||
fail "Failed to enable AOF on instance #$id"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Return nno-zero if the specified PID is about a process still in execution,
|
||||
# otherwise 0 is returned.
|
||||
proc process_is_running {pid} {
|
||||
@@ -84,18 +66,9 @@ test "Cluster consistency during live resharding" {
|
||||
}
|
||||
|
||||
# Write random data to random list.
|
||||
set listid [randomInt $numkeys]
|
||||
set key "key:$listid"
|
||||
set key "key:[randomInt $numkeys]"
|
||||
set ele [randomValue]
|
||||
# We write both with Lua scripts and with plain commands.
|
||||
# This way we are able to stress Lua -> Redis command invocation
|
||||
# as well, that has tests to prevent Lua to write into wrong
|
||||
# hash slots.
|
||||
if {$listid % 2} {
|
||||
$cluster rpush $key $ele
|
||||
} else {
|
||||
$cluster eval {redis.call("rpush",KEYS[1],ARGV[1])} 1 $key $ele
|
||||
}
|
||||
$cluster rpush $key $ele
|
||||
lappend content($key) $ele
|
||||
|
||||
if {($j % 1000) == 0} {
|
||||
@@ -118,50 +91,3 @@ test "Verify $numkeys keys for consistency with logical content" {
|
||||
assert {[$cluster lrange $key 0 -1] eq $value}
|
||||
}
|
||||
}
|
||||
|
||||
test "Crash and restart all the instances" {
|
||||
foreach_redis_id id {
|
||||
kill_instance redis $id
|
||||
restart_instance redis $id
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster should eventually be up again" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Verify $numkeys keys after the crash & restart" {
|
||||
# Check that the Redis Cluster content matches our logical content.
|
||||
foreach {key value} [array get content] {
|
||||
assert {[$cluster lrange $key 0 -1] eq $value}
|
||||
}
|
||||
}
|
||||
|
||||
test "Disable AOF in all the instances" {
|
||||
foreach_redis_id id {
|
||||
R $id config set appendonly no
|
||||
}
|
||||
}
|
||||
|
||||
test "Verify slaves consistency" {
|
||||
set verified_masters 0
|
||||
foreach_redis_id id {
|
||||
set role [R $id role]
|
||||
lassign $role myrole myoffset slaves
|
||||
if {$myrole eq {slave}} continue
|
||||
set masterport [get_instance_attrib redis $id port]
|
||||
set masterdigest [R $id debug digest]
|
||||
foreach_redis_id sid {
|
||||
set srole [R $sid role]
|
||||
if {[lindex $srole 0] eq {master}} continue
|
||||
if {[lindex $srole 2] != $masterport} continue
|
||||
wait_for_condition 1000 500 {
|
||||
[R $sid debug digest] eq $masterdigest
|
||||
} else {
|
||||
fail "Master and slave data digest are different"
|
||||
}
|
||||
incr verified_masters
|
||||
}
|
||||
}
|
||||
assert {$verified_masters >= 5}
|
||||
}
|
||||
|
||||
@@ -45,59 +45,3 @@ foreach_redis_id id {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Now test the migration to a master which used to be a slave, after
|
||||
# a failver.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
# Create a cluster with 5 master and 10 slaves, so that we have 2
|
||||
# slaves for each master.
|
||||
test "Create a 5 nodes cluster" {
|
||||
create_cluster 5 10
|
||||
}
|
||||
|
||||
test "Cluster is up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Kill slave #7 of master #2. Only slave left is #12 now" {
|
||||
kill_instance redis 7
|
||||
}
|
||||
|
||||
set current_epoch [CI 1 cluster_current_epoch]
|
||||
|
||||
test "Killing master node #2, #12 should failover" {
|
||||
kill_instance redis 2
|
||||
}
|
||||
|
||||
test "Wait for failover" {
|
||||
wait_for_condition 1000 50 {
|
||||
[CI 1 cluster_current_epoch] > $current_epoch
|
||||
} else {
|
||||
fail "No failover detected"
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster should eventually be up again" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 1
|
||||
}
|
||||
|
||||
test "Instance 12 is now a master without slaves" {
|
||||
assert {[RI 12 role] eq {master}}
|
||||
}
|
||||
|
||||
# The remaining instance is now without slaves. Some other slave
|
||||
# should migrate to it.
|
||||
|
||||
test "Master #12 should get at least one migrated replica" {
|
||||
wait_for_condition 1000 50 {
|
||||
[llength [lindex [R 12 role] 2]] >= 1
|
||||
} else {
|
||||
fail "Master #12 has no replicas"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
# Check the manual failover
|
||||
|
||||
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 "Instance #5 synced with the master" {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 5 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Instance #5 master link status is not up"
|
||||
}
|
||||
}
|
||||
|
||||
set current_epoch [CI 1 cluster_current_epoch]
|
||||
|
||||
set numkeys 50000
|
||||
set numops 10000
|
||||
set cluster [redis_cluster 127.0.0.1:[get_instance_attrib redis 0 port]]
|
||||
catch {unset content}
|
||||
array set content {}
|
||||
|
||||
test "Send CLUSTER FAILOVER to #5, during load" {
|
||||
for {set j 0} {$j < $numops} {incr j} {
|
||||
# Write random data to random list.
|
||||
set listid [randomInt $numkeys]
|
||||
set key "key:$listid"
|
||||
set ele [randomValue]
|
||||
# We write both with Lua scripts and with plain commands.
|
||||
# This way we are able to stress Lua -> Redis command invocation
|
||||
# as well, that has tests to prevent Lua to write into wrong
|
||||
# hash slots.
|
||||
if {$listid % 2} {
|
||||
$cluster rpush $key $ele
|
||||
} else {
|
||||
$cluster eval {redis.call("rpush",KEYS[1],ARGV[1])} 1 $key $ele
|
||||
}
|
||||
lappend content($key) $ele
|
||||
|
||||
if {($j % 1000) == 0} {
|
||||
puts -nonewline W; flush stdout
|
||||
}
|
||||
|
||||
if {$j == $numops/2} {R 5 cluster failover}
|
||||
}
|
||||
}
|
||||
|
||||
test "Wait for failover" {
|
||||
wait_for_condition 1000 50 {
|
||||
[CI 1 cluster_current_epoch] > $current_epoch
|
||||
} else {
|
||||
fail "No failover detected"
|
||||
}
|
||||
}
|
||||
|
||||
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}}
|
||||
}
|
||||
|
||||
test "Verify $numkeys keys for consistency with logical content" {
|
||||
# Check that the Redis Cluster content matches our logical content.
|
||||
foreach {key value} [array get content] {
|
||||
assert {[$cluster lrange $key 0 -1] eq $value}
|
||||
}
|
||||
}
|
||||
|
||||
test "Instance #0 gets converted into a slave" {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 0 role] eq {slave}
|
||||
} else {
|
||||
fail "Old master was not converted into slave"
|
||||
}
|
||||
}
|
||||
|
||||
## Check that manual failover does not happen if we can't talk with the master.
|
||||
|
||||
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 "Instance #5 synced with the master" {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 5 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Instance #5 master link status is not up"
|
||||
}
|
||||
}
|
||||
|
||||
test "Make instance #0 unreachable without killing it" {
|
||||
R 0 deferred 1
|
||||
R 0 DEBUG SLEEP 10
|
||||
}
|
||||
|
||||
test "Send CLUSTER FAILOVER to instance #5" {
|
||||
R 5 cluster failover
|
||||
}
|
||||
|
||||
test "Instance #5 is still a slave after some time (no failover)" {
|
||||
after 5000
|
||||
assert {[RI 5 role] eq {master}}
|
||||
}
|
||||
|
||||
test "Wait for instance #0 to return back alive" {
|
||||
R 0 deferred 0
|
||||
assert {[R 0 read] eq {OK}}
|
||||
}
|
||||
|
||||
## Check with "force" failover happens anyway.
|
||||
|
||||
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 "Instance #5 synced with the master" {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 5 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Instance #5 master link status is not up"
|
||||
}
|
||||
}
|
||||
|
||||
test "Make instance #0 unreachable without killing it" {
|
||||
R 0 deferred 1
|
||||
R 0 DEBUG SLEEP 10
|
||||
}
|
||||
|
||||
test "Send CLUSTER FAILOVER to instance #5" {
|
||||
R 5 cluster failover force
|
||||
}
|
||||
|
||||
test "Instance #5 is a master after some time" {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 5 role] eq {master}
|
||||
} else {
|
||||
fail "Instance #5 is not a master after some time regardless of FORCE"
|
||||
}
|
||||
}
|
||||
|
||||
test "Wait for instance #0 to return back alive" {
|
||||
R 0 deferred 0
|
||||
assert {[R 0 read] eq {OK}}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
# Manual takeover test
|
||||
|
||||
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 "Killing majority of master nodes" {
|
||||
kill_instance redis 0
|
||||
kill_instance redis 1
|
||||
kill_instance redis 2
|
||||
}
|
||||
|
||||
test "Cluster should eventually be down" {
|
||||
assert_cluster_state fail
|
||||
}
|
||||
|
||||
test "Use takeover to bring slaves back" {
|
||||
R 5 cluster failover takeover
|
||||
R 6 cluster failover takeover
|
||||
R 7 cluster failover takeover
|
||||
}
|
||||
|
||||
test "Cluster should eventually be up again" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 4
|
||||
}
|
||||
|
||||
test "Instance #5, #6, #7 are now masters" {
|
||||
assert {[RI 5 role] eq {master}}
|
||||
assert {[RI 6 role] eq {master}}
|
||||
assert {[RI 7 role] eq {master}}
|
||||
}
|
||||
|
||||
test "Restarting the previously killed master nodes" {
|
||||
restart_instance redis 0
|
||||
restart_instance redis 1
|
||||
restart_instance redis 2
|
||||
}
|
||||
|
||||
test "Instance #0, #1, #2 gets converted into a slaves" {
|
||||
wait_for_condition 1000 50 {
|
||||
[RI 0 role] eq {slave} && [RI 1 role] eq {slave} && [RI 2 role] eq {slave}
|
||||
} else {
|
||||
fail "Old masters not converted into slaves"
|
||||
}
|
||||
}
|
||||
@@ -27,17 +27,10 @@ test "Cluster nodes are reachable" {
|
||||
|
||||
test "Cluster nodes hard reset" {
|
||||
foreach_redis_id id {
|
||||
if {$::valgrind} {
|
||||
set node_timeout 10000
|
||||
} else {
|
||||
set node_timeout 3000
|
||||
}
|
||||
catch {R $id flushall} ; # May fail for readonly slaves.
|
||||
R $id MULTI
|
||||
R $id cluster reset hard
|
||||
R $id cluster set-config-epoch [expr {$id+1}]
|
||||
R $id EXEC
|
||||
R $id config set cluster-node-timeout $node_timeout
|
||||
R $id config set cluster-node-timeout 3000
|
||||
R $id config set cluster-slave-validity-factor 10
|
||||
R $id config rewrite
|
||||
}
|
||||
|
||||
+14
-117
@@ -16,10 +16,8 @@ source ../support/server.tcl
|
||||
source ../support/test.tcl
|
||||
|
||||
set ::verbose 0
|
||||
set ::valgrind 0
|
||||
set ::pause_on_error 0
|
||||
set ::simulate_error 0
|
||||
set ::failed 0
|
||||
set ::sentinel_instances {}
|
||||
set ::redis_instances {}
|
||||
set ::sentinel_base_port 20000
|
||||
@@ -34,25 +32,6 @@ if {[catch {cd tmp}]} {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Execute the specified instance of the server specified by 'type', using
|
||||
# the provided configuration file. Returns the PID of the process.
|
||||
proc exec_instance {type cfgfile} {
|
||||
if {$type eq "redis"} {
|
||||
set prgname redis-server
|
||||
} elseif {$type eq "sentinel"} {
|
||||
set prgname redis-sentinel
|
||||
} else {
|
||||
error "Unknown instance type."
|
||||
}
|
||||
|
||||
if {$::valgrind} {
|
||||
set pid [exec valgrind --track-origins=yes --suppressions=../../../src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full ../../../src/${prgname} $cfgfile &]
|
||||
} else {
|
||||
set pid [exec ../../../src/${prgname} $cfgfile &]
|
||||
}
|
||||
return $pid
|
||||
}
|
||||
|
||||
# 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} {
|
||||
@@ -79,7 +58,14 @@ proc spawn_instance {type base_port count {conf {}}} {
|
||||
close $cfg
|
||||
|
||||
# Finally exec it and remember the pid for later cleanup.
|
||||
set pid [exec_instance $type $cfgfile]
|
||||
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
|
||||
@@ -99,25 +85,8 @@ proc spawn_instance {type base_port count {conf {}}} {
|
||||
}
|
||||
}
|
||||
|
||||
proc log_crashes {} {
|
||||
set start_pattern {*REDIS BUG REPORT START*}
|
||||
set logs [glob */log.txt]
|
||||
foreach log $logs {
|
||||
set fd [open $log]
|
||||
set found 0
|
||||
while {[gets $fd line] >= 0} {
|
||||
if {[string match $start_pattern $line]} {
|
||||
puts "\n*** Crash report found in $log ***"
|
||||
set found 1
|
||||
}
|
||||
if {$found} {puts $line}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc cleanup {} {
|
||||
puts "Cleaning up..."
|
||||
log_crashes
|
||||
foreach pid $::pids {
|
||||
catch {exec kill -9 $pid}
|
||||
}
|
||||
@@ -127,10 +96,8 @@ proc cleanup {} {
|
||||
}
|
||||
|
||||
proc abort_sentinel_test msg {
|
||||
incr ::failed
|
||||
puts "WARNING: Aborting the test."
|
||||
puts ">>>>>>>> $msg"
|
||||
if {$::pause_on_error} pause_on_error
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
@@ -146,8 +113,6 @@ proc parse_options {} {
|
||||
set ::pause_on_error 1
|
||||
} elseif {$opt eq "--fail"} {
|
||||
set ::simulate_error 1
|
||||
} elseif {$opt eq {--valgrind}} {
|
||||
set ::valgrind 1
|
||||
} elseif {$opt eq "--help"} {
|
||||
puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests."
|
||||
puts "\nOptions:"
|
||||
@@ -250,7 +215,6 @@ proc test {descr code} {
|
||||
flush stdout
|
||||
|
||||
if {[catch {set retval [uplevel 1 $code]} error]} {
|
||||
incr ::failed
|
||||
if {[string match "assertion:*" $error]} {
|
||||
set msg [string range $error 10 end]
|
||||
puts [colorstr red $msg]
|
||||
@@ -266,38 +230,6 @@ proc test {descr code} {
|
||||
}
|
||||
}
|
||||
|
||||
# Check memory leaks when running on OSX using the "leaks" utility.
|
||||
proc check_leaks instance_types {
|
||||
if {[string match {*Darwin*} [exec uname -a]]} {
|
||||
puts -nonewline "Testing for memory leaks..."; flush stdout
|
||||
foreach type $instance_types {
|
||||
foreach_instance_id [set ::${type}_instances] id {
|
||||
if {[instance_is_killed $type $id]} continue
|
||||
set pid [get_instance_attrib $type $id pid]
|
||||
set output {0 leaks}
|
||||
catch {exec leaks $pid} output
|
||||
if {[string match {*process does not exist*} $output] ||
|
||||
[string match {*cannot examine*} $output]} {
|
||||
# In a few tests we kill the server process.
|
||||
set output "0 leaks"
|
||||
} else {
|
||||
puts -nonewline "$type/$pid "
|
||||
flush stdout
|
||||
}
|
||||
if {![string match {*0 leaks*} $output]} {
|
||||
puts [colorstr red "=== MEMORY LEAK DETECTED ==="]
|
||||
puts "Instance type $type, ID $id:"
|
||||
puts $output
|
||||
puts "==="
|
||||
incr ::failed
|
||||
}
|
||||
}
|
||||
}
|
||||
puts ""
|
||||
}
|
||||
}
|
||||
|
||||
# Execute all the units inside the 'tests' directory.
|
||||
proc run_tests {} {
|
||||
set tests [lsort [glob ../tests/*]]
|
||||
foreach test $tests {
|
||||
@@ -307,18 +239,6 @@ proc run_tests {} {
|
||||
if {[file isdirectory $test]} continue
|
||||
puts [colorstr yellow "Testing unit: [lindex [file split $test] end]"]
|
||||
source $test
|
||||
check_leaks {redis sentinel}
|
||||
}
|
||||
}
|
||||
|
||||
# Print a message and exists with 0 / 1 according to zero or more failures.
|
||||
proc end_tests {} {
|
||||
if {$::failed == 0} {
|
||||
puts "GOOD! No errors."
|
||||
exit 0
|
||||
} else {
|
||||
puts "WARNING $::failed tests faield."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,31 +360,15 @@ proc get_instance_id_by_port {type port} {
|
||||
# The instance can be restarted with restart-instance.
|
||||
proc kill_instance {type id} {
|
||||
set pid [get_instance_attrib $type $id pid]
|
||||
set port [get_instance_attrib $type $id port]
|
||||
|
||||
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]
|
||||
|
||||
# Wait for the port it was using to be available again, so that's not
|
||||
# an issue to start a new server ASAP with the same port.
|
||||
set retry 10
|
||||
while {[incr retry -1]} {
|
||||
set port_is_free [catch {set s [socket 127.0.01 $port]}]
|
||||
if {$port_is_free} break
|
||||
catch {close $s}
|
||||
after 1000
|
||||
}
|
||||
if {$retry == 0} {
|
||||
error "Port $port does not return available after killing instance."
|
||||
}
|
||||
}
|
||||
|
||||
# Return true of the instance of the specified type/id is killed.
|
||||
@@ -481,7 +385,12 @@ proc restart_instance {type id} {
|
||||
|
||||
# Execute the instance with its old setup and append the new pid
|
||||
# file for cleanup.
|
||||
set pid [exec_instance $type $cfgfile]
|
||||
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
|
||||
|
||||
@@ -494,17 +403,5 @@ proc restart_instance {type id} {
|
||||
set link [redis 127.0.0.1 $port]
|
||||
$link reconnect 1
|
||||
set_instance_attrib $type $id link $link
|
||||
|
||||
# Make sure the instance is not loading the dataset when this
|
||||
# function returns.
|
||||
while 1 {
|
||||
catch {[$link ping]} retval
|
||||
if {[string match {*LOADING*} $retval]} {
|
||||
after 100
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
set server_path [tmpdir server.log]
|
||||
set system_name [string tolower [exec uname -s]]
|
||||
|
||||
if {$system_name eq {linux} || $system_name eq {darwin}} {
|
||||
start_server [list overrides [list dir $server_path]] {
|
||||
test "Server is able to generate a stack trace on selected systems" {
|
||||
r config set watchdog-period 200
|
||||
r debug sleep 1
|
||||
set pattern "*debugCommand*"
|
||||
set retry 10
|
||||
while {$retry} {
|
||||
set result [exec tail -100 < [srv 0 stdout]]
|
||||
if {[string match $pattern $result]} {
|
||||
break
|
||||
}
|
||||
incr retry -1
|
||||
after 1000
|
||||
}
|
||||
if {$retry == 0} {
|
||||
error "assertion:expected stack trace not found into log file"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-13
@@ -7,19 +7,19 @@ start_server [list overrides [list "dir" $server_path "dbfilename" "encodings.rd
|
||||
test "RDB encoding loading test" {
|
||||
r select 0
|
||||
csvdump r
|
||||
} {"0","compressible","string","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
"0","hash","hash","a","1","aa","10","aaa","100","b","2","bb","20","bbb","200","c","3","cc","30","ccc","300","ddd","400","eee","5000000000",
|
||||
"0","hash_zipped","hash","a","1","b","2","c","3",
|
||||
"0","list","list","1","2","3","a","b","c","100000","6000000000","1","2","3","a","b","c","100000","6000000000","1","2","3","a","b","c","100000","6000000000",
|
||||
"0","list_zipped","list","1","2","3","a","b","c","100000","6000000000",
|
||||
"0","number","string","10"
|
||||
"0","set","set","1","100000","2","3","6000000000","a","b","c",
|
||||
"0","set_zipped_1","set","1","2","3","4",
|
||||
"0","set_zipped_2","set","100000","200000","300000","400000",
|
||||
"0","set_zipped_3","set","1000000000","2000000000","3000000000","4000000000","5000000000","6000000000",
|
||||
"0","string","string","Hello World"
|
||||
"0","zset","zset","a","1","b","2","c","3","aa","10","bb","20","cc","30","aaa","100","bbb","200","ccc","300","aaaa","1000","cccc","123456789","bbbb","5000000000",
|
||||
"0","zset_zipped","zset","a","1","b","2","c","3",
|
||||
} {"compressible","string","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
"hash","hash","a","1","aa","10","aaa","100","b","2","bb","20","bbb","200","c","3","cc","30","ccc","300","ddd","400","eee","5000000000",
|
||||
"hash_zipped","hash","a","1","b","2","c","3",
|
||||
"list","list","1","2","3","a","b","c","100000","6000000000","1","2","3","a","b","c","100000","6000000000","1","2","3","a","b","c","100000","6000000000",
|
||||
"list_zipped","list","1","2","3","a","b","c","100000","6000000000",
|
||||
"number","string","10"
|
||||
"set","set","1","100000","2","3","6000000000","a","b","c",
|
||||
"set_zipped_1","set","1","2","3","4",
|
||||
"set_zipped_2","set","100000","200000","300000","400000",
|
||||
"set_zipped_3","set","1000000000","2000000000","3000000000","4000000000","5000000000","6000000000",
|
||||
"string","string","Hello World"
|
||||
"zset","zset","a","1","b","2","c","3","aa","10","bb","20","cc","30","aaa","100","bbb","200","ccc","300","aaaa","1000","cccc","123456789","bbbb","5000000000",
|
||||
"zset_zipped","zset","a","1","b","2","c","3",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,7 @@ proc stop_bg_complex_data {handle} {
|
||||
#
|
||||
# You can specifiy backlog size, ttl, delay before reconnection, test duration
|
||||
# in seconds, and an additional condition to verify at the end.
|
||||
#
|
||||
# If reconnect is > 0, the test actually try to break the connection and
|
||||
# reconnect with the master, otherwise just the initial synchronization is
|
||||
# checked for consistency.
|
||||
proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless reconnect} {
|
||||
proc test_psync {descr duration backlog_size backlog_ttl delay cond} {
|
||||
start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
|
||||
@@ -28,8 +24,6 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
|
||||
|
||||
$master config set repl-backlog-size $backlog_size
|
||||
$master config set repl-backlog-ttl $backlog_ttl
|
||||
$master config set repl-diskless-sync $diskless
|
||||
$master config set repl-diskless-sync-delay 1
|
||||
|
||||
set load_handle0 [start_bg_complex_data $master_host $master_port 9 100000]
|
||||
set load_handle1 [start_bg_complex_data $master_host $master_port 11 100000]
|
||||
@@ -54,24 +48,22 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
|
||||
}
|
||||
}
|
||||
|
||||
test "Test replication partial resync: $descr (diskless: $diskless, reconnect: $reconnect)" {
|
||||
test "Test replication partial resync: $descr" {
|
||||
# Now while the clients are writing data, break the maste-slave
|
||||
# link multiple times.
|
||||
if ($reconnect) {
|
||||
for {set j 0} {$j < $duration*10} {incr j} {
|
||||
after 100
|
||||
# catch {puts "MASTER [$master dbsize] keys, SLAVE [$slave dbsize] keys"}
|
||||
for {set j 0} {$j < $duration*10} {incr j} {
|
||||
after 100
|
||||
# catch {puts "MASTER [$master dbsize] keys, SLAVE [$slave dbsize] keys"}
|
||||
|
||||
if {($j % 20) == 0} {
|
||||
catch {
|
||||
if {$delay} {
|
||||
$slave multi
|
||||
$slave client kill $master_host:$master_port
|
||||
$slave debug sleep $delay
|
||||
$slave exec
|
||||
} else {
|
||||
$slave client kill $master_host:$master_port
|
||||
}
|
||||
if {($j % 20) == 0} {
|
||||
catch {
|
||||
if {$delay} {
|
||||
$slave multi
|
||||
$slave client kill $master_host:$master_port
|
||||
$slave debug sleep $delay
|
||||
$slave exec
|
||||
} else {
|
||||
$slave client kill $master_host:$master_port
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,23 +98,18 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
|
||||
}
|
||||
}
|
||||
|
||||
foreach diskless {no yes} {
|
||||
test_psync {no reconnection, just sync} 6 1000000 3600 0 {
|
||||
} $diskless 0
|
||||
test_psync {ok psync} 6 1000000 3600 0 {
|
||||
assert {[s -1 sync_partial_ok] > 0}
|
||||
}
|
||||
|
||||
test_psync {ok psync} 6 1000000 3600 0 {
|
||||
assert {[s -1 sync_partial_ok] > 0}
|
||||
} $diskless 1
|
||||
test_psync {no backlog} 6 100 3600 0.5 {
|
||||
assert {[s -1 sync_partial_err] > 0}
|
||||
}
|
||||
|
||||
test_psync {no backlog} 6 100 3600 0.5 {
|
||||
assert {[s -1 sync_partial_err] > 0}
|
||||
} $diskless 1
|
||||
test_psync {ok after delay} 3 100000000 3600 3 {
|
||||
assert {[s -1 sync_partial_ok] > 0}
|
||||
}
|
||||
|
||||
test_psync {ok after delay} 3 100000000 3600 3 {
|
||||
assert {[s -1 sync_partial_ok] > 0}
|
||||
} $diskless 1
|
||||
|
||||
test_psync {backlog expired} 3 100000000 1 3 {
|
||||
assert {[s -1 sync_partial_err] > 0}
|
||||
} $diskless 1
|
||||
test_psync {backlog expired} 3 100000000 1 3 {
|
||||
assert {[s -1 sync_partial_err] > 0}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,10 @@
|
||||
proc log_file_matches {log pattern} {
|
||||
set fp [open $log r]
|
||||
set content [read $fp]
|
||||
close $fp
|
||||
string match $pattern $content
|
||||
}
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
set slave [srv 0 client]
|
||||
set slave_host [srv 0 host]
|
||||
set slave_port [srv 0 port]
|
||||
set slave_log [srv 0 stdout]
|
||||
start_server {} {
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
|
||||
# Configure the master in order to hang waiting for the BGSAVE
|
||||
# operation, so that the slave remains in the handshake state.
|
||||
$master config set repl-diskless-sync yes
|
||||
$master config set repl-diskless-sync-delay 1000
|
||||
|
||||
# Use a short replication timeout on the slave, so that if there
|
||||
# are no bugs the timeout is triggered in a reasonable amount
|
||||
# of time.
|
||||
$slave config set repl-timeout 5
|
||||
|
||||
# Start the replication process...
|
||||
$slave slaveof $master_host $master_port
|
||||
|
||||
test {Slave enters handshake} {
|
||||
wait_for_condition 50 1000 {
|
||||
[string match *handshake* [$slave role]]
|
||||
} else {
|
||||
fail "Slave does not enter handshake state"
|
||||
}
|
||||
}
|
||||
|
||||
# But make the master unable to send
|
||||
# the periodic newlines to refresh the connection. The slave
|
||||
# should detect the timeout.
|
||||
$master debug sleep 10
|
||||
|
||||
test {Slave is able to detect timeout during handshake} {
|
||||
wait_for_condition 50 1000 {
|
||||
[log_file_matches $slave_log "*Timeout connecting to the MASTER*"]
|
||||
} else {
|
||||
fail "Slave is not able to detect timeout"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
set A [srv 0 client]
|
||||
set A_host [srv 0 host]
|
||||
set A_port [srv 0 port]
|
||||
start_server {} {
|
||||
set B [srv 0 client]
|
||||
set B_host [srv 0 host]
|
||||
set B_port [srv 0 port]
|
||||
|
||||
test {Set instance A as slave of B} {
|
||||
$A slaveof $B_host $B_port
|
||||
test {First server should have role slave after SLAVEOF} {
|
||||
r -1 slaveof [srv 0 host] [srv 0 port]
|
||||
wait_for_condition 50 100 {
|
||||
[lindex [$A role] 0] eq {slave} &&
|
||||
[string match {*master_link_status:up*} [$A info replication]]
|
||||
[s -1 role] eq {slave} &&
|
||||
[string match {*master_link_status:up*} [r -1 info replication]]
|
||||
} else {
|
||||
fail "Can't turn the instance into a slave"
|
||||
}
|
||||
@@ -75,9 +15,9 @@ start_server {tags {"repl"}} {
|
||||
$rd brpoplpush a b 5
|
||||
r lpush a foo
|
||||
wait_for_condition 50 100 {
|
||||
[$A debug digest] eq [$B debug digest]
|
||||
[r debug digest] eq [r -1 debug digest]
|
||||
} else {
|
||||
fail "Master and slave have different digest: [$A debug digest] VS [$B debug digest]"
|
||||
fail "Master and slave have different digest: [r debug digest] VS [r -1 debug digest]"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,36 +28,7 @@ start_server {tags {"repl"}} {
|
||||
r lpush c 3
|
||||
$rd brpoplpush c d 5
|
||||
after 1000
|
||||
assert_equal [$A debug digest] [$B debug digest]
|
||||
}
|
||||
|
||||
test {BLPOP followed by role change, issue #2473} {
|
||||
set rd [redis_deferring_client]
|
||||
$rd blpop foo 0 ; # Block while B is a master
|
||||
|
||||
# Turn B into master of A
|
||||
$A slaveof no one
|
||||
$B slaveof $A_host $A_port
|
||||
wait_for_condition 50 100 {
|
||||
[lindex [$B role] 0] eq {slave} &&
|
||||
[string match {*master_link_status:up*} [$B info replication]]
|
||||
} else {
|
||||
fail "Can't turn the instance into a slave"
|
||||
}
|
||||
|
||||
# Push elements into the "foo" list of the new slave.
|
||||
# If the client is still attached to the instance, we'll get
|
||||
# a desync between the two instances.
|
||||
$A rpush foo a b c
|
||||
after 100
|
||||
|
||||
wait_for_condition 50 100 {
|
||||
[$A debug digest] eq [$B debug digest] &&
|
||||
[$A lrange foo 0 -1] eq {a b c} &&
|
||||
[$B lrange foo 0 -1] eq {a b c}
|
||||
} else {
|
||||
fail "Master and slave have different digest: [$A debug digest] VS [$B debug digest]"
|
||||
}
|
||||
assert_equal [r debug digest] [r -1 debug digest]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,13 +113,12 @@ foreach dl {no yes} {
|
||||
start_server {} {
|
||||
lappend slaves [srv 0 client]
|
||||
test "Connect multiple slaves at the same time (issue #141), diskless=$dl" {
|
||||
# Send SLAVEOF commands to slaves
|
||||
# Send SALVEOF commands to slaves
|
||||
[lindex $slaves 0] slaveof $master_host $master_port
|
||||
[lindex $slaves 1] slaveof $master_host $master_port
|
||||
[lindex $slaves 2] slaveof $master_host $master_port
|
||||
|
||||
# Wait for all the three slaves to reach the "online"
|
||||
# state from the POV of the master.
|
||||
# Wait for all the three slaves to reach the "online" state
|
||||
set retry 500
|
||||
while {$retry} {
|
||||
set info [r -3 info]
|
||||
@@ -223,17 +133,6 @@ foreach dl {no yes} {
|
||||
error "assertion:Slaves not correctly synchronized"
|
||||
}
|
||||
|
||||
# Wait that slaves acknowledge they are online so
|
||||
# we are sure that DBSIZE and DEBUG DIGEST will not
|
||||
# fail because of timing issues.
|
||||
wait_for_condition 500 100 {
|
||||
[lindex [[lindex $slaves 0] role] 3] eq {connected} &&
|
||||
[lindex [[lindex $slaves 1] role] 3] eq {connected} &&
|
||||
[lindex [[lindex $slaves 2] role] 3] eq {connected}
|
||||
} else {
|
||||
fail "Slaves still not connected after some time"
|
||||
}
|
||||
|
||||
# Stop the write load
|
||||
stop_write_load $load_handle0
|
||||
stop_write_load $load_handle1
|
||||
@@ -241,8 +140,16 @@ foreach dl {no yes} {
|
||||
stop_write_load $load_handle3
|
||||
stop_write_load $load_handle4
|
||||
|
||||
# Make sure that slaves and master have same
|
||||
# number of keys
|
||||
# Wait that slaves exit the "loading" state
|
||||
wait_for_condition 500 100 {
|
||||
![string match {*loading:1*} [[lindex $slaves 0] info]] &&
|
||||
![string match {*loading:1*} [[lindex $slaves 1] info]] &&
|
||||
![string match {*loading:1*} [[lindex $slaves 2] info]]
|
||||
} else {
|
||||
fail "Slaves still loading data after too much time"
|
||||
}
|
||||
|
||||
# Make sure that slaves and master have same number of keys
|
||||
wait_for_condition 500 100 {
|
||||
[$master dbsize] == [[lindex $slaves 0] dbsize] &&
|
||||
[$master dbsize] == [[lindex $slaves 1] dbsize] &&
|
||||
|
||||
@@ -13,7 +13,6 @@ proc main {} {
|
||||
spawn_instance redis $::redis_base_port $::instances_count
|
||||
run_tests
|
||||
cleanup
|
||||
end_tests
|
||||
}
|
||||
|
||||
if {[catch main e]} {
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# Test for the SENTINEL CKQUORUM command
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
set num_sentinels [llength $::sentinel_instances]
|
||||
|
||||
test "CKQUORUM reports OK and the right amount of Sentinels" {
|
||||
foreach_sentinel_id id {
|
||||
assert_match "*OK $num_sentinels usable*" [S $id SENTINEL CKQUORUM mymaster]
|
||||
}
|
||||
}
|
||||
|
||||
test "CKQUORUM detects quorum cannot be reached" {
|
||||
set orig_quorum [expr {$num_sentinels/2+1}]
|
||||
S 0 SENTINEL SET mymaster quorum [expr {$num_sentinels+1}]
|
||||
catch {[S 0 SENTINEL CKQUORUM mymaster]} err
|
||||
assert_match "*NOQUORUM*" $err
|
||||
S 0 SENTINEL SET mymaster quorum $orig_quorum
|
||||
}
|
||||
|
||||
test "CKQUORUM detects failover authorization cannot be reached" {
|
||||
set orig_quorum [expr {$num_sentinels/2+1}]
|
||||
S 0 SENTINEL SET mymaster quorum 1
|
||||
kill_instance sentinel 1
|
||||
kill_instance sentinel 2
|
||||
kill_instance sentinel 3
|
||||
after 5000
|
||||
catch {[S 0 SENTINEL CKQUORUM mymaster]} err
|
||||
assert_match "*NOQUORUM*" $err
|
||||
S 0 SENTINEL SET mymaster quorum $orig_quorum
|
||||
restart_instance sentinel 1
|
||||
restart_instance sentinel 2
|
||||
restart_instance sentinel 3
|
||||
}
|
||||
|
||||
@@ -226,8 +226,6 @@ proc ::redis_cluster::get_keys_from_command {cmd argv} {
|
||||
# Special handling for other commands
|
||||
switch -exact $cmd {
|
||||
mget {return $argv}
|
||||
eval {return [lrange $argv 2 1+[lindex $argv 1]]}
|
||||
evalsha {return [lrange $argv 2 1+[lindex $argv 1]]}
|
||||
}
|
||||
|
||||
# All the remaining commands are not handled.
|
||||
|
||||
@@ -54,15 +54,10 @@ proc kill_server config {
|
||||
|
||||
# kill server and wait for the process to be totally exited
|
||||
catch {exec kill $pid}
|
||||
if {$::valgrind} {
|
||||
set max_wait 60000
|
||||
} else {
|
||||
set max_wait 10000
|
||||
}
|
||||
while {[is_alive $config]} {
|
||||
incr wait 10
|
||||
|
||||
if {$wait >= $max_wait} {
|
||||
if {$wait >= 5000} {
|
||||
puts "Forcing process $pid to exit..."
|
||||
catch {exec kill -KILL $pid}
|
||||
} elseif {$wait % 1000 == 0} {
|
||||
@@ -75,9 +70,6 @@ proc kill_server config {
|
||||
if {$::valgrind} {
|
||||
check_valgrind_errors [dict get $config stderr]
|
||||
}
|
||||
|
||||
# Remove this pid from the set of active pids in the test server.
|
||||
send_data_packet $::test_server_fd server-killed $pid
|
||||
}
|
||||
|
||||
proc is_alive config {
|
||||
@@ -212,14 +204,11 @@ proc start_server {options {code undefined}} {
|
||||
set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
|
||||
|
||||
if {$::valgrind} {
|
||||
set pid [exec valgrind --track-origins=yes --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
exec valgrind --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/redis-server $config_file > $stdout 2> $stderr &
|
||||
} else {
|
||||
set pid [exec src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
exec src/redis-server $config_file > $stdout 2> $stderr &
|
||||
}
|
||||
|
||||
# Tell the test server about this new instance.
|
||||
send_data_packet $::test_server_fd server-spawned $pid
|
||||
|
||||
# check that the server actually started
|
||||
# ugly but tries to be as fast as possible...
|
||||
if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
|
||||
@@ -245,9 +234,9 @@ proc start_server {options {code undefined}} {
|
||||
return
|
||||
}
|
||||
|
||||
# Wait for actual startup
|
||||
while {![info exists _pid]} {
|
||||
regexp {PID:\s(\d+)} [exec cat $stdout] _ _pid
|
||||
# find out the pid
|
||||
while {![info exists pid]} {
|
||||
regexp {PID:\s(\d+)} [exec cat $stdout] _ pid
|
||||
after 100
|
||||
}
|
||||
|
||||
|
||||
+32
-36
@@ -262,50 +262,46 @@ proc formatCommand {args} {
|
||||
|
||||
proc csvdump r {
|
||||
set o {}
|
||||
for {set db 0} {$db < 16} {incr db} {
|
||||
{*}$r select $db
|
||||
foreach k [lsort [{*}$r keys *]] {
|
||||
set type [{*}$r type $k]
|
||||
append o [csvstring $db] , [csvstring $k] , [csvstring $type] ,
|
||||
switch $type {
|
||||
string {
|
||||
append o [csvstring [{*}$r get $k]] "\n"
|
||||
foreach k [lsort [{*}$r keys *]] {
|
||||
set type [{*}$r type $k]
|
||||
append o [csvstring $k] , [csvstring $type] ,
|
||||
switch $type {
|
||||
string {
|
||||
append o [csvstring [{*}$r get $k]] "\n"
|
||||
}
|
||||
list {
|
||||
foreach e [{*}$r lrange $k 0 -1] {
|
||||
append o [csvstring $e] ,
|
||||
}
|
||||
list {
|
||||
foreach e [{*}$r lrange $k 0 -1] {
|
||||
append o [csvstring $e] ,
|
||||
}
|
||||
append o "\n"
|
||||
append o "\n"
|
||||
}
|
||||
set {
|
||||
foreach e [lsort [{*}$r smembers $k]] {
|
||||
append o [csvstring $e] ,
|
||||
}
|
||||
set {
|
||||
foreach e [lsort [{*}$r smembers $k]] {
|
||||
append o [csvstring $e] ,
|
||||
}
|
||||
append o "\n"
|
||||
append o "\n"
|
||||
}
|
||||
zset {
|
||||
foreach e [{*}$r zrange $k 0 -1 withscores] {
|
||||
append o [csvstring $e] ,
|
||||
}
|
||||
zset {
|
||||
foreach e [{*}$r zrange $k 0 -1 withscores] {
|
||||
append o [csvstring $e] ,
|
||||
}
|
||||
append o "\n"
|
||||
append o "\n"
|
||||
}
|
||||
hash {
|
||||
set fields [{*}$r hgetall $k]
|
||||
set newfields {}
|
||||
foreach {k v} $fields {
|
||||
lappend newfields [list $k $v]
|
||||
}
|
||||
hash {
|
||||
set fields [{*}$r hgetall $k]
|
||||
set newfields {}
|
||||
foreach {k v} $fields {
|
||||
lappend newfields [list $k $v]
|
||||
}
|
||||
set fields [lsort -index 0 $newfields]
|
||||
foreach kv $fields {
|
||||
append o [csvstring [lindex $kv 0]] ,
|
||||
append o [csvstring [lindex $kv 1]] ,
|
||||
}
|
||||
append o "\n"
|
||||
set fields [lsort -index 0 $newfields]
|
||||
foreach kv $fields {
|
||||
append o [csvstring [lindex $kv 0]] ,
|
||||
append o [csvstring [lindex $kv 1]] ,
|
||||
}
|
||||
append o "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
{*}$r select 9
|
||||
return $o
|
||||
}
|
||||
|
||||
|
||||
+19
-55
@@ -38,7 +38,6 @@ set ::all_tests {
|
||||
integration/aof
|
||||
integration/rdb
|
||||
integration/convert-zipmap-hash-on-load
|
||||
integration/logging
|
||||
unit/pubsub
|
||||
unit/slowlog
|
||||
unit/scripting
|
||||
@@ -66,9 +65,6 @@ set ::file ""; # If set, runs only the tests in this comma separated list
|
||||
set ::curfile ""; # Hold the filename of the current suite
|
||||
set ::accurate 0; # If true runs fuzz tests with more iterations
|
||||
set ::force_failure 0
|
||||
set ::timeout 600; # 10 minutes without progresses will quit the test.
|
||||
set ::last_progress [clock seconds]
|
||||
set ::active_servers {} ; # Pids of active Redis instances.
|
||||
|
||||
# Set to 1 when we are running in client mode. The Redis test uses a
|
||||
# server-client model to run tests simultaneously. The server instance
|
||||
@@ -204,19 +200,11 @@ proc test_server_main {} {
|
||||
vwait forever
|
||||
}
|
||||
|
||||
# This function gets called 10 times per second.
|
||||
# This function gets called 10 times per second, for now does nothing but
|
||||
# may be used in the future in order to detect test clients taking too much
|
||||
# time to execute the task.
|
||||
proc test_server_cron {} {
|
||||
set elapsed [expr {[clock seconds]-$::last_progress}]
|
||||
|
||||
if {$elapsed > $::timeout} {
|
||||
set err "\[[colorstr red TIMEOUT]\]: clients state report follows."
|
||||
puts $err
|
||||
show_clients_state
|
||||
kill_clients
|
||||
force_kill_all_servers
|
||||
the_end
|
||||
}
|
||||
|
||||
# Do some work here.
|
||||
after 100 test_server_cron
|
||||
}
|
||||
|
||||
@@ -242,8 +230,6 @@ proc read_from_test_client fd {
|
||||
set bytes [gets $fd]
|
||||
set payload [read $fd $bytes]
|
||||
foreach {status data} $payload break
|
||||
set ::last_progress [clock seconds]
|
||||
|
||||
if {$status eq {ready}} {
|
||||
if {!$::quiet} {
|
||||
puts "\[$status\]: $data"
|
||||
@@ -270,15 +256,12 @@ proc read_from_test_client fd {
|
||||
set ::active_clients_task($fd) "(ERR) $data"
|
||||
} elseif {$status eq {exception}} {
|
||||
puts "\[[colorstr red $status]\]: $data"
|
||||
kill_clients
|
||||
force_kill_all_servers
|
||||
foreach p $::clients_pids {
|
||||
catch {exec kill -9 $p}
|
||||
}
|
||||
exit 1
|
||||
} elseif {$status eq {testing}} {
|
||||
set ::active_clients_task($fd) "(IN PROGRESS) $data"
|
||||
} elseif {$status eq {server-spawned}} {
|
||||
lappend ::active_servers $data
|
||||
} elseif {$status eq {server-killed}} {
|
||||
set ::active_servers [lsearch -all -inline -not -exact $::active_servers $data]
|
||||
} else {
|
||||
if {!$::quiet} {
|
||||
puts "\[$status\]: $data"
|
||||
@@ -286,31 +269,6 @@ proc read_from_test_client fd {
|
||||
}
|
||||
}
|
||||
|
||||
proc show_clients_state {} {
|
||||
# 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 => ???"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proc kill_clients {} {
|
||||
foreach p $::clients_pids {
|
||||
catch {exec kill $p}
|
||||
}
|
||||
}
|
||||
|
||||
proc force_kill_all_servers {} {
|
||||
foreach p $::active_servers {
|
||||
puts "Killing still running Redis server $p"
|
||||
catch {exec kill -9 $p}
|
||||
}
|
||||
}
|
||||
|
||||
# A new client is idle. Remove it from the list of active clients and
|
||||
# if there are still test units to run, launch them.
|
||||
proc signal_idle_client fd {
|
||||
@@ -318,7 +276,17 @@ proc signal_idle_client fd {
|
||||
set ::active_clients \
|
||||
[lsearch -all -inline -not -exact $::active_clients $fd]
|
||||
|
||||
if 0 {show_clients_state}
|
||||
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]} {
|
||||
@@ -393,8 +361,7 @@ proc print_help_screen {} {
|
||||
"--quiet Don't show individual tests."
|
||||
"--single <unit> Just execute the specified unit (see next option)."
|
||||
"--list-tests List all the available test units."
|
||||
"--clients <num> Number of test clients (default 16)."
|
||||
"--timeout <sec> Test timeout in seconds (default 10 min)."
|
||||
"--clients <num> Number of test clients (16)."
|
||||
"--force-failure Force the execution of a test that always fails."
|
||||
"--help Print this help screen."
|
||||
} "\n"]
|
||||
@@ -443,9 +410,6 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
} elseif {$opt eq {--clients}} {
|
||||
set ::numclients $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--timeout}} {
|
||||
set ::timeout $arg
|
||||
incr j
|
||||
} elseif {$opt eq {--help}} {
|
||||
print_help_screen
|
||||
exit 0
|
||||
|
||||
@@ -433,32 +433,6 @@ start_server {tags {"basic"}} {
|
||||
set e
|
||||
} {*ERR*index out of range}
|
||||
|
||||
test {MOVE can move key expire metadata as well} {
|
||||
r select 10
|
||||
r flushdb
|
||||
r select 9
|
||||
r set mykey foo ex 100
|
||||
r move mykey 10
|
||||
assert {[r ttl mykey] == -2}
|
||||
r select 10
|
||||
assert {[r ttl mykey] > 0 && [r ttl mykey] <= 100}
|
||||
assert {[r get mykey] eq "foo"}
|
||||
r select 9
|
||||
}
|
||||
|
||||
test {MOVE does not create an expire if it does not exist} {
|
||||
r select 10
|
||||
r flushdb
|
||||
r select 9
|
||||
r set mykey foo
|
||||
r move mykey 10
|
||||
assert {[r ttl mykey] == -2}
|
||||
r select 10
|
||||
assert {[r ttl mykey] == -1}
|
||||
assert {[r get mykey] eq "foo"}
|
||||
r select 9
|
||||
}
|
||||
|
||||
test {SET/GET keys in different DBs} {
|
||||
r set a hello
|
||||
r set b world
|
||||
|
||||
@@ -217,95 +217,4 @@ start_server {tags {"dump"}} {
|
||||
assert_match {IOERR*} $e
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE can migrate multiple keys at once} {
|
||||
set first [srv 0 client]
|
||||
r set key1 "v1"
|
||||
r set key2 "v2"
|
||||
r set key3 "v3"
|
||||
start_server {tags {"repl"}} {
|
||||
set second [srv 0 client]
|
||||
set second_host [srv 0 host]
|
||||
set second_port [srv 0 port]
|
||||
|
||||
assert {[$first exists key1] == 1}
|
||||
assert {[$second exists key1] == 0}
|
||||
set ret [r -1 migrate $second_host $second_port "" 9 5000 keys key1 key2 key3]
|
||||
assert {$ret eq {OK}}
|
||||
assert {[$first exists key1] == 0}
|
||||
assert {[$first exists key2] == 0}
|
||||
assert {[$first exists key3] == 0}
|
||||
assert {[$second get key1] eq {v1}}
|
||||
assert {[$second get key2] eq {v2}}
|
||||
assert {[$second get key3] eq {v3}}
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE with multiple keys must have empty key arg} {
|
||||
catch {r MIGRATE 127.0.0.1 6379 NotEmpty 9 5000 keys a b c} e
|
||||
set e
|
||||
} {*empty string*}
|
||||
|
||||
test {MIGRATE with mutliple keys migrate just existing ones} {
|
||||
set first [srv 0 client]
|
||||
r set key1 "v1"
|
||||
r set key2 "v2"
|
||||
r set key3 "v3"
|
||||
start_server {tags {"repl"}} {
|
||||
set second [srv 0 client]
|
||||
set second_host [srv 0 host]
|
||||
set second_port [srv 0 port]
|
||||
|
||||
set ret [r -1 migrate $second_host $second_port "" 9 5000 keys nokey-1 nokey-2 nokey-2]
|
||||
assert {$ret eq {NOKEY}}
|
||||
|
||||
assert {[$first exists key1] == 1}
|
||||
assert {[$second exists key1] == 0}
|
||||
set ret [r -1 migrate $second_host $second_port "" 9 5000 keys nokey-1 key1 nokey-2 key2 nokey-3 key3]
|
||||
assert {$ret eq {OK}}
|
||||
assert {[$first exists key1] == 0}
|
||||
assert {[$first exists key2] == 0}
|
||||
assert {[$first exists key3] == 0}
|
||||
assert {[$second get key1] eq {v1}}
|
||||
assert {[$second get key2] eq {v2}}
|
||||
assert {[$second get key3] eq {v3}}
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE with multiple keys: stress command rewriting} {
|
||||
set first [srv 0 client]
|
||||
r flushdb
|
||||
r mset a 1 b 2 c 3 d 4 c 5 e 6 f 7 g 8 h 9 i 10 l 11 m 12 n 13 o 14 p 15 q 16
|
||||
start_server {tags {"repl"}} {
|
||||
set second [srv 0 client]
|
||||
set second_host [srv 0 host]
|
||||
set second_port [srv 0 port]
|
||||
|
||||
set ret [r -1 migrate $second_host $second_port "" 9 5000 keys a b c d e f g h i l m n o p q]
|
||||
|
||||
assert {[$first dbsize] == 0}
|
||||
assert {[$second dbsize] == 15}
|
||||
}
|
||||
}
|
||||
|
||||
test {MIGRATE with multiple keys: delete just ack keys} {
|
||||
set first [srv 0 client]
|
||||
r flushdb
|
||||
r mset a 1 b 2 c 3 d 4 c 5 e 6 f 7 g 8 h 9 i 10 l 11 m 12 n 13 o 14 p 15 q 16
|
||||
start_server {tags {"repl"}} {
|
||||
set second [srv 0 client]
|
||||
set second_host [srv 0 host]
|
||||
set second_port [srv 0 port]
|
||||
|
||||
$second mset c _ d _; # Two busy keys and no REPLACE used
|
||||
|
||||
catch {r -1 migrate $second_host $second_port "" 9 5000 keys a b c d e f g h i l m n o p q} e
|
||||
|
||||
assert {[$first dbsize] == 2}
|
||||
assert {[$second dbsize] == 15}
|
||||
assert {[$first exists c] == 1}
|
||||
assert {[$first exists d] == 1}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
proc test_memory_efficiency {range} {
|
||||
r flushall
|
||||
set rd [redis_deferring_client]
|
||||
set base_mem [s used_memory]
|
||||
set written 0
|
||||
for {set j 0} {$j < 10000} {incr j} {
|
||||
set key key:$j
|
||||
set val [string repeat A [expr {int(rand()*$range)}]]
|
||||
$rd set $key $val
|
||||
r set $key $val
|
||||
incr written [string length $key]
|
||||
incr written [string length $val]
|
||||
incr written 2 ;# A separator is the minimum to store key-value data.
|
||||
}
|
||||
for {set j 0} {$j < 10000} {incr j} {
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
|
||||
set current_mem [s used_memory]
|
||||
set used [expr {$current_mem-$base_mem}]
|
||||
set efficiency [expr {double($written)/$used}]
|
||||
|
||||
@@ -226,14 +226,4 @@ start_server {tags {"scan"}} {
|
||||
set res [r zscan mykey 0 MATCH foo* COUNT 10000]
|
||||
lsort -unique [lindex $res 1]
|
||||
}
|
||||
|
||||
test "ZSCAN scores: regression test for issue #2175" {
|
||||
r del mykey
|
||||
for {set j 0} {$j < 500} {incr j} {
|
||||
r zadd mykey 9.8813129168249309e-323 $j
|
||||
}
|
||||
set res [lindex [r zscan mykey 0] 1]
|
||||
set first_score [lindex $res 1]
|
||||
assert {$first_score != 0}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,98 +184,6 @@ start_server {tags {"scripting"}} {
|
||||
set e
|
||||
} {*against a key*}
|
||||
|
||||
test {EVAL - JSON numeric decoding} {
|
||||
# We must return the table as a string because otherwise
|
||||
# Redis converts floats to ints and we get 0 and 1023 instead
|
||||
# of 0.0003 and 1023.2 as the parsed output.
|
||||
r eval {return
|
||||
table.concat(
|
||||
cjson.decode(
|
||||
"[0.0, -5e3, -1, 0.3e-3, 1023.2, 0e10]"), " ")
|
||||
} 0
|
||||
} {0 -5000 -1 0.0003 1023.2 0}
|
||||
|
||||
test {EVAL - JSON string decoding} {
|
||||
r eval {local decoded = cjson.decode('{"keya": "a", "keyb": "b"}')
|
||||
return {decoded.keya, decoded.keyb}
|
||||
} 0
|
||||
} {a b}
|
||||
|
||||
test {EVAL - cmsgpack can pack double?} {
|
||||
r eval {local encoded = cmsgpack.pack(0.1)
|
||||
local h = ""
|
||||
for i = 1, #encoded do
|
||||
h = h .. string.format("%02x",string.byte(encoded,i))
|
||||
end
|
||||
return h
|
||||
} 0
|
||||
} {cb3fb999999999999a}
|
||||
|
||||
test {EVAL - cmsgpack can pack negative int64?} {
|
||||
r eval {local encoded = cmsgpack.pack(-1099511627776)
|
||||
local h = ""
|
||||
for i = 1, #encoded do
|
||||
h = h .. string.format("%02x",string.byte(encoded,i))
|
||||
end
|
||||
return h
|
||||
} 0
|
||||
} {d3ffffff0000000000}
|
||||
|
||||
test {EVAL - cmsgpack can pack and unpack circular references?} {
|
||||
r eval {local a = {x=nil,y=5}
|
||||
local b = {x=a}
|
||||
a['x'] = b
|
||||
local encoded = cmsgpack.pack(a)
|
||||
local h = ""
|
||||
-- cmsgpack encodes to a depth of 16, but can't encode
|
||||
-- references, so the encoded object has a deep copy recusive
|
||||
-- depth of 16.
|
||||
for i = 1, #encoded do
|
||||
h = h .. string.format("%02x",string.byte(encoded,i))
|
||||
end
|
||||
-- when unpacked, re.x.x != re because the unpack creates
|
||||
-- individual tables down to a depth of 16.
|
||||
-- (that's why the encoded output is so large)
|
||||
local re = cmsgpack.unpack(encoded)
|
||||
assert(re)
|
||||
assert(re.x)
|
||||
assert(re.x.x.y == re.y)
|
||||
assert(re.x.x.x.x.y == re.y)
|
||||
assert(re.x.x.x.x.x.x.y == re.y)
|
||||
assert(re.x.x.x.x.x.x.x.x.x.x.y == re.y)
|
||||
-- maximum working depth:
|
||||
assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.y == re.y)
|
||||
-- now the last x would be b above and has no y
|
||||
assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x)
|
||||
-- so, the final x.x is at the depth limit and was assigned nil
|
||||
assert(re.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x == nil)
|
||||
return {h, re.x.x.x.x.x.x.x.x.y == re.y, re.y == 5}
|
||||
} 0
|
||||
} {82a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a17882a17905a17881a178c0 1 1}
|
||||
|
||||
test {EVAL - Numerical sanity check from bitop} {
|
||||
r eval {assert(0x7fffffff == 2147483647, "broken hex literals");
|
||||
assert(0xffffffff == -1 or 0xffffffff == 2^32-1,
|
||||
"broken hex literals");
|
||||
assert(tostring(-1) == "-1", "broken tostring()");
|
||||
assert(tostring(0xffffffff) == "-1" or
|
||||
tostring(0xffffffff) == "4294967295",
|
||||
"broken tostring()")
|
||||
} 0
|
||||
} {}
|
||||
|
||||
test {EVAL - Verify minimal bitop functionality} {
|
||||
r eval {assert(bit.tobit(1) == 1);
|
||||
assert(bit.band(1) == 1);
|
||||
assert(bit.bxor(1,2) == 3);
|
||||
assert(bit.bor(1,2,4,8,16,32,64,128) == 255)
|
||||
} 0
|
||||
} {}
|
||||
|
||||
test {EVAL - Able to parse trailing comments} {
|
||||
r eval {return 'hello' --trailing comment} 0
|
||||
} {hello}
|
||||
|
||||
test {SCRIPTING FLUSH - is able to clear the scripts cache?} {
|
||||
r set mykey myval
|
||||
set v [r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey]
|
||||
|
||||
@@ -246,24 +246,6 @@ start_server {
|
||||
r sort mylist by num get x:*->
|
||||
} {100}
|
||||
|
||||
test "SORT by nosort retains native order for lists" {
|
||||
r del testa
|
||||
r lpush testa 2 1 4 3 5
|
||||
r sort testa by nosort
|
||||
} {5 3 4 1 2}
|
||||
|
||||
test "SORT by nosort plus store retains native order for lists" {
|
||||
r del testa
|
||||
r lpush testa 2 1 4 3 5
|
||||
r sort testa by nosort store testb
|
||||
r lrange testb 0 -1
|
||||
} {5 3 4 1 2}
|
||||
|
||||
test "SORT by nosort with limit returns based on original list order" {
|
||||
r sort testa by nosort limit 0 3 store testb
|
||||
r lrange testb 0 -1
|
||||
} {5 3 4}
|
||||
|
||||
tags {"slow"} {
|
||||
set num 100
|
||||
set res [create_random_dataset $num lpush]
|
||||
|
||||
@@ -2,8 +2,8 @@ start_server {tags {"hash"}} {
|
||||
test {HSET/HLEN - Small hash creation} {
|
||||
array set smallhash {}
|
||||
for {set i 0} {$i < 8} {incr i} {
|
||||
set key __avoid_collisions__[randstring 0 8 alpha]
|
||||
set val __avoid_collisions__[randstring 0 8 alpha]
|
||||
set key [randstring 0 8 alpha]
|
||||
set val [randstring 0 8 alpha]
|
||||
if {[info exists smallhash($key)]} {
|
||||
incr i -1
|
||||
continue
|
||||
@@ -21,8 +21,8 @@ start_server {tags {"hash"}} {
|
||||
test {HSET/HLEN - Big hash creation} {
|
||||
array set bighash {}
|
||||
for {set i 0} {$i < 1024} {incr i} {
|
||||
set key __avoid_collisions__[randstring 0 8 alpha]
|
||||
set val __avoid_collisions__[randstring 0 8 alpha]
|
||||
set key [randstring 0 8 alpha]
|
||||
set val [randstring 0 8 alpha]
|
||||
if {[info exists bighash($key)]} {
|
||||
incr i -1
|
||||
continue
|
||||
@@ -33,7 +33,7 @@ start_server {tags {"hash"}} {
|
||||
list [r hlen bighash]
|
||||
} {1024}
|
||||
|
||||
test {Is the big hash encoded with an hash table?} {
|
||||
test {Is the big hash encoded with a ziplist?} {
|
||||
assert_encoding hashtable bighash
|
||||
}
|
||||
|
||||
|
||||
@@ -450,7 +450,6 @@ start_server {
|
||||
test "SMOVE non existing key" {
|
||||
setup_move
|
||||
assert_equal 0 [r smove myset1 myset2 foo]
|
||||
assert_equal 0 [r smove myset1 myset1 foo]
|
||||
assert_equal {1 a b} [lsort [r smembers myset1]]
|
||||
assert_equal {2 3 4} [lsort [r smembers myset2]]
|
||||
}
|
||||
|
||||
@@ -43,84 +43,6 @@ start_server {tags {"zset"}} {
|
||||
assert_error "*not*float*" {r zadd myzset nan abc}
|
||||
}
|
||||
|
||||
test "ZADD with options syntax error with incomplete pair" {
|
||||
r del ztmp
|
||||
catch {r zadd ztmp xx 10 x 20} err
|
||||
set err
|
||||
} {ERR*}
|
||||
|
||||
test "ZADD XX option without key - $encoding" {
|
||||
r del ztmp
|
||||
assert {[r zadd ztmp xx 10 x] == 0}
|
||||
assert {[r type ztmp] eq {none}}
|
||||
}
|
||||
|
||||
test "ZADD XX existing key - $encoding" {
|
||||
r del ztmp
|
||||
r zadd ztmp 10 x
|
||||
assert {[r zadd ztmp xx 20 y] == 0}
|
||||
assert {[r zcard ztmp] == 1}
|
||||
}
|
||||
|
||||
test "ZADD XX returns the number of elements actually added" {
|
||||
r del ztmp
|
||||
r zadd ztmp 10 x
|
||||
set retval [r zadd ztmp 10 x 20 y 30 z]
|
||||
assert {$retval == 2}
|
||||
}
|
||||
|
||||
test "ZADD XX updates existing elements score" {
|
||||
r del ztmp
|
||||
r zadd ztmp 10 x 20 y 30 z
|
||||
r zadd ztmp xx 5 foo 11 x 21 y 40 zap
|
||||
assert {[r zcard ztmp] == 3}
|
||||
assert {[r zscore ztmp x] == 11}
|
||||
assert {[r zscore ztmp y] == 21}
|
||||
}
|
||||
|
||||
test "ZADD XX and NX are not compatible" {
|
||||
r del ztmp
|
||||
catch {r zadd ztmp xx nx 10 x} err
|
||||
set err
|
||||
} {ERR*}
|
||||
|
||||
test "ZADD NX with non exisitng key" {
|
||||
r del ztmp
|
||||
r zadd ztmp nx 10 x 20 y 30 z
|
||||
assert {[r zcard ztmp] == 3}
|
||||
}
|
||||
|
||||
test "ZADD NX only add new elements without updating old ones" {
|
||||
r del ztmp
|
||||
r zadd ztmp 10 x 20 y 30 z
|
||||
assert {[r zadd ztmp nx 11 x 21 y 100 a 200 b] == 2}
|
||||
assert {[r zscore ztmp x] == 10}
|
||||
assert {[r zscore ztmp y] == 20}
|
||||
assert {[r zscore ztmp a] == 100}
|
||||
assert {[r zscore ztmp b] == 200}
|
||||
}
|
||||
|
||||
test "ZADD INCR works like ZINCRBY" {
|
||||
r del ztmp
|
||||
r zadd ztmp 10 x 20 y 30 z
|
||||
r zadd ztmp INCR 15 x
|
||||
assert {[r zscore ztmp x] == 25}
|
||||
}
|
||||
|
||||
test "ZADD INCR works with a single score-elemenet pair" {
|
||||
r del ztmp
|
||||
r zadd ztmp 10 x 20 y 30 z
|
||||
catch {r zadd ztmp INCR 15 x 10 y} err
|
||||
set err
|
||||
} {ERR*}
|
||||
|
||||
test "ZADD CH option changes return value to all changed elements" {
|
||||
r del ztmp
|
||||
r zadd ztmp 10 x 20 y 30 z
|
||||
assert {[r zadd ztmp 11 x 21 y 30 z] == 0}
|
||||
assert {[r zadd ztmp ch 12 x 22 y 30 z] == 2}
|
||||
}
|
||||
|
||||
test "ZINCRBY calls leading to NaN result in error" {
|
||||
r zincrby myzset +inf abc
|
||||
assert_error "*NaN*" {r zincrby myzset -inf abc}
|
||||
@@ -155,8 +77,6 @@ start_server {tags {"zset"}} {
|
||||
}
|
||||
|
||||
test "ZCARD basics - $encoding" {
|
||||
r del ztmp
|
||||
r zadd ztmp 10 a 20 b 30 c
|
||||
assert_equal 3 [r zcard ztmp]
|
||||
assert_equal 0 [r zcard zdoesntexist]
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
# This simple script is used in order to estimate the average PFAIL->FAIL
|
||||
# state switch after a failure.
|
||||
|
||||
set ::sleep_time 10 ; # How much to sleep to trigger PFAIL.
|
||||
set ::fail_port 30016 ; # Node to put in sleep.
|
||||
set ::other_port 30001 ; # Node to use to monitor the flag switch.
|
||||
|
||||
proc avg vector {
|
||||
set sum 0.0
|
||||
foreach x $vector {
|
||||
set sum [expr {$sum+$x}]
|
||||
}
|
||||
expr {$sum/[llength $vector]}
|
||||
}
|
||||
|
||||
set samples {}
|
||||
while 1 {
|
||||
exec redis-cli -p $::fail_port debug sleep $::sleep_time > /dev/null &
|
||||
|
||||
# Wait for fail? to appear.
|
||||
while 1 {
|
||||
set output [exec redis-cli -p $::other_port cluster nodes]
|
||||
if {[string match {*fail\?*} $output]} break
|
||||
after 100
|
||||
}
|
||||
|
||||
puts "FAIL?"
|
||||
set start [clock milliseconds]
|
||||
|
||||
# Wait for fail? to disappear.
|
||||
while 1 {
|
||||
set output [exec redis-cli -p $::other_port cluster nodes]
|
||||
if {![string match {*fail\?*} $output]} break
|
||||
after 100
|
||||
}
|
||||
|
||||
puts "FAIL"
|
||||
set now [clock milliseconds]
|
||||
set elapsed [expr {$now-$start}]
|
||||
puts $elapsed
|
||||
lappend samples $elapsed
|
||||
|
||||
puts "AVG([llength $samples]): [avg $samples]"
|
||||
|
||||
# Wait for the instance to be available again.
|
||||
exec redis-cli -p $::fail_port ping
|
||||
|
||||
# Wait for the fail flag to be cleared.
|
||||
after 2000
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
config.sh
|
||||
*.rdb
|
||||
*.aof
|
||||
*.conf
|
||||
*.log
|
||||
@@ -1,27 +0,0 @@
|
||||
Create-custer is a small script used to easily start a big number of Redis
|
||||
instances configured to run in cluster mode. Its main goal is to allow manual
|
||||
testing in a condition which is not easy to replicate with the Redis cluster
|
||||
unit tests, for example when a lot of instances are needed in order to trigger
|
||||
a give bug.
|
||||
|
||||
The tool can also be used just to easily create a number of instances in a
|
||||
Redis Cluster in order to experiment a bit with the system.
|
||||
|
||||
USAGE
|
||||
---
|
||||
|
||||
To create a cluster, follow this steps:
|
||||
|
||||
1. Edit create-cluster and change the start / end port, depending on the
|
||||
number of instances you want to create.
|
||||
2. Use "./create-cluster start" in order to run the instances.
|
||||
3. Use "./create-cluster create" in order to execute redis-trib create, so that
|
||||
an actual Redis cluster will be created.
|
||||
4. Now you are ready to play with the cluster. AOF files and logs for each instances are created in the current directory.
|
||||
|
||||
In order to stop a cluster:
|
||||
|
||||
1. Use "./craete-cluster stop" to stop all the instances. After you stopped the instances you can use "./create-cluster start" to restart them if you change ideas.
|
||||
2. Use "./create-cluster clean" to remove all the AOF / log files to restat with a clean environment.
|
||||
|
||||
Use the command "./create-cluster help" to get the full list of features.
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Settings
|
||||
PORT=30000
|
||||
TIMEOUT=2000
|
||||
NODES=6
|
||||
REPLICAS=1
|
||||
|
||||
# You may want to put the above config parameters into config.sh in order to
|
||||
# override the defaults without modifying this script.
|
||||
|
||||
if [ -a config.sh ]
|
||||
then
|
||||
source "config.sh"
|
||||
fi
|
||||
|
||||
# Computed vars
|
||||
ENDPORT=$((PORT+NODES))
|
||||
|
||||
if [ "$1" == "start" ]
|
||||
then
|
||||
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||
PORT=$((PORT+1))
|
||||
echo "Starting $PORT"
|
||||
../../src/redis-server --port $PORT --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$1" == "create" ]
|
||||
then
|
||||
HOSTS=""
|
||||
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||
PORT=$((PORT+1))
|
||||
HOSTS="$HOSTS 127.0.0.1:$PORT"
|
||||
done
|
||||
../../src/redis-trib.rb create --replicas $REPLICAS $HOSTS
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$1" == "stop" ]
|
||||
then
|
||||
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||
PORT=$((PORT+1))
|
||||
echo "Stopping $PORT"
|
||||
../../src/redis-cli -p $PORT shutdown nosave
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$1" == "watch" ]
|
||||
then
|
||||
PORT=$((PORT+1))
|
||||
while [ 1 ]; do
|
||||
clear
|
||||
date
|
||||
../../src/redis-cli -p $PORT cluster nodes | head -30
|
||||
sleep 1
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$1" == "tail" ]
|
||||
then
|
||||
INSTANCE=$2
|
||||
PORT=$((PORT+INSTANCE))
|
||||
tail -f ${PORT}.log
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$1" == "call" ]
|
||||
then
|
||||
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||
PORT=$((PORT+1))
|
||||
../../src/redis-cli -p $PORT $2 $3 $4 $5 $6 $7 $8 $9
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$1" == "clean" ]
|
||||
then
|
||||
rm -rf *.log
|
||||
rm -rf appendonly*.aof
|
||||
rm -rf dump*.rdb
|
||||
rm -rf nodes*.conf
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Usage: $0 [start|create|stop|watch|tail|clean]"
|
||||
echo "start -- Launch Redis Cluster instances."
|
||||
echo "create -- Create a cluster using redis-trib create."
|
||||
echo "stop -- Stop Redis Cluster instances."
|
||||
echo "watch -- Show CLUSTER NODES output (first 30 lines) of first node."
|
||||
echo "tail <id> -- Run tail -f of instance at base port + ID."
|
||||
echo "clean -- Remove all instances data, logs, configs."
|
||||
@@ -12,9 +12,7 @@ GROUPS = [
|
||||
"connection",
|
||||
"server",
|
||||
"scripting",
|
||||
"hyperloglog",
|
||||
"cluster",
|
||||
"geo"
|
||||
"hyperloglog"
|
||||
].freeze
|
||||
|
||||
GROUPS_BY_NAME = Hash[*
|
||||
|
||||
@@ -26,12 +26,11 @@ case "$1" in
|
||||
fi
|
||||
;;
|
||||
status)
|
||||
PID=$(cat $PIDFILE)
|
||||
if [ ! -x /proc/${PID} ]
|
||||
if [ ! -f $PIDFILE ]
|
||||
then
|
||||
echo 'Redis is not running'
|
||||
else
|
||||
echo "Redis is running ($PID)"
|
||||
echo "Redis is running ($(<$PIDFILE))"
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
if [ $# != "1" ]
|
||||
then
|
||||
echo "Usage: ${0} <git-ref>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG=$1
|
||||
TARNAME="redis-${TAG}.tar.gz"
|
||||
DOWNLOADURL="http://download.redis.io/releases/${TARNAME}"
|
||||
|
||||
ssh antirez@metal "export TERM=xterm;
|
||||
cd /tmp;
|
||||
rm -rf test_release_tmp_dir;
|
||||
cd test_release_tmp_dir;
|
||||
wget $DOWNLOADURL;
|
||||
tar xvzf $TARNAME;
|
||||
cd redis-${TAG};
|
||||
make;
|
||||
./runtest;
|
||||
./runtest-sentinel;
|
||||
if [ -x runtest-cluster ]; then
|
||||
./runtest-cluster;
|
||||
fi"
|
||||
@@ -8,6 +8,7 @@ fi
|
||||
TAG=$1
|
||||
TARNAME="redis-${TAG}.tar"
|
||||
echo "Generating /tmp/${TARNAME}"
|
||||
cd ~/hack/redis
|
||||
git archive $TAG --prefix redis-${TAG}/ > /tmp/$TARNAME || exit 1
|
||||
echo "Gizipping the archive"
|
||||
rm -f /tmp/$TARNAME.gz
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
echo "Uploading..."
|
||||
scp /tmp/redis-${1}.tar.gz antirez@antirez.com:/var/virtual/download.redis.io/httpdocs/releases/
|
||||
echo "Updating web site... (press any key if it is a stable release, or Ctrl+C)"
|
||||
read x
|
||||
ssh antirez@antirez.com "cd /var/virtual/download.redis.io/httpdocs; ./update.sh ${1}"
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
SHA=$(curl -s http://download.redis.io/releases/redis-${1}.tar.gz | shasum | cut -f 1 -d' ')
|
||||
ENTRY="hash redis-${1}.tar.gz sha1 $SHA http://download.redis.io/releases/redis-${1}.tar.gz"
|
||||
echo $ENTRY >> ~/hack/redis-hashes/README
|
||||
vi ~/hack/redis-hashes/README
|
||||
echo "Press any key to commit, Ctrl-C to abort)."
|
||||
read yes
|
||||
(cd ~/hack/redis-hashes; git commit -a -m "${1} hash."; git push)
|
||||
@@ -1,15 +1,9 @@
|
||||
# This script is from http://poormansprofiler.org/
|
||||
#
|
||||
# NOTE: Instead of using this script, you should use the Redis
|
||||
# Software Watchdog, which provides a similar functionality but in
|
||||
# a more reliable / easy to use way.
|
||||
#
|
||||
# Check http://redis.io/topics/latency for more information.
|
||||
|
||||
#!/bin/bash
|
||||
nsamples=1
|
||||
sleeptime=0
|
||||
pid=$(ps auxww | grep '[r]edis-server' | awk '{print $2}')
|
||||
pid=$(pidof redis-server)
|
||||
|
||||
for x in $(seq 1 $nsamples)
|
||||
do
|
||||
|
||||
Reference in New Issue
Block a user