Compare commits
801
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81428a24a8 | ||
|
|
eb706b4202 | ||
|
|
9e67df2a39 | ||
|
|
bcb4d09123 | ||
|
|
29391002f6 | ||
|
|
427794d845 | ||
|
|
6142ddc6eb | ||
|
|
f049cfdb0d | ||
|
|
25e1cb3f04 | ||
|
|
92c146dfd3 | ||
|
|
e0bb454a16 | ||
|
|
0f64080dcb | ||
|
|
4c7ee0d584 | ||
|
|
5c4fcaf3fe | ||
|
|
b96af595a5 | ||
|
|
965abcf10a | ||
|
|
1e12784259 | ||
|
|
5e04189887 | ||
|
|
5254c2d3c3 | ||
|
|
4160bf0448 | ||
|
|
d308cadc8a | ||
|
|
69c5b27273 | ||
|
|
083acbebc8 | ||
|
|
f108c687ad | ||
|
|
a12192f5ff | ||
|
|
aae0a1f9cc | ||
|
|
f6edd0cb93 | ||
|
|
7cd2a4e196 | ||
|
|
ddc7b85c5f | ||
|
|
6a8e108e2d | ||
|
|
1884bff12d | ||
|
|
db3df44184 | ||
|
|
6d21027a23 | ||
|
|
7d59e0a8c3 | ||
|
|
a3b07b1718 | ||
|
|
cd91beea1c | ||
|
|
710c05ac2a | ||
|
|
c232235734 | ||
|
|
fa9d62d34f | ||
|
|
03ce189628 | ||
|
|
5fd756bf13 | ||
|
|
87521f4455 | ||
|
|
c7462ca9ff | ||
|
|
d28c51d166 | ||
|
|
55c4a365d7 | ||
|
|
cf89a19f16 | ||
|
|
8d5ad19d15 | ||
|
|
0425c60381 | ||
|
|
bb3284563c | ||
|
|
ae5fd11563 | ||
|
|
a3018a215f | ||
|
|
51b4a4724b | ||
|
|
0b93139048 | ||
|
|
3d9031eda4 | ||
|
|
9fc47ddf0b | ||
|
|
575e247a0e | ||
|
|
2f66550729 | ||
|
|
fc03d08ee0 | ||
|
|
b18c68aa7f | ||
|
|
f193b3caa8 | ||
|
|
73134f6a0b | ||
|
|
5e46e8a58a | ||
|
|
7f4ac3d19c | ||
|
|
821a986643 | ||
|
|
4b5a0f0376 | ||
|
|
8366907bed | ||
|
|
a017b7ec0e | ||
|
|
8fa8b251a9 | ||
|
|
a401a84eb2 | ||
|
|
ffd6637e90 | ||
|
|
fdf9d45509 | ||
|
|
357a40c4fc | ||
|
|
0dc6a5d497 | ||
|
|
28a250d9e4 | ||
|
|
39b49bcaaf | ||
|
|
d8a8dca7fd | ||
|
|
23ba5c1092 | ||
|
|
910e72d1c1 | ||
|
|
c043a4e6f4 | ||
|
|
5d32abbb9e | ||
|
|
382a943414 | ||
|
|
a391c36324 | ||
|
|
c3297a7292 | ||
|
|
4082c38a60 | ||
|
|
20700fe566 | ||
|
|
5080f2d699 | ||
|
|
fb3af75f74 | ||
|
|
575eeb1a1c | ||
|
|
8c423c0bd6 | ||
|
|
c54de703f2 | ||
|
|
164b6bbab5 | ||
|
|
d614f1c37e | ||
|
|
abc65e8987 | ||
|
|
eb138f1511 | ||
|
|
cb9a5a7821 | ||
|
|
7f7ddbba3c | ||
|
|
b43431ac25 | ||
|
|
4dee18cb66 | ||
|
|
f9e942d4ae | ||
|
|
b44c37482c | ||
|
|
87b6013adb | ||
|
|
5a0516b5b9 | ||
|
|
05dbc82005 | ||
|
|
58d2bb951a | ||
|
|
3ab49895b4 | ||
|
|
0eb0b55ff0 | ||
|
|
9d5e2ed392 | ||
|
|
e0a5246f06 | ||
|
|
6b953a2681 | ||
|
|
d6e1347869 | ||
|
|
4e8ccbe7ea | ||
|
|
1029276c0d | ||
|
|
611283f743 | ||
|
|
3eca0752a6 | ||
|
|
b91434cab1 | ||
|
|
b849886a0d | ||
|
|
a0cd75cd1b | ||
|
|
794fc4c9a8 | ||
|
|
0610cb6296 | ||
|
|
23e304e313 | ||
|
|
2bc1527a95 | ||
|
|
f7bd816bbb | ||
|
|
8d18692018 | ||
|
|
9f9c44feef | ||
|
|
8af99d0c09 | ||
|
|
5a23ef88bf | ||
|
|
cc799d253f | ||
|
|
99c93f34a7 | ||
|
|
22d00d80ce | ||
|
|
827d07f005 | ||
|
|
7af420e78d | ||
|
|
9e7f39d29d | ||
|
|
fe0d71d52f | ||
|
|
c806dd799b | ||
|
|
eff212ea95 | ||
|
|
fb53288110 | ||
|
|
3ff49afff1 | ||
|
|
1b25757f41 | ||
|
|
1d6eb701d8 | ||
|
|
1a93501f8b | ||
|
|
49c1b60bd8 | ||
|
|
b100fee900 | ||
|
|
066d7a29eb | ||
|
|
921ca063f7 | ||
|
|
26a1a08fc7 | ||
|
|
239494db64 | ||
|
|
42b36c5ce9 | ||
|
|
626b4f6907 | ||
|
|
6c60526db9 | ||
|
|
d5a35c3946 | ||
|
|
386804246f | ||
|
|
159875b5a3 | ||
|
|
f472bb1069 | ||
|
|
66f9393ee4 | ||
|
|
65090401b7 | ||
|
|
7f330b16f9 | ||
|
|
34460dd6ee | ||
|
|
221d2932b5 | ||
|
|
37260bc3be | ||
|
|
068d3c9737 | ||
|
|
9cd8333ed2 | ||
|
|
c3ad70901f | ||
|
|
9b7f8b1c9b | ||
|
|
eff68bd656 | ||
|
|
761fc16b4a | ||
|
|
9b0bcf25e1 | ||
|
|
f300680408 | ||
|
|
631538cfe0 | ||
|
|
3b4de6aa18 | ||
|
|
2f4240b9d9 | ||
|
|
94030fa4d7 | ||
|
|
2950824ab6 | ||
|
|
d544600aa5 | ||
|
|
2b278a3394 | ||
|
|
5fe4a23131 | ||
|
|
a7010ae208 | ||
|
|
230d141420 | ||
|
|
4f2555aa17 | ||
|
|
25c0f5ac63 | ||
|
|
2ecb5edf34 | ||
|
|
438a1a84e8 | ||
|
|
61fb441c8c | ||
|
|
e791e2dda1 | ||
|
|
93b1320fac | ||
|
|
4ed7582c7b | ||
|
|
e1b6c9dd18 | ||
|
|
b2e8eca70d | ||
|
|
792c531688 | ||
|
|
50b41b6ad3 | ||
|
|
535b295f96 | ||
|
|
4cd4910f26 | ||
|
|
8e219224b9 | ||
|
|
4a2a0d9e9d | ||
|
|
36c1a7cba3 | ||
|
|
6201eb0c55 | ||
|
|
3da7408359 | ||
|
|
d68f28a367 | ||
|
|
a664040eb7 | ||
|
|
509a6cc1e8 | ||
|
|
fe63046e2a | ||
|
|
c77081a45a | ||
|
|
4f56f035a7 | ||
|
|
0e5e8ca9e6 | ||
|
|
084a59c324 | ||
|
|
0ace1e6d04 | ||
|
|
4e54b85a19 | ||
|
|
8855b8161f | ||
|
|
b49c00a79c | ||
|
|
d8f8b0575f | ||
|
|
c95507881a | ||
|
|
7e6b4ea67b | ||
|
|
832b0c7cce | ||
|
|
7fda935ad3 | ||
|
|
cc0d339bd1 | ||
|
|
126462ee36 | ||
|
|
74354ceef5 | ||
|
|
27c30b0e84 | ||
|
|
954151f62b | ||
|
|
9454f7b3db | ||
|
|
e00cb78f67 | ||
|
|
47ab570441 | ||
|
|
d83c810265 | ||
|
|
46bd13b806 | ||
|
|
3689a0582b | ||
|
|
5ff00ac8c1 | ||
|
|
fc365a3a00 | ||
|
|
db9461e466 | ||
|
|
0aa5acc8f3 | ||
|
|
1024664247 | ||
|
|
acb933a747 | ||
|
|
552e5908bb | ||
|
|
395e11255c | ||
|
|
cfe5eaaed6 | ||
|
|
41a1fb8186 | ||
|
|
e5760987e9 | ||
|
|
07a89654da | ||
|
|
9795ad1bc2 | ||
|
|
78c44ca646 | ||
|
|
f916a589b5 | ||
|
|
2576864ba7 | ||
|
|
c414db2f98 | ||
|
|
097b3079b4 | ||
|
|
cac9a900cb | ||
|
|
f638f045ce | ||
|
|
3101d2bffb | ||
|
|
b1de179c01 | ||
|
|
cd4e0f43df | ||
|
|
866b3fc0ec | ||
|
|
29b54db320 | ||
|
|
2b3eba056c | ||
|
|
a1d9ec0d44 | ||
|
|
dffbbb5a78 | ||
|
|
a37d0f8b48 | ||
|
|
9feee428f2 | ||
|
|
55003f7a11 | ||
|
|
cc7f0434b5 | ||
|
|
6b5922dcbb | ||
|
|
585d1a60bf | ||
|
|
8ddc14523f | ||
|
|
edda00b902 | ||
|
|
05841a6386 | ||
|
|
8696874d75 | ||
|
|
5792a217f8 | ||
|
|
f25fdd6246 | ||
|
|
2385630d0d | ||
|
|
4f427bc298 | ||
|
|
1bcf67a75f | ||
|
|
88cd9ebc09 | ||
|
|
cd0fcf11e7 | ||
|
|
777020839a | ||
|
|
170e41464d | ||
|
|
5e3dcc522b | ||
|
|
e1fce55237 | ||
|
|
9e9abe29fe | ||
|
|
50a57c67b2 | ||
|
|
bd128f7969 | ||
|
|
ca23b2a6b7 | ||
|
|
cfe21852e7 | ||
|
|
2723412b7b | ||
|
|
ace1acc532 | ||
|
|
c83a739bd8 | ||
|
|
414df143f5 | ||
|
|
2860cf4139 | ||
|
|
ba74711e06 | ||
|
|
2ac7b5a8b4 | ||
|
|
78dd2b313e | ||
|
|
c5dd686ecb | ||
|
|
719c6c2b96 | ||
|
|
96abf65900 | ||
|
|
5fbb36f9e7 | ||
|
|
1dbd8e94a7 | ||
|
|
51010007bc | ||
|
|
fad758b322 | ||
|
|
7d1e158084 | ||
|
|
45102a6f63 | ||
|
|
128c642d05 | ||
|
|
cdc0e9bfb5 | ||
|
|
d5e01519e5 | ||
|
|
c908774b9e | ||
|
|
6502947a85 | ||
|
|
79fa67cdb8 | ||
|
|
233729fe7f | ||
|
|
69b4f00d28 | ||
|
|
6b1c6334be | ||
|
|
e5a22064cc | ||
|
|
1efacfe53d | ||
|
|
3fd43062c8 | ||
|
|
d59ad97d76 | ||
|
|
69583be181 | ||
|
|
8899f91a7f | ||
|
|
d3ef6c94bf | ||
|
|
d8c7db1bdb | ||
|
|
764b000c3e | ||
|
|
145473acc5 | ||
|
|
6ec5f1f780 | ||
|
|
9802ec3c83 | ||
|
|
8aaf5075c5 | ||
|
|
7885e1264e | ||
|
|
e22d75734e | ||
|
|
ca14440aa1 | ||
|
|
d5ba544e67 | ||
|
|
e4d65e35e6 | ||
|
|
a330b6ca97 | ||
|
|
9826038f0b | ||
|
|
87301be151 | ||
|
|
af8d1b4bda | ||
|
|
7e79b3f51a | ||
|
|
051a43e03a | ||
|
|
29049507ec | ||
|
|
30152554ea | ||
|
|
72b8574cca | ||
|
|
acb1d8debf | ||
|
|
4433f5a7f2 | ||
|
|
b3bf7584b0 | ||
|
|
a5bb0a0774 | ||
|
|
2601e3e461 | ||
|
|
59ad6ac5fe | ||
|
|
491d57abaa | ||
|
|
b0146aafeb | ||
|
|
92cfab44b2 | ||
|
|
e467cf5db3 | ||
|
|
bc8675612f | ||
|
|
839767ad0b | ||
|
|
53c082ec39 | ||
|
|
f704360462 | ||
|
|
cf76af6b9f | ||
|
|
5b0f4a83ac | ||
|
|
237ab727b9 | ||
|
|
f08586347d | ||
|
|
2c925b0c30 | ||
|
|
10007cb78c | ||
|
|
e6416ca71c | ||
|
|
6274a6789d | ||
|
|
5a685f35a9 | ||
|
|
6741bb981c | ||
|
|
eb7d67a3ab | ||
|
|
36a3b75355 | ||
|
|
5e8b7e4f35 | ||
|
|
e6d499a446 | ||
|
|
0d22121c27 | ||
|
|
792a94153a | ||
|
|
f3fd58eb4a | ||
|
|
da95d22ad2 | ||
|
|
1019c72930 | ||
|
|
bf58f8b513 | ||
|
|
1c477f62bc | ||
|
|
622c69e93d | ||
|
|
b45e16e7ee | ||
|
|
161ff77572 | ||
|
|
219ab66cc8 | ||
|
|
42357668e8 | ||
|
|
a7722dc31b | ||
|
|
4c0e8923a6 | ||
|
|
206cd219b6 | ||
|
|
1a30e7ded1 | ||
|
|
e8614a1a77 | ||
|
|
32b10004e2 | ||
|
|
f699b5e801 | ||
|
|
68bc02c36c | ||
|
|
05ba119fbb | ||
|
|
5870e22423 | ||
|
|
25e12d10be | ||
|
|
9e11d07909 | ||
|
|
02bb515a09 | ||
|
|
bbbbfb1442 | ||
|
|
5f506b6d2b | ||
|
|
abdd1414a8 | ||
|
|
5127e39980 | ||
|
|
8d7021892e | ||
|
|
e0d94a7b01 | ||
|
|
101b3a6e42 | ||
|
|
127c15e2b2 | ||
|
|
e1619772db | ||
|
|
c6bf20c2a7 | ||
|
|
60a9418ed9 | ||
|
|
0f15eb183b | ||
|
|
9d2dc0249c | ||
|
|
5e362b84ab | ||
|
|
d956d809ac | ||
|
|
9b786b124d | ||
|
|
1dfcd75ae3 | ||
|
|
53b1ee34dd | ||
|
|
fae53dea33 | ||
|
|
d01d4ddcbb | ||
|
|
9b343678d2 | ||
|
|
8febcffdc5 | ||
|
|
8380655e85 | ||
|
|
27937c2821 | ||
|
|
ad41a7c404 | ||
|
|
9e718a1f57 | ||
|
|
840435ad0b | ||
|
|
f7bc1fcea9 | ||
|
|
3a1ee895fa | ||
|
|
32bba43ac7 | ||
|
|
5888290d26 | ||
|
|
14b04c062e | ||
|
|
d74a5a0880 | ||
|
|
b55f742e46 | ||
|
|
e3436dd9b8 | ||
|
|
efbf5a125e | ||
|
|
3c8a75583d | ||
|
|
70674ac677 | ||
|
|
abd95ffdfd | ||
|
|
9a6354316b | ||
|
|
615f6923d5 | ||
|
|
e1e41f691f | ||
|
|
a772747ffc | ||
|
|
25b06cd3ce | ||
|
|
24f4c01832 | ||
|
|
1aef29e079 | ||
|
|
90c7d8cfa1 | ||
|
|
352172a7ef | ||
|
|
288028876f | ||
|
|
c147cd8487 | ||
|
|
d6410ed19a | ||
|
|
97d43d0638 | ||
|
|
7b72dbf599 | ||
|
|
f2858dc8dc | ||
|
|
66e2bdf210 | ||
|
|
67e8061b17 | ||
|
|
7ef1239bd1 | ||
|
|
bbf0736c4e | ||
|
|
ce269ad3c5 | ||
|
|
badf0f008b | ||
|
|
391fc9b633 | ||
|
|
ec5a0c548b | ||
|
|
7d4c2a98b6 | ||
|
|
491881e13b | ||
|
|
2d90619f88 | ||
|
|
3cd36a4dd9 | ||
|
|
c50693d40f | ||
|
|
81772ce0e3 | ||
|
|
f24a0bc1c5 | ||
|
|
ff01ce55d8 | ||
|
|
f1742ac9df | ||
|
|
599e52d93e | ||
|
|
dea938440c | ||
|
|
4ff365b34a | ||
|
|
8b81383401 | ||
|
|
9c385ada22 | ||
|
|
b5686c1a4d | ||
|
|
06e76bc3e2 | ||
|
|
3da87b70dd | ||
|
|
d8158771b5 | ||
|
|
c83a917286 | ||
|
|
0422321617 | ||
|
|
7576a27d58 | ||
|
|
2d3f99a1b7 | ||
|
|
0b7f5e258a | ||
|
|
6a378dc312 | ||
|
|
0195afae39 | ||
|
|
9bb4ef871e | ||
|
|
181300d4a7 | ||
|
|
d2aa91ce1f | ||
|
|
562e72943b | ||
|
|
c58db75a69 | ||
|
|
5f5a9f0d14 | ||
|
|
f04ec18a36 | ||
|
|
87f1e27b27 | ||
|
|
b4b4192f19 | ||
|
|
5b2bbef9fc | ||
|
|
4dab282145 | ||
|
|
4b03be6d2f | ||
|
|
b2eb202281 | ||
|
|
7ecb880168 | ||
|
|
07a9e44b86 | ||
|
|
c87a37661e | ||
|
|
8c0f86842e | ||
|
|
d4a9836f1a | ||
|
|
ff25d1896d | ||
|
|
af453642e1 | ||
|
|
c3846becae | ||
|
|
1b732c09d0 | ||
|
|
eca9fbdb50 | ||
|
|
b8a48ad5fe | ||
|
|
3632026210 | ||
|
|
c98d4f5675 | ||
|
|
92c5ab4029 | ||
|
|
5bd3b9d93f | ||
|
|
8a7ccc58a1 | ||
|
|
e07dd8b3e9 | ||
|
|
0ec7672a5d | ||
|
|
7923d71fb7 | ||
|
|
f3c1aac7e6 | ||
|
|
560ec60ed4 | ||
|
|
fe0d371995 | ||
|
|
bd3a51615c | ||
|
|
8acb3a8843 | ||
|
|
669aa2a210 | ||
|
|
640d30c527 | ||
|
|
10c35b584e | ||
|
|
0ae65bec60 | ||
|
|
acf73a0592 | ||
|
|
e039791e39 | ||
|
|
231c8c2ecf | ||
|
|
9e579d91cc | ||
|
|
a8f9a989a7 | ||
|
|
02cdb353dd | ||
|
|
6064371085 | ||
|
|
8a09e12906 | ||
|
|
e9b014cfac | ||
|
|
f8c73e38b5 | ||
|
|
6739ef4447 | ||
|
|
0ed2c60118 | ||
|
|
620906693e | ||
|
|
8a75ec0fca | ||
|
|
ace628c792 | ||
|
|
90b6337c15 | ||
|
|
da18dd34a2 | ||
|
|
a9900ad38e | ||
|
|
4fdcd213f0 | ||
|
|
e327c8edb9 | ||
|
|
a2f929ab10 | ||
|
|
7ea331d601 | ||
|
|
110f0464e0 | ||
|
|
3ef0876b95 | ||
|
|
bb7fea0d5c | ||
|
|
f5c6ebbfe3 | ||
|
|
bc1a3b96e6 | ||
|
|
3ab832193c | ||
|
|
0f706adc5c | ||
|
|
bea45da07a | ||
|
|
84ee8268c7 | ||
|
|
6df9001d6c | ||
|
|
5a526c22cc | ||
|
|
a076743079 | ||
|
|
7fcfbea0f5 | ||
|
|
2c42b645bc | ||
|
|
0014966c1e | ||
|
|
14f2582da2 | ||
|
|
daca1edb6e | ||
|
|
bbf1af2da3 | ||
|
|
6c0abc4a0e | ||
|
|
e10c5444e7 | ||
|
|
6fbaeddf3f | ||
|
|
9ec22d9223 | ||
|
|
4b8f4b90b9 | ||
|
|
775cc30a98 | ||
|
|
8a416ca46e | ||
|
|
2540736366 | ||
|
|
a27befc495 | ||
|
|
3b9a97984a | ||
|
|
707352439c | ||
|
|
b4c4be785c | ||
|
|
dab5c09acc | ||
|
|
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 | ||
|
|
3fecb96122 | ||
|
|
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 |
@@ -4,6 +4,7 @@
|
||||
dump.rdb
|
||||
redis-benchmark
|
||||
redis-check-aof
|
||||
redis-check-rdb
|
||||
redis-check-dump
|
||||
redis-cli
|
||||
redis-sentinel
|
||||
@@ -19,6 +20,7 @@ src/transfer.sh
|
||||
src/configs
|
||||
redis.ds
|
||||
src/redis.conf
|
||||
src/nodes.conf
|
||||
deps/lua/src/lua
|
||||
deps/lua/src/luac
|
||||
deps/lua/src/liblua.a
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ 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.
|
||||
|
||||
Usually "unstable" is stable enough for you to use it in development enviromnets
|
||||
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:
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
Plese check https://github.com/antirez/redis/issues
|
||||
Please check https://github.com/antirez/redis/issues
|
||||
|
||||
+3
-1
@@ -20,7 +20,7 @@ each source file that you contribute.
|
||||
|
||||
# How to provide a patch for a new feature
|
||||
|
||||
1. Drop a message to the Redis Google Group with a proposal of semantics/API.
|
||||
1. If it is a major feature or a semantical change, write an RCP (Redis Change Proposal). Check the documentation here: https://github.com/redis/redis-rcp
|
||||
|
||||
2. If in step 1 you get an acknowledge from the project leaders, use the
|
||||
following procedure to submit a patch:
|
||||
@@ -31,4 +31,6 @@ 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-2014, Salvatore Sanfilippo
|
||||
Copyright (c) 2006-2015, 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:
|
||||
|
||||
+56
-19
@@ -1,18 +1,35 @@
|
||||
Where to find complete Redis documentation?
|
||||
-------------------------------------------
|
||||
This README is just a fast *quick start* document. You can find more detailed documentation at http://redis.io.
|
||||
|
||||
This README is just a fast "quick start" document. You can find more detailed
|
||||
documentation at http://redis.io
|
||||
What is Redis?
|
||||
--------------
|
||||
|
||||
Redis is often referred as a *data structures* server. What this means is that Redis provides access to mutable data structures via a set of commands, which are sent using a *server-client* model with TCP sockets and a simple protocol. So different processes can query and modify the same data structures in a shared way.
|
||||
|
||||
Data structures implemented into Redis have a few special properties:
|
||||
|
||||
* Redis cares to store them on disk, even if they are always served and modified into the server memory. This means that Redis is fast, but that is also non-volatile.
|
||||
* Implementation of data structures stress on memory efficiency, so data structures inside Redis will likely use less memory compared to the same data structure modeled using an high level programming language.
|
||||
* Redis offers a number of features that are natural to find in a database, like replication, tunable levels of durability, cluster, high availability.
|
||||
|
||||
Another good example is to think of Redis as a more complex version of memcached, where the operations are not just SETs and GETs, but operations to work with complex data types like Lists, Sets, ordered data structures, and so forth.
|
||||
|
||||
If you want to know more, this is a list of selected starting points:
|
||||
|
||||
* Introduction to Redis data types. http://redis.io/topics/data-types-intro
|
||||
* Try Redis directly inside your browser. http://try.redis.io
|
||||
* The full list of Redis commands. http://redis.io/commands
|
||||
* There is much more inside the Redis official documentation. http://redis.io/documentation
|
||||
|
||||
Building Redis
|
||||
--------------
|
||||
|
||||
Redis can be compiled and used on Linux, OSX, OpenBSD, NetBSD, FreeBSD.
|
||||
We support big endian and little endian architectures.
|
||||
We support big endian and little endian architectures, and both 32 bit
|
||||
and 64 bit systems.
|
||||
|
||||
It may compile on Solaris derived systems (for instance SmartOS) but our
|
||||
support for this platform is "best effort" and Redis is not guaranteed to
|
||||
work as well as in Linux, OSX, and *BSD there.
|
||||
support for this platform is *best effort* and Redis is not guaranteed to
|
||||
work as well as in Linux, OSX, and \*BSD there.
|
||||
|
||||
It is as simple as:
|
||||
|
||||
@@ -26,20 +43,39 @@ 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
|
||||
---------
|
||||
|
||||
If after building Redis with a 32 bit target you need to rebuild it
|
||||
with a 64 bit target, or the other way around, you need to perform a
|
||||
"make distclean" in the root directory of the Redis distribution.
|
||||
`make distclean` in the root directory of the Redis distribution.
|
||||
|
||||
In case of build errors when trying to build a 32 bit binary of Redis, try
|
||||
the following steps:
|
||||
|
||||
* Install the packages libc6-dev-i386 (also try g++-multilib).
|
||||
* Try using the following command line instead of "make 32bit":
|
||||
|
||||
make CFLAGS="-m32 -march=native" LDFLAGS="-m32"
|
||||
* Try using the following command line instead of `make 32bit`:
|
||||
`make CFLAGS="-m32 -march=native" LDFLAGS="-m32"`
|
||||
|
||||
Allocator
|
||||
---------
|
||||
@@ -107,11 +143,9 @@ then in another terminal try the following:
|
||||
(integer) 1
|
||||
redis> incr mycounter
|
||||
(integer) 2
|
||||
redis>
|
||||
redis>
|
||||
|
||||
You can find the list of all the available commands here:
|
||||
|
||||
http://redis.io/commands
|
||||
You can find the list of all the available commands at http://redis.io/commands.
|
||||
|
||||
Installing Redis
|
||||
-----------------
|
||||
@@ -120,7 +154,7 @@ In order to install Redis binaries into /usr/local/bin just use:
|
||||
|
||||
% make install
|
||||
|
||||
You can use "make PREFIX=/some/other/directory install" if you wish to use a
|
||||
You can use `make PREFIX=/some/other/directory install` if you wish to use a
|
||||
different destination.
|
||||
|
||||
Make install will just install binaries in your system, but will not configure
|
||||
@@ -137,7 +171,7 @@ to run Redis properly as a background daemon that will start again on
|
||||
system reboots.
|
||||
|
||||
You'll be able to stop and start Redis using the script named
|
||||
/etc/init.d/redis_<portnumber>, for instance /etc/init.d/redis_6379.
|
||||
`/etc/init.d/redis_<portnumber>`, for instance `/etc/init.d/redis_6379`.
|
||||
|
||||
Code contributions
|
||||
---
|
||||
@@ -145,10 +179,13 @@ Code contributions
|
||||
Note: by contributing code to the Redis project in any form, including sending
|
||||
a pull request via Github, a code fragment or patch via private email or
|
||||
public discussion groups, you agree to release your code under the terms
|
||||
of the BSD license that you can find in the COPYING file included in the Redis
|
||||
of the BSD license that you can find in the [COPYING][1] file included in the Redis
|
||||
source distribution.
|
||||
|
||||
Please see the CONTRIBUTING file in this source distribution for more
|
||||
Please see the [CONTRIBUTING][2] file in this source distribution for more
|
||||
information.
|
||||
|
||||
Enjoy!
|
||||
|
||||
[1]: https://github.com/antirez/redis/blob/unstable/COPYING
|
||||
[2]: https://github.com/antirez/redis/blob/unstable/CONTRIBUTING
|
||||
Vendored
+14
-2
@@ -36,6 +36,7 @@ distclean:
|
||||
-(cd hiredis && $(MAKE) clean) > /dev/null || true
|
||||
-(cd linenoise && $(MAKE) clean) > /dev/null || true
|
||||
-(cd lua && $(MAKE) clean) > /dev/null || true
|
||||
-(cd geohash-int && $(MAKE) clean) > /dev/null || true
|
||||
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
|
||||
-(rm -f .make-*)
|
||||
|
||||
@@ -58,12 +59,17 @@ ifeq ($(uname_S),SunOS)
|
||||
LUA_CFLAGS= -D__C99FEATURES__=1
|
||||
endif
|
||||
|
||||
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI $(CFLAGS)
|
||||
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL -DREDIS_STATIC='' $(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
|
||||
# to fit redis into cross-compilation environments, which typically set AR.
|
||||
AR=ar
|
||||
ARFLAGS=rcu
|
||||
|
||||
lua: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
cd lua/src && $(MAKE) all CFLAGS="$(LUA_CFLAGS)" MYLDFLAGS="$(LUA_LDFLAGS)"
|
||||
cd lua/src && $(MAKE) all CFLAGS="$(LUA_CFLAGS)" MYLDFLAGS="$(LUA_LDFLAGS)" AR="$(AR) $(ARFLAGS)"
|
||||
|
||||
.PHONY: lua
|
||||
|
||||
@@ -76,3 +82,9 @@ jemalloc: .make-prerequisites
|
||||
cd jemalloc && $(MAKE) CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" lib/libjemalloc.a
|
||||
|
||||
.PHONY: jemalloc
|
||||
|
||||
geohash-int: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
cd geohash-int && $(MAKE)
|
||||
|
||||
.PHONY: geohash-int
|
||||
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
STD=
|
||||
WARN= -Wall
|
||||
OPT= -O2
|
||||
|
||||
R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS)
|
||||
R_LDFLAGS= $(LDFLAGS)
|
||||
DEBUG= -g
|
||||
|
||||
R_CC=$(CC) $(R_CFLAGS)
|
||||
R_LD=$(CC) $(R_LDFLAGS)
|
||||
|
||||
all: geohash.o geohash_helper.o
|
||||
|
||||
.PHONY: all
|
||||
|
||||
geohash.o: geohash.h geohash.c
|
||||
geohash_helper.o: geohash.h geohash_helper.h geohash_helper.c
|
||||
|
||||
.c.o:
|
||||
$(R_CC) -c $<
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
Vendored
+295
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2014, yinqiwen <yinqiwen@gmail.com>
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
||||
* Copyright (c) 2015, Salvatore Sanfilippo <antirez@gmail.com>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include "geohash.h"
|
||||
|
||||
/**
|
||||
* Hashing works like this:
|
||||
* Divide the world into 4 buckets. Label each one as such:
|
||||
* -----------------
|
||||
* | | |
|
||||
* | | |
|
||||
* | 0,1 | 1,1 |
|
||||
* -----------------
|
||||
* | | |
|
||||
* | | |
|
||||
* | 0,0 | 1,0 |
|
||||
* -----------------
|
||||
*/
|
||||
|
||||
/* Interleave lower bits of x and y, so the bits of x
|
||||
* are in the even positions and bits from y in the odd;
|
||||
* x and y must initially be less than 2**32 (65536).
|
||||
* From: https://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN
|
||||
*/
|
||||
static inline uint64_t interleave64(uint32_t xlo, uint32_t ylo) {
|
||||
static const uint64_t B[] = {0x5555555555555555ULL, 0x3333333333333333ULL,
|
||||
0x0F0F0F0F0F0F0F0FULL, 0x00FF00FF00FF00FFULL,
|
||||
0x0000FFFF0000FFFFULL};
|
||||
static const unsigned int S[] = {1, 2, 4, 8, 16};
|
||||
|
||||
uint64_t x = xlo;
|
||||
uint64_t y = ylo;
|
||||
|
||||
x = (x | (x << S[4])) & B[4];
|
||||
y = (y | (y << S[4])) & B[4];
|
||||
|
||||
x = (x | (x << S[3])) & B[3];
|
||||
y = (y | (y << S[3])) & B[3];
|
||||
|
||||
x = (x | (x << S[2])) & B[2];
|
||||
y = (y | (y << S[2])) & B[2];
|
||||
|
||||
x = (x | (x << S[1])) & B[1];
|
||||
y = (y | (y << S[1])) & B[1];
|
||||
|
||||
x = (x | (x << S[0])) & B[0];
|
||||
y = (y | (y << S[0])) & B[0];
|
||||
|
||||
return x | (y << 1);
|
||||
}
|
||||
|
||||
/* reverse the interleave process
|
||||
* derived from http://stackoverflow.com/questions/4909263
|
||||
*/
|
||||
static inline uint64_t deinterleave64(uint64_t interleaved) {
|
||||
static const uint64_t B[] = {0x5555555555555555ULL, 0x3333333333333333ULL,
|
||||
0x0F0F0F0F0F0F0F0FULL, 0x00FF00FF00FF00FFULL,
|
||||
0x0000FFFF0000FFFFULL, 0x00000000FFFFFFFFULL};
|
||||
static const unsigned int S[] = {0, 1, 2, 4, 8, 16};
|
||||
|
||||
uint64_t x = interleaved;
|
||||
uint64_t y = interleaved >> 1;
|
||||
|
||||
x = (x | (x >> S[0])) & B[0];
|
||||
y = (y | (y >> S[0])) & B[0];
|
||||
|
||||
x = (x | (x >> S[1])) & B[1];
|
||||
y = (y | (y >> S[1])) & B[1];
|
||||
|
||||
x = (x | (x >> S[2])) & B[2];
|
||||
y = (y | (y >> S[2])) & B[2];
|
||||
|
||||
x = (x | (x >> S[3])) & B[3];
|
||||
y = (y | (y >> S[3])) & B[3];
|
||||
|
||||
x = (x | (x >> S[4])) & B[4];
|
||||
y = (y | (y >> S[4])) & B[4];
|
||||
|
||||
x = (x | (x >> S[5])) & B[5];
|
||||
y = (y | (y >> S[5])) & B[5];
|
||||
|
||||
return x | (y << 32);
|
||||
}
|
||||
|
||||
void geohashGetCoordRange(GeoHashRange *long_range, GeoHashRange *lat_range) {
|
||||
/* These are constraints from EPSG:900913 / EPSG:3785 / OSGEO:41001 */
|
||||
/* We can't geocode at the north/south pole. */
|
||||
long_range->max = GEO_LONG_MAX;
|
||||
long_range->min = GEO_LONG_MIN;
|
||||
lat_range->max = GEO_LAT_MAX;
|
||||
lat_range->min = GEO_LAT_MIN;
|
||||
}
|
||||
|
||||
int geohashEncode(GeoHashRange *long_range, GeoHashRange *lat_range,
|
||||
double longitude, double latitude, uint8_t step,
|
||||
GeoHashBits *hash) {
|
||||
/* Check basic arguments sanity. */
|
||||
if (hash == NULL || step > 32 || step == 0 ||
|
||||
RANGEPISZERO(lat_range) || RANGEPISZERO(long_range)) return 0;
|
||||
|
||||
/* Return an error when trying to index outside the supported
|
||||
* constraints. */
|
||||
if (longitude > 180 || longitude < -180 ||
|
||||
latitude > 85.05112878 || latitude < -85.05112878) return 0;
|
||||
|
||||
hash->bits = 0;
|
||||
hash->step = step;
|
||||
|
||||
if (latitude < lat_range->min || latitude > lat_range->max ||
|
||||
longitude < long_range->min || longitude > long_range->max) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double lat_offset =
|
||||
(latitude - lat_range->min) / (lat_range->max - lat_range->min);
|
||||
double long_offset =
|
||||
(longitude - long_range->min) / (long_range->max - long_range->min);
|
||||
|
||||
/* convert to fixed point based on the step size */
|
||||
lat_offset *= (1 << step);
|
||||
long_offset *= (1 << step);
|
||||
hash->bits = interleave64(lat_offset, long_offset);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int geohashEncodeType(double longitude, double latitude, uint8_t step, GeoHashBits *hash) {
|
||||
GeoHashRange r[2] = { { 0 } };
|
||||
geohashGetCoordRange(&r[0], &r[1]);
|
||||
return geohashEncode(&r[0], &r[1], longitude, latitude, step, hash);
|
||||
}
|
||||
|
||||
int geohashEncodeWGS84(double longitude, double latitude, uint8_t step,
|
||||
GeoHashBits *hash) {
|
||||
return geohashEncodeType(longitude, latitude, step, hash);
|
||||
}
|
||||
|
||||
int geohashDecode(const GeoHashRange long_range, const GeoHashRange lat_range,
|
||||
const GeoHashBits hash, GeoHashArea *area) {
|
||||
if (HASHISZERO(hash) || NULL == area || RANGEISZERO(lat_range) ||
|
||||
RANGEISZERO(long_range)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
area->hash = hash;
|
||||
uint8_t step = hash.step;
|
||||
uint64_t hash_sep = deinterleave64(hash.bits); /* hash = [LAT][LONG] */
|
||||
|
||||
double lat_scale = lat_range.max - lat_range.min;
|
||||
double long_scale = long_range.max - long_range.min;
|
||||
|
||||
uint32_t ilato = hash_sep; /* get lat part of deinterleaved hash */
|
||||
uint32_t ilono = hash_sep >> 32; /* shift over to get long part of hash */
|
||||
|
||||
/* divide by 2**step.
|
||||
* Then, for 0-1 coordinate, multiply times scale and add
|
||||
to the min to get the absolute coordinate. */
|
||||
area->latitude.min =
|
||||
lat_range.min + (ilato * 1.0 / (1ull << step)) * lat_scale;
|
||||
area->latitude.max =
|
||||
lat_range.min + ((ilato + 1) * 1.0 / (1ull << step)) * lat_scale;
|
||||
area->longitude.min =
|
||||
long_range.min + (ilono * 1.0 / (1ull << step)) * long_scale;
|
||||
area->longitude.max =
|
||||
long_range.min + ((ilono + 1) * 1.0 / (1ull << step)) * long_scale;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int geohashDecodeType(const GeoHashBits hash, GeoHashArea *area) {
|
||||
GeoHashRange r[2] = { { 0 } };
|
||||
geohashGetCoordRange(&r[0], &r[1]);
|
||||
return geohashDecode(r[0], r[1], hash, area);
|
||||
}
|
||||
|
||||
int geohashDecodeWGS84(const GeoHashBits hash, GeoHashArea *area) {
|
||||
return geohashDecodeType(hash, area);
|
||||
}
|
||||
|
||||
int geohashDecodeAreaToLongLat(const GeoHashArea *area, double *xy) {
|
||||
if (!xy) return 0;
|
||||
xy[0] = (area->longitude.min + area->longitude.max) / 2;
|
||||
xy[1] = (area->latitude.min + area->latitude.max) / 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int geohashDecodeToLongLatType(const GeoHashBits hash, double *xy) {
|
||||
GeoHashArea area = { { 0 } };
|
||||
if (!xy || !geohashDecodeType(hash, &area))
|
||||
return 0;
|
||||
return geohashDecodeAreaToLongLat(&area, xy);
|
||||
}
|
||||
|
||||
int geohashDecodeToLongLatWGS84(const GeoHashBits hash, double *xy) {
|
||||
return geohashDecodeToLongLatType(hash, xy);
|
||||
}
|
||||
|
||||
static void geohash_move_x(GeoHashBits *hash, int8_t d) {
|
||||
if (d == 0)
|
||||
return;
|
||||
|
||||
uint64_t x = hash->bits & 0xaaaaaaaaaaaaaaaaULL;
|
||||
uint64_t y = hash->bits & 0x5555555555555555ULL;
|
||||
|
||||
uint64_t zz = 0x5555555555555555ULL >> (64 - hash->step * 2);
|
||||
|
||||
if (d > 0) {
|
||||
x = x + (zz + 1);
|
||||
} else {
|
||||
x = x | zz;
|
||||
x = x - (zz + 1);
|
||||
}
|
||||
|
||||
x &= (0xaaaaaaaaaaaaaaaaULL >> (64 - hash->step * 2));
|
||||
hash->bits = (x | y);
|
||||
}
|
||||
|
||||
static void geohash_move_y(GeoHashBits *hash, int8_t d) {
|
||||
if (d == 0)
|
||||
return;
|
||||
|
||||
uint64_t x = hash->bits & 0xaaaaaaaaaaaaaaaaULL;
|
||||
uint64_t y = hash->bits & 0x5555555555555555ULL;
|
||||
|
||||
uint64_t zz = 0xaaaaaaaaaaaaaaaaULL >> (64 - hash->step * 2);
|
||||
if (d > 0) {
|
||||
y = y + (zz + 1);
|
||||
} else {
|
||||
y = y | zz;
|
||||
y = y - (zz + 1);
|
||||
}
|
||||
y &= (0x5555555555555555ULL >> (64 - hash->step * 2));
|
||||
hash->bits = (x | y);
|
||||
}
|
||||
|
||||
void geohashNeighbors(const GeoHashBits *hash, GeoHashNeighbors *neighbors) {
|
||||
neighbors->east = *hash;
|
||||
neighbors->west = *hash;
|
||||
neighbors->north = *hash;
|
||||
neighbors->south = *hash;
|
||||
neighbors->south_east = *hash;
|
||||
neighbors->south_west = *hash;
|
||||
neighbors->north_east = *hash;
|
||||
neighbors->north_west = *hash;
|
||||
|
||||
geohash_move_x(&neighbors->east, 1);
|
||||
geohash_move_y(&neighbors->east, 0);
|
||||
|
||||
geohash_move_x(&neighbors->west, -1);
|
||||
geohash_move_y(&neighbors->west, 0);
|
||||
|
||||
geohash_move_x(&neighbors->south, 0);
|
||||
geohash_move_y(&neighbors->south, -1);
|
||||
|
||||
geohash_move_x(&neighbors->north, 0);
|
||||
geohash_move_y(&neighbors->north, 1);
|
||||
|
||||
geohash_move_x(&neighbors->north_west, -1);
|
||||
geohash_move_y(&neighbors->north_west, 1);
|
||||
|
||||
geohash_move_x(&neighbors->north_east, 1);
|
||||
geohash_move_y(&neighbors->north_east, 1);
|
||||
|
||||
geohash_move_x(&neighbors->south_east, 1);
|
||||
geohash_move_y(&neighbors->south_east, -1);
|
||||
|
||||
geohash_move_x(&neighbors->south_west, -1);
|
||||
geohash_move_y(&neighbors->south_west, -1);
|
||||
}
|
||||
Vendored
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2014, yinqiwen <yinqiwen@gmail.com>
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
||||
* Copyright (c) 2015, Salvatore Sanfilippo <antirez@gmail.com>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef GEOHASH_H_
|
||||
#define GEOHASH_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define HASHISZERO(r) (!(r).bits && !(r).step)
|
||||
#define RANGEISZERO(r) (!(r).max && !(r).min)
|
||||
#define RANGEPISZERO(r) (r == NULL || RANGEISZERO(*r))
|
||||
|
||||
#define GEO_STEP_MAX 26 /* 26*2 = 52 bits. */
|
||||
|
||||
/* Limits from EPSG:900913 / EPSG:3785 / OSGEO:41001 */
|
||||
#define GEO_LAT_MIN -85.05112878
|
||||
#define GEO_LAT_MAX 85.05112878
|
||||
#define GEO_LONG_MIN -180
|
||||
#define GEO_LONG_MAX 180
|
||||
|
||||
typedef enum {
|
||||
GEOHASH_NORTH = 0,
|
||||
GEOHASH_EAST,
|
||||
GEOHASH_WEST,
|
||||
GEOHASH_SOUTH,
|
||||
GEOHASH_SOUTH_WEST,
|
||||
GEOHASH_SOUTH_EAST,
|
||||
GEOHASH_NORT_WEST,
|
||||
GEOHASH_NORT_EAST
|
||||
} GeoDirection;
|
||||
|
||||
typedef struct {
|
||||
uint64_t bits;
|
||||
uint8_t step;
|
||||
} GeoHashBits;
|
||||
|
||||
typedef struct {
|
||||
double min;
|
||||
double max;
|
||||
} GeoHashRange;
|
||||
|
||||
typedef struct {
|
||||
GeoHashBits hash;
|
||||
GeoHashRange longitude;
|
||||
GeoHashRange latitude;
|
||||
} GeoHashArea;
|
||||
|
||||
typedef struct {
|
||||
GeoHashBits north;
|
||||
GeoHashBits east;
|
||||
GeoHashBits west;
|
||||
GeoHashBits south;
|
||||
GeoHashBits north_east;
|
||||
GeoHashBits south_east;
|
||||
GeoHashBits north_west;
|
||||
GeoHashBits south_west;
|
||||
} GeoHashNeighbors;
|
||||
|
||||
/*
|
||||
* 0:success
|
||||
* -1:failed
|
||||
*/
|
||||
void geohashGetCoordRange(GeoHashRange *long_range, GeoHashRange *lat_range);
|
||||
int geohashEncode(GeoHashRange *long_range, GeoHashRange *lat_range,
|
||||
double longitude, double latitude, uint8_t step,
|
||||
GeoHashBits *hash);
|
||||
int geohashEncodeType(double longitude, double latitude,
|
||||
uint8_t step, GeoHashBits *hash);
|
||||
int geohashEncodeWGS84(double longitude, double latitude, uint8_t step,
|
||||
GeoHashBits *hash);
|
||||
int geohashDecode(const GeoHashRange long_range, const GeoHashRange lat_range,
|
||||
const GeoHashBits hash, GeoHashArea *area);
|
||||
int geohashDecodeType(const GeoHashBits hash, GeoHashArea *area);
|
||||
int geohashDecodeWGS84(const GeoHashBits hash, GeoHashArea *area);
|
||||
int geohashDecodeAreaToLongLat(const GeoHashArea *area, double *xy);
|
||||
int geohashDecodeToLongLatType(const GeoHashBits hash, double *xy);
|
||||
int geohashDecodeToLongLatWGS84(const GeoHashBits hash, double *xy);
|
||||
int geohashDecodeToLongLatMercator(const GeoHashBits hash, double *xy);
|
||||
void geohashNeighbors(const GeoHashBits *hash, GeoHashNeighbors *neighbors);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
#endif /* GEOHASH_H_ */
|
||||
Vendored
+195
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2014, yinqiwen <yinqiwen@gmail.com>
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
||||
* Copyright (c) 2015, Salvatore Sanfilippo <antirez@gmail.com>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* This is a C++ to C conversion from the ardb project.
|
||||
* This file started out as:
|
||||
* https://github.com/yinqiwen/ardb/blob/d42503/src/geo/geohash_helper.cpp
|
||||
*/
|
||||
|
||||
#include "geohash_helper.h"
|
||||
|
||||
#define D_R (M_PI / 180.0)
|
||||
#define R_MAJOR 6378137.0
|
||||
#define R_MINOR 6356752.3142
|
||||
#define RATIO (R_MINOR / R_MAJOR)
|
||||
#define ECCENT (sqrt(1.0 - (RATIO *RATIO)))
|
||||
#define COM (0.5 * ECCENT)
|
||||
|
||||
/// @brief The usual PI/180 constant
|
||||
const double DEG_TO_RAD = 0.017453292519943295769236907684886;
|
||||
/// @brief Earth's quatratic mean radius for WGS-84
|
||||
const double EARTH_RADIUS_IN_METERS = 6372797.560856;
|
||||
|
||||
const double MERCATOR_MAX = 20037726.37;
|
||||
const double MERCATOR_MIN = -20037726.37;
|
||||
|
||||
static inline double deg_rad(double ang) { return ang * D_R; }
|
||||
static inline double rad_deg(double ang) { return ang / D_R; }
|
||||
|
||||
/* You must *ONLY* estimate steps when you are encoding.
|
||||
* If you are decoding, always decode to GEO_STEP_MAX (26). */
|
||||
uint8_t geohashEstimateStepsByRadius(double range_meters, double lat) {
|
||||
if (range_meters == 0) return 26;
|
||||
int step = 1;
|
||||
while (range_meters < MERCATOR_MAX) {
|
||||
range_meters *= 2;
|
||||
step++;
|
||||
}
|
||||
step -= 2; /* Make sure range is included in the worst case. */
|
||||
/* Wider range torwards the poles... Note: it is possible to do better
|
||||
* than this approximation by computing the distance between meridians
|
||||
* at this latitude, but this does the trick for now. */
|
||||
if (lat > 67 || lat < -67) step--;
|
||||
if (lat > 80 || lat < -80) step--;
|
||||
|
||||
/* Frame to valid range. */
|
||||
if (step < 1) step = 1;
|
||||
if (step > 26) step = 25;
|
||||
return step;
|
||||
}
|
||||
|
||||
int geohashBitsComparator(const GeoHashBits *a, const GeoHashBits *b) {
|
||||
/* If step not equal, compare on step. Else, compare on bits. */
|
||||
return a->step != b->step ? a->step - b->step : a->bits - b->bits;
|
||||
}
|
||||
|
||||
int geohashBoundingBox(double longitude, double latitude, double radius_meters,
|
||||
double *bounds) {
|
||||
if (!bounds) return 0;
|
||||
|
||||
double lonr, latr;
|
||||
lonr = deg_rad(longitude);
|
||||
latr = deg_rad(latitude);
|
||||
|
||||
double distance = radius_meters / EARTH_RADIUS_IN_METERS;
|
||||
double min_latitude = latr - distance;
|
||||
double max_latitude = latr + distance;
|
||||
|
||||
/* Note: we're being lazy and not accounting for coordinates near poles */
|
||||
double min_longitude, max_longitude;
|
||||
double difference_longitude = asin(sin(distance) / cos(latr));
|
||||
min_longitude = lonr - difference_longitude;
|
||||
max_longitude = lonr + difference_longitude;
|
||||
|
||||
bounds[0] = rad_deg(min_longitude);
|
||||
bounds[1] = rad_deg(min_latitude);
|
||||
bounds[2] = rad_deg(max_longitude);
|
||||
bounds[3] = rad_deg(max_latitude);
|
||||
return 1;
|
||||
}
|
||||
|
||||
GeoHashRadius geohashGetAreasByRadius(double longitude, double latitude, double radius_meters) {
|
||||
GeoHashRange long_range, lat_range;
|
||||
GeoHashRadius radius = { { 0 } };
|
||||
GeoHashBits hash = { 0 };
|
||||
GeoHashNeighbors neighbors = { { 0 } };
|
||||
GeoHashArea area = { { 0 } };
|
||||
double min_lon, max_lon, min_lat, max_lat;
|
||||
double bounds[4];
|
||||
int steps;
|
||||
|
||||
geohashBoundingBox(longitude, latitude, radius_meters, bounds);
|
||||
min_lon = bounds[0];
|
||||
min_lat = bounds[1];
|
||||
max_lon = bounds[2];
|
||||
max_lat = bounds[3];
|
||||
|
||||
steps = geohashEstimateStepsByRadius(radius_meters,latitude);
|
||||
|
||||
geohashGetCoordRange(&long_range, &lat_range);
|
||||
geohashEncode(&long_range, &lat_range, longitude, latitude, steps, &hash);
|
||||
geohashNeighbors(&hash, &neighbors);
|
||||
geohashGetCoordRange(&long_range, &lat_range);
|
||||
geohashDecode(long_range, lat_range, hash, &area);
|
||||
|
||||
if (area.latitude.min < min_lat) {
|
||||
GZERO(neighbors.south);
|
||||
GZERO(neighbors.south_west);
|
||||
GZERO(neighbors.south_east);
|
||||
}
|
||||
if (area.latitude.max > max_lat) {
|
||||
GZERO(neighbors.north);
|
||||
GZERO(neighbors.north_east);
|
||||
GZERO(neighbors.north_west);
|
||||
}
|
||||
if (area.longitude.min < min_lon) {
|
||||
GZERO(neighbors.west);
|
||||
GZERO(neighbors.south_west);
|
||||
GZERO(neighbors.north_west);
|
||||
}
|
||||
if (area.longitude.max > max_lon) {
|
||||
GZERO(neighbors.east);
|
||||
GZERO(neighbors.south_east);
|
||||
GZERO(neighbors.north_east);
|
||||
}
|
||||
radius.hash = hash;
|
||||
radius.neighbors = neighbors;
|
||||
radius.area = area;
|
||||
return radius;
|
||||
}
|
||||
|
||||
GeoHashRadius geohashGetAreasByRadiusWGS84(double longitude, double latitude,
|
||||
double radius_meters) {
|
||||
return geohashGetAreasByRadius(longitude, latitude, radius_meters);
|
||||
}
|
||||
|
||||
GeoHashFix52Bits geohashAlign52Bits(const GeoHashBits hash) {
|
||||
uint64_t bits = hash.bits;
|
||||
bits <<= (52 - hash.step * 2);
|
||||
return bits;
|
||||
}
|
||||
|
||||
/* Calculate distance using haversin great circle distance formula. */
|
||||
double geohashGetDistance(double lon1d, double lat1d, double lon2d, double lat2d) {
|
||||
double lat1r, lon1r, lat2r, lon2r, u, v;
|
||||
lat1r = deg_rad(lat1d);
|
||||
lon1r = deg_rad(lon1d);
|
||||
lat2r = deg_rad(lat2d);
|
||||
lon2r = deg_rad(lon2d);
|
||||
u = sin((lat2r - lat1r) / 2);
|
||||
v = sin((lon2r - lon1r) / 2);
|
||||
return 2.0 * EARTH_RADIUS_IN_METERS *
|
||||
asin(sqrt(u * u + cos(lat1r) * cos(lat2r) * v * v));
|
||||
}
|
||||
|
||||
int geohashGetDistanceIfInRadius(double x1, double y1,
|
||||
double x2, double y2, double radius,
|
||||
double *distance) {
|
||||
*distance = geohashGetDistance(x1, y1, x2, y2);
|
||||
if (*distance > radius) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int geohashGetDistanceIfInRadiusWGS84(double x1, double y1, double x2,
|
||||
double y2, double radius,
|
||||
double *distance) {
|
||||
return geohashGetDistanceIfInRadius(x1, y1, x2, y2, radius, distance);
|
||||
}
|
||||
Vendored
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2014, yinqiwen <yinqiwen@gmail.com>
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
||||
* Copyright (c) 2015, Salvatore Sanfilippo <antirez@gmail.com>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
* THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef GEOHASH_HELPER_HPP_
|
||||
#define GEOHASH_HELPER_HPP_
|
||||
|
||||
#include <math.h>
|
||||
#include "geohash.h"
|
||||
|
||||
#define GZERO(s) s.bits = s.step = 0;
|
||||
#define GISZERO(s) (!s.bits && !s.step)
|
||||
#define GISNOTZERO(s) (s.bits || s.step)
|
||||
|
||||
typedef uint64_t GeoHashFix52Bits;
|
||||
typedef uint64_t GeoHashVarBits;
|
||||
|
||||
typedef struct {
|
||||
GeoHashBits hash;
|
||||
GeoHashArea area;
|
||||
GeoHashNeighbors neighbors;
|
||||
} GeoHashRadius;
|
||||
|
||||
int GeoHashBitsComparator(const GeoHashBits *a, const GeoHashBits *b);
|
||||
uint8_t geohashEstimateStepsByRadius(double range_meters, double lat);
|
||||
int geohashBoundingBox(double longitude, double latitude, double radius_meters,
|
||||
double *bounds);
|
||||
GeoHashRadius geohashGetAreasByRadius(double longitude,
|
||||
double latitude, double radius_meters);
|
||||
GeoHashRadius geohashGetAreasByRadiusWGS84(double longitude, double latitude,
|
||||
double radius_meters);
|
||||
GeoHashRadius geohashGetAreasByRadiusMercator(double longitude, double latitude,
|
||||
double radius_meters);
|
||||
GeoHashFix52Bits geohashAlign52Bits(const GeoHashBits hash);
|
||||
double geohashGetDistance(double lon1d, double lat1d,
|
||||
double lon2d, double lat2d);
|
||||
int geohashGetDistanceIfInRadius(double x1, double y1,
|
||||
double x2, double y2, double radius,
|
||||
double *distance);
|
||||
int geohashGetDistanceIfInRadiusWGS84(double x1, double y1, double x2,
|
||||
double y2, double radius,
|
||||
double *distance);
|
||||
|
||||
#endif /* GEOHASH_HELPER_HPP_ */
|
||||
Vendored
+1
@@ -443,6 +443,7 @@ 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
+4
@@ -5,6 +5,10 @@
|
||||
#define _BSD_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(_AIX)
|
||||
#define _ALL_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(__sun__)
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
|
||||
Vendored
+1
@@ -300,6 +300,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
break;
|
||||
}
|
||||
}
|
||||
freeaddrinfo(bservinfo);
|
||||
if (!bound) {
|
||||
char buf[128];
|
||||
snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno));
|
||||
|
||||
Vendored
+1
-1
@@ -35,7 +35,7 @@
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
#if defined(__sun)
|
||||
#if defined(__sun) || defined(_AIX)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
|
||||
Vendored
+7
-4
@@ -123,7 +123,7 @@ void sdsclear(sds s) {
|
||||
/* Enlarge the free space at the end of the sds string so that the caller
|
||||
* is sure that after calling this function can overwrite up to addlen
|
||||
* bytes after the end of the string, plus one more byte for nul term.
|
||||
*
|
||||
*
|
||||
* Note: this does not change the *length* of the sds string as returned
|
||||
* by sdslen(), but only the free buffer space we have. */
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
@@ -200,10 +200,12 @@ size_t sdsAllocSize(sds s) {
|
||||
void sdsIncrLen(sds s, int incr) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
|
||||
assert(sh->free >= incr);
|
||||
if (incr >= 0)
|
||||
assert(sh->free >= (unsigned int)incr);
|
||||
else
|
||||
assert(sh->len >= (unsigned int)(-incr));
|
||||
sh->len += incr;
|
||||
sh->free -= incr;
|
||||
assert(sh->free >= 0);
|
||||
s[sh->len] = '\0';
|
||||
}
|
||||
|
||||
@@ -388,6 +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(ap);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
if (buf != staticbuf) zfree(buf);
|
||||
buflen *= 2;
|
||||
@@ -457,7 +460,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
i = initlen; /* Position of the next byte to write to dest str. */
|
||||
while(*f) {
|
||||
char next, *str;
|
||||
int l;
|
||||
unsigned int l;
|
||||
long long num;
|
||||
unsigned long long unum;
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -39,8 +39,8 @@
|
||||
typedef char *sds;
|
||||
|
||||
struct sdshdr {
|
||||
int len;
|
||||
int free;
|
||||
unsigned int len;
|
||||
unsigned int free;
|
||||
char buf[];
|
||||
};
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -51,7 +51,7 @@ static redisContext *select_database(redisContext *c) {
|
||||
assert(reply != NULL);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Make sure the DB is emtpy */
|
||||
/* Make sure the DB is empty */
|
||||
reply = redisCommand(c,"DBSIZE");
|
||||
assert(reply != NULL);
|
||||
if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) {
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
linenoise_example*
|
||||
linenoise_example
|
||||
*.dSYM
|
||||
history.txt
|
||||
|
||||
Vendored
+15
-8
@@ -1,8 +1,13 @@
|
||||
# Linenoise
|
||||
|
||||
A minimal, zero-config, BSD licensed, readline replacement.
|
||||
A minimal, zero-config, BSD licensed, readline replacement used in Redis,
|
||||
MongoDB, and Android.
|
||||
|
||||
News: linenoise is now part of [Android](http://android.git.kernel.org/?p=platform/system/core.git;a=tree;f=liblinenoise;h=56450eaed7f783760e5e6a5993ef75cde2e29dea;hb=HEAD Android)!
|
||||
* Single and multi line editing mode with the usual key bindings implemented.
|
||||
* History handling.
|
||||
* Completion.
|
||||
* About 1,100 lines of BSD license source code.
|
||||
* Only uses a subset of VT100 escapes (ANSI.SYS compatible).
|
||||
|
||||
## Can a line editing library be 20k lines of code?
|
||||
|
||||
@@ -10,7 +15,7 @@ Line editing with some support for history is a really important feature for com
|
||||
|
||||
So what usually happens is either:
|
||||
|
||||
* Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (Readl world example of this problem: Tclsh).
|
||||
* Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (Real world example of this problem: Tclsh).
|
||||
* Smaller programs not using a configure script not supporting line editing at all (A problem we had with Redis-cli for instance).
|
||||
|
||||
The result is a pollution of binaries without line editing support.
|
||||
@@ -19,27 +24,29 @@ So I spent more or less two hours doing a reality check resulting in this little
|
||||
|
||||
## Terminals, in 2010.
|
||||
|
||||
Apparently almost every terminal you can happen to use today has some kind of support for VT100 alike escape sequences. So I tried to write a lib using just very basic VT100 features. The resulting library appears to work everywhere I tried to use it.
|
||||
Apparently almost every terminal you can happen to use today has some kind of support for basic VT100 escape sequences. So I tried to write a lib using just very basic VT100 features. The resulting library appears to work everywhere I tried to use it, and now can work even on ANSI.SYS compatible terminals, since no
|
||||
VT220 specific sequences are used anymore.
|
||||
|
||||
Since it's so young I guess there are a few bugs, or the lib may not compile or work with some operating system, but it's a matter of a few weeks and eventually we'll get it right, and there will be no excuses for not shipping command line tools without built-in line editing support.
|
||||
|
||||
The library is currently less than 400 lines of code. In order to use it in your project just look at the *example.c* file in the source distribution, it is trivial. Linenoise is BSD code, so you can use both in free software and commercial software.
|
||||
The library is currently about 1100 lines of code. In order to use it in your project just look at the *example.c* file in the source distribution, it is trivial. Linenoise is BSD code, so you can use both in free software and commercial software.
|
||||
|
||||
## Tested with...
|
||||
|
||||
* Linux text only console ($TERM = linux)
|
||||
* Linux KDE terminal application ($TERM = xterm)
|
||||
* Linux xterm ($TERM = xterm)
|
||||
* Linux Buildroot ($TERM = vt100)
|
||||
* Mac OS X iTerm ($TERM = xterm)
|
||||
* Mac OS X default Terminal.app ($TERM = xterm)
|
||||
* OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen)
|
||||
* IBM AIX 6.1
|
||||
* FreeBSD xterm ($TERM = xterm)
|
||||
* ANSI.SYS
|
||||
|
||||
Please test it everywhere you can and report back!
|
||||
|
||||
## Let's push this forward!
|
||||
|
||||
Please fork it and add something interesting and send me a pull request. What's especially interesting are fixes, new key bindings, completion.
|
||||
Patches should be provided in the respect of linenoise sensibility for small
|
||||
easy to understand code.
|
||||
|
||||
Send feedbacks to antirez at gmail
|
||||
|
||||
Vendored
+41
-4
@@ -1,5 +1,6 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "linenoise.h"
|
||||
|
||||
|
||||
@@ -10,16 +11,52 @@ void completion(const char *buf, linenoiseCompletions *lc) {
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int main(int argc, char **argv) {
|
||||
char *line;
|
||||
char *prgname = argv[0];
|
||||
|
||||
/* Parse options, with --multiline we enable multi line editing. */
|
||||
while(argc > 1) {
|
||||
argc--;
|
||||
argv++;
|
||||
if (!strcmp(*argv,"--multiline")) {
|
||||
linenoiseSetMultiLine(1);
|
||||
printf("Multi-line mode enabled.\n");
|
||||
} else if (!strcmp(*argv,"--keycodes")) {
|
||||
linenoisePrintKeyCodes();
|
||||
exit(0);
|
||||
} else {
|
||||
fprintf(stderr, "Usage: %s [--multiline] [--keycodes]\n", prgname);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the completion callback. This will be called every time the
|
||||
* user uses the <tab> key. */
|
||||
linenoiseSetCompletionCallback(completion);
|
||||
|
||||
/* Load history from file. The history file is just a plain text file
|
||||
* where entries are separated by newlines. */
|
||||
linenoiseHistoryLoad("history.txt"); /* Load the history at startup */
|
||||
|
||||
/* Now this is the main loop of the typical linenoise-based application.
|
||||
* The call to linenoise() will block as long as the user types something
|
||||
* and presses enter.
|
||||
*
|
||||
* The typed string is returned as a malloc() allocated string by
|
||||
* linenoise, so the user needs to free() it. */
|
||||
while((line = linenoise("hello> ")) != NULL) {
|
||||
if (line[0] != '\0') {
|
||||
/* Do something with the string. */
|
||||
if (line[0] != '\0' && line[0] != '/') {
|
||||
printf("echo: '%s'\n", line);
|
||||
linenoiseHistoryAdd(line);
|
||||
linenoiseHistorySave("history.txt"); /* Save every new entry */
|
||||
linenoiseHistoryAdd(line); /* Add to the history. */
|
||||
linenoiseHistorySave("history.txt"); /* Save the history on disk. */
|
||||
} else if (!strncmp(line,"/historylen",11)) {
|
||||
/* The "/historylen" command will change the history len. */
|
||||
int len = atoi(line+11);
|
||||
linenoiseHistorySetMaxLen(len);
|
||||
} else if (line[0] == '/') {
|
||||
printf("Unreconized command: %s\n", line);
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
|
||||
Vendored
+47
-32
@@ -2,7 +2,7 @@
|
||||
* line editing lib needs to be 20,000 lines of C code.
|
||||
*
|
||||
* You can find the latest source code at:
|
||||
*
|
||||
*
|
||||
* http://github.com/antirez/linenoise
|
||||
*
|
||||
* Does a number of crazy assumptions that happen to be true in 99.9999% of
|
||||
@@ -14,18 +14,18 @@
|
||||
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
@@ -37,7 +37,7 @@
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*
|
||||
* ------------------------------------------------------------------------
|
||||
*
|
||||
* References:
|
||||
@@ -56,10 +56,6 @@
|
||||
* flickering effect with some slow terminal, but the lesser sequences
|
||||
* the more compatible.
|
||||
*
|
||||
* CHA (Cursor Horizontal Absolute)
|
||||
* Sequence: ESC [ n G
|
||||
* Effect: moves cursor to column n
|
||||
*
|
||||
* EL (Erase Line)
|
||||
* Sequence: ESC [ n K
|
||||
* Effect: if n is 0 or missing, clear from cursor to end of line
|
||||
@@ -68,7 +64,19 @@
|
||||
*
|
||||
* CUF (CUrsor Forward)
|
||||
* Sequence: ESC [ n C
|
||||
* Effect: moves cursor forward of n chars
|
||||
* Effect: moves cursor forward n chars
|
||||
*
|
||||
* CUB (CUrsor Backward)
|
||||
* Sequence: ESC [ n D
|
||||
* Effect: moves cursor backward n chars
|
||||
*
|
||||
* The following is used to get the terminal width if getting
|
||||
* the width with the TIOCGWINSZ ioctl fails
|
||||
*
|
||||
* DSR (Device Status Report)
|
||||
* Sequence: ESC [ 6 n
|
||||
* Effect: reports the current cusor position as ESC [ n ; m R
|
||||
* where n is the row and m is the column
|
||||
*
|
||||
* When multi line mode is enabled, we also use an additional escape
|
||||
* sequence. However multi line editing is disabled by default.
|
||||
@@ -81,17 +89,18 @@
|
||||
* Sequence: ESC [ n B
|
||||
* Effect: moves cursor down of n chars.
|
||||
*
|
||||
* The following are used to clear the screen: ESC [ H ESC [ 2 J
|
||||
* This is actually composed of two sequences:
|
||||
* When linenoiseClearScreen() is called, two additional escape sequences
|
||||
* are used in order to clear the screen and position the cursor at home
|
||||
* position.
|
||||
*
|
||||
* cursorhome
|
||||
* CUP (Cursor position)
|
||||
* Sequence: ESC [ H
|
||||
* Effect: moves the cursor to upper left corner
|
||||
*
|
||||
* ED2 (Clear entire screen)
|
||||
* ED (Erase display)
|
||||
* Sequence: ESC [ 2 J
|
||||
* Effect: clear the whole screen
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <termios.h>
|
||||
@@ -332,7 +341,7 @@ static void freeCompletions(linenoiseCompletions *lc) {
|
||||
/* This is an helper function for linenoiseEdit() and is called when the
|
||||
* user types the <tab> key in order to complete the string currently in the
|
||||
* input.
|
||||
*
|
||||
*
|
||||
* The state of the editing is encapsulated into the pointed linenoiseState
|
||||
* structure as described in the structure definition. */
|
||||
static int completeLine(struct linenoiseState *ls) {
|
||||
@@ -459,7 +468,7 @@ static void refreshSingleLine(struct linenoiseState *l) {
|
||||
size_t len = l->len;
|
||||
size_t pos = l->pos;
|
||||
struct abuf ab;
|
||||
|
||||
|
||||
while((plen+pos) >= l->cols) {
|
||||
buf++;
|
||||
len--;
|
||||
@@ -471,7 +480,7 @@ static void refreshSingleLine(struct linenoiseState *l) {
|
||||
|
||||
abInit(&ab);
|
||||
/* Cursor to left edge */
|
||||
snprintf(seq,64,"\x1b[0G");
|
||||
snprintf(seq,64,"\r");
|
||||
abAppend(&ab,seq,strlen(seq));
|
||||
/* Write the prompt and the current buffer content */
|
||||
abAppend(&ab,l->prompt,strlen(l->prompt));
|
||||
@@ -480,7 +489,7 @@ static void refreshSingleLine(struct linenoiseState *l) {
|
||||
snprintf(seq,64,"\x1b[0K");
|
||||
abAppend(&ab,seq,strlen(seq));
|
||||
/* Move cursor to original position. */
|
||||
snprintf(seq,64,"\x1b[0G\x1b[%dC", (int)(pos+plen));
|
||||
snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen));
|
||||
abAppend(&ab,seq,strlen(seq));
|
||||
if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
|
||||
abFree(&ab);
|
||||
@@ -496,6 +505,7 @@ static void refreshMultiLine(struct linenoiseState *l) {
|
||||
int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */
|
||||
int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */
|
||||
int rpos2; /* rpos after refresh. */
|
||||
int col; /* colum position, zero-based. */
|
||||
int old_rows = l->maxrows;
|
||||
int fd = l->ofd, j;
|
||||
struct abuf ab;
|
||||
@@ -515,15 +525,15 @@ static void refreshMultiLine(struct linenoiseState *l) {
|
||||
/* Now for every row clear it, go up. */
|
||||
for (j = 0; j < old_rows-1; j++) {
|
||||
lndebug("clear+up");
|
||||
snprintf(seq,64,"\x1b[0G\x1b[0K\x1b[1A");
|
||||
snprintf(seq,64,"\r\x1b[0K\x1b[1A");
|
||||
abAppend(&ab,seq,strlen(seq));
|
||||
}
|
||||
|
||||
/* Clean the top line. */
|
||||
lndebug("clear");
|
||||
snprintf(seq,64,"\x1b[0G\x1b[0K");
|
||||
snprintf(seq,64,"\r\x1b[0K");
|
||||
abAppend(&ab,seq,strlen(seq));
|
||||
|
||||
|
||||
/* Write the prompt and the current buffer content */
|
||||
abAppend(&ab,l->prompt,strlen(l->prompt));
|
||||
abAppend(&ab,l->buf,l->len);
|
||||
@@ -536,7 +546,7 @@ static void refreshMultiLine(struct linenoiseState *l) {
|
||||
{
|
||||
lndebug("<newline>");
|
||||
abAppend(&ab,"\n",1);
|
||||
snprintf(seq,64,"\x1b[0G");
|
||||
snprintf(seq,64,"\r");
|
||||
abAppend(&ab,seq,strlen(seq));
|
||||
rows++;
|
||||
if (rows > (int)l->maxrows) l->maxrows = rows;
|
||||
@@ -554,8 +564,12 @@ static void refreshMultiLine(struct linenoiseState *l) {
|
||||
}
|
||||
|
||||
/* Set column. */
|
||||
lndebug("set col %d", 1+((plen+(int)l->pos) % (int)l->cols));
|
||||
snprintf(seq,64,"\x1b[%dG", 1+((plen+(int)l->pos) % (int)l->cols));
|
||||
col = (plen+(int)l->pos) % (int)l->cols;
|
||||
lndebug("set col %d", 1+col);
|
||||
if (col)
|
||||
snprintf(seq,64,"\r\x1b[%dC", col);
|
||||
else
|
||||
snprintf(seq,64,"\r");
|
||||
abAppend(&ab,seq,strlen(seq));
|
||||
|
||||
lndebug("\n");
|
||||
@@ -732,7 +746,7 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
|
||||
/* The latest history entry is always our current buffer, that
|
||||
* initially is just an empty string. */
|
||||
linenoiseHistoryAdd("");
|
||||
|
||||
|
||||
if (write(l.ofd,prompt,l.plen) == -1) return -1;
|
||||
while(1) {
|
||||
char c;
|
||||
@@ -757,6 +771,7 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
|
||||
case ENTER: /* enter */
|
||||
history_len--;
|
||||
free(history[history_len]);
|
||||
if (mlmode) linenoiseEditMoveEnd(&l);
|
||||
return (int)l.len;
|
||||
case CTRL_C: /* ctrl-c */
|
||||
errno = EAGAIN;
|
||||
@@ -765,8 +780,8 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
|
||||
case 8: /* ctrl-h */
|
||||
linenoiseEditBackspace(&l);
|
||||
break;
|
||||
case CTRL_D: /* ctrl-d, remove char at right of cursor, or of the
|
||||
line is empty, act as end-of-file. */
|
||||
case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the
|
||||
line is empty, act as end-of-file. */
|
||||
if (l.len > 0) {
|
||||
linenoiseEditDelete(&l);
|
||||
} else {
|
||||
@@ -904,7 +919,7 @@ void linenoisePrintKeyCodes(void) {
|
||||
|
||||
printf("'%c' %02x (%d) (type quit to exit)\n",
|
||||
isprint(c) ? c : '?', (int)c, (int)c);
|
||||
printf("\x1b[0G"); /* Go left edge manually, we are in raw mode. */
|
||||
printf("\r"); /* Go left edge manually, we are in raw mode. */
|
||||
fflush(stdout);
|
||||
}
|
||||
disableRawMode(STDIN_FILENO);
|
||||
@@ -1058,7 +1073,7 @@ int linenoiseHistorySetMaxLen(int len) {
|
||||
int linenoiseHistorySave(const char *filename) {
|
||||
FILE *fp = fopen(filename,"w");
|
||||
int j;
|
||||
|
||||
|
||||
if (fp == NULL) return -1;
|
||||
for (j = 0; j < history_len; j++)
|
||||
fprintf(fp,"%s\n",history[j]);
|
||||
@@ -1074,12 +1089,12 @@ int linenoiseHistorySave(const char *filename) {
|
||||
int linenoiseHistoryLoad(const char *filename) {
|
||||
FILE *fp = fopen(filename,"r");
|
||||
char buf[LINENOISE_MAX_LINE];
|
||||
|
||||
|
||||
if (fp == NULL) return -1;
|
||||
|
||||
while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
|
||||
char *p;
|
||||
|
||||
|
||||
p = strchr(buf,'\r');
|
||||
if (!p) p = strchr(buf,'\n');
|
||||
if (p) *p = '\0';
|
||||
|
||||
Vendored
+3
-3
@@ -9,18 +9,18 @@
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
|
||||
Vendored
+3
-2
@@ -25,9 +25,10 @@ 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
|
||||
lundump.o lvm.o lzio.o strbuf.o fpconv.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
|
||||
lstrlib.o loadlib.o linit.o lua_cjson.o lua_struct.o lua_cmsgpack.o \
|
||||
lua_bit.o
|
||||
|
||||
LUA_T= lua
|
||||
LUA_O= lua.o
|
||||
|
||||
Vendored
+205
@@ -0,0 +1,205 @@
|
||||
/* 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
@@ -0,0 +1,22 @@
|
||||
/* 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 = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z,
|
||||
tf = (luaY_parser)(L, p->z,
|
||||
&p->buff, p->name);
|
||||
cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));
|
||||
cl->l.p = tf;
|
||||
|
||||
Vendored
+189
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
** 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
+434
-306
File diff suppressed because it is too large
Load Diff
Vendored
+368
-127
@@ -7,14 +7,38 @@
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
|
||||
#define LUACMSGPACK_VERSION "lua-cmsgpack 0.3.0"
|
||||
#define LUACMSGPACK_NAME "cmsgpack"
|
||||
#define LUACMSGPACK_SAFE_NAME "cmsgpack_safe"
|
||||
#define LUACMSGPACK_VERSION "lua-cmsgpack 0.4.0"
|
||||
#define LUACMSGPACK_COPYRIGHT "Copyright (C) 2012, Salvatore Sanfilippo"
|
||||
#define LUACMSGPACK_DESCRIPTION "MessagePack C implementation for Lua"
|
||||
|
||||
#define LUACMSGPACK_MAX_NESTING 16 /* Max tables nesting. */
|
||||
/* Allows a preprocessor directive to override MAX_NESTING */
|
||||
#ifndef LUACMSGPACK_MAX_NESTING
|
||||
#define LUACMSGPACK_MAX_NESTING 16 /* Max tables nesting. */
|
||||
#endif
|
||||
|
||||
/* ==============================================================================
|
||||
* MessagePack implementation and bindings for Lua 5.1.
|
||||
/* 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.
|
||||
* Copyright(C) 2012 Salvatore Sanfilippo <antirez@gmail.com>
|
||||
*
|
||||
* http://github.com/antirez/lua-cmsgpack
|
||||
@@ -29,23 +53,27 @@
|
||||
* 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 are performed
|
||||
/* -------------------------- Endian conversion --------------------------------
|
||||
* We use it only for floats and doubles, all the other conversions performed
|
||||
* in an endian independent fashion. So the only thing we need is a function
|
||||
* that swaps a binary string if the arch is little endian (and left it untouched
|
||||
* that swaps a binary string if 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 to check for endianess at runtime.
|
||||
* simplicity of the Lua build system we prefer check for endianess at runtime.
|
||||
* The performance difference should be acceptable. */
|
||||
static void memrevifle(void *ptr, size_t len) {
|
||||
unsigned char *p = ptr, *e = p+len-1, aux;
|
||||
void memrevifle(void *ptr, size_t len) {
|
||||
unsigned char *p = (unsigned char *)ptr,
|
||||
*e = (unsigned char *)p+len-1,
|
||||
aux;
|
||||
int test = 1;
|
||||
unsigned char *testp = (unsigned char*) &test;
|
||||
|
||||
if (testp[0] == 0) return; /* Big endian, nothign to do. */
|
||||
if (testp[0] == 0) return; /* Big endian, nothing to do. */
|
||||
len /= 2;
|
||||
while(len--) {
|
||||
aux = *p;
|
||||
@@ -56,20 +84,34 @@ static void memrevifle(void *ptr, size_t len) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------- String buffer ----------------------------------
|
||||
* This is a simple implementation of string buffers. The only opereation
|
||||
/* ---------------------------- String buffer ----------------------------------
|
||||
* This is a simple implementation of string buffers. The only operation
|
||||
* 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;
|
||||
|
||||
static mp_buf *mp_buf_new(void) {
|
||||
mp_buf *buf = malloc(sizeof(*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;
|
||||
buf->b = NULL;
|
||||
buf->len = buf->free = 0;
|
||||
return buf;
|
||||
@@ -79,7 +121,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 = realloc(buf->b,newlen*2);
|
||||
buf->b = (unsigned char*)mp_realloc(buf->L, buf->b, buf->len, newlen*2);
|
||||
buf->free = newlen;
|
||||
}
|
||||
memcpy(buf->b+buf->len,s,len);
|
||||
@@ -88,11 +130,11 @@ void mp_buf_append(mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
}
|
||||
|
||||
void mp_buf_free(mp_buf *buf) {
|
||||
free(buf->b);
|
||||
free(buf);
|
||||
mp_realloc(buf->L, buf->b, buf->len, 0); /* realloc to 0 = free */
|
||||
mp_realloc(buf->L, buf, sizeof(*buf), 0);
|
||||
}
|
||||
|
||||
/* ------------------------------ 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
|
||||
@@ -102,7 +144,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 the opereation. */
|
||||
#define MP_CUR_ERROR_EOF 1 /* Not enough data to complete operation. */
|
||||
#define MP_CUR_ERROR_BADFMT 2 /* Bad data format */
|
||||
|
||||
typedef struct mp_cur {
|
||||
@@ -111,22 +153,15 @@ typedef struct mp_cur {
|
||||
int err;
|
||||
} mp_cur;
|
||||
|
||||
static mp_cur *mp_cur_new(const unsigned char *s, size_t len) {
|
||||
mp_cur *cursor = malloc(sizeof(*cursor));
|
||||
|
||||
void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) {
|
||||
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 { \
|
||||
@@ -136,15 +171,19 @@ static void mp_cur_free(mp_cur *cursor) {
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* --------------------------- Low level MP encoding -------------------------- */
|
||||
/* ------------------------- Low level MP encoding -------------------------- */
|
||||
|
||||
static void mp_encode_bytes(mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
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;
|
||||
@@ -163,7 +202,7 @@ static 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. */
|
||||
static void mp_encode_double(mp_buf *buf, double d) {
|
||||
void mp_encode_double(mp_buf *buf, double d) {
|
||||
unsigned char b[9];
|
||||
float f = d;
|
||||
|
||||
@@ -181,7 +220,7 @@ static void mp_encode_double(mp_buf *buf, double d) {
|
||||
}
|
||||
}
|
||||
|
||||
static void mp_encode_int(mp_buf *buf, int64_t n) {
|
||||
void mp_encode_int(mp_buf *buf, int64_t n) {
|
||||
unsigned char b[9];
|
||||
int enclen;
|
||||
|
||||
@@ -219,7 +258,7 @@ static void mp_encode_int(mp_buf *buf, int64_t n) {
|
||||
}
|
||||
} else {
|
||||
if (n >= -32) {
|
||||
b[0] = ((char)n); /* negative fixnum */
|
||||
b[0] = ((signed char)n); /* negative fixnum */
|
||||
enclen = 1;
|
||||
} else if (n >= -128) {
|
||||
b[0] = 0xd0; /* int 8 */
|
||||
@@ -253,7 +292,7 @@ static void mp_encode_int(mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(buf,b,enclen);
|
||||
}
|
||||
|
||||
static void mp_encode_array(mp_buf *buf, int64_t n) {
|
||||
void mp_encode_array(mp_buf *buf, int64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
|
||||
@@ -276,7 +315,7 @@ static void mp_encode_array(mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(buf,b,enclen);
|
||||
}
|
||||
|
||||
static void mp_encode_map(mp_buf *buf, int64_t n) {
|
||||
void mp_encode_map(mp_buf *buf, int64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
|
||||
@@ -299,9 +338,9 @@ static void mp_encode_map(mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(buf,b,enclen);
|
||||
}
|
||||
|
||||
/* ----------------------------- Lua types encoding --------------------------- */
|
||||
/* --------------------------- Lua types encoding --------------------------- */
|
||||
|
||||
static void mp_encode_lua_string(lua_State *L, mp_buf *buf) {
|
||||
void mp_encode_lua_string(lua_State *L, mp_buf *buf) {
|
||||
size_t len;
|
||||
const char *s;
|
||||
|
||||
@@ -309,26 +348,43 @@ static void mp_encode_lua_string(lua_State *L, mp_buf *buf) {
|
||||
mp_encode_bytes(buf,(const unsigned char*)s,len);
|
||||
}
|
||||
|
||||
static void mp_encode_lua_bool(lua_State *L, mp_buf *buf) {
|
||||
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);
|
||||
}
|
||||
|
||||
static void mp_encode_lua_number(lua_State *L, mp_buf *buf) {
|
||||
/* 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) {
|
||||
lua_Number n = lua_tonumber(L,-1);
|
||||
|
||||
if (floor(n) != n) {
|
||||
mp_encode_double(buf,(double)n);
|
||||
if (IS_INT64_EQUIVALENT(n)) {
|
||||
mp_encode_lua_integer(L, buf);
|
||||
} else {
|
||||
mp_encode_int(buf,(int64_t)n);
|
||||
mp_encode_double(buf,(double)n);
|
||||
}
|
||||
}
|
||||
|
||||
static void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level);
|
||||
void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level);
|
||||
|
||||
/* Convert a lua table into a message pack list. */
|
||||
static void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) {
|
||||
void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) {
|
||||
#if LUA_VERSION_NUM < 502
|
||||
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++) {
|
||||
@@ -339,13 +395,13 @@ static 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. */
|
||||
static void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) {
|
||||
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 hachish for this lib. */
|
||||
* map opcodes for message pack. Too hackish for this lib. */
|
||||
lua_pushnil(L);
|
||||
while(lua_next(L,-2)) {
|
||||
lua_pop(L,1); /* remove value, keep key for next iteration. */
|
||||
@@ -366,80 +422,131 @@ static 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. */
|
||||
static int table_is_an_array(lua_State *L) {
|
||||
long count = 0, max = 0, idx = 0;
|
||||
int table_is_an_array(lua_State *L) {
|
||||
int count = 0, max = 0;
|
||||
#if LUA_VERSION_NUM < 503
|
||||
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 */
|
||||
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;
|
||||
/* 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);
|
||||
count++;
|
||||
max = idx;
|
||||
}
|
||||
/* We have the total number of elements in "count". Also we have
|
||||
* the max index encountered in "idx". We can't reach this code
|
||||
* the max index encountered in "max". 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 idx==count you are sure
|
||||
* repeated keys into a table, you have that if max==count you are sure
|
||||
* that there are all the keys form 1 to count (both included). */
|
||||
return idx == count;
|
||||
|
||||
not_array:
|
||||
lua_pop(L,1);
|
||||
return 0;
|
||||
lua_settop(L, stacktop);
|
||||
return max == count;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
static void mp_encode_lua_table(lua_State *L, mp_buf *buf, int level) {
|
||||
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);
|
||||
}
|
||||
|
||||
static void mp_encode_lua_null(lua_State *L, mp_buf *buf) {
|
||||
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);
|
||||
}
|
||||
|
||||
static void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level) {
|
||||
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 specfiied maximum depth, so that
|
||||
/* Limit the encoding of nested tables to a specified 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: mp_encode_lua_number(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_TTABLE: mp_encode_lua_table(L,buf,level); break;
|
||||
default: mp_encode_lua_null(L,buf); break;
|
||||
}
|
||||
lua_pop(L,1);
|
||||
}
|
||||
|
||||
static int mp_pack(lua_State *L) {
|
||||
mp_buf *buf = mp_buf_new();
|
||||
/*
|
||||
* 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;
|
||||
|
||||
mp_encode_lua_type(L,buf,0);
|
||||
lua_pushlstring(L,(char*)buf->b,buf->len);
|
||||
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_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);
|
||||
@@ -452,6 +559,7 @@ 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 */
|
||||
@@ -466,34 +574,44 @@ 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_pushnumber(L,c->p[1]);
|
||||
lua_pushunsigned(L,c->p[1]);
|
||||
mp_cur_consume(c,2);
|
||||
break;
|
||||
case 0xd0: /* int 8 */
|
||||
mp_cur_need(c,2);
|
||||
lua_pushnumber(L,(char)c->p[1]);
|
||||
lua_pushinteger(L,(signed char)c->p[1]);
|
||||
mp_cur_consume(c,2);
|
||||
break;
|
||||
case 0xcd: /* uint 16 */
|
||||
mp_cur_need(c,3);
|
||||
lua_pushnumber(L,
|
||||
lua_pushunsigned(L,
|
||||
(c->p[1] << 8) |
|
||||
c->p[2]);
|
||||
mp_cur_consume(c,3);
|
||||
break;
|
||||
case 0xd1: /* int 16 */
|
||||
mp_cur_need(c,3);
|
||||
lua_pushnumber(L,(int16_t)
|
||||
lua_pushinteger(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_pushnumber(L,
|
||||
lua_pushunsigned(L,
|
||||
((uint32_t)c->p[1] << 24) |
|
||||
((uint32_t)c->p[2] << 16) |
|
||||
((uint32_t)c->p[3] << 8) |
|
||||
@@ -502,7 +620,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
break;
|
||||
case 0xd2: /* int 32 */
|
||||
mp_cur_need(c,5);
|
||||
lua_pushnumber(L,
|
||||
lua_pushinteger(L,
|
||||
((int32_t)c->p[1] << 24) |
|
||||
((int32_t)c->p[2] << 16) |
|
||||
((int32_t)c->p[3] << 8) |
|
||||
@@ -511,7 +629,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
break;
|
||||
case 0xcf: /* uint 64 */
|
||||
mp_cur_need(c,9);
|
||||
lua_pushnumber(L,
|
||||
lua_pushunsigned(L,
|
||||
((uint64_t)c->p[1] << 56) |
|
||||
((uint64_t)c->p[2] << 48) |
|
||||
((uint64_t)c->p[3] << 40) |
|
||||
@@ -524,7 +642,11 @@ 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) |
|
||||
@@ -569,6 +691,15 @@ 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);
|
||||
{
|
||||
@@ -581,13 +712,14 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
case 0xdb: /* raw 32 */
|
||||
mp_cur_need(c,5);
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
break;
|
||||
case 0xdc: /* array 16 */
|
||||
@@ -601,10 +733,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 = (c->p[1] << 24) |
|
||||
(c->p[2] << 16) |
|
||||
(c->p[3] << 8) |
|
||||
c->p[4];
|
||||
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_decode_to_lua_array(L,c,l);
|
||||
}
|
||||
@@ -620,20 +752,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 = (c->p[1] << 24) |
|
||||
(c->p[2] << 16) |
|
||||
(c->p[3] << 8) |
|
||||
c->p[4];
|
||||
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_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_pushnumber(L,c->p[0]);
|
||||
lua_pushunsigned(L,c->p[0]);
|
||||
mp_cur_consume(c,1);
|
||||
} else if ((c->p[0] & 0xe0) == 0xe0) { /* negative fixnum */
|
||||
lua_pushnumber(L,(signed char)c->p[0]);
|
||||
lua_pushinteger(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;
|
||||
@@ -654,54 +786,163 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
}
|
||||
}
|
||||
|
||||
static int mp_unpack(lua_State *L) {
|
||||
int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
size_t len;
|
||||
const unsigned char *s;
|
||||
mp_cur *c;
|
||||
const char *s;
|
||||
mp_cur c;
|
||||
int cnt; /* Number of objects unpacked */
|
||||
int decode_all = (!limit && !offset);
|
||||
|
||||
if (!lua_isstring(L,-1)) {
|
||||
lua_pushstring(L,"MessagePack decoding needs a string as input.");
|
||||
lua_error(L);
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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 */
|
||||
}
|
||||
mp_cur_free(c);
|
||||
return 1;
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------- */
|
||||
int mp_unpack(lua_State *L) {
|
||||
return mp_unpack_full(L, 0, 0);
|
||||
}
|
||||
|
||||
static const struct luaL_reg thislib[] = {
|
||||
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[] = {
|
||||
{"pack", mp_pack},
|
||||
{"unpack", mp_unpack},
|
||||
{NULL, NULL}
|
||||
{"unpack_one", mp_unpack_one},
|
||||
{"unpack_limit", mp_unpack_limit},
|
||||
{0}
|
||||
};
|
||||
|
||||
LUALIB_API int luaopen_cmsgpack (lua_State *L) {
|
||||
luaL_register(L, "cmsgpack", thislib);
|
||||
int luaopen_create(lua_State *L) {
|
||||
int i;
|
||||
/* Manually construct our module table instead of
|
||||
* relying on _register or _newlib */
|
||||
lua_newtable(L);
|
||||
|
||||
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");
|
||||
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
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -1,6 +1,6 @@
|
||||
/* strbuf - string buffer routines
|
||||
/* strbuf - String buffer routines
|
||||
*
|
||||
* Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au>
|
||||
* Copyright (c) 2010-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
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
#include "strbuf.h"
|
||||
|
||||
void die(const char *fmt, ...)
|
||||
static void die(const char *fmt, ...)
|
||||
{
|
||||
va_list arg;
|
||||
|
||||
|
||||
Vendored
+14
-2
@@ -1,6 +1,6 @@
|
||||
/* strbuf - String buffer routines
|
||||
*
|
||||
* Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au>
|
||||
* Copyright (c) 2010-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
|
||||
@@ -62,7 +62,9 @@ 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 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);
|
||||
|
||||
/* Update */
|
||||
extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...);
|
||||
@@ -96,6 +98,16 @@ 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;
|
||||
|
||||
+247
-63
@@ -15,7 +15,7 @@
|
||||
################################## INCLUDES ###################################
|
||||
|
||||
# Include one or more other config files here. This is useful if you
|
||||
# have a standard template that goes to all Redis server but also need
|
||||
# have a standard template that goes to all Redis servers but also need
|
||||
# to customize a few per-server settings. Include files can include
|
||||
# other files, so use this wisely.
|
||||
#
|
||||
@@ -30,17 +30,32 @@
|
||||
# include /path/to/local.conf
|
||||
# include /path/to/other.conf
|
||||
|
||||
################################ GENERAL #####################################
|
||||
################################## NETWORK #####################################
|
||||
|
||||
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
|
||||
daemonize no
|
||||
# By default, if no "bind" configuration directive is specified, Redis listens
|
||||
# for connections from all the network interfaces available on the server.
|
||||
# It is possible to listen to just one or multiple selected interfaces using
|
||||
# the "bind" configuration directive, followed by one or more IP addresses.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# bind 192.168.1.100 10.0.0.1
|
||||
# bind 127.0.0.1 ::1
|
||||
#
|
||||
# ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the
|
||||
# internet, binding to all the interfaces is dangerous and will expose the
|
||||
# instance to everybody on the internet. So by default we uncomment the
|
||||
# following bind directive, that will force Redis to listen only into
|
||||
# the IPv4 lookback interface address (this means Redis will be able to
|
||||
# accept connections only from clients running into the same computer it
|
||||
# is running).
|
||||
#
|
||||
# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
|
||||
# JUST UNCOMMENT THE FOLLOWING LINE.
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
bind 127.0.0.1
|
||||
|
||||
# When running daemonized, Redis writes a pid file in /var/run/redis.pid by
|
||||
# default. You can specify a custom pid file location here.
|
||||
pidfile /var/run/redis.pid
|
||||
|
||||
# Accept connections on the specified port, default is 6379.
|
||||
# Accept connections on the specified port, default is 6379 (IANA #815344).
|
||||
# If port 0 is specified Redis will not listen on a TCP socket.
|
||||
port 6379
|
||||
|
||||
@@ -53,22 +68,14 @@ port 6379
|
||||
# in order to get the desired effect.
|
||||
tcp-backlog 511
|
||||
|
||||
# By default Redis listens for connections from all the network interfaces
|
||||
# available on the server. It is possible to listen to just one or multiple
|
||||
# interfaces using the "bind" configuration directive, followed by one or
|
||||
# more IP addresses.
|
||||
# Unix socket.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# bind 192.168.1.100 10.0.0.1
|
||||
# bind 127.0.0.1
|
||||
|
||||
# Specify the path for the Unix socket that will be used to listen for
|
||||
# incoming connections. There is no default, so Redis will not listen
|
||||
# on a unix socket when not specified.
|
||||
#
|
||||
# unixsocket /tmp/redis.sock
|
||||
# unixsocketperm 755
|
||||
# unixsocketperm 700
|
||||
|
||||
# Close the connection after a client is idle for N seconds (0 to disable)
|
||||
timeout 0
|
||||
@@ -89,6 +96,34 @@ timeout 0
|
||||
# A reasonable value for this option is 60 seconds.
|
||||
tcp-keepalive 0
|
||||
|
||||
################################# GENERAL #####################################
|
||||
|
||||
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
||||
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
|
||||
daemonize no
|
||||
|
||||
# If you run Redis from upstart or systemd, Redis can interact with your
|
||||
# supervision tree. Options:
|
||||
# supervised no - no supervision interaction
|
||||
# supervised upstart - signal upstart by putting Redis into SIGSTOP mode
|
||||
# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
|
||||
# supervised auto - detect upstart or systemd method based on
|
||||
# UPSTART_JOB or NOTIFY_SOCKET environment variables
|
||||
# Note: these supervision methods only signal "process is ready."
|
||||
# They do not enable continuous liveness pings back to your supervisor.
|
||||
supervised no
|
||||
|
||||
# If a pid file is specified, Redis writes it where specified at startup
|
||||
# and removes it at exit.
|
||||
#
|
||||
# When the server runs non daemonized, no pid file is created if none is
|
||||
# specified in the configuration. When the server is daemonized, the pid file
|
||||
# is used even if not specified, defaulting to "/var/run/redis.pid".
|
||||
#
|
||||
# Creating a pid file is best effort: if Redis is not able to create it
|
||||
# nothing bad happens, the server will start and run normally.
|
||||
pidfile /var/run/redis.pid
|
||||
|
||||
# Specify the server verbosity level.
|
||||
# This can be one of:
|
||||
# debug (a lot of information, useful for development/testing)
|
||||
@@ -131,7 +166,7 @@ databases 16
|
||||
# after 300 sec (5 min) if at least 10 keys changed
|
||||
# after 60 sec if at least 10000 keys changed
|
||||
#
|
||||
# Note: you can disable saving at all commenting all the "save" lines.
|
||||
# Note: you can disable saving completely by commenting out all "save" lines.
|
||||
#
|
||||
# It is also possible to remove all the previously configured save
|
||||
# points by adding a save directive with a single empty string argument
|
||||
@@ -180,18 +215,27 @@ dbfilename dump.rdb
|
||||
#
|
||||
# The DB will be written inside this directory, with the filename specified
|
||||
# above using the 'dbfilename' configuration directive.
|
||||
#
|
||||
#
|
||||
# The Append Only File will also be created inside this directory.
|
||||
#
|
||||
#
|
||||
# Note that you must specify a directory here, not a file name.
|
||||
dir ./
|
||||
|
||||
################################# REPLICATION #################################
|
||||
|
||||
# Master-Slave replication. Use slaveof to make a Redis instance a copy of
|
||||
# another Redis server. Note that the configuration is local to the slave
|
||||
# so for example it is possible to configure the slave to save the DB with a
|
||||
# different interval, or to listen to another port, and so on.
|
||||
# another Redis server. A few things to understand ASAP about Redis replication.
|
||||
#
|
||||
# 1) Redis replication is asynchronous, but you can configure a master to
|
||||
# stop accepting writes if it appears to be not connected with at least
|
||||
# a given number of slaves.
|
||||
# 2) Redis slaves are able to perform a partial resynchronization with the
|
||||
# master if the replication link is lost for a relatively small amount of
|
||||
# time. You may want to configure the replication backlog size (see the next
|
||||
# sections of this file) with a sensible value depending on your needs.
|
||||
# 3) Replication is automatic and does not need user intervention. After a
|
||||
# network partition slaves automatically try to reconnect to masters
|
||||
# and resynchronize with them.
|
||||
#
|
||||
# slaveof <masterip> <masterport>
|
||||
|
||||
@@ -231,6 +275,79 @@ slave-serve-stale-data yes
|
||||
# administrative / dangerous commands.
|
||||
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.
|
||||
# The transmission can happen in two different ways:
|
||||
#
|
||||
# 1) Disk-backed: The Redis master creates a new process that writes the RDB
|
||||
# file on disk. Later the file is transferred by the parent
|
||||
# process to the slaves incrementally.
|
||||
# 2) Diskless: The Redis master creates a new process that directly writes the
|
||||
# RDB file to slave sockets, without touching the disk at all.
|
||||
#
|
||||
# With disk-backed replication, while the RDB file is generated, more slaves
|
||||
# can be queued and served with the RDB file as soon as the current child producing
|
||||
# the RDB file finishes its work. With diskless replication instead once
|
||||
# the transfer starts, new slaves arriving will be queued and a new transfer
|
||||
# will start when the current one terminates.
|
||||
#
|
||||
# When diskless replication is used, the master waits a configurable amount of
|
||||
# time (in seconds) before starting the transfer in the hope that multiple slaves
|
||||
# will arrive and the transfer can be parallelized.
|
||||
#
|
||||
# With slow disks and fast (large bandwidth) networks, diskless replication
|
||||
# works better.
|
||||
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
|
||||
# to the slaves.
|
||||
#
|
||||
# This is important since once the transfer starts, it is not possible to serve
|
||||
# new slaves arriving, that will be queued for the next RDB transfer, so the server
|
||||
# waits a delay in order to let more slaves arrive.
|
||||
#
|
||||
# The delay is specified in seconds, and by default is 5 seconds. To disable
|
||||
# it entirely just set it to 0 seconds and the transfer will start ASAP.
|
||||
repl-diskless-sync-delay 5
|
||||
|
||||
# Enable diskless replication on slave side.
|
||||
#
|
||||
# When this option is on, the slave loads the RDB directly from the socket
|
||||
# rather than saving it to disk first. However there are data loss risks
|
||||
# associated with this feature, so make sure to read the following WARNING
|
||||
# section.
|
||||
#
|
||||
# WARNING: Note that this means that the dataset in the slave gets flushed
|
||||
# before the slave is actually sure the RDB transfer is complete, so if the
|
||||
# replication link is disconnected after the slave already flushed away its
|
||||
# dataset, but before successfully loading the new one, the slave will
|
||||
# remain empty (for all the time needed to attempt a new synchornization with
|
||||
# the master).
|
||||
#
|
||||
# This means that you should carefully consider the effects of this feature
|
||||
# on slaves that may be promoted to masters:
|
||||
#
|
||||
# 1) Sentinel checks the disconnection time and the offset of slaves before
|
||||
# promotion. However it is possible that after the check, the slave
|
||||
# attempts to connect with the master again and flushes its dataset.
|
||||
# In order to run Sentinel safely in this setup, make sure to enable
|
||||
# the "slave-protected-restart" option.
|
||||
#
|
||||
# 2) Redis Cluster slaves will refuse to try to be promoted to masters if
|
||||
# if the dataset was flushed, so this is safe in the context of Redis Cluster.
|
||||
#
|
||||
# 3) If you are using your own HA setup, make sure to enable slave
|
||||
# "slave-protected-restart".
|
||||
repl-diskless-load no
|
||||
|
||||
# Slaves send PINGs to server in a predefined interval. It's possible to change
|
||||
# this interval with the repl_ping_slave_period option. The default value is 10
|
||||
# seconds.
|
||||
@@ -270,7 +387,7 @@ repl-disable-tcp-nodelay no
|
||||
# resync is enough, just passing the portion of data the slave missed while
|
||||
# disconnected.
|
||||
#
|
||||
# The biggest the replication backlog, the longer the time the slave can be
|
||||
# The bigger the replication backlog, the longer the time the slave can be
|
||||
# disconnected and later be able to perform a partial resynchronization.
|
||||
#
|
||||
# The backlog is only allocated once there is at least a slave connected.
|
||||
@@ -309,7 +426,7 @@ slave-priority 100
|
||||
# The lag in seconds, that must be <= the specified value, is calculated from
|
||||
# the last ping received from the slave, that is usually sent every second.
|
||||
#
|
||||
# This option does not GUARANTEES that N replicas will accept the write, but
|
||||
# This option does not GUARANTEE that N replicas will accept the write, but
|
||||
# will limit the window of exposure for lost writes in case not enough slaves
|
||||
# are available, to the specified number of seconds.
|
||||
#
|
||||
@@ -331,7 +448,7 @@ slave-priority 100
|
||||
#
|
||||
# This should stay commented out for backward compatibility and because most
|
||||
# people do not need auth (e.g. they run their own servers).
|
||||
#
|
||||
#
|
||||
# Warning: since Redis is pretty fast an outside user can try up to
|
||||
# 150k passwords per second against a good box. This means that you should
|
||||
# use a very strong password otherwise it will be very easy to break.
|
||||
@@ -397,18 +514,18 @@ slave-priority 100
|
||||
|
||||
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
|
||||
# is reached. You can select among five behaviors:
|
||||
#
|
||||
#
|
||||
# volatile-lru -> remove the key with an expire set using an LRU algorithm
|
||||
# allkeys-lru -> remove any key accordingly to the LRU algorithm
|
||||
# allkeys-lru -> remove any key according to the LRU algorithm
|
||||
# volatile-random -> remove a random key with an expire set
|
||||
# allkeys-random -> remove a random key, any key
|
||||
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
|
||||
# noeviction -> don't expire at all, just return an error on write operations
|
||||
#
|
||||
# Note: with any of the above policies, Redis will return an error on write
|
||||
# operations, when there are not suitable keys for eviction.
|
||||
#
|
||||
# At the date of writing this commands are: set setnx setex append
|
||||
# Note: with any of the above policies, Redis will return an error on write
|
||||
# operations, when there are no suitable keys for eviction.
|
||||
#
|
||||
# At the date of writing these commands are: set setnx setex append
|
||||
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
|
||||
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
|
||||
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
|
||||
@@ -456,13 +573,13 @@ appendonly no
|
||||
appendfilename "appendonly.aof"
|
||||
|
||||
# The fsync() call tells the Operating System to actually write data on disk
|
||||
# instead to wait for more data in the output buffer. Some OS will really flush
|
||||
# instead of waiting for more data in the output buffer. Some OS will really flush
|
||||
# data on disk, some other OS will just try to do it ASAP.
|
||||
#
|
||||
# Redis supports three different modes:
|
||||
#
|
||||
# no: don't fsync, just let the OS flush the data when it wants. Faster.
|
||||
# always: fsync after every write to the append only log . Slow, Safest.
|
||||
# always: fsync after every write to the append only log. Slow, Safest.
|
||||
# everysec: fsync only one time every second. Compromise.
|
||||
#
|
||||
# The default is "everysec", as that's usually the right compromise between
|
||||
@@ -497,7 +614,7 @@ appendfsync everysec
|
||||
# the same as "appendfsync none". In practical terms, this means that it is
|
||||
# possible to lose up to 30 seconds of log in the worst scenario (with the
|
||||
# default Linux settings).
|
||||
#
|
||||
#
|
||||
# If you have latency problems turn this to "yes". Otherwise leave it as
|
||||
# "no" that is the safest pick from the point of view of durability.
|
||||
|
||||
@@ -506,7 +623,7 @@ no-appendfsync-on-rewrite no
|
||||
# Automatic rewrite of the append only file.
|
||||
# Redis is able to automatically rewrite the log file implicitly calling
|
||||
# BGREWRITEAOF when the AOF log size grows by the specified percentage.
|
||||
#
|
||||
#
|
||||
# This is how it works: Redis remembers the size of the AOF file after the
|
||||
# latest rewrite (if no rewrite has happened since the restart, the size of
|
||||
# the AOF at startup is used).
|
||||
@@ -523,6 +640,30 @@ no-appendfsync-on-rewrite no
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# An AOF file may be found to be truncated at the end during the Redis
|
||||
# startup process, when the AOF data gets loaded back into memory.
|
||||
# This may happen when the system where Redis is running
|
||||
# crashes, especially when an ext4 filesystem is mounted without the
|
||||
# data=ordered option (however this can't happen when Redis itself
|
||||
# crashes or aborts but the operating system still works correctly).
|
||||
#
|
||||
# Redis can either exit with an error when this happens, or load as much
|
||||
# data as possible (the default now) and start if the AOF file is found
|
||||
# to be truncated at the end. The following option controls this behavior.
|
||||
#
|
||||
# If aof-load-truncated is set to yes, a truncated AOF file is loaded and
|
||||
# the Redis server starts emitting a log to inform the user of the event.
|
||||
# Otherwise if the option is set to no, the server aborts with an error
|
||||
# and refuses to start. When the option is set to no, the user requires
|
||||
# to fix the AOF file using the "redis-check-aof" utility before to restart
|
||||
# the server.
|
||||
#
|
||||
# Note that if the AOF file will be found to be corrupted in the middle
|
||||
# the server will still exit with an error. This option only applies when
|
||||
# Redis will try to read more data from the AOF file but not enough bytes
|
||||
# will be found.
|
||||
aof-load-truncated yes
|
||||
|
||||
################################ LUA SCRIPTING ###############################
|
||||
|
||||
# Max execution time of a Lua script in milliseconds.
|
||||
@@ -531,11 +672,11 @@ auto-aof-rewrite-min-size 64mb
|
||||
# still in execution after the maximum allowed time and will start to
|
||||
# reply to queries with an error.
|
||||
#
|
||||
# When a long running script exceed the maximum execution time only the
|
||||
# When a long running script exceeds the maximum execution time only the
|
||||
# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be
|
||||
# used to stop a script that did not yet called write commands. The second
|
||||
# is the only way to shut down the server in the case a write commands was
|
||||
# already issue by the script but the user don't want to wait for the natural
|
||||
# is the only way to shut down the server in the case a write command was
|
||||
# already issued by the script but the user doesn't want to wait for the natural
|
||||
# termination of the script.
|
||||
#
|
||||
# Set it to 0 or a negative value for unlimited execution without warnings.
|
||||
@@ -543,6 +684,12 @@ 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:
|
||||
@@ -552,12 +699,12 @@ lua-time-limit 5000
|
||||
# Every cluster node has a cluster configuration file. This file is not
|
||||
# intended to be edited by hand. It is created and updated by Redis nodes.
|
||||
# Every Redis Cluster node requires a different cluster configuration file.
|
||||
# Make sure that instances running in the same system does not have
|
||||
# Make sure that instances running in the same system do not have
|
||||
# overlapping cluster configuration file names.
|
||||
#
|
||||
# cluster-config-file nodes-6379.conf
|
||||
|
||||
# Cluster node timeout is the amount of milliseconds a node must be unreachable
|
||||
# Cluster node timeout is the amount of milliseconds a node must be unreachable
|
||||
# for it to be considered in failure state.
|
||||
# Most other internal time limits are multiple of the node timeout.
|
||||
#
|
||||
@@ -627,6 +774,19 @@ lua-time-limit 5000
|
||||
#
|
||||
# cluster-migration-barrier 1
|
||||
|
||||
# By default Redis Cluster nodes stop accepting queries if they detect there
|
||||
# is at least an hash slot uncovered (no available node is serving it).
|
||||
# This way if the cluster is partially down (for example a range of hash slots
|
||||
# are no longer covered) all the cluster becomes, eventually, unavailable.
|
||||
# It automatically returns available as soon as all the slots are covered again.
|
||||
#
|
||||
# However sometimes you want the subset of the cluster which is working,
|
||||
# to continue to accept queries for the part of the key space that is still
|
||||
# covered. In order to do so, just set the cluster-require-full-coverage
|
||||
# option to no.
|
||||
#
|
||||
# cluster-require-full-coverage yes
|
||||
|
||||
# In order to setup your cluster make sure to read the documentation
|
||||
# available at http://redis.io web site.
|
||||
|
||||
@@ -638,7 +798,7 @@ lua-time-limit 5000
|
||||
# but just the time needed to actually execute the command (this is the only
|
||||
# stage of command execution where the thread is blocked and can not serve
|
||||
# other requests in the meantime).
|
||||
#
|
||||
#
|
||||
# You can configure the slow log with two parameters: one tells Redis
|
||||
# what is the execution time, in microseconds, to exceed in order for the
|
||||
# command to get logged, and the other parameter is the length of the
|
||||
@@ -671,15 +831,15 @@ 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 enalbed at runtime using the command
|
||||
# monitoring can easily be enabled 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/keyspace-events
|
||||
#
|
||||
# This feature is documented at http://redis.io/topics/notifications
|
||||
#
|
||||
# For instance if keyspace events notification is enabled, and a client
|
||||
# performs a DEL operation on key "foo" stored in the Database 0, two
|
||||
# messages will be published via Pub/Sub:
|
||||
@@ -703,8 +863,8 @@ latency-monitor-threshold 0
|
||||
# A Alias for g$lshzxe, so that the "AKE" string means all the events.
|
||||
#
|
||||
# The "notify-keyspace-events" takes as argument a string that is composed
|
||||
# by zero or multiple characters. The empty string means that notifications
|
||||
# are disabled at all.
|
||||
# of zero or multiple characters. The empty string means that notifications
|
||||
# are disabled.
|
||||
#
|
||||
# Example: to enable list and generic events, from the point of view of the
|
||||
# event name, use:
|
||||
@@ -729,14 +889,39 @@ notify-keyspace-events ""
|
||||
hash-max-ziplist-entries 512
|
||||
hash-max-ziplist-value 64
|
||||
|
||||
# Similarly to hashes, small lists are also encoded in a special way in order
|
||||
# to save a lot of space. The special representation is only used when
|
||||
# you are under the following limits:
|
||||
list-max-ziplist-entries 512
|
||||
list-max-ziplist-value 64
|
||||
# Lists are also encoded in a special way to save a lot of space.
|
||||
# The number of entries allowed per internal list node can be specified
|
||||
# as a fixed maximum size or a maximum number of elements.
|
||||
# For a fixed maximum size, use -5 through -1, meaning:
|
||||
# -5: max size: 64 Kb <-- not recommended for normal workloads
|
||||
# -4: max size: 32 Kb <-- not recommended
|
||||
# -3: max size: 16 Kb <-- probably not recommended
|
||||
# -2: max size: 8 Kb <-- good
|
||||
# -1: max size: 4 Kb <-- good
|
||||
# Positive numbers mean store up to _exactly_ that number of elements
|
||||
# per list node.
|
||||
# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),
|
||||
# but if your use case is unique, adjust the settings as necessary.
|
||||
list-max-ziplist-size -2
|
||||
|
||||
# Lists may also be compressed.
|
||||
# Compress depth is the number of quicklist ziplist nodes from *each* side of
|
||||
# the list to *exclude* from compression. The head and tail of the list
|
||||
# are always uncompressed for fast push/pop operations. Settings are:
|
||||
# 0: disable all list compression
|
||||
# 1: depth 1 means "don't start compressing until after 1 node into the list,
|
||||
# going from either the head or tail"
|
||||
# So: [head]->node->node->...->node->[tail]
|
||||
# [head], [tail] will always be uncompressed; inner nodes will compress.
|
||||
# 2: [head]->[next]->node->node->...->node->[prev]->[tail]
|
||||
# 2 here means: don't compress head or head->next or tail->prev or tail,
|
||||
# but compress all nodes between them.
|
||||
# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail]
|
||||
# etc.
|
||||
list-compress-depth 0
|
||||
|
||||
# Sets have a special encoding in just one case: when a set is composed
|
||||
# of just strings that happens to be integers in radix 10 in the range
|
||||
# of just strings that happen to be integers in radix 10 in the range
|
||||
# of 64 bit signed integers.
|
||||
# The following configuration setting sets the limit in the size of the
|
||||
# set in order to use this special memory saving encoding.
|
||||
@@ -754,7 +939,7 @@ zset-max-ziplist-value 64
|
||||
#
|
||||
# A value greater than 16000 is totally useless, since at that point the
|
||||
# dense representation is more memory efficient.
|
||||
#
|
||||
#
|
||||
# The suggested value is ~ 3000 in order to have the benefits of
|
||||
# the space efficient encoding without slowing down too much PFADD,
|
||||
# which is O(N) with the sparse encoding. The value can be raised to
|
||||
@@ -769,13 +954,13 @@ hll-sparse-max-bytes 3000
|
||||
# that is rehashing, the more rehashing "steps" are performed, so if the
|
||||
# server is idle the rehashing is never complete and some more memory is used
|
||||
# by the hash table.
|
||||
#
|
||||
#
|
||||
# The default is to use this millisecond 10 times every second in order to
|
||||
# active rehashing the main dictionaries, freeing memory when possible.
|
||||
# actively rehash the main dictionaries, freeing memory when possible.
|
||||
#
|
||||
# If unsure:
|
||||
# use "activerehashing no" if you have hard latency requirements and it is
|
||||
# not a good thing in your environment that Redis can reply form time to time
|
||||
# not a good thing in your environment that Redis can reply from time to time
|
||||
# to queries with 2 milliseconds delay.
|
||||
#
|
||||
# use "activerehashing yes" if you don't have such hard requirements but
|
||||
@@ -824,7 +1009,7 @@ client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
# never requested, and so forth.
|
||||
#
|
||||
# Not all tasks are performed with the same frequency, but Redis checks for
|
||||
# tasks to perform accordingly to the specified "hz" value.
|
||||
# tasks to perform according to the specified "hz" value.
|
||||
#
|
||||
# By default "hz" is set to 10. Raising the value will use more CPU when
|
||||
# Redis is idle, but at the same time will make Redis more responsive when
|
||||
@@ -841,4 +1026,3 @@ hz 10
|
||||
# in order to commit the file to the disk more incrementally and avoid
|
||||
# big latency spikes.
|
||||
aof-rewrite-incremental-fsync yes
|
||||
|
||||
|
||||
+23
-1
@@ -4,10 +4,32 @@
|
||||
# The port that this sentinel instance will run on
|
||||
port 26379
|
||||
|
||||
# sentinel announce-ip <ip>
|
||||
# sentinel announce-port <port>
|
||||
#
|
||||
# The above two configuration directives are useful in environments where,
|
||||
# because of NAT, Sentinel is reachable from outside via a non-local address.
|
||||
#
|
||||
# When announce-ip is provided, the Sentinel will claim the specified IP address
|
||||
# in HELLO messages used to gossip its presence, instead of auto-detecting the
|
||||
# local address as it usually does.
|
||||
#
|
||||
# Similarly when announce-port is provided and is valid and non-zero, Sentinel
|
||||
# will announce the specified TCP port.
|
||||
#
|
||||
# The two options don't need to be used together, if only announce-ip is
|
||||
# provided, the Sentinel will announce the specified IP and the server port
|
||||
# as specified by the "port" option. If only announce-port is provided, the
|
||||
# Sentinel will announce the auto-detected local IP and the specified port.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# sentinel announce-ip 1.2.3.4
|
||||
|
||||
# dir <working-directory>
|
||||
# Every long running process should have a well-defined working directory.
|
||||
# For Redis Sentinel to chdir to /tmp at startup is the simplest thing
|
||||
# for the process to don't interferer with administrative tasks such as
|
||||
# for the process to don't interfere with administrative tasks such as
|
||||
# unmounting filesystems.
|
||||
dir /tmp
|
||||
|
||||
|
||||
+33
-18
@@ -15,11 +15,11 @@
|
||||
release_hdr := $(shell sh -c './mkreleasehdr.sh')
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
OPTIMIZATION?=-O2
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua geohash-int
|
||||
|
||||
# Default settings
|
||||
STD=-std=c99 -pedantic
|
||||
WARN=-Wall
|
||||
STD=-std=c99 -pedantic -DREDIS_STATIC=''
|
||||
WARN=-Wall -W
|
||||
OPT=$(OPTIMIZATION)
|
||||
|
||||
PREFIX?=/usr/local
|
||||
@@ -46,10 +46,14 @@ ifeq ($(USE_JEMALLOC),yes)
|
||||
MALLOC=jemalloc
|
||||
endif
|
||||
|
||||
ifeq ($(USE_JEMALLOC),no)
|
||||
MALLOC=libc
|
||||
endif
|
||||
|
||||
# Override default settings if possible
|
||||
-include .make-settings
|
||||
|
||||
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS) -I../deps/geohash-int
|
||||
FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LIBS=-lm
|
||||
DEBUG=-g -ggdb
|
||||
@@ -58,17 +62,23 @@ ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
INSTALL=cp -pf
|
||||
FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lpthread
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lresolv -lpthread -lrt
|
||||
else
|
||||
ifeq ($(uname_S),Darwin)
|
||||
# Darwin (nothing to do)
|
||||
else
|
||||
ifeq ($(uname_S),AIX)
|
||||
# AIX
|
||||
FINAL_LDFLAGS+= -Wl,-bexpall
|
||||
FINAL_LIBS+= -pthread -lcrypt -lbsd
|
||||
|
||||
else
|
||||
# All the other OSes (notably Linux)
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
FINAL_LIBS+= -pthread
|
||||
endif
|
||||
endif
|
||||
|
||||
endif
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
|
||||
|
||||
@@ -107,19 +117,19 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o geo.o
|
||||
REDIS_GEOHASH_OBJ=../deps/geohash-int/geohash.o ../deps/geohash-int/geohash_helper.o
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o redis-benchmark.o
|
||||
REDIS_CHECK_DUMP_NAME=redis-check-dump
|
||||
REDIS_CHECK_DUMP_OBJ=redis-check-dump.o lzf_c.o lzf_d.o crc64.o
|
||||
REDIS_CHECK_RDB_NAME=redis-check-rdb
|
||||
REDIS_CHECK_AOF_NAME=redis-check-aof
|
||||
REDIS_CHECK_AOF_OBJ=redis-check-aof.o
|
||||
|
||||
all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
@echo ""
|
||||
@echo "Hint: To run 'make test' is a good idea ;)"
|
||||
@echo "Hint: It's a good idea to run 'make test' ;)"
|
||||
@echo ""
|
||||
|
||||
.PHONY: all
|
||||
@@ -162,12 +172,16 @@ endif
|
||||
|
||||
# redis-server
|
||||
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a $(FINAL_LIBS)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a $(REDIS_GEOHASH_OBJ) $(FINAL_LIBS)
|
||||
|
||||
# redis-sentinel
|
||||
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
|
||||
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME)
|
||||
|
||||
# redis-check-rdb
|
||||
$(REDIS_CHECK_RDB_NAME): $(REDIS_SERVER_NAME)
|
||||
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_CHECK_RDB_NAME)
|
||||
|
||||
# redis-cli
|
||||
$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS)
|
||||
@@ -176,10 +190,6 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
|
||||
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS)
|
||||
|
||||
# redis-check-dump
|
||||
$(REDIS_CHECK_DUMP_NAME): $(REDIS_CHECK_DUMP_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ $(FINAL_LIBS)
|
||||
|
||||
# redis-check-aof
|
||||
$(REDIS_CHECK_AOF_NAME): $(REDIS_CHECK_AOF_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ $(FINAL_LIBS)
|
||||
@@ -191,7 +201,7 @@ $(REDIS_CHECK_AOF_NAME): $(REDIS_CHECK_AOF_OBJ)
|
||||
$(REDIS_CC) -c $<
|
||||
|
||||
clean:
|
||||
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html
|
||||
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
@@ -215,6 +225,10 @@ lcov:
|
||||
@geninfo -o redis.info .
|
||||
@genhtml --legend -o lcov-html redis.info
|
||||
|
||||
test-sds: sds.c sds.h
|
||||
$(REDIS_CC) sds.c zmalloc.c -DSDS_TEST_MAIN -o /tmp/sds_test
|
||||
/tmp/sds_test
|
||||
|
||||
.PHONY: lcov
|
||||
|
||||
bench: $(REDIS_BENCHMARK_NAME)
|
||||
@@ -243,5 +257,6 @@ install: all
|
||||
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_DUMP_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_RDB_NAME) $(INSTALL_BIN)
|
||||
$(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN)
|
||||
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ void listRelease(list *list)
|
||||
zfree(list);
|
||||
}
|
||||
|
||||
/* Add a new node to the list, to head, contaning the specified 'value'
|
||||
/* Add a new node to the list, to head, containing the specified 'value'
|
||||
* pointer as value.
|
||||
*
|
||||
* On error, NULL is returned and no operation is performed (i.e. the
|
||||
|
||||
@@ -156,8 +156,9 @@ void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
|
||||
{
|
||||
if (fd >= eventLoop->setsize) return;
|
||||
aeFileEvent *fe = &eventLoop->events[fd];
|
||||
|
||||
if (fe->mask == AE_NONE) return;
|
||||
|
||||
aeApiDelEvent(eventLoop, fd, mask);
|
||||
fe->mask = fe->mask & (~mask);
|
||||
if (fd == eventLoop->maxfd && fe->mask == AE_NONE) {
|
||||
/* Update the max fd */
|
||||
@@ -167,7 +168,6 @@ void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
|
||||
if (eventLoop->events[j].mask != AE_NONE) break;
|
||||
eventLoop->maxfd = j;
|
||||
}
|
||||
aeApiDelEvent(eventLoop, fd, mask);
|
||||
}
|
||||
|
||||
int aeGetFileEvents(aeEventLoop *eventLoop, int fd) {
|
||||
|
||||
+123
-20
@@ -34,6 +34,7 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/time.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
@@ -57,24 +58,37 @@ static void anetSetError(char *err, const char *fmt, ...)
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
int anetNonBlock(char *err, int fd)
|
||||
{
|
||||
int anetSetBlock(char *err, int fd, int non_block) {
|
||||
int flags;
|
||||
|
||||
/* Set the socket non-blocking.
|
||||
/* Set the socket blocking (if non_block is zero) or non-blocking.
|
||||
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
|
||||
* interrupted by a signal. */
|
||||
if ((flags = fcntl(fd, F_GETFL)) == -1) {
|
||||
anetSetError(err, "fcntl(F_GETFL): %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||
|
||||
if (non_block)
|
||||
flags |= O_NONBLOCK;
|
||||
else
|
||||
flags &= ~O_NONBLOCK;
|
||||
|
||||
if (fcntl(fd, F_SETFL, flags) == -1) {
|
||||
anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
int anetNonBlock(char *err, int fd) {
|
||||
return anetSetBlock(err,fd,1);
|
||||
}
|
||||
|
||||
int anetBlock(char *err, int fd) {
|
||||
return anetSetBlock(err,fd,0);
|
||||
}
|
||||
|
||||
/* Set TCP keep alive option to detect dead peers. The interval option
|
||||
* is only used for Linux as we are using Linux-specific APIs to set
|
||||
* the probe send time, interval, and count. */
|
||||
@@ -117,6 +131,8 @@ int anetKeepAlive(char *err, int fd, int interval)
|
||||
anetSetError(err, "setsockopt TCP_KEEPCNT: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
#else
|
||||
((void) interval); /* Avoid unused var warning for non Linux systems. */
|
||||
#endif
|
||||
|
||||
return ANET_OK;
|
||||
@@ -163,6 +179,34 @@ int anetTcpKeepAlive(char *err, int fd)
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
/* Set the socket send timeout (SO_SNDTIMEO socket option) to the specified
|
||||
* number of milliseconds, or disable it if the 'ms' argument is zero. */
|
||||
int anetSendTimeout(char *err, int fd, long long ms) {
|
||||
struct timeval tv;
|
||||
|
||||
tv.tv_sec = ms/1000;
|
||||
tv.tv_usec = (ms%1000)*1000;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_SNDTIMEO: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
/* Set the socket receive timeout (SO_RCVTIMEO socket option) to the specified
|
||||
* number of milliseconds, or disable it if the 'ms' argument is zero. */
|
||||
int anetRecvTimeout(char *err, int fd, long long ms) {
|
||||
struct timeval tv;
|
||||
|
||||
tv.tv_sec = ms/1000;
|
||||
tv.tv_usec = (ms%1000)*1000;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_RCVTIMEO: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to
|
||||
* do the actual work. It resolves the hostname "host" and set the string
|
||||
* representation of the IP address into the buffer pointed by "ipbuf".
|
||||
@@ -234,6 +278,7 @@ 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)
|
||||
{
|
||||
@@ -262,9 +307,10 @@ static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
if (source_addr) {
|
||||
int bound = 0;
|
||||
/* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
|
||||
if ((rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)) != 0) {
|
||||
if ((rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)) != 0)
|
||||
{
|
||||
anetSetError(err, "%s", gai_strerror(rv));
|
||||
goto end;
|
||||
goto error;
|
||||
}
|
||||
for (b = bservinfo; b != NULL; b = b->ai_next) {
|
||||
if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
|
||||
@@ -272,9 +318,10 @@ static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
break;
|
||||
}
|
||||
}
|
||||
freeaddrinfo(bservinfo);
|
||||
if (!bound) {
|
||||
anetSetError(err, "bind: %s", strerror(errno));
|
||||
goto end;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
|
||||
@@ -299,9 +346,17 @@ error:
|
||||
close(s);
|
||||
s = ANET_ERR;
|
||||
}
|
||||
|
||||
end:
|
||||
freeaddrinfo(servinfo);
|
||||
return s;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port)
|
||||
@@ -314,9 +369,18 @@ 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);
|
||||
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);
|
||||
}
|
||||
|
||||
int anetUnixGenericConnect(char *err, char *path, int flags)
|
||||
@@ -359,7 +423,7 @@ int anetUnixNonBlockConnect(char *err, char *path)
|
||||
* (unless error or EOF condition is encountered) */
|
||||
int anetRead(int fd, char *buf, int count)
|
||||
{
|
||||
int nread, totlen = 0;
|
||||
ssize_t nread, totlen = 0;
|
||||
while(totlen != count) {
|
||||
nread = read(fd,buf,count-totlen);
|
||||
if (nread == 0) return totlen;
|
||||
@@ -370,11 +434,11 @@ int anetRead(int fd, char *buf, int count)
|
||||
return totlen;
|
||||
}
|
||||
|
||||
/* Like write(2) but make sure 'count' is read before to return
|
||||
/* Like write(2) but make sure 'count' is written before to return
|
||||
* (unless error is encountered) */
|
||||
int anetWrite(int fd, char *buf, int count)
|
||||
{
|
||||
int nwritten, totlen = 0;
|
||||
ssize_t nwritten, totlen = 0;
|
||||
while(totlen != count) {
|
||||
nwritten = write(fd,buf,count-totlen);
|
||||
if (nwritten == 0) return totlen;
|
||||
@@ -525,22 +589,53 @@ int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) {
|
||||
struct sockaddr_storage sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
|
||||
if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) {
|
||||
if (port) *port = 0;
|
||||
ip[0] = '?';
|
||||
ip[1] = '\0';
|
||||
return -1;
|
||||
}
|
||||
if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) goto error;
|
||||
if (ip_len == 0) goto error;
|
||||
|
||||
if (sa.ss_family == AF_INET) {
|
||||
struct sockaddr_in *s = (struct sockaddr_in *)&sa;
|
||||
if (ip) inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len);
|
||||
if (port) *port = ntohs(s->sin_port);
|
||||
} else {
|
||||
} else if (sa.ss_family == AF_INET6) {
|
||||
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa;
|
||||
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
|
||||
if (port) *port = ntohs(s->sin6_port);
|
||||
} else if (sa.ss_family == AF_UNIX) {
|
||||
if (ip) strncpy(ip,"/unixsocket",ip_len);
|
||||
if (port) *port = 0;
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (ip) {
|
||||
if (ip_len >= 2) {
|
||||
ip[0] = '?';
|
||||
ip[1] = '\0';
|
||||
} else if (ip_len == 1) {
|
||||
ip[0] = '\0';
|
||||
}
|
||||
}
|
||||
if (port) *port = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Format an IP,port pair into something easy to parse. If IP is IPv6
|
||||
* (matches for ":"), the ip is surrounded by []. IP and port are just
|
||||
* separated by colons. This the standard to display addresses within Redis. */
|
||||
int anetFormatAddr(char *buf, size_t buf_len, char *ip, int port) {
|
||||
return snprintf(buf,buf_len, strchr(ip,':') ?
|
||||
"[%s]:%d" : "%s:%d", ip, port);
|
||||
}
|
||||
|
||||
/* Like anetFormatAddr() but extract ip and port from the socket's peer. */
|
||||
int anetFormatPeer(int fd, char *buf, size_t buf_len) {
|
||||
char ip[INET6_ADDRSTRLEN];
|
||||
int port;
|
||||
|
||||
anetPeerToString(fd,ip,sizeof(ip),&port);
|
||||
return anetFormatAddr(buf, buf_len, ip, port);
|
||||
}
|
||||
|
||||
int anetSockName(int fd, char *ip, size_t ip_len, int *port) {
|
||||
@@ -564,3 +659,11 @@ int anetSockName(int fd, char *ip, size_t ip_len, int *port) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int anetFormatSock(int fd, char *fmt, size_t fmt_len) {
|
||||
char ip[INET6_ADDRSTRLEN];
|
||||
int port;
|
||||
|
||||
anetSockName(fd,ip,sizeof(ip),&port);
|
||||
return anetFormatAddr(fmt, fmt_len, ip, port);
|
||||
}
|
||||
|
||||
+12
-1
@@ -39,13 +39,18 @@
|
||||
#define ANET_NONE 0
|
||||
#define ANET_IP_ONLY (1<<0)
|
||||
|
||||
#if defined(__sun)
|
||||
#if defined(__sun) || defined(_AIX)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
#ifdef _AIX
|
||||
#undef ip_len
|
||||
#endif
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr);
|
||||
int 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);
|
||||
@@ -58,11 +63,17 @@ int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port)
|
||||
int anetUnixAccept(char *err, int serversock);
|
||||
int anetWrite(int fd, char *buf, int count);
|
||||
int anetNonBlock(char *err, int fd);
|
||||
int anetBlock(char *err, int fd);
|
||||
int anetEnableTcpNoDelay(char *err, int fd);
|
||||
int anetDisableTcpNoDelay(char *err, int fd);
|
||||
int anetTcpKeepAlive(char *err, int fd);
|
||||
int anetSendTimeout(char *err, int fd, long long ms);
|
||||
int anetRecvTimeout(char *err, int fd, long long ms);
|
||||
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port);
|
||||
int anetKeepAlive(char *err, int fd, int interval);
|
||||
int anetSockName(int fd, char *ip, size_t ip_len, int *port);
|
||||
int anetFormatAddr(char *fmt, size_t fmt_len, char *ip, int port);
|
||||
int anetFormatPeer(int fd, char *fmt, size_t fmt_len);
|
||||
int anetFormatSock(int fd, char *fmt, size_t fmt_len);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -74,7 +74,7 @@ void aofRewriteBufferReset(void) {
|
||||
listSetFreeMethod(server.aof_rewrite_buf_blocks,zfree);
|
||||
}
|
||||
|
||||
/* Return the current size of the AOF rerwite buffer. */
|
||||
/* Return the current size of the AOF rewrite buffer. */
|
||||
unsigned long aofRewriteBufferSize(void) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
@@ -95,6 +95,10 @@ void aofChildWriteDiffData(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
listNode *ln;
|
||||
aofrwblock *block;
|
||||
ssize_t nwritten;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(fd);
|
||||
REDIS_NOTUSED(privdata);
|
||||
REDIS_NOTUSED(mask);
|
||||
|
||||
while(1) {
|
||||
ln = listFirst(server.aof_rewrite_buf_blocks);
|
||||
@@ -177,7 +181,7 @@ ssize_t aofRewriteBufferWrite(int fd) {
|
||||
|
||||
if (block->used) {
|
||||
nwritten = write(fd,block->buf,block->used);
|
||||
if (nwritten != block->used) {
|
||||
if (nwritten != (ssize_t)block->used) {
|
||||
if (nwritten == 0) errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
@@ -241,7 +245,7 @@ int startAppendOnly(void) {
|
||||
redisLog(REDIS_WARNING,"Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error.");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
/* We correctly switched on AOF, now wait for the rerwite to be complete
|
||||
/* We correctly switched on AOF, now wait for the rewrite to be complete
|
||||
* in order to append data on disk. */
|
||||
server.aof_state = REDIS_AOF_WAIT_REWRITE;
|
||||
return REDIS_OK;
|
||||
@@ -282,7 +286,7 @@ void flushAppendOnlyFile(int force) {
|
||||
* the write for a couple of seconds. */
|
||||
if (sync_in_progress) {
|
||||
if (server.aof_flush_postponed_start == 0) {
|
||||
/* No previous write postponinig, remember that we are
|
||||
/* No previous write postponing, remember that we are
|
||||
* postponing the flush and return. */
|
||||
server.aof_flush_postponed_start = server.unixtime;
|
||||
return;
|
||||
@@ -333,7 +337,7 @@ void flushAppendOnlyFile(int force) {
|
||||
last_write_error_log = server.unixtime;
|
||||
}
|
||||
|
||||
/* Lof the AOF write error and record the error code. */
|
||||
/* Log the AOF write error and record the error code. */
|
||||
if (nwritten == -1) {
|
||||
if (can_log) {
|
||||
redisLog(REDIS_WARNING,"Error writing to the AOF file: %s",
|
||||
@@ -357,7 +361,7 @@ void flushAppendOnlyFile(int force) {
|
||||
"ftruncate: %s", strerror(errno));
|
||||
}
|
||||
} else {
|
||||
/* If the ftrunacate() succeeded we can set nwritten to
|
||||
/* If the ftruncate() succeeded we can set nwritten to
|
||||
* -1 since there is no longer partial data into the AOF. */
|
||||
nwritten = -1;
|
||||
}
|
||||
@@ -369,7 +373,7 @@ void flushAppendOnlyFile(int force) {
|
||||
/* We can't recover when the fsync policy is ALWAYS since the
|
||||
* reply for the client is already in the output buffers, and we
|
||||
* have the contract with the user that on acknowledged write data
|
||||
* is synched on disk. */
|
||||
* is synced on disk. */
|
||||
redisLog(REDIS_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
|
||||
exit(1);
|
||||
} else {
|
||||
@@ -464,7 +468,7 @@ sds catAppendOnlyExpireAtCommand(sds buf, struct redisCommand *cmd, robj *key, r
|
||||
long long when;
|
||||
robj *argv[3];
|
||||
|
||||
/* Make sure we can use strtol */
|
||||
/* Make sure we can use strtoll */
|
||||
seconds = getDecodedObject(seconds);
|
||||
when = strtoll(seconds->ptr,NULL,10);
|
||||
/* Convert argument into milliseconds for EXPIRE, SETEX, EXPIREAT */
|
||||
@@ -495,7 +499,7 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
||||
robj *tmpargv[3];
|
||||
|
||||
/* The DB this command was targeting is not the same as the last command
|
||||
* we appendend. To issue a SELECT command is needed. */
|
||||
* we appended. To issue a SELECT command is needed. */
|
||||
if (dictid != server.aof_selected_db) {
|
||||
char seldb[64];
|
||||
|
||||
@@ -573,6 +577,14 @@ struct redisClient *createFakeClient(void) {
|
||||
return c;
|
||||
}
|
||||
|
||||
void freeFakeClientArgv(struct redisClient *c) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < c->argc; j++)
|
||||
decrRefCount(c->argv[j]);
|
||||
zfree(c->argv);
|
||||
}
|
||||
|
||||
void freeFakeClient(struct redisClient *c) {
|
||||
sdsfree(c->querybuf);
|
||||
listRelease(c->reply);
|
||||
@@ -581,7 +593,7 @@ void freeFakeClient(struct redisClient *c) {
|
||||
zfree(c);
|
||||
}
|
||||
|
||||
/* Replay the append log file. On error REDIS_OK is returned. On non fatal
|
||||
/* Replay the append log file. On success REDIS_OK is returned. On non fatal
|
||||
* error (the append only file is zero-length) REDIS_ERR is returned. On
|
||||
* fatal error an error message is logged and the program exists. */
|
||||
int loadAppendOnlyFile(char *filename) {
|
||||
@@ -590,6 +602,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
struct redis_stat sb;
|
||||
int old_aof_state = server.aof_state;
|
||||
long loops = 0;
|
||||
off_t valid_up_to = 0; /* Offset of the latest well-formed command loaded. */
|
||||
|
||||
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
|
||||
server.aof_current_size = 0;
|
||||
@@ -607,7 +620,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
server.aof_state = REDIS_AOF_OFF;
|
||||
|
||||
fakeClient = createFakeClient();
|
||||
startLoading(fp);
|
||||
startLoadingFile(fp);
|
||||
|
||||
while(1) {
|
||||
int argc, j;
|
||||
@@ -630,18 +643,35 @@ int loadAppendOnlyFile(char *filename) {
|
||||
goto readerr;
|
||||
}
|
||||
if (buf[0] != '*') goto fmterr;
|
||||
if (buf[1] == '\0') goto readerr;
|
||||
argc = atoi(buf+1);
|
||||
if (argc < 1) goto fmterr;
|
||||
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
fakeClient->argc = argc;
|
||||
fakeClient->argv = argv;
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) goto readerr;
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
fakeClient->argc = j; /* Free up to j-1. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr;
|
||||
}
|
||||
if (buf[0] != '$') goto fmterr;
|
||||
len = strtol(buf+1,NULL,10);
|
||||
argsds = sdsnewlen(NULL,len);
|
||||
if (len && fread(argsds,len,1,fp) == 0) goto fmterr;
|
||||
if (len && fread(argsds,len,1,fp) == 0) {
|
||||
sdsfree(argsds);
|
||||
fakeClient->argc = j; /* Free up to j-1. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr;
|
||||
}
|
||||
argv[j] = createObject(REDIS_STRING,argsds);
|
||||
if (fread(buf,2,1,fp) == 0) goto fmterr; /* discard CRLF */
|
||||
if (fread(buf,2,1,fp) == 0) {
|
||||
fakeClient->argc = j+1; /* Free up to j. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr; /* discard CRLF */
|
||||
}
|
||||
}
|
||||
|
||||
/* Command lookup */
|
||||
@@ -650,9 +680,8 @@ int loadAppendOnlyFile(char *filename) {
|
||||
redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Run the command in the context of a fake client */
|
||||
fakeClient->argc = argc;
|
||||
fakeClient->argv = argv;
|
||||
cmd->proc(fakeClient);
|
||||
|
||||
/* The fake client should not have a reply */
|
||||
@@ -662,15 +691,15 @@ int loadAppendOnlyFile(char *filename) {
|
||||
|
||||
/* Clean up. Command code may have changed argv/argc so we use the
|
||||
* argv/argc of the client instead of the local variables. */
|
||||
for (j = 0; j < fakeClient->argc; j++)
|
||||
decrRefCount(fakeClient->argv[j]);
|
||||
zfree(fakeClient->argv);
|
||||
freeFakeClientArgv(fakeClient);
|
||||
if (server.aof_load_truncated) valid_up_to = ftello(fp);
|
||||
}
|
||||
|
||||
/* This point can only be reached when EOF is reached without errors.
|
||||
* If the client is in the middle of a MULTI/EXEC, log error and quit. */
|
||||
if (fakeClient->flags & REDIS_MULTI) goto readerr;
|
||||
if (fakeClient->flags & REDIS_MULTI) goto uxeof;
|
||||
|
||||
loaded_ok: /* DB loaded, cleanup and return REDIS_OK to the caller. */
|
||||
fclose(fp);
|
||||
freeFakeClient(fakeClient);
|
||||
server.aof_state = old_aof_state;
|
||||
@@ -679,14 +708,41 @@ int loadAppendOnlyFile(char *filename) {
|
||||
server.aof_rewrite_base_size = server.aof_current_size;
|
||||
return REDIS_OK;
|
||||
|
||||
readerr:
|
||||
if (feof(fp)) {
|
||||
redisLog(REDIS_WARNING,"Unexpected end of file reading the append only file");
|
||||
} else {
|
||||
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
|
||||
if (!feof(fp)) {
|
||||
redisLog(REDIS_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
uxeof: /* Unexpected AOF end of file. */
|
||||
if (server.aof_load_truncated) {
|
||||
redisLog(REDIS_WARNING,"!!! Warning: short read while loading the AOF file !!!");
|
||||
redisLog(REDIS_WARNING,"!!! Truncating the AOF at offset %llu !!!",
|
||||
(unsigned long long) valid_up_to);
|
||||
if (valid_up_to == -1 || truncate(filename,valid_up_to) == -1) {
|
||||
if (valid_up_to == -1) {
|
||||
redisLog(REDIS_WARNING,"Last valid command offset is invalid");
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"Error truncating the AOF file: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
} else {
|
||||
/* Make sure the AOF file descriptor points to the end of the
|
||||
* file after the truncate call. */
|
||||
if (server.aof_fd != -1 && lseek(server.aof_fd,0,SEEK_END) == -1) {
|
||||
redisLog(REDIS_WARNING,"Can't seek the end of the AOF file: %s",
|
||||
strerror(errno));
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,
|
||||
"AOF loaded anyway because aof-load-truncated is enabled");
|
||||
goto loaded_ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
redisLog(REDIS_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.");
|
||||
exit(1);
|
||||
fmterr:
|
||||
|
||||
fmterr: /* Format error. */
|
||||
redisLog(REDIS_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>");
|
||||
exit(1);
|
||||
}
|
||||
@@ -714,52 +770,29 @@ int rioWriteBulkObject(rio *r, robj *obj) {
|
||||
int rewriteListObject(rio *r, robj *key, robj *o) {
|
||||
long long count = 0, items = listTypeLength(o);
|
||||
|
||||
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = o->ptr;
|
||||
unsigned char *p = ziplistIndex(zl,0);
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vlong;
|
||||
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
quicklist *list = o->ptr;
|
||||
quicklistIter *li = quicklistGetIterator(list, AL_START_HEAD);
|
||||
quicklistEntry entry;
|
||||
|
||||
while(ziplistGet(p,&vstr,&vlen,&vlong)) {
|
||||
while (quicklistNext(li,&entry)) {
|
||||
if (count == 0) {
|
||||
int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
REDIS_AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
|
||||
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
}
|
||||
if (vstr) {
|
||||
if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0;
|
||||
|
||||
if (entry.value) {
|
||||
if (rioWriteBulkString(r,(char*)entry.value,entry.sz) == 0) return 0;
|
||||
} else {
|
||||
if (rioWriteBulkLongLong(r,vlong) == 0) return 0;
|
||||
if (rioWriteBulkLongLong(r,entry.longval) == 0) return 0;
|
||||
}
|
||||
p = ziplistNext(zl,p);
|
||||
if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||
items--;
|
||||
}
|
||||
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
list *list = o->ptr;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
listRewind(list,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
robj *eleobj = listNodeValue(ln);
|
||||
|
||||
if (count == 0) {
|
||||
int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
REDIS_AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
|
||||
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
}
|
||||
if (rioWriteBulkObject(r,eleobj) == 0) return 0;
|
||||
if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||
items--;
|
||||
}
|
||||
quicklistReleaseIterator(li);
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
@@ -944,7 +977,7 @@ int rewriteHashObject(rio *r, robj *key, robj *o) {
|
||||
* the difference accumulated from the parent into a buffer, that is
|
||||
* concatenated at the end of the rewrite. */
|
||||
ssize_t aofReadDiffFromParent(void) {
|
||||
char buf[65536]; /* Default pipe buffer size on most Linux sytems. */
|
||||
char buf[65536]; /* Default pipe buffer size on most Linux systems. */
|
||||
ssize_t nread, total = 0;
|
||||
|
||||
while ((nread =
|
||||
@@ -1049,6 +1082,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
di = NULL;
|
||||
}
|
||||
|
||||
/* Do an initial slow fsync here while the parent is still sending
|
||||
@@ -1058,7 +1092,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
|
||||
/* Read again a few times to get more data from the parent.
|
||||
* We can't read forever (the server may receive data from clients
|
||||
* fater than it is able to send data to the child), so we try to read
|
||||
* faster than it is able to send data to the child), so we try to read
|
||||
* some more data in a loop as soon as there is a good chance more data
|
||||
* will come. If it looks like we are wasting time, we abort (this
|
||||
* happens after 20 ms without new data). */
|
||||
@@ -1112,9 +1146,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;
|
||||
}
|
||||
@@ -1128,6 +1162,9 @@ werr:
|
||||
* parent sends a '!' as well to acknowledge. */
|
||||
void aofChildPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
char byte;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(privdata);
|
||||
REDIS_NOTUSED(mask);
|
||||
|
||||
if (read(fd,&byte,1) == 1 && byte == '!') {
|
||||
redisLog(REDIS_NOTICE,"AOF rewrite child asks to stop sending diffs.");
|
||||
@@ -1191,7 +1228,7 @@ void aofClosePipes(void) {
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* AOF backgorund rewrite
|
||||
* AOF background rewrite
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* This is how rewriting of the append only file in background works:
|
||||
@@ -1280,7 +1317,7 @@ void aofRemoveTempFile(pid_t childpid) {
|
||||
unlink(tmpfile);
|
||||
}
|
||||
|
||||
/* Update the server.aof_current_size filed explicitly using stat(2)
|
||||
/* Update the server.aof_current_size field explicitly using stat(2)
|
||||
* to check the size of the file. This is useful after a rewrite or after
|
||||
* a restart, normally the size is updated just adding the write length
|
||||
* to the current length, that is much faster. */
|
||||
@@ -1333,7 +1370,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
latencyAddSampleIfNeeded("aof-rewrite-diff-write",latency);
|
||||
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Redidual parent diff successfully flushed to the rewritten AOF (%.2f MB)", (double) aofRewriteBufferSize() / (1024*1024));
|
||||
"Residual parent diff successfully flushed to the rewritten AOF (%.2f MB)", (double) aofRewriteBufferSize() / (1024*1024));
|
||||
|
||||
/* The only remaining thing to do is to rename the temporary file to
|
||||
* the configured file and switch the file descriptor used to do AOF
|
||||
|
||||
@@ -142,6 +142,13 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
unsigned long type = (unsigned long) arg;
|
||||
sigset_t sigset;
|
||||
|
||||
/* Check that the type is within the right interval. */
|
||||
if (type >= REDIS_BIO_NUM_OPS) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Warning: bio thread started with wrong type %lu",type);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Make the thread killable at any time, so that bioKillThreads()
|
||||
* can work reliably. */
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
|
||||
+35
-23
@@ -70,16 +70,19 @@ size_t redisPopcount(void *s, long count) {
|
||||
count--;
|
||||
}
|
||||
|
||||
/* Count bits 16 bytes at a time */
|
||||
/* Count bits 28 bytes at a time */
|
||||
p4 = (uint32_t*)p;
|
||||
while(count>=16) {
|
||||
uint32_t aux1, aux2, aux3, aux4;
|
||||
while(count>=28) {
|
||||
uint32_t aux1, aux2, aux3, aux4, aux5, aux6, aux7;
|
||||
|
||||
aux1 = *p4++;
|
||||
aux2 = *p4++;
|
||||
aux3 = *p4++;
|
||||
aux4 = *p4++;
|
||||
count -= 16;
|
||||
aux5 = *p4++;
|
||||
aux6 = *p4++;
|
||||
aux7 = *p4++;
|
||||
count -= 28;
|
||||
|
||||
aux1 = aux1 - ((aux1 >> 1) & 0x55555555);
|
||||
aux1 = (aux1 & 0x33333333) + ((aux1 >> 2) & 0x33333333);
|
||||
@@ -89,10 +92,19 @@ size_t redisPopcount(void *s, long count) {
|
||||
aux3 = (aux3 & 0x33333333) + ((aux3 >> 2) & 0x33333333);
|
||||
aux4 = aux4 - ((aux4 >> 1) & 0x55555555);
|
||||
aux4 = (aux4 & 0x33333333) + ((aux4 >> 2) & 0x33333333);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
/* Count the remaining bytes. */
|
||||
p = (unsigned char*)p4;
|
||||
@@ -107,12 +119,12 @@ size_t redisPopcount(void *s, long count) {
|
||||
* no zero bit is found, it returns count*8 assuming the string is zero
|
||||
* padded on the right. However if 'bit' is 1 it is possible that there is
|
||||
* not a single set bit in the bitmap. In this special case -1 is returned. */
|
||||
long redisBitpos(void *s, long count, int bit) {
|
||||
long redisBitpos(void *s, unsigned long count, int bit) {
|
||||
unsigned long *l;
|
||||
unsigned char *c;
|
||||
unsigned long skipval, word = 0, one;
|
||||
long pos = 0; /* Position of bit, to return to the caller. */
|
||||
int j;
|
||||
unsigned long j;
|
||||
|
||||
/* Process whole words first, seeking for first word that is not
|
||||
* all ones or all zeros respectively if we are lookig for zeros
|
||||
@@ -217,19 +229,17 @@ void setbitCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte = bitoffset >> 3;
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (o == NULL) {
|
||||
o = createObject(REDIS_STRING,sdsempty());
|
||||
o = createObject(REDIS_STRING,sdsnewlen(NULL, byte+1));
|
||||
dbAdd(c->db,c->argv[1],o);
|
||||
} else {
|
||||
if (checkType(c,o,REDIS_STRING)) return;
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
o->ptr = sdsgrowzero(o->ptr,byte+1);
|
||||
}
|
||||
|
||||
/* Grow sds value to the right length if necessary */
|
||||
byte = bitoffset >> 3;
|
||||
o->ptr = sdsgrowzero(o->ptr,byte+1);
|
||||
|
||||
/* Get current values */
|
||||
byteval = ((uint8_t*)o->ptr)[byte];
|
||||
bit = 7 - (bitoffset & 0x7);
|
||||
@@ -276,11 +286,12 @@ void getbitCommand(redisClient *c) {
|
||||
void bitopCommand(redisClient *c) {
|
||||
char *opname = c->argv[1]->ptr;
|
||||
robj *o, *targetkey = c->argv[2];
|
||||
long op, j, numkeys;
|
||||
unsigned long op, j, numkeys;
|
||||
robj **objects; /* Array of source objects. */
|
||||
unsigned char **src; /* Array of source strings pointers. */
|
||||
long *len, maxlen = 0; /* Array of length of src strings, and max len. */
|
||||
long minlen = 0; /* Min len among the input keys. */
|
||||
unsigned long *len, maxlen = 0; /* Array of length of src strings,
|
||||
and max len. */
|
||||
unsigned long minlen = 0; /* Min len among the input keys. */
|
||||
unsigned char *res = NULL; /* Resulting string. */
|
||||
|
||||
/* Parse the operation name. */
|
||||
@@ -320,9 +331,10 @@ void bitopCommand(redisClient *c) {
|
||||
}
|
||||
/* Return an error if one of the keys is not a string. */
|
||||
if (checkType(c,o,REDIS_STRING)) {
|
||||
for (j = j-1; j >= 0; j--) {
|
||||
if (objects[j])
|
||||
decrRefCount(objects[j]);
|
||||
unsigned long i;
|
||||
for (i = 0; i < j; i++) {
|
||||
if (objects[i])
|
||||
decrRefCount(objects[i]);
|
||||
}
|
||||
zfree(src);
|
||||
zfree(len);
|
||||
@@ -340,13 +352,13 @@ void bitopCommand(redisClient *c) {
|
||||
if (maxlen) {
|
||||
res = (unsigned char*) sdsnewlen(NULL,maxlen);
|
||||
unsigned char output, byte;
|
||||
long i;
|
||||
unsigned long i;
|
||||
|
||||
/* Fast path: as far as we have data for all the input bitmaps we
|
||||
* can take a fast path that performs much better than the
|
||||
* vanilla algorithm. */
|
||||
j = 0;
|
||||
if (minlen && numkeys <= 16) {
|
||||
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
|
||||
unsigned long *lp[16];
|
||||
unsigned long *lres = (unsigned long*) res;
|
||||
|
||||
|
||||
+40
-8
@@ -59,6 +59,8 @@
|
||||
* 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"
|
||||
@@ -114,13 +116,15 @@ 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) {
|
||||
server.current_client = c;
|
||||
processInputBuffer(c);
|
||||
server.current_client = NULL;
|
||||
/* Process remaining data in the input buffer, unless the client
|
||||
* is blocked again. Actually processInputBuffer() checks that the
|
||||
* client is not blocked before to proceed, but things may change and
|
||||
* the code is conceptually more correct this way. */
|
||||
if (!(c->flags & REDIS_BLOCKED)) {
|
||||
if (c->querybuf && sdslen(c->querybuf) > 0) {
|
||||
processInputBuffer(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,10 +142,14 @@ void unblockClient(redisClient *c) {
|
||||
/* Clear the flags, and put the client in the unblocked list so that
|
||||
* we'll process new commands in its query buffer ASAP. */
|
||||
c->flags &= ~REDIS_BLOCKED;
|
||||
c->flags |= REDIS_UNBLOCKED;
|
||||
c->btype = REDIS_BLOCKED_NONE;
|
||||
server.bpop_blocked_clients--;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
/* The client may already be into the unblocked list because of a previous
|
||||
* blocking operation, don't add back it into the list multiple times. */
|
||||
if (!(c->flags & REDIS_UNBLOCKED)) {
|
||||
c->flags |= REDIS_UNBLOCKED;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function gets called when a blocked client timed out in order to
|
||||
@@ -156,3 +164,27 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+689
-271
File diff suppressed because it is too large
Load Diff
+34
-15
@@ -11,10 +11,11 @@
|
||||
#define REDIS_CLUSTER_NAMELEN 40 /* sha1 hex length */
|
||||
#define REDIS_CLUSTER_PORT_INCR 10000 /* Cluster port = baseport + PORT_INCR */
|
||||
|
||||
/* The following defines are amunt of time, sometimes expressed as
|
||||
/* The following defines are amount of time, sometimes expressed as
|
||||
* multiplicators of the node timeout value (when ending with MULT). */
|
||||
#define REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT 15000
|
||||
#define REDIS_CLUSTER_DEFAULT_SLAVE_VALIDITY 10 /* Slave max data age factor. */
|
||||
#define REDIS_CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE 1
|
||||
#define REDIS_CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */
|
||||
#define REDIS_CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if master is back. */
|
||||
#define REDIS_CLUSTER_FAIL_UNDO_TIME_ADD 10 /* Some additional time. */
|
||||
@@ -25,10 +26,12 @@
|
||||
|
||||
/* Redirection errors returned by getNodeByQuery(). */
|
||||
#define REDIS_CLUSTER_REDIR_NONE 0 /* Node can serve the request. */
|
||||
#define REDIS_CLUSTER_REDIR_CROSS_SLOT 1 /* Keys in different slots. */
|
||||
#define REDIS_CLUSTER_REDIR_UNSTABLE 2 /* Keys in slot resharding. */
|
||||
#define REDIS_CLUSTER_REDIR_CROSS_SLOT 1 /* -CROSSSLOT request. */
|
||||
#define REDIS_CLUSTER_REDIR_UNSTABLE 2 /* -TRYAGAIN redirection required */
|
||||
#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;
|
||||
|
||||
@@ -50,7 +53,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_PROMOTED 256 /* Master was a slave propoted by failover */
|
||||
#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)
|
||||
@@ -61,13 +64,21 @@ typedef struct clusterLink {
|
||||
#define nodeTimedOut(n) ((n)->flags & REDIS_NODE_PFAIL)
|
||||
#define nodeFailed(n) ((n)->flags & REDIS_NODE_FAIL)
|
||||
|
||||
/* Reasons why a slave is not able to failover. */
|
||||
#define REDIS_CLUSTER_CANT_FAILOVER_NONE 0
|
||||
#define REDIS_CLUSTER_CANT_FAILOVER_DATA_AGE 1
|
||||
#define REDIS_CLUSTER_CANT_FAILOVER_WAITING_DELAY 2
|
||||
#define REDIS_CLUSTER_CANT_FAILOVER_EXPIRED 3
|
||||
#define REDIS_CLUSTER_CANT_FAILOVER_WAITING_VOTES 4
|
||||
#define REDIS_CLUSTER_CANT_FAILOVER_RELOG_PERIOD (60*5) /* seconds. */
|
||||
|
||||
/* This structure represent elements of node->fail_reports. */
|
||||
struct clusterNodeFailReport {
|
||||
typedef struct clusterNodeFailReport {
|
||||
struct clusterNode *node; /* Node reporting the failure condition. */
|
||||
mstime_t time; /* Time of the last report from this node. */
|
||||
} typedef clusterNodeFailReport;
|
||||
} clusterNodeFailReport;
|
||||
|
||||
struct clusterNode {
|
||||
typedef struct clusterNode {
|
||||
mstime_t ctime; /* Node object creation time. */
|
||||
char name[REDIS_CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
|
||||
int flags; /* REDIS_NODE_... */
|
||||
@@ -87,8 +98,7 @@ struct clusterNode {
|
||||
int port; /* Latest known port of this node */
|
||||
clusterLink *link; /* TCP/IP link with this node */
|
||||
list *fail_reports; /* List of nodes signaling this as failing */
|
||||
};
|
||||
typedef struct clusterNode clusterNode;
|
||||
} clusterNode;
|
||||
|
||||
typedef struct clusterState {
|
||||
clusterNode *myself; /* This node */
|
||||
@@ -107,6 +117,8 @@ typedef struct clusterState {
|
||||
int failover_auth_sent; /* True if we already asked for votes. */
|
||||
int failover_auth_rank; /* This slave rank for current auth request. */
|
||||
uint64_t failover_auth_epoch; /* Epoch of the current election. */
|
||||
int cant_failover_reason; /* Why a slave is currently not able to
|
||||
failover. See the CANT_FAILOVER_* macros. */
|
||||
/* Manual failover state in common. */
|
||||
mstime_t mf_end; /* Manual failover time limit (ms unixtime).
|
||||
It is zero if there is no MF in progress. */
|
||||
@@ -117,7 +129,7 @@ typedef struct clusterState {
|
||||
or zero if stil not received. */
|
||||
int mf_can_start; /* If non-zero signal that the manual failover
|
||||
can start requesting masters vote. */
|
||||
/* The followign fields are uesd by masters to take state on elections. */
|
||||
/* The followign fields are used by masters to take state on elections. */
|
||||
uint64_t lastVoteEpoch; /* Epoch of the last vote granted. */
|
||||
int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */
|
||||
long long stats_bus_messages_sent; /* Num of msg sent via cluster bus. */
|
||||
@@ -153,10 +165,11 @@ typedef struct {
|
||||
char nodename[REDIS_CLUSTER_NAMELEN];
|
||||
uint32_t ping_sent;
|
||||
uint32_t pong_received;
|
||||
char ip[REDIS_IP_STR_LEN]; /* IP address last time it was seen */
|
||||
uint16_t port; /* port last time it was seen */
|
||||
uint16_t flags;
|
||||
uint32_t notused; /* for 64 bit alignment */
|
||||
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;
|
||||
} clusterMsgDataGossip;
|
||||
|
||||
typedef struct {
|
||||
@@ -166,7 +179,10 @@ typedef struct {
|
||||
typedef struct {
|
||||
uint32_t channel_len;
|
||||
uint32_t message_len;
|
||||
unsigned char bulk_data[8]; /* defined as 8 just for alignment concerns. */
|
||||
/* 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];
|
||||
} clusterMsgDataPublish;
|
||||
|
||||
typedef struct {
|
||||
@@ -198,6 +214,7 @@ union clusterMsgData {
|
||||
} update;
|
||||
};
|
||||
|
||||
#define CLUSTER_PROTO_VER 0 /* Cluster bus protocol version. */
|
||||
|
||||
typedef struct {
|
||||
char sig[4]; /* Siganture "RCmb" (Redis Cluster message bus). */
|
||||
@@ -233,5 +250,7 @@ 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 */
|
||||
|
||||
+362
-344
@@ -34,10 +34,26 @@
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static struct {
|
||||
const char *name;
|
||||
const int value;
|
||||
} validSyslogFacilities[] = {
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Config file name-value maps.
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
typedef struct configEnum {
|
||||
const char *name;
|
||||
const int val;
|
||||
} configEnum;
|
||||
|
||||
configEnum maxmemory_policy_enum[] = {
|
||||
{"volatile-lru", REDIS_MAXMEMORY_VOLATILE_LRU},
|
||||
{"volatile-random",REDIS_MAXMEMORY_VOLATILE_RANDOM},
|
||||
{"volatile-ttl",REDIS_MAXMEMORY_VOLATILE_TTL},
|
||||
{"allkeys-lru",REDIS_MAXMEMORY_ALLKEYS_LRU},
|
||||
{"allkeys-random",REDIS_MAXMEMORY_ALLKEYS_RANDOM},
|
||||
{"noeviction",REDIS_MAXMEMORY_NO_EVICTION},
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
configEnum syslog_facility_enum[] = {
|
||||
{"user", LOG_USER},
|
||||
{"local0", LOG_LOCAL0},
|
||||
{"local1", LOG_LOCAL1},
|
||||
@@ -50,12 +66,70 @@ static struct {
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
configEnum loglevel_enum[] = {
|
||||
{"debug", REDIS_DEBUG},
|
||||
{"verbose", REDIS_VERBOSE},
|
||||
{"notice", REDIS_NOTICE},
|
||||
{"warning", REDIS_WARNING},
|
||||
{NULL,0}
|
||||
};
|
||||
|
||||
configEnum supervised_mode_enum[] = {
|
||||
{"upstart", REDIS_SUPERVISED_UPSTART},
|
||||
{"systemd", REDIS_SUPERVISED_SYSTEMD},
|
||||
{"auto", REDIS_SUPERVISED_AUTODETECT},
|
||||
{"no", REDIS_SUPERVISED_NONE},
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
configEnum aof_fsync_enum[] = {
|
||||
{"everysec", AOF_FSYNC_EVERYSEC},
|
||||
{"always", AOF_FSYNC_ALWAYS},
|
||||
{"no", AOF_FSYNC_NO},
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
/* Output buffer limits presets. */
|
||||
clientBufferLimitsConfig clientBufferLimitsDefaults[REDIS_CLIENT_TYPE_COUNT] = {
|
||||
{0, 0, 0}, /* normal */
|
||||
{1024*1024*256, 1024*1024*64, 60}, /* slave */
|
||||
{1024*1024*32, 1024*1024*8, 60} /* pubsub */
|
||||
};
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Enum access functions
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Get enum value from name. If there is no match INT_MIN is returned. */
|
||||
int configEnumGetValue(configEnum *ce, char *name) {
|
||||
while(ce->name != NULL) {
|
||||
if (!strcasecmp(ce->name,name)) return ce->val;
|
||||
ce++;
|
||||
}
|
||||
return INT_MIN;
|
||||
}
|
||||
|
||||
/* Get enum name from value. If no match is found NULL is returned. */
|
||||
const char *configEnumGetName(configEnum *ce, int val) {
|
||||
while(ce->name != NULL) {
|
||||
if (ce->val == val) return ce->name;
|
||||
ce++;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Wrapper for configEnumGetName() returning "unknown" insetad of NULL if
|
||||
* there is no match. */
|
||||
const char *configEnumGetNameOrUnknown(configEnum *ce, int val) {
|
||||
const char *name = configEnumGetName(ce,val);
|
||||
return name ? name : "unknown";
|
||||
}
|
||||
|
||||
/* Used for INFO generation. */
|
||||
const char *maxmemoryToString(void) {
|
||||
return configEnumGetNameOrUnknown(maxmemory_policy_enum,server.maxmemory);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Config file parsing
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -73,7 +147,7 @@ void appendServerSaveParams(time_t seconds, int changes) {
|
||||
server.saveparamslen++;
|
||||
}
|
||||
|
||||
void resetServerSaveParams() {
|
||||
void resetServerSaveParams(void) {
|
||||
zfree(server.saveparams);
|
||||
server.saveparams = NULL;
|
||||
server.saveparamslen = 0;
|
||||
@@ -167,12 +241,10 @@ void loadServerConfigFromString(char *config) {
|
||||
exit(1);
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"loglevel") && argc == 2) {
|
||||
if (!strcasecmp(argv[1],"debug")) server.verbosity = REDIS_DEBUG;
|
||||
else if (!strcasecmp(argv[1],"verbose")) server.verbosity = REDIS_VERBOSE;
|
||||
else if (!strcasecmp(argv[1],"notice")) server.verbosity = REDIS_NOTICE;
|
||||
else if (!strcasecmp(argv[1],"warning")) server.verbosity = REDIS_WARNING;
|
||||
else {
|
||||
err = "Invalid log level. Must be one of debug, notice, warning";
|
||||
server.verbosity = configEnumGetValue(loglevel_enum,argv[1]);
|
||||
if (server.verbosity == INT_MIN) {
|
||||
err = "Invalid log level. "
|
||||
"Must be one of debug, verbose, notice, warning";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"logfile") && argc == 2) {
|
||||
@@ -199,16 +271,9 @@ void loadServerConfigFromString(char *config) {
|
||||
if (server.syslog_ident) zfree(server.syslog_ident);
|
||||
server.syslog_ident = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"syslog-facility") && argc == 2) {
|
||||
int i;
|
||||
|
||||
for (i = 0; validSyslogFacilities[i].name; i++) {
|
||||
if (!strcasecmp(validSyslogFacilities[i].name, argv[1])) {
|
||||
server.syslog_facility = validSyslogFacilities[i].value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!validSyslogFacilities[i].name) {
|
||||
server.syslog_facility =
|
||||
configEnumGetValue(syslog_facility_enum,argv[1]);
|
||||
if (server.syslog_facility == INT_MIN) {
|
||||
err = "Invalid log facility. Must be one of USER or between LOCAL0-LOCAL7";
|
||||
goto loaderr;
|
||||
}
|
||||
@@ -227,19 +292,9 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) {
|
||||
server.maxmemory = memtoll(argv[1],NULL);
|
||||
} else if (!strcasecmp(argv[0],"maxmemory-policy") && argc == 2) {
|
||||
if (!strcasecmp(argv[1],"volatile-lru")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
|
||||
} else if (!strcasecmp(argv[1],"volatile-random")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM;
|
||||
} else if (!strcasecmp(argv[1],"volatile-ttl")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL;
|
||||
} else if (!strcasecmp(argv[1],"allkeys-lru")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU;
|
||||
} else if (!strcasecmp(argv[1],"allkeys-random")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM;
|
||||
} else if (!strcasecmp(argv[1],"noeviction")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
|
||||
} else {
|
||||
server.maxmemory_policy =
|
||||
configEnumGetValue(maxmemory_policy_enum,argv[1]);
|
||||
if (server.maxmemory_policy == INT_MIN) {
|
||||
err = "Invalid maxmemory policy";
|
||||
goto loaderr;
|
||||
}
|
||||
@@ -270,6 +325,20 @@ void loadServerConfigFromString(char *config) {
|
||||
if ((server.repl_disable_tcp_nodelay = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"repl-diskless-sync") && argc==2) {
|
||||
if ((server.repl_diskless_sync = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"repl-diskless-load") && argc==2) {
|
||||
if ((server.repl_diskless_load = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) {
|
||||
server.repl_diskless_sync_delay = atoi(argv[1]);
|
||||
if (server.repl_diskless_sync_delay < 0) {
|
||||
err = "repl-diskless-sync-delay can't be negative";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"repl-backlog-size") && argc == 2) {
|
||||
long long size = memtoll(argv[1],NULL);
|
||||
if (size <= 0) {
|
||||
@@ -284,7 +353,7 @@ void loadServerConfigFromString(char *config) {
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
|
||||
server.masterauth = zstrdup(argv[1]);
|
||||
server.masterauth = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) {
|
||||
if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
@@ -333,13 +402,8 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"appendfsync") && argc == 2) {
|
||||
if (!strcasecmp(argv[1],"no")) {
|
||||
server.aof_fsync = AOF_FSYNC_NO;
|
||||
} else if (!strcasecmp(argv[1],"always")) {
|
||||
server.aof_fsync = AOF_FSYNC_ALWAYS;
|
||||
} else if (!strcasecmp(argv[1],"everysec")) {
|
||||
server.aof_fsync = AOF_FSYNC_EVERYSEC;
|
||||
} else {
|
||||
server.aof_fsync = configEnumGetValue(aof_fsync_enum,argv[1]);
|
||||
if (server.aof_fsync == INT_MIN) {
|
||||
err = "argument must be 'no', 'always' or 'everysec'";
|
||||
goto loaderr;
|
||||
}
|
||||
@@ -358,7 +422,12 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if (!strcasecmp(argv[0],"aof-rewrite-incremental-fsync") &&
|
||||
argc == 2)
|
||||
{
|
||||
if ((server.aof_rewrite_incremental_fsync = yesnotoi(argv[1])) == -1) {
|
||||
if ((server.aof_rewrite_incremental_fsync =
|
||||
yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"aof-load-truncated") && argc == 2) {
|
||||
if ((server.aof_load_truncated = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
|
||||
@@ -382,9 +451,13 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) {
|
||||
server.hash_max_ziplist_value = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
|
||||
server.list_max_ziplist_entries = memtoll(argv[1], NULL);
|
||||
/* DEAD OPTION */
|
||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
|
||||
server.list_max_ziplist_value = memtoll(argv[1], NULL);
|
||||
/* DEAD OPTION */
|
||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-size") && argc == 2) {
|
||||
server.list_max_ziplist_size = atoi(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"list-compress-depth") && argc == 2) {
|
||||
server.list_compress_depth = atoi(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"set-max-intset-entries") && argc == 2) {
|
||||
server.set_max_intset_entries = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"zset-max-ziplist-entries") && argc == 2) {
|
||||
@@ -424,6 +497,13 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if (!strcasecmp(argv[0],"cluster-config-file") && argc == 2) {
|
||||
zfree(server.cluster_configfile);
|
||||
server.cluster_configfile = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"cluster-require-full-coverage") &&
|
||||
argc == 2)
|
||||
{
|
||||
if ((server.cluster_require_full_coverage = yesnotoi(argv[1])) == -1)
|
||||
{
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-node-timeout") && argc == 2) {
|
||||
server.cluster_node_timeout = strtoll(argv[1],NULL,10);
|
||||
if (server.cluster_node_timeout <= 0) {
|
||||
@@ -507,6 +587,15 @@ void loadServerConfigFromString(char *config) {
|
||||
goto loaderr;
|
||||
}
|
||||
server.notify_keyspace_events = flags;
|
||||
} else if (!strcasecmp(argv[0],"supervised") && argc == 2) {
|
||||
server.supervised_mode =
|
||||
configEnumGetValue(supervised_mode_enum,argv[1]);
|
||||
|
||||
if (server.supervised_mode == INT_MIN) {
|
||||
err = "Invalid option for 'supervised'. "
|
||||
"Allowed values: 'upstart', 'systemd', 'auto', or 'no'";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"sentinel")) {
|
||||
/* argc == 1 is handled by main() as we need to enter the sentinel
|
||||
* mode ASAP. */
|
||||
@@ -584,38 +673,62 @@ void loadServerConfig(char *filename, char *options) {
|
||||
* CONFIG SET implementation
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#define config_set_bool_field(_name,_var) \
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
|
||||
int yn = yesnotoi(o->ptr); \
|
||||
if (yn == -1) goto badfmt; \
|
||||
_var = yn;
|
||||
|
||||
#define config_set_numerical_field(_name,_var,min,max) \
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt; \
|
||||
if (min != LLONG_MIN && ll < min) goto badfmt; \
|
||||
if (max != LLONG_MAX && ll > max) goto badfmt; \
|
||||
_var = ll;
|
||||
|
||||
#define config_set_memory_field(_name,_var) \
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
|
||||
ll = memtoll(o->ptr,&err); \
|
||||
if (err || ll < 0) goto badfmt; \
|
||||
_var = ll;
|
||||
|
||||
#define config_set_enum_field(_name,_var,_enumvar) \
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
|
||||
int enumval = configEnumGetValue(_enumvar,o->ptr); \
|
||||
if (enumval == INT_MIN) goto badfmt; \
|
||||
_var = enumval;
|
||||
|
||||
#define config_set_special_field(_name) \
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,_name)) {
|
||||
|
||||
#define config_set_else } else
|
||||
|
||||
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];
|
||||
|
||||
if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) {
|
||||
if (0) { /* this starts the config_set macros else-if chain. */
|
||||
|
||||
/* Special fields that can't be handled with general macros. */
|
||||
config_set_special_field("dbfilename") {
|
||||
if (!pathIsBaseName(o->ptr)) {
|
||||
addReplyError(c, "dbfilename can't be a path, just a filename");
|
||||
return;
|
||||
}
|
||||
zfree(server.rdb_filename);
|
||||
server.rdb_filename = zstrdup(o->ptr);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"requirepass")) {
|
||||
} config_set_special_field("requirepass") {
|
||||
if (sdslen(o->ptr) > REDIS_AUTHPASS_MAX_LEN) goto badfmt;
|
||||
zfree(server.requirepass);
|
||||
server.requirepass = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"masterauth")) {
|
||||
} config_set_special_field("masterauth") {
|
||||
zfree(server.masterauth);
|
||||
server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.maxmemory = ll;
|
||||
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,"maxclients")) {
|
||||
} config_set_special_field("maxclients") {
|
||||
int orig_value = server.maxclients;
|
||||
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 1) goto badfmt;
|
||||
@@ -629,7 +742,7 @@ void configSetCommand(redisClient *c) {
|
||||
server.maxclients = orig_value;
|
||||
return;
|
||||
}
|
||||
if (aeGetSetSize(server.el) <
|
||||
if ((unsigned int) aeGetSetSize(server.el) <
|
||||
server.maxclients + REDIS_EVENTLOOP_FDSET_INCR)
|
||||
{
|
||||
if (aeResizeSetSize(server.el,
|
||||
@@ -641,55 +754,7 @@ void configSetCommand(redisClient *c) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"hz")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.hz = ll;
|
||||
if (server.hz < REDIS_MIN_HZ) server.hz = REDIS_MIN_HZ;
|
||||
if (server.hz > REDIS_MAX_HZ) server.hz = REDIS_MAX_HZ;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-policy")) {
|
||||
if (!strcasecmp(o->ptr,"volatile-lru")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
|
||||
} else if (!strcasecmp(o->ptr,"volatile-random")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM;
|
||||
} else if (!strcasecmp(o->ptr,"volatile-ttl")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL;
|
||||
} else if (!strcasecmp(o->ptr,"allkeys-lru")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU;
|
||||
} else if (!strcasecmp(o->ptr,"allkeys-random")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM;
|
||||
} else if (!strcasecmp(o->ptr,"noeviction")) {
|
||||
server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
|
||||
} else {
|
||||
goto badfmt;
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-samples")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll <= 0) goto badfmt;
|
||||
server.maxmemory_samples = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"timeout")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0 || ll > LONG_MAX) goto badfmt;
|
||||
server.maxidletime = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"tcp-keepalive")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0 || ll > INT_MAX) goto badfmt;
|
||||
server.tcpkeepalive = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"appendfsync")) {
|
||||
if (!strcasecmp(o->ptr,"no")) {
|
||||
server.aof_fsync = AOF_FSYNC_NO;
|
||||
} else if (!strcasecmp(o->ptr,"everysec")) {
|
||||
server.aof_fsync = AOF_FSYNC_EVERYSEC;
|
||||
} else if (!strcasecmp(o->ptr,"always")) {
|
||||
server.aof_fsync = AOF_FSYNC_ALWAYS;
|
||||
} else {
|
||||
goto badfmt;
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"no-appendfsync-on-rewrite")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.aof_no_fsync_on_rewrite = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"appendonly")) {
|
||||
} config_set_special_field("appendonly") {
|
||||
int enable = yesnotoi(o->ptr);
|
||||
|
||||
if (enable == -1) goto badfmt;
|
||||
@@ -702,18 +767,7 @@ void configSetCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"auto-aof-rewrite-percentage")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.aof_rewrite_perc = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"auto-aof-rewrite-min-size")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.aof_rewrite_min_size = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"aof-rewrite-incremental-fsync")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.aof_rewrite_incremental_fsync = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"save")) {
|
||||
} config_set_special_field("save") {
|
||||
int vlen, j;
|
||||
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
|
||||
|
||||
@@ -747,70 +801,12 @@ void configSetCommand(redisClient *c) {
|
||||
appendServerSaveParams(seconds, changes);
|
||||
}
|
||||
sdsfreesplitres(v,vlen);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"slave-serve-stale-data")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.repl_serve_stale_data = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"slave-read-only")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.repl_slave_ro = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"dir")) {
|
||||
} config_set_special_field("dir") {
|
||||
if (chdir((char*)o->ptr) == -1) {
|
||||
addReplyErrorFormat(c,"Changing directory: %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"hash-max-ziplist-entries")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.hash_max_ziplist_entries = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"hash-max-ziplist-value")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.hash_max_ziplist_value = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-entries")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.list_max_ziplist_entries = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-value")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.list_max_ziplist_value = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"set-max-intset-entries")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.set_max_intset_entries = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-entries")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.zset_max_ziplist_entries = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-value")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.zset_max_ziplist_value = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"hll-sparse-max-bytes")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.hll_sparse_max_bytes = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"lua-time-limit")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.lua_time_limit = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"slowlog-log-slower-than")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR) goto badfmt;
|
||||
server.slowlog_log_slower_than = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"slowlog-max-len")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.slowlog_max_len = (unsigned)ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"latency-monitor-threshold")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.latency_monitor_threshold = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"loglevel")) {
|
||||
if (!strcasecmp(o->ptr,"warning")) {
|
||||
server.verbosity = REDIS_WARNING;
|
||||
} else if (!strcasecmp(o->ptr,"notice")) {
|
||||
server.verbosity = REDIS_NOTICE;
|
||||
} else if (!strcasecmp(o->ptr,"verbose")) {
|
||||
server.verbosity = REDIS_VERBOSE;
|
||||
} else if (!strcasecmp(o->ptr,"debug")) {
|
||||
server.verbosity = REDIS_DEBUG;
|
||||
} else {
|
||||
goto badfmt;
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"client-output-buffer-limit")) {
|
||||
} config_set_special_field("client-output-buffer-limit") {
|
||||
int vlen, j;
|
||||
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
|
||||
|
||||
@@ -824,7 +820,6 @@ 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) {
|
||||
@@ -833,8 +828,8 @@ void configSetCommand(redisClient *c) {
|
||||
goto badfmt;
|
||||
}
|
||||
} else {
|
||||
val = strtoll(v[j], &eptr, 10);
|
||||
if (eptr[0] != '\0' || val < 0) {
|
||||
val = memtoll(v[j], &err);
|
||||
if (err || val < 0) {
|
||||
sdsfreesplitres(v,vlen);
|
||||
goto badfmt;
|
||||
}
|
||||
@@ -856,75 +851,139 @@ void configSetCommand(redisClient *c) {
|
||||
server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
|
||||
}
|
||||
sdsfreesplitres(v,vlen);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"stop-writes-on-bgsave-error")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.stop_writes_on_bgsave_err = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"repl-ping-slave-period")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
|
||||
server.repl_ping_slave_period = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"repl-timeout")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
|
||||
server.repl_timeout = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-size")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
|
||||
resizeReplicationBacklog(ll);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-ttl")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.repl_backlog_time_limit = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"watchdog-period")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
if (ll)
|
||||
enableWatchdog(ll);
|
||||
else
|
||||
disableWatchdog();
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"rdbcompression")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.rdb_compression = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"notify-keyspace-events")) {
|
||||
} config_set_special_field("notify-keyspace-events") {
|
||||
int flags = keyspaceEventsStringToFlags(o->ptr);
|
||||
|
||||
if (flags == -1) goto badfmt;
|
||||
server.notify_keyspace_events = flags;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"repl-disable-tcp-nodelay")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.repl_disable_tcp_nodelay = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"slave-priority")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.slave_priority = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"min-slaves-to-write")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.repl_min_slaves_to_write = ll;
|
||||
/* Boolean fields.
|
||||
* config_set_bool_field(name,var). */
|
||||
} config_set_bool_field(
|
||||
"rdbcompression", server.rdb_compression) {
|
||||
} config_set_bool_field(
|
||||
"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay) {
|
||||
} config_set_bool_field(
|
||||
"repl-diskless-sync",server.repl_diskless_sync) {
|
||||
} config_set_bool_field(
|
||||
"repl-diskless-load",server.repl_diskless_load) {
|
||||
} config_set_bool_field(
|
||||
"cluster-require-full-coverage",server.cluster_require_full_coverage) {
|
||||
} config_set_bool_field(
|
||||
"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync) {
|
||||
} config_set_bool_field(
|
||||
"aof-load-truncated",server.aof_load_truncated) {
|
||||
} config_set_bool_field(
|
||||
"slave-serve-stale-data",server.repl_serve_stale_data) {
|
||||
} config_set_bool_field(
|
||||
"slave-read-only",server.repl_slave_ro) {
|
||||
} config_set_bool_field(
|
||||
"activerehashing",server.activerehashing) {
|
||||
} config_set_bool_field(
|
||||
"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err) {
|
||||
|
||||
/* Numerical fields.
|
||||
* config_set_numerical_field(name,var,min,max) */
|
||||
} config_set_numerical_field(
|
||||
"tcp-keepalive",server.tcpkeepalive,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"maxmemory-samples",server.maxmemory_samples,1,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"timeout",server.maxidletime,0,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"auto-aof-rewrite-percentage",server.aof_rewrite_perc,0,LLONG_MAX){
|
||||
} config_set_numerical_field(
|
||||
"auto-aof-rewrite-min-size",server.aof_rewrite_min_size,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"hash-max-ziplist-entries",server.hash_max_ziplist_entries,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"hash-max-ziplist-value",server.hash_max_ziplist_value,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"list-max-ziplist-size",server.list_max_ziplist_size,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"list-compress-depth",server.list_compress_depth,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"set-max-intset-entries",server.set_max_intset_entries,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"zset-max-ziplist-entries",server.zset_max_ziplist_entries,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"zset-max-ziplist-value",server.zset_max_ziplist_value,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"hll-sparse-max-bytes",server.hll_sparse_max_bytes,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"lua-time-limit",server.lua_time_limit,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slowlog-log-slower-than",server.slowlog_log_slower_than,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slowlog-max-len",ll,0,LLONG_MAX) {
|
||||
/* Cast to unsigned. */
|
||||
server.slowlog_max_len = (unsigned)ll;
|
||||
} config_set_numerical_field(
|
||||
"latency-monitor-threshold",server.latency_monitor_threshold,0,LLONG_MAX){
|
||||
} config_set_numerical_field(
|
||||
"repl-ping-slave-period",server.repl_ping_slave_period,1,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"repl-timeout",server.repl_timeout,1,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"repl-backlog-ttl",server.repl_backlog_time_limit,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"repl-diskless-sync-delay",server.repl_diskless_sync_delay,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"slave-priority",server.slave_priority,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"min-slaves-to-write",server.repl_min_slaves_to_write,0,LLONG_MAX) {
|
||||
refreshGoodSlavesCount();
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"min-slaves-max-lag")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.repl_min_slaves_max_lag = ll;
|
||||
} config_set_numerical_field(
|
||||
"min-slaves-max-lag",server.repl_min_slaves_max_lag,0,LLONG_MAX) {
|
||||
refreshGoodSlavesCount();
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"cluster-node-timeout")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll <= 0) goto badfmt;
|
||||
server.cluster_node_timeout = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"cluster-migration-barrier")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.cluster_migration_barrier = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"cluster-slave-validity-factor")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.cluster_slave_validity_factor = ll;
|
||||
} else {
|
||||
} config_set_numerical_field(
|
||||
"cluster-node-timeout",server.cluster_node_timeout,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"cluster-migration-barrier",server.cluster_migration_barrier,0,LLONG_MAX){
|
||||
} config_set_numerical_field(
|
||||
"cluster-slave-validity-factor",server.cluster_slave_validity_factor,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"hz",server.hz,0,LLONG_MAX) {
|
||||
/* Hz is more an hint from the user, so we accept values out of range
|
||||
* but cap them to reasonable values. */
|
||||
if (server.hz < REDIS_MIN_HZ) server.hz = REDIS_MIN_HZ;
|
||||
if (server.hz > REDIS_MAX_HZ) server.hz = REDIS_MAX_HZ;
|
||||
} config_set_numerical_field(
|
||||
"watchdog-period",ll,0,LLONG_MAX) {
|
||||
if (ll)
|
||||
enableWatchdog(ll);
|
||||
else
|
||||
disableWatchdog();
|
||||
|
||||
/* Memory fields.
|
||||
* config_set_memory_field(name,var) */
|
||||
} config_set_memory_field("maxmemory",server.maxmemory) {
|
||||
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();
|
||||
}
|
||||
} config_set_memory_field("repl-backlog-size",ll) {
|
||||
resizeReplicationBacklog(ll);
|
||||
|
||||
/* Enumeration fields.
|
||||
* config_set_enum_field(name,var,enum_var) */
|
||||
} config_set_enum_field(
|
||||
"loglevel",server.verbosity,loglevel_enum) {
|
||||
} config_set_enum_field(
|
||||
"maxmemory-policy",server.maxmemory_policy,maxmemory_policy_enum) {
|
||||
} config_set_enum_field(
|
||||
"appendfsync",server.aof_fsync,aof_fsync_enum) {
|
||||
|
||||
/* Everyhing else is an error... */
|
||||
} config_set_else {
|
||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||
(char*)c->argv[2]->ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* On success we just return a generic OK for all the options. */
|
||||
addReply(c,shared.ok);
|
||||
return;
|
||||
|
||||
@@ -963,6 +1022,14 @@ badfmt: /* Bad format errors */
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
#define config_get_enum_field(_name,_var,_enumvar) do { \
|
||||
if (stringmatch(pattern,_name,0)) { \
|
||||
addReplyBulkCString(c,_name); \
|
||||
addReplyBulkCString(c,configEnumGetNameOrUnknown(_enumvar,_var)); \
|
||||
matches++; \
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
void configGetCommand(redisClient *c) {
|
||||
robj *o = c->argv[2];
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
@@ -983,7 +1050,6 @@ void configGetCommand(redisClient *c) {
|
||||
config_get_numerical_field("maxmemory",server.maxmemory);
|
||||
config_get_numerical_field("maxmemory-samples",server.maxmemory_samples);
|
||||
config_get_numerical_field("timeout",server.maxidletime);
|
||||
config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
|
||||
config_get_numerical_field("auto-aof-rewrite-percentage",
|
||||
server.aof_rewrite_perc);
|
||||
config_get_numerical_field("auto-aof-rewrite-min-size",
|
||||
@@ -992,10 +1058,10 @@ void configGetCommand(redisClient *c) {
|
||||
server.hash_max_ziplist_entries);
|
||||
config_get_numerical_field("hash-max-ziplist-value",
|
||||
server.hash_max_ziplist_value);
|
||||
config_get_numerical_field("list-max-ziplist-entries",
|
||||
server.list_max_ziplist_entries);
|
||||
config_get_numerical_field("list-max-ziplist-value",
|
||||
server.list_max_ziplist_value);
|
||||
config_get_numerical_field("list-max-ziplist-size",
|
||||
server.list_max_ziplist_size);
|
||||
config_get_numerical_field("list-compress-depth",
|
||||
server.list_compress_depth);
|
||||
config_get_numerical_field("set-max-intset-entries",
|
||||
server.set_max_intset_entries);
|
||||
config_get_numerical_field("zset-max-ziplist-entries",
|
||||
@@ -1027,8 +1093,12 @@ void configGetCommand(redisClient *c) {
|
||||
config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout);
|
||||
config_get_numerical_field("cluster-migration-barrier",server.cluster_migration_barrier);
|
||||
config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor);
|
||||
config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay);
|
||||
config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
|
||||
|
||||
/* Bool (yes/no) values */
|
||||
config_get_bool_field("cluster-require-full-coverage",
|
||||
server.cluster_require_full_coverage);
|
||||
config_get_bool_field("no-appendfsync-on-rewrite",
|
||||
server.aof_no_fsync_on_rewrite);
|
||||
config_get_bool_field("slave-serve-stale-data",
|
||||
@@ -1043,8 +1113,26 @@ void configGetCommand(redisClient *c) {
|
||||
config_get_bool_field("activerehashing", server.activerehashing);
|
||||
config_get_bool_field("repl-disable-tcp-nodelay",
|
||||
server.repl_disable_tcp_nodelay);
|
||||
config_get_bool_field("repl-diskless-sync",
|
||||
server.repl_diskless_sync);
|
||||
config_get_bool_field("repl-diskless-load",
|
||||
server.repl_diskless_load);
|
||||
config_get_bool_field("aof-rewrite-incremental-fsync",
|
||||
server.aof_rewrite_incremental_fsync);
|
||||
config_get_bool_field("aof-load-truncated",
|
||||
server.aof_load_truncated);
|
||||
|
||||
/* Enum values */
|
||||
config_get_enum_field("maxmemory-policy",
|
||||
server.maxmemory_policy,maxmemory_policy_enum);
|
||||
config_get_enum_field("loglevel",
|
||||
server.verbosity,loglevel_enum);
|
||||
config_get_enum_field("supervised",
|
||||
server.supervised_mode,supervised_mode_enum);
|
||||
config_get_enum_field("appendfsync",
|
||||
server.aof_fsync,aof_fsync_enum);
|
||||
config_get_enum_field("syslog-facility",
|
||||
server.syslog_facility,syslog_facility_enum);
|
||||
|
||||
/* Everything we can't handle with macros follows. */
|
||||
|
||||
@@ -1063,35 +1151,6 @@ void configGetCommand(redisClient *c) {
|
||||
addReplyBulkCString(c,buf);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"maxmemory-policy",0)) {
|
||||
char *s;
|
||||
|
||||
switch(server.maxmemory_policy) {
|
||||
case REDIS_MAXMEMORY_VOLATILE_LRU: s = "volatile-lru"; break;
|
||||
case REDIS_MAXMEMORY_VOLATILE_TTL: s = "volatile-ttl"; break;
|
||||
case REDIS_MAXMEMORY_VOLATILE_RANDOM: s = "volatile-random"; break;
|
||||
case REDIS_MAXMEMORY_ALLKEYS_LRU: s = "allkeys-lru"; break;
|
||||
case REDIS_MAXMEMORY_ALLKEYS_RANDOM: s = "allkeys-random"; break;
|
||||
case REDIS_MAXMEMORY_NO_EVICTION: s = "noeviction"; break;
|
||||
default: s = "unknown"; break; /* too harmless to panic */
|
||||
}
|
||||
addReplyBulkCString(c,"maxmemory-policy");
|
||||
addReplyBulkCString(c,s);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"appendfsync",0)) {
|
||||
char *policy;
|
||||
|
||||
switch(server.aof_fsync) {
|
||||
case AOF_FSYNC_NO: policy = "no"; break;
|
||||
case AOF_FSYNC_EVERYSEC: policy = "everysec"; break;
|
||||
case AOF_FSYNC_ALWAYS: policy = "always"; break;
|
||||
default: policy = "unknown"; break; /* too harmless to panic */
|
||||
}
|
||||
addReplyBulkCString(c,"appendfsync");
|
||||
addReplyBulkCString(c,policy);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"save",0)) {
|
||||
sds buf = sdsempty();
|
||||
int j;
|
||||
@@ -1108,20 +1167,6 @@ void configGetCommand(redisClient *c) {
|
||||
sdsfree(buf);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"loglevel",0)) {
|
||||
char *s;
|
||||
|
||||
switch(server.verbosity) {
|
||||
case REDIS_WARNING: s = "warning"; break;
|
||||
case REDIS_VERBOSE: s = "verbose"; break;
|
||||
case REDIS_NOTICE: s = "notice"; break;
|
||||
case REDIS_DEBUG: s = "debug"; break;
|
||||
default: s = "unknown"; break; /* too harmless to panic */
|
||||
}
|
||||
addReplyBulkCString(c,"loglevel");
|
||||
addReplyBulkCString(c,s);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern,"client-output-buffer-limit",0)) {
|
||||
sds buf = sdsempty();
|
||||
int j;
|
||||
@@ -1246,7 +1291,7 @@ void rewriteConfigAddLineNumberToOption(struct rewriteConfigState *state, sds op
|
||||
* This is useful as only unused lines of processed options will be blanked
|
||||
* in the config file, while options the rewrite process does not understand
|
||||
* remain untouched. */
|
||||
void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, char *option) {
|
||||
void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, const char *option) {
|
||||
sds opt = sdsnew(option);
|
||||
|
||||
if (dictAdd(state->rewritten,opt,NULL) != DICT_OK) sdsfree(opt);
|
||||
@@ -1330,7 +1375,7 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
|
||||
*
|
||||
* "line" is either used, or freed, so the caller does not need to free it
|
||||
* in any way. */
|
||||
void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sds line, int force) {
|
||||
void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *option, sds line, int force) {
|
||||
sds o = sdsnew(option);
|
||||
list *l = dictFetchValue(state->option_to_line,o);
|
||||
|
||||
@@ -1415,7 +1460,7 @@ void rewriteConfigStringOption(struct rewriteConfigState *state, char *option, c
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compare the strings as sds strings to have a binary safe comparison. */
|
||||
/* Set force to zero if the value is set to its default. */
|
||||
if (defvalue && strcmp(value,defvalue) == 0) force = 0;
|
||||
|
||||
line = sdsnew(option);
|
||||
@@ -1441,45 +1486,26 @@ void rewriteConfigOctalOption(struct rewriteConfigState *state, char *option, in
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite an enumeration option, after the "value" every enum/value pair
|
||||
* is specified, terminated by NULL. After NULL the default value is
|
||||
* specified. See how the function is used for more information. */
|
||||
void rewriteConfigEnumOption(struct rewriteConfigState *state, char *option, int value, ...) {
|
||||
va_list ap;
|
||||
char *enum_name, *matching_name = NULL;
|
||||
int enum_val, def_val, force;
|
||||
/* Rewrite an enumeration option. It takes as usually state and option name,
|
||||
* and in addition the enumeration array and the default value for the
|
||||
* option. */
|
||||
void rewriteConfigEnumOption(struct rewriteConfigState *state, char *option, int value, configEnum *ce, int defval) {
|
||||
sds line;
|
||||
const char *name = configEnumGetNameOrUnknown(ce,value);
|
||||
int force = value != defval;
|
||||
|
||||
va_start(ap, value);
|
||||
while(1) {
|
||||
enum_name = va_arg(ap,char*);
|
||||
enum_val = va_arg(ap,int);
|
||||
if (enum_name == NULL) {
|
||||
def_val = enum_val;
|
||||
break;
|
||||
}
|
||||
if (value == enum_val) matching_name = enum_name;
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
force = value != def_val;
|
||||
line = sdscatprintf(sdsempty(),"%s %s",option,matching_name);
|
||||
line = sdscatprintf(sdsempty(),"%s %s",option,name);
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite the syslog-fability option. */
|
||||
/* Rewrite the syslog-facility option. */
|
||||
void rewriteConfigSyslogfacilityOption(struct rewriteConfigState *state) {
|
||||
int value = server.syslog_facility, j;
|
||||
int value = server.syslog_facility;
|
||||
int force = value != LOG_LOCAL0;
|
||||
char *name = NULL, *option = "syslog-facility";
|
||||
const char *name = NULL, *option = "syslog-facility";
|
||||
sds line;
|
||||
|
||||
for (j = 0; validSyslogFacilities[j].name; j++) {
|
||||
if (validSyslogFacilities[j].value == value) {
|
||||
name = (char*) validSyslogFacilities[j].name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
name = configEnumGetNameOrUnknown(syslog_facility_enum,value);
|
||||
line = sdscatprintf(sdsempty(),"%s %s",option,name);
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
@@ -1740,12 +1766,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,REDIS_DEFAULT_UNIX_SOCKET_PERM);
|
||||
rewriteConfigNumericalOption(state,"timeout",server.maxidletime,REDIS_MAXIDLETIME);
|
||||
rewriteConfigNumericalOption(state,"tcp-keepalive",server.tcpkeepalive,REDIS_DEFAULT_TCP_KEEPALIVE);
|
||||
rewriteConfigEnumOption(state,"loglevel",server.verbosity,
|
||||
"debug", REDIS_DEBUG,
|
||||
"verbose", REDIS_VERBOSE,
|
||||
"notice", REDIS_NOTICE,
|
||||
"warning", REDIS_WARNING,
|
||||
NULL, REDIS_DEFAULT_VERBOSITY);
|
||||
rewriteConfigEnumOption(state,"loglevel",server.verbosity,loglevel_enum,REDIS_DEFAULT_VERBOSITY);
|
||||
rewriteConfigStringOption(state,"logfile",server.logfile,REDIS_DEFAULT_LOGFILE);
|
||||
rewriteConfigYesNoOption(state,"syslog-enabled",server.syslog_enabled,REDIS_DEFAULT_SYSLOG_ENABLED);
|
||||
rewriteConfigStringOption(state,"syslog-ident",server.syslog_ident,REDIS_DEFAULT_SYSLOG_IDENT);
|
||||
@@ -1766,34 +1787,27 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigBytesOption(state,"repl-backlog-size",server.repl_backlog_size,REDIS_DEFAULT_REPL_BACKLOG_SIZE);
|
||||
rewriteConfigBytesOption(state,"repl-backlog-ttl",server.repl_backlog_time_limit,REDIS_DEFAULT_REPL_BACKLOG_TIME_LIMIT);
|
||||
rewriteConfigYesNoOption(state,"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay,REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY);
|
||||
rewriteConfigYesNoOption(state,"repl-diskless-sync",server.repl_diskless_sync,REDIS_DEFAULT_REPL_DISKLESS_SYNC);
|
||||
rewriteConfigYesNoOption(state,"repl-diskless-load",server.repl_diskless_load,REDIS_DEFAULT_REPL_DISKLESS_LOAD);
|
||||
rewriteConfigNumericalOption(state,"repl-diskless-sync-delay",server.repl_diskless_sync_delay,REDIS_DEFAULT_REPL_DISKLESS_SYNC_DELAY);
|
||||
rewriteConfigNumericalOption(state,"slave-priority",server.slave_priority,REDIS_DEFAULT_SLAVE_PRIORITY);
|
||||
rewriteConfigNumericalOption(state,"min-slaves-to-write",server.repl_min_slaves_to_write,REDIS_DEFAULT_MIN_SLAVES_TO_WRITE);
|
||||
rewriteConfigNumericalOption(state,"min-slaves-max-lag",server.repl_min_slaves_max_lag,REDIS_DEFAULT_MIN_SLAVES_MAX_LAG);
|
||||
rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL);
|
||||
rewriteConfigNumericalOption(state,"maxclients",server.maxclients,REDIS_MAX_CLIENTS);
|
||||
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,REDIS_DEFAULT_MAXMEMORY);
|
||||
rewriteConfigEnumOption(state,"maxmemory-policy",server.maxmemory_policy,
|
||||
"volatile-lru", REDIS_MAXMEMORY_VOLATILE_LRU,
|
||||
"allkeys-lru", REDIS_MAXMEMORY_ALLKEYS_LRU,
|
||||
"volatile-random", REDIS_MAXMEMORY_VOLATILE_RANDOM,
|
||||
"allkeys-random", REDIS_MAXMEMORY_ALLKEYS_RANDOM,
|
||||
"volatile-ttl", REDIS_MAXMEMORY_VOLATILE_TTL,
|
||||
"noeviction", REDIS_MAXMEMORY_NO_EVICTION,
|
||||
NULL, REDIS_DEFAULT_MAXMEMORY_POLICY);
|
||||
rewriteConfigEnumOption(state,"maxmemory-policy",server.maxmemory_policy,maxmemory_policy_enum,REDIS_DEFAULT_MAXMEMORY_POLICY);
|
||||
rewriteConfigNumericalOption(state,"maxmemory-samples",server.maxmemory_samples,REDIS_DEFAULT_MAXMEMORY_SAMPLES);
|
||||
rewriteConfigYesNoOption(state,"appendonly",server.aof_state != REDIS_AOF_OFF,0);
|
||||
rewriteConfigStringOption(state,"appendfilename",server.aof_filename,REDIS_DEFAULT_AOF_FILENAME);
|
||||
rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,
|
||||
"everysec", AOF_FSYNC_EVERYSEC,
|
||||
"always", AOF_FSYNC_ALWAYS,
|
||||
"no", AOF_FSYNC_NO,
|
||||
NULL, REDIS_DEFAULT_AOF_FSYNC);
|
||||
rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,aof_fsync_enum,REDIS_DEFAULT_AOF_FSYNC);
|
||||
rewriteConfigYesNoOption(state,"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite,REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE);
|
||||
rewriteConfigNumericalOption(state,"auto-aof-rewrite-percentage",server.aof_rewrite_perc,REDIS_AOF_REWRITE_PERC);
|
||||
rewriteConfigBytesOption(state,"auto-aof-rewrite-min-size",server.aof_rewrite_min_size,REDIS_AOF_REWRITE_MIN_SIZE);
|
||||
rewriteConfigNumericalOption(state,"lua-time-limit",server.lua_time_limit,REDIS_LUA_TIME_LIMIT);
|
||||
rewriteConfigYesNoOption(state,"cluster-enabled",server.cluster_enabled,0);
|
||||
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,REDIS_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
rewriteConfigYesNoOption(state,"cluster-require-full-coverage",server.cluster_require_full_coverage,REDIS_CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE);
|
||||
rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT);
|
||||
rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,REDIS_CLUSTER_DEFAULT_MIGRATION_BARRIER);
|
||||
rewriteConfigNumericalOption(state,"cluster-slave-validity-factor",server.cluster_slave_validity_factor,REDIS_CLUSTER_DEFAULT_SLAVE_VALIDITY);
|
||||
@@ -1803,8 +1817,8 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigNotifykeyspaceeventsOption(state);
|
||||
rewriteConfigNumericalOption(state,"hash-max-ziplist-entries",server.hash_max_ziplist_entries,REDIS_HASH_MAX_ZIPLIST_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"hash-max-ziplist-value",server.hash_max_ziplist_value,REDIS_HASH_MAX_ZIPLIST_VALUE);
|
||||
rewriteConfigNumericalOption(state,"list-max-ziplist-entries",server.list_max_ziplist_entries,REDIS_LIST_MAX_ZIPLIST_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"list-max-ziplist-value",server.list_max_ziplist_value,REDIS_LIST_MAX_ZIPLIST_VALUE);
|
||||
rewriteConfigNumericalOption(state,"list-max-ziplist-size",server.list_max_ziplist_size,REDIS_LIST_MAX_ZIPLIST_SIZE);
|
||||
rewriteConfigNumericalOption(state,"list-compress-depth",server.list_compress_depth,REDIS_LIST_COMPRESS_DEPTH);
|
||||
rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,REDIS_SET_MAX_INTSET_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,REDIS_ZSET_MAX_ZIPLIST_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,REDIS_ZSET_MAX_ZIPLIST_VALUE);
|
||||
@@ -1813,6 +1827,10 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
rewriteConfigNumericalOption(state,"hz",server.hz,REDIS_DEFAULT_HZ);
|
||||
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
|
||||
rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,REDIS_DEFAULT_AOF_LOAD_TRUNCATED);
|
||||
rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,REDIS_SUPERVISED_NONE);
|
||||
|
||||
/* Rewrite Sentinel config if in Sentinel mode. */
|
||||
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
|
||||
|
||||
/* Step 3: remove all the orphaned lines in the old file, that is, lines
|
||||
|
||||
+19
-5
@@ -34,6 +34,11 @@
|
||||
#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
|
||||
@@ -48,6 +53,7 @@
|
||||
#define HAVE_PROC_STAT 1
|
||||
#define HAVE_PROC_MAPS 1
|
||||
#define HAVE_PROC_SMAPS 1
|
||||
#define HAVE_PROC_SOMAXCONN 1
|
||||
#endif
|
||||
|
||||
/* Test for task_info() */
|
||||
@@ -56,10 +62,15 @@
|
||||
#endif
|
||||
|
||||
/* Test for backtrace() */
|
||||
#if defined(__APPLE__) || defined(__linux__)
|
||||
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__))
|
||||
#define HAVE_BACKTRACE 1
|
||||
#endif
|
||||
|
||||
/* MSG_NOSIGNAL. */
|
||||
#ifdef __linux__
|
||||
#define HAVE_MSG_NOSIGNAL 1
|
||||
#endif
|
||||
|
||||
/* Test for polling API */
|
||||
#ifdef __linux__
|
||||
#define HAVE_EPOLL 1
|
||||
@@ -86,8 +97,6 @@
|
||||
/* 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
|
||||
@@ -112,7 +121,7 @@
|
||||
#define USE_SETPROCTITLE
|
||||
#endif
|
||||
|
||||
#if (defined __linux || defined __APPLE__)
|
||||
#if ((defined __linux && defined(__GLIBC__)) || defined __APPLE__)
|
||||
#define USE_SETPROCTITLE
|
||||
#define INIT_SETPROCTITLE_REPLACEMENT
|
||||
void spt_init(int argc, char *argv[]);
|
||||
@@ -187,9 +196,14 @@ void setproctitle(const char *fmt, ...);
|
||||
|
||||
#if (__i386 || __amd64 || __powerpc__) && __GNUC__
|
||||
#define GNUC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
|
||||
#if (GNUC_VERSION >= 40100) || defined(__clang__)
|
||||
#if defined(__clang__)
|
||||
#define HAVE_ATOMIC
|
||||
#endif
|
||||
#if (defined(__GLIBC__) && defined(__GLIBC_PREREQ))
|
||||
#if (GNUC_VERSION >= 40100 && __GLIBC_PREREQ(2, 6))
|
||||
#define HAVE_ATOMIC
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
+6
-2
@@ -181,9 +181,13 @@ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
|
||||
}
|
||||
|
||||
/* Test main */
|
||||
#ifdef TEST_MAIN
|
||||
#ifdef REDIS_TEST
|
||||
#include <stdio.h>
|
||||
int main(void) {
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
int crc64Test(int argc, char *argv[]) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
printf("e9c6d914c4b8d9ca == %016llx\n",
|
||||
(unsigned long long) crc64(0,(unsigned char*)"123456789",9));
|
||||
return 0;
|
||||
|
||||
@@ -5,4 +5,8 @@
|
||||
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int crc64Test(int argc, char *argv[]);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -60,7 +60,32 @@ robj *lookupKey(redisDb *db, robj *key) {
|
||||
robj *lookupKeyRead(redisDb *db, robj *key) {
|
||||
robj *val;
|
||||
|
||||
expireIfNeeded(db,key);
|
||||
if (expireIfNeeded(db,key) == 1) {
|
||||
/* Key expired. If we are in the context of a master, expireIfNeeded()
|
||||
* returns 0 only when the key does not exist at all, so it's save
|
||||
* to return NULL ASAP. */
|
||||
if (server.masterhost == NULL) return NULL;
|
||||
|
||||
/* However if we are in the context of a slave, expireIfNeeded() will
|
||||
* not really try to expire the key, it only returns information
|
||||
* about the "logical" status of the key: key expiring is up to the
|
||||
* master in order to have a consistent view of master's data set.
|
||||
*
|
||||
* However, if the command caller is not the master, and as additional
|
||||
* safety measure, the command invoked is a read-only command, we can
|
||||
* safely return NULL here, and provide a more consistent behavior
|
||||
* to clients accessign expired values in a read-only fashion, that
|
||||
* will say the key as non exisitng.
|
||||
*
|
||||
* Notably this covers GETs when slaves are used to scale reads. */
|
||||
if (server.current_client &&
|
||||
server.current_client != server.master &&
|
||||
server.current_client->cmd &&
|
||||
server.current_client->cmd->flags & REDIS_CMD_READONLY)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
val = lookupKey(db,key);
|
||||
if (val == NULL)
|
||||
server.stat_keyspace_misses++;
|
||||
@@ -293,13 +318,17 @@ void delCommand(redisClient *c) {
|
||||
addReplyLongLong(c,deleted);
|
||||
}
|
||||
|
||||
/* EXISTS key1 key2 ... key_N.
|
||||
* Return value is the number of keys existing. */
|
||||
void existsCommand(redisClient *c) {
|
||||
expireIfNeeded(c->db,c->argv[1]);
|
||||
if (dbExists(c->db,c->argv[1])) {
|
||||
addReply(c, shared.cone);
|
||||
} else {
|
||||
addReply(c, shared.czero);
|
||||
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++;
|
||||
}
|
||||
addReplyLongLong(c,count);
|
||||
}
|
||||
|
||||
void selectCommand(redisClient *c) {
|
||||
@@ -381,7 +410,7 @@ void scanCallback(void *privdata, const dictEntry *de) {
|
||||
} else if (o->type == REDIS_ZSET) {
|
||||
key = dictGetKey(de);
|
||||
incrRefCount(key);
|
||||
val = createStringObjectFromLongDouble(*(double*)dictGetVal(de));
|
||||
val = createStringObjectFromLongDouble(*(double*)dictGetVal(de),0);
|
||||
} else {
|
||||
redisPanic("Type not handled in SCAN callback.");
|
||||
}
|
||||
@@ -421,14 +450,12 @@ int parseScanCursorOrReply(redisClient *c, robj *o, unsigned long *cursor) {
|
||||
* In the case of a Hash object the function returns both the field and value
|
||||
* of every element on the Hash. */
|
||||
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
int rv;
|
||||
int i, j;
|
||||
char buf[REDIS_LONGSTR_SIZE];
|
||||
list *keys = listCreate();
|
||||
listNode *node, *nextnode;
|
||||
long count = 10;
|
||||
sds pat;
|
||||
int patlen, use_pattern = 0;
|
||||
sds pat = NULL;
|
||||
int patlen = 0, use_pattern = 0;
|
||||
dict *ht;
|
||||
|
||||
/* Object must be NULL (to iterate keys names), or the type of the object
|
||||
@@ -495,6 +522,11 @@ void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
|
||||
if (ht) {
|
||||
void *privdata[2];
|
||||
/* We set the max number of iterations to ten times the specified
|
||||
* COUNT, so if the hash table is in a pathological state (very
|
||||
* sparsely populated) we avoid to block too much time at the cost
|
||||
* of returning no or very few elements. */
|
||||
long maxiterations = count*10;
|
||||
|
||||
/* We pass two pointers to the callback: the list to which it will
|
||||
* add new elements, and the object containing the dictionary so that
|
||||
@@ -503,7 +535,9 @@ void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
privdata[1] = o;
|
||||
do {
|
||||
cursor = dictScan(ht, cursor, scanCallback, privdata);
|
||||
} while (cursor && listLength(keys) < count);
|
||||
} while (cursor &&
|
||||
maxiterations-- &&
|
||||
listLength(keys) < (unsigned long)count);
|
||||
} else if (o->type == REDIS_SET) {
|
||||
int pos = 0;
|
||||
int64_t ll;
|
||||
@@ -577,9 +611,7 @@ void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
|
||||
/* Step 4: Reply to the client. */
|
||||
addReplyMultiBulkLen(c, 2);
|
||||
rv = snprintf(buf, sizeof(buf), "%lu", cursor);
|
||||
redisAssert(rv < sizeof(buf));
|
||||
addReplyBulkCBuffer(c, buf, rv);
|
||||
addReplyBulkLongLong(c,cursor);
|
||||
|
||||
addReplyMultiBulkLen(c, listLength(keys));
|
||||
while ((node = listFirst(keys)) != NULL) {
|
||||
@@ -660,16 +692,20 @@ void shutdownCommand(redisClient *c) {
|
||||
void renameGenericCommand(redisClient *c, int nx) {
|
||||
robj *o;
|
||||
long long expire;
|
||||
int samekey = 0;
|
||||
|
||||
/* To use the same key as src and dst is probably an error */
|
||||
if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) {
|
||||
addReply(c,shared.sameobjecterr);
|
||||
return;
|
||||
}
|
||||
/* When source and dest key is the same, no operation is performed,
|
||||
* if the key exists, however we still return an error on unexisting key. */
|
||||
if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) samekey = 1;
|
||||
|
||||
if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL)
|
||||
return;
|
||||
|
||||
if (samekey) {
|
||||
addReply(c,nx ? shared.czero : shared.ok);
|
||||
return;
|
||||
}
|
||||
|
||||
incrRefCount(o);
|
||||
expire = getExpire(c->db,c->argv[1]);
|
||||
if (lookupKeyWrite(c->db,c->argv[2]) != NULL) {
|
||||
@@ -707,6 +743,7 @@ void moveCommand(redisClient *c) {
|
||||
robj *o;
|
||||
redisDb *src, *dst;
|
||||
int srcid;
|
||||
long long dbid;
|
||||
|
||||
if (server.cluster_enabled) {
|
||||
addReplyError(c,"MOVE is not allowed in cluster mode");
|
||||
@@ -716,7 +753,11 @@ void moveCommand(redisClient *c) {
|
||||
/* Obtain source and target DB pointers */
|
||||
src = c->db;
|
||||
srcid = c->db->id;
|
||||
if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) {
|
||||
|
||||
if (getLongLongFromObject(c->argv[2],&dbid) == REDIS_ERR ||
|
||||
dbid < INT_MIN || dbid > INT_MAX ||
|
||||
selectDb(c,dbid) == REDIS_ERR)
|
||||
{
|
||||
addReply(c,shared.outofrangeerr);
|
||||
return;
|
||||
}
|
||||
@@ -869,7 +910,7 @@ void expireGenericCommand(redisClient *c, long long basetime, int unit) {
|
||||
when += basetime;
|
||||
|
||||
/* No key, return zero. */
|
||||
if (lookupKeyRead(c->db,key) == NULL) {
|
||||
if (lookupKeyWrite(c->db,key) == NULL) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
@@ -1076,7 +1117,7 @@ int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
* follow in SQL-alike style. Here we parse just the minimum in order to
|
||||
* correctly identify keys in the "STORE" option. */
|
||||
int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, j, num, *keys;
|
||||
int i, j, num, *keys, found_store = 0;
|
||||
REDIS_NOTUSED(cmd);
|
||||
|
||||
num = 0;
|
||||
@@ -1107,12 +1148,13 @@ int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
/* Note: we don't increment "num" here and continue the loop
|
||||
* to be sure to process the *last* "STORE" option if multiple
|
||||
* ones are provided. This is same behavior as SORT. */
|
||||
found_store = 1;
|
||||
keys[num] = i+1; /* <store-key> */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*numkeys = num;
|
||||
*numkeys = num + found_store;
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
||||
+90
-9
@@ -252,6 +252,12 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
}
|
||||
}
|
||||
|
||||
void inputCatSds(void *result, const char *str) {
|
||||
/* result is actually a (sds *), so re-cast it here */
|
||||
sds *info = (sds *)result;
|
||||
*info = sdscat(*info, str);
|
||||
}
|
||||
|
||||
void debugCommand(redisClient *c) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
|
||||
*((char*)-1) = 'x';
|
||||
@@ -295,13 +301,46 @@ void debugCommand(redisClient *c) {
|
||||
val = dictGetVal(de);
|
||||
strenc = strEncoding(val->encoding);
|
||||
|
||||
char extra[128] = {0};
|
||||
if (val->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
char *nextra = extra;
|
||||
int remaining = sizeof(extra);
|
||||
quicklist *ql = val->ptr;
|
||||
/* Add number of quicklist nodes */
|
||||
int used = snprintf(nextra, remaining, " ql_nodes:%u", ql->len);
|
||||
nextra += used;
|
||||
remaining -= used;
|
||||
/* Add average quicklist fill factor */
|
||||
double avg = (double)ql->count/ql->len;
|
||||
used = snprintf(nextra, remaining, " ql_avg_node:%.2f", avg);
|
||||
nextra += used;
|
||||
remaining -= used;
|
||||
/* Add quicklist fill level / max ziplist size */
|
||||
used = snprintf(nextra, remaining, " ql_ziplist_max:%d", ql->fill);
|
||||
nextra += used;
|
||||
remaining -= used;
|
||||
/* Add isCompressed? */
|
||||
int compressed = ql->compress != 0;
|
||||
used = snprintf(nextra, remaining, " ql_compressed:%d", compressed);
|
||||
nextra += used;
|
||||
remaining -= used;
|
||||
/* Add total uncompressed size */
|
||||
unsigned long sz = 0;
|
||||
for (quicklistNode *node = ql->head; node; node = node->next) {
|
||||
sz += node->sz;
|
||||
}
|
||||
used = snprintf(nextra, remaining, " ql_uncompressed_size:%lu", sz);
|
||||
nextra += used;
|
||||
remaining -= used;
|
||||
}
|
||||
|
||||
addReplyStatusFormat(c,
|
||||
"Value at:%p refcount:%d "
|
||||
"encoding:%s serializedlength:%lld "
|
||||
"lru:%d lru_seconds_idle:%llu",
|
||||
"encoding:%s serializedlength:%zu "
|
||||
"lru:%d lru_seconds_idle:%llu%s",
|
||||
(void*)val, val->refcount,
|
||||
strenc, (long long) rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val));
|
||||
strenc, rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val)/1000, extra);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) {
|
||||
dictEntry *de;
|
||||
robj *val;
|
||||
@@ -325,7 +364,8 @@ void debugCommand(redisClient *c) {
|
||||
(long long) sdslen(val->ptr),
|
||||
(long long) sdsavail(val->ptr));
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"populate") && c->argc == 3) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"populate") &&
|
||||
(c->argc == 3 || c->argc == 4)) {
|
||||
long keys, j;
|
||||
robj *key, *val;
|
||||
char buf[128];
|
||||
@@ -334,9 +374,10 @@ void debugCommand(redisClient *c) {
|
||||
return;
|
||||
dictExpand(c->db->dict,keys);
|
||||
for (j = 0; j < keys; j++) {
|
||||
snprintf(buf,sizeof(buf),"key:%lu",j);
|
||||
snprintf(buf,sizeof(buf),"%s:%lu",
|
||||
(c->argc == 3) ? "key" : (char*)c->argv[3]->ptr, j);
|
||||
key = createStringObject(buf,strlen(buf));
|
||||
if (lookupKeyRead(c->db,key) != NULL) {
|
||||
if (lookupKeyWrite(c->db,key) != NULL) {
|
||||
decrRefCount(key);
|
||||
continue;
|
||||
}
|
||||
@@ -377,6 +418,46 @@ void debugCommand(redisClient *c) {
|
||||
errstr = sdsmapchars(errstr,"\n\r"," ",2); /* no newlines in errors. */
|
||||
errstr = sdscatlen(errstr,"\r\n",2);
|
||||
addReplySds(c,errstr);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"structsize") && c->argc == 2) {
|
||||
sds sizes = sdsempty();
|
||||
sizes = sdscatprintf(sizes,"bits:%d ", (sizeof(void*) == 8)?64:32);
|
||||
sizes = sdscatprintf(sizes,"robj:%d ", (int)sizeof(robj));
|
||||
sizes = sdscatprintf(sizes,"dictentry:%d ", (int)sizeof(dictEntry));
|
||||
sizes = sdscatprintf(sizes,"sdshdr:%d", (int)sizeof(struct sdshdr));
|
||||
addReplyBulkSds(c,sizes);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"htstats") && c->argc == 3) {
|
||||
long dbid;
|
||||
sds stats = sdsempty();
|
||||
char buf[4096];
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != REDIS_OK)
|
||||
return;
|
||||
if (dbid < 0 || dbid >= server.dbnum) {
|
||||
addReplyError(c,"Out of range database");
|
||||
return;
|
||||
}
|
||||
|
||||
stats = sdscatprintf(stats,"[Dictionary HT]\n");
|
||||
dictGetStats(buf,sizeof(buf),server.db[dbid].dict);
|
||||
stats = sdscat(stats,buf);
|
||||
|
||||
stats = sdscatprintf(stats,"[Expires HT]\n");
|
||||
dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
|
||||
stats = sdscat(stats,buf);
|
||||
|
||||
addReplyBulkSds(c,stats);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"jemalloc") && c->argc == 3) {
|
||||
#if defined(USE_JEMALLOC)
|
||||
if (!strcasecmp(c->argv[2]->ptr, "info")) {
|
||||
sds info = sdsempty();
|
||||
je_malloc_stats_print(inputCatSds, &info, NULL);
|
||||
addReplyBulkSds(c, info);
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Valid jemalloc debug fields: info");
|
||||
}
|
||||
#else
|
||||
addReplyErrorFormat(c, "jemalloc support not available");
|
||||
#endif
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown DEBUG subcommand or wrong number of arguments for '%s'",
|
||||
(char*)c->argv[1]->ptr);
|
||||
@@ -850,12 +931,12 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
|
||||
redisLog(REDIS_WARNING,
|
||||
"\n=== REDIS BUG REPORT END. Make sure to include from START to END. ===\n\n"
|
||||
" Please report the crash opening an issue on github:\n\n"
|
||||
" Please report the crash by opening an issue on github:\n\n"
|
||||
" http://github.com/antirez/redis/issues\n\n"
|
||||
" Suspect RAM error? Use redis-server --test-memory to verify it.\n\n"
|
||||
);
|
||||
/* free(messages); Don't call free() with possibly corrupted memory. */
|
||||
if (server.daemonize) unlink(server.pidfile);
|
||||
if (server.daemonize && server.supervised == 0) unlink(server.pidfile);
|
||||
|
||||
/* Make sure we exit with the right signal at the end. So for instance
|
||||
* the core will be dumped if enabled. */
|
||||
|
||||
+169
-163
@@ -79,12 +79,6 @@ unsigned int dictIntHashFunction(unsigned int key)
|
||||
return key;
|
||||
}
|
||||
|
||||
/* Identity hash function for integer keys */
|
||||
unsigned int dictIdentityHashFunction(unsigned int key)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
static uint32_t dict_hash_function_seed = 5381;
|
||||
|
||||
void dictSetHashFunctionSeed(uint32_t seed) {
|
||||
@@ -217,6 +211,9 @@ 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;
|
||||
@@ -238,27 +235,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. */
|
||||
* 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. */
|
||||
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--) {
|
||||
while(n-- && d->ht[0].used != 0) {
|
||||
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)d->rehashidx);
|
||||
while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
|
||||
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;
|
||||
}
|
||||
de = d->ht[0].table[d->rehashidx];
|
||||
/* Move all the keys in this bucket from the old to the new hash HT */
|
||||
while(de) {
|
||||
@@ -276,6 +273,17 @@ 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;
|
||||
}
|
||||
|
||||
@@ -348,7 +356,10 @@ dictEntry *dictAddRaw(dict *d, void *key)
|
||||
if ((index = _dictKeyIndex(d, key)) == -1)
|
||||
return NULL;
|
||||
|
||||
/* Allocate the memory and store the new entry */
|
||||
/* Allocate the memory and store the new entry.
|
||||
* Insert the element in top, with the assumption that in a database
|
||||
* system it is more likely that recently added entries are accessed
|
||||
* more frequently. */
|
||||
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
|
||||
entry = zmalloc(sizeof(*entry));
|
||||
entry->next = ht->table[index];
|
||||
@@ -576,7 +587,7 @@ dictEntry *dictNext(dictIterator *iter)
|
||||
iter->fingerprint = dictFingerprint(iter->d);
|
||||
}
|
||||
iter->index++;
|
||||
if (iter->index >= (signed) ht->size) {
|
||||
if (iter->index >= (long) ht->size) {
|
||||
if (dictIsRehashing(iter->d) && iter->table == 0) {
|
||||
iter->table++;
|
||||
iter->index = 0;
|
||||
@@ -622,7 +633,11 @@ dictEntry *dictGetRandomKey(dict *d)
|
||||
if (dictIsRehashing(d)) _dictRehashStep(d);
|
||||
if (dictIsRehashing(d)) {
|
||||
do {
|
||||
h = random() % (d->ht[0].size+d->ht[1].size);
|
||||
/* 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));
|
||||
he = (h >= d->ht[0].size) ? d->ht[1].table[h - d->ht[0].size] :
|
||||
d->ht[0].table[h];
|
||||
} while(he == NULL);
|
||||
@@ -649,9 +664,12 @@ dictEntry *dictGetRandomKey(dict *d)
|
||||
return he;
|
||||
}
|
||||
|
||||
/* This is a version of dictGetRandomKey() that is modified in order to
|
||||
* return multiple entries by jumping at a random place of the hash table
|
||||
* and scanning linearly for entries.
|
||||
/* 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.
|
||||
*
|
||||
* Returned pointers to hash table entries are stored into 'des' that
|
||||
* points to an array of dictEntry pointers. The array must have room for
|
||||
@@ -660,28 +678,65 @@ 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.
|
||||
* inside, or if not enough elements were found in a reasonable amount of
|
||||
* steps.
|
||||
*
|
||||
* Note that this function is not suitable when you need a good distribution
|
||||
* of the returned items, but only when you need to "sample" a given number
|
||||
* of continuous elements to run some kind of algorithm or to produce
|
||||
* statistics. However the function is much faster than dictGetRandomKey()
|
||||
* at producing N elements, and the elements are guaranteed to be non
|
||||
* repeating. */
|
||||
int dictGetRandomKeys(dict *d, dictEntry **des, int count) {
|
||||
int j; /* internal hash table id, 0 or 1. */
|
||||
int stored = 0;
|
||||
* at producing N elements. */
|
||||
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
unsigned long j; /* internal hash table id, 0 or 1. */
|
||||
unsigned long tables; /* 1 or 2 tables? */
|
||||
unsigned long stored = 0, maxsizemask;
|
||||
unsigned long maxsteps;
|
||||
|
||||
if (dictSize(d) < count) count = dictSize(d);
|
||||
while(stored < count) {
|
||||
for (j = 0; j < 2; j++) {
|
||||
/* Pick a random point inside the hash table 0 or 1. */
|
||||
unsigned int i = random() & d->ht[j].sizemask;
|
||||
int size = d->ht[j].size;
|
||||
maxsteps = count*10;
|
||||
|
||||
/* Make sure to visit every bucket by iterating 'size' times. */
|
||||
while(size--) {
|
||||
dictEntry *he = d->ht[j].table[i];
|
||||
/* 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 long i = random() & maxsizemask;
|
||||
unsigned long 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 long) 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;
|
||||
while (he) {
|
||||
/* Collect all the elements of the buckets found non
|
||||
* empty while iterating. */
|
||||
@@ -691,14 +746,11 @@ int dictGetRandomKeys(dict *d, dictEntry **des, 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; /* Never reached. */
|
||||
return stored;
|
||||
}
|
||||
|
||||
/* Function to reverse bits. Algorithm from:
|
||||
@@ -715,72 +767,72 @@ static unsigned long rev(unsigned long v) {
|
||||
|
||||
/* dictScan() is used to iterate over the elements of a dictionary.
|
||||
*
|
||||
* Iterating works in the following way:
|
||||
* Iterating works the following way:
|
||||
*
|
||||
* 1) Initially you call the function using a cursor (v) value of 0.
|
||||
* 2) The function performs one step of the iteration, and returns the
|
||||
* new cursor value that you must use in the next call.
|
||||
* new cursor value you must use in the next call.
|
||||
* 3) When the returned cursor is 0, the iteration is complete.
|
||||
*
|
||||
* The function guarantees that all the elements that are present in the
|
||||
* dictionary from the start to the end of the iteration are returned.
|
||||
* However it is possible that some element is returned multiple time.
|
||||
* The function guarantees all elements present in the
|
||||
* dictionary get returned between the start and end of the iteration.
|
||||
* However it is possible some elements get returned multiple times.
|
||||
*
|
||||
* For every element returned, the callback 'fn' passed as argument is
|
||||
* called, with 'privdata' as first argument and the dictionar entry
|
||||
* For every element returned, the callback argument 'fn' is
|
||||
* called with 'privdata' as first argument and the dictionary entry
|
||||
* 'de' as second argument.
|
||||
*
|
||||
* HOW IT WORKS.
|
||||
*
|
||||
* The algorithm used in the iteration was designed by Pieter Noordhuis.
|
||||
* The iteration algorithm was designed by Pieter Noordhuis.
|
||||
* The main idea is to increment a cursor starting from the higher order
|
||||
* bits, that is, instead of incrementing the cursor normally, the bits
|
||||
* bits. That is, instead of incrementing the cursor normally, the bits
|
||||
* of the cursor are reversed, then the cursor is incremented, and finally
|
||||
* the bits are reversed again.
|
||||
*
|
||||
* This strategy is needed because the hash table may be resized from one
|
||||
* call to the other call of the same iteration.
|
||||
* This strategy is needed because the hash table may be resized between
|
||||
* iteration calls.
|
||||
*
|
||||
* dict.c hash tables are always power of two in size, and they
|
||||
* use chaining, so the position of an element in a given table is given
|
||||
* always by computing the bitwise AND between Hash(key) and SIZE-1
|
||||
* by computing the bitwise AND between Hash(key) and SIZE-1
|
||||
* (where SIZE-1 is always the mask that is equivalent to taking the rest
|
||||
* of the division between the Hash of the key and SIZE).
|
||||
*
|
||||
* For example if the current hash table size is 16, the mask is
|
||||
* (in binary) 1111. The position of a key in the hash table will be always
|
||||
* (in binary) 1111. The position of a key in the hash table will always be
|
||||
* the last four bits of the hash output, and so forth.
|
||||
*
|
||||
* WHAT HAPPENS IF THE TABLE CHANGES IN SIZE?
|
||||
*
|
||||
* If the hash table grows, elements can go anyway in one multiple of
|
||||
* the old bucket: for example let's say that we already iterated with
|
||||
* a 4 bit cursor 1100, since the mask is 1111 (hash table size = 16).
|
||||
* If the hash table grows, elements can go anywhere in one multiple of
|
||||
* the old bucket: for example let's say we already iterated with
|
||||
* a 4 bit cursor 1100 (the mask is 1111 because hash table size = 16).
|
||||
*
|
||||
* If the hash table will be resized to 64 elements, and the new mask will
|
||||
* be 111111, the new buckets that you obtain substituting in ??1100
|
||||
* either 0 or 1, can be targeted only by keys that we already visited
|
||||
* If the hash table will be resized to 64 elements, then the new mask will
|
||||
* be 111111. The new buckets you obtain by substituting in ??1100
|
||||
* with either 0 or 1 can be targeted only by keys we already visited
|
||||
* when scanning the bucket 1100 in the smaller hash table.
|
||||
*
|
||||
* By iterating the higher bits first, because of the inverted counter, the
|
||||
* cursor does not need to restart if the table size gets bigger, and will
|
||||
* just continue iterating with cursors that don't have '1100' at the end,
|
||||
* nor any other combination of final 4 bits already explored.
|
||||
* cursor does not need to restart if the table size gets bigger. It will
|
||||
* continue iterating using cursors without '1100' at the end, and also
|
||||
* without any other combination of the final 4 bits already explored.
|
||||
*
|
||||
* Similarly when the table size shrinks over time, for example going from
|
||||
* 16 to 8, If a combination of the lower three bits (the mask for size 8
|
||||
* is 111) was already completely explored, it will not be visited again
|
||||
* as we are sure that, we tried for example, both 0111 and 1111 (all the
|
||||
* 16 to 8, if a combination of the lower three bits (the mask for size 8
|
||||
* is 111) were already completely explored, it would not be visited again
|
||||
* because we are sure we tried, for example, both 0111 and 1111 (all the
|
||||
* variations of the higher bit) so we don't need to test it again.
|
||||
*
|
||||
* WAIT... YOU HAVE *TWO* TABLES DURING REHASHING!
|
||||
*
|
||||
* Yes, this is true, but we always iterate the smaller one of the tables,
|
||||
* testing also all the expansions of the current cursor into the larger
|
||||
* table. So for example if the current cursor is 101 and we also have a
|
||||
* Yes, this is true, but we always iterate the smaller table first, then
|
||||
* we test all the expansions of the current cursor into the larger
|
||||
* table. For example if the current cursor is 101 and we also have a
|
||||
* larger table of size 16, we also test (0)101 and (1)101 inside the larger
|
||||
* table. This reduces the problem back to having only one table, where
|
||||
* the larger one, if exists, is just an expansion of the smaller one.
|
||||
* the larger one, if it exists, is just an expansion of the smaller one.
|
||||
*
|
||||
* LIMITATIONS
|
||||
*
|
||||
@@ -789,11 +841,11 @@ static unsigned long rev(unsigned long v) {
|
||||
*
|
||||
* The disadvantages resulting from this design are:
|
||||
*
|
||||
* 1) It is possible that we return duplicated elements. However this is usually
|
||||
* 1) It is possible we return elements more than once. However this is usually
|
||||
* easy to deal with in the application level.
|
||||
* 2) The iterator must return multiple elements per call, as it needs to always
|
||||
* return all the keys chained in a given bucket, and all the expansions, so
|
||||
* we are sure we don't miss keys moving.
|
||||
* we are sure we don't miss keys moving during rehashing.
|
||||
* 3) The reverse cursor is somewhat hard to understand at first, but this
|
||||
* comment is supposed to help.
|
||||
*/
|
||||
@@ -950,24 +1002,21 @@ void dictDisableResize(void) {
|
||||
dict_can_resize = 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
/* The following is code that we don't use for Redis currently, but that is part
|
||||
of the library. */
|
||||
|
||||
/* ----------------------- Debugging ------------------------*/
|
||||
/* ------------------------------- Debugging ---------------------------------*/
|
||||
|
||||
#define DICT_STATS_VECTLEN 50
|
||||
static void _dictPrintStatsHt(dictht *ht) {
|
||||
size_t _dictGetStatsHt(char *buf, size_t bufsize, dictht *ht, int tableid) {
|
||||
unsigned long i, slots = 0, chainlen, maxchainlen = 0;
|
||||
unsigned long totchainlen = 0;
|
||||
unsigned long clvector[DICT_STATS_VECTLEN];
|
||||
size_t l = 0;
|
||||
|
||||
if (ht->used == 0) {
|
||||
printf("No stats available for empty dictionaries\n");
|
||||
return;
|
||||
return snprintf(buf,bufsize,
|
||||
"No stats available for empty dictionaries\n");
|
||||
}
|
||||
|
||||
/* Compute stats. */
|
||||
for (i = 0; i < DICT_STATS_VECTLEN; i++) clvector[i] = 0;
|
||||
for (i = 0; i < ht->size; i++) {
|
||||
dictEntry *he;
|
||||
@@ -988,89 +1037,46 @@ static void _dictPrintStatsHt(dictht *ht) {
|
||||
if (chainlen > maxchainlen) maxchainlen = chainlen;
|
||||
totchainlen += chainlen;
|
||||
}
|
||||
printf("Hash table stats:\n");
|
||||
printf(" table size: %ld\n", ht->size);
|
||||
printf(" number of elements: %ld\n", ht->used);
|
||||
printf(" different slots: %ld\n", slots);
|
||||
printf(" max chain length: %ld\n", maxchainlen);
|
||||
printf(" avg chain length (counted): %.02f\n", (float)totchainlen/slots);
|
||||
printf(" avg chain length (computed): %.02f\n", (float)ht->used/slots);
|
||||
printf(" Chain length distribution:\n");
|
||||
|
||||
/* Generate human readable stats. */
|
||||
l += snprintf(buf+l,bufsize-l,
|
||||
"Hash table %d stats (%s):\n"
|
||||
" table size: %ld\n"
|
||||
" number of elements: %ld\n"
|
||||
" different slots: %ld\n"
|
||||
" max chain length: %ld\n"
|
||||
" avg chain length (counted): %.02f\n"
|
||||
" avg chain length (computed): %.02f\n"
|
||||
" Chain length distribution:\n",
|
||||
tableid, (tableid == 0) ? "main hash table" : "rehashing target",
|
||||
ht->size, ht->used, slots, maxchainlen,
|
||||
(float)totchainlen/slots, (float)ht->used/slots);
|
||||
|
||||
for (i = 0; i < DICT_STATS_VECTLEN-1; i++) {
|
||||
if (clvector[i] == 0) continue;
|
||||
printf(" %s%ld: %ld (%.02f%%)\n",(i == DICT_STATS_VECTLEN-1)?">= ":"", i, clvector[i], ((float)clvector[i]/ht->size)*100);
|
||||
if (l >= bufsize) break;
|
||||
l += snprintf(buf+l,bufsize-l,
|
||||
" %s%ld: %ld (%.02f%%)\n",
|
||||
(i == DICT_STATS_VECTLEN-1)?">= ":"",
|
||||
i, clvector[i], ((float)clvector[i]/ht->size)*100);
|
||||
}
|
||||
|
||||
/* Unlike snprintf(), teturn the number of characters actually written. */
|
||||
if (bufsize) buf[bufsize-1] = '\0';
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
void dictPrintStats(dict *d) {
|
||||
_dictPrintStatsHt(&d->ht[0]);
|
||||
if (dictIsRehashing(d)) {
|
||||
printf("-- Rehashing into ht[1]:\n");
|
||||
_dictPrintStatsHt(&d->ht[1]);
|
||||
void dictGetStats(char *buf, size_t bufsize, dict *d) {
|
||||
size_t l;
|
||||
char *orig_buf = buf;
|
||||
size_t orig_bufsize = bufsize;
|
||||
|
||||
l = _dictGetStatsHt(buf,bufsize,&d->ht[0],0);
|
||||
buf += l;
|
||||
bufsize -= l;
|
||||
if (dictIsRehashing(d) && bufsize > 0) {
|
||||
_dictGetStatsHt(buf,bufsize,&d->ht[1],1);
|
||||
}
|
||||
/* Make sure there is a NULL term at the end. */
|
||||
if (orig_bufsize) orig_buf[orig_bufsize-1] = '\0';
|
||||
}
|
||||
|
||||
/* ----------------------- StringCopy Hash Table Type ------------------------*/
|
||||
|
||||
static unsigned int _dictStringCopyHTHashFunction(const void *key)
|
||||
{
|
||||
return dictGenHashFunction(key, strlen(key));
|
||||
}
|
||||
|
||||
static void *_dictStringDup(void *privdata, const void *key)
|
||||
{
|
||||
int len = strlen(key);
|
||||
char *copy = zmalloc(len+1);
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
memcpy(copy, key, len);
|
||||
copy[len] = '\0';
|
||||
return copy;
|
||||
}
|
||||
|
||||
static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1,
|
||||
const void *key2)
|
||||
{
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
return strcmp(key1, key2) == 0;
|
||||
}
|
||||
|
||||
static void _dictStringDestructor(void *privdata, void *key)
|
||||
{
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
zfree(key);
|
||||
}
|
||||
|
||||
dictType dictTypeHeapStringCopyKey = {
|
||||
_dictStringCopyHTHashFunction, /* hash function */
|
||||
_dictStringDup, /* key dup */
|
||||
NULL, /* val dup */
|
||||
_dictStringCopyHTKeyCompare, /* key compare */
|
||||
_dictStringDestructor, /* key destructor */
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
/* This is like StringCopy but does not auto-duplicate the key.
|
||||
* It's used for intepreter's shared strings. */
|
||||
dictType dictTypeHeapStrings = {
|
||||
_dictStringCopyHTHashFunction, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
_dictStringCopyHTKeyCompare, /* key compare */
|
||||
_dictStringDestructor, /* key destructor */
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
/* This is like StringCopy but also automatically handle dynamic
|
||||
* allocated C strings as values. */
|
||||
dictType dictTypeHeapStringCopyKeyValue = {
|
||||
_dictStringCopyHTHashFunction, /* hash function */
|
||||
_dictStringDup, /* key dup */
|
||||
_dictStringDup, /* val dup */
|
||||
_dictStringCopyHTKeyCompare, /* key compare */
|
||||
_dictStringDestructor, /* key destructor */
|
||||
_dictStringDestructor, /* val destructor */
|
||||
};
|
||||
#endif
|
||||
|
||||
+13
-6
@@ -50,6 +50,7 @@ typedef struct dictEntry {
|
||||
void *val;
|
||||
uint64_t u64;
|
||||
int64_t s64;
|
||||
double d;
|
||||
} v;
|
||||
struct dictEntry *next;
|
||||
} dictEntry;
|
||||
@@ -76,7 +77,7 @@ typedef struct dict {
|
||||
dictType *type;
|
||||
void *privdata;
|
||||
dictht ht[2];
|
||||
int rehashidx; /* rehashing not in progress if rehashidx == -1 */
|
||||
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
|
||||
int iterators; /* number of iterators currently running */
|
||||
} dict;
|
||||
|
||||
@@ -86,9 +87,11 @@ typedef struct dict {
|
||||
* should be called while iterating. */
|
||||
typedef struct dictIterator {
|
||||
dict *d;
|
||||
int table, index, safe;
|
||||
long index;
|
||||
int table, safe;
|
||||
dictEntry *entry, *nextEntry;
|
||||
long long fingerprint; /* unsafe iterator fingerprint for misuse detection */
|
||||
/* unsafe iterator fingerprint for misuse detection. */
|
||||
long long fingerprint;
|
||||
} dictIterator;
|
||||
|
||||
typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
@@ -114,6 +117,9 @@ typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
#define dictSetUnsignedIntegerVal(entry, _val_) \
|
||||
do { entry->v.u64 = _val_; } while(0)
|
||||
|
||||
#define dictSetDoubleVal(entry, _val_) \
|
||||
do { entry->v.d = _val_; } while(0)
|
||||
|
||||
#define dictFreeKey(d, entry) \
|
||||
if ((d)->type->keyDestructor) \
|
||||
(d)->type->keyDestructor((d)->privdata, (entry)->key)
|
||||
@@ -135,9 +141,10 @@ typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
#define dictGetVal(he) ((he)->v.val)
|
||||
#define dictGetSignedIntegerVal(he) ((he)->v.s64)
|
||||
#define dictGetUnsignedIntegerVal(he) ((he)->v.u64)
|
||||
#define dictGetDoubleVal(he) ((he)->v.d)
|
||||
#define dictSlots(d) ((d)->ht[0].size+(d)->ht[1].size)
|
||||
#define dictSize(d) ((d)->ht[0].used+(d)->ht[1].used)
|
||||
#define dictIsRehashing(ht) ((ht)->rehashidx != -1)
|
||||
#define dictIsRehashing(d) ((d)->rehashidx != -1)
|
||||
|
||||
/* API */
|
||||
dict *dictCreate(dictType *type, void *privDataPtr);
|
||||
@@ -157,8 +164,8 @@ dictIterator *dictGetSafeIterator(dict *d);
|
||||
dictEntry *dictNext(dictIterator *iter);
|
||||
void dictReleaseIterator(dictIterator *iter);
|
||||
dictEntry *dictGetRandomKey(dict *d);
|
||||
int dictGetRandomKeys(dict *d, dictEntry **des, int count);
|
||||
void dictPrintStats(dict *d);
|
||||
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
|
||||
void dictGetStats(char *buf, size_t bufsize, dict *d);
|
||||
unsigned int dictGenHashFunction(const void *key, int len);
|
||||
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
|
||||
void dictEmpty(dict *d, void(callback)(void*));
|
||||
|
||||
+6
-2
@@ -101,12 +101,16 @@ uint64_t intrev64(uint64_t v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
#ifdef TESTMAIN
|
||||
#ifdef REDIS_TEST
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
#define UNUSED(x) (void)(x)
|
||||
int endianconvTest(int argc, char *argv[]) {
|
||||
char buf[32];
|
||||
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
|
||||
sprintf(buf,"ciaoroma");
|
||||
memrev16(buf);
|
||||
printf("%s\n", buf);
|
||||
|
||||
@@ -71,4 +71,8 @@ uint64_t intrev64(uint64_t v);
|
||||
#define ntohu64(v) intrev64(v)
|
||||
#endif
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int endianconvTest(int argc, char *argv[]);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
|
||||
#if defined(__linux__)
|
||||
#define _GNU_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(_AIX)
|
||||
#define _ALL_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__OpenBSD__)
|
||||
|
||||
@@ -0,0 +1,706 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
||||
* Copyright (c) 2015, Salvatore Sanfilippo <antirez@gmail.com>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "geo.h"
|
||||
#include "geohash_helper.h"
|
||||
|
||||
/* Things exported from t_zset.c only for geo.c, since it is the only other
|
||||
* part of Redis that requires close zset introspection. */
|
||||
unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec *range);
|
||||
int zslValueLteMax(double value, zrangespec *spec);
|
||||
|
||||
/* ====================================================================
|
||||
* This file implements the following commands:
|
||||
*
|
||||
* - geoadd - add coordinates for value to geoset
|
||||
* - georadius - search radius by coordinates in geoset
|
||||
* - georadiusbymember - search radius based on geoset member position
|
||||
* ==================================================================== */
|
||||
|
||||
/* ====================================================================
|
||||
* geoArray implementation
|
||||
* ==================================================================== */
|
||||
|
||||
/* Create a new array of geoPoints. */
|
||||
geoArray *geoArrayCreate(void) {
|
||||
geoArray *ga = zmalloc(sizeof(*ga));
|
||||
/* It gets allocated on first geoArrayAppend() call. */
|
||||
ga->array = NULL;
|
||||
ga->buckets = 0;
|
||||
ga->used = 0;
|
||||
return ga;
|
||||
}
|
||||
|
||||
/* Add a new entry and return its pointer so that the caller can populate
|
||||
* it with data. */
|
||||
geoPoint *geoArrayAppend(geoArray *ga) {
|
||||
if (ga->used == ga->buckets) {
|
||||
ga->buckets = (ga->buckets == 0) ? 8 : ga->buckets*2;
|
||||
ga->array = zrealloc(ga->array,sizeof(geoPoint)*ga->buckets);
|
||||
}
|
||||
geoPoint *gp = ga->array+ga->used;
|
||||
ga->used++;
|
||||
return gp;
|
||||
}
|
||||
|
||||
/* Destroy a geoArray created with geoArrayCreate(). */
|
||||
void geoArrayFree(geoArray *ga) {
|
||||
size_t i;
|
||||
for (i = 0; i < ga->used; i++) sdsfree(ga->array[i].member);
|
||||
zfree(ga->array);
|
||||
zfree(ga);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* Helpers
|
||||
* ==================================================================== */
|
||||
int decodeGeohash(double bits, double *xy) {
|
||||
GeoHashBits hash = { .bits = (uint64_t)bits, .step = GEO_STEP_MAX };
|
||||
return geohashDecodeToLongLatWGS84(hash, xy);
|
||||
}
|
||||
|
||||
/* Input Argument Helper */
|
||||
/* Take a pointer to the latitude arg then use the next arg for longitude.
|
||||
* On parse error REDIS_ERR is returned, otherwise REDIS_OK. */
|
||||
int extractLongLatOrReply(redisClient *c, robj **argv,
|
||||
double *xy) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (getDoubleFromObjectOrReply(c, argv[i], xy + i, NULL) !=
|
||||
REDIS_OK) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (xy[0] < GEO_LONG_MIN || xy[0] > GEO_LONG_MAX ||
|
||||
xy[1] < GEO_LAT_MIN || xy[1] > GEO_LAT_MAX) {
|
||||
addReplySds(c, sdscatprintf(sdsempty(),
|
||||
"-ERR invalid longitude,latitude pair %f,%f\r\n",xy[0],xy[1]));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Input Argument Helper */
|
||||
/* Decode lat/long from a zset member's score.
|
||||
* Returns REDIS_OK on successful decoding, otherwise REDIS_ERR is returned. */
|
||||
int longLatFromMember(robj *zobj, robj *member, double *xy) {
|
||||
double score = 0;
|
||||
|
||||
if (zsetScore(zobj, member, &score) == REDIS_ERR) return REDIS_ERR;
|
||||
if (!decodeGeohash(score, xy)) return REDIS_ERR;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Check that the unit argument matches one of the known units, and returns
|
||||
* the conversion factor to meters (you need to divide meters by the conversion
|
||||
* factor to convert to the right unit).
|
||||
*
|
||||
* If the unit is not valid, an error is reported to the client, and a value
|
||||
* less than zero is returned. */
|
||||
double extractUnitOrReply(redisClient *c, robj *unit) {
|
||||
char *u = unit->ptr;
|
||||
|
||||
if (!strcmp(u, "m")) {
|
||||
return 1;
|
||||
} else if (!strcmp(u, "km")) {
|
||||
return 1000;
|
||||
} else if (!strcmp(u, "ft")) {
|
||||
return 0.3048;
|
||||
} else if (!strcmp(u, "mi")) {
|
||||
return 1609.34;
|
||||
} else {
|
||||
addReplyError(c,
|
||||
"unsupported unit provided. please use m, km, ft, mi");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Input Argument Helper.
|
||||
* Extract the dinstance from the specified two arguments starting at 'argv'
|
||||
* that shouldbe in the form: <number> <unit> and return the dinstance in the
|
||||
* specified unit on success. *conversino is populated with the coefficient
|
||||
* to use in order to convert meters to the unit.
|
||||
*
|
||||
* On error a value less than zero is returned. */
|
||||
double extractDistanceOrReply(redisClient *c, robj **argv,
|
||||
double *conversion) {
|
||||
double distance;
|
||||
if (getDoubleFromObjectOrReply(c, argv[0], &distance,
|
||||
"need numeric radius") != REDIS_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
double to_meters = extractUnitOrReply(c,argv[1]);
|
||||
if (to_meters < 0) return -1;
|
||||
|
||||
if (conversion) *conversion = to_meters;
|
||||
return distance * to_meters;
|
||||
}
|
||||
|
||||
/* The defailt addReplyDouble has too much accuracy. We use this
|
||||
* for returning location distances. "5.2145 meters away" is nicer
|
||||
* than "5.2144992818115 meters away." We provide 4 digits after the dot
|
||||
* so that the returned value is decently accurate even when the unit is
|
||||
* the kilometer. */
|
||||
void addReplyDoubleDistance(redisClient *c, double d) {
|
||||
char dbuf[128];
|
||||
int dlen = snprintf(dbuf, sizeof(dbuf), "%.4f", d);
|
||||
addReplyBulkCBuffer(c, dbuf, dlen);
|
||||
}
|
||||
|
||||
/* Helper function for geoGetPointsInRange(): given a sorted set score
|
||||
* representing a point, and another point (the center of our search) and
|
||||
* a radius, appends this entry as a geoPoint into the specified geoArray
|
||||
* only if the point is within the search area.
|
||||
*
|
||||
* returns REDIS_OK if the point is included, or REIDS_ERR if it is outside. */
|
||||
int geoAppendIfWithinRadius(geoArray *ga, double lon, double lat, double radius, double score, sds member) {
|
||||
double distance, xy[2];
|
||||
|
||||
if (!decodeGeohash(score,xy)) return REDIS_ERR; /* Can't decode. */
|
||||
/* Note that geohashGetDistanceIfInRadiusWGS84() takes arguments in
|
||||
* reverse order: longitude first, latitude later. */
|
||||
if (!geohashGetDistanceIfInRadiusWGS84(lon,lat, xy[0], xy[1],
|
||||
radius, &distance))
|
||||
{
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Append the new element. */
|
||||
geoPoint *gp = geoArrayAppend(ga);
|
||||
gp->longitude = xy[0];
|
||||
gp->latitude = xy[1];
|
||||
gp->dist = distance;
|
||||
gp->member = member;
|
||||
gp->score = score;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Query a Redis sorted set to extract all the elements between 'min' and
|
||||
* 'max', appending them into the array of geoPoint structures 'gparray'.
|
||||
* The command returns the number of elements added to the array.
|
||||
*
|
||||
* Elements which are farest than 'radius' from the specified 'x' and 'y'
|
||||
* coordinates are not included.
|
||||
*
|
||||
* The ability of this function to append to an existing set of points is
|
||||
* important for good performances because querying by radius is performed
|
||||
* using multiple queries to the sorted set, that we later need to sort
|
||||
* via qsort. Similarly we need to be able to reject points outside the search
|
||||
* radius area ASAP in order to allocate and process more points than needed. */
|
||||
int geoGetPointsInRange(robj *zobj, double min, double max, double lon, double lat, double radius, geoArray *ga) {
|
||||
/* minex 0 = include min in range; maxex 1 = exclude max in range */
|
||||
/* That's: min <= val < max */
|
||||
zrangespec range = { .min = min, .max = max, .minex = 0, .maxex = 1 };
|
||||
size_t origincount = ga->used;
|
||||
sds member;
|
||||
|
||||
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *zl = zobj->ptr;
|
||||
unsigned char *eptr, *sptr;
|
||||
unsigned char *vstr = NULL;
|
||||
unsigned int vlen = 0;
|
||||
long long vlong = 0;
|
||||
double score = 0;
|
||||
|
||||
if ((eptr = zzlFirstInRange(zl, &range)) == NULL) {
|
||||
/* Nothing exists starting at our min. No results. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
sptr = ziplistNext(zl, eptr);
|
||||
while (eptr) {
|
||||
score = zzlGetScore(sptr);
|
||||
|
||||
/* If we fell out of range, break. */
|
||||
if (!zslValueLteMax(score, &range))
|
||||
break;
|
||||
|
||||
/* We know the element exists. ziplistGet should always succeed */
|
||||
ziplistGet(eptr, &vstr, &vlen, &vlong);
|
||||
member = (vstr == NULL) ? sdsfromlonglong(vlong) :
|
||||
sdsnewlen(vstr,vlen);
|
||||
if (geoAppendIfWithinRadius(ga,lon,lat,radius,score,member)
|
||||
== REDIS_ERR) sdsfree(member);
|
||||
zzlNext(zl, &eptr, &sptr);
|
||||
}
|
||||
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
|
||||
zset *zs = zobj->ptr;
|
||||
zskiplist *zsl = zs->zsl;
|
||||
zskiplistNode *ln;
|
||||
|
||||
if ((ln = zslFirstInRange(zsl, &range)) == NULL) {
|
||||
/* Nothing exists starting at our min. No results. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (ln) {
|
||||
robj *o = ln->obj;
|
||||
/* Abort when the node is no longer in range. */
|
||||
if (!zslValueLteMax(ln->score, &range))
|
||||
break;
|
||||
|
||||
member = (o->encoding == REDIS_ENCODING_INT) ?
|
||||
sdsfromlonglong((long)o->ptr) :
|
||||
sdsdup(o->ptr);
|
||||
if (geoAppendIfWithinRadius(ga,lon,lat,radius,ln->score,member)
|
||||
== REDIS_ERR) sdsfree(member);
|
||||
ln = ln->level[0].forward;
|
||||
}
|
||||
}
|
||||
return ga->used - origincount;
|
||||
}
|
||||
|
||||
/* Compute the sorted set scores min (inclusive), max (exclusive) we should
|
||||
* query in order to retrieve all the elements inside the specified area
|
||||
* 'hash'. The two scores are returned by reference in *min and *max. */
|
||||
void scoresOfGeoHashBox(GeoHashBits hash, GeoHashFix52Bits *min, GeoHashFix52Bits *max) {
|
||||
/* We want to compute the sorted set scores that will include all the
|
||||
* elements inside the specified Geohash 'hash', which has as many
|
||||
* bits as specified by hash.step * 2.
|
||||
*
|
||||
* So if step is, for example, 3, and the hash value in binary
|
||||
* is 101010, since our score is 52 bits we want every element which
|
||||
* is in binary: 101010?????????????????????????????????????????????
|
||||
* Where ? can be 0 or 1.
|
||||
*
|
||||
* To get the min score we just use the initial hash value left
|
||||
* shifted enough to get the 52 bit value. Later we increment the
|
||||
* 6 bit prefis (see the hash.bits++ statement), and get the new
|
||||
* prefix: 101011, which we align again to 52 bits to get the maximum
|
||||
* value (which is excluded from the search). So we get everything
|
||||
* between the two following scores (represented in binary):
|
||||
*
|
||||
* 1010100000000000000000000000000000000000000000000000 (included)
|
||||
* and
|
||||
* 1010110000000000000000000000000000000000000000000000 (excluded).
|
||||
*/
|
||||
*min = geohashAlign52Bits(hash);
|
||||
hash.bits++;
|
||||
*max = geohashAlign52Bits(hash);
|
||||
}
|
||||
|
||||
/* Obtain all members between the min/max of this geohash bounding box.
|
||||
* Populate a geoArray of GeoPoints by calling geoGetPointsInRange().
|
||||
* Return the number of points added to the array. */
|
||||
int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, double lon, double lat, double radius) {
|
||||
GeoHashFix52Bits min, max;
|
||||
|
||||
scoresOfGeoHashBox(hash,&min,&max);
|
||||
return geoGetPointsInRange(zobj, min, max, lon, lat, radius, ga);
|
||||
}
|
||||
|
||||
/* Search all eight neighbors + self geohash box */
|
||||
int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, double lon, double lat, double radius, geoArray *ga) {
|
||||
GeoHashBits neighbors[9];
|
||||
unsigned int i, count = 0;
|
||||
|
||||
neighbors[0] = n.hash;
|
||||
neighbors[1] = n.neighbors.north;
|
||||
neighbors[2] = n.neighbors.south;
|
||||
neighbors[3] = n.neighbors.east;
|
||||
neighbors[4] = n.neighbors.west;
|
||||
neighbors[5] = n.neighbors.north_east;
|
||||
neighbors[6] = n.neighbors.north_west;
|
||||
neighbors[7] = n.neighbors.south_east;
|
||||
neighbors[8] = n.neighbors.south_west;
|
||||
|
||||
/* For each neighbor (*and* our own hashbox), get all the matching
|
||||
* members and add them to the potential result list. */
|
||||
for (i = 0; i < sizeof(neighbors) / sizeof(*neighbors); i++) {
|
||||
if (HASHISZERO(neighbors[i]))
|
||||
continue;
|
||||
count += membersOfGeoHashBox(zobj, neighbors[i], ga, lon, lat, radius);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Sort comparators for qsort() */
|
||||
static int sort_gp_asc(const void *a, const void *b) {
|
||||
const struct geoPoint *gpa = a, *gpb = b;
|
||||
/* We can't do adist - bdist because they are doubles and
|
||||
* the comparator returns an int. */
|
||||
if (gpa->dist > gpb->dist)
|
||||
return 1;
|
||||
else if (gpa->dist == gpb->dist)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int sort_gp_desc(const void *a, const void *b) {
|
||||
return -sort_gp_asc(a, b);
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* Commands
|
||||
* ==================================================================== */
|
||||
|
||||
/* GEOADD key long lat name [long2 lat2 name2 ... longN latN nameN] */
|
||||
void geoaddCommand(redisClient *c) {
|
||||
/* Check arguments number for sanity. */
|
||||
if ((c->argc - 2) % 3 != 0) {
|
||||
/* Need an odd number of arguments if we got this far... */
|
||||
addReplyError(c, "syntax error. Try GEOADD key [x1] [y1] [name1] "
|
||||
"[x2] [y2] [name2] ... ");
|
||||
return;
|
||||
}
|
||||
|
||||
int elements = (c->argc - 2) / 3;
|
||||
int argc = 2+elements*2; /* ZADD key score ele ... */
|
||||
robj **argv = zcalloc(argc*sizeof(robj*));
|
||||
argv[0] = createRawStringObject("zadd",4);
|
||||
argv[1] = c->argv[1]; /* key */
|
||||
incrRefCount(argv[1]);
|
||||
|
||||
/* Create the argument vector to call ZADD in order to add all
|
||||
* the score,value pairs to the requested zset, where score is actually
|
||||
* an encoded version of lat,long. */
|
||||
int i;
|
||||
for (i = 0; i < elements; i++) {
|
||||
double xy[2];
|
||||
|
||||
if (extractLongLatOrReply(c, (c->argv+2)+(i*3),xy) == REDIS_ERR) {
|
||||
for (i = 0; i < argc; i++)
|
||||
if (argv[i]) decrRefCount(argv[i]);
|
||||
zfree(argv);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Turn the coordinates into the score of the element. */
|
||||
GeoHashBits hash;
|
||||
geohashEncodeWGS84(xy[0], xy[1], GEO_STEP_MAX, &hash);
|
||||
GeoHashFix52Bits bits = geohashAlign52Bits(hash);
|
||||
robj *score = createObject(REDIS_STRING, sdsfromlonglong(bits));
|
||||
robj *val = c->argv[2 + i * 3 + 2];
|
||||
argv[2+i*2] = score;
|
||||
argv[3+i*2] = val;
|
||||
incrRefCount(val);
|
||||
}
|
||||
|
||||
/* Finally call ZADD that will do the work for us. */
|
||||
replaceClientCommandVector(c,argc,argv);
|
||||
zaddCommand(c);
|
||||
}
|
||||
|
||||
#define SORT_NONE 0
|
||||
#define SORT_ASC 1
|
||||
#define SORT_DESC 2
|
||||
|
||||
#define RADIUS_COORDS 1
|
||||
#define RADIUS_MEMBER 2
|
||||
|
||||
/* GEORADIUS key x y radius unit [WITHDIST] [WITHHASH] [WITHCOORD] [ASC|DESC]
|
||||
* [COUNT count]
|
||||
* GEORADIUSBYMEMBER key member radius unit ... options ... */
|
||||
void georadiusGeneric(redisClient *c, int type) {
|
||||
robj *key = c->argv[1];
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.emptymultibulk)) == NULL ||
|
||||
checkType(c, zobj, REDIS_ZSET)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find long/lat to use for radius search based on inquiry type */
|
||||
int base_args;
|
||||
double xy[2] = { 0 };
|
||||
if (type == RADIUS_COORDS) {
|
||||
base_args = 6;
|
||||
if (extractLongLatOrReply(c, c->argv + 2, xy) == REDIS_ERR)
|
||||
return;
|
||||
} else if (type == RADIUS_MEMBER) {
|
||||
base_args = 5;
|
||||
robj *member = c->argv[2];
|
||||
if (longLatFromMember(zobj, member, xy) == REDIS_ERR) {
|
||||
addReplyError(c, "could not decode requested zset member");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
addReplyError(c, "unknown georadius search type");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Extract radius and units from arguments */
|
||||
double radius_meters = 0, conversion = 1;
|
||||
if ((radius_meters = extractDistanceOrReply(c, c->argv + base_args - 2,
|
||||
&conversion)) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Discover and populate all optional parameters. */
|
||||
int withdist = 0, withhash = 0, withcoords = 0;
|
||||
int sort = SORT_NONE;
|
||||
long long count = 0;
|
||||
if (c->argc > base_args) {
|
||||
int remaining = c->argc - base_args;
|
||||
for (int i = 0; i < remaining; i++) {
|
||||
char *arg = c->argv[base_args + i]->ptr;
|
||||
if (!strcasecmp(arg, "withdist")) {
|
||||
withdist = 1;
|
||||
} else if (!strcasecmp(arg, "withhash")) {
|
||||
withhash = 1;
|
||||
} else if (!strcasecmp(arg, "withcoord")) {
|
||||
withcoords = 1;
|
||||
} else if (!strcasecmp(arg, "asc")) {
|
||||
sort = SORT_ASC;
|
||||
} else if (!strcasecmp(arg, "desc")) {
|
||||
sort = SORT_DESC;
|
||||
} else if (!strcasecmp(arg, "count") && remaining > 0) {
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[base_args+i+1],
|
||||
&count, NULL) != REDIS_OK) return;
|
||||
if (count <= 0) {
|
||||
addReplyError(c,"COUNT must be > 0");
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
} else {
|
||||
addReply(c, shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* COUNT without ordering does not make much sense, force ASC
|
||||
* ordering if COUNT was specified but no sorting was requested. */
|
||||
if (count != 0 && sort == SORT_NONE) sort = SORT_ASC;
|
||||
|
||||
/* Get all neighbor geohash boxes for our radius search */
|
||||
GeoHashRadius georadius =
|
||||
geohashGetAreasByRadiusWGS84(xy[0], xy[1], radius_meters);
|
||||
|
||||
/* Search the zset for all matching points */
|
||||
geoArray *ga = geoArrayCreate();
|
||||
membersOfAllNeighbors(zobj, georadius, xy[0], xy[1], radius_meters, ga);
|
||||
|
||||
/* If no matching results, the user gets an empty reply. */
|
||||
if (ga->used == 0) {
|
||||
addReply(c, shared.emptymultibulk);
|
||||
geoArrayFree(ga);
|
||||
return;
|
||||
}
|
||||
|
||||
long result_length = ga->used;
|
||||
long option_length = 0;
|
||||
|
||||
/* Our options are self-contained nested multibulk replies, so we
|
||||
* only need to track how many of those nested replies we return. */
|
||||
if (withdist)
|
||||
option_length++;
|
||||
|
||||
if (withcoords)
|
||||
option_length++;
|
||||
|
||||
if (withhash)
|
||||
option_length++;
|
||||
|
||||
/* The multibulk len we send is exactly result_length. The result is either
|
||||
* all strings of just zset members *or* a nested multi-bulk reply
|
||||
* containing the zset member string _and_ all the additional options the
|
||||
* user enabled for this request. */
|
||||
addReplyMultiBulkLen(c, (count == 0 || result_length < count) ?
|
||||
result_length : count);
|
||||
|
||||
/* Process [optional] requested sorting */
|
||||
if (sort == SORT_ASC) {
|
||||
qsort(ga->array, result_length, sizeof(geoPoint), sort_gp_asc);
|
||||
} else if (sort == SORT_DESC) {
|
||||
qsort(ga->array, result_length, sizeof(geoPoint), sort_gp_desc);
|
||||
}
|
||||
|
||||
/* Finally send results back to the caller */
|
||||
int i;
|
||||
for (i = 0; i < result_length; i++) {
|
||||
geoPoint *gp = ga->array+i;
|
||||
gp->dist /= conversion; /* Fix according to unit. */
|
||||
|
||||
/* If we have options in option_length, return each sub-result
|
||||
* as a nested multi-bulk. Add 1 to account for result value itself. */
|
||||
if (option_length)
|
||||
addReplyMultiBulkLen(c, option_length + 1);
|
||||
|
||||
addReplyBulkSds(c,gp->member);
|
||||
gp->member = NULL;
|
||||
|
||||
if (withdist)
|
||||
addReplyDoubleDistance(c, gp->dist);
|
||||
|
||||
if (withhash)
|
||||
addReplyLongLong(c, gp->score);
|
||||
|
||||
if (withcoords) {
|
||||
addReplyMultiBulkLen(c, 2);
|
||||
addReplyDouble(c, gp->longitude);
|
||||
addReplyDouble(c, gp->latitude);
|
||||
}
|
||||
|
||||
/* Stop if COUNT was specified and we already provided the
|
||||
* specified number of elements. */
|
||||
if (count != 0 && count == i+1) break;
|
||||
}
|
||||
geoArrayFree(ga);
|
||||
}
|
||||
|
||||
/* GEORADIUS wrapper function. */
|
||||
void georadiusCommand(redisClient *c) {
|
||||
georadiusGeneric(c, RADIUS_COORDS);
|
||||
}
|
||||
|
||||
/* GEORADIUSBYMEMBER wrapper function. */
|
||||
void georadiusByMemberCommand(redisClient *c) {
|
||||
georadiusGeneric(c, RADIUS_MEMBER);
|
||||
}
|
||||
|
||||
/* GEOHASH key ele1 ele2 ... eleN
|
||||
*
|
||||
* Returns an array with an 11 characters geohash representation of the
|
||||
* position of the specified elements. */
|
||||
void geohashCommand(redisClient *c) {
|
||||
char *geoalphabet= "0123456789bcdefghjkmnpqrstuvwxyz";
|
||||
int j;
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptymultibulk))
|
||||
== NULL || checkType(c, zobj, REDIS_ZSET)) return;
|
||||
|
||||
/* Geohash elements one after the other, using a null bulk reply for
|
||||
* missing elements. */
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
double score;
|
||||
if (zsetScore(zobj, c->argv[j], &score) == REDIS_ERR) {
|
||||
addReply(c,shared.nullbulk);
|
||||
} else {
|
||||
/* The internal format we use for geocoding is a bit different
|
||||
* than the standard, since we use as initial latitude range
|
||||
* -85,85, while the normal geohashing algorithm uses -90,90.
|
||||
* So we have to decode our position and re-encode using the
|
||||
* standard ranges in order to output a valid geohash string. */
|
||||
|
||||
/* Decode... */
|
||||
double xy[2];
|
||||
if (!decodeGeohash(score,xy)) {
|
||||
addReply(c,shared.nullbulk);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Re-encode */
|
||||
GeoHashRange r[2];
|
||||
GeoHashBits hash;
|
||||
r[0].min = -180;
|
||||
r[0].max = 180;
|
||||
r[1].min = -90;
|
||||
r[1].max = 90;
|
||||
geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
|
||||
|
||||
char buf[12];
|
||||
int i;
|
||||
for (i = 0; i < 11; i++) {
|
||||
int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
|
||||
buf[i] = geoalphabet[idx];
|
||||
}
|
||||
buf[11] = '\0';
|
||||
addReplyBulkCBuffer(c,buf,11);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* GEOPOS key ele1 ele2 ... eleN
|
||||
*
|
||||
* Returns an array of two-items arrays representing the x,y position of each
|
||||
* element specified in the arguments. For missing elements NULL is returned. */
|
||||
void geoposCommand(redisClient *c) {
|
||||
int j;
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptymultibulk))
|
||||
== NULL || checkType(c, zobj, REDIS_ZSET)) return;
|
||||
|
||||
/* Report elements one after the other, using a null bulk reply for
|
||||
* missing elements. */
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
double score;
|
||||
if (zsetScore(zobj, c->argv[j], &score) == REDIS_ERR) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
} else {
|
||||
/* Decode... */
|
||||
double xy[2];
|
||||
if (!decodeGeohash(score,xy)) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
continue;
|
||||
}
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyDouble(c,xy[0]);
|
||||
addReplyDouble(c,xy[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* GEODIST key ele1 ele2 [unit]
|
||||
*
|
||||
* Return the distance, in meters by default, otherwise accordig to "unit",
|
||||
* between points ele1 and ele2. If one or more elements are missing NULL
|
||||
* is returned. */
|
||||
void geodistCommand(redisClient *c) {
|
||||
double to_meter = 1;
|
||||
|
||||
/* Check if there is the unit to extract, otherwise assume meters. */
|
||||
if (c->argc == 5) {
|
||||
to_meter = extractUnitOrReply(c,c->argv[4]);
|
||||
if (to_meter < 0) return;
|
||||
} else if (c->argc > 5) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptybulk))
|
||||
== NULL || checkType(c, zobj, REDIS_ZSET)) return;
|
||||
|
||||
/* Get the scores. We need both otherwise NULL is returned. */
|
||||
double score1, score2, xyxy[4];
|
||||
if (zsetScore(zobj, c->argv[2], &score1) == REDIS_ERR ||
|
||||
zsetScore(zobj, c->argv[3], &score2) == REDIS_ERR)
|
||||
{
|
||||
addReply(c,shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Decode & compute the distance. */
|
||||
if (!decodeGeohash(score1,xyxy) || !decodeGeohash(score2,xyxy+2))
|
||||
addReply(c,shared.nullbulk);
|
||||
else
|
||||
addReplyDouble(c,
|
||||
geohashGetDistance(xyxy[0],xyxy[1],xyxy[2],xyxy[3]) / to_meter);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef __GEO_H__
|
||||
#define __GEO_H__
|
||||
|
||||
#include "redis.h"
|
||||
|
||||
/* Structures used inside geo.c in order to represent points and array of
|
||||
* points on the earth. */
|
||||
typedef struct geoPoint {
|
||||
double longitude;
|
||||
double latitude;
|
||||
double dist;
|
||||
double score;
|
||||
char *member;
|
||||
} geoPoint;
|
||||
|
||||
typedef struct geoArray {
|
||||
struct geoPoint *array;
|
||||
size_t buckets;
|
||||
size_t used;
|
||||
} geoArray;
|
||||
|
||||
#endif
|
||||
+2
-2
@@ -651,8 +651,8 @@ struct commandHelp {
|
||||
0,
|
||||
"1.0.0" },
|
||||
{ "SPOP",
|
||||
"key",
|
||||
"Remove and return a random member from a set",
|
||||
"key [count]",
|
||||
"Remove and return one or multiple random members from a set",
|
||||
3,
|
||||
"1.0.0" },
|
||||
{ "SRANDMEMBER",
|
||||
|
||||
+14
-6
@@ -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]
|
||||
@@ -1233,7 +1233,7 @@ void pfcountCommand(redisClient *c) {
|
||||
*
|
||||
* The user specified a single key. Either return the cached value
|
||||
* or compute one and update the cache. */
|
||||
o = lookupKeyRead(c->db,c->argv[1]);
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (o == NULL) {
|
||||
/* No key? Cardinality is zero since no element was added, otherwise
|
||||
* we would have a key as HLLADD creates it as a side effect. */
|
||||
@@ -1349,7 +1349,7 @@ void pfmergeCommand(redisClient *c) {
|
||||
* Something that is not easy to test from within the outside. */
|
||||
#define HLL_TEST_CYCLES 1000
|
||||
void pfselftestCommand(redisClient *c) {
|
||||
int j, i;
|
||||
unsigned int j, i;
|
||||
sds bitcounters = sdsnewlen(NULL,HLL_DENSE_SIZE);
|
||||
struct hllhdr *hdr = (struct hllhdr*) bitcounters, *hdr2;
|
||||
robj *o = NULL;
|
||||
@@ -1386,7 +1386,7 @@ void pfselftestCommand(redisClient *c) {
|
||||
* The test adds unique elements and check that the estimated value
|
||||
* is always reasonable bounds.
|
||||
*
|
||||
* We check that the error is smaller than 4 times than the expected
|
||||
* We check that the error is smaller than a few times than the expected
|
||||
* standard error, to make it very unlikely for the test to fail because
|
||||
* of a "bad" run.
|
||||
*
|
||||
@@ -1422,8 +1422,16 @@ void pfselftestCommand(redisClient *c) {
|
||||
/* Check error. */
|
||||
if (j == checkpoint) {
|
||||
int64_t abserr = checkpoint - (int64_t)hllCount(hdr,NULL);
|
||||
uint64_t maxerr = ceil(relerr*6*checkpoint);
|
||||
|
||||
/* Adjust the max error we expect for cardinality 10
|
||||
* since from time to time it is statistically likely to get
|
||||
* much higher error due to collision, resulting into a false
|
||||
* positive. */
|
||||
if (j == 10) maxerr = 1;
|
||||
|
||||
if (abserr < 0) abserr = -abserr;
|
||||
if (abserr > (uint64_t)(relerr*4*checkpoint)) {
|
||||
if (abserr > (int64_t)maxerr) {
|
||||
addReplyErrorFormat(c,
|
||||
"TESTFAILED Too big error. card:%llu abserr:%llu",
|
||||
(unsigned long long) checkpoint,
|
||||
@@ -1450,7 +1458,7 @@ void pfdebugCommand(redisClient *c) {
|
||||
robj *o;
|
||||
int j;
|
||||
|
||||
o = lookupKeyRead(c->db,c->argv[2]);
|
||||
o = lookupKeyWrite(c->db,c->argv[2]);
|
||||
if (o == NULL) {
|
||||
addReplyError(c,"The specified key does not exist");
|
||||
return;
|
||||
|
||||
+31
-22
@@ -133,7 +133,7 @@ static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
|
||||
}
|
||||
|
||||
while(max >= min) {
|
||||
mid = (min+max)/2;
|
||||
mid = ((unsigned int)min + (unsigned int)max) >> 1;
|
||||
cur = _intsetGet(is,mid);
|
||||
if (value > cur) {
|
||||
min = mid+1;
|
||||
@@ -281,44 +281,46 @@ size_t intsetBlobLen(intset *is) {
|
||||
return sizeof(intset)+intrev32ifbe(is->length)*intrev32ifbe(is->encoding);
|
||||
}
|
||||
|
||||
#ifdef INTSET_TEST_MAIN
|
||||
#ifdef REDIS_TEST
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
void intsetRepr(intset *is) {
|
||||
int i;
|
||||
for (i = 0; i < intrev32ifbe(is->length); i++) {
|
||||
#if 0
|
||||
static void intsetRepr(intset *is) {
|
||||
for (uint32_t i = 0; i < intrev32ifbe(is->length); i++) {
|
||||
printf("%lld\n", (uint64_t)_intsetGet(is,i));
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void error(char *err) {
|
||||
static void error(char *err) {
|
||||
printf("%s\n", err);
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
void ok(void) {
|
||||
static void ok(void) {
|
||||
printf("OK\n");
|
||||
}
|
||||
|
||||
long long usec(void) {
|
||||
static long long usec(void) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv,NULL);
|
||||
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
|
||||
}
|
||||
|
||||
#define assert(_e) ((_e)?(void)0:(_assert(#_e,__FILE__,__LINE__),exit(1)))
|
||||
void _assert(char *estr, char *file, int line) {
|
||||
static void _assert(char *estr, char *file, int line) {
|
||||
printf("\n\n=== ASSERTION FAILED ===\n");
|
||||
printf("==> %s:%d '%s' is not true\n",file,line,estr);
|
||||
}
|
||||
|
||||
intset *createSet(int bits, int size) {
|
||||
static intset *createSet(int bits, int size) {
|
||||
uint64_t mask = (1<<bits)-1;
|
||||
uint64_t i, value;
|
||||
uint64_t value;
|
||||
intset *is = intsetNew();
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (bits > 32) {
|
||||
value = (rand()*rand()) & mask;
|
||||
} else {
|
||||
@@ -329,10 +331,8 @@ intset *createSet(int bits, int size) {
|
||||
return is;
|
||||
}
|
||||
|
||||
void checkConsistency(intset *is) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < (intrev32ifbe(is->length)-1); i++) {
|
||||
static void checkConsistency(intset *is) {
|
||||
for (uint32_t i = 0; i < (intrev32ifbe(is->length)-1); i++) {
|
||||
uint32_t encoding = intrev32ifbe(is->encoding);
|
||||
|
||||
if (encoding == INTSET_ENC_INT16) {
|
||||
@@ -348,11 +348,15 @@ void checkConsistency(intset *is) {
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
#define UNUSED(x) (void)(x)
|
||||
int intsetTest(int argc, char **argv) {
|
||||
uint8_t success;
|
||||
int i;
|
||||
intset *is;
|
||||
sranddev();
|
||||
srand(time(NULL));
|
||||
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
|
||||
printf("Value encodings: "); {
|
||||
assert(_intsetValueEncoding(-32768) == INTSET_ENC_INT16);
|
||||
@@ -363,8 +367,10 @@ int main(int argc, char **argv) {
|
||||
assert(_intsetValueEncoding(+2147483647) == INTSET_ENC_INT32);
|
||||
assert(_intsetValueEncoding(-2147483649) == INTSET_ENC_INT64);
|
||||
assert(_intsetValueEncoding(+2147483648) == INTSET_ENC_INT64);
|
||||
assert(_intsetValueEncoding(-9223372036854775808ull) == INTSET_ENC_INT64);
|
||||
assert(_intsetValueEncoding(+9223372036854775807ull) == INTSET_ENC_INT64);
|
||||
assert(_intsetValueEncoding(-9223372036854775808ull) ==
|
||||
INTSET_ENC_INT64);
|
||||
assert(_intsetValueEncoding(+9223372036854775807ull) ==
|
||||
INTSET_ENC_INT64);
|
||||
ok();
|
||||
}
|
||||
|
||||
@@ -378,7 +384,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
printf("Large number of random adds: "); {
|
||||
int inserts = 0;
|
||||
uint32_t inserts = 0;
|
||||
is = intsetNew();
|
||||
for (i = 0; i < 1024; i++) {
|
||||
is = intsetAdd(is,rand()%0x800,&success);
|
||||
@@ -461,7 +467,8 @@ int main(int argc, char **argv) {
|
||||
|
||||
start = usec();
|
||||
for (i = 0; i < num; i++) intsetSearch(is,rand() % ((1<<bits)-1),NULL);
|
||||
printf("%ld lookups, %ld element set, %lldusec\n",num,size,usec()-start);
|
||||
printf("%ld lookups, %ld element set, %lldusec\n",
|
||||
num,size,usec()-start);
|
||||
}
|
||||
|
||||
printf("Stress add+delete: "); {
|
||||
@@ -479,5 +486,7 @@ int main(int argc, char **argv) {
|
||||
checkConsistency(is);
|
||||
ok();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -47,4 +47,8 @@ uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
|
||||
uint32_t intsetLen(intset *is);
|
||||
size_t intsetBlobLen(intset *is);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int intsetTest(int argc, char *argv[]);
|
||||
#endif
|
||||
|
||||
#endif // __INTSET_H
|
||||
|
||||
+59
-10
@@ -37,6 +37,7 @@
|
||||
|
||||
/* Dictionary type for latency events. */
|
||||
int dictStringKeyCompare(void *privdata, const void *key1, const void *key2) {
|
||||
REDIS_NOTUSED(privdata);
|
||||
return strcmp(key1,key2) == 0;
|
||||
}
|
||||
|
||||
@@ -55,6 +56,32 @@ dictType latencyTimeSeriesDictType = {
|
||||
dictVanillaFree /* val destructor */
|
||||
};
|
||||
|
||||
/* ------------------------- Utility functions ------------------------------ */
|
||||
|
||||
#ifdef __linux__
|
||||
/* Returns 1 if Transparent Huge Pages support is enabled in the kernel.
|
||||
* Otherwise (or if we are unable to check) 0 is returned. */
|
||||
int THPIsEnabled(void) {
|
||||
char buf[1024];
|
||||
|
||||
FILE *fp = fopen("/sys/kernel/mm/transparent_hugepage/enabled","r");
|
||||
if (!fp) return 0;
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
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
|
||||
* THP support, and is likely to have memory usage / latency issues. */
|
||||
int THPGetAnonHugePagesSize(void) {
|
||||
return zmalloc_get_smap_bytes_by_field("AnonHugePages:");
|
||||
}
|
||||
|
||||
/* ---------------------------- Latency API --------------------------------- */
|
||||
|
||||
/* Latency monitor initialization. We just need to create the dictionary
|
||||
@@ -201,7 +228,9 @@ 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;
|
||||
|
||||
/* Return ASAP if the latency engine is disabled and it looks like it
|
||||
@@ -219,7 +248,7 @@ sds createLatencyReport(void) {
|
||||
dictEntry *de;
|
||||
int eventnum = 0;
|
||||
|
||||
di = dictGetIterator(server.latency_events);
|
||||
di = dictGetSafeIterator(server.latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = dictGetKey(de);
|
||||
struct latencyTimeSeries *ts = dictGetVal(de);
|
||||
@@ -336,18 +365,29 @@ sds createLatencyReport(void) {
|
||||
}
|
||||
|
||||
/* Eviction cycle. */
|
||||
if (!strcasecmp(event,"eviction-cycle")) {
|
||||
if (!strcasecmp(event,"eviction-del")) {
|
||||
advise_large_objects = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"eviction-cycle")) {
|
||||
advise_mass_eviction = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
report = sdscatlen(report,"\n",1);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
if (eventnum == 0) {
|
||||
/* Add non event based advices. */
|
||||
if (THPGetAnonHugePagesSize() > 0) {
|
||||
advise_disable_thp = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
if (eventnum == 0 && advices == 0) {
|
||||
report = sdscat(report,"Dave, no latency spike was observed during the lifetime of this Redis instance, not in the slightest bit. I honestly think you ought to sit down calmly, take a stress pill, and think things over.\n");
|
||||
} else if (advices == 0) {
|
||||
} else if (eventnum > 0 && advices == 0) {
|
||||
report = sdscat(report,"\nWhile there are latency events logged, I'm not able to suggest any easy fix. Please use the Redis community to get some help, providing this report in your help request.\n");
|
||||
} else {
|
||||
/* Add all the suggestions accumulated so far. */
|
||||
@@ -368,7 +408,7 @@ sds createLatencyReport(void) {
|
||||
}
|
||||
|
||||
if (advise_slowlog_inspect) {
|
||||
report = sdscat(report,"- Check your Slow Log to understand what are the commads you are running which are too slow to execute. Please check http://redis.io/commands/slowlog for more information.\n");
|
||||
report = sdscat(report,"- Check your Slow Log to understand what are the commands you are running which are too slow to execute. Please check http://redis.io/commands/slowlog for more information.\n");
|
||||
}
|
||||
|
||||
/* Intrinsic latency. */
|
||||
@@ -417,6 +457,14 @@ sds createLatencyReport(void) {
|
||||
if (advise_large_objects) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
return report;
|
||||
@@ -474,7 +522,6 @@ 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;
|
||||
@@ -496,8 +543,7 @@ sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
|
||||
snprintf(buf,sizeof(buf),"%dh",elapsed/3600);
|
||||
else
|
||||
snprintf(buf,sizeof(buf),"%dd",elapsed/(3600*24));
|
||||
label = zstrdup(buf);
|
||||
sparklineSequenceAddSample(seq,ts->samples[i].latency,label);
|
||||
sparklineSequenceAddSample(seq,ts->samples[i].latency,buf);
|
||||
}
|
||||
|
||||
graph = sdscatprintf(graph,
|
||||
@@ -524,8 +570,11 @@ void latencyCommand(redisClient *c) {
|
||||
if (!strcasecmp(c->argv[1]->ptr,"history") && c->argc == 3) {
|
||||
/* LATENCY HISTORY <event> */
|
||||
ts = dictFetchValue(server.latency_events,c->argv[2]->ptr);
|
||||
if (ts == NULL) goto nodataerr;
|
||||
latencyCommandReplyWithSamples(c,ts);
|
||||
if (ts == NULL) {
|
||||
addReplyMultiBulkLen(c,0);
|
||||
} else {
|
||||
latencyCommandReplyWithSamples(c,ts);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"graph") && c->argc == 3) {
|
||||
/* LATENCY GRAPH <event> */
|
||||
sds graph;
|
||||
|
||||
@@ -63,6 +63,7 @@ struct latencyStats {
|
||||
|
||||
void latencyMonitorInit(void);
|
||||
void latencyAddSample(char *event, mstime_t latency);
|
||||
int THPIsEnabled(void);
|
||||
|
||||
/* Latency monitoring macros. */
|
||||
|
||||
@@ -85,4 +86,8 @@ void latencyAddSample(char *event, mstime_t latency);
|
||||
(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 */
|
||||
|
||||
+41
-15
@@ -49,7 +49,7 @@
|
||||
* the difference between 15 and 14 is very small
|
||||
* for small blocks (and 14 is usually a bit faster).
|
||||
* For a low-memory/faster configuration, use HLOG == 13;
|
||||
* For best compression, use 15 or 16 (or more, up to 23).
|
||||
* For best compression, use 15 or 16 (or more, up to 22).
|
||||
*/
|
||||
#ifndef HLOG
|
||||
# define HLOG 16
|
||||
@@ -94,7 +94,7 @@
|
||||
/*
|
||||
* Avoid assigning values to errno variable? for some embedding purposes
|
||||
* (linux kernel for example), this is necessary. NOTE: this breaks
|
||||
* the documentation in lzf.h.
|
||||
* the documentation in lzf.h. Avoiding errno has no speed impact.
|
||||
*/
|
||||
#ifndef AVOID_ERRNO
|
||||
# define AVOID_ERRNO 0
|
||||
@@ -121,16 +121,52 @@
|
||||
# define CHECK_INPUT 1
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Whether to store pointers or offsets inside the hash table. On
|
||||
* 64 bit architetcures, pointers take up twice as much space,
|
||||
* and might also be slower. Default is to autodetect.
|
||||
*/
|
||||
/*#define LZF_USER_OFFSETS autodetect */
|
||||
|
||||
/*****************************************************************************/
|
||||
/* nothing should be changed below */
|
||||
|
||||
#ifdef __cplusplus
|
||||
# include <cstring>
|
||||
# include <climits>
|
||||
using namespace std;
|
||||
#else
|
||||
# include <string.h>
|
||||
# include <limits.h>
|
||||
#endif
|
||||
|
||||
#ifndef LZF_USE_OFFSETS
|
||||
# if defined (WIN32)
|
||||
# define LZF_USE_OFFSETS defined(_M_X64)
|
||||
# else
|
||||
# if __cplusplus > 199711L
|
||||
# include <cstdint>
|
||||
# else
|
||||
# include <stdint.h>
|
||||
# endif
|
||||
# define LZF_USE_OFFSETS (UINTPTR_MAX > 0xffffffffU)
|
||||
# endif
|
||||
#endif
|
||||
|
||||
typedef unsigned char u8;
|
||||
|
||||
typedef const u8 *LZF_STATE[1 << (HLOG)];
|
||||
#if LZF_USE_OFFSETS
|
||||
# define LZF_HSLOT_BIAS ((const u8 *)in_data)
|
||||
typedef unsigned int LZF_HSLOT;
|
||||
#else
|
||||
# define LZF_HSLOT_BIAS 0
|
||||
typedef const u8 *LZF_HSLOT;
|
||||
#endif
|
||||
|
||||
typedef LZF_HSLOT LZF_STATE[1 << (HLOG)];
|
||||
|
||||
#if !STRICT_ALIGN
|
||||
/* for unaligned accesses we need a 16 bit datatype. */
|
||||
# include <limits.h>
|
||||
# if USHRT_MAX == 65535
|
||||
typedef unsigned short u16;
|
||||
# elif UINT_MAX == 65535
|
||||
@@ -142,17 +178,7 @@ typedef const u8 *LZF_STATE[1 << (HLOG)];
|
||||
#endif
|
||||
|
||||
#if ULTRA_FAST
|
||||
# if defined(VERY_FAST)
|
||||
# undef VERY_FAST
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if INIT_HTAB
|
||||
# ifdef __cplusplus
|
||||
# include <cstring>
|
||||
# else
|
||||
# include <string.h>
|
||||
# endif
|
||||
# undef VERY_FAST
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
+19
-23
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000-2008 Marc Alexander Lehmann <schmorp@schmorp.de>
|
||||
* Copyright (c) 2000-2010 Marc Alexander Lehmann <schmorp@schmorp.de>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modifica-
|
||||
* tion, are permitted provided that the following conditions are met:
|
||||
@@ -40,8 +40,8 @@
|
||||
|
||||
/*
|
||||
* don't play with this unless you benchmark!
|
||||
* decompression is not dependent on the hash function
|
||||
* the hashing function might seem strange, just believe me
|
||||
* the data format is not dependent on the hash function.
|
||||
* the hash function might seem strange, just believe me,
|
||||
* it works ;)
|
||||
*/
|
||||
#ifndef FRST
|
||||
@@ -89,9 +89,9 @@
|
||||
/*
|
||||
* compressed format
|
||||
*
|
||||
* 000LLLLL <L+1> ; literal
|
||||
* LLLooooo oooooooo ; backref L
|
||||
* 111ooooo LLLLLLLL oooooooo ; backref L+7
|
||||
* 000LLLLL <L+1> ; literal, L+1=1..33 octets
|
||||
* LLLooooo oooooooo ; backref L+1=1..7 octets, o+1=1..4096 offset
|
||||
* 111ooooo LLLLLLLL oooooooo ; backref L+8 octets, o+1=1..4096 offset
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -106,7 +106,6 @@ lzf_compress (const void *const in_data, unsigned int in_len,
|
||||
#if !LZF_STATE_ARG
|
||||
LZF_STATE htab;
|
||||
#endif
|
||||
const u8 **hslot;
|
||||
const u8 *ip = (const u8 *)in_data;
|
||||
u8 *op = (u8 *)out_data;
|
||||
const u8 *in_end = ip + in_len;
|
||||
@@ -133,10 +132,6 @@ lzf_compress (const void *const in_data, unsigned int in_len,
|
||||
|
||||
#if INIT_HTAB
|
||||
memset (htab, 0, sizeof (htab));
|
||||
# if 0
|
||||
for (hslot = htab; hslot < htab + HSIZE; hslot++)
|
||||
*hslot++ = ip;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
lit = 0; op++; /* start run */
|
||||
@@ -144,24 +139,23 @@ lzf_compress (const void *const in_data, unsigned int in_len,
|
||||
hval = FRST (ip);
|
||||
while (ip < in_end - 2)
|
||||
{
|
||||
LZF_HSLOT *hslot;
|
||||
|
||||
hval = NEXT (hval, ip);
|
||||
hslot = htab + IDX (hval);
|
||||
ref = *hslot; *hslot = ip;
|
||||
ref = *hslot + LZF_HSLOT_BIAS; *hslot = ip - LZF_HSLOT_BIAS;
|
||||
|
||||
if (1
|
||||
#if INIT_HTAB
|
||||
&& ref < ip /* the next test will actually take care of this, but this is faster */
|
||||
#endif
|
||||
&& (off = ip - ref - 1) < MAX_OFF
|
||||
&& ip + 4 < in_end
|
||||
&& ref > (u8 *)in_data
|
||||
#if STRICT_ALIGN
|
||||
&& ref[0] == ip[0]
|
||||
&& ref[1] == ip[1]
|
||||
&& ref[2] == ip[2]
|
||||
#if STRICT_ALIGN
|
||||
&& ((ref[1] << 8) | ref[0]) == ((ip[1] << 8) | ip[0])
|
||||
#else
|
||||
&& *(u16 *)ref == *(u16 *)ip
|
||||
&& ref[2] == ip[2]
|
||||
#endif
|
||||
)
|
||||
{
|
||||
@@ -170,12 +164,13 @@ lzf_compress (const void *const in_data, unsigned int in_len,
|
||||
unsigned int maxlen = in_end - ip - len;
|
||||
maxlen = maxlen > MAX_REF ? MAX_REF : maxlen;
|
||||
|
||||
if (expect_false (op + 3 + 1 >= out_end)) /* first a faster conservative test */
|
||||
if (op - !lit + 3 + 1 >= out_end) /* second the exact but rare test */
|
||||
return 0;
|
||||
|
||||
op [- lit - 1] = lit - 1; /* stop run */
|
||||
op -= !lit; /* undo run if length is zero */
|
||||
|
||||
if (expect_false (op + 3 + 1 >= out_end))
|
||||
return 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (expect_true (maxlen > 16))
|
||||
@@ -222,6 +217,7 @@ lzf_compress (const void *const in_data, unsigned int in_len,
|
||||
}
|
||||
|
||||
*op++ = off;
|
||||
|
||||
lit = 0; op++; /* start run */
|
||||
|
||||
ip += len + 1;
|
||||
@@ -237,12 +233,12 @@ lzf_compress (const void *const in_data, unsigned int in_len,
|
||||
hval = FRST (ip);
|
||||
|
||||
hval = NEXT (hval, ip);
|
||||
htab[IDX (hval)] = ip;
|
||||
htab[IDX (hval)] = ip - LZF_HSLOT_BIAS;
|
||||
ip++;
|
||||
|
||||
# if VERY_FAST && !ULTRA_FAST
|
||||
hval = NEXT (hval, ip);
|
||||
htab[IDX (hval)] = ip;
|
||||
htab[IDX (hval)] = ip - LZF_HSLOT_BIAS;
|
||||
ip++;
|
||||
# endif
|
||||
#else
|
||||
@@ -251,7 +247,7 @@ lzf_compress (const void *const in_data, unsigned int in_len,
|
||||
do
|
||||
{
|
||||
hval = NEXT (hval, ip);
|
||||
htab[IDX (hval)] = ip;
|
||||
htab[IDX (hval)] = ip - LZF_HSLOT_BIAS;
|
||||
ip++;
|
||||
}
|
||||
while (len--);
|
||||
|
||||
+46
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000-2007 Marc Alexander Lehmann <schmorp@schmorp.de>
|
||||
* Copyright (c) 2000-2010 Marc Alexander Lehmann <schmorp@schmorp.de>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modifica-
|
||||
* tion, are permitted provided that the following conditions are met:
|
||||
@@ -43,14 +43,14 @@
|
||||
# define SET_ERRNO(n) errno = (n)
|
||||
#endif
|
||||
|
||||
/*
|
||||
#if USE_REP_MOVSB /* small win on amd, big loss on intel */
|
||||
#if (__i386 || __amd64) && __GNUC__ >= 3
|
||||
# define lzf_movsb(dst, src, len) \
|
||||
asm ("rep movsb" \
|
||||
: "=D" (dst), "=S" (src), "=c" (len) \
|
||||
: "0" (dst), "1" (src), "2" (len));
|
||||
#endif
|
||||
*/
|
||||
#endif
|
||||
|
||||
unsigned int
|
||||
lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
@@ -86,9 +86,17 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
#ifdef lzf_movsb
|
||||
lzf_movsb (op, ip, ctrl);
|
||||
#else
|
||||
do
|
||||
*op++ = *ip++;
|
||||
while (--ctrl);
|
||||
switch (ctrl)
|
||||
{
|
||||
case 32: *op++ = *ip++; case 31: *op++ = *ip++; case 30: *op++ = *ip++; case 29: *op++ = *ip++;
|
||||
case 28: *op++ = *ip++; case 27: *op++ = *ip++; case 26: *op++ = *ip++; case 25: *op++ = *ip++;
|
||||
case 24: *op++ = *ip++; case 23: *op++ = *ip++; case 22: *op++ = *ip++; case 21: *op++ = *ip++;
|
||||
case 20: *op++ = *ip++; case 19: *op++ = *ip++; case 18: *op++ = *ip++; case 17: *op++ = *ip++;
|
||||
case 16: *op++ = *ip++; case 15: *op++ = *ip++; case 14: *op++ = *ip++; case 13: *op++ = *ip++;
|
||||
case 12: *op++ = *ip++; case 11: *op++ = *ip++; case 10: *op++ = *ip++; case 9: *op++ = *ip++;
|
||||
case 8: *op++ = *ip++; case 7: *op++ = *ip++; case 6: *op++ = *ip++; case 5: *op++ = *ip++;
|
||||
case 4: *op++ = *ip++; case 3: *op++ = *ip++; case 2: *op++ = *ip++; case 1: *op++ = *ip++;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else /* back reference */
|
||||
@@ -134,12 +142,39 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
len += 2;
|
||||
lzf_movsb (op, ref, len);
|
||||
#else
|
||||
*op++ = *ref++;
|
||||
*op++ = *ref++;
|
||||
switch (len)
|
||||
{
|
||||
default:
|
||||
len += 2;
|
||||
|
||||
do
|
||||
*op++ = *ref++;
|
||||
while (--len);
|
||||
if (op >= ref + len)
|
||||
{
|
||||
/* disjunct areas */
|
||||
memcpy (op, ref, len);
|
||||
op += len;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* overlapping, use octte by octte copying */
|
||||
do
|
||||
*op++ = *ref++;
|
||||
while (--len);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 9: *op++ = *ref++;
|
||||
case 8: *op++ = *ref++;
|
||||
case 7: *op++ = *ref++;
|
||||
case 6: *op++ = *ref++;
|
||||
case 5: *op++ = *ref++;
|
||||
case 4: *op++ = *ref++;
|
||||
case 3: *op++ = *ref++;
|
||||
case 2: *op++ = *ref++;
|
||||
case 1: *op++ = *ref++;
|
||||
case 0: *op++ = *ref++; /* two octets more */
|
||||
*op++ = *ref++;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
#include <errno.h>
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
#if defined(__sun)
|
||||
#include <stropts.h>
|
||||
#endif
|
||||
#include "config.h"
|
||||
|
||||
#if (ULONG_MAX == 4294967295UL)
|
||||
@@ -237,6 +240,7 @@ void memtest_test(size_t megabytes, int passes) {
|
||||
memtest_progress_end();
|
||||
memtest_compare_times(m,bytes,pass,4);
|
||||
}
|
||||
free(m);
|
||||
}
|
||||
|
||||
void memtest_non_destructive_invert(void *addr, size_t size) {
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ void queueMultiCommand(redisClient *c) {
|
||||
void discardTransaction(redisClient *c) {
|
||||
freeClientMultiState(c);
|
||||
initClientMultiState(c);
|
||||
c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC);;
|
||||
c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC);
|
||||
unwatchAllKeys(c);
|
||||
}
|
||||
|
||||
|
||||
+103
-73
@@ -100,6 +100,7 @@ redisClient *createClient(int fd) {
|
||||
c->ctime = c->lastinteraction = server.unixtime;
|
||||
c->authenticated = 0;
|
||||
c->replstate = REDIS_REPL_NONE;
|
||||
c->repl_put_online_on_ack = 0;
|
||||
c->reploff = 0;
|
||||
c->repl_ack_off = 0;
|
||||
c->repl_ack_time = 0;
|
||||
@@ -120,6 +121,7 @@ redisClient *createClient(int fd) {
|
||||
c->pubsub_channels = dictCreate(&setDictType,NULL);
|
||||
c->pubsub_patterns = listCreate();
|
||||
c->peerid = NULL;
|
||||
c->repl_eof_supported = 0;
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||
if (fd != -1) listAddNodeTail(server.clients,c);
|
||||
@@ -134,23 +136,49 @@ redisClient *createClient(int fd) {
|
||||
* returns REDIS_OK, and make sure to install the write handler in our event
|
||||
* loop so that when the socket is writable new data gets written.
|
||||
*
|
||||
* If the client should not receive new data, because it is a fake client,
|
||||
* a master, a slave not yet online, or because the setup of the write handler
|
||||
* failed, the function returns REDIS_ERR.
|
||||
* If the client should not receive new data, because it is a fake client
|
||||
* (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.
|
||||
*
|
||||
* 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 */
|
||||
|
||||
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->bufpos == 0 && listLength(c->reply) == 0 &&
|
||||
(c->replstate == REDIS_REPL_NONE ||
|
||||
c->replstate == REDIS_REPL_ONLINE) &&
|
||||
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
|
||||
sendReplyToClient, c) == AE_ERR) return REDIS_ERR;
|
||||
(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. */
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -174,7 +202,7 @@ robj *dupLastObjectIfNeeded(list *reply) {
|
||||
* Low level functions to add more data to output buffers.
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
int _addReplyToBuffer(redisClient *c, char *s, size_t len) {
|
||||
int _addReplyToBuffer(redisClient *c, const char *s, size_t len) {
|
||||
size_t available = sizeof(c->buf)-c->bufpos;
|
||||
|
||||
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK;
|
||||
@@ -254,7 +282,7 @@ void _addReplySdsToList(redisClient *c, sds s) {
|
||||
asyncCloseClientOnOutputBufferLimitReached(c);
|
||||
}
|
||||
|
||||
void _addReplyStringToList(redisClient *c, char *s, size_t len) {
|
||||
void _addReplyStringToList(redisClient *c, const char *s, size_t len) {
|
||||
robj *tail;
|
||||
|
||||
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;
|
||||
@@ -340,19 +368,19 @@ void addReplySds(redisClient *c, sds s) {
|
||||
}
|
||||
}
|
||||
|
||||
void addReplyString(redisClient *c, char *s, size_t len) {
|
||||
void addReplyString(redisClient *c, const char *s, size_t len) {
|
||||
if (prepareClientToWrite(c) != REDIS_OK) return;
|
||||
if (_addReplyToBuffer(c,s,len) != REDIS_OK)
|
||||
_addReplyStringToList(c,s,len);
|
||||
}
|
||||
|
||||
void addReplyErrorLength(redisClient *c, char *s, size_t len) {
|
||||
void addReplyErrorLength(redisClient *c, const char *s, size_t len) {
|
||||
addReplyString(c,"-ERR ",5);
|
||||
addReplyString(c,s,len);
|
||||
addReplyString(c,"\r\n",2);
|
||||
}
|
||||
|
||||
void addReplyError(redisClient *c, char *err) {
|
||||
void addReplyError(redisClient *c, const char *err) {
|
||||
addReplyErrorLength(c,err,strlen(err));
|
||||
}
|
||||
|
||||
@@ -372,13 +400,13 @@ void addReplyErrorFormat(redisClient *c, const char *fmt, ...) {
|
||||
sdsfree(s);
|
||||
}
|
||||
|
||||
void addReplyStatusLength(redisClient *c, char *s, size_t len) {
|
||||
void addReplyStatusLength(redisClient *c, const char *s, size_t len) {
|
||||
addReplyString(c,"+",1);
|
||||
addReplyString(c,s,len);
|
||||
addReplyString(c,"\r\n",2);
|
||||
}
|
||||
|
||||
void addReplyStatus(redisClient *c, char *status) {
|
||||
void addReplyStatus(redisClient *c, const char *status) {
|
||||
addReplyStatusLength(c,status,strlen(status));
|
||||
}
|
||||
|
||||
@@ -453,10 +481,10 @@ void addReplyLongLongWithPrefix(redisClient *c, long long ll, char prefix) {
|
||||
/* Things like $3\r\n or *2\r\n are emitted very often by the protocol
|
||||
* so we have a few shared objects to use if the integer is small
|
||||
* like it is most of the times. */
|
||||
if (prefix == '*' && ll < REDIS_SHARED_BULKHDR_LEN) {
|
||||
if (prefix == '*' && ll < REDIS_SHARED_BULKHDR_LEN && ll >= 0) {
|
||||
addReply(c,shared.mbulkhdr[ll]);
|
||||
return;
|
||||
} else if (prefix == '$' && ll < REDIS_SHARED_BULKHDR_LEN) {
|
||||
} else if (prefix == '$' && ll < REDIS_SHARED_BULKHDR_LEN && ll >= 0) {
|
||||
addReply(c,shared.bulkhdr[ll]);
|
||||
return;
|
||||
}
|
||||
@@ -518,14 +546,22 @@ void addReplyBulk(redisClient *c, robj *obj) {
|
||||
}
|
||||
|
||||
/* Add a C buffer as bulk reply */
|
||||
void addReplyBulkCBuffer(redisClient *c, void *p, size_t len) {
|
||||
void addReplyBulkCBuffer(redisClient *c, const void *p, size_t len) {
|
||||
addReplyLongLongWithPrefix(c,len,'$');
|
||||
addReplyString(c,p,len);
|
||||
addReply(c,shared.crlf);
|
||||
}
|
||||
|
||||
/* Add sds to reply (takes ownership of sds and frees it) */
|
||||
void addReplyBulkSds(redisClient *c, sds s) {
|
||||
addReplySds(c,sdscatfmt(sdsempty(),"$%u\r\n",
|
||||
(unsigned long)sdslen(s)));
|
||||
addReplySds(c,s);
|
||||
addReply(c,shared.crlf);
|
||||
}
|
||||
|
||||
/* Add a C nul term string as bulk reply */
|
||||
void addReplyBulkCString(redisClient *c, char *s) {
|
||||
void addReplyBulkCString(redisClient *c, const char *s) {
|
||||
if (s == NULL) {
|
||||
addReply(c,shared.nullbulk);
|
||||
} else {
|
||||
@@ -678,12 +714,8 @@ void freeClient(redisClient *c) {
|
||||
|
||||
/* Log link disconnection with slave */
|
||||
if ((c->flags & REDIS_SLAVE) && !(c->flags & REDIS_MONITOR)) {
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
|
||||
if (anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1) {
|
||||
redisLog(REDIS_WARNING,"Connection with slave %s:%d lost.",
|
||||
ip, c->slave_listening_port);
|
||||
}
|
||||
redisLog(REDIS_WARNING,"Connection with slave %s lost.",
|
||||
replicationGetSlaveName(c));
|
||||
}
|
||||
|
||||
/* Free the query buffer */
|
||||
@@ -774,7 +806,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) return;
|
||||
if (c->flags & REDIS_CLOSE_ASAP || c->flags & REDIS_LUA_CLIENT) return;
|
||||
c->flags |= REDIS_CLOSE_ASAP;
|
||||
listAddNodeTail(server.clients_to_close,c);
|
||||
}
|
||||
@@ -792,7 +824,8 @@ void freeClientsInAsyncFreeQueue(void) {
|
||||
|
||||
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
redisClient *c = privdata;
|
||||
int nwritten = 0, totwritten = 0, objlen;
|
||||
ssize_t nwritten = 0, totwritten = 0;
|
||||
size_t objlen;
|
||||
size_t objmem;
|
||||
robj *o;
|
||||
REDIS_NOTUSED(el);
|
||||
@@ -807,7 +840,7 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
|
||||
/* If the buffer was sent, set bufpos to zero to continue with
|
||||
* the remainder of the reply. */
|
||||
if (c->sentlen == c->bufpos) {
|
||||
if ((int)c->sentlen == c->bufpos) {
|
||||
c->bufpos = 0;
|
||||
c->sentlen = 0;
|
||||
}
|
||||
@@ -842,6 +875,7 @@ 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;
|
||||
@@ -929,8 +963,10 @@ int processInlineBuffer(redisClient *c) {
|
||||
sdsrange(c->querybuf,querylen+2,-1);
|
||||
|
||||
/* Setup argv array on client structure */
|
||||
if (c->argv) zfree(c->argv);
|
||||
c->argv = zmalloc(sizeof(robj*)*argc);
|
||||
if (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++) {
|
||||
@@ -948,7 +984,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);
|
||||
@@ -1051,7 +1087,7 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
qblen = sdslen(c->querybuf);
|
||||
/* Hint the sds library about the amount of bytes this string is
|
||||
* going to contain. */
|
||||
if (qblen < ll+2)
|
||||
if (qblen < (size_t)ll+2)
|
||||
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2-qblen);
|
||||
}
|
||||
c->bulklen = ll;
|
||||
@@ -1097,18 +1133,19 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
}
|
||||
|
||||
void processInputBuffer(redisClient *c) {
|
||||
server.current_client = c;
|
||||
/* Keep processing while there is something in the input buffer */
|
||||
while(sdslen(c->querybuf)) {
|
||||
/* Return if clients are paused. */
|
||||
if (!(c->flags & REDIS_SLAVE) && clientsArePaused()) return;
|
||||
if (!(c->flags & REDIS_SLAVE) && clientsArePaused()) break;
|
||||
|
||||
/* Immediately abort if the client is in the middle of something. */
|
||||
if (c->flags & REDIS_BLOCKED) return;
|
||||
if (c->flags & REDIS_BLOCKED) break;
|
||||
|
||||
/* REDIS_CLOSE_AFTER_REPLY closes the connection once the reply is
|
||||
* written to the client. Make sure to not let the reply grow after
|
||||
* this flag has been set (i.e. don't process more commands). */
|
||||
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;
|
||||
if (c->flags & REDIS_CLOSE_AFTER_REPLY) break;
|
||||
|
||||
/* Determine request type when unknown. */
|
||||
if (!c->reqtype) {
|
||||
@@ -1136,6 +1173,7 @@ void processInputBuffer(redisClient *c) {
|
||||
resetClient(c);
|
||||
}
|
||||
}
|
||||
server.current_client = NULL;
|
||||
}
|
||||
|
||||
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
@@ -1145,7 +1183,6 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
|
||||
server.current_client = c;
|
||||
readlen = REDIS_IOBUF_LEN;
|
||||
/* If this is a multi bulk request, and we are processing a bulk reply
|
||||
* that is large enough, try to maximize the probability that the query
|
||||
@@ -1167,7 +1204,7 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
nread = read(fd, c->querybuf+qblen, readlen);
|
||||
if (nread == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
nread = 0;
|
||||
return;
|
||||
} else {
|
||||
redisLog(REDIS_VERBOSE, "Reading from client: %s",strerror(errno));
|
||||
freeClient(c);
|
||||
@@ -1178,14 +1215,11 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
freeClient(c);
|
||||
return;
|
||||
}
|
||||
if (nread) {
|
||||
sdsIncrLen(c->querybuf,nread);
|
||||
c->lastinteraction = server.unixtime;
|
||||
if (c->flags & REDIS_MASTER) c->reploff += nread;
|
||||
} else {
|
||||
server.current_client = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
sdsIncrLen(c->querybuf,nread);
|
||||
c->lastinteraction = server.unixtime;
|
||||
if (c->flags & REDIS_MASTER) c->reploff += nread;
|
||||
server.stat_net_input_bytes += nread;
|
||||
if (sdslen(c->querybuf) > server.client_max_querybuf_len) {
|
||||
sds ci = catClientInfoString(sdsempty(),c), bytes = sdsempty();
|
||||
|
||||
@@ -1197,7 +1231,6 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
processInputBuffer(c);
|
||||
server.current_client = NULL;
|
||||
}
|
||||
|
||||
void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
@@ -1218,48 +1251,30 @@ void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
*biggest_input_buffer = bib;
|
||||
}
|
||||
|
||||
/* This is a helper function for genClientPeerId().
|
||||
* It writes the specified ip/port to "peerid" as a null termiated string
|
||||
* in the form ip:port if ip does not contain ":" itself, otherwise
|
||||
* [ip]:port format is used (for IPv6 addresses basically). */
|
||||
void formatPeerId(char *peerid, size_t peerid_len, char *ip, int port) {
|
||||
if (strchr(ip,':'))
|
||||
snprintf(peerid,peerid_len,"[%s]:%d",ip,port);
|
||||
else
|
||||
snprintf(peerid,peerid_len,"%s:%d",ip,port);
|
||||
}
|
||||
|
||||
/* A Redis "Peer ID" is a colon separated ip:port pair.
|
||||
* For IPv4 it's in the form x.y.z.k:pork, example: "127.0.0.1:1234".
|
||||
* For IPv4 it's in the form x.y.z.k:port, example: "127.0.0.1:1234".
|
||||
* For IPv6 addresses we use [] around the IP part, like in "[::1]:1234".
|
||||
* For Unix socekts we use path:0, like in "/tmp/redis:0".
|
||||
* For Unix sockets we use path:0, like in "/tmp/redis:0".
|
||||
*
|
||||
* A Peer ID always fits inside a buffer of REDIS_PEER_ID_LEN bytes, including
|
||||
* the null term.
|
||||
*
|
||||
* The function returns REDIS_OK on succcess, and REDIS_ERR on failure.
|
||||
*
|
||||
* On failure the function still populates 'peerid' with the "?:0" string
|
||||
* in case you want to relax error checking or need to display something
|
||||
* anyway (see anetPeerToString implementation for more info). */
|
||||
int genClientPeerId(redisClient *client, char *peerid, size_t peerid_len) {
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
int port;
|
||||
|
||||
void genClientPeerId(redisClient *client, char *peerid,
|
||||
size_t peerid_len) {
|
||||
if (client->flags & REDIS_UNIX_SOCKET) {
|
||||
/* Unix socket client. */
|
||||
snprintf(peerid,peerid_len,"%s:0",server.unixsocket);
|
||||
return REDIS_OK;
|
||||
} else {
|
||||
/* TCP client. */
|
||||
int retval = anetPeerToString(client->fd,ip,sizeof(ip),&port);
|
||||
formatPeerId(peerid,peerid_len,ip,port);
|
||||
return (retval == -1) ? REDIS_ERR : REDIS_OK;
|
||||
anetFormatPeer(client->fd,peerid,peerid_len);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function returns the client peer id, by creating and caching it
|
||||
* if client->perrid is NULL, otherwise returning the cached value.
|
||||
* if client->peerid is NULL, otherwise returning the cached value.
|
||||
* The Peer ID never changes during the life of the client, however it
|
||||
* is expensive to compute. */
|
||||
char *getClientPeerId(redisClient *c) {
|
||||
@@ -1513,6 +1528,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. */
|
||||
void rewriteClientCommandArgument(redisClient *c, int i, robj *newval) {
|
||||
@@ -1561,7 +1586,7 @@ unsigned long getClientOutputBufferMemoryUsage(redisClient *c) {
|
||||
int getClientType(redisClient *c) {
|
||||
if ((c->flags & REDIS_SLAVE) && !(c->flags & REDIS_MONITOR))
|
||||
return REDIS_CLIENT_TYPE_SLAVE;
|
||||
if (dictSize(c->pubsub_channels) || listLength(c->pubsub_patterns))
|
||||
if (c->flags & REDIS_PUBSUB)
|
||||
return REDIS_CLIENT_TYPE_PUBSUB;
|
||||
return REDIS_CLIENT_TYPE_NORMAL;
|
||||
}
|
||||
@@ -1630,7 +1655,7 @@ int checkClientOutputBufferLimits(redisClient *c) {
|
||||
* called from contexts where the client can't be freed safely, i.e. from the
|
||||
* lower level functions pushing data inside the client output buffers. */
|
||||
void asyncCloseClientOnOutputBufferLimitReached(redisClient *c) {
|
||||
redisAssert(c->reply_bytes < ULONG_MAX-(1024*64));
|
||||
redisAssert(c->reply_bytes < SIZE_MAX-(1024*64));
|
||||
if (c->reply_bytes == 0 || c->flags & REDIS_CLOSE_ASAP) return;
|
||||
if (checkClientOutputBufferLimits(c)) {
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
@@ -1688,7 +1713,9 @@ 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;
|
||||
@@ -1701,7 +1728,10 @@ int clientsArePaused(void) {
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
c = listNodeValue(ln);
|
||||
|
||||
if (c->flags & REDIS_SLAVE) continue;
|
||||
/* 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;
|
||||
listAddNodeTail(server.unblocked_clients,c);
|
||||
}
|
||||
}
|
||||
|
||||
+48
-34
@@ -50,14 +50,14 @@ robj *createObject(int type, void *ptr) {
|
||||
|
||||
/* Create a string object with encoding REDIS_ENCODING_RAW, that is a plain
|
||||
* string object where o->ptr points to a proper sds string. */
|
||||
robj *createRawStringObject(char *ptr, size_t len) {
|
||||
robj *createRawStringObject(const char *ptr, size_t len) {
|
||||
return createObject(REDIS_STRING,sdsnewlen(ptr,len));
|
||||
}
|
||||
|
||||
/* Create a string object with encoding REDIS_ENCODING_EMBSTR, that is
|
||||
* an object where the sds string is actually an unmodifiable string
|
||||
* allocated in the same chunk as the object itself. */
|
||||
robj *createEmbeddedStringObject(char *ptr, size_t len) {
|
||||
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
|
||||
robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1);
|
||||
struct sdshdr *sh = (void*)(o+1);
|
||||
|
||||
@@ -85,7 +85,7 @@ robj *createEmbeddedStringObject(char *ptr, size_t len) {
|
||||
* The current limit of 39 is chosen so that the biggest string object
|
||||
* we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
|
||||
#define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 39
|
||||
robj *createStringObject(char *ptr, size_t len) {
|
||||
robj *createStringObject(const char *ptr, size_t len) {
|
||||
if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT)
|
||||
return createEmbeddedStringObject(ptr,len);
|
||||
else
|
||||
@@ -109,26 +109,44 @@ robj *createStringObjectFromLongLong(long long value) {
|
||||
return o;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
/* 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) {
|
||||
char buf[256];
|
||||
int len;
|
||||
|
||||
/* 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 (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;
|
||||
}
|
||||
if (*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);
|
||||
}
|
||||
return createStringObject(buf,len);
|
||||
}
|
||||
@@ -162,11 +180,10 @@ robj *dupStringObject(robj *o) {
|
||||
}
|
||||
}
|
||||
|
||||
robj *createListObject(void) {
|
||||
list *l = listCreate();
|
||||
robj *createQuicklistObject(void) {
|
||||
quicklist *l = quicklistCreate();
|
||||
robj *o = createObject(REDIS_LIST,l);
|
||||
listSetFreeMethod(l,decrRefCountVoid);
|
||||
o->encoding = REDIS_ENCODING_LINKEDLIST;
|
||||
o->encoding = REDIS_ENCODING_QUICKLIST;
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -224,11 +241,8 @@ void freeStringObject(robj *o) {
|
||||
|
||||
void freeListObject(robj *o) {
|
||||
switch (o->encoding) {
|
||||
case REDIS_ENCODING_LINKEDLIST:
|
||||
listRelease((list*) o->ptr);
|
||||
break;
|
||||
case REDIS_ENCODING_ZIPLIST:
|
||||
zfree(o->ptr);
|
||||
case REDIS_ENCODING_QUICKLIST:
|
||||
quicklistRelease(o->ptr);
|
||||
break;
|
||||
default:
|
||||
redisPanic("Unknown list encoding type");
|
||||
@@ -373,7 +387,9 @@ robj *tryObjectEncoding(robj *o) {
|
||||
* Note that we avoid using shared integers when maxmemory is used
|
||||
* because every object needs to have a private LRU field for the LRU
|
||||
* algorithm to work well. */
|
||||
if (server.maxmemory == 0 &&
|
||||
if ((server.maxmemory == 0 ||
|
||||
(server.maxmemory_policy != REDIS_MAXMEMORY_VOLATILE_LRU &&
|
||||
server.maxmemory_policy != REDIS_MAXMEMORY_ALLKEYS_LRU)) &&
|
||||
value >= 0 &&
|
||||
value < REDIS_SHARED_INTEGERS)
|
||||
{
|
||||
@@ -513,9 +529,7 @@ size_t stringObjectLen(robj *o) {
|
||||
if (sdsEncodedObject(o)) {
|
||||
return sdslen(o->ptr);
|
||||
} else {
|
||||
char buf[32];
|
||||
|
||||
return ll2string(buf,32,(long)o->ptr);
|
||||
return sdigits10((long)o->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,7 +672,7 @@ char *strEncoding(int encoding) {
|
||||
case REDIS_ENCODING_RAW: return "raw";
|
||||
case REDIS_ENCODING_INT: return "int";
|
||||
case REDIS_ENCODING_HT: return "hashtable";
|
||||
case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
|
||||
case REDIS_ENCODING_QUICKLIST: return "quicklist";
|
||||
case REDIS_ENCODING_ZIPLIST: return "ziplist";
|
||||
case REDIS_ENCODING_INTSET: return "intset";
|
||||
case REDIS_ENCODING_SKIPLIST: return "skiplist";
|
||||
@@ -696,7 +710,7 @@ robj *objectCommandLookupOrReply(redisClient *c, robj *key, robj *reply) {
|
||||
}
|
||||
|
||||
/* Object command allows to inspect the internals of an Redis Object.
|
||||
* Usage: OBJECT <verb> ... arguments ... */
|
||||
* Usage: OBJECT <refcount|encoding|idletime> <key> */
|
||||
void objectCommand(redisClient *c) {
|
||||
robj *o;
|
||||
|
||||
|
||||
+14
-4
@@ -47,6 +47,12 @@ int listMatchPubsubPattern(void *a, void *b) {
|
||||
(equalStringObjects(pa->pattern,pb->pattern));
|
||||
}
|
||||
|
||||
/* Return the number of channels + patterns a client is subscribed to. */
|
||||
int clientSubscriptionsCount(redisClient *c) {
|
||||
return dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns);
|
||||
}
|
||||
|
||||
/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was already subscribed to that channel. */
|
||||
int pubsubSubscribeChannel(redisClient *c, robj *channel) {
|
||||
@@ -73,7 +79,7 @@ int pubsubSubscribeChannel(redisClient *c, robj *channel) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.subscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -135,7 +141,7 @@ int pubsubSubscribePattern(redisClient *c, robj *pattern) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.psubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -168,7 +174,7 @@ int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) {
|
||||
}
|
||||
|
||||
/* Unsubscribe from all the channels. Return the number of channels the
|
||||
* client was subscribed from. */
|
||||
* client was subscribed to. */
|
||||
int pubsubUnsubscribeAllChannels(redisClient *c, int notify) {
|
||||
dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
|
||||
dictEntry *de;
|
||||
@@ -273,6 +279,7 @@ void subscribeCommand(redisClient *c) {
|
||||
|
||||
for (j = 1; j < c->argc; j++)
|
||||
pubsubSubscribeChannel(c,c->argv[j]);
|
||||
c->flags |= REDIS_PUBSUB;
|
||||
}
|
||||
|
||||
void unsubscribeCommand(redisClient *c) {
|
||||
@@ -284,6 +291,7 @@ void unsubscribeCommand(redisClient *c) {
|
||||
for (j = 1; j < c->argc; j++)
|
||||
pubsubUnsubscribeChannel(c,c->argv[j],1);
|
||||
}
|
||||
if (clientSubscriptionsCount(c) == 0) c->flags &= ~REDIS_PUBSUB;
|
||||
}
|
||||
|
||||
void psubscribeCommand(redisClient *c) {
|
||||
@@ -291,6 +299,7 @@ void psubscribeCommand(redisClient *c) {
|
||||
|
||||
for (j = 1; j < c->argc; j++)
|
||||
pubsubSubscribePattern(c,c->argv[j]);
|
||||
c->flags |= REDIS_PUBSUB;
|
||||
}
|
||||
|
||||
void punsubscribeCommand(redisClient *c) {
|
||||
@@ -302,6 +311,7 @@ void punsubscribeCommand(redisClient *c) {
|
||||
for (j = 1; j < c->argc; j++)
|
||||
pubsubUnsubscribePattern(c,c->argv[j],1);
|
||||
}
|
||||
if (clientSubscriptionsCount(c) == 0) c->flags &= ~REDIS_PUBSUB;
|
||||
}
|
||||
|
||||
void publishCommand(redisClient *c) {
|
||||
@@ -348,7 +358,7 @@ void pubsubCommand(redisClient *c) {
|
||||
list *l = dictFetchValue(server.pubsub_channels,c->argv[j]);
|
||||
|
||||
addReplyBulk(c,c->argv[j]);
|
||||
addReplyBulkLongLong(c,l ? listLength(l) : 0);
|
||||
addReplyLongLong(c,l ? listLength(l) : 0);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) {
|
||||
/* PUBSUB NUMPAT */
|
||||
|
||||
+2650
File diff suppressed because it is too large
Load Diff
+169
@@ -0,0 +1,169 @@
|
||||
/* quicklist.h - A generic doubly linked quicklist implementation
|
||||
*
|
||||
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this quicklist of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this quicklist of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __QUICKLIST_H__
|
||||
#define __QUICKLIST_H__
|
||||
|
||||
/* Node, quicklist, and Iterator are the only data structures used currently. */
|
||||
|
||||
/* quicklistNode is a 32 byte struct describing a ziplist for a quicklist.
|
||||
* We use bit fields keep the quicklistNode at 32 bytes.
|
||||
* count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually < 32k).
|
||||
* encoding: 2 bits, RAW=1, LZF=2.
|
||||
* container: 2 bits, NONE=1, ZIPLIST=2.
|
||||
* recompress: 1 bit, bool, true if node is temporarry decompressed for usage.
|
||||
* attempted_compress: 1 bit, boolean, used for verifying during testing.
|
||||
* extra: 12 bits, free for future use; pads out the remainder of 32 bits */
|
||||
typedef struct quicklistNode {
|
||||
struct quicklistNode *prev;
|
||||
struct quicklistNode *next;
|
||||
unsigned char *zl;
|
||||
unsigned int sz; /* ziplist size in bytes */
|
||||
unsigned int count : 16; /* count of items in ziplist */
|
||||
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
|
||||
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
|
||||
unsigned int recompress : 1; /* was this node previous compressed? */
|
||||
unsigned int attempted_compress : 1; /* node can't compress; too small */
|
||||
unsigned int extra : 10; /* more bits to steal for future usage */
|
||||
} quicklistNode;
|
||||
|
||||
/* quicklistLZF is a 4+N byte struct holding 'sz' followed by 'compressed'.
|
||||
* 'sz' is byte length of 'compressed' field.
|
||||
* 'compressed' is LZF data with total (compressed) length 'sz'
|
||||
* NOTE: uncompressed length is stored in quicklistNode->sz.
|
||||
* When quicklistNode->zl is compressed, node->zl points to a quicklistLZF */
|
||||
typedef struct quicklistLZF {
|
||||
unsigned int sz; /* LZF size in bytes*/
|
||||
char compressed[];
|
||||
} quicklistLZF;
|
||||
|
||||
/* quicklist is a 32 byte struct (on 64-bit systems) describing a quicklist.
|
||||
* 'count' is the number of total entries.
|
||||
* 'len' is the number of quicklist nodes.
|
||||
* 'compress' is: -1 if compression disabled, otherwise it's the number
|
||||
* of quicklistNodes to leave uncompressed at ends of quicklist.
|
||||
* 'fill' is the user-requested (or default) fill factor. */
|
||||
typedef struct quicklist {
|
||||
quicklistNode *head;
|
||||
quicklistNode *tail;
|
||||
unsigned long count; /* total count of all entries in all ziplists */
|
||||
unsigned int len; /* number of quicklistNodes */
|
||||
int fill : 16; /* fill factor for individual nodes */
|
||||
unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
|
||||
} quicklist;
|
||||
|
||||
typedef struct quicklistIter {
|
||||
const quicklist *quicklist;
|
||||
quicklistNode *current;
|
||||
unsigned char *zi;
|
||||
long offset; /* offset in current ziplist */
|
||||
int direction;
|
||||
} quicklistIter;
|
||||
|
||||
typedef struct quicklistEntry {
|
||||
const quicklist *quicklist;
|
||||
quicklistNode *node;
|
||||
unsigned char *zi;
|
||||
unsigned char *value;
|
||||
unsigned int sz;
|
||||
long long longval;
|
||||
int offset;
|
||||
} quicklistEntry;
|
||||
|
||||
#define QUICKLIST_HEAD 0
|
||||
#define QUICKLIST_TAIL -1
|
||||
|
||||
/* quicklist node encodings */
|
||||
#define QUICKLIST_NODE_ENCODING_RAW 1
|
||||
#define QUICKLIST_NODE_ENCODING_LZF 2
|
||||
|
||||
/* quicklist compression disable */
|
||||
#define QUICKLIST_NOCOMPRESS 0
|
||||
|
||||
/* quicklist container formats */
|
||||
#define QUICKLIST_NODE_CONTAINER_NONE 1
|
||||
#define QUICKLIST_NODE_CONTAINER_ZIPLIST 2
|
||||
|
||||
#define quicklistNodeIsCompressed(node) \
|
||||
((node)->encoding == QUICKLIST_NODE_ENCODING_LZF)
|
||||
|
||||
/* Prototypes */
|
||||
quicklist *quicklistCreate(void);
|
||||
quicklist *quicklistNew(int fill, int compress);
|
||||
void quicklistSetCompressDepth(quicklist *quicklist, int depth);
|
||||
void quicklistSetFill(quicklist *quicklist, int fill);
|
||||
void quicklistSetOptions(quicklist *quicklist, int fill, int depth);
|
||||
void quicklistRelease(quicklist *quicklist);
|
||||
int quicklistPushHead(quicklist *quicklist, void *value, const size_t sz);
|
||||
int quicklistPushTail(quicklist *quicklist, void *value, const size_t sz);
|
||||
void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
|
||||
int where);
|
||||
void quicklistAppendZiplist(quicklist *quicklist, unsigned char *zl);
|
||||
quicklist *quicklistAppendValuesFromZiplist(quicklist *quicklist,
|
||||
unsigned char *zl);
|
||||
quicklist *quicklistCreateFromZiplist(int fill, int compress,
|
||||
unsigned char *zl);
|
||||
void quicklistInsertAfter(quicklist *quicklist, quicklistEntry *node,
|
||||
void *value, const size_t sz);
|
||||
void quicklistInsertBefore(quicklist *quicklist, quicklistEntry *node,
|
||||
void *value, const size_t sz);
|
||||
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry);
|
||||
int quicklistReplaceAtIndex(quicklist *quicklist, long index, void *data,
|
||||
int sz);
|
||||
int quicklistDelRange(quicklist *quicklist, const long start, const long stop);
|
||||
quicklistIter *quicklistGetIterator(const quicklist *quicklist, int direction);
|
||||
quicklistIter *quicklistGetIteratorAtIdx(const quicklist *quicklist,
|
||||
int direction, const long long idx);
|
||||
int quicklistNext(quicklistIter *iter, quicklistEntry *node);
|
||||
void quicklistReleaseIterator(quicklistIter *iter);
|
||||
quicklist *quicklistDup(quicklist *orig);
|
||||
int quicklistIndex(const quicklist *quicklist, const long long index,
|
||||
quicklistEntry *entry);
|
||||
void quicklistRewind(quicklist *quicklist, quicklistIter *li);
|
||||
void quicklistRewindTail(quicklist *quicklist, quicklistIter *li);
|
||||
void quicklistRotate(quicklist *quicklist);
|
||||
int quicklistPopCustom(quicklist *quicklist, int where, unsigned char **data,
|
||||
unsigned int *sz, long long *sval,
|
||||
void *(*saver)(unsigned char *data, unsigned int sz));
|
||||
int quicklistPop(quicklist *quicklist, int where, unsigned char **data,
|
||||
unsigned int *sz, long long *slong);
|
||||
unsigned int quicklistCount(quicklist *ql);
|
||||
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
|
||||
size_t quicklistGetLzf(const quicklistNode *node, void **data);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int quicklistTest(int argc, char *argv[]);
|
||||
#endif
|
||||
|
||||
/* Directions for iterators */
|
||||
#define AL_START_HEAD 0
|
||||
#define AL_START_TAIL 1
|
||||
|
||||
#endif /* __QUICKLIST_H__ */
|
||||
+2
-2
@@ -66,7 +66,7 @@
|
||||
#define HI_BIT (1L << (2 * N - 1))
|
||||
|
||||
static uint32_t x[3] = { X0, X1, X2 }, a[3] = { A0, A1, A2 }, c = C;
|
||||
static void next();
|
||||
static void next(void);
|
||||
|
||||
int32_t redisLrand48() {
|
||||
next();
|
||||
@@ -77,7 +77,7 @@ void redisSrand48(int32_t seedval) {
|
||||
SEED(X0, LOW(seedval), HIGH(seedval));
|
||||
}
|
||||
|
||||
static void next() {
|
||||
static void next(void) {
|
||||
uint32_t p[2], q[2], r[2], carry0, carry1;
|
||||
|
||||
MUL(a[0], x[0], p);
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
/* The current RDB version. When the format changes in a way that is no longer
|
||||
* backward compatible this number gets incremented. */
|
||||
#define REDIS_RDB_VERSION 6
|
||||
#define REDIS_RDB_VERSION 7
|
||||
|
||||
/* Defines related to the dump file format. To store 32 bits lengths for short
|
||||
* keys requires a lot of space, so we check the most significant 2 bits of
|
||||
@@ -74,6 +74,7 @@
|
||||
#define REDIS_RDB_TYPE_SET 2
|
||||
#define REDIS_RDB_TYPE_ZSET 3
|
||||
#define REDIS_RDB_TYPE_HASH 4
|
||||
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
|
||||
|
||||
/* Object types for encoded objects. */
|
||||
#define REDIS_RDB_TYPE_HASH_ZIPMAP 9
|
||||
@@ -81,11 +82,15 @@
|
||||
#define REDIS_RDB_TYPE_SET_INTSET 11
|
||||
#define REDIS_RDB_TYPE_ZSET_ZIPLIST 12
|
||||
#define REDIS_RDB_TYPE_HASH_ZIPLIST 13
|
||||
#define REDIS_RDB_TYPE_LIST_QUICKLIST 14
|
||||
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
|
||||
|
||||
/* Test if a type is an object type. */
|
||||
#define rdbIsObjectType(t) ((t >= 0 && t <= 4) || (t >= 9 && t <= 13))
|
||||
#define rdbIsObjectType(t) ((t >= 0 && t <= 4) || (t >= 9 && t <= 14))
|
||||
|
||||
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
|
||||
#define REDIS_RDB_OPCODE_AUX 250
|
||||
#define REDIS_RDB_OPCODE_RESIZEDB 251
|
||||
#define REDIS_RDB_OPCODE_EXPIRETIME_MS 252
|
||||
#define REDIS_RDB_OPCODE_EXPIRETIME 253
|
||||
#define REDIS_RDB_OPCODE_SELECTDB 254
|
||||
@@ -100,12 +105,13 @@ uint32_t rdbLoadLen(rio *rdb, int *isencoded);
|
||||
int rdbSaveObjectType(rio *rdb, robj *o);
|
||||
int rdbLoadObjectType(rio *rdb);
|
||||
int rdbLoad(char *filename);
|
||||
int rdbLoadRio(rio *rdb, size_t size);
|
||||
int rdbSaveBackground(char *filename);
|
||||
int rdbSaveToSlavesSockets(void);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
int rdbSave(char *filename);
|
||||
int rdbSaveObject(rio *rdb, robj *o);
|
||||
off_t rdbSavedObjectLen(robj *o);
|
||||
off_t rdbSavedObjectPages(robj *o);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime, long long now);
|
||||
|
||||
+69
-45
@@ -77,6 +77,7 @@ static struct config {
|
||||
int dbnum;
|
||||
sds dbnumstr;
|
||||
char *tests;
|
||||
char *auth;
|
||||
} config;
|
||||
|
||||
typedef struct _client {
|
||||
@@ -85,13 +86,14 @@ typedef struct _client {
|
||||
char **randptr; /* Pointers to :rand: strings inside the command buf */
|
||||
size_t randlen; /* Number of pointers in client->randptr */
|
||||
size_t randfree; /* Number of unused pointers in client->randptr */
|
||||
unsigned int written; /* Bytes of 'obuf' already written */
|
||||
size_t written; /* Bytes of 'obuf' already written */
|
||||
long long start; /* Start time of a request */
|
||||
long long latency; /* Request latency */
|
||||
int pending; /* Number of pending requests (replies to consume) */
|
||||
int selectlen; /* If non-zero, a SELECT of 'selectlen' bytes is currently
|
||||
used as a prefix of the pipline of commands. This gets
|
||||
discarded the first time it's sent. */
|
||||
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 */
|
||||
} *client;
|
||||
|
||||
/* Prototypes */
|
||||
@@ -211,20 +213,21 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
|
||||
if (c->selectlen) {
|
||||
int j;
|
||||
|
||||
/* This is the OK from SELECT. Just discard the SELECT
|
||||
* from the buffer. */
|
||||
/* This is an OK for prefix commands such as auth and select.*/
|
||||
if (c->prefix_pending > 0) {
|
||||
c->prefix_pending--;
|
||||
c->pending--;
|
||||
sdsrange(c->obuf,c->selectlen,-1);
|
||||
/* We also need to fix the pointers to the strings
|
||||
* we need to randomize. */
|
||||
for (j = 0; j < c->randlen; j++)
|
||||
c->randptr[j] -= c->selectlen;
|
||||
c->selectlen = 0;
|
||||
continue;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
if (config.requests_finished < config.requests)
|
||||
@@ -263,7 +266,7 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
|
||||
if (sdslen(c->obuf) > c->written) {
|
||||
void *ptr = c->obuf+c->written;
|
||||
int nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written);
|
||||
ssize_t nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written);
|
||||
if (nwritten == -1) {
|
||||
if (errno != EPIPE)
|
||||
fprintf(stderr, "Writing to socket: %s\n", strerror(errno));
|
||||
@@ -298,8 +301,7 @@ 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, the SELECT command is automatically prefixed
|
||||
* if needed. */
|
||||
* Even when cloning another client, prefix commands are applied if needed.*/
|
||||
static client createClient(char *cmd, size_t len, client from) {
|
||||
int j;
|
||||
client c = zmalloc(sizeof(struct _client));
|
||||
@@ -324,6 +326,17 @@ 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
|
||||
* buffer with the SELECT command, that will be discarded the first
|
||||
@@ -332,25 +345,23 @@ 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->selectlen = sdslen(c->obuf);
|
||||
} else {
|
||||
c->selectlen = 0;
|
||||
c->prefix_pending++;
|
||||
}
|
||||
|
||||
c->prefixlen = sdslen(c->obuf);
|
||||
/* Append the request itself. */
|
||||
if (from) {
|
||||
c->obuf = sdscatlen(c->obuf,
|
||||
from->obuf+from->selectlen,
|
||||
sdslen(from->obuf)-from->selectlen);
|
||||
from->obuf+from->prefixlen,
|
||||
sdslen(from->obuf)-from->prefixlen);
|
||||
} else {
|
||||
for (j = 0; j < config.pipeline; j++)
|
||||
c->obuf = sdscatlen(c->obuf,cmd,len);
|
||||
}
|
||||
|
||||
c->written = 0;
|
||||
c->pending = config.pipeline;
|
||||
c->pending = config.pipeline+c->prefix_pending;
|
||||
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) {
|
||||
@@ -359,10 +370,10 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
c->randfree = 0;
|
||||
c->randptr = zmalloc(sizeof(char*)*c->randlen);
|
||||
/* copy the offsets. */
|
||||
for (j = 0; j < c->randlen; j++) {
|
||||
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->selectlen - from->selectlen;
|
||||
c->randptr[j] += c->prefixlen - from->prefixlen;
|
||||
}
|
||||
} else {
|
||||
char *p = c->obuf;
|
||||
@@ -381,7 +392,8 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
}
|
||||
}
|
||||
}
|
||||
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
|
||||
if (config.idlemode == 0)
|
||||
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
|
||||
listAddNodeTail(config.clients,c);
|
||||
config.liveclients++;
|
||||
return c;
|
||||
@@ -389,15 +401,6 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
|
||||
static void createMissingClients(client c) {
|
||||
int n = 0;
|
||||
char *buf = c->obuf;
|
||||
size_t buflen = sdslen(c->obuf);
|
||||
|
||||
/* If we are cloning from a client with a SELECT prefix, skip it since the
|
||||
* client will be created with the prefixed SELECT if needed. */
|
||||
if (c->selectlen) {
|
||||
buf += c->selectlen;
|
||||
buflen -= c->selectlen;
|
||||
}
|
||||
|
||||
while(config.liveclients < config.numclients) {
|
||||
createClient(NULL,0,c);
|
||||
@@ -489,6 +492,9 @@ int parseOptions(int argc, const char **argv) {
|
||||
} else if (!strcmp(argv[i],"-s")) {
|
||||
if (lastarg) goto invalid;
|
||||
config.hostsocket = strdup(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"-a") ) {
|
||||
if (lastarg) goto invalid;
|
||||
config.auth = strdup(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"-d")) {
|
||||
if (lastarg) goto invalid;
|
||||
config.datasize = atoi(argv[++i]);
|
||||
@@ -550,8 +556,9 @@ usage:
|
||||
" -h <hostname> Server hostname (default 127.0.0.1)\n"
|
||||
" -p <port> Server port (default 6379)\n"
|
||||
" -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 10000)\n"
|
||||
" -n <requests> Total number of requests (default 100000)\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"
|
||||
@@ -593,11 +600,15 @@ int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData
|
||||
REDIS_NOTUSED(clientData);
|
||||
|
||||
if (config.liveclients == 0) {
|
||||
fprintf(stderr,"All clients disconnected... aborting.");
|
||||
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;
|
||||
}
|
||||
float dt = (float)(mstime()-config.start)/1000.0;
|
||||
float rps = (float)config.requests_finished/dt;
|
||||
printf("%s: %.2f\r", config.title, rps);
|
||||
@@ -631,7 +642,7 @@ int main(int argc, const char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
config.numclients = 50;
|
||||
config.requests = 10000;
|
||||
config.requests = 100000;
|
||||
config.liveclients = 0;
|
||||
config.el = aeCreateEventLoop(1024*10);
|
||||
aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL);
|
||||
@@ -651,6 +662,7 @@ int main(int argc, const char **argv) {
|
||||
config.hostsocket = NULL;
|
||||
config.tests = NULL;
|
||||
config.dbnum = 0;
|
||||
config.auth = NULL;
|
||||
|
||||
i = parseOptions(argc,argv);
|
||||
argc -= i;
|
||||
@@ -688,8 +700,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';
|
||||
|
||||
@@ -726,12 +738,24 @@ int main(int argc, const char **argv) {
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("rpush")) {
|
||||
len = redisFormatCommand(&cmd,"RPUSH mylist %s",data);
|
||||
benchmark("RPUSH",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("lpop")) {
|
||||
len = redisFormatCommand(&cmd,"LPOP mylist");
|
||||
benchmark("LPOP",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("rpop")) {
|
||||
len = redisFormatCommand(&cmd,"RPOP mylist");
|
||||
benchmark("RPOP",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("sadd")) {
|
||||
len = redisFormatCommand(&cmd,
|
||||
"SADD myset element:__rand_int__");
|
||||
|
||||
@@ -29,74 +29,19 @@
|
||||
*/
|
||||
|
||||
|
||||
#include "redis.h"
|
||||
#include "rdb.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include "lzf.h"
|
||||
#include "crc64.h"
|
||||
|
||||
/* Object types */
|
||||
#define REDIS_STRING 0
|
||||
#define REDIS_LIST 1
|
||||
#define REDIS_SET 2
|
||||
#define REDIS_ZSET 3
|
||||
#define REDIS_HASH 4
|
||||
#define REDIS_HASH_ZIPMAP 9
|
||||
#define REDIS_LIST_ZIPLIST 10
|
||||
#define REDIS_SET_INTSET 11
|
||||
#define REDIS_ZSET_ZIPLIST 12
|
||||
#define REDIS_HASH_ZIPLIST 13
|
||||
|
||||
/* Objects encoding. Some kind of objects like Strings and Hashes can be
|
||||
* internally represented in multiple ways. The 'encoding' field of the object
|
||||
* is set to one of this fields for this object. */
|
||||
#define REDIS_ENCODING_RAW 0 /* Raw representation */
|
||||
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
|
||||
#define REDIS_ENCODING_ZIPMAP 2 /* Encoded as zipmap */
|
||||
#define REDIS_ENCODING_HT 3 /* Encoded as a hash table */
|
||||
|
||||
/* Object types only used for dumping to disk */
|
||||
#define REDIS_EXPIRETIME_MS 252
|
||||
#define REDIS_EXPIRETIME 253
|
||||
#define REDIS_SELECTDB 254
|
||||
#define REDIS_EOF 255
|
||||
|
||||
/* Defines related to the dump file format. To store 32 bits lengths for short
|
||||
* keys requires a lot of space, so we check the most significant 2 bits of
|
||||
* the first byte to interpreter the length:
|
||||
*
|
||||
* 00|000000 => if the two MSB are 00 the len is the 6 bits of this byte
|
||||
* 01|000000 00000000 => 01, the len is 14 byes, 6 bits + 8 bits of next byte
|
||||
* 10|000000 [32 bit integer] => if it's 01, a full 32 bit len will follow
|
||||
* 11|000000 this means: specially encoded object will follow. The six bits
|
||||
* number specify the kind of object that follows.
|
||||
* See the REDIS_RDB_ENC_* defines.
|
||||
*
|
||||
* Lengths up to 63 are stored using a single byte, most DB keys, and may
|
||||
* values, will fit inside. */
|
||||
#define REDIS_RDB_6BITLEN 0
|
||||
#define REDIS_RDB_14BITLEN 1
|
||||
#define REDIS_RDB_32BITLEN 2
|
||||
#define REDIS_RDB_ENCVAL 3
|
||||
#define REDIS_RDB_LENERR UINT_MAX
|
||||
|
||||
/* When a length of a string object stored on disk has the first two bits
|
||||
* set, the remaining two bits specify a special encoding for the object
|
||||
* accordingly to the following defines: */
|
||||
#define REDIS_RDB_ENC_INT8 0 /* 8 bit signed integer */
|
||||
#define REDIS_RDB_ENC_INT16 1 /* 16 bit signed integer */
|
||||
#define REDIS_RDB_ENC_INT32 2 /* 32 bit signed integer */
|
||||
#define REDIS_RDB_ENC_LZF 3 /* string compressed with FASTLZ */
|
||||
|
||||
#define ERROR(...) { \
|
||||
printf(__VA_ARGS__); \
|
||||
redisLog(REDIS_WARNING, __VA_ARGS__); \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
@@ -133,26 +78,23 @@ typedef struct {
|
||||
char success;
|
||||
} entry;
|
||||
|
||||
/* Global vars that are actually used as constants. The following double
|
||||
* values are used for double on-disk serialization, and are initialized
|
||||
* at runtime to avoid strange compiler optimizations. */
|
||||
static double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
|
||||
#define MAX_TYPES_NUM 256
|
||||
#define MAX_TYPE_NAME_LEN 16
|
||||
/* store string types for output */
|
||||
static char types[256][16];
|
||||
static char types[MAX_TYPES_NUM][MAX_TYPE_NAME_LEN];
|
||||
|
||||
/* Return true if 't' is a valid object type. */
|
||||
int checkType(unsigned char t) {
|
||||
static int rdbCheckType(unsigned char t) {
|
||||
/* In case a new object type is added, update the following
|
||||
* condition as necessary. */
|
||||
return
|
||||
(t >= REDIS_HASH_ZIPMAP && t <= REDIS_HASH_ZIPLIST) ||
|
||||
t <= REDIS_HASH ||
|
||||
t >= REDIS_EXPIRETIME_MS;
|
||||
(t >= REDIS_RDB_TYPE_HASH_ZIPMAP && t <= REDIS_RDB_TYPE_HASH_ZIPLIST) ||
|
||||
t <= REDIS_RDB_TYPE_HASH ||
|
||||
t >= REDIS_RDB_OPCODE_EXPIRETIME_MS;
|
||||
}
|
||||
|
||||
/* when number of bytes to read is negative, do a peek */
|
||||
int readBytes(void *target, long num) {
|
||||
static int readBytes(void *target, long num) {
|
||||
char peek = (num < 0) ? 1 : 0;
|
||||
num = (num < 0) ? -num : num;
|
||||
|
||||
@@ -166,33 +108,33 @@ int readBytes(void *target, long num) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int processHeader() {
|
||||
int processHeader(void) {
|
||||
char buf[10] = "_________";
|
||||
int dump_version;
|
||||
|
||||
if (!readBytes(buf, 9)) {
|
||||
ERROR("Cannot read header\n");
|
||||
ERROR("Cannot read header");
|
||||
}
|
||||
|
||||
/* expect the first 5 bytes to equal REDIS */
|
||||
if (memcmp(buf,"REDIS",5) != 0) {
|
||||
ERROR("Wrong signature in header\n");
|
||||
ERROR("Wrong signature in header");
|
||||
}
|
||||
|
||||
dump_version = (int)strtol(buf + 5, NULL, 10);
|
||||
if (dump_version < 1 || dump_version > 6) {
|
||||
ERROR("Unknown RDB format version: %d\n", dump_version);
|
||||
ERROR("Unknown RDB format version: %d", dump_version);
|
||||
}
|
||||
return dump_version;
|
||||
}
|
||||
|
||||
int loadType(entry *e) {
|
||||
static int loadType(entry *e) {
|
||||
uint32_t offset = CURR_OFFSET;
|
||||
|
||||
/* this byte needs to qualify as type */
|
||||
unsigned char t;
|
||||
if (readBytes(&t, 1)) {
|
||||
if (checkType(t)) {
|
||||
if (rdbCheckType(t)) {
|
||||
e->type = t;
|
||||
return 1;
|
||||
} else {
|
||||
@@ -206,18 +148,18 @@ int loadType(entry *e) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int peekType() {
|
||||
static int peekType() {
|
||||
unsigned char t;
|
||||
if (readBytes(&t, -1) && (checkType(t)))
|
||||
if (readBytes(&t, -1) && (rdbCheckType(t)))
|
||||
return t;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* discard time, just consume the bytes */
|
||||
int processTime(int type) {
|
||||
static int processTime(int type) {
|
||||
uint32_t offset = CURR_OFFSET;
|
||||
unsigned char t[8];
|
||||
int timelen = (type == REDIS_EXPIRETIME_MS) ? 8 : 4;
|
||||
int timelen = (type == REDIS_RDB_OPCODE_EXPIRETIME_MS) ? 8 : 4;
|
||||
|
||||
if (readBytes(t,timelen)) {
|
||||
return 1;
|
||||
@@ -229,7 +171,7 @@ int processTime(int type) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t loadLength(int *isencoded) {
|
||||
static uint32_t loadLength(int *isencoded) {
|
||||
unsigned char buf[2];
|
||||
uint32_t len;
|
||||
int type;
|
||||
@@ -255,7 +197,7 @@ uint32_t loadLength(int *isencoded) {
|
||||
}
|
||||
}
|
||||
|
||||
char *loadIntegerObject(int enctype) {
|
||||
static char *loadIntegerObject(int enctype) {
|
||||
uint32_t offset = CURR_OFFSET;
|
||||
unsigned char enc[4];
|
||||
long long val;
|
||||
@@ -282,36 +224,36 @@ char *loadIntegerObject(int enctype) {
|
||||
|
||||
/* convert val into string */
|
||||
char *buf;
|
||||
buf = malloc(sizeof(char) * 128);
|
||||
buf = zmalloc(sizeof(char) * 128);
|
||||
sprintf(buf, "%lld", val);
|
||||
return buf;
|
||||
}
|
||||
|
||||
char* loadLzfStringObject() {
|
||||
static char* loadLzfStringObject() {
|
||||
unsigned int slen, clen;
|
||||
char *c, *s;
|
||||
|
||||
if ((clen = loadLength(NULL)) == REDIS_RDB_LENERR) return NULL;
|
||||
if ((slen = loadLength(NULL)) == REDIS_RDB_LENERR) return NULL;
|
||||
|
||||
c = malloc(clen);
|
||||
c = zmalloc(clen);
|
||||
if (!readBytes(c, clen)) {
|
||||
free(c);
|
||||
zfree(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = malloc(slen+1);
|
||||
s = zmalloc(slen+1);
|
||||
if (lzf_decompress(c,clen,s,slen) == 0) {
|
||||
free(c); free(s);
|
||||
zfree(c); zfree(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
free(c);
|
||||
zfree(c);
|
||||
return s;
|
||||
}
|
||||
|
||||
/* returns NULL when not processable, char* when valid */
|
||||
char* loadStringObject() {
|
||||
static char* loadStringObject() {
|
||||
uint32_t offset = CURR_OFFSET;
|
||||
int isencoded;
|
||||
uint32_t len;
|
||||
@@ -334,47 +276,48 @@ char* loadStringObject() {
|
||||
|
||||
if (len == REDIS_RDB_LENERR) return NULL;
|
||||
|
||||
char *buf = malloc(sizeof(char) * (len+1));
|
||||
char *buf = zmalloc(sizeof(char) * (len+1));
|
||||
if (buf == NULL) return NULL;
|
||||
buf[len] = '\0';
|
||||
if (!readBytes(buf, len)) {
|
||||
free(buf);
|
||||
zfree(buf);
|
||||
return NULL;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
int processStringObject(char** store) {
|
||||
static int processStringObject(char** store) {
|
||||
unsigned long offset = CURR_OFFSET;
|
||||
char *key = loadStringObject();
|
||||
if (key == NULL) {
|
||||
SHIFT_ERROR(offset, "Error reading string object");
|
||||
free(key);
|
||||
zfree(key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (store != NULL) {
|
||||
*store = key;
|
||||
} else {
|
||||
free(key);
|
||||
zfree(key);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
double* loadDoubleValue() {
|
||||
static double* loadDoubleValue() {
|
||||
char buf[256];
|
||||
unsigned char len;
|
||||
double* val;
|
||||
|
||||
if (!readBytes(&len,1)) return NULL;
|
||||
|
||||
val = malloc(sizeof(double));
|
||||
val = zmalloc(sizeof(double));
|
||||
switch(len) {
|
||||
case 255: *val = R_NegInf; return val;
|
||||
case 254: *val = R_PosInf; return val;
|
||||
case 253: *val = R_Nan; return val;
|
||||
default:
|
||||
if (!readBytes(buf, len)) {
|
||||
free(val);
|
||||
zfree(val);
|
||||
return NULL;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
@@ -383,24 +326,24 @@ double* loadDoubleValue() {
|
||||
}
|
||||
}
|
||||
|
||||
int processDoubleValue(double** store) {
|
||||
static int processDoubleValue(double** store) {
|
||||
unsigned long offset = CURR_OFFSET;
|
||||
double *val = loadDoubleValue();
|
||||
if (val == NULL) {
|
||||
SHIFT_ERROR(offset, "Error reading double value");
|
||||
free(val);
|
||||
zfree(val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (store != NULL) {
|
||||
*store = val;
|
||||
} else {
|
||||
free(val);
|
||||
zfree(val);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int loadPair(entry *e) {
|
||||
static int loadPair(entry *e) {
|
||||
uint32_t offset = CURR_OFFSET;
|
||||
uint32_t i;
|
||||
|
||||
@@ -414,10 +357,10 @@ int loadPair(entry *e) {
|
||||
}
|
||||
|
||||
uint32_t length = 0;
|
||||
if (e->type == REDIS_LIST ||
|
||||
e->type == REDIS_SET ||
|
||||
e->type == REDIS_ZSET ||
|
||||
e->type == REDIS_HASH) {
|
||||
if (e->type == REDIS_RDB_TYPE_LIST ||
|
||||
e->type == REDIS_RDB_TYPE_SET ||
|
||||
e->type == REDIS_RDB_TYPE_ZSET ||
|
||||
e->type == REDIS_RDB_TYPE_HASH) {
|
||||
if ((length = loadLength(NULL)) == REDIS_RDB_LENERR) {
|
||||
SHIFT_ERROR(offset, "Error reading %s length", types[e->type]);
|
||||
return 0;
|
||||
@@ -425,19 +368,19 @@ int loadPair(entry *e) {
|
||||
}
|
||||
|
||||
switch(e->type) {
|
||||
case REDIS_STRING:
|
||||
case REDIS_HASH_ZIPMAP:
|
||||
case REDIS_LIST_ZIPLIST:
|
||||
case REDIS_SET_INTSET:
|
||||
case REDIS_ZSET_ZIPLIST:
|
||||
case REDIS_HASH_ZIPLIST:
|
||||
case REDIS_RDB_TYPE_STRING:
|
||||
case REDIS_RDB_TYPE_HASH_ZIPMAP:
|
||||
case REDIS_RDB_TYPE_LIST_ZIPLIST:
|
||||
case REDIS_RDB_TYPE_SET_INTSET:
|
||||
case REDIS_RDB_TYPE_ZSET_ZIPLIST:
|
||||
case REDIS_RDB_TYPE_HASH_ZIPLIST:
|
||||
if (!processStringObject(NULL)) {
|
||||
SHIFT_ERROR(offset, "Error reading entry value");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
case REDIS_LIST:
|
||||
case REDIS_SET:
|
||||
case REDIS_RDB_TYPE_LIST:
|
||||
case REDIS_RDB_TYPE_SET:
|
||||
for (i = 0; i < length; i++) {
|
||||
offset = CURR_OFFSET;
|
||||
if (!processStringObject(NULL)) {
|
||||
@@ -446,7 +389,7 @@ int loadPair(entry *e) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case REDIS_ZSET:
|
||||
case REDIS_RDB_TYPE_ZSET:
|
||||
for (i = 0; i < length; i++) {
|
||||
offset = CURR_OFFSET;
|
||||
if (!processStringObject(NULL)) {
|
||||
@@ -460,7 +403,7 @@ int loadPair(entry *e) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case REDIS_HASH:
|
||||
case REDIS_RDB_TYPE_HASH:
|
||||
for (i = 0; i < length; i++) {
|
||||
offset = CURR_OFFSET;
|
||||
if (!processStringObject(NULL)) {
|
||||
@@ -483,7 +426,7 @@ int loadPair(entry *e) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
entry loadEntry() {
|
||||
static entry loadEntry() {
|
||||
entry e = { NULL, -1, 0 };
|
||||
uint32_t length, offset[4];
|
||||
|
||||
@@ -496,7 +439,7 @@ entry loadEntry() {
|
||||
}
|
||||
|
||||
offset[1] = CURR_OFFSET;
|
||||
if (e.type == REDIS_SELECTDB) {
|
||||
if (e.type == REDIS_RDB_OPCODE_SELECTDB) {
|
||||
if ((length = loadLength(NULL)) == REDIS_RDB_LENERR) {
|
||||
SHIFT_ERROR(offset[1], "Error reading database number");
|
||||
return e;
|
||||
@@ -505,7 +448,7 @@ entry loadEntry() {
|
||||
SHIFT_ERROR(offset[1], "Database number out of range (%d)", length);
|
||||
return e;
|
||||
}
|
||||
} else if (e.type == REDIS_EOF) {
|
||||
} else if (e.type == REDIS_RDB_OPCODE_EOF) {
|
||||
if (positions[level].offset < positions[level].size) {
|
||||
SHIFT_ERROR(offset[0], "Unexpected EOF");
|
||||
} else {
|
||||
@@ -514,8 +457,8 @@ entry loadEntry() {
|
||||
return e;
|
||||
} else {
|
||||
/* optionally consume expire */
|
||||
if (e.type == REDIS_EXPIRETIME ||
|
||||
e.type == REDIS_EXPIRETIME_MS) {
|
||||
if (e.type == REDIS_RDB_OPCODE_EXPIRETIME ||
|
||||
e.type == REDIS_RDB_OPCODE_EXPIRETIME_MS) {
|
||||
if (!processTime(e.type)) return e;
|
||||
if (!loadType(&e)) return e;
|
||||
}
|
||||
@@ -541,31 +484,31 @@ entry loadEntry() {
|
||||
return e;
|
||||
}
|
||||
|
||||
void printCentered(int indent, int width, char* body) {
|
||||
static void printCentered(int indent, int width, char* body) {
|
||||
char head[256], tail[256];
|
||||
memset(head, '\0', 256);
|
||||
memset(tail, '\0', 256);
|
||||
|
||||
memset(head, '=', indent);
|
||||
memset(tail, '=', width - 2 - indent - strlen(body));
|
||||
printf("%s %s %s\n", head, body, tail);
|
||||
redisLog(REDIS_WARNING, "%s %s %s", head, body, tail);
|
||||
}
|
||||
|
||||
void printValid(uint64_t ops, uint64_t bytes) {
|
||||
static void printValid(uint64_t ops, uint64_t bytes) {
|
||||
char body[80];
|
||||
sprintf(body, "Processed %llu valid opcodes (in %llu bytes)",
|
||||
(unsigned long long) ops, (unsigned long long) bytes);
|
||||
printCentered(4, 80, body);
|
||||
}
|
||||
|
||||
void printSkipped(uint64_t bytes, uint64_t offset) {
|
||||
static void printSkipped(uint64_t bytes, uint64_t offset) {
|
||||
char body[80];
|
||||
sprintf(body, "Skipped %llu bytes (resuming at 0x%08llx)",
|
||||
(unsigned long long) bytes, (unsigned long long) offset);
|
||||
printCentered(4, 80, body);
|
||||
}
|
||||
|
||||
void printErrorStack(entry *e) {
|
||||
static void printErrorStack(entry *e) {
|
||||
unsigned int i;
|
||||
char body[64];
|
||||
|
||||
@@ -595,23 +538,23 @@ void printErrorStack(entry *e) {
|
||||
|
||||
/* display error stack */
|
||||
for (i = 0; i < errors.level; i++) {
|
||||
printf("0x%08lx - %s\n",
|
||||
redisLog(REDIS_WARNING, "0x%08lx - %s",
|
||||
(unsigned long) errors.offset[i], errors.error[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void process() {
|
||||
void process(void) {
|
||||
uint64_t num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
|
||||
entry entry;
|
||||
entry entry = { NULL, -1, 0 };
|
||||
int dump_version = processHeader();
|
||||
|
||||
/* Exclude the final checksum for RDB >= 5. Will be checked at the end. */
|
||||
if (dump_version >= 5) {
|
||||
if (positions[0].size < 8) {
|
||||
printf("RDB version >= 5 but no room for checksum.\n");
|
||||
redisLog(REDIS_WARNING, "RDB version >= 5 but no room for checksum.");
|
||||
exit(1);
|
||||
}
|
||||
positions[0].size -= 8;;
|
||||
positions[0].size -= 8;
|
||||
}
|
||||
|
||||
level = 1;
|
||||
@@ -657,7 +600,7 @@ void process() {
|
||||
/* advance position */
|
||||
positions[0] = positions[1];
|
||||
}
|
||||
free(entry.key);
|
||||
zfree(entry.key);
|
||||
}
|
||||
|
||||
/* because there is another potential error,
|
||||
@@ -665,7 +608,7 @@ void process() {
|
||||
printValid(num_valid_ops, num_valid_bytes);
|
||||
|
||||
/* expect an eof */
|
||||
if (entry.type != REDIS_EOF) {
|
||||
if (entry.type != REDIS_RDB_OPCODE_EOF) {
|
||||
/* last byte should be EOF, add error */
|
||||
errors.level = 0;
|
||||
SHIFT_ERROR(positions[0].offset, "Expected EOF, got %s", types[entry.type]);
|
||||
@@ -693,47 +636,40 @@ void process() {
|
||||
if (crc != crc2) {
|
||||
SHIFT_ERROR(positions[0].offset, "RDB CRC64 does not match.");
|
||||
} else {
|
||||
printf("CRC64 checksum is OK\n");
|
||||
redisLog(REDIS_WARNING, "CRC64 checksum is OK");
|
||||
}
|
||||
}
|
||||
|
||||
/* print summary on errors */
|
||||
if (num_errors) {
|
||||
printf("\n");
|
||||
printf("Total unprocessable opcodes: %llu\n",
|
||||
redisLog(REDIS_WARNING, "Total unprocessable opcodes: %llu",
|
||||
(unsigned long long) num_errors);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
/* expect the first argument to be the dump file */
|
||||
if (argc <= 1) {
|
||||
printf("Usage: %s <dump.rdb>\n", argv[0]);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int redis_check_rdb(char *rdbfilename) {
|
||||
int fd;
|
||||
off_t size;
|
||||
struct stat stat;
|
||||
void *data;
|
||||
|
||||
fd = open(argv[1], O_RDONLY);
|
||||
fd = open(rdbfilename, O_RDONLY);
|
||||
if (fd < 1) {
|
||||
ERROR("Cannot open file: %s\n", argv[1]);
|
||||
ERROR("Cannot open file: %s", rdbfilename);
|
||||
}
|
||||
if (fstat(fd, &stat) == -1) {
|
||||
ERROR("Cannot stat: %s\n", argv[1]);
|
||||
ERROR("Cannot stat: %s", rdbfilename);
|
||||
} else {
|
||||
size = stat.st_size;
|
||||
}
|
||||
|
||||
if (sizeof(size_t) == sizeof(int32_t) && size >= INT_MAX) {
|
||||
ERROR("Cannot check dump files >2GB on a 32-bit platform\n");
|
||||
ERROR("Cannot check dump files >2GB on a 32-bit platform");
|
||||
}
|
||||
|
||||
data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (data == MAP_FAILED) {
|
||||
ERROR("Cannot mmap: %s\n", argv[1]);
|
||||
ERROR("Cannot mmap: %s", rdbfilename);
|
||||
}
|
||||
|
||||
/* Initialize static vars */
|
||||
@@ -743,22 +679,16 @@ int main(int argc, char **argv) {
|
||||
errors.level = 0;
|
||||
|
||||
/* Object types */
|
||||
sprintf(types[REDIS_STRING], "STRING");
|
||||
sprintf(types[REDIS_LIST], "LIST");
|
||||
sprintf(types[REDIS_SET], "SET");
|
||||
sprintf(types[REDIS_ZSET], "ZSET");
|
||||
sprintf(types[REDIS_HASH], "HASH");
|
||||
sprintf(types[REDIS_RDB_TYPE_STRING], "STRING");
|
||||
sprintf(types[REDIS_RDB_TYPE_LIST], "LIST");
|
||||
sprintf(types[REDIS_RDB_TYPE_SET], "SET");
|
||||
sprintf(types[REDIS_RDB_TYPE_ZSET], "ZSET");
|
||||
sprintf(types[REDIS_RDB_TYPE_HASH], "HASH");
|
||||
|
||||
/* Object types only used for dumping to disk */
|
||||
sprintf(types[REDIS_EXPIRETIME], "EXPIRETIME");
|
||||
sprintf(types[REDIS_SELECTDB], "SELECTDB");
|
||||
sprintf(types[REDIS_EOF], "EOF");
|
||||
|
||||
/* Double constants initialization */
|
||||
R_Zero = 0.0;
|
||||
R_PosInf = 1.0/R_Zero;
|
||||
R_NegInf = -1.0/R_Zero;
|
||||
R_Nan = R_Zero/R_Zero;
|
||||
sprintf(types[REDIS_RDB_OPCODE_EXPIRETIME], "EXPIRETIME");
|
||||
sprintf(types[REDIS_RDB_OPCODE_SELECTDB], "SELECTDB");
|
||||
sprintf(types[REDIS_RDB_OPCODE_EOF], "EOF");
|
||||
|
||||
process();
|
||||
|
||||
@@ -766,3 +696,15 @@ int main(int argc, char **argv) {
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* RDB check main: called form redis.c when Redis is executed with the
|
||||
* redis-check-rdb alias. */
|
||||
int redis_check_rdb_main(char **argv, int argc) {
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: %s <rdb-file-name>\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
redisLog(REDIS_WARNING, "Checking RDB file %s", argv[1]);
|
||||
exit(redis_check_rdb(argv[1]));
|
||||
return 0;
|
||||
}
|
||||
+406
-64
@@ -44,6 +44,7 @@
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "sds.h"
|
||||
@@ -60,6 +61,19 @@
|
||||
#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 {
|
||||
@@ -74,7 +88,10 @@ 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;
|
||||
@@ -94,10 +111,11 @@ static struct config {
|
||||
sds mb_delim;
|
||||
char prompt[128];
|
||||
char *eval;
|
||||
int last_cmd_type;
|
||||
} config;
|
||||
|
||||
static volatile sig_atomic_t force_cancel_loop = 0;
|
||||
static void usage();
|
||||
static void usage(void);
|
||||
static void slaveMode(void);
|
||||
char *redisGitSHA1(void);
|
||||
char *redisGitDirty(void);
|
||||
@@ -127,16 +145,39 @@ static void cliRefreshPrompt(void) {
|
||||
len = snprintf(config.prompt,sizeof(config.prompt),"redis %s",
|
||||
config.hostsocket);
|
||||
else
|
||||
len = snprintf(config.prompt,sizeof(config.prompt),
|
||||
strchr(config.hostip,':') ? "[%s]:%d" : "%s:%d",
|
||||
config.hostip, config.hostport);
|
||||
len = anetFormatAddr(config.prompt, sizeof(config.prompt),
|
||||
config.hostip, config.hostport);
|
||||
/* Add [dbnum] if needed */
|
||||
if (config.dbnum != 0)
|
||||
if (config.dbnum != 0 && config.last_cmd_type != REDIS_REPLY_ERROR)
|
||||
len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]",
|
||||
config.dbnum);
|
||||
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
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -157,7 +198,7 @@ typedef struct {
|
||||
static helpEntry *helpEntries;
|
||||
static int helpEntriesLen;
|
||||
|
||||
static sds cliVersion() {
|
||||
static sds cliVersion(void) {
|
||||
sds version;
|
||||
version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);
|
||||
|
||||
@@ -171,7 +212,7 @@ static sds cliVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
static void cliInitHelp() {
|
||||
static void cliInitHelp(void) {
|
||||
int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
|
||||
int groupslen = sizeof(commandGroups)/sizeof(char*);
|
||||
int i, len, pos = 0;
|
||||
@@ -210,7 +251,7 @@ static void cliOutputCommandHelp(struct commandHelp *help, int group) {
|
||||
}
|
||||
|
||||
/* Print generic help. */
|
||||
static void cliOutputGenericHelp() {
|
||||
static void cliOutputGenericHelp(void) {
|
||||
sds version = cliVersion();
|
||||
printf(
|
||||
"redis-cli %s\r\n"
|
||||
@@ -301,7 +342,7 @@ static void completionCallback(const char *buf, linenoiseCompletions *lc) {
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
/* Send AUTH command to the server */
|
||||
static int cliAuth() {
|
||||
static int cliAuth(void) {
|
||||
redisReply *reply;
|
||||
if (config.auth == NULL) return REDIS_OK;
|
||||
|
||||
@@ -314,14 +355,16 @@ static int cliAuth() {
|
||||
}
|
||||
|
||||
/* Send SELECT dbnum to the server */
|
||||
static int cliSelect() {
|
||||
static int cliSelect(void) {
|
||||
redisReply *reply;
|
||||
if (config.dbnum == 0) return REDIS_OK;
|
||||
|
||||
reply = redisCommand(context,"SELECT %d",config.dbnum);
|
||||
if (reply != NULL) {
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
|
||||
freeReplyObject(reply);
|
||||
return REDIS_OK;
|
||||
return result;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
@@ -365,7 +408,7 @@ static int cliConnect(int force) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static void cliPrintContextError() {
|
||||
static void cliPrintContextError(void) {
|
||||
if (context == NULL) return;
|
||||
fprintf(stderr,"Error: %s\n",context->errstr);
|
||||
}
|
||||
@@ -490,7 +533,7 @@ static sds cliFormatReplyCSV(redisReply *r) {
|
||||
out = sdscatrepr(out,r->str,r->len);
|
||||
break;
|
||||
case REDIS_REPLY_NIL:
|
||||
out = sdscat(out,"NIL\n");
|
||||
out = sdscat(out,"NIL");
|
||||
break;
|
||||
case REDIS_REPLY_ARRAY:
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
@@ -514,11 +557,15 @@ static int cliReadReply(int output_raw_strings) {
|
||||
int output = 1;
|
||||
|
||||
if (redisGetReply(context,&_reply) != REDIS_OK) {
|
||||
if (config.shutdown)
|
||||
if (config.shutdown) {
|
||||
redisFree(context);
|
||||
context = NULL;
|
||||
return REDIS_OK;
|
||||
}
|
||||
if (config.interactive) {
|
||||
/* Filter cases where we should reconnect */
|
||||
if (context->err == REDIS_ERR_IO && errno == ECONNRESET)
|
||||
if (context->err == REDIS_ERR_IO &&
|
||||
(errno == ECONNRESET || errno == EPIPE))
|
||||
return REDIS_ERR;
|
||||
if (context->err == REDIS_ERR_EOF)
|
||||
return REDIS_ERR;
|
||||
@@ -530,6 +577,8 @@ static int cliReadReply(int output_raw_strings) {
|
||||
|
||||
reply = (redisReply*)_reply;
|
||||
|
||||
config.last_cmd_type = reply->type;
|
||||
|
||||
/* Check if we need to connect to a different node and reissue the
|
||||
* request. */
|
||||
if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR &&
|
||||
@@ -595,6 +644,9 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
|
||||
output_raw = 0;
|
||||
if (!strcasecmp(command,"info") ||
|
||||
(argc >= 2 && !strcasecmp(command,"debug") &&
|
||||
(!strcasecmp(argv[1],"jemalloc") ||
|
||||
!strcasecmp(argv[1],"htstats"))) ||
|
||||
(argc == 2 && !strcasecmp(command,"cluster") &&
|
||||
(!strcasecmp(argv[1],"nodes") ||
|
||||
!strcasecmp(argv[1],"info"))) ||
|
||||
@@ -639,6 +691,7 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
printf("Entering slave output mode... (press Ctrl-C to quit)\n");
|
||||
slaveMode();
|
||||
config.slave_mode = 0;
|
||||
free(argvlen);
|
||||
return REDIS_ERR; /* Error = slaveMode lost connection to master */
|
||||
}
|
||||
|
||||
@@ -650,6 +703,8 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
if (!strcasecmp(command,"select") && argc == 2) {
|
||||
config.dbnum = atoi(argv[1]);
|
||||
cliRefreshPrompt();
|
||||
} else if (!strcasecmp(command,"auth") && argc == 2) {
|
||||
cliSelect();
|
||||
}
|
||||
}
|
||||
if (config.interval) usleep(config.interval);
|
||||
@@ -660,16 +715,17 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Send the INFO command, reconnecting the link if needed. */
|
||||
static redisReply *reconnectingInfo(void) {
|
||||
redisContext *c = context;
|
||||
/* Send a command reconnecting the link if needed. */
|
||||
static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ...) {
|
||||
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("Reconnecting (%d)...\r", ++tries);
|
||||
printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
|
||||
printf("Reconnecting... %d\r", ++tries);
|
||||
fflush(stdout);
|
||||
|
||||
redisFree(c);
|
||||
@@ -677,12 +733,15 @@ static redisReply *reconnectingInfo(void) {
|
||||
usleep(1000000);
|
||||
}
|
||||
|
||||
reply = redisCommand(c,"INFO");
|
||||
va_start(ap,fmt);
|
||||
reply = redisvCommand(c,fmt,ap);
|
||||
va_end(ap);
|
||||
|
||||
if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
|
||||
fprintf(stderr, "Error: %s\n", c->errstr);
|
||||
exit(1);
|
||||
} else if (tries > 0) {
|
||||
printf("\n");
|
||||
printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,13 +783,23 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.auth = argv[++i];
|
||||
} else if (!strcmp(argv[i],"--raw")) {
|
||||
config.output = OUTPUT_RAW;
|
||||
} else if (!strcmp(argv[i],"--no-raw")) {
|
||||
config.output = OUTPUT_STANDARD;
|
||||
} else if (!strcmp(argv[i],"--csv")) {
|
||||
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")) {
|
||||
@@ -795,7 +864,7 @@ static sds readArgFromStdin(void) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
static void usage(void) {
|
||||
sds version = cliVersion();
|
||||
fprintf(stderr,
|
||||
"redis-cli %s\n"
|
||||
@@ -814,10 +883,15 @@ static void usage() {
|
||||
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
|
||||
" --raw Use raw formatting for replies (default when STDOUT is\n"
|
||||
" not a tty).\n"
|
||||
" --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"
|
||||
@@ -862,8 +936,34 @@ static char **convertToSds(int count, char** args) {
|
||||
return sds;
|
||||
}
|
||||
|
||||
#define LINE_BUFLEN 4096
|
||||
static void repl() {
|
||||
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;
|
||||
char *line;
|
||||
@@ -876,10 +976,9 @@ static void repl() {
|
||||
|
||||
/* Only use history when stdin is a tty. */
|
||||
if (isatty(fileno(stdin))) {
|
||||
history = 1;
|
||||
|
||||
if (getenv("HOME") != NULL) {
|
||||
historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME"));
|
||||
historyfile = getHistoryPath();
|
||||
if (historyfile != NULL) {
|
||||
history = 1;
|
||||
linenoiseHistoryLoad(historyfile);
|
||||
}
|
||||
}
|
||||
@@ -919,26 +1018,8 @@ static void repl() {
|
||||
repeat = 1;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
config.cluster_reissue_command = 0;
|
||||
if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
|
||||
!= REDIS_OK)
|
||||
{
|
||||
cliConnect(1);
|
||||
issueCommandRepeat(argc-skipargs, argv+skipargs, repeat);
|
||||
|
||||
/* 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);
|
||||
@@ -959,10 +1040,9 @@ static int noninteractive(int argc, char **argv) {
|
||||
if (config.stdinarg) {
|
||||
argv = zrealloc(argv, (argc+1)*sizeof(char*));
|
||||
argv[argc] = readArgFromStdin();
|
||||
retval = cliSendCommand(argc+1, argv, config.repeat);
|
||||
retval = issueCommand(argc+1, argv);
|
||||
} else {
|
||||
/* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */
|
||||
retval = cliSendCommand(argc, argv, config.repeat);
|
||||
retval = issueCommand(argc, argv);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
@@ -1006,7 +1086,7 @@ static int evalMode(int argc, char **argv) {
|
||||
argv2[2] = sdscatprintf(sdsempty(),"%d",keys);
|
||||
|
||||
/* Call it */
|
||||
return cliSendCommand(argc+3-got_comma, argv2, config.repeat);
|
||||
return issueCommand(argc+3-got_comma, argv2);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
@@ -1027,7 +1107,7 @@ static void latencyMode(void) {
|
||||
if (!context) exit(1);
|
||||
while(1) {
|
||||
start = mstime();
|
||||
reply = redisCommand(context,"PING");
|
||||
reply = reconnectingRedisCommand(context,"PING");
|
||||
if (reply == NULL) {
|
||||
fprintf(stderr,"\nI/O error\n");
|
||||
exit(1);
|
||||
@@ -1057,6 +1137,148 @@ 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
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -1406,7 +1628,7 @@ static int toIntType(char *key, char *type) {
|
||||
|
||||
static void getKeyTypes(redisReply *keys, int *types) {
|
||||
redisReply *reply;
|
||||
int i;
|
||||
unsigned int i;
|
||||
|
||||
/* Pipeline TYPE commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
@@ -1435,7 +1657,7 @@ static void getKeySizes(redisReply *keys, int *types,
|
||||
{
|
||||
redisReply *reply;
|
||||
char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"};
|
||||
int i;
|
||||
unsigned int i;
|
||||
|
||||
/* Pipeline size commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
@@ -1482,7 +1704,8 @@ static void findBigKeys(void) {
|
||||
char *typename[] = {"string","list","set","hash","zset"};
|
||||
char *typeunit[] = {"bytes","items","members","fields","members"};
|
||||
redisReply *reply, *keys;
|
||||
int type, *types=NULL, arrsize=0, i;
|
||||
unsigned int arrsize=0, i;
|
||||
int type, *types=NULL;
|
||||
double pct;
|
||||
|
||||
/* Total keys pre scanning */
|
||||
@@ -1497,7 +1720,7 @@ static void findBigKeys(void) {
|
||||
for(i=0;i<TYPE_NONE; i++) {
|
||||
maxkeys[i] = sdsempty();
|
||||
if(!maxkeys[i]) {
|
||||
fprintf(stderr, "Failed to allocate memory for largest key names!");
|
||||
fprintf(stderr, "Failed to allocate memory for largest key names!\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1666,7 +1889,7 @@ void bytesToHuman(char *s, long long n) {
|
||||
}
|
||||
}
|
||||
|
||||
static void statMode() {
|
||||
static void statMode(void) {
|
||||
redisReply *reply;
|
||||
long aux, requests = 0;
|
||||
int i = 0;
|
||||
@@ -1675,7 +1898,7 @@ static void statMode() {
|
||||
char buf[64];
|
||||
int j;
|
||||
|
||||
reply = reconnectingInfo();
|
||||
reply = reconnectingRedisCommand(context,"INFO");
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
printf("ERROR: %s\n", reply->str);
|
||||
exit(1);
|
||||
@@ -1729,6 +1952,7 @@ static void statMode() {
|
||||
/* 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:
|
||||
@@ -1740,6 +1964,9 @@ static void statMode() {
|
||||
case 3:
|
||||
printf("SAVE+AOF");
|
||||
break;
|
||||
case 4:
|
||||
printf("LOAD");
|
||||
break;
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
@@ -1752,7 +1979,7 @@ static void statMode() {
|
||||
* Scan mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static void scanMode() {
|
||||
static void scanMode(void) {
|
||||
redisReply *reply;
|
||||
unsigned long long cur = 0;
|
||||
|
||||
@@ -1769,7 +1996,7 @@ static void scanMode() {
|
||||
printf("ERROR: %s\n", reply->str);
|
||||
exit(1);
|
||||
} else {
|
||||
int j;
|
||||
unsigned int j;
|
||||
|
||||
cur = strtoull(reply->element[0]->str,NULL,10);
|
||||
for (j = 0; j < reply->element[1]->elements; j++)
|
||||
@@ -1781,6 +2008,94 @@ static void scanMode() {
|
||||
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.
|
||||
*
|
||||
@@ -1840,11 +2155,15 @@ static void intrinsicLatencyMode(void) {
|
||||
printf("Max latency so far: %lld microseconds.\n", max_latency);
|
||||
}
|
||||
|
||||
double avg_us = (double)run_time/runs;
|
||||
double avg_ns = avg_us * 10e3;
|
||||
if (force_cancel_loop || end > test_end) {
|
||||
printf("\n%lld total runs (avg %lld microseconds per run).\n",
|
||||
runs, run_time/runs);
|
||||
printf("Worst run took %.02fx times the average.\n",
|
||||
(double) max_latency / (run_time/runs));
|
||||
printf("\n%lld total runs "
|
||||
"(avg latency: "
|
||||
"%.4f microseconds / %.2f nanoseconds per run).\n",
|
||||
runs, avg_us, avg_ns);
|
||||
printf("Worst run took %.0fx longer than the average latency.\n",
|
||||
max_latency / avg_us);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
@@ -1868,7 +2187,10 @@ 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;
|
||||
@@ -1883,6 +2205,11 @@ int main(int argc, char **argv) {
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
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
|
||||
@@ -1900,6 +2227,12 @@ int main(int argc, char **argv) {
|
||||
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);
|
||||
@@ -1937,11 +2270,20 @@ 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);
|
||||
|
||||
+158
-76
@@ -50,14 +50,16 @@ end
|
||||
class ClusterNode
|
||||
def initialize(addr)
|
||||
s = addr.split(":")
|
||||
if s.length != 2
|
||||
puts "Invalid node name #{addr}"
|
||||
exit 1
|
||||
if s.length < 2
|
||||
puts "Invalid IP or Port (given as #{addr}) - use IP:Port format"
|
||||
exit 1
|
||||
end
|
||||
port = s.pop # removes port from split array
|
||||
ip = s.join(":") # if s.length > 1 here, it's IPv6, so restore address
|
||||
@r = nil
|
||||
@info = {}
|
||||
@info[:host] = s[0]
|
||||
@info[:port] = s[1]
|
||||
@info[:host] = ip
|
||||
@info[:port] = port
|
||||
@info[:slots] = {}
|
||||
@info[:migrating] = {}
|
||||
@info[:importing] = {}
|
||||
@@ -70,7 +72,7 @@ class ClusterNode
|
||||
@friends
|
||||
end
|
||||
|
||||
def slots
|
||||
def slots
|
||||
@info[:slots]
|
||||
end
|
||||
|
||||
@@ -152,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!
|
||||
@@ -211,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
|
||||
@@ -271,7 +273,7 @@ class ClusterNode
|
||||
def info
|
||||
@info
|
||||
end
|
||||
|
||||
|
||||
def is_dirty?
|
||||
@dirty
|
||||
end
|
||||
@@ -538,7 +540,6 @@ 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
|
||||
@@ -556,16 +557,31 @@ class RedisTrib
|
||||
|
||||
# Select master instances
|
||||
puts "Using #{masters_count} masters:"
|
||||
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
|
||||
}
|
||||
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
|
||||
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
|
||||
@@ -592,8 +608,8 @@ class RedisTrib
|
||||
# all nodes will be used.
|
||||
assignment_verbose = false
|
||||
|
||||
[:requested,:unused].each{|assign|
|
||||
masters.each{|m|
|
||||
[:requested,:unused].each do |assign|
|
||||
masters.each do |m|
|
||||
assigned_replicas = 0
|
||||
while assigned_replicas < @replicas
|
||||
break if nodes_count == 0
|
||||
@@ -607,21 +623,33 @@ class RedisTrib
|
||||
"role too (#{nodes_count} remaining)."
|
||||
end
|
||||
end
|
||||
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
|
||||
}
|
||||
|
||||
# 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
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def flush_nodes_config
|
||||
@@ -686,8 +714,13 @@ class RedisTrib
|
||||
f[:flags].index("fail")
|
||||
fnode = ClusterNode.new(f[:addr])
|
||||
fnode.connect()
|
||||
fnode.load_info()
|
||||
add_node(fnode)
|
||||
next if !fnode.r
|
||||
begin
|
||||
fnode.load_info()
|
||||
add_node(fnode)
|
||||
rescue => e
|
||||
xputs "[ERR] Unable to load info for node #{fnode}"
|
||||
end
|
||||
}
|
||||
populate_nodes_replicas_info
|
||||
end
|
||||
@@ -756,7 +789,7 @@ 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 / reconfiguring the nodes.
|
||||
@@ -821,50 +854,96 @@ class RedisTrib
|
||||
puts "*** Please fix your cluster problems before resharding"
|
||||
exit 1
|
||||
end
|
||||
numslots = 0
|
||||
while numslots <= 0 or numslots > ClusterHashSlots
|
||||
print "How many slots do you want to move (from 1 to #{ClusterHashSlots})? "
|
||||
numslots = STDIN.gets.to_i
|
||||
|
||||
# Get number of slots
|
||||
if opt['slots']
|
||||
numslots = opt['slots'].to_i
|
||||
else
|
||||
numslots = 0
|
||||
while numslots <= 0 or numslots > ClusterHashSlots
|
||||
print "How many slots do you want to move (from 1 to #{ClusterHashSlots})? "
|
||||
numslots = STDIN.gets.to_i
|
||||
end
|
||||
end
|
||||
target = nil
|
||||
while not target
|
||||
print "What is the receiving node ID? "
|
||||
target = get_node_by_name(STDIN.gets.chop)
|
||||
|
||||
# Get the target instance
|
||||
if opt['to']
|
||||
target = get_node_by_name(opt['to'])
|
||||
if !target || target.has_flag?("slave")
|
||||
xputs "*** The specified node is not known or not a master, please retry."
|
||||
target = nil
|
||||
exit 1
|
||||
end
|
||||
else
|
||||
target = nil
|
||||
while not target
|
||||
print "What is the receiving node ID? "
|
||||
target = get_node_by_name(STDIN.gets.chop)
|
||||
if !target || target.has_flag?("slave")
|
||||
xputs "*** The specified node is not known or not a master, please retry."
|
||||
target = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Get the source instances
|
||||
sources = []
|
||||
xputs "Please enter all the source node IDs."
|
||||
xputs " Type 'all' to use all the nodes as source nodes for the hash slots."
|
||||
xputs " Type 'done' once you entered all the source nodes IDs."
|
||||
while true
|
||||
print "Source node ##{sources.length+1}:"
|
||||
line = STDIN.gets.chop
|
||||
src = get_node_by_name(line)
|
||||
if line == "done"
|
||||
if sources.length == 0
|
||||
puts "No source nodes given, operation aborted"
|
||||
exit 1
|
||||
else
|
||||
if opt['from']
|
||||
opt['from'].split(',').each{|node_id|
|
||||
if node_id == "all"
|
||||
sources = "all"
|
||||
break
|
||||
end
|
||||
elsif line == "all"
|
||||
@nodes.each{|n|
|
||||
next if n.info[:name] == target.info[:name]
|
||||
next if n.has_flag?("slave")
|
||||
sources << n
|
||||
}
|
||||
break
|
||||
elsif !src || src.has_flag?("slave")
|
||||
xputs "*** The specified node is not known or is not a master, please retry."
|
||||
elsif src.info[:name] == target.info[:name]
|
||||
xputs "*** It is not possible to use the target node as source node."
|
||||
else
|
||||
src = get_node_by_name(node_id)
|
||||
if !src || src.has_flag?("slave")
|
||||
xputs "*** The specified node is not known or is not a master, please retry."
|
||||
exit 1
|
||||
end
|
||||
sources << src
|
||||
}
|
||||
else
|
||||
xputs "Please enter all the source node IDs."
|
||||
xputs " Type 'all' to use all the nodes as source nodes for the hash slots."
|
||||
xputs " Type 'done' once you entered all the source nodes IDs."
|
||||
while true
|
||||
print "Source node ##{sources.length+1}:"
|
||||
line = STDIN.gets.chop
|
||||
src = get_node_by_name(line)
|
||||
if line == "done"
|
||||
break
|
||||
elsif line == "all"
|
||||
sources = "all"
|
||||
break
|
||||
elsif !src || src.has_flag?("slave")
|
||||
xputs "*** The specified node is not known or is not a master, please retry."
|
||||
elsif src.info[:name] == target.info[:name]
|
||||
xputs "*** It is not possible to use the target node as source node."
|
||||
else
|
||||
sources << src
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if sources.length == 0
|
||||
puts "*** No source nodes given, operation aborted"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Handle soures == all.
|
||||
if sources == "all"
|
||||
sources = []
|
||||
@nodes.each{|n|
|
||||
next if n.info[:name] == target.info[:name]
|
||||
next if n.has_flag?("slave")
|
||||
sources << n
|
||||
}
|
||||
end
|
||||
|
||||
# Check if the destination node is the same of any source nodes.
|
||||
if sources.index(target)
|
||||
xputs "*** Target node is also listed among the source nodes!"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "\nReady to move #{numslots} slots."
|
||||
puts " Source nodes:"
|
||||
sources.each{|s| puts " "+s.info_string}
|
||||
@@ -873,9 +952,11 @@ class RedisTrib
|
||||
reshard_table = compute_reshard_table(sources,numslots)
|
||||
puts " Resharding plan:"
|
||||
show_reshard_table(reshard_table)
|
||||
print "Do you want to proceed with the proposed reshard plan (yes/no)? "
|
||||
yesno = STDIN.gets.chop
|
||||
exit(1) if (yesno != "yes")
|
||||
if !opt['yes']
|
||||
print "Do you want to proceed with the proposed reshard plan (yes/no)? "
|
||||
yesno = STDIN.gets.chop
|
||||
exit(1) if (yesno != "yes")
|
||||
end
|
||||
reshard_table.each{|e|
|
||||
move_slot(e[:source],target,e[:slot],:verbose=>true)
|
||||
}
|
||||
@@ -1084,7 +1165,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.
|
||||
@@ -1151,7 +1232,7 @@ end
|
||||
|
||||
#################################################################################
|
||||
# Libraries
|
||||
#
|
||||
#
|
||||
# We try to don't depend on external libs since this is a critical part
|
||||
# of Redis Cluster.
|
||||
#################################################################################
|
||||
@@ -1253,7 +1334,8 @@ COMMANDS={
|
||||
ALLOWED_OPTIONS={
|
||||
"create" => {"replicas" => true},
|
||||
"add-node" => {"slave" => false, "master-id" => true},
|
||||
"import" => {"from" => :required}
|
||||
"import" => {"from" => :required},
|
||||
"reshard" => {"from" => true, "to" => true, "slots" => true, "yes" => false}
|
||||
}
|
||||
|
||||
def show_help
|
||||
|
||||
+490
-152
File diff suppressed because it is too large
Load Diff
+121
-45
@@ -32,10 +32,7 @@
|
||||
|
||||
#include "fmacros.h"
|
||||
#include "config.h"
|
||||
|
||||
#if defined(__sun)
|
||||
#include "solarisfixes.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -65,6 +62,13 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#include "util.h" /* Misc functions useful in many places */
|
||||
#include "latency.h" /* Latency monitor API */
|
||||
#include "sparkline.h" /* ASII graphs API */
|
||||
#include "quicklist.h"
|
||||
|
||||
/* Following includes allow test functions to be called from Redis main() */
|
||||
#include "zipmap.h"
|
||||
#include "sha1.h"
|
||||
#include "endianconv.h"
|
||||
#include "crc64.h"
|
||||
|
||||
/* Error codes */
|
||||
#define REDIS_OK 0
|
||||
@@ -96,7 +100,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDIS_REPL_TIMEOUT 60
|
||||
#define REDIS_REPL_PING_SLAVE_PERIOD 10
|
||||
#define REDIS_RUN_ID_SIZE 40
|
||||
#define REDIS_OPS_SEC_SAMPLES 16
|
||||
#define REDIS_EOF_MARK_SIZE 40
|
||||
#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 */
|
||||
@@ -113,6 +117,9 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDIS_DEFAULT_RDB_COMPRESSION 1
|
||||
#define REDIS_DEFAULT_RDB_CHECKSUM 1
|
||||
#define REDIS_DEFAULT_RDB_FILENAME "dump.rdb"
|
||||
#define REDIS_DEFAULT_REPL_DISKLESS_SYNC 0
|
||||
#define REDIS_DEFAULT_REPL_DISKLESS_LOAD 0
|
||||
#define REDIS_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5
|
||||
#define REDIS_DEFAULT_SLAVE_SERVE_STALE_DATA 1
|
||||
#define REDIS_DEFAULT_SLAVE_READ_ONLY 1
|
||||
#define REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY 0
|
||||
@@ -120,11 +127,12 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDIS_DEFAULT_MAXMEMORY_SAMPLES 5
|
||||
#define REDIS_DEFAULT_AOF_FILENAME "appendonly.aof"
|
||||
#define REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE 0
|
||||
#define REDIS_DEFAULT_AOF_LOAD_TRUNCATED 1
|
||||
#define REDIS_DEFAULT_ACTIVE_REHASHING 1
|
||||
#define REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1
|
||||
#define REDIS_DEFAULT_MIN_SLAVES_TO_WRITE 0
|
||||
#define REDIS_DEFAULT_MIN_SLAVES_MAX_LAG 10
|
||||
#define REDIS_IP_STR_LEN INET6_ADDRSTRLEN
|
||||
#define REDIS_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */
|
||||
#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,6 +144,13 @@ 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 */
|
||||
@@ -188,6 +203,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
|
||||
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
|
||||
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
|
||||
#define REDIS_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
|
||||
|
||||
/* Defines related to the dump file format. To store 32 bits lengths for short
|
||||
* keys requires a lot of space, so we check the most significant 2 bits of
|
||||
@@ -241,6 +257,8 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDIS_FORCE_REPL (1<<15) /* Force replication of current cmd. */
|
||||
#define REDIS_PRE_PSYNC (1<<16) /* Instance don't understand PSYNC. */
|
||||
#define REDIS_READONLY (1<<17) /* Cluster client is in read-only state. */
|
||||
#define REDIS_PUBSUB (1<<18) /* Client is in Pub/Sub mode. */
|
||||
#define REDIS_PREVENT_PROP (1<<19) /* Don't propagate to AOF / Slaves. */
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if REDIS_BLOCKED flag is set. */
|
||||
@@ -297,6 +315,12 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDIS_LOG_RAW (1<<10) /* Modifier to log without timestamp */
|
||||
#define REDIS_DEFAULT_VERBOSITY REDIS_NOTICE
|
||||
|
||||
/* Supervision options */
|
||||
#define REDIS_SUPERVISED_NONE 0
|
||||
#define REDIS_SUPERVISED_AUTODETECT 1
|
||||
#define REDIS_SUPERVISED_SYSTEMD 2
|
||||
#define REDIS_SUPERVISED_UPSTART 3
|
||||
|
||||
/* Anti-warning macro... */
|
||||
#define REDIS_NOTUSED(V) ((void) V)
|
||||
|
||||
@@ -312,12 +336,14 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
/* Zip structure related defaults */
|
||||
#define REDIS_HASH_MAX_ZIPLIST_ENTRIES 512
|
||||
#define REDIS_HASH_MAX_ZIPLIST_VALUE 64
|
||||
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 512
|
||||
#define REDIS_LIST_MAX_ZIPLIST_VALUE 64
|
||||
#define REDIS_SET_MAX_INTSET_ENTRIES 512
|
||||
#define REDIS_ZSET_MAX_ZIPLIST_ENTRIES 128
|
||||
#define REDIS_ZSET_MAX_ZIPLIST_VALUE 64
|
||||
|
||||
/* List defaults */
|
||||
#define REDIS_LIST_MAX_ZIPLIST_SIZE -2
|
||||
#define REDIS_LIST_COMPRESS_DEPTH 0
|
||||
|
||||
/* HyperLogLog defines */
|
||||
#define REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES 3000
|
||||
|
||||
@@ -359,6 +385,11 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define REDIS_PROPAGATE_AOF 1
|
||||
#define REDIS_PROPAGATE_REPL 2
|
||||
|
||||
/* RDB active child save type. */
|
||||
#define REDIS_RDB_CHILD_TYPE_NONE 0
|
||||
#define REDIS_RDB_CHILD_TYPE_DISK 1 /* RDB is written to disk. */
|
||||
#define REDIS_RDB_CHILD_TYPE_SOCKET 2 /* RDB is written to slave socket. */
|
||||
|
||||
/* Keyspace changes notification classes. Every class is associated with a
|
||||
* character for configuration purposes. */
|
||||
#define REDIS_NOTIFY_KEYSPACE (1<<0) /* K */
|
||||
@@ -497,7 +528,7 @@ typedef struct readyList {
|
||||
} readyList;
|
||||
|
||||
/* With multiplexing we need to take per-client state.
|
||||
* Clients are taken in a liked list. */
|
||||
* Clients are taken in a linked list. */
|
||||
typedef struct redisClient {
|
||||
uint64_t id; /* Client incremental unique ID. */
|
||||
int fd;
|
||||
@@ -513,8 +544,8 @@ typedef struct redisClient {
|
||||
int multibulklen; /* number of multi bulk arguments left to read */
|
||||
long bulklen; /* length of bulk argument in multi bulk request */
|
||||
list *reply;
|
||||
unsigned long reply_bytes; /* Tot bytes of objects in reply list */
|
||||
int sentlen; /* Amount of bytes already sent in the current
|
||||
unsigned long long reply_bytes; /* Tot bytes of objects in reply list */
|
||||
size_t sentlen; /* Amount of bytes already sent in the current
|
||||
buffer or object being sent. */
|
||||
time_t ctime; /* Client creation time */
|
||||
time_t lastinteraction; /* time of the last interaction, used for timeout */
|
||||
@@ -522,9 +553,10 @@ typedef struct redisClient {
|
||||
int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */
|
||||
int authenticated; /* when requirepass is non-NULL */
|
||||
int replstate; /* replication state if this is a slave */
|
||||
int repl_put_online_on_ack; /* Install slave write handler on ACK. */
|
||||
int repldbfd; /* replication DB file descriptor */
|
||||
off_t repldboff; /* replication DB file offset */
|
||||
off_t repldbsize; /* replication DB file size */
|
||||
off_t repldboff; /* replication DB file offset */
|
||||
off_t repldbsize; /* replication DB file size */
|
||||
sds replpreamble; /* replication DB preamble. */
|
||||
long long reploff; /* replication offset if this is our master */
|
||||
long long repl_ack_off; /* replication ack offset, if this is a slave */
|
||||
@@ -539,6 +571,7 @@ typedef struct redisClient {
|
||||
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
|
||||
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
|
||||
sds peerid; /* Cached peer ID. */
|
||||
int repl_eof_supported; /* slave supports EOF based, diskless replication */
|
||||
|
||||
/* Response buffer */
|
||||
int bufpos;
|
||||
@@ -625,6 +658,12 @@ typedef struct redisOpArray {
|
||||
|
||||
struct clusterState;
|
||||
|
||||
/* AIX defines hz to __hz, we don't use this define and in order to allow
|
||||
* Redis build on AIX we need to undef it. */
|
||||
#ifdef _AIX
|
||||
#undef hz
|
||||
#endif
|
||||
|
||||
struct redisServer {
|
||||
/* General */
|
||||
pid_t pid; /* Main process pid. */
|
||||
@@ -672,7 +711,7 @@ struct redisServer {
|
||||
off_t loading_process_events_interval_bytes;
|
||||
/* Fast pointers to often looked up command */
|
||||
struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand,
|
||||
*rpopCommand;
|
||||
*rpopCommand, *sremCommand;
|
||||
/* Fields used only for stats */
|
||||
time_t stat_starttime; /* Server start time */
|
||||
long long stat_numcommands; /* Number of processed commands */
|
||||
@@ -693,12 +732,16 @@ 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(). */
|
||||
/* 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;
|
||||
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];
|
||||
/* Configuration */
|
||||
int verbosity; /* Loglevel in redis.conf */
|
||||
int maxidletime; /* Client timeout in seconds */
|
||||
@@ -706,6 +749,8 @@ struct redisServer {
|
||||
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
||||
size_t client_max_querybuf_len; /* Limit for client query buffer length */
|
||||
int dbnum; /* Total number of configured DBs */
|
||||
int supervised; /* 1 if supervised, 0 otherwise. */
|
||||
int supervised_mode; /* See REDIS_SUPERVISED_* */
|
||||
int daemonize; /* True if running as a daemon */
|
||||
clientBufferLimitsConfig client_obuf_limits[REDIS_CLIENT_TYPE_COUNT];
|
||||
/* AOF persistence */
|
||||
@@ -732,6 +777,7 @@ struct redisServer {
|
||||
int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
|
||||
int aof_last_write_status; /* REDIS_OK or REDIS_ERR */
|
||||
int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */
|
||||
int aof_load_truncated; /* Don't stop on unexpected AOF EOF. */
|
||||
/* AOF pipes used to communicate between parent and child during rewrite. */
|
||||
int aof_pipe_write_data_to_child;
|
||||
int aof_pipe_read_data_from_parent;
|
||||
@@ -755,8 +801,11 @@ struct redisServer {
|
||||
time_t lastbgsave_try; /* Unix time of last attempted bgsave */
|
||||
time_t rdb_save_time_last; /* Time used by last RDB save run. */
|
||||
time_t rdb_save_time_start; /* Current RDB save start time. */
|
||||
int rdb_child_type; /* Type of save by active child. */
|
||||
int lastbgsave_status; /* REDIS_OK or REDIS_ERR */
|
||||
int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */
|
||||
int rdb_pipe_write_result_to_parent; /* RDB pipes used to return the state */
|
||||
int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */
|
||||
/* Propagation of commands in AOF / replication */
|
||||
redisOpArray also_propagate; /* Additional command to propagate. */
|
||||
/* Logging */
|
||||
@@ -781,6 +830,9 @@ struct redisServer {
|
||||
int repl_min_slaves_to_write; /* Min number of slaves to write. */
|
||||
int repl_min_slaves_max_lag; /* Max lag of <count> slaves to write. */
|
||||
int repl_good_slaves_count; /* Number of slaves with lag <= max_lag. */
|
||||
int repl_diskless_load; /* Slave parse RDB directly from the socket. */
|
||||
int repl_diskless_sync; /* Master send RDB to slaves sockets directly. */
|
||||
int repl_diskless_sync_delay; /* Delay to start a diskless repl BGSAVE. */
|
||||
/* Replication (slave) */
|
||||
char *masterauth; /* AUTH with this password with master */
|
||||
char *masterhost; /* Hostname of master */
|
||||
@@ -807,12 +859,12 @@ struct redisServer {
|
||||
/* Replication script cache. */
|
||||
dict *repl_scriptcache_dict; /* SHA1 all slaves are aware of. */
|
||||
list *repl_scriptcache_fifo; /* First in, first out LRU eviction. */
|
||||
int repl_scriptcache_size; /* Max number of elements. */
|
||||
unsigned int repl_scriptcache_size; /* Max number of elements. */
|
||||
/* Synchronous replication. */
|
||||
list *clients_waiting_acks; /* Clients waiting in WAIT command. */
|
||||
int get_ack_from_slaves; /* If true we send REPLCONF GETACK. */
|
||||
/* Limits */
|
||||
int maxclients; /* Max number of simultaneous clients */
|
||||
unsigned int maxclients; /* Max number of simultaneous clients */
|
||||
unsigned long long maxmemory; /* Max number of memory bytes to use */
|
||||
int maxmemory_policy; /* Policy for key eviction */
|
||||
int maxmemory_samples; /* Pricision of random sampling */
|
||||
@@ -829,12 +881,14 @@ struct redisServer {
|
||||
/* Zip structure config, see redis.conf for more information */
|
||||
size_t hash_max_ziplist_entries;
|
||||
size_t hash_max_ziplist_value;
|
||||
size_t list_max_ziplist_entries;
|
||||
size_t list_max_ziplist_value;
|
||||
size_t set_max_intset_entries;
|
||||
size_t zset_max_ziplist_entries;
|
||||
size_t zset_max_ziplist_value;
|
||||
size_t hll_sparse_max_bytes;
|
||||
/* List parameters */
|
||||
int list_max_ziplist_size;
|
||||
int list_compress_depth;
|
||||
/* time cache */
|
||||
time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
|
||||
/* Pubsub */
|
||||
@@ -849,6 +903,8 @@ struct redisServer {
|
||||
struct clusterState *cluster; /* State of the cluster */
|
||||
int cluster_migration_barrier; /* Cluster replicas migration barrier. */
|
||||
int cluster_slave_validity_factor; /* Slave max data age for failover. */
|
||||
int cluster_require_full_coverage; /* If true, put the cluster down if
|
||||
there is at least an uncovered slot. */
|
||||
/* Scripting */
|
||||
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
|
||||
redisClient *lua_client; /* The "fake client" to query Redis from Lua */
|
||||
@@ -872,6 +928,8 @@ struct redisServer {
|
||||
int assert_line;
|
||||
int bug_report_start; /* True if bug report header was already logged. */
|
||||
int watchdog_period; /* Software watchdog period in ms. 0 = off */
|
||||
/* System hardware info */
|
||||
size_t system_memory_size; /* Total memory in system as reported by OS */
|
||||
};
|
||||
|
||||
typedef struct pubsubPattern {
|
||||
@@ -920,15 +978,13 @@ typedef struct {
|
||||
robj *subject;
|
||||
unsigned char encoding;
|
||||
unsigned char direction; /* Iteration direction */
|
||||
unsigned char *zi;
|
||||
listNode *ln;
|
||||
quicklistIter *iter;
|
||||
} listTypeIterator;
|
||||
|
||||
/* Structure for an entry while iterating over a list. */
|
||||
typedef struct {
|
||||
listTypeIterator *li;
|
||||
unsigned char *zi; /* Entry in ziplist */
|
||||
listNode *ln; /* Entry in linked list */
|
||||
quicklistEntry entry; /* Entry in quicklist */
|
||||
} listTypeEntry;
|
||||
|
||||
/* Structure to hold set iteration abstraction. */
|
||||
@@ -992,23 +1048,22 @@ void freeClient(redisClient *c);
|
||||
void freeClientAsync(redisClient *c);
|
||||
void resetClient(redisClient *c);
|
||||
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReply(redisClient *c, robj *obj);
|
||||
void *addDeferredMultiBulkLength(redisClient *c);
|
||||
void setDeferredMultiBulkLength(redisClient *c, void *node, long length);
|
||||
void addReplySds(redisClient *c, sds s);
|
||||
void processInputBuffer(redisClient *c);
|
||||
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReplyBulk(redisClient *c, robj *obj);
|
||||
void addReplyBulkCString(redisClient *c, char *s);
|
||||
void addReplyBulkCBuffer(redisClient *c, void *p, size_t len);
|
||||
void addReplyBulkCString(redisClient *c, const char *s);
|
||||
void addReplyBulkCBuffer(redisClient *c, const void *p, size_t len);
|
||||
void addReplyBulkLongLong(redisClient *c, long long ll);
|
||||
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReply(redisClient *c, robj *obj);
|
||||
void addReplySds(redisClient *c, sds s);
|
||||
void addReplyError(redisClient *c, char *err);
|
||||
void addReplyStatus(redisClient *c, char *status);
|
||||
void addReplyBulkSds(redisClient *c, sds s);
|
||||
void addReplyError(redisClient *c, const char *err);
|
||||
void addReplyStatus(redisClient *c, const char *status);
|
||||
void addReplyDouble(redisClient *c, double d);
|
||||
void addReplyLongLong(redisClient *c, long long ll);
|
||||
void addReplyMultiBulkLen(redisClient *c, long length);
|
||||
@@ -1022,6 +1077,7 @@ 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);
|
||||
@@ -1056,7 +1112,7 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry);
|
||||
robj *listTypeGet(listTypeEntry *entry);
|
||||
void listTypeInsert(listTypeEntry *entry, robj *value, int where);
|
||||
int listTypeEqual(listTypeEntry *entry, robj *o);
|
||||
void listTypeDelete(listTypeEntry *entry);
|
||||
void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
|
||||
void listTypeConvert(robj *subject, int enc);
|
||||
void unblockClientWaitingData(redisClient *c);
|
||||
void handleClientsBlockedOnLists(void);
|
||||
@@ -1084,17 +1140,17 @@ void freeSetObject(robj *o);
|
||||
void freeZsetObject(robj *o);
|
||||
void freeHashObject(robj *o);
|
||||
robj *createObject(int type, void *ptr);
|
||||
robj *createStringObject(char *ptr, size_t len);
|
||||
robj *createRawStringObject(char *ptr, size_t len);
|
||||
robj *createEmbeddedStringObject(char *ptr, size_t len);
|
||||
robj *createStringObject(const char *ptr, size_t len);
|
||||
robj *createRawStringObject(const char *ptr, size_t len);
|
||||
robj *createEmbeddedStringObject(const char *ptr, size_t len);
|
||||
robj *dupStringObject(robj *o);
|
||||
int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
|
||||
robj *tryObjectEncoding(robj *o);
|
||||
robj *getDecodedObject(robj *o);
|
||||
size_t stringObjectLen(robj *o);
|
||||
robj *createStringObjectFromLongLong(long long value);
|
||||
robj *createStringObjectFromLongDouble(long double value);
|
||||
robj *createListObject(void);
|
||||
robj *createStringObjectFromLongDouble(long double value, int humanfriendly);
|
||||
robj *createQuicklistObject(void);
|
||||
robj *createZiplistObject(void);
|
||||
robj *createSetObject(void);
|
||||
robj *createIntsetObject(void);
|
||||
@@ -1123,7 +1179,7 @@ ssize_t syncReadLine(int fd, char *ptr, ssize_t size, long long timeout);
|
||||
/* Replication */
|
||||
void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc);
|
||||
void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **argv, int argc);
|
||||
void updateSlavesWaitingBgsave(int bgsaveerr);
|
||||
void updateSlavesWaitingBgsave(int bgsaveerr, int type);
|
||||
void replicationCron(void);
|
||||
void replicationHandleMasterDisconnection(void);
|
||||
void replicationCacheMaster(redisClient *c);
|
||||
@@ -1140,9 +1196,11 @@ void unblockClientWaitingReplicas(redisClient *c);
|
||||
int replicationCountAcksByOffset(long long offset);
|
||||
void replicationSendNewlineToMaster(void);
|
||||
long long replicationGetSlaveOffset(void);
|
||||
char *replicationGetSlaveName(redisClient *c);
|
||||
|
||||
/* Generic persistence functions */
|
||||
void startLoading(FILE *fp);
|
||||
void startLoading(size_t size);
|
||||
void startLoadingFile(FILE *fp);
|
||||
void loadingProgress(off_t pos);
|
||||
void stopLoading(void);
|
||||
|
||||
@@ -1187,6 +1245,7 @@ void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
|
||||
void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
|
||||
unsigned int zsetLength(robj *zobj);
|
||||
void zsetConvert(robj *zobj, int encoding);
|
||||
int zsetScore(robj *zobj, robj *member, double *score);
|
||||
unsigned long zslGetRank(zskiplist *zsl, double score, robj *o);
|
||||
|
||||
/* Core functions */
|
||||
@@ -1200,6 +1259,7 @@ void call(redisClient *c, int flags);
|
||||
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
|
||||
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
|
||||
void forceCommandPropagation(redisClient *c, int flags);
|
||||
void preventCommandPropagation(redisClient *c);
|
||||
int prepareForShutdown();
|
||||
#ifdef __GNUC__
|
||||
void redisLog(int level, const char *fmt, ...)
|
||||
@@ -1209,7 +1269,7 @@ void redisLog(int level, const char *fmt, ...);
|
||||
#endif
|
||||
void redisLogRaw(int level, const char *msg);
|
||||
void redisLogFromHandler(int level, const char *msg);
|
||||
void usage();
|
||||
void usage(void);
|
||||
void updateDictResizePolicy(void);
|
||||
int htNeedsResize(dict *dict);
|
||||
void oom(const char *msg);
|
||||
@@ -1220,6 +1280,7 @@ void closeListeningSockets(int unlink_unix_socket);
|
||||
void updateCachedTime(void);
|
||||
void resetServerStats(void);
|
||||
unsigned int getLRUClock(void);
|
||||
const char *maxmemoryToString(void);
|
||||
|
||||
/* Set data type */
|
||||
robj *setTypeCreate(robj *value);
|
||||
@@ -1231,6 +1292,7 @@ void setTypeReleaseIterator(setTypeIterator *si);
|
||||
int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele);
|
||||
robj *setTypeNextObject(setTypeIterator *si);
|
||||
int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele);
|
||||
unsigned long setTypeRandomElements(robj *set, unsigned long count, robj *aux_set);
|
||||
unsigned long setTypeSize(robj *subject);
|
||||
void setTypeConvert(robj *subject, int enc);
|
||||
|
||||
@@ -1269,9 +1331,9 @@ sds keyspaceEventsFlagsToString(int flags);
|
||||
/* Configuration */
|
||||
void loadServerConfig(char *filename, char *options);
|
||||
void appendServerSaveParams(time_t seconds, int changes);
|
||||
void resetServerSaveParams();
|
||||
void resetServerSaveParams(void);
|
||||
struct rewriteConfigState; /* Forward declaration to export API. */
|
||||
void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sds line, int force);
|
||||
void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *option, sds line, int force);
|
||||
int rewriteConfig(char *path);
|
||||
|
||||
/* db.c -- Keyspace access API */
|
||||
@@ -1326,6 +1388,10 @@ void sentinelTimer(void);
|
||||
char *sentinelHandleConfiguration(char **argv, int argc);
|
||||
void sentinelIsRunning(void);
|
||||
|
||||
/* redis-check-rdb */
|
||||
int redis_check_rdb(char *rdbfilename);
|
||||
int redis_check_rdb_main(char **argv, int argc);
|
||||
|
||||
/* Scripting */
|
||||
void scriptingInit(void);
|
||||
|
||||
@@ -1335,6 +1401,7 @@ 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);
|
||||
@@ -1456,6 +1523,7 @@ void hmsetCommand(redisClient *c);
|
||||
void hmgetCommand(redisClient *c);
|
||||
void hdelCommand(redisClient *c);
|
||||
void hlenCommand(redisClient *c);
|
||||
void hstrlenCommand(redisClient *c);
|
||||
void zremrangebyrankCommand(redisClient *c);
|
||||
void zunionstoreCommand(redisClient *c);
|
||||
void zinterstoreCommand(redisClient *c);
|
||||
@@ -1494,6 +1562,14 @@ void bitcountCommand(redisClient *c);
|
||||
void bitposCommand(redisClient *c);
|
||||
void replconfCommand(redisClient *c);
|
||||
void waitCommand(redisClient *c);
|
||||
void geoencodeCommand(redisClient *c);
|
||||
void geodecodeCommand(redisClient *c);
|
||||
void georadiusByMemberCommand(redisClient *c);
|
||||
void georadiusCommand(redisClient *c);
|
||||
void geoaddCommand(redisClient *c);
|
||||
void geohashCommand(redisClient *c);
|
||||
void geoposCommand(redisClient *c);
|
||||
void geodistCommand(redisClient *c);
|
||||
void pfselftestCommand(redisClient *c);
|
||||
void pfaddCommand(redisClient *c);
|
||||
void pfcountCommand(redisClient *c);
|
||||
|
||||
+497
-164
@@ -40,6 +40,31 @@
|
||||
void replicationDiscardCachedMaster(void);
|
||||
void replicationResurrectCachedMaster(int newfd);
|
||||
void replicationSendAck(void);
|
||||
void putSlaveOnline(redisClient *slave);
|
||||
|
||||
/* --------------------------- Utility functions ---------------------------- */
|
||||
|
||||
/* Return the pointer to a string representing the slave ip:listening_port
|
||||
* pair. Mostly useful for logging, since we want to log a slave using its
|
||||
* IP address and it's listening port which is more clear for the user, for
|
||||
* example: "Closing connection with slave 10.1.2.3:6380". */
|
||||
char *replicationGetSlaveName(redisClient *c) {
|
||||
static char buf[REDIS_PEER_ID_LEN];
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
|
||||
ip[0] = '\0';
|
||||
buf[0] = '\0';
|
||||
if (anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1) {
|
||||
if (c->slave_listening_port)
|
||||
anetFormatAddr(buf,sizeof(buf),ip,c->slave_listening_port);
|
||||
else
|
||||
snprintf(buf,sizeof(buf),"%s:<unknown-slave-port>",ip);
|
||||
} else {
|
||||
snprintf(buf,sizeof(buf),"client id #%llu",
|
||||
(unsigned long long) c->id);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ---------------------------------- MASTER -------------------------------- */
|
||||
|
||||
@@ -82,7 +107,7 @@ void resizeReplicationBacklog(long long newsize) {
|
||||
server.repl_backlog = zmalloc(server.repl_backlog_size);
|
||||
server.repl_backlog_histlen = 0;
|
||||
server.repl_backlog_idx = 0;
|
||||
/* Next byte we have is... the next since the buffer is emtpy. */
|
||||
/* Next byte we have is... the next since the buffer is empty. */
|
||||
server.repl_backlog_off = server.master_repl_offset+1;
|
||||
}
|
||||
}
|
||||
@@ -200,7 +225,7 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
|
||||
|
||||
/* We need to feed the buffer with the object as a bulk reply
|
||||
* not just as a plain string, so create the $..CRLF payload len
|
||||
* ad add the final CRLF */
|
||||
* and add the final CRLF */
|
||||
aux[0] = '$';
|
||||
len = ll2string(aux+1,sizeof(aux)-1,objlen);
|
||||
aux[len+1] = '\r';
|
||||
@@ -212,7 +237,7 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
|
||||
}
|
||||
|
||||
/* Write the command to every slave. */
|
||||
listRewind(slaves,&li);
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
@@ -345,7 +370,8 @@ int masterTryPartialResynchronization(redisClient *c) {
|
||||
"Runid mismatch (Client asked for runid '%s', my runid is '%s')",
|
||||
master_runid, server.runid);
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,"Full resync requested by slave.");
|
||||
redisLog(REDIS_NOTICE,"Full resync requested by slave %s",
|
||||
replicationGetSlaveName(c));
|
||||
}
|
||||
goto need_full_resync;
|
||||
}
|
||||
@@ -358,10 +384,10 @@ int masterTryPartialResynchronization(redisClient *c) {
|
||||
psync_offset > (server.repl_backlog_off + server.repl_backlog_histlen))
|
||||
{
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Unable to partial resync with the slave for lack of backlog (Slave request was: %lld).", psync_offset);
|
||||
"Unable to partial resync with slave %s for lack of backlog (Slave request was: %lld).", replicationGetSlaveName(c), psync_offset);
|
||||
if (psync_offset > server.master_repl_offset) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Warning: slave tried to PSYNC with an offset that is greater than the master replication offset.");
|
||||
"Warning: slave %s tried to PSYNC with an offset that is greater than the master replication offset.", replicationGetSlaveName(c));
|
||||
}
|
||||
goto need_full_resync;
|
||||
}
|
||||
@@ -373,10 +399,11 @@ int masterTryPartialResynchronization(redisClient *c) {
|
||||
c->flags |= REDIS_SLAVE;
|
||||
c->replstate = REDIS_REPL_ONLINE;
|
||||
c->repl_ack_time = server.unixtime;
|
||||
c->repl_put_online_on_ack = 0;
|
||||
listAddNodeTail(server.slaves,c);
|
||||
/* We can't use the connection buffers since they are used to accumulate
|
||||
* new commands at this stage. But we are sure the socket send buffer is
|
||||
* emtpy so this write will never fail actually. */
|
||||
* empty so this write will never fail actually. */
|
||||
buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n");
|
||||
if (write(c->fd,buf,buflen) != buflen) {
|
||||
freeClientAsync(c);
|
||||
@@ -384,7 +411,9 @@ int masterTryPartialResynchronization(redisClient *c) {
|
||||
}
|
||||
psync_len = addReplyReplicationBacklog(c,psync_offset);
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Partial resynchronization request accepted. Sending %lld bytes of backlog starting from offset %lld.", psync_len, psync_offset);
|
||||
"Partial resynchronization request from %s accepted. Sending %lld bytes of backlog starting from offset %lld.",
|
||||
replicationGetSlaveName(c),
|
||||
psync_len, psync_offset);
|
||||
/* Note that we don't need to set the selected DB at server.slaveseldb
|
||||
* to -1 to force the master to emit SELECT, since the slave already
|
||||
* has this state from the previous connection with the master. */
|
||||
@@ -408,7 +437,34 @@ need_full_resync:
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* SYNC ad PSYNC command implemenation. */
|
||||
/* Start a BGSAVE for replication goals, which is, selecting the disk or
|
||||
* socket target depending on the configuration, and making sure that
|
||||
* the script cache is flushed before to start.
|
||||
*
|
||||
* Returns REDIS_OK on success or REDIS_ERR otherwise.
|
||||
*
|
||||
* The caller should pass '1' as the function argument if all the slaves
|
||||
* currently waiting for a BGSAVE all claimed to support the EOF-style
|
||||
* streaming format for RDB transfer. Otherwise it should be '0'. */
|
||||
int startBgsaveForReplication(int all_slaves_supprot_eof) {
|
||||
int retval;
|
||||
int use_eof = all_slaves_support_eof && server.repl_diskless_sync;
|
||||
|
||||
redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC with target: %s",
|
||||
use_eof ? "slaves sockets" : "disk");
|
||||
|
||||
if (use_eof)
|
||||
retval = rdbSaveToSlavesSockets();
|
||||
else
|
||||
retval = rdbSaveBackground(server.rdb_filename);
|
||||
|
||||
/* 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();
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* SYNC and PSYNC command implemenation. */
|
||||
void syncCommand(redisClient *c) {
|
||||
/* ignore SYNC if already slave or in monitor mode */
|
||||
if (c->flags & REDIS_SLAVE) return;
|
||||
@@ -429,7 +485,8 @@ void syncCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
redisLog(REDIS_NOTICE,"Slave asks for synchronization");
|
||||
redisLog(REDIS_NOTICE,"Slave %s asks for synchronization",
|
||||
replicationGetSlaveName(c));
|
||||
|
||||
/* Try a partial resynchronization if this is a PSYNC command.
|
||||
* If it fails, we continue with usual full resynchronization, however
|
||||
@@ -465,10 +522,12 @@ void syncCommand(redisClient *c) {
|
||||
|
||||
/* 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) {
|
||||
if (server.rdb_child_pid != -1 &&
|
||||
server.rdb_child_type == REDIS_RDB_CHILD_TYPE_DISK)
|
||||
{
|
||||
/* Ok a background save is in progress. Let's check if it is a good
|
||||
* one for replication, i.e. if there is another slave that is
|
||||
* registering differences since the server forked to save */
|
||||
* registering differences since the server forked to save. */
|
||||
redisClient *slave;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
@@ -486,21 +545,35 @@ void syncCommand(redisClient *c) {
|
||||
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 */
|
||||
* register differences. */
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
|
||||
redisLog(REDIS_NOTICE,"Waiting for next BGSAVE for SYNC");
|
||||
}
|
||||
} 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");
|
||||
} else {
|
||||
/* Ok we don't have a BGSAVE in progress, let's start one */
|
||||
redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC");
|
||||
if (rdbSaveBackground(server.rdb_filename) != REDIS_OK) {
|
||||
redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE");
|
||||
addReplyError(c,"Unable to perform background save");
|
||||
return;
|
||||
if (server.repl_diskless_sync && c->repl_eof_supported) {
|
||||
/* 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 {
|
||||
/* Ok we don't have a BGSAVE in progress, let's start one. */
|
||||
if (startBgsaveForReplication(0) != 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;
|
||||
}
|
||||
c->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
/* Flush the script cache for the new slave. */
|
||||
replicationScriptCacheFlush();
|
||||
}
|
||||
|
||||
if (server.repl_disable_tcp_nodelay)
|
||||
@@ -545,6 +618,8 @@ void replconfCommand(redisClient *c) {
|
||||
&port,NULL) != REDIS_OK))
|
||||
return;
|
||||
c->slave_listening_port = port;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"eof-supported")) {
|
||||
c->repl_eof_supported = 1;
|
||||
} 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
|
||||
@@ -557,6 +632,11 @@ void replconfCommand(redisClient *c) {
|
||||
if (offset > c->repl_ack_off)
|
||||
c->repl_ack_off = offset;
|
||||
c->repl_ack_time = server.unixtime;
|
||||
/* If this was a diskless replication, we need to really put
|
||||
* the slave online when the first ACK is received (which
|
||||
* confirms slave is online and ready to get more data). */
|
||||
if (c->repl_put_online_on_ack && c->replstate == REDIS_REPL_ONLINE)
|
||||
putSlaveOnline(c);
|
||||
/* Note: this command does not reply anything! */
|
||||
return;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"getack")) {
|
||||
@@ -573,6 +653,33 @@ void replconfCommand(redisClient *c) {
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
/* This function puts a slave in the online state, and should be called just
|
||||
* after a slave received the RDB file for the initial synchronization, and
|
||||
* we are finally ready to send the incremental stream of commands.
|
||||
*
|
||||
* 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).
|
||||
* 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.
|
||||
* 3) Update the count of good slaves. */
|
||||
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. */
|
||||
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE,
|
||||
sendReplyToClient, slave) == AE_ERR) {
|
||||
redisLog(REDIS_WARNING,"Unable to register writable event for slave bulk transfer: %s", strerror(errno));
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
refreshGoodSlavesCount();
|
||||
redisLog(REDIS_NOTICE,"Synchronization with slave %s succeeded",
|
||||
replicationGetSlaveName(slave));
|
||||
}
|
||||
|
||||
void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
redisClient *slave = privdata;
|
||||
REDIS_NOTUSED(el);
|
||||
@@ -591,6 +698,7 @@ 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);
|
||||
@@ -619,32 +727,33 @@ 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;
|
||||
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
|
||||
slave->replstate = REDIS_REPL_ONLINE;
|
||||
slave->repl_ack_time = server.unixtime;
|
||||
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE,
|
||||
sendReplyToClient, slave) == AE_ERR) {
|
||||
redisLog(REDIS_WARNING,"Unable to register writable event for slave bulk transfer: %s", strerror(errno));
|
||||
freeClient(slave);
|
||||
return;
|
||||
}
|
||||
refreshGoodSlavesCount();
|
||||
redisLog(REDIS_NOTICE,"Synchronization with slave succeeded");
|
||||
putSlaveOnline(slave);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called at the end of every background saving.
|
||||
* The argument bgsaveerr is REDIS_OK if the background saving succeeded
|
||||
* otherwise REDIS_ERR is passed to the function.
|
||||
/* This function is called at the end of every background saving,
|
||||
* or when the replication RDB transfer strategy is modified from
|
||||
* disk to socket or the other way around.
|
||||
*
|
||||
* The goal of this function is to handle slaves waiting for a successful
|
||||
* background saving in order to perform non-blocking synchronization. */
|
||||
void updateSlavesWaitingBgsave(int bgsaveerr) {
|
||||
* background saving in order to perform non-blocking synchronization, and
|
||||
* to schedule a new BGSAVE if there are slaves that attached while a
|
||||
* BGSAVE was in progress, but it was not a good one for replication (no
|
||||
* other slave was accumulating differences).
|
||||
*
|
||||
* The argument bgsaveerr is REDIS_OK if the background saving succeeded
|
||||
* otherwise REDIS_ERR is passed to the function.
|
||||
* The 'type' argument is the type of the child that terminated
|
||||
* (if it had a disk or socket target). */
|
||||
void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
||||
listNode *ln;
|
||||
int startbgsave = 0;
|
||||
int slaves_waiting_eof = 0;
|
||||
int slaves_waiting_noneof = 0;
|
||||
listIter li;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
@@ -652,42 +761,61 @@ void updateSlavesWaitingBgsave(int bgsaveerr) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
|
||||
startbgsave = 1;
|
||||
if (slave->repl_eof_supported)
|
||||
slaves_waiting_eof++;
|
||||
else
|
||||
slaves_waiting_noneof++;
|
||||
slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
|
||||
} else if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) {
|
||||
struct redis_stat buf;
|
||||
|
||||
if (bgsaveerr != REDIS_OK) {
|
||||
freeClient(slave);
|
||||
redisLog(REDIS_WARNING,"SYNC failed. BGSAVE child returned an error");
|
||||
continue;
|
||||
}
|
||||
if ((slave->repldbfd = open(server.rdb_filename,O_RDONLY)) == -1 ||
|
||||
redis_fstat(slave->repldbfd,&buf) == -1) {
|
||||
freeClient(slave);
|
||||
redisLog(REDIS_WARNING,"SYNC failed. Can't open/stat DB after BGSAVE: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
slave->repldboff = 0;
|
||||
slave->repldbsize = buf.st_size;
|
||||
slave->replstate = REDIS_REPL_SEND_BULK;
|
||||
slave->replpreamble = sdscatprintf(sdsempty(),"$%lld\r\n",
|
||||
(unsigned long long) slave->repldbsize);
|
||||
/* If this was an RDB on disk save, we have to prepare to send
|
||||
* the RDB from disk to the slave socket. Otherwise if this was
|
||||
* already an RDB -> Slaves socket transfer, used in the case of
|
||||
* diskless replication, our work is trivial, we can just put
|
||||
* the slave online. */
|
||||
if (type == REDIS_RDB_CHILD_TYPE_SOCKET) {
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Streamed RDB transfer with slave %s succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming",
|
||||
replicationGetSlaveName(slave));
|
||||
/* Note: we wait for a REPLCONF ACK message from slave in
|
||||
* order to really put it online (install the write handler
|
||||
* so that the accumulated data can be transfered). However
|
||||
* we change the replication state ASAP, since our slave
|
||||
* 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);
|
||||
redisLog(REDIS_WARNING,"SYNC failed. BGSAVE child returned an error");
|
||||
continue;
|
||||
}
|
||||
if ((slave->repldbfd = open(server.rdb_filename,O_RDONLY)) == -1 ||
|
||||
redis_fstat(slave->repldbfd,&buf) == -1) {
|
||||
freeClient(slave);
|
||||
redisLog(REDIS_WARNING,"SYNC failed. Can't open/stat DB after BGSAVE: %s", strerror(errno));
|
||||
continue;
|
||||
}
|
||||
slave->repldboff = 0;
|
||||
slave->repldbsize = buf.st_size;
|
||||
slave->replstate = REDIS_REPL_SEND_BULK;
|
||||
slave->replpreamble = sdscatprintf(sdsempty(),"$%lld\r\n",
|
||||
(unsigned long long) slave->repldbsize);
|
||||
|
||||
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
|
||||
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, sendBulkToSlave, slave) == AE_ERR) {
|
||||
freeClient(slave);
|
||||
continue;
|
||||
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
|
||||
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, sendBulkToSlave, slave) == AE_ERR) {
|
||||
freeClient(slave);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (startbgsave) {
|
||||
/* Since we are starting a new background save for one or more slaves,
|
||||
* we flush the Replication Script Cache to use EVAL to propagate every
|
||||
* new EVALSHA for the first time, since all the new slaves don't know
|
||||
* about previous scripts. */
|
||||
replicationScriptCacheFlush();
|
||||
if (rdbSaveBackground(server.rdb_filename) != REDIS_OK) {
|
||||
if (slaves_waiting_eof || slaves_waiting_noneof) {
|
||||
/* if there is at least one slave that doesn't support EOF, we'll
|
||||
* start an non-eof replication */
|
||||
if (startBgsaveForReplication(slaves_waiting_noneof==0) != REDIS_OK) {
|
||||
listIter li;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
@@ -710,9 +838,13 @@ void replicationAbortSyncTransfer(void) {
|
||||
|
||||
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
|
||||
close(server.repl_transfer_s);
|
||||
close(server.repl_transfer_fd);
|
||||
unlink(server.repl_transfer_tmpfile);
|
||||
zfree(server.repl_transfer_tmpfile);
|
||||
if (server.repl_transfer_fd!=-1) {
|
||||
close(server.repl_transfer_fd);
|
||||
unlink(server.repl_transfer_tmpfile);
|
||||
zfree(server.repl_transfer_tmpfile);
|
||||
server.repl_transfer_tmpfile = NULL;
|
||||
server.repl_transfer_fd = -1;
|
||||
}
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
}
|
||||
|
||||
@@ -741,16 +873,40 @@ void replicationEmptyDbCallback(void *privdata) {
|
||||
replicationSendNewlineToMaster();
|
||||
}
|
||||
|
||||
/* Once we have a link with the master and the synchroniziation was
|
||||
* performed, this function materializes the master client we store
|
||||
* at server.master, starting from the specified file descriptor. */
|
||||
void replicationCreateMasterClient(int fd) {
|
||||
server.master = createClient(fd);
|
||||
server.master->flags |= REDIS_MASTER;
|
||||
server.master->authenticated = 1;
|
||||
server.repl_state = REDIS_REPL_CONNECTED;
|
||||
server.master->reploff = server.repl_master_initial_offset;
|
||||
memcpy(server.master->replrunid, server.repl_master_runid,
|
||||
sizeof(server.repl_master_runid));
|
||||
/* If master offset is set to -1, this master is old and is not
|
||||
* PSYNC capable, so we flag it accordingly. */
|
||||
if (server.master->reploff == -1)
|
||||
server.master->flags |= REDIS_PRE_PSYNC;
|
||||
}
|
||||
|
||||
/* Asynchronously read the SYNC payload we receive from a master */
|
||||
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
|
||||
void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
char buf[4096];
|
||||
ssize_t nread, readlen;
|
||||
off_t left;
|
||||
sds initialCommandStream = NULL;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(privdata);
|
||||
REDIS_NOTUSED(mask);
|
||||
|
||||
/* Static vars used to hold the EOF mark, and the last bytes received
|
||||
* form the server: when they match, we reached the end of the transfer. */
|
||||
static char eofmark[REDIS_RUN_ID_SIZE];
|
||||
static char lastbytes[REDIS_RUN_ID_SIZE];
|
||||
static int usemark = 0;
|
||||
|
||||
/* If repl_transfer_size == -1 we still have to read the bulk length
|
||||
* from the master reply. */
|
||||
if (server.repl_transfer_size == -1) {
|
||||
@@ -776,97 +932,208 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
redisLog(REDIS_WARNING,"Bad protocol from MASTER, the first byte is not '$' (we received '%s'), are you sure the host and port are right?", buf);
|
||||
goto error;
|
||||
}
|
||||
server.repl_transfer_size = strtol(buf+1,NULL,10);
|
||||
redisLog(REDIS_NOTICE,
|
||||
"MASTER <-> SLAVE sync: receiving %lld bytes from master",
|
||||
(long long) server.repl_transfer_size);
|
||||
|
||||
/* There are two possible forms for the bulk payload. One is the
|
||||
* usual $<count> bulk format. The other is used for diskless transfers
|
||||
* when the master does not know beforehand the size of the file to
|
||||
* transfer. In the latter case, the following format is used:
|
||||
*
|
||||
* $EOF:<40 bytes delimiter>
|
||||
*
|
||||
* At the end of the file the announced delimiter is transmitted. The
|
||||
* delimiter is long and random enough that the probability of a
|
||||
* collision with the actual file content can be ignored. */
|
||||
if (strncmp(buf+1,"EOF:",4) == 0 && strlen(buf+5) >= REDIS_RUN_ID_SIZE) {
|
||||
usemark = 1;
|
||||
memcpy(eofmark,buf+5,REDIS_RUN_ID_SIZE);
|
||||
memset(lastbytes,0,REDIS_RUN_ID_SIZE);
|
||||
/* Set any repl_transfer_size to avoid entering this code path
|
||||
* at the next call. */
|
||||
server.repl_transfer_size = 0;
|
||||
redisLog(REDIS_NOTICE,
|
||||
"MASTER <-> SLAVE sync: receiving streamed RDB from master with EOF %s",
|
||||
server.repl_diskless_load? "to parser":"to disk");
|
||||
} else {
|
||||
usemark = 0;
|
||||
server.repl_transfer_size = strtol(buf+1,NULL,10);
|
||||
redisLog(REDIS_NOTICE,
|
||||
"MASTER <-> SLAVE sync: receiving %lld bytes from master %s",
|
||||
(long long) server.repl_transfer_size,
|
||||
server.repl_diskless_load? "to parser":"to disk");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read bulk data */
|
||||
left = server.repl_transfer_size - server.repl_transfer_read;
|
||||
readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf);
|
||||
nread = read(fd,buf,readlen);
|
||||
if (nread <= 0) {
|
||||
redisLog(REDIS_WARNING,"I/O error trying to sync with MASTER: %s",
|
||||
(nread == -1) ? strerror(errno) : "connection lost");
|
||||
replicationAbortSyncTransfer();
|
||||
return;
|
||||
}
|
||||
server.repl_transfer_lastio = server.unixtime;
|
||||
if (write(server.repl_transfer_fd,buf,nread) != nread) {
|
||||
redisLog(REDIS_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> SLAVE synchronization: %s", strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
server.repl_transfer_read += nread;
|
||||
if (!server.repl_diskless_load) {
|
||||
/* read the data from the socket, store it to a file and search for the EOF */
|
||||
if (usemark) {
|
||||
readlen = sizeof(buf);
|
||||
} else {
|
||||
left = server.repl_transfer_size - server.repl_transfer_read;
|
||||
readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf);
|
||||
}
|
||||
|
||||
/* Sync data on disk from time to time, otherwise at the end of the transfer
|
||||
* we may suffer a big delay as the memory buffers are copied into the
|
||||
* actual disk. */
|
||||
if (server.repl_transfer_read >=
|
||||
server.repl_transfer_last_fsync_off + REPL_MAX_WRITTEN_BEFORE_FSYNC)
|
||||
{
|
||||
off_t sync_size = server.repl_transfer_read -
|
||||
server.repl_transfer_last_fsync_off;
|
||||
rdb_fsync_range(server.repl_transfer_fd,
|
||||
server.repl_transfer_last_fsync_off, sync_size);
|
||||
server.repl_transfer_last_fsync_off += sync_size;
|
||||
nread = read(fd,buf,readlen);
|
||||
if (nread <= 0) {
|
||||
redisLog(REDIS_WARNING,"I/O error trying to sync with MASTER: %s",
|
||||
(nread == -1) ? strerror(errno) : "connection lost");
|
||||
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... */
|
||||
int eof_reached = 0;
|
||||
|
||||
if (usemark) {
|
||||
/* Update the last bytes array, and check if it matches our delimiter.*/
|
||||
if (nread >= REDIS_RUN_ID_SIZE) {
|
||||
memcpy(lastbytes,buf+nread-REDIS_RUN_ID_SIZE,REDIS_RUN_ID_SIZE);
|
||||
} else {
|
||||
int rem = REDIS_RUN_ID_SIZE-nread;
|
||||
memmove(lastbytes,lastbytes+nread,rem);
|
||||
memcpy(lastbytes+rem,buf,nread);
|
||||
}
|
||||
if (memcmp(lastbytes,eofmark,REDIS_RUN_ID_SIZE) == 0) eof_reached = 1;
|
||||
}
|
||||
|
||||
server.repl_transfer_lastio = server.unixtime;
|
||||
if (write(server.repl_transfer_fd,buf,nread) != nread) {
|
||||
redisLog(REDIS_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> SLAVE synchronization: %s", strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
server.repl_transfer_read += nread;
|
||||
|
||||
/* Delete the last 40 bytes from the file if we reached EOF. */
|
||||
if (usemark && eof_reached) {
|
||||
if (ftruncate(server.repl_transfer_fd,
|
||||
server.repl_transfer_read - REDIS_RUN_ID_SIZE) == -1)
|
||||
{
|
||||
redisLog(REDIS_WARNING,"Error truncating the RDB file received from the master for SYNC: %s", strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sync data on disk from time to time, otherwise at the end of the transfer
|
||||
* we may suffer a big delay as the memory buffers are copied into the
|
||||
* actual disk. */
|
||||
if (server.repl_transfer_read >=
|
||||
server.repl_transfer_last_fsync_off + REPL_MAX_WRITTEN_BEFORE_FSYNC)
|
||||
{
|
||||
off_t sync_size = server.repl_transfer_read -
|
||||
server.repl_transfer_last_fsync_off;
|
||||
rdb_fsync_range(server.repl_transfer_fd,
|
||||
server.repl_transfer_last_fsync_off, sync_size);
|
||||
server.repl_transfer_last_fsync_off += sync_size;
|
||||
}
|
||||
|
||||
/* Check if the transfer is now complete */
|
||||
if (!usemark) {
|
||||
if (server.repl_transfer_read == server.repl_transfer_size)
|
||||
eof_reached = 1;
|
||||
}
|
||||
if (!eof_reached)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if the transfer is now complete */
|
||||
if (server.repl_transfer_read == server.repl_transfer_size) {
|
||||
/* We reach here when the slave is using diskless replication,
|
||||
* or when we are done reading from the socket to the rdb file. */
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Flushing old data");
|
||||
signalFlushedDb(-1);
|
||||
emptyDb(replicationEmptyDbCallback);
|
||||
/* Before loading the DB into memory we need to delete the readable
|
||||
* handler, otherwise it will get called recursively since
|
||||
* rdbLoad() will call the event loop to process events from time to
|
||||
* time for non blocking loading. */
|
||||
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Loading DB in memory");
|
||||
if (server.repl_diskless_load) {
|
||||
rio rdb;
|
||||
rioInitWithFd(&rdb,fd);
|
||||
/* Put the socket in blocking mode to simplify RDB transfer.
|
||||
* We'll restore it when the RDB is received. */
|
||||
anetBlock(NULL,fd);
|
||||
anetRecvTimeout(NULL,fd,server.repl_timeout*1000);
|
||||
|
||||
if (rdbLoadRio(&rdb, server.repl_transfer_size) != REDIS_OK) {
|
||||
redisLog(REDIS_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
|
||||
replicationAbortSyncTransfer();
|
||||
rioFreeFd(&rdb, NULL);
|
||||
/* Remove the half-loaded data, and load back the old dataset
|
||||
* if we have persistence turned on.
|
||||
*
|
||||
* TODO:
|
||||
* 1) Actually allow rdbLoadRio() to don't fail with exit().
|
||||
* 2) Load RDB / AOF.
|
||||
*
|
||||
* Right now this code path is not entered when the connection
|
||||
* breaks between master and slave AFAIK.
|
||||
*/
|
||||
emptyDb(NULL);
|
||||
return;
|
||||
}
|
||||
if (usemark) {
|
||||
if (!rioRead(&rdb,buf,REDIS_RUN_ID_SIZE) || memcmp(buf,eofmark,REDIS_RUN_ID_SIZE) != 0) {
|
||||
redisLog(REDIS_WARNING,"replication stream EOF marker is broken");
|
||||
replicationAbortSyncTransfer();
|
||||
rioFreeFd(&rdb, NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* get the unread command stream from the rio buffer */
|
||||
rioFreeFd(&rdb, &initialCommandStream);
|
||||
/* Restore the socket as non-blocking. */
|
||||
anetNonBlock(NULL,fd);
|
||||
anetRecvTimeout(NULL,fd,0);
|
||||
} else {
|
||||
if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
|
||||
redisLog(REDIS_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> SLAVE synchronization: %s", strerror(errno));
|
||||
replicationAbortSyncTransfer();
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Flushing old data");
|
||||
signalFlushedDb(-1);
|
||||
emptyDb(replicationEmptyDbCallback);
|
||||
/* Before loading the DB into memory we need to delete the readable
|
||||
* handler, otherwise it will get called recursively since
|
||||
* rdbLoad() will call the event loop to process events from time to
|
||||
* time for non blocking loading. */
|
||||
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Loading DB in memory");
|
||||
if (rdbLoad(server.rdb_filename) != REDIS_OK) {
|
||||
redisLog(REDIS_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
|
||||
replicationAbortSyncTransfer();
|
||||
return;
|
||||
}
|
||||
/* Final setup of the connected slave <- master link */
|
||||
zfree(server.repl_transfer_tmpfile);
|
||||
close(server.repl_transfer_fd);
|
||||
server.master = createClient(server.repl_transfer_s);
|
||||
server.master->flags |= REDIS_MASTER;
|
||||
server.master->authenticated = 1;
|
||||
server.repl_state = REDIS_REPL_CONNECTED;
|
||||
server.master->reploff = server.repl_master_initial_offset;
|
||||
memcpy(server.master->replrunid, server.repl_master_runid,
|
||||
sizeof(server.repl_master_runid));
|
||||
/* If master offset is set to -1, this master is old and is not
|
||||
* PSYNC capable, so we flag it accordingly. */
|
||||
if (server.master->reploff == -1)
|
||||
server.master->flags |= REDIS_PRE_PSYNC;
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
|
||||
/* Restart the AOF subsystem now that we finished the sync. This
|
||||
* will trigger an AOF rewrite, and when done will start appending
|
||||
* to the new file. */
|
||||
if (server.aof_state != REDIS_AOF_OFF) {
|
||||
int retry = 10;
|
||||
|
||||
stopAppendOnly();
|
||||
while (retry-- && startAppendOnly() == REDIS_ERR) {
|
||||
redisLog(REDIS_WARNING,"Failed enabling the AOF after successful master synchronization! Trying it again in one second.");
|
||||
sleep(1);
|
||||
}
|
||||
if (!retry) {
|
||||
redisLog(REDIS_WARNING,"FATAL: this slave instance finished the synchronization with its master, but the AOF can't be turned on. Exiting now.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
server.repl_transfer_fd = -1;
|
||||
server.repl_transfer_tmpfile = NULL;
|
||||
}
|
||||
/* Final setup of the connected slave <- master link */
|
||||
replicationCreateMasterClient(server.repl_transfer_s);
|
||||
/* feed the initial command stream into the client input buffer */
|
||||
if (initialCommandStream) {
|
||||
int nread = sdslen(initialCommandStream);
|
||||
server.current_client = server.master;
|
||||
server.master->querybuf = sdscatsds(server.master->querybuf, initialCommandStream);
|
||||
server.master->reploff += nread;
|
||||
server.master->lastinteraction = server.unixtime;
|
||||
server.stat_net_input_bytes += nread;
|
||||
processInputBuffer(server.master);
|
||||
server.current_client = NULL;
|
||||
sdsfree(initialCommandStream);
|
||||
}
|
||||
|
||||
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
|
||||
/* Restart the AOF subsystem now that we finished the sync. This
|
||||
* will trigger an AOF rewrite, and when done will start appending
|
||||
* to the new file. */
|
||||
if (server.aof_state != REDIS_AOF_OFF) {
|
||||
int retry = 10;
|
||||
|
||||
stopAppendOnly();
|
||||
while (retry-- && startAppendOnly() == REDIS_ERR) {
|
||||
redisLog(REDIS_WARNING,"Failed enabling the AOF after successful master synchronization! Trying it again in one second.");
|
||||
sleep(1);
|
||||
}
|
||||
if (!retry) {
|
||||
redisLog(REDIS_WARNING,"FATAL: this slave instance finished the synchronization with its master, but the AOF can't be turned on. Exiting now.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
error:
|
||||
@@ -1130,6 +1397,18 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
sdsfree(err);
|
||||
}
|
||||
|
||||
/* Inform the master that this slave supports EOF marker of diskless-sync */
|
||||
{
|
||||
err = sendSynchronousCommand(fd,"REPLCONF","eof-supported","yes",
|
||||
NULL);
|
||||
/* Ignore the error if any, not all the Redis versions support
|
||||
* REPLCONF eof-supported. */
|
||||
if (err[0] == '-') {
|
||||
redisLog(REDIS_NOTICE,"(Non critical) Master does not understand REPLCONF eof-supported: %s", err);
|
||||
}
|
||||
sdsfree(err);
|
||||
}
|
||||
|
||||
/* Try a partial resynchonization. If we don't have a cached master
|
||||
* slaveTryPartialResynchronization() will at least try to use PSYNC
|
||||
* to start a full resynchronization so that we get the master run id
|
||||
@@ -1154,16 +1433,20 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
|
||||
/* Prepare a suitable temp file for bulk transfer */
|
||||
while(maxtries--) {
|
||||
snprintf(tmpfile,256,
|
||||
"temp-%d.%ld.rdb",(int)server.unixtime,(long int)getpid());
|
||||
dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644);
|
||||
if (dfd != -1) break;
|
||||
sleep(1);
|
||||
}
|
||||
if (dfd == -1) {
|
||||
redisLog(REDIS_WARNING,"Opening the temp file needed for MASTER <-> SLAVE synchronization: %s",strerror(errno));
|
||||
goto error;
|
||||
if (!server.repl_diskless_load) {
|
||||
while(maxtries--) {
|
||||
snprintf(tmpfile,256,
|
||||
"temp-%d.%ld.rdb",(int)server.unixtime,(long int)getpid());
|
||||
dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644);
|
||||
if (dfd != -1) break;
|
||||
sleep(1);
|
||||
}
|
||||
if (dfd == -1) {
|
||||
redisLog(REDIS_WARNING,"Opening the temp file needed for MASTER <-> SLAVE synchronization: %s",strerror(errno));
|
||||
goto error;
|
||||
}
|
||||
server.repl_transfer_tmpfile = zstrdup(tmpfile);
|
||||
server.repl_transfer_fd = dfd;
|
||||
}
|
||||
|
||||
/* Setup the non blocking download of the bulk file. */
|
||||
@@ -1180,13 +1463,17 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
server.repl_transfer_size = -1;
|
||||
server.repl_transfer_read = 0;
|
||||
server.repl_transfer_last_fsync_off = 0;
|
||||
server.repl_transfer_fd = dfd;
|
||||
server.repl_transfer_lastio = server.unixtime;
|
||||
server.repl_transfer_tmpfile = zstrdup(tmpfile);
|
||||
return;
|
||||
|
||||
error:
|
||||
close(fd);
|
||||
if (server.repl_transfer_fd != -1)
|
||||
close(server.repl_transfer_fd);
|
||||
if (server.repl_transfer_tmpfile)
|
||||
zfree(server.repl_transfer_tmpfile);
|
||||
server.repl_transfer_tmpfile = NULL;
|
||||
server.repl_transfer_fd = -1;
|
||||
server.repl_transfer_s = -1;
|
||||
server.repl_state = REDIS_REPL_CONNECT;
|
||||
return;
|
||||
@@ -1195,7 +1482,8 @@ error:
|
||||
int connectWithMaster(void) {
|
||||
int fd;
|
||||
|
||||
fd = anetTcpNonBlockConnect(NULL,server.masterhost,server.masterport);
|
||||
fd = anetTcpNonBlockBestEffortBindConnect(NULL,
|
||||
server.masterhost,server.masterport,REDIS_BIND_ADDR);
|
||||
if (fd == -1) {
|
||||
redisLog(REDIS_WARNING,"Unable to connect to MASTER: %s",
|
||||
strerror(errno));
|
||||
@@ -1256,6 +1544,7 @@ 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. */
|
||||
@@ -1460,7 +1749,7 @@ void replicationDiscardCachedMaster(void) {
|
||||
/* Turn the cached master into the current master, using the file descriptor
|
||||
* passed as argument as the socket for the new master.
|
||||
*
|
||||
* This funciton is called when successfully setup a partial resynchronization
|
||||
* This function is called when successfully setup a partial resynchronization
|
||||
* so the stream of data that we'll receive will start from were this
|
||||
* master left. */
|
||||
void replicationResurrectCachedMaster(int newfd) {
|
||||
@@ -1750,7 +2039,7 @@ long long replicationGetSlaveOffset(void) {
|
||||
|
||||
/* --------------------------- REPLICATION CRON ---------------------------- */
|
||||
|
||||
/* Replication cron funciton, called 1 time per second. */
|
||||
/* Replication cron function, called 1 time per second. */
|
||||
void replicationCron(void) {
|
||||
/* Non blocking connection timeout? */
|
||||
if (server.masterhost &&
|
||||
@@ -1817,7 +2106,9 @@ void replicationCron(void) {
|
||||
redisClient *slave = ln->value;
|
||||
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START ||
|
||||
slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) {
|
||||
(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. */
|
||||
}
|
||||
@@ -1838,14 +2129,8 @@ void replicationCron(void) {
|
||||
if (slave->flags & REDIS_PRE_PSYNC) continue;
|
||||
if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout)
|
||||
{
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
int port;
|
||||
|
||||
if (anetPeerToString(slave->fd,ip,sizeof(ip),&port) != -1) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Disconnecting timedout slave: %s:%d",
|
||||
ip, slave->slave_listening_port);
|
||||
}
|
||||
redisLog(REDIS_WARNING, "Disconnecting timedout slave: %s",
|
||||
replicationGetSlaveName(slave));
|
||||
freeClient(slave);
|
||||
}
|
||||
}
|
||||
@@ -1877,6 +2162,54 @@ void replicationCron(void) {
|
||||
replicationScriptCacheFlush();
|
||||
}
|
||||
|
||||
/* If we are using diskless replication and there are slaves waiting
|
||||
* in WAIT_BGSAVE_START state, check if enough seconds elapsed and
|
||||
* start a BGSAVE.
|
||||
*
|
||||
* This code is also useful to trigger a BGSAVE if the diskless
|
||||
* replication was turned off with CONFIG SET, while there were already
|
||||
* slaves in WAIT_BGSAVE_START state. */
|
||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
|
||||
time_t idle, max_idle = 0;
|
||||
int slaves_waiting_eof = 0;
|
||||
int slaves_waiting_noneof = 0;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
redisClient *slave = ln->value;
|
||||
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
|
||||
idle = server.unixtime - slave->lastinteraction;
|
||||
if (idle > max_idle) max_idle = idle;
|
||||
if (slave->repl_eof_supported)
|
||||
slaves_waiting_eof++;
|
||||
else
|
||||
slaves_waiting_noneof++;
|
||||
}
|
||||
}
|
||||
|
||||
if ((slaves_waiting_eof || slaves_waiting_noneof) && 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.
|
||||
* if there is at least one slave that doesn't support EOF, we'll
|
||||
* start an non-eof replication */
|
||||
if (startBgsaveForReplication(slaves_waiting_noneof==0) == 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();
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@
|
||||
#include "config.h"
|
||||
#include "redis.h"
|
||||
|
||||
/* ------------------------- Buffer I/O implementation ----------------------- */
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioBufferWrite(rio *r, const void *buf, size_t len) {
|
||||
r->io.buffer.ptr = sdscatlen(r->io.buffer.ptr,(char*)buf,len);
|
||||
@@ -76,6 +78,33 @@ static off_t rioBufferTell(rio *r) {
|
||||
return r->io.buffer.pos;
|
||||
}
|
||||
|
||||
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
||||
* and 0 on failures. */
|
||||
static int rioBufferFlush(rio *r) {
|
||||
REDIS_NOTUSED(r);
|
||||
return 1; /* Nothing to do, our write just appends to the buffer. */
|
||||
}
|
||||
|
||||
static const rio rioBufferIO = {
|
||||
rioBufferRead,
|
||||
rioBufferWrite,
|
||||
rioBufferTell,
|
||||
rioBufferFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
void rioInitWithBuffer(rio *r, sds s) {
|
||||
*r = rioBufferIO;
|
||||
r->io.buffer.ptr = s;
|
||||
r->io.buffer.pos = 0;
|
||||
}
|
||||
|
||||
/* --------------------- Stdio file pointer implementation ------------------- */
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
|
||||
size_t retval;
|
||||
@@ -103,21 +132,17 @@ static off_t rioFileTell(rio *r) {
|
||||
return ftello(r->io.file.fp);
|
||||
}
|
||||
|
||||
static const rio rioBufferIO = {
|
||||
rioBufferRead,
|
||||
rioBufferWrite,
|
||||
rioBufferTell,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
||||
* and 0 on failures. */
|
||||
static int rioFileFlush(rio *r) {
|
||||
return (fflush(r->io.file.fp) == 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
static const rio rioFileIO = {
|
||||
rioFileRead,
|
||||
rioFileWrite,
|
||||
rioFileTell,
|
||||
rioFileFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* bytes read or written */
|
||||
@@ -132,12 +157,227 @@ void rioInitWithFile(rio *r, FILE *fp) {
|
||||
r->io.file.autosync = 0;
|
||||
}
|
||||
|
||||
void rioInitWithBuffer(rio *r, sds s) {
|
||||
*r = rioBufferIO;
|
||||
r->io.buffer.ptr = s;
|
||||
r->io.buffer.pos = 0;
|
||||
/* ------------------- File descriptor implementation ------------------- */
|
||||
|
||||
static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
|
||||
REDIS_NOTUSED(r);
|
||||
REDIS_NOTUSED(buf);
|
||||
REDIS_NOTUSED(len);
|
||||
return 0; /* Error, this target does not yet support writing. */
|
||||
}
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioFdRead(rio *r, void *buf, size_t len) {
|
||||
size_t avail = sdslen(r->io.fd.buf)-r->io.fd.pos;
|
||||
|
||||
/* if the buffer is too small for the entire request: realloc */
|
||||
if (sdslen(r->io.fd.buf) + sdsavail(r->io.fd.buf) < len)
|
||||
r->io.fd.buf = sdsMakeRoomFor(r->io.fd.buf, len - sdslen(r->io.fd.buf));
|
||||
|
||||
/* if the remaining unused buffer is not large enough: memmove so that we can read the rest */
|
||||
if (len > avail && sdsavail(r->io.fd.buf) < len - avail) {
|
||||
sdsrange(r->io.fd.buf, r->io.fd.pos, -1);
|
||||
r->io.fd.pos = 0;
|
||||
}
|
||||
|
||||
/* if we don't already have all the data in the sds, read more */
|
||||
while (len > sdslen(r->io.fd.buf) - r->io.fd.pos) {
|
||||
size_t toread = len - (sdslen(r->io.fd.buf) - r->io.fd.pos);
|
||||
/* read either what's missing, or REDIS_IOBUF_LEN, the bigger of the two */
|
||||
if (toread < REDIS_IOBUF_LEN)
|
||||
toread = REDIS_IOBUF_LEN;
|
||||
if (toread > sdsavail(r->io.fd.buf))
|
||||
toread = sdsavail(r->io.fd.buf);
|
||||
int retval = read(r->io.fd.fd, (char*)r->io.fd.buf + sdslen(r->io.fd.buf), toread);
|
||||
if (retval <= 0) {
|
||||
if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
|
||||
return 0;
|
||||
}
|
||||
sdsIncrLen(r->io.fd.buf, retval);
|
||||
}
|
||||
|
||||
memcpy(buf, (char*)r->io.fd.buf + r->io.fd.pos, len);
|
||||
r->io.fd.pos += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Returns read/write position in file. */
|
||||
static off_t rioFdTell(rio *r) {
|
||||
off_t pos = lseek(r->io.fd.fd, 0, SEEK_CUR);
|
||||
return pos - sdslen(r->io.fd.buf) + r->io.fd.pos;
|
||||
}
|
||||
|
||||
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
||||
* and 0 on failures. */
|
||||
static int rioFdFlush(rio *r) {
|
||||
/* Our flush is implemented by the write method, that recognizes a
|
||||
* buffer set to NULL with a count of zero as a flush request. */
|
||||
return rioFdWrite(r,NULL,0);
|
||||
}
|
||||
|
||||
static const rio rioFdIO = {
|
||||
rioFdRead,
|
||||
rioFdWrite,
|
||||
rioFdTell,
|
||||
rioFdFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
void rioInitWithFd(rio *r, int fd) {
|
||||
*r = rioFdIO;
|
||||
r->io.fd.fd = fd;
|
||||
r->io.fd.pos = 0;
|
||||
r->io.fd.buf = sdsnewlen(NULL, REDIS_IOBUF_LEN);
|
||||
sdsclear(r->io.fd.buf);
|
||||
}
|
||||
|
||||
/* release the rio stream.
|
||||
* optionally returns the unread buffered data. */
|
||||
void rioFreeFd(rio *r, sds* out_remainingBufferedData) {
|
||||
if(out_remainingBufferedData && (size_t)r->io.fd.pos < sdslen(r->io.fd.buf)) {
|
||||
if (r->io.fd.pos > 0)
|
||||
sdsrange(r->io.fd.buf, r->io.fd.pos, -1);
|
||||
*out_remainingBufferedData = r->io.fd.buf;
|
||||
} else {
|
||||
sdsfree(r->io.fd.buf);
|
||||
if (out_remainingBufferedData)
|
||||
*out_remainingBufferedData = NULL;
|
||||
}
|
||||
r->io.fd.buf = NULL;
|
||||
}
|
||||
|
||||
/* ------------------- File descriptors set implementation ------------------- */
|
||||
|
||||
/* Returns 1 or 0 for success/failure.
|
||||
* The function returns success as long as we are able to correctly write
|
||||
* to at least one file descriptor.
|
||||
*
|
||||
* When buf is NULL and len is 0, the function performs a flush operation
|
||||
* if there is some pending buffer, so this function is also used in order
|
||||
* to implement rioFdsetFlush(). */
|
||||
static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
|
||||
ssize_t retval;
|
||||
int j;
|
||||
unsigned char *p = (unsigned char*) buf;
|
||||
int doflush = (buf == NULL && len == 0);
|
||||
|
||||
/* To start we always append to our buffer. If it gets larger than
|
||||
* a given size, we actually write to the sockets. */
|
||||
if (len) {
|
||||
r->io.fdset.buf = sdscatlen(r->io.fdset.buf,buf,len);
|
||||
len = 0; /* Prevent entering the while below if we don't flush. */
|
||||
if (sdslen(r->io.fdset.buf) > REDIS_IOBUF_LEN) doflush = 1;
|
||||
}
|
||||
|
||||
if (doflush) {
|
||||
p = (unsigned char*) r->io.fdset.buf;
|
||||
len = sdslen(r->io.fdset.buf);
|
||||
}
|
||||
|
||||
/* Write in little chunchs so that when there are big writes we
|
||||
* parallelize while the kernel is sending data in background to
|
||||
* the TCP socket. */
|
||||
while(len) {
|
||||
size_t count = len < 1024 ? len : 1024;
|
||||
int broken = 0;
|
||||
for (j = 0; j < r->io.fdset.numfds; j++) {
|
||||
if (r->io.fdset.state[j] != 0) {
|
||||
/* Skip FDs alraedy in error. */
|
||||
broken++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Make sure to write 'count' bytes to the socket regardless
|
||||
* of short writes. */
|
||||
size_t nwritten = 0;
|
||||
while(nwritten != count) {
|
||||
retval = write(r->io.fdset.fds[j],p+nwritten,count-nwritten);
|
||||
if (retval <= 0) {
|
||||
/* With blocking sockets, which is the sole user of this
|
||||
* rio target, EWOULDBLOCK is returned only because of
|
||||
* the SO_SNDTIMEO socket option, so we translate the error
|
||||
* into one more recognizable by the user. */
|
||||
if (retval == -1 && errno == EWOULDBLOCK) errno = ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
nwritten += retval;
|
||||
}
|
||||
|
||||
if (nwritten != count) {
|
||||
/* Mark this FD as broken. */
|
||||
r->io.fdset.state[j] = errno;
|
||||
if (r->io.fdset.state[j] == 0) r->io.fdset.state[j] = EIO;
|
||||
}
|
||||
}
|
||||
if (broken == r->io.fdset.numfds) return 0; /* All the FDs in error. */
|
||||
p += count;
|
||||
len -= count;
|
||||
r->io.fdset.pos += count;
|
||||
}
|
||||
|
||||
if (doflush) sdsclear(r->io.fdset.buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
static size_t rioFdsetRead(rio *r, void *buf, size_t len) {
|
||||
REDIS_NOTUSED(r);
|
||||
REDIS_NOTUSED(buf);
|
||||
REDIS_NOTUSED(len);
|
||||
return 0; /* Error, this target does not support reading. */
|
||||
}
|
||||
|
||||
/* Returns read/write position in file. */
|
||||
static off_t rioFdsetTell(rio *r) {
|
||||
return r->io.fdset.pos;
|
||||
}
|
||||
|
||||
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
||||
* and 0 on failures. */
|
||||
static int rioFdsetFlush(rio *r) {
|
||||
/* Our flush is implemented by the write method, that recognizes a
|
||||
* buffer set to NULL with a count of zero as a flush request. */
|
||||
return rioFdsetWrite(r,NULL,0);
|
||||
}
|
||||
|
||||
static const rio rioFdsetIO = {
|
||||
rioFdsetRead,
|
||||
rioFdsetWrite,
|
||||
rioFdsetTell,
|
||||
rioFdsetFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
void rioInitWithFdset(rio *r, int *fds, int numfds) {
|
||||
int j;
|
||||
|
||||
*r = rioFdsetIO;
|
||||
r->io.fdset.fds = zmalloc(sizeof(int)*numfds);
|
||||
r->io.fdset.state = zmalloc(sizeof(int)*numfds);
|
||||
memcpy(r->io.fdset.fds,fds,sizeof(int)*numfds);
|
||||
for (j = 0; j < numfds; j++) r->io.fdset.state[j] = 0;
|
||||
r->io.fdset.numfds = numfds;
|
||||
r->io.fdset.pos = 0;
|
||||
r->io.fdset.buf = sdsempty();
|
||||
}
|
||||
|
||||
/* release the rio stream. */
|
||||
void rioFreeFdset(rio *r) {
|
||||
zfree(r->io.fdset.fds);
|
||||
zfree(r->io.fdset.state);
|
||||
sdsfree(r->io.fdset.buf);
|
||||
}
|
||||
|
||||
/* ---------------------------- Generic functions ---------------------------- */
|
||||
|
||||
/* This function can be installed both in memory and file streams when checksum
|
||||
* computation is needed. */
|
||||
void rioGenericUpdateChecksum(rio *r, const void *buf, size_t len) {
|
||||
@@ -157,7 +397,8 @@ void rioSetAutoSync(rio *r, off_t bytes) {
|
||||
r->io.file.autosync = bytes;
|
||||
}
|
||||
|
||||
/* ------------------------------ Higher level interface ---------------------------
|
||||
/* --------------------------- Higher level interface --------------------------
|
||||
*
|
||||
* The following higher level functions use lower level rio.c functions to help
|
||||
* generating the Redis protocol for the Append Only File. */
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ struct _rio {
|
||||
size_t (*read)(struct _rio *, void *buf, size_t len);
|
||||
size_t (*write)(struct _rio *, const void *buf, size_t len);
|
||||
off_t (*tell)(struct _rio *);
|
||||
int (*flush)(struct _rio *);
|
||||
/* The update_cksum method if not NULL is used to compute the checksum of
|
||||
* all the data that was read or written so far. The method should be
|
||||
* designed so that can be called with the current checksum, and the buf
|
||||
@@ -61,15 +62,31 @@ struct _rio {
|
||||
|
||||
/* Backend-specific vars. */
|
||||
union {
|
||||
/* In-memory buffer target. */
|
||||
struct {
|
||||
sds ptr;
|
||||
off_t pos;
|
||||
} buffer;
|
||||
/* Stdio file pointer target. */
|
||||
struct {
|
||||
FILE *fp;
|
||||
off_t buffered; /* Bytes written since last fsync. */
|
||||
off_t autosync; /* fsync after 'autosync' bytes written. */
|
||||
} file;
|
||||
/* file descriptor */
|
||||
struct {
|
||||
int fd; /* File descriptor. */
|
||||
off_t pos;
|
||||
sds buf;
|
||||
} fd;
|
||||
/* Multiple FDs target (used to write to N sockets). */
|
||||
struct {
|
||||
int *fds; /* File descriptors. */
|
||||
int *state; /* Error state of each fd. 0 (if ok) or errno. */
|
||||
int numfds;
|
||||
off_t pos;
|
||||
sds buf;
|
||||
} fdset;
|
||||
} io;
|
||||
};
|
||||
|
||||
@@ -109,8 +126,17 @@ static inline off_t rioTell(rio *r) {
|
||||
return r->tell(r);
|
||||
}
|
||||
|
||||
static inline int rioFlush(rio *r) {
|
||||
return r->flush(r);
|
||||
}
|
||||
|
||||
void rioInitWithFile(rio *r, FILE *fp);
|
||||
void rioInitWithBuffer(rio *r, sds s);
|
||||
void rioInitWithFd(rio *r, int fd);
|
||||
void rioInitWithFdset(rio *r, int *fds, int numfds);
|
||||
|
||||
void rioFreeFdset(rio *r);
|
||||
void rioFreeFd(rio *r, sds* out_remainingBufferedData);
|
||||
|
||||
size_t rioWriteBulkCount(rio *r, char prefix, int count);
|
||||
size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
|
||||
|
||||
+57
-13
@@ -30,6 +30,7 @@
|
||||
#include "redis.h"
|
||||
#include "sha1.h"
|
||||
#include "rand.h"
|
||||
#include "cluster.h"
|
||||
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
@@ -212,19 +213,33 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
static robj **argv = NULL;
|
||||
static int argv_size = 0;
|
||||
static robj *cached_objects[LUA_CMD_OBJCACHE_SIZE];
|
||||
static int cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
|
||||
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;
|
||||
}
|
||||
|
||||
/* Build the arguments vector */
|
||||
if (!argv) {
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
} else if (argv_size < argc) {
|
||||
if (argv_size < argc) {
|
||||
argv = zrealloc(argv,sizeof(robj*)*argc);
|
||||
argv_size = argc;
|
||||
}
|
||||
@@ -274,6 +289,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
}
|
||||
luaPushError(lua,
|
||||
"Lua redis() command arguments must be strings or integers");
|
||||
inuse--;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -293,6 +309,7 @@ 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) {
|
||||
@@ -339,8 +356,24 @@ 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.
|
||||
@@ -402,6 +435,7 @@ cleanup:
|
||||
if (c->argv != argv) {
|
||||
zfree(c->argv);
|
||||
argv = NULL;
|
||||
argv_size = 0;
|
||||
}
|
||||
|
||||
if (raise_error) {
|
||||
@@ -410,8 +444,10 @@ cleanup:
|
||||
* return the plain error. */
|
||||
lua_pushstring(lua,"err");
|
||||
lua_gettable(lua,-2);
|
||||
inuse--;
|
||||
return lua_error(lua);
|
||||
}
|
||||
inuse--;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -538,6 +574,7 @@ 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);
|
||||
@@ -548,6 +585,7 @@ 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);
|
||||
@@ -574,11 +612,12 @@ 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 debug.getinfo(2) then\n";
|
||||
s[j++]=" local w = debug.getinfo(2, \"S\").what\n";
|
||||
s[j++]=" if dbg.getinfo(2) then\n";
|
||||
s[j++]=" local w = dbg.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";
|
||||
@@ -586,11 +625,12 @@ 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 debug.getinfo(2) and debug.getinfo(2, \"S\").what ~= \"C\" then\n";
|
||||
s[j++]=" if dbg.getinfo(2) and dbg.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]));
|
||||
@@ -694,10 +734,11 @@ 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 = "function __redis__err__handler(err)\n"
|
||||
" local i = debug.getinfo(2,'nSl')\n"
|
||||
char *errh_func = "local dbg = debug\n"
|
||||
"function __redis__err__handler(err)\n"
|
||||
" local i = dbg.getinfo(2,'nSl')\n"
|
||||
" if i and i.what == 'C' then\n"
|
||||
" i = debug.getinfo(3,'nSl')\n"
|
||||
" i = dbg.getinfo(3,'nSl')\n"
|
||||
" end\n"
|
||||
" if i then\n"
|
||||
" return i.source .. ':' .. i.currentline .. ': ' .. err\n"
|
||||
@@ -718,7 +759,7 @@ void scriptingInit(void) {
|
||||
server.lua_client->flags |= REDIS_LUA_CLIENT;
|
||||
}
|
||||
|
||||
/* Lua beginners ofter don't use "local", this is likely to introduce
|
||||
/* Lua beginners often don't use "local", this is likely to introduce
|
||||
* subtle bugs in their code. To prevent problems we protect accesses
|
||||
* to global variables. */
|
||||
scriptingEnableGlobalsProtection(lua);
|
||||
@@ -839,7 +880,7 @@ void luaSetGlobalArray(lua_State *lua, char *var, robj **elev, int elec) {
|
||||
}
|
||||
|
||||
/* Define a lua function with the specified function name and body.
|
||||
* The function name musts be a 2 characters long string, since all the
|
||||
* The function name musts be a 42 characters long string, since all the
|
||||
* functions we defined in the Lua context are in the form:
|
||||
*
|
||||
* f_<hex sha1 sum>
|
||||
@@ -910,6 +951,9 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
if (numkeys > (c->argc - 3)) {
|
||||
addReplyError(c,"Number of keys can't be greater than number of args");
|
||||
return;
|
||||
} else if (numkeys < 0) {
|
||||
addReplyError(c,"Number of keys can't be negative");
|
||||
return;
|
||||
}
|
||||
|
||||
/* We obtain the script SHA1, then check if this function is already
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
* The string is always null-termined (all the sds strings are, always) so
|
||||
* even if you create an sds string with:
|
||||
*
|
||||
* mystring = sdsnewlen("abc",3");
|
||||
* mystring = sdsnewlen("abc",3);
|
||||
*
|
||||
* You can print the string with printf() as there is an implicit \0 at the
|
||||
* end of the string. However the string is binary safe and can contain
|
||||
@@ -71,7 +71,7 @@ sds sdsempty(void) {
|
||||
return sdsnewlen("",0);
|
||||
}
|
||||
|
||||
/* Create a new sds string starting from a null termined C string. */
|
||||
/* Create a new sds string starting from a null terminated C string. */
|
||||
sds sdsnew(const char *init) {
|
||||
size_t initlen = (init == NULL) ? 0 : strlen(init);
|
||||
return sdsnewlen(init, initlen);
|
||||
@@ -109,7 +109,7 @@ void sdsupdatelen(sds s) {
|
||||
sh->len = reallen;
|
||||
}
|
||||
|
||||
/* Modify an sds string on-place to make it empty (zero length).
|
||||
/* Modify an sds string in-place to make it empty (zero length).
|
||||
* However all the existing buffer is not discarded but set as free space
|
||||
* so that next append operations will not require allocations up to the
|
||||
* number of bytes previously available. */
|
||||
@@ -200,10 +200,12 @@ size_t sdsAllocSize(sds s) {
|
||||
void sdsIncrLen(sds s, int incr) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
|
||||
assert(sh->free >= incr);
|
||||
if (incr >= 0)
|
||||
assert(sh->free >= (unsigned int)incr);
|
||||
else
|
||||
assert(sh->len >= (unsigned int)(-incr));
|
||||
sh->len += incr;
|
||||
sh->free -= incr;
|
||||
assert(sh->free >= 0);
|
||||
s[sh->len] = '\0';
|
||||
}
|
||||
|
||||
@@ -293,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 lenght of the null-terminated string
|
||||
* The function returns the length of the null-terminated string
|
||||
* representation stored at 's'. */
|
||||
#define SDS_LLSTR_SIZE 21
|
||||
int sdsll2str(char *s, long long value) {
|
||||
@@ -367,7 +369,7 @@ sds sdsfromlonglong(long long value) {
|
||||
return sdsnewlen(buf,len);
|
||||
}
|
||||
|
||||
/* Like sdscatpritf() but gets va_list instead of being variadic. */
|
||||
/* Like sdscatprintf() 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;
|
||||
@@ -388,6 +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);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
if (buf != staticbuf) zfree(buf);
|
||||
buflen *= 2;
|
||||
@@ -412,7 +415,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* s = sdsempty("Sum is: ");
|
||||
* s = sdsnew("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
|
||||
@@ -457,7 +460,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
i = initlen; /* Position of the next byte to write to dest str. */
|
||||
while(*f) {
|
||||
char next, *str;
|
||||
int l;
|
||||
unsigned int l;
|
||||
long long num;
|
||||
unsigned long long unum;
|
||||
|
||||
@@ -554,7 +557,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
* Example:
|
||||
*
|
||||
* s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
|
||||
* s = sdstrim(s,"A. :");
|
||||
* s = sdstrim(s,"Aa. :");
|
||||
* printf("%s\n", s);
|
||||
*
|
||||
* Output will be just "Hello World".
|
||||
@@ -567,7 +570,7 @@ sds sdstrim(sds s, const char *cset) {
|
||||
sp = start = s;
|
||||
ep = end = s+sdslen(s)-1;
|
||||
while(sp <= end && strchr(cset, *sp)) sp++;
|
||||
while(ep > start && strchr(cset, *ep)) ep--;
|
||||
while(ep > sp && strchr(cset, *ep)) ep--;
|
||||
len = (sp > ep) ? 0 : ((ep-sp)+1);
|
||||
if (sh->buf != sp) memmove(sh->buf, sp, len);
|
||||
sh->buf[len] = '\0';
|
||||
@@ -640,8 +643,8 @@ void sdstoupper(sds s) {
|
||||
*
|
||||
* Return value:
|
||||
*
|
||||
* 1 if s1 > s2.
|
||||
* -1 if s1 < s2.
|
||||
* positive if s1 > s2.
|
||||
* negative 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
|
||||
@@ -959,12 +962,15 @@ sds sdsjoin(char **argv, int argc, char *sep) {
|
||||
return join;
|
||||
}
|
||||
|
||||
#ifdef SDS_TEST_MAIN
|
||||
#if defined(REDIS_TEST) || defined(SDS_TEST_MAIN)
|
||||
#include <stdio.h>
|
||||
#include "testhelp.h"
|
||||
#include "limits.h"
|
||||
|
||||
int main(void) {
|
||||
#define UNUSED(x) (void)(x)
|
||||
int sdsTest(int argc, char *argv[]) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
{
|
||||
struct sdshdr *sh;
|
||||
sds x = sdsnew("foo"), y;
|
||||
@@ -1010,6 +1016,18 @@ int main(void) {
|
||||
sdslen(x) == 35 &&
|
||||
memcmp(x,"--4294967295,18446744073709551615--",35) == 0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnew(" x ");
|
||||
sdstrim(x," x");
|
||||
test_cond("sdstrim() works when all chars match",
|
||||
sdslen(x) == 0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnew(" x ");
|
||||
sdstrim(x," ");
|
||||
test_cond("sdstrim() works when a single char remains",
|
||||
sdslen(x) == 1 && x[0] == 'x')
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnew("xxciaoyyy");
|
||||
sdstrim(x,"xy");
|
||||
@@ -1077,9 +1095,10 @@ int main(void) {
|
||||
memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
|
||||
|
||||
{
|
||||
int oldfree;
|
||||
unsigned 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);
|
||||
@@ -1092,9 +1111,17 @@ 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()
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SDS_TEST_MAIN
|
||||
int main(void) {
|
||||
return sdsTest();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
typedef char *sds;
|
||||
|
||||
struct sdshdr {
|
||||
int len;
|
||||
int free;
|
||||
unsigned int len;
|
||||
unsigned int free;
|
||||
char buf[];
|
||||
};
|
||||
|
||||
@@ -98,4 +98,8 @@ void sdsIncrLen(sds s, int incr);
|
||||
sds sdsRemoveFreeSpace(sds s);
|
||||
size_t sdsAllocSize(sds s);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int sdsTest(int argc, char *argv[]);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
+752
-310
File diff suppressed because it is too large
Load Diff
+13
-14
@@ -23,10 +23,8 @@ A million repetitions of "a"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h> /* for u_int*_t */
|
||||
#if defined(__sun)
|
||||
#include <stdint.h>
|
||||
#include "solarisfixes.h"
|
||||
#endif
|
||||
#include "sha1.h"
|
||||
#include "config.h"
|
||||
|
||||
@@ -55,12 +53,12 @@ A million repetitions of "a"
|
||||
|
||||
/* Hash a single 512-bit block. This is the core of the algorithm. */
|
||||
|
||||
void SHA1Transform(u_int32_t state[5], const unsigned char buffer[64])
|
||||
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
|
||||
{
|
||||
u_int32_t a, b, c, d, e;
|
||||
uint32_t a, b, c, d, e;
|
||||
typedef union {
|
||||
unsigned char c[64];
|
||||
u_int32_t l[16];
|
||||
uint32_t l[16];
|
||||
} CHAR64LONG16;
|
||||
#ifdef SHA1HANDSOFF
|
||||
CHAR64LONG16 block[1]; /* use array to appear as a pointer */
|
||||
@@ -130,9 +128,9 @@ void SHA1Init(SHA1_CTX* context)
|
||||
|
||||
/* Run your data through this. */
|
||||
|
||||
void SHA1Update(SHA1_CTX* context, const unsigned char* data, u_int32_t len)
|
||||
void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len)
|
||||
{
|
||||
u_int32_t i, j;
|
||||
uint32_t i, j;
|
||||
|
||||
j = context->count[0];
|
||||
if ((context->count[0] += len << 3) < j)
|
||||
@@ -170,7 +168,7 @@ void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
u_int32_t t = context->count[i];
|
||||
uint32_t t = context->count[i];
|
||||
int j;
|
||||
|
||||
for (j = 0; j < 4; t >>= 8, j++)
|
||||
@@ -199,16 +197,19 @@ void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
|
||||
}
|
||||
/* ================ end of sha1.c ================ */
|
||||
|
||||
#if 0
|
||||
#ifdef REDIS_TEST
|
||||
#define BUFSIZE 4096
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
#define UNUSED(x) (void)(x)
|
||||
int sha1Test(int argc, char **argv)
|
||||
{
|
||||
SHA1_CTX ctx;
|
||||
unsigned char hash[20], buf[BUFSIZE];
|
||||
int i;
|
||||
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
|
||||
for(i=0;i<BUFSIZE;i++)
|
||||
buf[i] = i;
|
||||
|
||||
@@ -223,6 +224,4 @@ main(int argc, char **argv)
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
+11
-4
@@ -1,3 +1,5 @@
|
||||
#ifndef SHA1_H
|
||||
#define SHA1_H
|
||||
/* ================ sha1.h ================ */
|
||||
/*
|
||||
SHA-1 in C
|
||||
@@ -6,12 +8,17 @@ By Steve Reid <steve@edmweb.com>
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
u_int32_t state[5];
|
||||
u_int32_t count[2];
|
||||
uint32_t state[5];
|
||||
uint32_t count[2];
|
||||
unsigned char buffer[64];
|
||||
} SHA1_CTX;
|
||||
|
||||
void SHA1Transform(u_int32_t state[5], const unsigned char buffer[64]);
|
||||
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
|
||||
void SHA1Init(SHA1_CTX* context);
|
||||
void SHA1Update(SHA1_CTX* context, const unsigned char* data, u_int32_t len);
|
||||
void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len);
|
||||
void SHA1Final(unsigned char digest[20], SHA1_CTX* context);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int sha1Test(int argc, char **argv);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#if defined(__sun)
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#include <math.h>
|
||||
#undef isnan
|
||||
@@ -48,3 +50,5 @@
|
||||
#define u_int uint
|
||||
#define u_int32_t uint32_t
|
||||
#endif /* __GNUC__ */
|
||||
|
||||
#endif /* __sun */
|
||||
|
||||
+46
-25
@@ -209,7 +209,7 @@ void sortCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
/* Create a list of operations to perform for every sorted element.
|
||||
* Operations can be GET/DEL/INCR/DECR */
|
||||
* Operations can be GET */
|
||||
operations = listCreate();
|
||||
listSetFreeMethod(operations,zfree);
|
||||
j = 2; /* options start at argv[2] */
|
||||
@@ -220,7 +220,7 @@ void sortCommand(redisClient *c) {
|
||||
if (sortval)
|
||||
incrRefCount(sortval);
|
||||
else
|
||||
sortval = createListObject();
|
||||
sortval = createQuicklistObject();
|
||||
|
||||
/* The SORT command has an SQL-alike syntax, parse it */
|
||||
while(j < c->argc) {
|
||||
@@ -285,16 +285,15 @@ void sortCommand(redisClient *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* For the STORE option, or when SORT is called from a Lua script,
|
||||
* we want to force a specific ordering even when no explicit ordering
|
||||
* was asked (SORT BY nosort). This guarantees that replication / AOF
|
||||
* is deterministic.
|
||||
/* When sorting a set with no sort specified, we must sort the output
|
||||
* so the result is consistent across scripting and replication.
|
||||
*
|
||||
* 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))
|
||||
* 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))
|
||||
{
|
||||
/* Force ALPHA sorting */
|
||||
dontsort = 0;
|
||||
@@ -323,17 +322,17 @@ void sortCommand(redisClient *c) {
|
||||
}
|
||||
if (end >= vectorlen) end = vectorlen-1;
|
||||
|
||||
/* Optimization:
|
||||
/* Whenever possible, we load elements into the output array in a more
|
||||
* direct way. This is possible if:
|
||||
*
|
||||
* 1) if the object to sort is a sorted set.
|
||||
* 1) The object to sort is a sorted set or a list (internally sorted).
|
||||
* 2) There is nothing to sort as dontsort is true (BY <constant string>).
|
||||
* 3) We have a LIMIT option that actually reduces the number of elements
|
||||
* to fetch.
|
||||
*
|
||||
* In this case to load all the objects in the vector is a huge waste of
|
||||
* resources. We just allocate a vector that is big enough for the selected
|
||||
* range length, and make sure to load just this part in the vector. */
|
||||
if (sortval->type == REDIS_ZSET &&
|
||||
* In this special case, if we have a LIMIT option that actually reduces
|
||||
* the number of elements to fetch, we also optimize to just load the
|
||||
* range we are interested in and allocating a vector that is big enough
|
||||
* for the selected range length. */
|
||||
if ((sortval->type == REDIS_ZSET || sortval->type == REDIS_LIST) &&
|
||||
dontsort &&
|
||||
(start != 0 || end != vectorlen-1))
|
||||
{
|
||||
@@ -344,7 +343,32 @@ void sortCommand(redisClient *c) {
|
||||
vector = zmalloc(sizeof(redisSortObject)*vectorlen);
|
||||
j = 0;
|
||||
|
||||
if (sortval->type == REDIS_LIST) {
|
||||
if (sortval->type == REDIS_LIST && dontsort) {
|
||||
/* Special handling for a list, if 'dontsort' is true.
|
||||
* This makes sure we return elements in the list original
|
||||
* ordering, accordingly to DESC / ASC options.
|
||||
*
|
||||
* Note that in this case we also handle LIMIT here in a direct
|
||||
* way, just getting the required range, as an optimization. */
|
||||
if (end >= start) {
|
||||
listTypeIterator *li;
|
||||
listTypeEntry entry;
|
||||
li = listTypeInitIterator(sortval,
|
||||
desc ? (long)(listTypeLength(sortval) - start - 1) : start,
|
||||
desc ? REDIS_HEAD : REDIS_TAIL);
|
||||
|
||||
while(j < vectorlen && listTypeNext(li,&entry)) {
|
||||
vector[j].obj = listTypeGet(&entry);
|
||||
vector[j].u.score = 0;
|
||||
vector[j].u.cmpobj = NULL;
|
||||
j++;
|
||||
}
|
||||
listTypeReleaseIterator(li);
|
||||
/* Fix start/end: output code is not aware of this optimization. */
|
||||
end -= start;
|
||||
start = 0;
|
||||
}
|
||||
} else if (sortval->type == REDIS_LIST) {
|
||||
listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL);
|
||||
listTypeEntry entry;
|
||||
while(listTypeNext(li,&entry)) {
|
||||
@@ -400,10 +424,7 @@ void sortCommand(redisClient *c) {
|
||||
j++;
|
||||
ln = desc ? ln->backward : ln->level[0].forward;
|
||||
}
|
||||
/* The code producing the output does not know that in the case of
|
||||
* sorted set, 'dontsort', and LIMIT, we are able to get just the
|
||||
* range, already sorted, so we need to adjust "start" and "end"
|
||||
* to make sure start is set to 0. */
|
||||
/* Fix start/end: output code is not aware of this optimization. */
|
||||
end -= start;
|
||||
start = 0;
|
||||
} else if (sortval->type == REDIS_ZSET) {
|
||||
@@ -510,7 +531,7 @@ void sortCommand(redisClient *c) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
robj *sobj = createZiplistObject();
|
||||
robj *sobj = createQuicklistObject();
|
||||
|
||||
/* STORE option specified, set the sorting result as a List object */
|
||||
for (j = start; j <= end; j++) {
|
||||
|
||||
+2
-1
@@ -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(seq, 80, 4);
|
||||
* sds output = sparklineRender(sdsempty(), seq, 80, 4, SPARKLINE_FILL);
|
||||
* freeSparklineSequence(seq);
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
@@ -63,6 +63,7 @@ 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 {
|
||||
|
||||
@@ -139,6 +139,7 @@ ssize_t syncReadLine(int fd, char *ptr, ssize_t size, long long timeout) {
|
||||
*ptr = '\0';
|
||||
nread++;
|
||||
}
|
||||
size--;
|
||||
}
|
||||
return nread;
|
||||
}
|
||||
|
||||
+33
-2
@@ -130,7 +130,6 @@ robj *hashTypeGetObject(robj *o, robj *field) {
|
||||
value = createStringObjectFromLongLong(vll);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (o->encoding == REDIS_ENCODING_HT) {
|
||||
robj *aux;
|
||||
|
||||
@@ -144,6 +143,29 @@ robj *hashTypeGetObject(robj *o, robj *field) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Higher level function using hashTypeGet*() to return the length of the
|
||||
* object associated with the requested field, or 0 if the field does not
|
||||
* exist. */
|
||||
size_t hashTypeGetValueLength(robj *o, robj *field) {
|
||||
size_t len = 0;
|
||||
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *vstr = NULL;
|
||||
unsigned int vlen = UINT_MAX;
|
||||
long long vll = LLONG_MAX;
|
||||
|
||||
if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0)
|
||||
len = vstr ? vlen : sdigits10(vll);
|
||||
} else if (o->encoding == REDIS_ENCODING_HT) {
|
||||
robj *aux;
|
||||
|
||||
if (hashTypeGetFromHashTable(o, field, &aux) == 0)
|
||||
len = stringObjectLen(aux);
|
||||
} else {
|
||||
redisPanic("Unknown hash encoding");
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Test if the specified field exists in the given hash. Returns 1 if the field
|
||||
* exists, and 0 when it doesn't. */
|
||||
int hashTypeExists(robj *o, robj *field) {
|
||||
@@ -565,7 +587,7 @@ void hincrbyfloatCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
value += incr;
|
||||
new = createStringObjectFromLongDouble(value);
|
||||
new = createStringObjectFromLongDouble(value,1);
|
||||
hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
|
||||
hashTypeSet(o,c->argv[2],new);
|
||||
addReplyBulk(c,new);
|
||||
@@ -679,12 +701,21 @@ void hdelCommand(redisClient *c) {
|
||||
|
||||
void hlenCommand(redisClient *c) {
|
||||
robj *o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,o,REDIS_HASH)) return;
|
||||
|
||||
addReplyLongLong(c,hashTypeLength(o));
|
||||
}
|
||||
|
||||
void hstrlenCommand(redisClient *c) {
|
||||
robj *o;
|
||||
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,o,REDIS_HASH)) return;
|
||||
addReplyLongLong(c,hashTypeGetValueLength(o,c->argv[2]));
|
||||
}
|
||||
|
||||
static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) {
|
||||
if (hi->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *vstr = NULL;
|
||||
|
||||
+99
-261
@@ -33,75 +33,37 @@
|
||||
* List API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Check the argument length to see if it requires us to convert the ziplist
|
||||
* to a real list. Only check raw-encoded objects because integer encoded
|
||||
* objects are never too long. */
|
||||
void listTypeTryConversion(robj *subject, robj *value) {
|
||||
if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;
|
||||
if (sdsEncodedObject(value) &&
|
||||
sdslen(value->ptr) > server.list_max_ziplist_value)
|
||||
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
|
||||
}
|
||||
|
||||
/* The function pushes an element to the specified list object 'subject',
|
||||
* at head or tail position as specified by 'where'.
|
||||
*
|
||||
* There is no need for the caller to increment the refcount of 'value' as
|
||||
* the function takes care of it if needed. */
|
||||
void listTypePush(robj *subject, robj *value, int where) {
|
||||
/* Check if we need to convert the ziplist */
|
||||
listTypeTryConversion(subject,value);
|
||||
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
|
||||
ziplistLen(subject->ptr) >= server.list_max_ziplist_entries)
|
||||
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
|
||||
|
||||
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
|
||||
if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
int pos = (where == REDIS_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
|
||||
value = getDecodedObject(value);
|
||||
subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
|
||||
size_t len = sdslen(value->ptr);
|
||||
quicklistPush(subject->ptr, value->ptr, len, pos);
|
||||
decrRefCount(value);
|
||||
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
if (where == REDIS_HEAD) {
|
||||
listAddNodeHead(subject->ptr,value);
|
||||
} else {
|
||||
listAddNodeTail(subject->ptr,value);
|
||||
}
|
||||
incrRefCount(value);
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
}
|
||||
|
||||
void *listPopSaver(unsigned char *data, unsigned int sz) {
|
||||
return createStringObject((char*)data,sz);
|
||||
}
|
||||
|
||||
robj *listTypePop(robj *subject, int where) {
|
||||
long long vlong;
|
||||
robj *value = NULL;
|
||||
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *p;
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vlong;
|
||||
int pos = (where == REDIS_HEAD) ? 0 : -1;
|
||||
p = ziplistIndex(subject->ptr,pos);
|
||||
if (ziplistGet(p,&vstr,&vlen,&vlong)) {
|
||||
if (vstr) {
|
||||
value = createStringObject((char*)vstr,vlen);
|
||||
} else {
|
||||
|
||||
int ql_where = where == REDIS_HEAD ? QUICKLIST_HEAD : QUICKLIST_TAIL;
|
||||
if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
if (quicklistPopCustom(subject->ptr, ql_where, (unsigned char **)&value,
|
||||
NULL, &vlong, listPopSaver)) {
|
||||
if (!value)
|
||||
value = createStringObjectFromLongLong(vlong);
|
||||
}
|
||||
/* We only need to delete an element when it exists */
|
||||
subject->ptr = ziplistDelete(subject->ptr,&p);
|
||||
}
|
||||
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
list *list = subject->ptr;
|
||||
listNode *ln;
|
||||
if (where == REDIS_HEAD) {
|
||||
ln = listFirst(list);
|
||||
} else {
|
||||
ln = listLast(list);
|
||||
}
|
||||
if (ln != NULL) {
|
||||
value = listNodeValue(ln);
|
||||
incrRefCount(value);
|
||||
listDelNode(list,ln);
|
||||
}
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
@@ -110,25 +72,28 @@ robj *listTypePop(robj *subject, int where) {
|
||||
}
|
||||
|
||||
unsigned long listTypeLength(robj *subject) {
|
||||
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
return ziplistLen(subject->ptr);
|
||||
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
return listLength((list*)subject->ptr);
|
||||
if (subject->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
return quicklistCount(subject->ptr);
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize an iterator at the specified index. */
|
||||
listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char direction) {
|
||||
listTypeIterator *listTypeInitIterator(robj *subject, long index,
|
||||
unsigned char direction) {
|
||||
listTypeIterator *li = zmalloc(sizeof(listTypeIterator));
|
||||
li->subject = subject;
|
||||
li->encoding = subject->encoding;
|
||||
li->direction = direction;
|
||||
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
li->zi = ziplistIndex(subject->ptr,index);
|
||||
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
li->ln = listIndex(subject->ptr,index);
|
||||
li->iter = NULL;
|
||||
/* REDIS_HEAD means start at TAIL and move *towards* head.
|
||||
* REDIS_TAIL means start at HEAD and move *towards tail. */
|
||||
int iter_direction =
|
||||
direction == REDIS_HEAD ? AL_START_TAIL : AL_START_HEAD;
|
||||
if (li->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
li->iter = quicklistGetIteratorAtIdx(li->subject->ptr,
|
||||
iter_direction, index);
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
@@ -137,6 +102,7 @@ listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char
|
||||
|
||||
/* Clean up the iterator. */
|
||||
void listTypeReleaseIterator(listTypeIterator *li) {
|
||||
zfree(li->iter);
|
||||
zfree(li);
|
||||
}
|
||||
|
||||
@@ -148,24 +114,8 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) {
|
||||
redisAssert(li->subject->encoding == li->encoding);
|
||||
|
||||
entry->li = li;
|
||||
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
entry->zi = li->zi;
|
||||
if (entry->zi != NULL) {
|
||||
if (li->direction == REDIS_TAIL)
|
||||
li->zi = ziplistNext(li->subject->ptr,li->zi);
|
||||
else
|
||||
li->zi = ziplistPrev(li->subject->ptr,li->zi);
|
||||
return 1;
|
||||
}
|
||||
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
entry->ln = li->ln;
|
||||
if (entry->ln != NULL) {
|
||||
if (li->direction == REDIS_TAIL)
|
||||
li->ln = li->ln->next;
|
||||
else
|
||||
li->ln = li->ln->prev;
|
||||
return 1;
|
||||
}
|
||||
if (li->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
return quicklistNext(li->iter, &entry->entry);
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
@@ -174,24 +124,14 @@ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) {
|
||||
|
||||
/* Return entry or NULL at the current position of the iterator. */
|
||||
robj *listTypeGet(listTypeEntry *entry) {
|
||||
listTypeIterator *li = entry->li;
|
||||
robj *value = NULL;
|
||||
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vlong;
|
||||
redisAssert(entry->zi != NULL);
|
||||
if (ziplistGet(entry->zi,&vstr,&vlen,&vlong)) {
|
||||
if (vstr) {
|
||||
value = createStringObject((char*)vstr,vlen);
|
||||
} else {
|
||||
value = createStringObjectFromLongLong(vlong);
|
||||
}
|
||||
if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
if (entry->entry.value) {
|
||||
value = createStringObject((char *)entry->entry.value,
|
||||
entry->entry.sz);
|
||||
} else {
|
||||
value = createStringObjectFromLongLong(entry->entry.longval);
|
||||
}
|
||||
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
redisAssert(entry->ln != NULL);
|
||||
value = listNodeValue(entry->ln);
|
||||
incrRefCount(value);
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
@@ -199,30 +139,18 @@ robj *listTypeGet(listTypeEntry *entry) {
|
||||
}
|
||||
|
||||
void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
|
||||
robj *subject = entry->li->subject;
|
||||
if (entry->li->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
value = getDecodedObject(value);
|
||||
sds str = value->ptr;
|
||||
size_t len = sdslen(str);
|
||||
if (where == REDIS_TAIL) {
|
||||
unsigned char *next = ziplistNext(subject->ptr,entry->zi);
|
||||
|
||||
/* When we insert after the current element, but the current element
|
||||
* is the tail of the list, we need to do a push. */
|
||||
if (next == NULL) {
|
||||
subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),REDIS_TAIL);
|
||||
} else {
|
||||
subject->ptr = ziplistInsert(subject->ptr,next,value->ptr,sdslen(value->ptr));
|
||||
}
|
||||
} else {
|
||||
subject->ptr = ziplistInsert(subject->ptr,entry->zi,value->ptr,sdslen(value->ptr));
|
||||
quicklistInsertAfter((quicklist *)entry->entry.quicklist,
|
||||
&entry->entry, str, len);
|
||||
} else if (where == REDIS_HEAD) {
|
||||
quicklistInsertBefore((quicklist *)entry->entry.quicklist,
|
||||
&entry->entry, str, len);
|
||||
}
|
||||
decrRefCount(value);
|
||||
} else if (entry->li->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
if (where == REDIS_TAIL) {
|
||||
listInsertNode(subject->ptr,entry->ln,value,AL_START_TAIL);
|
||||
} else {
|
||||
listInsertNode(subject->ptr,entry->ln,value,AL_START_HEAD);
|
||||
}
|
||||
incrRefCount(value);
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
@@ -230,59 +158,33 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
|
||||
|
||||
/* Compare the given object with the entry at the current position. */
|
||||
int listTypeEqual(listTypeEntry *entry, robj *o) {
|
||||
listTypeIterator *li = entry->li;
|
||||
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
redisAssertWithInfo(NULL,o,sdsEncodedObject(o));
|
||||
return ziplistCompare(entry->zi,o->ptr,sdslen(o->ptr));
|
||||
} else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
return equalStringObjects(o,listNodeValue(entry->ln));
|
||||
return quicklistCompare(entry->entry.zi,o->ptr,sdslen(o->ptr));
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete the element pointed to. */
|
||||
void listTypeDelete(listTypeEntry *entry) {
|
||||
listTypeIterator *li = entry->li;
|
||||
if (li->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *p = entry->zi;
|
||||
li->subject->ptr = ziplistDelete(li->subject->ptr,&p);
|
||||
|
||||
/* Update position of the iterator depending on the direction */
|
||||
if (li->direction == REDIS_TAIL)
|
||||
li->zi = p;
|
||||
else
|
||||
li->zi = ziplistPrev(li->subject->ptr,p);
|
||||
} else if (entry->li->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
listNode *next;
|
||||
if (li->direction == REDIS_TAIL)
|
||||
next = entry->ln->next;
|
||||
else
|
||||
next = entry->ln->prev;
|
||||
listDelNode(li->subject->ptr,entry->ln);
|
||||
li->ln = next;
|
||||
void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry) {
|
||||
if (entry->li->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
quicklistDelEntry(iter->iter, &entry->entry);
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
}
|
||||
|
||||
/* Create a quicklist from a single ziplist */
|
||||
void listTypeConvert(robj *subject, int enc) {
|
||||
listTypeIterator *li;
|
||||
listTypeEntry entry;
|
||||
redisAssertWithInfo(NULL,subject,subject->type == REDIS_LIST);
|
||||
redisAssertWithInfo(NULL,subject,subject->type==REDIS_LIST);
|
||||
redisAssertWithInfo(NULL,subject,subject->encoding==REDIS_ENCODING_ZIPLIST);
|
||||
|
||||
if (enc == REDIS_ENCODING_LINKEDLIST) {
|
||||
list *l = listCreate();
|
||||
listSetFreeMethod(l,decrRefCountVoid);
|
||||
|
||||
/* listTypeGet returns a robj with incremented refcount */
|
||||
li = listTypeInitIterator(subject,0,REDIS_TAIL);
|
||||
while (listTypeNext(li,&entry)) listAddNodeTail(l,listTypeGet(&entry));
|
||||
listTypeReleaseIterator(li);
|
||||
|
||||
subject->encoding = REDIS_ENCODING_LINKEDLIST;
|
||||
zfree(subject->ptr);
|
||||
subject->ptr = l;
|
||||
if (enc == REDIS_ENCODING_QUICKLIST) {
|
||||
size_t zlen = server.list_max_ziplist_size;
|
||||
int depth = server.list_compress_depth;
|
||||
subject->ptr = quicklistCreateFromZiplist(zlen, depth, subject->ptr);
|
||||
subject->encoding = REDIS_ENCODING_QUICKLIST;
|
||||
} else {
|
||||
redisPanic("Unsupported list conversion");
|
||||
}
|
||||
@@ -304,7 +206,9 @@ void pushGenericCommand(redisClient *c, int where) {
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
c->argv[j] = tryObjectEncoding(c->argv[j]);
|
||||
if (!lobj) {
|
||||
lobj = createZiplistObject();
|
||||
lobj = createQuicklistObject();
|
||||
quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
|
||||
server.list_compress_depth);
|
||||
dbAdd(c->db,c->argv[1],lobj);
|
||||
}
|
||||
listTypePush(lobj,c->argv[j],where);
|
||||
@@ -334,17 +238,10 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
|
||||
listTypeEntry entry;
|
||||
int inserted = 0;
|
||||
|
||||
if ((subject = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
if ((subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
|
||||
checkType(c,subject,REDIS_LIST)) return;
|
||||
|
||||
if (refval != NULL) {
|
||||
/* We're not sure if this value can be inserted yet, but we cannot
|
||||
* convert the list inside the iterator. We don't want to loop over
|
||||
* the list twice (once to see if the value can be inserted and once
|
||||
* to do the actual insert), so we assume this value can be inserted
|
||||
* and convert the ziplist to a regular list if necessary. */
|
||||
listTypeTryConversion(subject,val);
|
||||
|
||||
/* Seek refval from head to tail */
|
||||
iter = listTypeInitIterator(subject,0,REDIS_TAIL);
|
||||
while (listTypeNext(iter,&entry)) {
|
||||
@@ -357,10 +254,6 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
|
||||
listTypeReleaseIterator(iter);
|
||||
|
||||
if (inserted) {
|
||||
/* Check if the length exceeds the ziplist length threshold. */
|
||||
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
|
||||
ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
|
||||
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"linsert",
|
||||
c->argv[1],c->db->id);
|
||||
@@ -418,31 +311,19 @@ void lindexCommand(redisClient *c) {
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK))
|
||||
return;
|
||||
|
||||
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *p;
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vlong;
|
||||
p = ziplistIndex(o->ptr,index);
|
||||
if (ziplistGet(p,&vstr,&vlen,&vlong)) {
|
||||
if (vstr) {
|
||||
value = createStringObject((char*)vstr,vlen);
|
||||
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
quicklistEntry entry;
|
||||
if (quicklistIndex(o->ptr, index, &entry)) {
|
||||
if (entry.value) {
|
||||
value = createStringObject((char*)entry.value,entry.sz);
|
||||
} else {
|
||||
value = createStringObjectFromLongLong(vlong);
|
||||
value = createStringObjectFromLongLong(entry.longval);
|
||||
}
|
||||
addReplyBulk(c,value);
|
||||
decrRefCount(value);
|
||||
} else {
|
||||
addReply(c,shared.nullbulk);
|
||||
}
|
||||
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
listNode *ln = listIndex(o->ptr,index);
|
||||
if (ln != NULL) {
|
||||
value = listNodeValue(ln);
|
||||
addReplyBulk(c,value);
|
||||
} else {
|
||||
addReply(c,shared.nullbulk);
|
||||
}
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
@@ -452,35 +333,18 @@ void lsetCommand(redisClient *c) {
|
||||
robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
|
||||
if (o == NULL || checkType(c,o,REDIS_LIST)) return;
|
||||
long index;
|
||||
robj *value = (c->argv[3] = tryObjectEncoding(c->argv[3]));
|
||||
robj *value = c->argv[3];
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK))
|
||||
return;
|
||||
|
||||
listTypeTryConversion(o,value);
|
||||
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *p, *zl = o->ptr;
|
||||
p = ziplistIndex(zl,index);
|
||||
if (p == NULL) {
|
||||
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
quicklist *ql = o->ptr;
|
||||
int replaced = quicklistReplaceAtIndex(ql, index,
|
||||
value->ptr, sdslen(value->ptr));
|
||||
if (!replaced) {
|
||||
addReply(c,shared.outofrangeerr);
|
||||
} else {
|
||||
o->ptr = ziplistDelete(o->ptr,&p);
|
||||
value = getDecodedObject(value);
|
||||
o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr));
|
||||
decrRefCount(value);
|
||||
addReply(c,shared.ok);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
listNode *ln = listIndex(o->ptr,index);
|
||||
if (ln == NULL) {
|
||||
addReply(c,shared.outofrangeerr);
|
||||
} else {
|
||||
decrRefCount((robj*)listNodeValue(ln));
|
||||
listNodeValue(ln) = value;
|
||||
incrRefCount(value);
|
||||
addReply(c,shared.ok);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lset",c->argv[1],c->db->id);
|
||||
@@ -549,43 +413,28 @@ void lrangeCommand(redisClient *c) {
|
||||
|
||||
/* Return the result in form of a multi-bulk reply */
|
||||
addReplyMultiBulkLen(c,rangelen);
|
||||
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
unsigned char *p = ziplistIndex(o->ptr,start);
|
||||
unsigned char *vstr;
|
||||
unsigned int vlen;
|
||||
long long vlong;
|
||||
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
listTypeIterator *iter = listTypeInitIterator(o, start, REDIS_TAIL);
|
||||
|
||||
while(rangelen--) {
|
||||
ziplistGet(p,&vstr,&vlen,&vlong);
|
||||
if (vstr) {
|
||||
addReplyBulkCBuffer(c,vstr,vlen);
|
||||
listTypeEntry entry;
|
||||
listTypeNext(iter, &entry);
|
||||
quicklistEntry *qe = &entry.entry;
|
||||
if (qe->value) {
|
||||
addReplyBulkCBuffer(c,qe->value,qe->sz);
|
||||
} else {
|
||||
addReplyBulkLongLong(c,vlong);
|
||||
addReplyBulkLongLong(c,qe->longval);
|
||||
}
|
||||
p = ziplistNext(o->ptr,p);
|
||||
}
|
||||
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
listNode *ln;
|
||||
|
||||
/* If we are nearest to the end of the list, reach the element
|
||||
* starting from tail and going backward, as it is faster. */
|
||||
if (start > llen/2) start -= llen;
|
||||
ln = listIndex(o->ptr,start);
|
||||
|
||||
while(rangelen--) {
|
||||
addReplyBulk(c,ln->value);
|
||||
ln = ln->next;
|
||||
}
|
||||
listTypeReleaseIterator(iter);
|
||||
} else {
|
||||
redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!");
|
||||
redisPanic("List encoding is not QUICKLIST!");
|
||||
}
|
||||
}
|
||||
|
||||
void ltrimCommand(redisClient *c) {
|
||||
robj *o;
|
||||
long start, end, llen, j, ltrim, rtrim;
|
||||
list *list;
|
||||
listNode *ln;
|
||||
long start, end, llen, ltrim, rtrim;
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
|
||||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
|
||||
@@ -612,19 +461,9 @@ void ltrimCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
/* Remove list elements to perform the trim */
|
||||
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
|
||||
o->ptr = ziplistDeleteRange(o->ptr,0,ltrim);
|
||||
o->ptr = ziplistDeleteRange(o->ptr,-rtrim,rtrim);
|
||||
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
|
||||
list = o->ptr;
|
||||
for (j = 0; j < ltrim; j++) {
|
||||
ln = listFirst(list);
|
||||
listDelNode(list,ln);
|
||||
}
|
||||
for (j = 0; j < rtrim; j++) {
|
||||
ln = listLast(list);
|
||||
listDelNode(list,ln);
|
||||
}
|
||||
if (o->encoding == REDIS_ENCODING_QUICKLIST) {
|
||||
quicklistDelRange(o->ptr,0,ltrim);
|
||||
quicklistDelRange(o->ptr,-rtrim,rtrim);
|
||||
} else {
|
||||
redisPanic("Unknown list encoding");
|
||||
}
|
||||
@@ -641,10 +480,9 @@ void ltrimCommand(redisClient *c) {
|
||||
|
||||
void lremCommand(redisClient *c) {
|
||||
robj *subject, *obj;
|
||||
obj = c->argv[3] = tryObjectEncoding(c->argv[3]);
|
||||
obj = c->argv[3];
|
||||
long toremove;
|
||||
long removed = 0;
|
||||
listTypeEntry entry;
|
||||
|
||||
if ((getLongFromObjectOrReply(c, c->argv[2], &toremove, NULL) != REDIS_OK))
|
||||
return;
|
||||
@@ -652,10 +490,6 @@ void lremCommand(redisClient *c) {
|
||||
subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero);
|
||||
if (subject == NULL || checkType(c,subject,REDIS_LIST)) return;
|
||||
|
||||
/* Make sure obj is raw when we're dealing with a ziplist */
|
||||
if (subject->encoding == REDIS_ENCODING_ZIPLIST)
|
||||
obj = getDecodedObject(obj);
|
||||
|
||||
listTypeIterator *li;
|
||||
if (toremove < 0) {
|
||||
toremove = -toremove;
|
||||
@@ -664,9 +498,10 @@ void lremCommand(redisClient *c) {
|
||||
li = listTypeInitIterator(subject,0,REDIS_TAIL);
|
||||
}
|
||||
|
||||
listTypeEntry entry;
|
||||
while (listTypeNext(li,&entry)) {
|
||||
if (listTypeEqual(&entry,obj)) {
|
||||
listTypeDelete(&entry);
|
||||
listTypeDelete(li, &entry);
|
||||
server.dirty++;
|
||||
removed++;
|
||||
if (toremove && removed == toremove) break;
|
||||
@@ -674,11 +509,10 @@ void lremCommand(redisClient *c) {
|
||||
}
|
||||
listTypeReleaseIterator(li);
|
||||
|
||||
/* Clean up raw encoded object */
|
||||
if (subject->encoding == REDIS_ENCODING_ZIPLIST)
|
||||
decrRefCount(obj);
|
||||
if (listTypeLength(subject) == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
}
|
||||
|
||||
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
|
||||
addReplyLongLong(c,removed);
|
||||
if (removed) signalModifiedKey(c->db,c->argv[1]);
|
||||
}
|
||||
@@ -702,7 +536,9 @@ void lremCommand(redisClient *c) {
|
||||
void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
|
||||
/* Create the list if the key does not exist */
|
||||
if (!dstobj) {
|
||||
dstobj = createZiplistObject();
|
||||
dstobj = createQuicklistObject();
|
||||
quicklistSetOptions(dstobj->ptr, server.list_max_ziplist_size,
|
||||
server.list_compress_depth);
|
||||
dbAdd(c->db,dstkey,dstobj);
|
||||
}
|
||||
signalModifiedKey(c->db,dstkey);
|
||||
@@ -1010,7 +846,9 @@ void handleClientsBlockedOnLists(void) {
|
||||
}
|
||||
}
|
||||
|
||||
if (listTypeLength(o) == 0) dbDelete(rl->db,rl->key);
|
||||
if (listTypeLength(o) == 0) {
|
||||
dbDelete(rl->db,rl->key);
|
||||
}
|
||||
/* We don't call signalModifiedKey() as it was already called
|
||||
* when an element was pushed on the list. */
|
||||
}
|
||||
|
||||
+208
-8
@@ -33,7 +33,8 @@
|
||||
* Set Commands
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op);
|
||||
void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum,
|
||||
robj *dstkey, int op);
|
||||
|
||||
/* Factory method to return a set that *can* hold "value". When the object has
|
||||
* an integer-encodable value, an intset will be returned. Otherwise a regular
|
||||
@@ -44,6 +45,11 @@ robj *setTypeCreate(robj *value) {
|
||||
return createSetObject();
|
||||
}
|
||||
|
||||
/* Add the specified value into a set. The function takes care of incrementing
|
||||
* the reference count of the object if needed in order to retain a copy.
|
||||
*
|
||||
* If the value was already member of the set, nothing is done and 0 is
|
||||
* returned, otherwise the new element is added and 1 is returned. */
|
||||
int setTypeAdd(robj *subject, robj *value) {
|
||||
long long llval;
|
||||
if (subject->encoding == REDIS_ENCODING_HT) {
|
||||
@@ -68,7 +74,8 @@ int setTypeAdd(robj *subject, robj *value) {
|
||||
|
||||
/* The set *was* an intset and this value is not integer
|
||||
* encodable, so dictAdd should always work. */
|
||||
redisAssertWithInfo(NULL,value,dictAdd(subject->ptr,value,NULL) == DICT_OK);
|
||||
redisAssertWithInfo(NULL,value,
|
||||
dictAdd(subject->ptr,value,NULL) == DICT_OK);
|
||||
incrRefCount(value);
|
||||
return 1;
|
||||
}
|
||||
@@ -137,7 +144,11 @@ void setTypeReleaseIterator(setTypeIterator *si) {
|
||||
* Since set elements can be internally be stored as redis objects or
|
||||
* simple arrays of integers, setTypeNext returns the encoding of the
|
||||
* set object you are iterating, and will populate the appropriate pointer
|
||||
* (eobj) or (llobj) accordingly.
|
||||
* (objele) or (llele) accordingly.
|
||||
*
|
||||
* Note that both the objele and llele pointers should be passed and cannot
|
||||
* be NULL since the function will try to defensively populate the non
|
||||
* used field with values which are easy to trap if misused.
|
||||
*
|
||||
* When there are no longer elements -1 is returned.
|
||||
* Returned objects ref count is not incremented, so this function is
|
||||
@@ -147,9 +158,13 @@ int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele) {
|
||||
dictEntry *de = dictNext(si->di);
|
||||
if (de == NULL) return -1;
|
||||
*objele = dictGetKey(de);
|
||||
*llele = -123456789; /* Not needed. Defensive. */
|
||||
} else if (si->encoding == REDIS_ENCODING_INTSET) {
|
||||
if (!intsetGet(si->subject->ptr,si->ii++,llele))
|
||||
return -1;
|
||||
*objele = NULL; /* Not needed. Defensive. */
|
||||
} else {
|
||||
redisPanic("Wrong set encoding in setTypeNext");
|
||||
}
|
||||
return si->encoding;
|
||||
}
|
||||
@@ -190,6 +205,10 @@ robj *setTypeNextObject(setTypeIterator *si) {
|
||||
* field of the object and is used by the caller to check if the
|
||||
* int64_t pointer or the redis object pointer was populated.
|
||||
*
|
||||
* Note that both the objele and llele pointers should be passed and cannot
|
||||
* be NULL since the function will try to defensively populate the non
|
||||
* used field with values which are easy to trap if misused.
|
||||
*
|
||||
* When an object is returned (the set was a real set) the ref count
|
||||
* of the object is not incremented so this function can be considered
|
||||
* copy on write friendly. */
|
||||
@@ -197,8 +216,10 @@ int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele) {
|
||||
if (setobj->encoding == REDIS_ENCODING_HT) {
|
||||
dictEntry *de = dictGetRandomKey(setobj->ptr);
|
||||
*objele = dictGetKey(de);
|
||||
*llele = -123456789; /* Not needed. Defensive. */
|
||||
} else if (setobj->encoding == REDIS_ENCODING_INTSET) {
|
||||
*llele = intsetRandom(setobj->ptr);
|
||||
*objele = NULL; /* Not needed. Defensive. */
|
||||
} else {
|
||||
redisPanic("Unknown set encoding");
|
||||
}
|
||||
@@ -233,9 +254,10 @@ void setTypeConvert(robj *setobj, int enc) {
|
||||
|
||||
/* To add the elements we extract integers and create redis objects */
|
||||
si = setTypeInitIterator(setobj);
|
||||
while (setTypeNext(si,NULL,&intele) != -1) {
|
||||
while (setTypeNext(si,&element,&intele) != -1) {
|
||||
element = createStringObjectFromLongLong(intele);
|
||||
redisAssertWithInfo(NULL,element,dictAdd(d,element,NULL) == DICT_OK);
|
||||
redisAssertWithInfo(NULL,element,
|
||||
dictAdd(d,element,NULL) == DICT_OK);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
|
||||
@@ -321,7 +343,7 @@ void smoveCommand(redisClient *c) {
|
||||
|
||||
/* If srcset and dstset are equal, SMOVE is a no-op */
|
||||
if (srcset == dstset) {
|
||||
addReply(c,shared.cone);
|
||||
addReply(c,setTypeIsMember(srcset,ele) ? shared.cone : shared.czero);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -377,15 +399,185 @@ void scardCommand(redisClient *c) {
|
||||
addReplyLongLong(c,setTypeSize(o));
|
||||
}
|
||||
|
||||
/* Handle the "SPOP key <count>" variant. The normal version of the
|
||||
* command is handled by the spopCommand() function itself. */
|
||||
|
||||
/* How many times bigger should be the set compared to the remaining size
|
||||
* for us to use the "create new set" strategy? Read later in the
|
||||
* implementation for more info. */
|
||||
#define SPOP_MOVE_STRATEGY_MUL 5
|
||||
|
||||
void spopWithCountCommand(redisClient *c) {
|
||||
long l;
|
||||
unsigned long count, size;
|
||||
robj *set;
|
||||
|
||||
/* Get the count argument */
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != REDIS_OK) return;
|
||||
if (l >= 0) {
|
||||
count = (unsigned) l;
|
||||
} else {
|
||||
addReply(c,shared.outofrangeerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Make sure a key with the name inputted exists, and that it's type is
|
||||
* indeed a set. Otherwise, return nil */
|
||||
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk))
|
||||
== NULL || checkType(c,set,REDIS_SET)) return;
|
||||
|
||||
/* If count is zero, serve an empty multibulk ASAP to avoid special
|
||||
* cases later. */
|
||||
if (count == 0) {
|
||||
addReply(c,shared.emptymultibulk);
|
||||
return;
|
||||
}
|
||||
|
||||
size = setTypeSize(set);
|
||||
|
||||
/* Generate an SPOP keyspace notification */
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id);
|
||||
server.dirty += count;
|
||||
|
||||
/* CASE 1:
|
||||
* The number of requested elements is greater than or equal to
|
||||
* the number of elements inside the set: simply return the whole set. */
|
||||
if (count >= size) {
|
||||
/* We just return the entire set */
|
||||
sunionDiffGenericCommand(c,c->argv+1,1,NULL,REDIS_OP_UNION);
|
||||
|
||||
/* Delete the set as it is now empty */
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
|
||||
/* Propagate this command as an DEL operation */
|
||||
rewriteClientCommandVector(c,2,shared.del,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Case 2 and 3 require to replicate SPOP as a set of SERM commands.
|
||||
* Prepare our replication argument vector. Also send the array length
|
||||
* which is common to both the code paths. */
|
||||
robj *propargv[3];
|
||||
propargv[0] = createStringObject("SREM",4);
|
||||
propargv[1] = c->argv[1];
|
||||
addReplyMultiBulkLen(c,count);
|
||||
|
||||
/* Common iteration vars. */
|
||||
robj *objele;
|
||||
int encoding;
|
||||
int64_t llele;
|
||||
unsigned long remaining = size-count; /* Elements left after SPOP. */
|
||||
|
||||
/* If we are here, the number of requested elements is less than the
|
||||
* number of elements inside the set. Also we are sure that count < size.
|
||||
* Use two different strategies.
|
||||
*
|
||||
* CASE 2: The number of elements to return is small compared to the
|
||||
* set size. We can just extract random elements and return them to
|
||||
* the set. */
|
||||
if (remaining*SPOP_MOVE_STRATEGY_MUL > count) {
|
||||
while(count--) {
|
||||
encoding = setTypeRandomElement(set,&objele,&llele);
|
||||
if (encoding == REDIS_ENCODING_INTSET) {
|
||||
objele = createStringObjectFromLongLong(llele);
|
||||
} else {
|
||||
incrRefCount(objele);
|
||||
}
|
||||
|
||||
/* Return the element to the client and remove from the set. */
|
||||
addReplyBulk(c,objele);
|
||||
setTypeRemove(set,objele);
|
||||
|
||||
/* Replicate/AOF this command as an SREM operation */
|
||||
propargv[2] = objele;
|
||||
alsoPropagate(server.sremCommand,c->db->id,propargv,3,
|
||||
REDIS_PROPAGATE_AOF|REDIS_PROPAGATE_REPL);
|
||||
decrRefCount(objele);
|
||||
}
|
||||
} else {
|
||||
/* CASE 3: The number of elements to return is very big, approaching
|
||||
* the size of the set itself. After some time extracting random elements
|
||||
* from such a set becomes computationally expensive, so we use
|
||||
* a different strategy, we extract random elements that we don't
|
||||
* want to return (the elements that will remain part of the set),
|
||||
* creating a new set as we do this (that will be stored as the original
|
||||
* set). Then we return the elements left in the original set and
|
||||
* release it. */
|
||||
robj *newset = NULL;
|
||||
|
||||
/* Create a new set with just the remaining elements. */
|
||||
while(remaining--) {
|
||||
encoding = setTypeRandomElement(set,&objele,&llele);
|
||||
if (encoding == REDIS_ENCODING_INTSET) {
|
||||
objele = createStringObjectFromLongLong(llele);
|
||||
} else {
|
||||
incrRefCount(objele);
|
||||
}
|
||||
if (!newset) newset = setTypeCreate(objele);
|
||||
setTypeAdd(newset,objele);
|
||||
setTypeRemove(set,objele);
|
||||
decrRefCount(objele);
|
||||
}
|
||||
|
||||
/* Assign the new set as the key value. */
|
||||
incrRefCount(set); /* Protect the old set value. */
|
||||
dbOverwrite(c->db,c->argv[1],newset);
|
||||
|
||||
/* Tranfer the old set to the client and release it. */
|
||||
setTypeIterator *si;
|
||||
si = setTypeInitIterator(set);
|
||||
while((encoding = setTypeNext(si,&objele,&llele)) != -1) {
|
||||
if (encoding == REDIS_ENCODING_INTSET) {
|
||||
objele = createStringObjectFromLongLong(llele);
|
||||
} else {
|
||||
incrRefCount(objele);
|
||||
}
|
||||
addReplyBulk(c,objele);
|
||||
|
||||
/* Replicate/AOF this command as an SREM operation */
|
||||
propargv[2] = objele;
|
||||
alsoPropagate(server.sremCommand,c->db->id,propargv,3,
|
||||
REDIS_PROPAGATE_AOF|REDIS_PROPAGATE_REPL);
|
||||
|
||||
decrRefCount(objele);
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
decrRefCount(set);
|
||||
}
|
||||
|
||||
/* Don't propagate the command itself even if we incremented the
|
||||
* dirty counter. We don't want to propagate an SPOP command since
|
||||
* we propagated the command as a set of SREMs operations using
|
||||
* the alsoPropagate() API. */
|
||||
decrRefCount(propargv[0]);
|
||||
preventCommandPropagation(c);
|
||||
}
|
||||
|
||||
void spopCommand(redisClient *c) {
|
||||
robj *set, *ele, *aux;
|
||||
int64_t llele;
|
||||
int encoding;
|
||||
|
||||
if (c->argc == 3) {
|
||||
spopWithCountCommand(c);
|
||||
return;
|
||||
} else if (c->argc > 3) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Make sure a key with the name inputted exists, and that it's type is
|
||||
* indeed a set */
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
|
||||
checkType(c,set,REDIS_SET)) return;
|
||||
|
||||
/* Get a random element from the set */
|
||||
encoding = setTypeRandomElement(set,&ele,&llele);
|
||||
|
||||
/* Remove the element from the set */
|
||||
if (encoding == REDIS_ENCODING_INTSET) {
|
||||
ele = createStringObjectFromLongLong(llele);
|
||||
set->ptr = intsetRemove(set->ptr,llele,NULL);
|
||||
@@ -393,6 +585,7 @@ void spopCommand(redisClient *c) {
|
||||
incrRefCount(ele);
|
||||
setTypeRemove(set,ele);
|
||||
}
|
||||
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id);
|
||||
|
||||
/* Replicate/AOF this command as an SREM operation */
|
||||
@@ -401,11 +594,16 @@ void spopCommand(redisClient *c) {
|
||||
decrRefCount(ele);
|
||||
decrRefCount(aux);
|
||||
|
||||
/* Add the element to the reply */
|
||||
addReplyBulk(c,ele);
|
||||
|
||||
/* Delete the set if it's empty */
|
||||
if (setTypeSize(set) == 0) {
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
|
||||
}
|
||||
|
||||
/* Set has been modified */
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -587,7 +785,8 @@ int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) {
|
||||
return (o2 ? setTypeSize(o2) : 0) - (o1 ? setTypeSize(o1) : 0);
|
||||
}
|
||||
|
||||
void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) {
|
||||
void sinterGenericCommand(redisClient *c, robj **setkeys,
|
||||
unsigned long setnum, robj *dstkey) {
|
||||
robj **sets = zmalloc(sizeof(robj*)*setnum);
|
||||
setTypeIterator *si;
|
||||
robj *eleobj, *dstset = NULL;
|
||||
@@ -734,7 +933,8 @@ void sinterstoreCommand(redisClient *c) {
|
||||
#define REDIS_OP_DIFF 1
|
||||
#define REDIS_OP_INTER 2
|
||||
|
||||
void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op) {
|
||||
void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum,
|
||||
robj *dstkey, int op) {
|
||||
robj **sets = zmalloc(sizeof(robj*)*setnum);
|
||||
setTypeIterator *si;
|
||||
robj *ele, *dstset = NULL;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user