Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de0d9632b5 | ||
|
|
a1589c3769 | ||
|
|
4fe8a0af3f | ||
|
|
87e3c4fb12 | ||
|
|
6c3aedc06f | ||
|
|
61cf011388 | ||
|
|
c924ac3fdf | ||
|
|
ebe2e4583f | ||
|
|
fe4eb9b10f | ||
|
|
8b565570f2 | ||
|
|
789f6a95db | ||
|
|
d49ffeea1c | ||
|
|
6ed4dc5052 | ||
|
|
cbcdebe3b5 | ||
|
|
196d06b9b3 | ||
|
|
cb6beec2e3 | ||
|
|
542ccdc916 | ||
|
|
c6bbfec2ff | ||
|
|
4779ed5ebf | ||
|
|
2fc6a24f41 | ||
|
|
dad7f71f15 | ||
|
|
5e841b5533 | ||
|
|
04ca7d8aee | ||
|
|
280e6a89e4 | ||
|
|
a511af7c5e | ||
|
|
2adbbbcdc0 | ||
|
|
5895d119b1 | ||
|
|
aabe95125d | ||
|
|
03cb27e8ec | ||
|
|
dde1c975b8 | ||
|
|
5a82df05aa | ||
|
|
12a6b438a3 | ||
|
|
c6ad876774 | ||
|
|
f6a40570fa | ||
|
|
666ed7facf | ||
|
|
6ac3c0b7ab | ||
|
|
5674b0057f | ||
|
|
bb7597f46e | ||
|
|
a30d367a71 | ||
|
|
e0cf85b848 | ||
|
|
5f49f4fb40 | ||
|
|
2cd271e5d5 | ||
|
|
7d9878e4e5 | ||
|
|
240ff70ae6 | ||
|
|
3941d2e062 | ||
|
|
62bc09d92b | ||
|
|
c5446aca4c | ||
|
|
4f59673df5 | ||
|
|
1655576e23 | ||
|
|
8c0f06c2e6 | ||
|
|
32d923f85a | ||
|
|
8df81c0326 | ||
|
|
78c1623553 | ||
|
|
518fd2dcc4 | ||
|
|
d80c87111b | ||
|
|
981953a6ef | ||
|
|
da127ed3dd | ||
|
|
fe417b06d6 | ||
|
|
ea0a376432 | ||
|
|
18429ce594 | ||
|
|
f0a91d5a00 | ||
|
|
941d58d4c9 | ||
|
|
dd27c4e15e | ||
|
|
0506d85122 | ||
|
|
0d05570a82 | ||
|
|
1d4ebefbff | ||
|
|
68e93c22c3 | ||
|
|
adf8f6f63f | ||
|
|
394614a5f9 | ||
|
|
789f101560 | ||
|
|
5c6f0b26c0 | ||
|
|
8bcca06cb0 | ||
|
|
aae2d6e351 | ||
|
|
9469401088 | ||
|
|
1b03a0d754 | ||
|
|
f86385f70b | ||
|
|
d0762100b0 | ||
|
|
40ec7a2796 | ||
|
|
10b240fd31 | ||
|
|
91ffe99e03 | ||
|
|
c992857618 | ||
|
|
4bba84f096 | ||
|
|
17c3ac89e7 | ||
|
|
cde69883a1 | ||
|
|
e6a94aa569 | ||
|
|
840b393c06 | ||
|
|
56ef0aa7c8 | ||
|
|
dcf409f8e7 | ||
|
|
1237cce65d | ||
|
|
d182820fa7 | ||
|
|
0c65342889 | ||
|
|
8d70d498d5 | ||
|
|
5745b469d3 | ||
|
|
a11c842ecd | ||
|
|
615eb0db86 | ||
|
|
203feb6ea2 | ||
|
|
08c3f0bca6 | ||
|
|
3bf02ea7e8 | ||
|
|
acb4cd2135 | ||
|
|
fb86ecb802 | ||
|
|
2c5865cd90 | ||
|
|
ebbcd05e03 | ||
|
|
de4f04a272 | ||
|
|
f2f57eb44f | ||
|
|
8119259bb6 | ||
|
|
e664f38170 | ||
|
|
f464cf2380 | ||
|
|
02c395a179 | ||
|
|
acdccab019 | ||
|
|
e9f674e5fb | ||
|
|
0f8d47c9ed | ||
|
|
f1d56c981d | ||
|
|
ea595784f9 | ||
|
|
549fe1f3af | ||
|
|
cf6b0f9cef | ||
|
|
02e930624a | ||
|
|
ec56906bd6 | ||
|
|
23d299234c | ||
|
|
f84c181eac | ||
|
|
8ff0226361 | ||
|
|
d756b11fa2 | ||
|
|
ec74ae7ec1 | ||
|
|
785851a736 | ||
|
|
2a2d5a7a1f | ||
|
|
a60ed4a599 | ||
|
|
5ed6662145 | ||
|
|
f885e364ba | ||
|
|
3ac666b3e6 | ||
|
|
4b37eb13dc | ||
|
|
3a13c654e6 | ||
|
|
25214bd7dc | ||
|
|
3cc673bdd8 | ||
|
|
de0919cd62 | ||
|
|
5aa0fef2ce | ||
|
|
3988e32f41 | ||
|
|
6f16e52a6d | ||
|
|
c6c2de9567 | ||
|
|
cebc1f2696 | ||
|
|
56d8ad932d | ||
|
|
0103dc09d2 | ||
|
|
71c1427a1b | ||
|
|
30bb0dd595 | ||
|
|
99f39d32ab | ||
|
|
851ec530b4 | ||
|
|
79e6ab31d8 | ||
|
|
b20af834ff | ||
|
|
1453277294 | ||
|
|
e4e24460f9 | ||
|
|
db9cffe7c2 | ||
|
|
9c475272da | ||
|
|
6f2c894d69 | ||
|
|
4147a2202e | ||
|
|
28d1fe6718 | ||
|
|
116a2042a7 | ||
|
|
945983ac52 | ||
|
|
e7c8002b56 | ||
|
|
86b5e1a72f | ||
|
|
bab834574c | ||
|
|
76f3a63de1 | ||
|
|
93b4c6b31e | ||
|
|
12055ed8c2 | ||
|
|
c80f6219d5 | ||
|
|
801c7307fb | ||
|
|
a7b95b7c72 | ||
|
|
919580a228 | ||
|
|
e83d6b0cfa | ||
|
|
cb070accb4 | ||
|
|
9ce6fc7322 | ||
|
|
a7b9cfbe98 | ||
|
|
219bab2d85 | ||
|
|
d51bebd4d7 | ||
|
|
120c6b43e1 | ||
|
|
63075b81e6 | ||
|
|
9418083770 | ||
|
|
5d1ac2df64 | ||
|
|
90555566ed | ||
|
|
bb7e00b699 | ||
|
|
faa431c98d | ||
|
|
757ad7cdf2 | ||
|
|
31491202aa | ||
|
|
26c1c60187 | ||
|
|
2ccbba650c | ||
|
|
c73406c2bd | ||
|
|
c1423c016c | ||
|
|
ecf51b0b3d | ||
|
|
9d0388a043 | ||
|
|
6a4da4958e | ||
|
|
e8f772efff | ||
|
|
59866fcd30 | ||
|
|
4832cf4fd6 | ||
|
|
fd610ae025 | ||
|
|
88441019a5 | ||
|
|
aa1feec746 | ||
|
|
e5e353c81d | ||
|
|
0bdddd3c89 | ||
|
|
7bc0726016 | ||
|
|
ea4bf91abc | ||
|
|
5d9332266d | ||
|
|
7601ed344c | ||
|
|
405f041e90 | ||
|
|
aa6b869c15 | ||
|
|
00a767db38 | ||
|
|
5106a144f0 | ||
|
|
3efdbbc79c | ||
|
|
2ae7f49115 | ||
|
|
03b59cd5f0 | ||
|
|
5380716a70 | ||
|
|
18b30467f7 | ||
|
|
0052d5196d | ||
|
|
b55b0ea0f2 | ||
|
|
25e211bcf0 | ||
|
|
635704d424 | ||
|
|
47364bce69 | ||
|
|
3cd6c26056 | ||
|
|
8b0d797ecd | ||
|
|
3c8b394511 | ||
|
|
84ba2ec82b | ||
|
|
cf3375bf7c | ||
|
|
643d3e0b79 | ||
|
|
874c3a9978 | ||
|
|
781e50d41f | ||
|
|
57dbd6e90d | ||
|
|
e9fef49e12 | ||
|
|
a227bc8053 | ||
|
|
d410dc3162 | ||
|
|
41c7c7919c | ||
|
|
db6c763d8b | ||
|
|
72d6f966ac | ||
|
|
28e074608c | ||
|
|
5b8de5b7f2 | ||
|
|
bce350c666 | ||
|
|
332aa2f919 | ||
|
|
e4db67ea57 | ||
|
|
b983015dd4 | ||
|
|
9b718d41f0 | ||
|
|
da33a18588 | ||
|
|
dbea5f7a8d | ||
|
|
4bb40a9688 | ||
|
|
d2532d1335 | ||
|
|
00d0d870d2 | ||
|
|
f38e2802b6 | ||
|
|
1b100a167a | ||
|
|
d4e1c88052 | ||
|
|
2c0c5a809c | ||
|
|
f6d04d01b9 | ||
|
|
2fc915f509 | ||
|
|
e3cfc92180 | ||
|
|
7fcf29e615 | ||
|
|
e81bac32fd | ||
|
|
2257f38b68 | ||
|
|
2165a78d10 | ||
|
|
e6f6731c66 | ||
|
|
5a9a653f3e | ||
|
|
ff0846cbb7 | ||
|
|
3e2ed279fd | ||
|
|
19ef1f371d | ||
|
|
8d79702d8a | ||
|
|
6cfcf76e0b | ||
|
|
c9478a3b4e | ||
|
|
113d5ae872 | ||
|
|
1bd75aafdb | ||
|
|
ba48e7e9cd | ||
|
|
4e19358d65 | ||
|
|
43bc1ea065 | ||
|
|
3842bfd899 | ||
|
|
6dcd641681 | ||
|
|
1180537589 | ||
|
|
2e7ad58918 | ||
|
|
39ffc3702f | ||
|
|
31415cb27a | ||
|
|
840a9951b5 | ||
|
|
3c87d572b9 | ||
|
|
d3831559cf | ||
|
|
44ef19743e | ||
|
|
171f83306e | ||
|
|
0314429c8f | ||
|
|
086707315f | ||
|
|
9fd0c027e7 | ||
|
|
0e581e0044 | ||
|
|
e8801f7eaa | ||
|
|
3a677b45ac | ||
|
|
dc46f6f041 | ||
|
|
916b215fc5 | ||
|
|
9f0e8f7849 | ||
|
|
a5294c4e52 | ||
|
|
e511ab2cc6 | ||
|
|
7200b3aa0f | ||
|
|
e8efcdee0a | ||
|
|
2bcc056a41 | ||
|
|
b2bd3c0653 | ||
|
|
4238df5436 | ||
|
|
c68b3908a6 | ||
|
|
1c05b87e30 | ||
|
|
67750ce3b3 | ||
|
|
096285ab64 | ||
|
|
0b2f7c6407 | ||
|
|
8a4240b1fa | ||
|
|
395d339234 | ||
|
|
6daa8b9adb | ||
|
|
e00b6248c9 | ||
|
|
e8aa5583d0 | ||
|
|
f1d5d5d28e | ||
|
|
cc7f4ba67e | ||
|
|
af907e4b6d | ||
|
|
d2f09a054c | ||
|
|
cef46edebc | ||
|
|
9e997cd0d1 | ||
|
|
171aa22b0b | ||
|
|
7bf665f125 | ||
|
|
a5696bdf4f | ||
|
|
e15528bf1d | ||
|
|
e28aa99af1 | ||
|
|
3051430040 | ||
|
|
29b20fd528 | ||
|
|
5b36681213 | ||
|
|
0f75036c07 | ||
|
|
b1a01fda91 | ||
|
|
83f55f61a6 | ||
|
|
f89f50dbd0 | ||
|
|
05f8975d21 | ||
|
|
4780cc5efa | ||
|
|
9bc6af2449 | ||
|
|
c4b428a388 | ||
|
|
aea4db2f5a | ||
|
|
b5c5f870a4 | ||
|
|
23bf3d1cb9 | ||
|
|
1be6dbbdf6 | ||
|
|
1acdc84d10 | ||
|
|
52929b61f8 | ||
|
|
b0faf6113e | ||
|
|
f838df92d2 | ||
|
|
7a536c2912 | ||
|
|
6f1e828454 | ||
|
|
2d3687655b | ||
|
|
b057ff81ee | ||
|
|
a9d1014fb2 | ||
|
|
183bdc11c0 | ||
|
|
95ba01b538 | ||
|
|
7b21b8c3fb | ||
|
|
33ca884cc5 | ||
|
|
2b5f23197c | ||
|
|
1104113c07 | ||
|
|
62f8583445 | ||
|
|
3155adb299 | ||
|
|
14a59d4ce7 | ||
|
|
17aaf5ec97 | ||
|
|
3f5cf24da9 | ||
|
|
05e483cbb3 | ||
|
|
c8f250f8dd | ||
|
|
9412094f25 | ||
|
|
8312aa27d4 | ||
|
|
d3aa3791fe | ||
|
|
2b189f098e | ||
|
|
3328b7a514 | ||
|
|
88e1f80eee | ||
|
|
0ebbc36059 | ||
|
|
43ed3c3589 | ||
|
|
8095daea4a | ||
|
|
51e178454d | ||
|
|
82b2bfd20b | ||
|
|
ec1faeec72 | ||
|
|
c1326d7b10 | ||
|
|
845fb2d1c1 | ||
|
|
930fac8058 | ||
|
|
0cae0900cf | ||
|
|
e0022d8cfe | ||
|
|
495327c020 | ||
|
|
afb8b3c030 | ||
|
|
31c9cd8604 | ||
|
|
51efb7fe25 | ||
|
|
a7d3670da5 | ||
|
|
ee8dd01bbc | ||
|
|
a4a856d532 | ||
|
|
e2046b3003 | ||
|
|
b35fdf1de1 | ||
|
|
1d7bf208ce | ||
|
|
676445ad95 | ||
|
|
4846c0c8af | ||
|
|
f33de403ed | ||
|
|
49af4d07e4 | ||
|
|
329fddbda1 | ||
|
|
925a2cd5aa | ||
|
|
84a7a90586 | ||
|
|
cd63359a17 | ||
|
|
886d287c40 | ||
|
|
59cd4c9f65 | ||
|
|
6c1bb7b194 | ||
|
|
1aee695e52 | ||
|
|
a2ae463520 | ||
|
|
86e562d691 | ||
|
|
ab2984b1e2 | ||
|
|
1ff5a222de | ||
|
|
cc549b46a4 | ||
|
|
377dd0515c | ||
|
|
3f8d113f1b | ||
|
|
d4541349dc | ||
|
|
2112a57028 | ||
|
|
d2eb6e0b47 | ||
|
|
9c1df3b769 | ||
|
|
115ed1911e | ||
|
|
11c748aaca | ||
|
|
8f10137227 | ||
|
|
2e591fc4a4 | ||
|
|
cbb51fb8f9 | ||
|
|
e0fc88b4d9 | ||
|
|
e3f864b5fd | ||
|
|
8af1e513f6 | ||
|
|
3c21418cd8 | ||
|
|
e201f83cee | ||
|
|
58fc456cbd | ||
|
|
3febc5c29e | ||
|
|
8d67385593 | ||
|
|
7a35eec540 | ||
|
|
f93e1417b9 | ||
|
|
d0c9e44545 | ||
|
|
1cde6a0603 | ||
|
|
436be34986 | ||
|
|
f9d2ffdc5d | ||
|
|
d7968ee920 | ||
|
|
a902e6b251 | ||
|
|
9564ed7c39 | ||
|
|
2e41827435 | ||
|
|
eb2c8b2c61 | ||
|
|
928e6976bb | ||
|
|
cdcf5af5aa | ||
|
|
e8b09d2203 | ||
|
|
57b4fb0d84 | ||
|
|
29f25e4111 | ||
|
|
00d8b92b89 | ||
|
|
5e17e6276c | ||
|
|
96e7c011e2 | ||
|
|
7803b1148d | ||
|
|
1eab62f7e5 | ||
|
|
ae306a3df6 | ||
|
|
16ba33c05b | ||
|
|
77ae66930c | ||
|
|
389697988a | ||
|
|
2435341d74 | ||
|
|
80c906bd30 | ||
|
|
74249be4a2 | ||
|
|
8bf660af90 | ||
|
|
9da134cd88 | ||
|
|
f7f219a137 | ||
|
|
693629585b | ||
|
|
e3b5648df0 | ||
|
|
5c41802d55 | ||
|
|
a23cdbb94b | ||
|
|
1276058ea8 | ||
|
|
002fcde3da | ||
|
|
960186a71f | ||
|
|
3672875b4c | ||
|
|
24e12641d5 | ||
|
|
c7edffbd56 | ||
|
|
8a9c84f4a5 | ||
|
|
cb683a84f7 | ||
|
|
84d9766d6a | ||
|
|
ec1e106ec5 | ||
|
|
e48c37316e | ||
|
|
1e561cfaaf | ||
|
|
d1af82a883 | ||
|
|
897a360d06 | ||
|
|
ac316d8cc5 | ||
|
|
fc7bc32045 | ||
|
|
a6e55c096d | ||
|
|
b062fd5235 | ||
|
|
4efb25d9c0 | ||
|
|
d6436eb7cf | ||
|
|
3a441c7d95 | ||
|
|
75addb4fe2 | ||
|
|
c57d9146f4 | ||
|
|
3d3861dd88 | ||
|
|
47ff13637e | ||
|
|
db73d0998f | ||
|
|
7c0fe72714 | ||
|
|
8fe25edc75 | ||
|
|
17dfd7cabb | ||
|
|
1f9b82bd56 | ||
|
|
3fcffe7d04 | ||
|
|
455d8a05c1 | ||
|
|
70287bbc94 | ||
|
|
7be21139aa | ||
|
|
e1ee1a49d6 | ||
|
|
d56f058c08 | ||
|
|
96dd5fc932 | ||
|
|
5b59d9c5d7 | ||
|
|
c163d4addc | ||
|
|
aab74b7151 | ||
|
|
606134f9de | ||
|
|
ee627bb667 | ||
|
|
47b8a7f9b8 | ||
|
|
6c0bc608a1 | ||
|
|
5bfc18950a | ||
|
|
56d628f851 | ||
|
|
cac9d7cf7e | ||
|
|
b712fba17c | ||
|
|
ea63aea72d | ||
|
|
64e588bfab | ||
|
|
0d1e8c93bf | ||
|
|
a1bed447b3 | ||
|
|
a75fa3aad1 | ||
|
|
52c75e9db1 | ||
|
|
8110ba8880 | ||
|
|
e4d2bb62b2 | ||
|
|
fea9788cc4 | ||
|
|
2144047e14 | ||
|
|
43329c9b64 | ||
|
|
1e17d3de79 | ||
|
|
3722f89f49 | ||
|
|
373ae6061a | ||
|
|
f03f1fad67 | ||
|
|
374ffdf1c1 | ||
|
|
4db38d2efd | ||
|
|
f0a261448c | ||
|
|
fbdef6a9bd | ||
|
|
05a41da75b | ||
|
|
345c3768de | ||
|
|
639c8a1d91 | ||
|
|
321acea038 | ||
|
|
b0920e6e86 | ||
|
|
149b658b56 | ||
|
|
06917e581c | ||
|
|
e434b2ce4f | ||
|
|
f3a1728873 | ||
|
|
e63bb7ec8c | ||
|
|
6791ff0525 | ||
|
|
725b8cc680 | ||
|
|
3575b8706d | ||
|
|
a76c67578c | ||
|
|
3a27064c41 | ||
|
|
5c4c73e2c2 | ||
|
|
6148f94939 | ||
|
|
51d3012d48 | ||
|
|
c39f16c422 | ||
|
|
7cf0a77d59 | ||
|
|
9f594e2432 | ||
|
|
48781dd95c | ||
|
|
371ab0cff8 | ||
|
|
b86140ac5f | ||
|
|
a7ee3c3e77 | ||
|
|
e5b9eb8171 | ||
|
|
0f31bb5c1a | ||
|
|
b92d9a895f | ||
|
|
8f896e57ae | ||
|
|
8f5157058b | ||
|
|
ddeda9ceb7 | ||
|
|
bde1f0a8e2 | ||
|
|
bec68bff29 | ||
|
|
0f239e51b0 | ||
|
|
f855db61b0 | ||
|
|
23094ba015 | ||
|
|
8161a7a3ef | ||
|
|
61b153073f | ||
|
|
414debfd04 | ||
|
|
cbcd07777d | ||
|
|
2437455f27 | ||
|
|
35c64b898c | ||
|
|
c3ac717487 | ||
|
|
c8dbcff9db | ||
|
|
abd5156f25 | ||
|
|
5719b3054a | ||
|
|
c89e1f2937 | ||
|
|
0b16f8d444 | ||
|
|
9254a805d7 | ||
|
|
a4c4907035 | ||
|
|
cb92c23de0 | ||
|
|
56a52e804c | ||
|
|
ebb09a5c3b | ||
|
|
a9f8a8cba0 | ||
|
|
4aa24e62a4 | ||
|
|
2b67b6b87e | ||
|
|
420aac7274 | ||
|
|
a518a9a766 | ||
|
|
026cc11b05 | ||
|
|
02b594f6ad | ||
|
|
f695d18306 | ||
|
|
0e42cfc365 | ||
|
|
91ed9b3c47 | ||
|
|
3e0d209625 | ||
|
|
240094c9b0 | ||
|
|
209f3a1eba | ||
|
|
024c380b9d | ||
|
|
a38ff404bd | ||
|
|
814874d68b | ||
|
|
061616c1b1 | ||
|
|
7764996bec | ||
|
|
eba28e2cea | ||
|
|
15c9e79a7d | ||
|
|
cd2b5df971 | ||
|
|
8cdc153f58 | ||
|
|
b35407fa7c | ||
|
|
4395889c9e | ||
|
|
1907e0f18f | ||
|
|
c35a53169f | ||
|
|
6fe66e0969 | ||
|
|
193fc241ca | ||
|
|
957e917a84 | ||
|
|
ef1b1f01a8 | ||
|
|
9f347fabba | ||
|
|
a509400d58 | ||
|
|
805c8c94a9 | ||
|
|
97b80b57e9 | ||
|
|
4f6b6b8008 | ||
|
|
616b1cb7ac | ||
|
|
08fdef4bf6 | ||
|
|
26b79ca18c | ||
|
|
fa41863752 | ||
|
|
bbbc80acae | ||
|
|
c3b268a0bc | ||
|
|
0f7dfc378c | ||
|
|
67643ead69 | ||
|
|
ad94066ec8 | ||
|
|
a443ec2e9a | ||
|
|
6862fd70da | ||
|
|
30f1df8c48 | ||
|
|
7add0f2490 | ||
|
|
9d6d177994 | ||
|
|
316a8f1545 | ||
|
|
11db53f875 | ||
|
|
e257f121e1 | ||
|
|
5f72f69688 | ||
|
|
8caa271476 | ||
|
|
e43cd8316f | ||
|
|
34b8983220 | ||
|
|
70a98a43ea | ||
|
|
8783304a2d | ||
|
|
ec9cf002d5 | ||
|
|
b3e4abf06e | ||
|
|
50f8f9504b | ||
|
|
f2f3dc5e73 | ||
|
|
eb80887936 | ||
|
|
cdcab0e820 | ||
|
|
50dcd9f96d | ||
|
|
87dbd8f54c | ||
|
|
81e8686cc7 | ||
|
|
c7524a7e44 | ||
|
|
2dd1ca6af0 | ||
|
|
95324b8190 | ||
|
|
04c53fa145 | ||
|
|
0578157d56 | ||
|
|
299f1d0258 | ||
|
|
61b98f32a2 | ||
|
|
34ea2f4e1a | ||
|
|
cbbf9b3931 | ||
|
|
9c2e42ddfc | ||
|
|
d387f67dcb | ||
|
|
7c07841632 | ||
|
|
9cc7038e54 | ||
|
|
da8c7c49bf | ||
|
|
bdb338cf77 | ||
|
|
b3e0305448 | ||
|
|
e628f94436 | ||
|
|
5d4c4df3ef | ||
|
|
da14982d1e | ||
|
|
dc8885a1ca | ||
|
|
bfb18e5519 | ||
|
|
76d57161d9 | ||
|
|
0e5820d893 | ||
|
|
916dd79f19 | ||
|
|
c0c67c9be3 | ||
|
|
e1c29434b2 | ||
|
|
e5a063bcb4 | ||
|
|
d28cbaf770 | ||
|
|
23d5e8b8e4 | ||
|
|
6173815408 | ||
|
|
70e0e49952 | ||
|
|
b3e4aa6797 | ||
|
|
e74e68c84f | ||
|
|
4af0d7fd94 | ||
|
|
6ef018785b | ||
|
|
fe81d5c8a9 | ||
|
|
c2f01d7f9e | ||
|
|
127e09bca1 | ||
|
|
a20303c623 | ||
|
|
7a23b94559 | ||
|
|
baaf869fc3 | ||
|
|
be4bc1a5be | ||
|
|
07dc1b42fb | ||
|
|
10e71b3d01 | ||
|
|
edc0ed1415 | ||
|
|
c5319612b4 | ||
|
|
fff6b26ae3 | ||
|
|
650484604c | ||
|
|
3144a278dd | ||
|
|
afe0b16c02 | ||
|
|
df152b0ce7 | ||
|
|
e3c1f43952 | ||
|
|
141c0679a0 | ||
|
|
fe902461f4 | ||
|
|
9d4219ebac | ||
|
|
8424306433 | ||
|
|
dafb94db79 | ||
|
|
ea697b6345 | ||
|
|
dd4798802c | ||
|
|
ecf3b2ef32 | ||
|
|
2966132c52 | ||
|
|
7277e5d8a8 | ||
|
|
1bb5ee9c68 | ||
|
|
973297336f | ||
|
|
15ea13245a | ||
|
|
4d12c37c54 | ||
|
|
12626ce9bb | ||
|
|
2ecab0b63a | ||
|
|
635321d47e | ||
|
|
0b988fa9ec | ||
|
|
60096bc1a1 | ||
|
|
b439542604 | ||
|
|
ef3551d149 | ||
|
|
72c053519c | ||
|
|
294c9af469 | ||
|
|
ba0270799e | ||
|
|
5d0890c0a8 | ||
|
|
d1f22eaca4 | ||
|
|
762fbcb687 | ||
|
|
349aa24511 | ||
|
|
b4ddc7b7ba | ||
|
|
73806f74ca | ||
|
|
b6129f86c1 | ||
|
|
f15fb727a0 | ||
|
|
e374573f30 | ||
|
|
73d47d5749 | ||
|
|
b7cb28d501 | ||
|
|
1db7257107 | ||
|
|
fe96e29d5f | ||
|
|
f21be1ec55 | ||
|
|
6fb1aa2381 | ||
|
|
d4fe79a174 | ||
|
|
abb81c6351 | ||
|
|
77da960815 | ||
|
|
3e8c69a9de | ||
|
|
a788c373e6 | ||
|
|
df83892760 | ||
|
|
0517da3618 | ||
|
|
3c16d6b32d | ||
|
|
3b4f147775 | ||
|
|
5e762d847c | ||
|
|
54f5499aee | ||
|
|
0c952b13d0 | ||
|
|
dd34f70368 | ||
|
|
5e042dbc05 | ||
|
|
dcbe8bfad1 | ||
|
|
36caf2e42b | ||
|
|
3067352a8d | ||
|
|
9baaf858f9 | ||
|
|
4440133e9a | ||
|
|
c957794144 | ||
|
|
1333a46b7e | ||
|
|
8e7282eb3e | ||
|
|
a678390e52 | ||
|
|
919fbf421d | ||
|
|
22e45d46fe | ||
|
|
ba28924418 | ||
|
|
f42ce57d0f | ||
|
|
df096bc96b | ||
|
|
a55e584706 | ||
|
|
5a6cfbf4ca | ||
|
|
fae306b374 | ||
|
|
736309660f | ||
|
|
488e194787 | ||
|
|
c82ccf0670 | ||
|
|
51c1a9f8fb | ||
|
|
ea1e1b12c9 | ||
|
|
7379c78a9b | ||
|
|
9f6e84f6be | ||
|
|
82790e510f | ||
|
|
943008ebac | ||
|
|
e271a61103 | ||
|
|
f1974d5d67 | ||
|
|
d9b153c9f6 | ||
|
|
577fc4388b | ||
|
|
f7a94526dd | ||
|
|
eecfa9793e | ||
|
|
d2509811b7 | ||
|
|
29d4a1502a | ||
|
|
ec0c61da05 | ||
|
|
6fe55c2f29 | ||
|
|
bbce3ba974 | ||
|
|
40295fb3fe | ||
|
|
8e9d19bc65 | ||
|
|
ba146d4c09 | ||
|
|
721a39ddff | ||
|
|
5be3a15a82 | ||
|
|
cbabf779c2 | ||
|
|
658749cc54 | ||
|
|
47988c9666 | ||
|
|
cc90f79baf | ||
|
|
ecd17e819c | ||
|
|
1927932b43 | ||
|
|
f2df5773b1 | ||
|
|
b3ff8a4b6d | ||
|
|
b1f2c51037 | ||
|
|
7bbafc5639 | ||
|
|
c7f752667e | ||
|
|
ed7ea13a2a | ||
|
|
3be7762388 | ||
|
|
15f6b74814 | ||
|
|
1b5bf40c20 | ||
|
|
954c20edd9 | ||
|
|
11c3afd711 | ||
|
|
b752e83db5 | ||
|
|
e16eb87465 | ||
|
|
35ea9d231b | ||
|
|
3872912679 | ||
|
|
b9a1530345 | ||
|
|
5e7e5e6b61 | ||
|
|
e61dde8806 | ||
|
|
cddf1da2e9 | ||
|
|
52ea44e53b | ||
|
|
0f28ea16a7 | ||
|
|
00e5fefe6d | ||
|
|
baafd30ba6 |
+39
-14
@@ -3,26 +3,51 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest, ubuntu-16.04]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
test-ubuntu-latest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make
|
||||
# build with TLS just for compilatoin coverage
|
||||
run: make BUILD_TLS=yes
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.5
|
||||
make test
|
||||
sudo apt-get install tcl8.6
|
||||
./runtest --verbose
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
|
||||
build-debian-old:
|
||||
runs-on: ubuntu-latest
|
||||
container: debian:oldoldstable
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
apt-get update && apt-get install -y build-essential
|
||||
make
|
||||
|
||||
build-macos-latest:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [macos-latest, macOS-10.14]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make
|
||||
|
||||
build-32bit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386
|
||||
make 32bit
|
||||
|
||||
build-libc-malloc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make MALLOC=libc
|
||||
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
name: Daily
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
# any PR to a release branch.
|
||||
- '[0-9].[0-9]'
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
|
||||
test-ubuntu-jemalloc:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.6
|
||||
./runtest --accurate --verbose
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
run: ./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-ubuntu-libc-malloc:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make MALLOC=libc
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.6
|
||||
./runtest --accurate --verbose
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
run: ./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-ubuntu-32bit:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386
|
||||
make 32bit
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.6
|
||||
./runtest --accurate --verbose
|
||||
- name: module api test
|
||||
run: |
|
||||
make -C tests/modules 32bit # the script below doesn't have an argument, we must build manually ahead of time
|
||||
./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
run: ./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-ubuntu-tls:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
make BUILD_TLS=yes
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.6 tcl-tls
|
||||
./utils/gen-test-certs.sh
|
||||
./runtest --accurate --verbose --tls
|
||||
./runtest --accurate --verbose
|
||||
- name: module api test
|
||||
run: |
|
||||
./runtest-moduleapi --verbose --tls
|
||||
./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
run: |
|
||||
./runtest-sentinel --tls
|
||||
./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: |
|
||||
./runtest-cluster --tls
|
||||
./runtest-cluster
|
||||
|
||||
test-valgrind:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make valgrind
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install tcl8.6 valgrind -y
|
||||
./runtest --valgrind --verbose --clients 1
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --valgrind --verbose --clients 1
|
||||
|
||||
test-centos7-jemalloc:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
container: centos:7
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
yum -y install centos-release-scl
|
||||
yum -y install devtoolset-7
|
||||
scl enable devtoolset-7 "make"
|
||||
- name: test
|
||||
run: |
|
||||
yum -y install tcl
|
||||
./runtest --accurate --verbose
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
run: ./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-centos7-tls:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
container: centos:7
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
yum -y install centos-release-scl epel-release
|
||||
yum -y install devtoolset-7 openssl-devel openssl
|
||||
scl enable devtoolset-7 "make BUILD_TLS=yes"
|
||||
- name: test
|
||||
run: |
|
||||
yum -y install tcl tcltls
|
||||
./utils/gen-test-certs.sh
|
||||
./runtest --accurate --verbose --tls
|
||||
./runtest --accurate --verbose
|
||||
- name: module api test
|
||||
run: |
|
||||
./runtest-moduleapi --verbose --tls
|
||||
./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
run: |
|
||||
./runtest-sentinel --tls
|
||||
./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: |
|
||||
./runtest-cluster --tls
|
||||
./runtest-cluster
|
||||
|
||||
test-macos-latest:
|
||||
runs-on: macos-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: make
|
||||
- name: test
|
||||
run: |
|
||||
./runtest --accurate --verbose --no-latency
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
run: ./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-alpine-jemalloc:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
container: alpine:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
apk add build-base
|
||||
make REDIS_CFLAGS='-Werror'
|
||||
- name: test
|
||||
run: |
|
||||
apk add tcl procps
|
||||
./runtest --accurate --verbose
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
run: ./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
|
||||
test-alpine-libc-malloc:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'redis/redis'
|
||||
container: alpine:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: make
|
||||
run: |
|
||||
apk add build-base
|
||||
make REDIS_CFLAGS='-Werror' USE_JEMALLOC=no
|
||||
- name: test
|
||||
run: |
|
||||
apk add tcl procps
|
||||
./runtest --accurate --verbose
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
- name: sentinel tests
|
||||
run: ./runtest-sentinel
|
||||
- name: cluster tests
|
||||
run: ./runtest-cluster
|
||||
@@ -1,5 +1,8 @@
|
||||
.*.swp
|
||||
*.o
|
||||
*.xo
|
||||
*.so
|
||||
*.d
|
||||
*.log
|
||||
dump.rdb
|
||||
redis-benchmark
|
||||
@@ -24,8 +27,13 @@ src/nodes.conf
|
||||
deps/lua/src/lua
|
||||
deps/lua/src/luac
|
||||
deps/lua/src/liblua.a
|
||||
tests/tls/*
|
||||
.make-*
|
||||
.prerequisites
|
||||
*.dSYM
|
||||
Makefile.dep
|
||||
.vscode/*
|
||||
.idea/*
|
||||
.ccls
|
||||
.ccls-cache/*
|
||||
compile_commands.json
|
||||
|
||||
+3504
-11
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
Please check https://github.com/antirez/redis/issues
|
||||
Please check https://github.com/redis/redis/issues
|
||||
|
||||
@@ -20,6 +20,10 @@ each source file that you contribute.
|
||||
|
||||
http://stackoverflow.com/questions/tagged/redis
|
||||
|
||||
Issues and pull requests for documentation belong on the redis-doc repo:
|
||||
|
||||
https://github.com/redis/redis-doc
|
||||
|
||||
# How to provide a patch for a new feature
|
||||
|
||||
1. If it is a major feature or a semantical change, please don't start coding
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2006-2015, Salvatore Sanfilippo
|
||||
Copyright (c) 2006-2020, 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:
|
||||
|
||||
@@ -3,22 +3,22 @@ This README is just a fast *quick start* document. You can find more detailed do
|
||||
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.
|
||||
Redis is often referred to 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.
|
||||
* 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 it is also non-volatile.
|
||||
* The implementation of data structures emphasizes memory efficiency, so data structures inside Redis will likely use less memory compared to the same data structure modelled using a high-level programming language.
|
||||
* Redis offers a number of features that are natural to find in a database, like replication, tunable levels of durability, clustering, and 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.
|
||||
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 that 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
|
||||
* There is much more inside the official Redis documentation. http://redis.io/documentation
|
||||
|
||||
Building Redis
|
||||
--------------
|
||||
@@ -29,12 +29,26 @@ 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.
|
||||
work as well as in Linux, OSX, and \*BSD.
|
||||
|
||||
It is as simple as:
|
||||
|
||||
% make
|
||||
|
||||
To build with TLS support, you'll need OpenSSL development libraries (e.g.
|
||||
libssl-dev on Debian/Ubuntu) and run:
|
||||
|
||||
% make BUILD_TLS=yes
|
||||
|
||||
To build with systemd support, you'll need systemd development libraries (such
|
||||
as libsystemd-dev on Debian/Ubuntu or systemd-devel on CentOS) and run:
|
||||
|
||||
% make USE_SYSTEMD=yes
|
||||
|
||||
To append a suffix to Redis program names, use:
|
||||
|
||||
% make PROG_SUFFIX="-alt"
|
||||
|
||||
You can run a 32 bit Redis binary using:
|
||||
|
||||
% make 32bit
|
||||
@@ -43,10 +57,17 @@ After building Redis, it is a good idea to test it using:
|
||||
|
||||
% make test
|
||||
|
||||
If TLS is built, running the tests with TLS enabled (you will need `tcl-tls`
|
||||
installed):
|
||||
|
||||
% ./utils/gen-test-certs.sh
|
||||
% ./runtest --tls
|
||||
|
||||
|
||||
Fixing build problems with dependencies or cached build options
|
||||
---------
|
||||
|
||||
Redis has some dependencies which are included into the `deps` directory.
|
||||
Redis has some dependencies which are included in the `deps` directory.
|
||||
`make` does not automatically rebuild dependencies even if something in
|
||||
the source code of dependencies changes.
|
||||
|
||||
@@ -73,7 +94,7 @@ with a 64 bit target, or the other way around, you need to perform a
|
||||
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).
|
||||
* Install the package libc6-dev-i386 (also try g++-multilib).
|
||||
* Try using the following command line instead of `make 32bit`:
|
||||
`make CFLAGS="-m32 -march=native" LDFLAGS="-m32"`
|
||||
|
||||
@@ -97,15 +118,15 @@ To compile against jemalloc on Mac OS X systems, use:
|
||||
Verbose build
|
||||
-------------
|
||||
|
||||
Redis will build with a user friendly colorized output by default.
|
||||
If you want to see a more verbose output use the following:
|
||||
Redis will build with a user-friendly colorized output by default.
|
||||
If you want to see a more verbose output, use the following:
|
||||
|
||||
% make V=1
|
||||
|
||||
Running Redis
|
||||
-------------
|
||||
|
||||
To run Redis with the default configuration just type:
|
||||
To run Redis with the default configuration, just type:
|
||||
|
||||
% cd src
|
||||
% ./redis-server
|
||||
@@ -125,6 +146,12 @@ as options using the command line. Examples:
|
||||
All the options in redis.conf are also supported as options using the command
|
||||
line, with exactly the same name.
|
||||
|
||||
Running Redis with TLS:
|
||||
------------------
|
||||
|
||||
Please consult the [TLS.md](TLS.md) file for more information on
|
||||
how to use Redis with TLS.
|
||||
|
||||
Playing with Redis
|
||||
------------------
|
||||
|
||||
@@ -150,7 +177,7 @@ You can find the list of all the available commands at http://redis.io/commands.
|
||||
Installing Redis
|
||||
-----------------
|
||||
|
||||
In order to install Redis binaries into /usr/local/bin just use:
|
||||
In order to install Redis binaries into /usr/local/bin, just use:
|
||||
|
||||
% make install
|
||||
|
||||
@@ -159,8 +186,8 @@ different destination.
|
||||
|
||||
Make install will just install binaries in your system, but will not configure
|
||||
init scripts and configuration files in the appropriate place. This is not
|
||||
needed if you want just to play a bit with Redis, but if you are installing
|
||||
it the proper way for a production system, we have a script doing this
|
||||
needed if you just want to play a bit with Redis, but if you are installing
|
||||
it the proper way for a production system, we have a script that does this
|
||||
for Ubuntu and Debian systems:
|
||||
|
||||
% cd utils
|
||||
@@ -178,7 +205,7 @@ You'll be able to stop and start Redis using the script named
|
||||
Code contributions
|
||||
-----------------
|
||||
|
||||
Note: by contributing code to the Redis project in any form, including sending
|
||||
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][1] file included in the Redis
|
||||
@@ -187,8 +214,8 @@ source distribution.
|
||||
Please see the [CONTRIBUTING][2] file in this source distribution for more
|
||||
information.
|
||||
|
||||
[1]: https://github.com/antirez/redis/blob/unstable/COPYING
|
||||
[2]: https://github.com/antirez/redis/blob/unstable/CONTRIBUTING
|
||||
[1]: https://github.com/redis/redis/blob/unstable/COPYING
|
||||
[2]: https://github.com/redis/redis/blob/unstable/CONTRIBUTING
|
||||
|
||||
Redis internals
|
||||
===
|
||||
@@ -218,7 +245,7 @@ Inside the root are the following important directories:
|
||||
|
||||
* `src`: contains the Redis implementation, written in C.
|
||||
* `tests`: contains the unit tests, implemented in Tcl.
|
||||
* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `antirez/redis`.
|
||||
* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `redis/redis`.
|
||||
|
||||
There are a few more directories but they are not very important for our goals
|
||||
here. We'll focus mostly on `src`, where the Redis implementation is contained,
|
||||
@@ -228,7 +255,7 @@ of complexity incrementally.
|
||||
|
||||
Note: lately Redis was refactored quite a bit. Function names and file
|
||||
names have been changed, so you may find that this documentation reflects the
|
||||
`unstable` branch more closely. For instance in Redis 3.0 the `server.c`
|
||||
`unstable` branch more closely. For instance, in Redis 3.0 the `server.c`
|
||||
and `server.h` files were named `redis.c` and `redis.h`. However the overall
|
||||
structure is the same. Keep in mind that all the new developments and pull
|
||||
requests should be performed against the `unstable` branch.
|
||||
@@ -273,7 +300,7 @@ The client structure defines a *connected client*:
|
||||
* The `fd` field is the client socket file descriptor.
|
||||
* `argc` and `argv` are populated with the command the client is executing, so that functions implementing a given Redis command can read the arguments.
|
||||
* `querybuf` accumulates the requests from the client, which are parsed by the Redis server according to the Redis protocol and executed by calling the implementations of the commands the client is executing.
|
||||
* `reply` and `buf` are dynamic and static buffers that accumulate the replies the server sends to the client. These buffers are incrementally written to the socket as soon as the file descriptor is writable.
|
||||
* `reply` and `buf` are dynamic and static buffers that accumulate the replies the server sends to the client. These buffers are incrementally written to the socket as soon as the file descriptor is writeable.
|
||||
|
||||
As you can see in the client structure above, arguments in a command
|
||||
are described as `robj` structures. The following is the full `robj`
|
||||
@@ -306,13 +333,13 @@ This is the entry point of the Redis server, where the `main()` function
|
||||
is defined. The following are the most important steps in order to startup
|
||||
the Redis server.
|
||||
|
||||
* `initServerConfig()` setups the default values of the `server` structure.
|
||||
* `initServerConfig()` sets up the default values of the `server` structure.
|
||||
* `initServer()` allocates the data structures needed to operate, setup the listening socket, and so forth.
|
||||
* `aeMain()` starts the event loop which listens for new connections.
|
||||
|
||||
There are two special functions called periodically by the event loop:
|
||||
|
||||
1. `serverCron()` is called periodically (according to `server.hz` frequency), and performs tasks that must be performed from time to time, like checking for timedout clients.
|
||||
1. `serverCron()` is called periodically (according to `server.hz` frequency), and performs tasks that must be performed from time to time, like checking for timed out clients.
|
||||
2. `beforeSleep()` is called every time the event loop fired, Redis served a few requests, and is returning back into the event loop.
|
||||
|
||||
Inside server.c you can find code that handles other vital things of the Redis server:
|
||||
@@ -329,16 +356,16 @@ This file defines all the I/O functions with clients, masters and replicas
|
||||
(which in Redis are just special clients):
|
||||
|
||||
* `createClient()` allocates and initializes a new client.
|
||||
* the `addReply*()` family of functions are used by commands implementations in order to append data to the client structure, that will be transmitted to the client as a reply for a given command executed.
|
||||
* the `addReply*()` family of functions are used by command implementations in order to append data to the client structure, that will be transmitted to the client as a reply for a given command executed.
|
||||
* `writeToClient()` transmits the data pending in the output buffers to the client and is called by the *writable event handler* `sendReplyToClient()`.
|
||||
* `readQueryFromClient()` is the *readable event handler* and accumulates data from read from the client into the query buffer.
|
||||
* `readQueryFromClient()` is the *readable event handler* and accumulates data read from the client into the query buffer.
|
||||
* `processInputBuffer()` is the entry point in order to parse the client query buffer according to the Redis protocol. Once commands are ready to be processed, it calls `processCommand()` which is defined inside `server.c` in order to actually execute the command.
|
||||
* `freeClient()` deallocates, disconnects and removes a client.
|
||||
|
||||
aof.c and rdb.c
|
||||
---
|
||||
|
||||
As you can guess from the names these files implement the RDB and AOF
|
||||
As you can guess from the names, these files implement the RDB and AOF
|
||||
persistence for Redis. Redis uses a persistence model based on the `fork()`
|
||||
system call in order to create a thread with the same (shared) memory
|
||||
content of the main Redis thread. This secondary thread dumps the content
|
||||
@@ -350,13 +377,13 @@ The implementation inside `aof.c` has additional functions in order to
|
||||
implement an API that allows commands to append new commands into the AOF
|
||||
file as clients execute them.
|
||||
|
||||
The `call()` function defined inside `server.c` is responsible to call
|
||||
The `call()` function defined inside `server.c` is responsible for calling
|
||||
the functions that in turn will write the commands into the AOF.
|
||||
|
||||
db.c
|
||||
---
|
||||
|
||||
Certain Redis commands operate on specific data types, others are general.
|
||||
Certain Redis commands operate on specific data types; others are general.
|
||||
Examples of generic commands are `DEL` and `EXPIRE`. They operate on keys
|
||||
and not on their values specifically. All those generic commands are
|
||||
defined inside `db.c`.
|
||||
@@ -364,7 +391,7 @@ defined inside `db.c`.
|
||||
Moreover `db.c` implements an API in order to perform certain operations
|
||||
on the Redis dataset without directly accessing the internal data structures.
|
||||
|
||||
The most important functions inside `db.c` which are used in many commands
|
||||
The most important functions inside `db.c` which are used in many command
|
||||
implementations are the following:
|
||||
|
||||
* `lookupKeyRead()` and `lookupKeyWrite()` are used in order to get a pointer to the value associated to a given key, or `NULL` if the key does not exist.
|
||||
@@ -382,7 +409,7 @@ The `robj` structure defining Redis objects was already described. Inside
|
||||
a basic level, like functions to allocate new objects, handle the reference
|
||||
counting and so forth. Notable functions inside this file:
|
||||
|
||||
* `incrRefcount()` and `decrRefCount()` are used in order to increment or decrement an object reference count. When it drops to 0 the object is finally freed.
|
||||
* `incrRefCount()` and `decrRefCount()` are used in order to increment or decrement an object reference count. When it drops to 0 the object is finally freed.
|
||||
* `createObject()` allocates a new object. There are also specialized functions to allocate string objects having a specific content, like `createStringObjectFromLongLong()` and similar functions.
|
||||
|
||||
This file also implements the `OBJECT` command.
|
||||
@@ -406,12 +433,12 @@ replicas, or to continue the replication after a disconnection.
|
||||
Other C files
|
||||
---
|
||||
|
||||
* `t_hash.c`, `t_list.c`, `t_set.c`, `t_string.c`, `t_zset.c` and `t_stream.c` contains the implementation of the Redis data types. They implement both an API to access a given data type, and the client commands implementations for these data types.
|
||||
* `t_hash.c`, `t_list.c`, `t_set.c`, `t_string.c`, `t_zset.c` and `t_stream.c` contains the implementation of the Redis data types. They implement both an API to access a given data type, and the client command implementations for these data types.
|
||||
* `ae.c` implements the Redis event loop, it's a self contained library which is simple to read and understand.
|
||||
* `sds.c` is the Redis string library, check http://github.com/antirez/sds for more information.
|
||||
* `anet.c` is a library to use POSIX networking in a simpler way compared to the raw interface exposed by the kernel.
|
||||
* `dict.c` is an implementation of a non-blocking hash table which rehashes incrementally.
|
||||
* `scripting.c` implements Lua scripting. It is completely self contained from the rest of the Redis implementation and is simple enough to understand if you are familar with the Lua API.
|
||||
* `scripting.c` implements Lua scripting. It is completely self-contained and isolated from the rest of the Redis implementation and is simple enough to understand if you are familiar with the Lua API.
|
||||
* `cluster.c` implements the Redis Cluster. Probably a good read only after being very familiar with the rest of the Redis code base. If you want to read `cluster.c` make sure to read the [Redis Cluster specification][3].
|
||||
|
||||
[3]: http://redis.io/topics/cluster-spec
|
||||
@@ -437,12 +464,12 @@ top comment inside `server.c`.
|
||||
After the command operates in some way, it returns a reply to the client,
|
||||
usually using `addReply()` or a similar function defined inside `networking.c`.
|
||||
|
||||
There are tons of commands implementations inside the Redis source code
|
||||
that can serve as examples of actual commands implementations. To write
|
||||
a few toy commands can be a good exercise to familiarize with the code base.
|
||||
There are tons of command implementations inside the Redis source code
|
||||
that can serve as examples of actual commands implementations. Writing
|
||||
a few toy commands can be a good exercise to get familiar with the code base.
|
||||
|
||||
There are also many other files not described here, but it is useless to
|
||||
cover everything. We want to just help you with the first steps.
|
||||
cover everything. We just want to help you with the first steps.
|
||||
Eventually you'll find your way inside the Redis code base :-)
|
||||
|
||||
Enjoy!
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
TLS Support -- Work In Progress
|
||||
===============================
|
||||
|
||||
This is a brief note to capture current thoughts/ideas and track pending action
|
||||
items.
|
||||
TLS Support
|
||||
===========
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
@@ -69,37 +66,21 @@ probably not be so hard. For cluster keys migration it might be more difficult,
|
||||
but there are probably other good reasons to improve that part anyway.
|
||||
|
||||
To-Do List
|
||||
==========
|
||||
----------
|
||||
|
||||
Additional TLS Features
|
||||
-----------------------
|
||||
- [ ] redis-benchmark support. The current implementation is a mix of using
|
||||
hiredis for parsing and basic networking (establishing connections), but
|
||||
directly manipulating sockets for most actions. This will need to be cleaned
|
||||
up for proper TLS support. The best approach is probably to migrate to hiredis
|
||||
async mode.
|
||||
- [ ] redis-cli `--slave` and `--rdb` support.
|
||||
|
||||
1. Add metrics to INFO?
|
||||
2. Add session caching support. Check if/how it's handled by clients to assess
|
||||
how useful/important it is.
|
||||
|
||||
redis-benchmark
|
||||
---------------
|
||||
|
||||
The current implementation is a mix of using hiredis for parsing and basic
|
||||
networking (establishing connections), but directly manipulating sockets for
|
||||
most actions.
|
||||
|
||||
This will need to be cleaned up for proper TLS support. The best approach is
|
||||
probably to migrate to hiredis async mode.
|
||||
|
||||
redis-cli
|
||||
---------
|
||||
|
||||
1. Add support for TLS in --slave and --rdb modes.
|
||||
|
||||
Others
|
||||
------
|
||||
Multi-port
|
||||
----------
|
||||
|
||||
Consider the implications of allowing TLS to be configured on a separate port,
|
||||
making Redis listening on multiple ports.
|
||||
making Redis listening on multiple ports:
|
||||
|
||||
This impacts many things, like
|
||||
1. Startup banner port notification
|
||||
2. Proctitle
|
||||
3. How slaves announce themselves
|
||||
|
||||
Vendored
+7
@@ -2,6 +2,8 @@
|
||||
|
||||
uname_S:= $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
|
||||
LUA_COVERAGE?=no
|
||||
|
||||
CCCOLOR="\033[34m"
|
||||
LINKCOLOR="\033[34;1m"
|
||||
SRCCOLOR="\033[33m"
|
||||
@@ -64,6 +66,11 @@ endif
|
||||
|
||||
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL -DREDIS_STATIC='' $(CFLAGS)
|
||||
LUA_LDFLAGS+= $(LDFLAGS)
|
||||
ifeq ($(LUA_COVERAGE),yes)
|
||||
LUA_CFLAGS += -fprofile-arcs -ftest-coverage
|
||||
LUA_LDFLAGS += -fprofile-arcs -ftest-coverage
|
||||
endif
|
||||
|
||||
# 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.
|
||||
|
||||
Vendored
+5
-5
@@ -21,7 +21,7 @@ just following tose steps:
|
||||
|
||||
1. Remove the jemalloc directory.
|
||||
2. Substitute it with the new jemalloc source tree.
|
||||
3. Edit the Makefile localted in the same directory as the README you are
|
||||
3. Edit the Makefile located in the same directory as the README you are
|
||||
reading, and change the --with-version in the Jemalloc configure script
|
||||
options with the version you are using. This is required because otherwise
|
||||
Jemalloc configuration script is broken and will not work nested in another
|
||||
@@ -33,7 +33,7 @@ If you want to upgrade Jemalloc while also providing support for
|
||||
active defragmentation, in addition to the above steps you need to perform
|
||||
the following additional steps:
|
||||
|
||||
5. In Jemalloc three, file `include/jemalloc/jemalloc_macros.h.in`, make sure
|
||||
5. In Jemalloc tree, file `include/jemalloc/jemalloc_macros.h.in`, make sure
|
||||
to add `#define JEMALLOC_FRAG_HINT`.
|
||||
6. Implement the function `je_get_defrag_hint()` inside `src/jemalloc.c`. You
|
||||
can see how it is implemented in the current Jemalloc source tree shipped
|
||||
@@ -47,9 +47,9 @@ Hiredis
|
||||
Hiredis uses the SDS string library, that must be the same version used inside Redis itself. Hiredis is also very critical for Sentinel. Historically Redis often used forked versions of hiredis in a way or the other. In order to upgrade it is advised to take a lot of care:
|
||||
|
||||
1. Check with diff if hiredis API changed and what impact it could have in Redis.
|
||||
2. Make sure thet the SDS library inside Hiredis and inside Redis are compatible.
|
||||
2. Make sure that the SDS library inside Hiredis and inside Redis are compatible.
|
||||
3. After the upgrade, run the Redis Sentinel test.
|
||||
4. Check manually that redis-cli and redis-benchmark behave as expecteed, since we have no tests for CLI utilities currently.
|
||||
4. Check manually that redis-cli and redis-benchmark behave as expected, since we have no tests for CLI utilities currently.
|
||||
|
||||
Linenoise
|
||||
---
|
||||
@@ -77,6 +77,6 @@ and our version:
|
||||
|
||||
1. Makefile is modified to allow a different compiler than GCC.
|
||||
2. We have the implementation source code, and directly link to the following external libraries: `lua_cjson.o`, `lua_struct.o`, `lua_cmsgpack.o` and `lua_bit.o`.
|
||||
3. There is a security fix in `ldo.c`, line 498: The check for `LUA_SIGNATURE[0]` is removed in order toa void direct bytecode execution.
|
||||
3. There is a security fix in `ldo.c`, line 498: The check for `LUA_SIGNATURE[0]` is removed in order to avoid direct bytecode execution.
|
||||
|
||||
|
||||
|
||||
Vendored
+1
@@ -168,6 +168,7 @@ static void *createArrayObject(const redisReadTask *task, size_t elements) {
|
||||
return NULL;
|
||||
|
||||
if (elements > 0) {
|
||||
if (SIZE_MAX / sizeof(redisReply*) < elements) return NULL; /* Don't overflow */
|
||||
r->element = calloc(elements,sizeof(redisReply*));
|
||||
if (r->element == NULL) {
|
||||
freeReplyObject(r);
|
||||
|
||||
Vendored
+14
@@ -400,6 +400,20 @@ static void test_reply_reader(void) {
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Multi-bulk never overflows regardless of maxelements: ");
|
||||
size_t bad_mbulk_len = (SIZE_MAX / sizeof(void *)) + 3;
|
||||
char bad_mbulk_reply[100];
|
||||
snprintf(bad_mbulk_reply, sizeof(bad_mbulk_reply), "*%llu\r\n+asdf\r\n",
|
||||
(unsigned long long) bad_mbulk_len);
|
||||
|
||||
reader = redisReaderCreate();
|
||||
reader->maxelements = 0; /* Don't rely on default limit */
|
||||
redisReaderFeed(reader, bad_mbulk_reply, strlen(bad_mbulk_reply));
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR && strcasecmp(reader->errstr, "Out of memory") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
#if LLONG_MAX > SIZE_MAX
|
||||
test("Set error when array > SIZE_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
|
||||
@@ -216,7 +216,7 @@ ixalloc(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size, size_t extra,
|
||||
}
|
||||
|
||||
JEMALLOC_ALWAYS_INLINE int
|
||||
iget_defrag_hint(tsdn_t *tsdn, void* ptr, int *bin_util, int *run_util) {
|
||||
iget_defrag_hint(tsdn_t *tsdn, void* ptr) {
|
||||
int defrag = 0;
|
||||
rtree_ctx_t rtree_ctx_fallback;
|
||||
rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
|
||||
@@ -232,11 +232,22 @@ iget_defrag_hint(tsdn_t *tsdn, void* ptr, int *bin_util, int *run_util) {
|
||||
malloc_mutex_lock(tsdn, &bin->lock);
|
||||
/* don't bother moving allocations from the slab currently used for new allocations */
|
||||
if (slab != bin->slabcur) {
|
||||
const bin_info_t *bin_info = &bin_infos[binind];
|
||||
size_t availregs = bin_info->nregs * bin->stats.curslabs;
|
||||
*bin_util = ((long long)bin->stats.curregs<<16) / availregs;
|
||||
*run_util = ((long long)(bin_info->nregs - extent_nfree_get(slab))<<16) / bin_info->nregs;
|
||||
defrag = 1;
|
||||
int free_in_slab = extent_nfree_get(slab);
|
||||
if (free_in_slab) {
|
||||
const bin_info_t *bin_info = &bin_infos[binind];
|
||||
unsigned long curslabs = bin->stats.curslabs;
|
||||
size_t curregs = bin->stats.curregs;
|
||||
if (bin->slabcur) {
|
||||
/* remove slabcur from the overall utilization */
|
||||
curregs -= bin_info->nregs - extent_nfree_get(bin->slabcur);
|
||||
curslabs -= 1;
|
||||
}
|
||||
/* Compare the utilization ratio of the slab in question to the total average,
|
||||
* to avoid precision lost and division, we do that by extrapolating the usage
|
||||
* of the slab as if all slabs have the same usage. If this slab is less used
|
||||
* than the average, we'll prefer to evict the data to hopefully more used ones */
|
||||
defrag = (bin_info->nregs - free_in_slab) * curslabs <= curregs;
|
||||
}
|
||||
}
|
||||
malloc_mutex_unlock(tsdn, &bin->lock);
|
||||
}
|
||||
|
||||
Vendored
+3
-5
@@ -3326,12 +3326,10 @@ jemalloc_postfork_child(void) {
|
||||
/******************************************************************************/
|
||||
|
||||
/* Helps the application decide if a pointer is worth re-allocating in order to reduce fragmentation.
|
||||
* returns 0 if the allocation is in the currently active run,
|
||||
* or when it is not causing any frag issue (large or huge bin)
|
||||
* returns the bin utilization and run utilization both in fixed point 16:16.
|
||||
* returns 1 if the allocation should be moved, and 0 if the allocation be kept.
|
||||
* If the application decides to re-allocate it should use MALLOCX_TCACHE_NONE when doing so. */
|
||||
JEMALLOC_EXPORT int JEMALLOC_NOTHROW
|
||||
get_defrag_hint(void* ptr, int *bin_util, int *run_util) {
|
||||
get_defrag_hint(void* ptr) {
|
||||
assert(ptr != NULL);
|
||||
return iget_defrag_hint(TSDN_NULL, ptr, bin_util, run_util);
|
||||
return iget_defrag_hint(TSDN_NULL, ptr);
|
||||
}
|
||||
|
||||
Vendored
+24
-1
@@ -21,7 +21,7 @@ So what usually happens is either:
|
||||
|
||||
The result is a pollution of binaries without line editing support.
|
||||
|
||||
So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporing line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not.
|
||||
So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporting line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not.
|
||||
|
||||
## Terminals, in 2010.
|
||||
|
||||
@@ -126,6 +126,24 @@ Linenoise has direct support for persisting the history into an history
|
||||
file. The functions `linenoiseHistorySave` and `linenoiseHistoryLoad` do
|
||||
just that. Both functions return -1 on error and 0 on success.
|
||||
|
||||
## Mask mode
|
||||
|
||||
Sometimes it is useful to allow the user to type passwords or other
|
||||
secrets that should not be displayed. For such situations linenoise supports
|
||||
a "mask mode" that will just replace the characters the user is typing
|
||||
with `*` characters, like in the following example:
|
||||
|
||||
$ ./linenoise_example
|
||||
hello> get mykey
|
||||
echo: 'get mykey'
|
||||
hello> /mask
|
||||
hello> *********
|
||||
|
||||
You can enable and disable mask mode using the following two functions:
|
||||
|
||||
void linenoiseMaskModeEnable(void);
|
||||
void linenoiseMaskModeDisable(void);
|
||||
|
||||
## Completion
|
||||
|
||||
Linenoise supports completion, which is the ability to complete the user
|
||||
@@ -222,3 +240,8 @@ Sometimes you may want to clear the screen as a result of something the
|
||||
user typed. You can do this by calling the following function:
|
||||
|
||||
void linenoiseClearScreen(void);
|
||||
|
||||
## Related projects
|
||||
|
||||
* [Linenoise NG](https://github.com/arangodb/linenoise-ng) is a fork of Linenoise that aims to add more advanced features like UTF-8 support, Windows support and other features. Uses C++ instead of C as development language.
|
||||
* [Linenoise-swift](https://github.com/andybest/linenoise-swift) is a reimplementation of Linenoise written in Swift.
|
||||
|
||||
Vendored
+5
@@ -55,6 +55,7 @@ int main(int argc, char **argv) {
|
||||
*
|
||||
* The typed string is returned as a malloc() allocated string by
|
||||
* linenoise, so the user needs to free() it. */
|
||||
|
||||
while((line = linenoise("hello> ")) != NULL) {
|
||||
/* Do something with the string. */
|
||||
if (line[0] != '\0' && line[0] != '/') {
|
||||
@@ -65,6 +66,10 @@ int main(int argc, char **argv) {
|
||||
/* The "/historylen" command will change the history len. */
|
||||
int len = atoi(line+11);
|
||||
linenoiseHistorySetMaxLen(len);
|
||||
} else if (!strncmp(line, "/mask", 5)) {
|
||||
linenoiseMaskModeEnable();
|
||||
} else if (!strncmp(line, "/unmask", 7)) {
|
||||
linenoiseMaskModeDisable();
|
||||
} else if (line[0] == '/') {
|
||||
printf("Unreconized command: %s\n", line);
|
||||
}
|
||||
|
||||
Vendored
+31
-5
@@ -125,6 +125,7 @@ static linenoiseHintsCallback *hintsCallback = NULL;
|
||||
static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
|
||||
|
||||
static struct termios orig_termios; /* In order to restore at exit.*/
|
||||
static int maskmode = 0; /* Show "***" instead of input. For passwords. */
|
||||
static int rawmode = 0; /* For atexit() function to check if restore is needed*/
|
||||
static int mlmode = 0; /* Multi line mode. Default is single line. */
|
||||
static int atexit_registered = 0; /* Register atexit just 1 time. */
|
||||
@@ -197,6 +198,19 @@ FILE *lndebug_fp = NULL;
|
||||
|
||||
/* ======================= Low level terminal handling ====================== */
|
||||
|
||||
/* Enable "mask mode". When it is enabled, instead of the input that
|
||||
* the user is typing, the terminal will just display a corresponding
|
||||
* number of asterisks, like "****". This is useful for passwords and other
|
||||
* secrets that should not be displayed. */
|
||||
void linenoiseMaskModeEnable(void) {
|
||||
maskmode = 1;
|
||||
}
|
||||
|
||||
/* Disable mask mode. */
|
||||
void linenoiseMaskModeDisable(void) {
|
||||
maskmode = 0;
|
||||
}
|
||||
|
||||
/* Set if to use or not the multi line mode. */
|
||||
void linenoiseSetMultiLine(int ml) {
|
||||
mlmode = ml;
|
||||
@@ -485,6 +499,8 @@ void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
|
||||
if (bold == 1 && color == -1) color = 37;
|
||||
if (color != -1 || bold != 0)
|
||||
snprintf(seq,64,"\033[%d;%d;49m",bold,color);
|
||||
else
|
||||
seq[0] = '\0';
|
||||
abAppend(ab,seq,strlen(seq));
|
||||
abAppend(ab,hint,hintlen);
|
||||
if (color != -1 || bold != 0)
|
||||
@@ -523,7 +539,11 @@ static void refreshSingleLine(struct linenoiseState *l) {
|
||||
abAppend(&ab,seq,strlen(seq));
|
||||
/* Write the prompt and the current buffer content */
|
||||
abAppend(&ab,l->prompt,strlen(l->prompt));
|
||||
abAppend(&ab,buf,len);
|
||||
if (maskmode == 1) {
|
||||
while (len--) abAppend(&ab,"*",1);
|
||||
} else {
|
||||
abAppend(&ab,buf,len);
|
||||
}
|
||||
/* Show hits if any. */
|
||||
refreshShowHints(&ab,l,plen);
|
||||
/* Erase to right */
|
||||
@@ -577,7 +597,12 @@ static void refreshMultiLine(struct linenoiseState *l) {
|
||||
|
||||
/* Write the prompt and the current buffer content */
|
||||
abAppend(&ab,l->prompt,strlen(l->prompt));
|
||||
abAppend(&ab,l->buf,l->len);
|
||||
if (maskmode == 1) {
|
||||
unsigned int i;
|
||||
for (i = 0; i < l->len; i++) abAppend(&ab,"*",1);
|
||||
} else {
|
||||
abAppend(&ab,l->buf,l->len);
|
||||
}
|
||||
|
||||
/* Show hits if any. */
|
||||
refreshShowHints(&ab,l,plen);
|
||||
@@ -600,7 +625,7 @@ static void refreshMultiLine(struct linenoiseState *l) {
|
||||
rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */
|
||||
lndebug("rpos2 %d", rpos2);
|
||||
|
||||
/* Go up till we reach the expected positon. */
|
||||
/* Go up till we reach the expected position. */
|
||||
if (rows-rpos2 > 0) {
|
||||
lndebug("go-up %d", rows-rpos2);
|
||||
snprintf(seq,64,"\x1b[%dA", rows-rpos2);
|
||||
@@ -645,7 +670,8 @@ int linenoiseEditInsert(struct linenoiseState *l, char c) {
|
||||
if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) {
|
||||
/* Avoid a full update of the line in the
|
||||
* trivial case. */
|
||||
if (write(l->ofd,&c,1) == -1) return -1;
|
||||
char d = (maskmode==1) ? '*' : c;
|
||||
if (write(l->ofd,&d,1) == -1) return -1;
|
||||
} else {
|
||||
refreshLine(l);
|
||||
}
|
||||
@@ -741,7 +767,7 @@ void linenoiseEditBackspace(struct linenoiseState *l) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete the previosu word, maintaining the cursor at the start of the
|
||||
/* Delete the previous word, maintaining the cursor at the start of the
|
||||
* current word. */
|
||||
void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
|
||||
size_t old_pos = l->pos;
|
||||
|
||||
Vendored
+2
@@ -65,6 +65,8 @@ int linenoiseHistoryLoad(const char *filename);
|
||||
void linenoiseClearScreen(void);
|
||||
void linenoiseSetMultiLine(int ml);
|
||||
void linenoisePrintKeyCodes(void);
|
||||
void linenoiseMaskModeEnable(void);
|
||||
void linenoiseMaskModeDisable(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -274,7 +274,7 @@ int luaD_precall (lua_State *L, StkId func, int nresults) {
|
||||
CallInfo *ci;
|
||||
StkId st, base;
|
||||
Proto *p = cl->p;
|
||||
luaD_checkstack(L, p->maxstacksize);
|
||||
luaD_checkstack(L, p->maxstacksize + p->numparams);
|
||||
func = restorestack(L, funcr);
|
||||
if (!p->is_vararg) { /* no varargs? */
|
||||
base = func + 1;
|
||||
|
||||
Vendored
+6
-3
@@ -39,6 +39,7 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
@@ -141,13 +142,13 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
json_token_type_t type;
|
||||
int index;
|
||||
size_t index;
|
||||
union {
|
||||
const char *string;
|
||||
double number;
|
||||
int boolean;
|
||||
} value;
|
||||
int string_len;
|
||||
size_t string_len;
|
||||
} json_token_t;
|
||||
|
||||
static const char *char2escape[256] = {
|
||||
@@ -473,6 +474,8 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
|
||||
* This buffer is reused constantly for small strings
|
||||
* If there are any excess pages, they won't be hit anyway.
|
||||
* This gains ~5% speedup. */
|
||||
if (len > SIZE_MAX / 6 - 3)
|
||||
abort(); /* Overflow check */
|
||||
strbuf_ensure_empty_length(json, len * 6 + 2);
|
||||
|
||||
strbuf_append_char_unsafe(json, '\"');
|
||||
@@ -706,7 +709,7 @@ static int json_encode(lua_State *l)
|
||||
strbuf_t local_encode_buf;
|
||||
strbuf_t *encode_buf;
|
||||
char *json;
|
||||
int len;
|
||||
size_t len;
|
||||
|
||||
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
|
||||
|
||||
|
||||
Vendored
+17
-14
@@ -117,7 +117,9 @@ mp_buf *mp_buf_new(lua_State *L) {
|
||||
|
||||
void mp_buf_append(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
if (buf->free < len) {
|
||||
size_t newsize = (buf->len+len)*2;
|
||||
size_t newsize = buf->len+len;
|
||||
if (newsize < buf->len || newsize >= SIZE_MAX/2) abort();
|
||||
newsize *= 2;
|
||||
|
||||
buf->b = (unsigned char*)mp_realloc(L, buf->b, buf->len + buf->free, newsize);
|
||||
buf->free = newsize - buf->len;
|
||||
@@ -173,7 +175,7 @@ void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) {
|
||||
|
||||
void mp_encode_bytes(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
unsigned char hdr[5];
|
||||
int hdrlen;
|
||||
size_t hdrlen;
|
||||
|
||||
if (len < 32) {
|
||||
hdr[0] = 0xa0 | (len&0xff); /* fix raw */
|
||||
@@ -220,7 +222,7 @@ void mp_encode_double(lua_State *L, mp_buf *buf, double d) {
|
||||
|
||||
void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
unsigned char b[9];
|
||||
int enclen;
|
||||
size_t enclen;
|
||||
|
||||
if (n >= 0) {
|
||||
if (n <= 127) {
|
||||
@@ -290,9 +292,9 @@ void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(L,buf,b,enclen);
|
||||
}
|
||||
|
||||
void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
void mp_encode_array(lua_State *L, mp_buf *buf, uint64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
size_t enclen;
|
||||
|
||||
if (n <= 15) {
|
||||
b[0] = 0x90 | (n & 0xf); /* fix array */
|
||||
@@ -313,7 +315,7 @@ void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(L,buf,b,enclen);
|
||||
}
|
||||
|
||||
void mp_encode_map(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
void mp_encode_map(lua_State *L, mp_buf *buf, uint64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
|
||||
@@ -790,7 +792,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
}
|
||||
}
|
||||
|
||||
int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
int mp_unpack_full(lua_State *L, lua_Integer limit, lua_Integer offset) {
|
||||
size_t len;
|
||||
const char *s;
|
||||
mp_cur c;
|
||||
@@ -802,10 +804,10 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
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);
|
||||
(int) offset, (int) len);
|
||||
else if (offset > len)
|
||||
return luaL_error(L,
|
||||
"Start offset %d greater than input length %d.", offset, len);
|
||||
"Start offset %d greater than input length %d.", (int) offset, (int) len);
|
||||
|
||||
if (decode_all) limit = INT_MAX;
|
||||
|
||||
@@ -827,12 +829,13 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
/* 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;
|
||||
size_t new_offset = len - c.left;
|
||||
if (new_offset > LONG_MAX) abort();
|
||||
|
||||
luaL_checkstack(L, 1, "in function mp_unpack_full");
|
||||
|
||||
/* Return offset -1 when we have have processed the entire buffer. */
|
||||
lua_pushinteger(L, c.left == 0 ? -1 : offset);
|
||||
lua_pushinteger(L, c.left == 0 ? -1 : (lua_Integer) new_offset);
|
||||
/* Results are returned with the arg elements still
|
||||
* in place. Lua takes care of only returning
|
||||
* elements above the args for us.
|
||||
@@ -851,15 +854,15 @@ int mp_unpack(lua_State *L) {
|
||||
}
|
||||
|
||||
int mp_unpack_one(lua_State *L) {
|
||||
int offset = luaL_optinteger(L, 2, 0);
|
||||
lua_Integer 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);
|
||||
lua_Integer limit = luaL_checkinteger(L, 2);
|
||||
lua_Integer offset = luaL_optinteger(L, 3, 0);
|
||||
/* Variable pop because offset may not exist */
|
||||
lua_pop(L, lua_gettop(L)-1);
|
||||
|
||||
|
||||
Vendored
+6
-4
@@ -89,12 +89,14 @@ typedef struct Header {
|
||||
} Header;
|
||||
|
||||
|
||||
static int getnum (const char **fmt, int df) {
|
||||
static int getnum (lua_State *L, const char **fmt, int df) {
|
||||
if (!isdigit(**fmt)) /* no number? */
|
||||
return df; /* return default value */
|
||||
else {
|
||||
int a = 0;
|
||||
do {
|
||||
if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0')))
|
||||
luaL_error(L, "integral size overflow");
|
||||
a = a*10 + *((*fmt)++) - '0';
|
||||
} while (isdigit(**fmt));
|
||||
return a;
|
||||
@@ -115,9 +117,9 @@ static size_t optsize (lua_State *L, char opt, const char **fmt) {
|
||||
case 'f': return sizeof(float);
|
||||
case 'd': return sizeof(double);
|
||||
case 'x': return 1;
|
||||
case 'c': return getnum(fmt, 1);
|
||||
case 'c': return getnum(L, fmt, 1);
|
||||
case 'i': case 'I': {
|
||||
int sz = getnum(fmt, sizeof(int));
|
||||
int sz = getnum(L, fmt, sizeof(int));
|
||||
if (sz > MAXINTSIZE)
|
||||
luaL_error(L, "integral size %d is larger than limit of %d",
|
||||
sz, MAXINTSIZE);
|
||||
@@ -150,7 +152,7 @@ static void controloptions (lua_State *L, int opt, const char **fmt,
|
||||
case '>': h->endian = BIG; return;
|
||||
case '<': h->endian = LITTLE; return;
|
||||
case '!': {
|
||||
int a = getnum(fmt, MAXALIGN);
|
||||
int a = getnum(L, fmt, MAXALIGN);
|
||||
if (!isp2(a))
|
||||
luaL_error(L, "alignment %d is not a power of 2", a);
|
||||
h->align = a;
|
||||
|
||||
Vendored
+28
-81
@@ -26,6 +26,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "strbuf.h"
|
||||
|
||||
@@ -38,22 +39,22 @@ static void die(const char *fmt, ...)
|
||||
va_end(arg);
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
exit(-1);
|
||||
abort();
|
||||
}
|
||||
|
||||
void strbuf_init(strbuf_t *s, int len)
|
||||
void strbuf_init(strbuf_t *s, size_t len)
|
||||
{
|
||||
int size;
|
||||
size_t size;
|
||||
|
||||
if (len <= 0)
|
||||
if (!len)
|
||||
size = STRBUF_DEFAULT_SIZE;
|
||||
else
|
||||
size = len + 1; /* \0 terminator */
|
||||
|
||||
size = len + 1;
|
||||
if (size < len)
|
||||
die("Overflow, len: %zu", len);
|
||||
s->buf = NULL;
|
||||
s->size = size;
|
||||
s->length = 0;
|
||||
s->increment = STRBUF_DEFAULT_INCREMENT;
|
||||
s->dynamic = 0;
|
||||
s->reallocs = 0;
|
||||
s->debug = 0;
|
||||
@@ -65,7 +66,7 @@ void strbuf_init(strbuf_t *s, int len)
|
||||
strbuf_ensure_null(s);
|
||||
}
|
||||
|
||||
strbuf_t *strbuf_new(int len)
|
||||
strbuf_t *strbuf_new(size_t len)
|
||||
{
|
||||
strbuf_t *s;
|
||||
|
||||
@@ -81,20 +82,10 @@ strbuf_t *strbuf_new(int len)
|
||||
return s;
|
||||
}
|
||||
|
||||
void strbuf_set_increment(strbuf_t *s, int increment)
|
||||
{
|
||||
/* Increment > 0: Linear buffer growth rate
|
||||
* Increment < -1: Exponential buffer growth rate */
|
||||
if (increment == 0 || increment == -1)
|
||||
die("BUG: Invalid string increment");
|
||||
|
||||
s->increment = increment;
|
||||
}
|
||||
|
||||
static inline void debug_stats(strbuf_t *s)
|
||||
{
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n",
|
||||
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %zd, size: %zd\n",
|
||||
(long)s, s->reallocs, s->length, s->size);
|
||||
}
|
||||
}
|
||||
@@ -113,7 +104,7 @@ void strbuf_free(strbuf_t *s)
|
||||
free(s);
|
||||
}
|
||||
|
||||
char *strbuf_free_to_string(strbuf_t *s, int *len)
|
||||
char *strbuf_free_to_string(strbuf_t *s, size_t *len)
|
||||
{
|
||||
char *buf;
|
||||
|
||||
@@ -131,57 +122,62 @@ char *strbuf_free_to_string(strbuf_t *s, int *len)
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int calculate_new_size(strbuf_t *s, int len)
|
||||
static size_t calculate_new_size(strbuf_t *s, size_t len)
|
||||
{
|
||||
int reqsize, newsize;
|
||||
size_t reqsize, newsize;
|
||||
|
||||
if (len <= 0)
|
||||
die("BUG: Invalid strbuf length requested");
|
||||
|
||||
/* Ensure there is room for optional NULL termination */
|
||||
reqsize = len + 1;
|
||||
if (reqsize < len)
|
||||
die("Overflow, len: %zu", len);
|
||||
|
||||
/* If the user has requested to shrink the buffer, do it exactly */
|
||||
if (s->size > reqsize)
|
||||
return reqsize;
|
||||
|
||||
newsize = s->size;
|
||||
if (s->increment < 0) {
|
||||
if (reqsize >= SIZE_MAX / 2) {
|
||||
newsize = reqsize;
|
||||
} else {
|
||||
/* Exponential sizing */
|
||||
while (newsize < reqsize)
|
||||
newsize *= -s->increment;
|
||||
} else {
|
||||
/* Linear sizing */
|
||||
newsize = ((newsize + s->increment - 1) / s->increment) * s->increment;
|
||||
newsize *= 2;
|
||||
}
|
||||
|
||||
if (newsize < reqsize)
|
||||
die("BUG: strbuf length would overflow, len: %zu", len);
|
||||
|
||||
return newsize;
|
||||
}
|
||||
|
||||
|
||||
/* Ensure strbuf can handle a string length bytes long (ignoring NULL
|
||||
* optional termination). */
|
||||
void strbuf_resize(strbuf_t *s, int len)
|
||||
void strbuf_resize(strbuf_t *s, size_t len)
|
||||
{
|
||||
int newsize;
|
||||
size_t newsize;
|
||||
|
||||
newsize = calculate_new_size(s, len);
|
||||
|
||||
if (s->debug > 1) {
|
||||
fprintf(stderr, "strbuf(%lx) resize: %d => %d\n",
|
||||
fprintf(stderr, "strbuf(%lx) resize: %zd => %zd\n",
|
||||
(long)s, s->size, newsize);
|
||||
}
|
||||
|
||||
s->size = newsize;
|
||||
s->buf = realloc(s->buf, s->size);
|
||||
if (!s->buf)
|
||||
die("Out of memory");
|
||||
die("Out of memory, len: %zu", len);
|
||||
s->reallocs++;
|
||||
}
|
||||
|
||||
void strbuf_append_string(strbuf_t *s, const char *str)
|
||||
{
|
||||
int space, i;
|
||||
int i;
|
||||
size_t space;
|
||||
|
||||
space = strbuf_empty_length(s);
|
||||
|
||||
@@ -197,55 +193,6 @@ void strbuf_append_string(strbuf_t *s, const char *str)
|
||||
}
|
||||
}
|
||||
|
||||
/* strbuf_append_fmt() should only be used when an upper bound
|
||||
* is known for the output string. */
|
||||
void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...)
|
||||
{
|
||||
va_list arg;
|
||||
int fmt_len;
|
||||
|
||||
strbuf_ensure_empty_length(s, len);
|
||||
|
||||
va_start(arg, fmt);
|
||||
fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg);
|
||||
va_end(arg);
|
||||
|
||||
if (fmt_len < 0)
|
||||
die("BUG: Unable to convert number"); /* This should never happen.. */
|
||||
|
||||
s->length += fmt_len;
|
||||
}
|
||||
|
||||
/* strbuf_append_fmt_retry() can be used when the there is no known
|
||||
* upper bound for the output string. */
|
||||
void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...)
|
||||
{
|
||||
va_list arg;
|
||||
int fmt_len, try;
|
||||
int empty_len;
|
||||
|
||||
/* If the first attempt to append fails, resize the buffer appropriately
|
||||
* and try again */
|
||||
for (try = 0; ; try++) {
|
||||
va_start(arg, fmt);
|
||||
/* Append the new formatted string */
|
||||
/* fmt_len is the length of the string required, excluding the
|
||||
* trailing NULL */
|
||||
empty_len = strbuf_empty_length(s);
|
||||
/* Add 1 since there is also space to store the terminating NULL. */
|
||||
fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg);
|
||||
va_end(arg);
|
||||
|
||||
if (fmt_len <= empty_len)
|
||||
break; /* SUCCESS */
|
||||
if (try > 0)
|
||||
die("BUG: length of formatted string changed");
|
||||
|
||||
strbuf_resize(s, s->length + fmt_len);
|
||||
}
|
||||
|
||||
s->length += fmt_len;
|
||||
}
|
||||
|
||||
/* vi:ai et sw=4 ts=4:
|
||||
*/
|
||||
|
||||
Vendored
+19
-27
@@ -27,15 +27,13 @@
|
||||
|
||||
/* Size: Total bytes allocated to *buf
|
||||
* Length: String length, excluding optional NULL terminator.
|
||||
* Increment: Allocation increments when resizing the string buffer.
|
||||
* Dynamic: True if created via strbuf_new()
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
char *buf;
|
||||
int size;
|
||||
int length;
|
||||
int increment;
|
||||
size_t size;
|
||||
size_t length;
|
||||
int dynamic;
|
||||
int reallocs;
|
||||
int debug;
|
||||
@@ -44,32 +42,26 @@ typedef struct {
|
||||
#ifndef STRBUF_DEFAULT_SIZE
|
||||
#define STRBUF_DEFAULT_SIZE 1023
|
||||
#endif
|
||||
#ifndef STRBUF_DEFAULT_INCREMENT
|
||||
#define STRBUF_DEFAULT_INCREMENT -2
|
||||
#endif
|
||||
|
||||
/* Initialise */
|
||||
extern strbuf_t *strbuf_new(int len);
|
||||
extern void strbuf_init(strbuf_t *s, int len);
|
||||
extern void strbuf_set_increment(strbuf_t *s, int increment);
|
||||
extern strbuf_t *strbuf_new(size_t len);
|
||||
extern void strbuf_init(strbuf_t *s, size_t len);
|
||||
|
||||
/* Release */
|
||||
extern void strbuf_free(strbuf_t *s);
|
||||
extern char *strbuf_free_to_string(strbuf_t *s, int *len);
|
||||
extern char *strbuf_free_to_string(strbuf_t *s, size_t *len);
|
||||
|
||||
/* Management */
|
||||
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);
|
||||
extern void strbuf_resize(strbuf_t *s, size_t len);
|
||||
static size_t strbuf_empty_length(strbuf_t *s);
|
||||
static size_t strbuf_length(strbuf_t *s);
|
||||
static char *strbuf_string(strbuf_t *s, size_t *len);
|
||||
static void strbuf_ensure_empty_length(strbuf_t *s, size_t len);
|
||||
static char *strbuf_empty_ptr(strbuf_t *s);
|
||||
static void strbuf_extend_length(strbuf_t *s, int len);
|
||||
static void strbuf_extend_length(strbuf_t *s, size_t len);
|
||||
|
||||
/* Update */
|
||||
extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...);
|
||||
extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...);
|
||||
static void strbuf_append_mem(strbuf_t *s, const char *c, int len);
|
||||
static void strbuf_append_mem(strbuf_t *s, const char *c, size_t len);
|
||||
extern void strbuf_append_string(strbuf_t *s, const char *str);
|
||||
static void strbuf_append_char(strbuf_t *s, const char c);
|
||||
static void strbuf_ensure_null(strbuf_t *s);
|
||||
@@ -87,12 +79,12 @@ static inline int strbuf_allocated(strbuf_t *s)
|
||||
|
||||
/* Return bytes remaining in the string buffer
|
||||
* Ensure there is space for a NULL terminator. */
|
||||
static inline int strbuf_empty_length(strbuf_t *s)
|
||||
static inline size_t strbuf_empty_length(strbuf_t *s)
|
||||
{
|
||||
return s->size - s->length - 1;
|
||||
}
|
||||
|
||||
static inline void strbuf_ensure_empty_length(strbuf_t *s, int len)
|
||||
static inline void strbuf_ensure_empty_length(strbuf_t *s, size_t len)
|
||||
{
|
||||
if (len > strbuf_empty_length(s))
|
||||
strbuf_resize(s, s->length + len);
|
||||
@@ -103,12 +95,12 @@ 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)
|
||||
static inline void strbuf_extend_length(strbuf_t *s, size_t len)
|
||||
{
|
||||
s->length += len;
|
||||
}
|
||||
|
||||
static inline int strbuf_length(strbuf_t *s)
|
||||
static inline size_t strbuf_length(strbuf_t *s)
|
||||
{
|
||||
return s->length;
|
||||
}
|
||||
@@ -124,14 +116,14 @@ static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c)
|
||||
s->buf[s->length++] = c;
|
||||
}
|
||||
|
||||
static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len)
|
||||
static inline void strbuf_append_mem(strbuf_t *s, const char *c, size_t len)
|
||||
{
|
||||
strbuf_ensure_empty_length(s, len);
|
||||
memcpy(s->buf + s->length, c, len);
|
||||
s->length += len;
|
||||
}
|
||||
|
||||
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len)
|
||||
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, size_t len)
|
||||
{
|
||||
memcpy(s->buf + s->length, c, len);
|
||||
s->length += len;
|
||||
@@ -142,7 +134,7 @@ static inline void strbuf_ensure_null(strbuf_t *s)
|
||||
s->buf[s->length] = 0;
|
||||
}
|
||||
|
||||
static inline char *strbuf_string(strbuf_t *s, int *len)
|
||||
static inline char *strbuf_string(strbuf_t *s, size_t *len)
|
||||
{
|
||||
if (len)
|
||||
*len = s->length;
|
||||
|
||||
+263
-88
@@ -24,7 +24,7 @@
|
||||
# to customize a few per-server settings. Include files can include
|
||||
# other files, so use this wisely.
|
||||
#
|
||||
# Notice option "include" won't be rewritten by command "CONFIG REWRITE"
|
||||
# Note that option "include" won't be rewritten by command "CONFIG REWRITE"
|
||||
# from admin or Redis Sentinel. Since Redis always uses the last processed
|
||||
# line as value of a configuration directive, you'd better put includes
|
||||
# at the beginning of this file to avoid overwriting config change at runtime.
|
||||
@@ -46,7 +46,7 @@
|
||||
################################## NETWORK #####################################
|
||||
|
||||
# By default, if no "bind" configuration directive is specified, Redis listens
|
||||
# for connections from all the network interfaces available on the server.
|
||||
# for connections from all available network interfaces on the host machine.
|
||||
# It is possible to listen to just one or multiple selected interfaces using
|
||||
# the "bind" configuration directive, followed by one or more IP addresses.
|
||||
#
|
||||
@@ -58,13 +58,12 @@
|
||||
# ~~~ 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 loopback interface address (this means Redis will be able to
|
||||
# accept connections only from clients running into the same computer it
|
||||
# is running).
|
||||
# following bind directive, that will force Redis to listen only on the
|
||||
# IPv4 loopback interface address (this means Redis will only be able to
|
||||
# accept client connections from the same host that it is running on).
|
||||
#
|
||||
# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
|
||||
# JUST COMMENT THE FOLLOWING LINE.
|
||||
# JUST COMMENT OUT THE FOLLOWING LINE.
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
bind 127.0.0.1
|
||||
|
||||
@@ -93,8 +92,8 @@ port 6379
|
||||
|
||||
# TCP listen() backlog.
|
||||
#
|
||||
# In high requests-per-second environments you need an high backlog in order
|
||||
# to avoid slow clients connections issues. Note that the Linux kernel
|
||||
# In high requests-per-second environments you need a high backlog in order
|
||||
# to avoid slow clients connection issues. Note that the Linux kernel
|
||||
# will silently truncate it to the value of /proc/sys/net/core/somaxconn so
|
||||
# make sure to raise both the value of somaxconn and tcp_max_syn_backlog
|
||||
# in order to get the desired effect.
|
||||
@@ -118,8 +117,8 @@ timeout 0
|
||||
# of communication. This is useful for two reasons:
|
||||
#
|
||||
# 1) Detect dead peers.
|
||||
# 2) Take the connection alive from the point of view of network
|
||||
# equipment in the middle.
|
||||
# 2) Force network equipment in the middle to consider the connection to be
|
||||
# alive.
|
||||
#
|
||||
# On Linux, the specified value (in seconds) is the period used to send ACKs.
|
||||
# Note that to close the connection the double of the time is needed.
|
||||
@@ -142,7 +141,8 @@ tcp-keepalive 300
|
||||
# server to connected clients, masters or cluster peers. These files should be
|
||||
# PEM formatted.
|
||||
#
|
||||
# tls-cert-file redis.crt tls-key-file redis.key
|
||||
# tls-cert-file redis.crt
|
||||
# tls-key-file redis.key
|
||||
|
||||
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
|
||||
#
|
||||
@@ -155,31 +155,33 @@ tcp-keepalive 300
|
||||
# tls-ca-cert-file ca.crt
|
||||
# tls-ca-cert-dir /etc/ssl/certs
|
||||
|
||||
# If TLS/SSL clients are required to authenticate using a client side
|
||||
# certificate, use this directive.
|
||||
# By default, clients (including replica servers) on a TLS port are required
|
||||
# to authenticate using valid client side certificates.
|
||||
#
|
||||
# Note: this applies to all incoming clients, including replicas.
|
||||
# If "no" is specified, client certificates are not required and not accepted.
|
||||
# If "optional" is specified, client certificates are accepted and must be
|
||||
# valid if provided, but are not required.
|
||||
#
|
||||
# tls-auth-clients yes
|
||||
# tls-auth-clients no
|
||||
# tls-auth-clients optional
|
||||
|
||||
# If TLS/SSL should be used when connecting as a replica to a master, enable
|
||||
# this configuration directive:
|
||||
# By default, a Redis replica does not attempt to establish a TLS connection
|
||||
# with its master.
|
||||
#
|
||||
# Use the following directive to enable TLS on replication links.
|
||||
#
|
||||
# tls-replication yes
|
||||
|
||||
# If TLS/SSL should be used for the Redis Cluster bus, enable this configuration
|
||||
# directive.
|
||||
#
|
||||
# NOTE: If TLS/SSL is enabled for Cluster Bus, mutual authentication is always
|
||||
# enforced.
|
||||
# By default, the Redis Cluster bus uses a plain TCP connection. To enable
|
||||
# TLS for the bus protocol, use the following directive:
|
||||
#
|
||||
# tls-cluster yes
|
||||
|
||||
# Explicitly specify TLS versions to support. Allowed values are case insensitive
|
||||
# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or
|
||||
# "default" which is currently >= TLSv1.1.
|
||||
# any combination. To enable only TLSv1.2 and TLSv1.3, use:
|
||||
#
|
||||
# tls-protocols TLSv1.2
|
||||
# tls-protocols "TLSv1.2 TLSv1.3"
|
||||
|
||||
# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information
|
||||
# about the syntax of this string.
|
||||
@@ -197,7 +199,23 @@ tcp-keepalive 300
|
||||
# When choosing a cipher, use the server's preference instead of the client
|
||||
# preference. By default, the server follows the client's preference.
|
||||
#
|
||||
# tls-prefer-server-cipher yes
|
||||
# tls-prefer-server-ciphers yes
|
||||
|
||||
# By default, TLS session caching is enabled to allow faster and less expensive
|
||||
# reconnections by clients that support it. Use the following directive to disable
|
||||
# caching.
|
||||
#
|
||||
# tls-session-caching no
|
||||
|
||||
# Change the default number of TLS sessions cached. A zero value sets the cache
|
||||
# to unlimited size. The default size is 20480.
|
||||
#
|
||||
# tls-session-cache-size 5000
|
||||
|
||||
# Change the default timeout of cached TLS sessions. The default timeout is 300
|
||||
# seconds.
|
||||
#
|
||||
# tls-session-cache-timeout 60
|
||||
|
||||
################################# GENERAL #####################################
|
||||
|
||||
@@ -209,11 +227,12 @@ daemonize no
|
||||
# supervision tree. Options:
|
||||
# supervised no - no supervision interaction
|
||||
# supervised upstart - signal upstart by putting Redis into SIGSTOP mode
|
||||
# requires "expect stop" in your upstart job config
|
||||
# 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.
|
||||
# They do not enable continuous pings back to your supervisor.
|
||||
supervised no
|
||||
|
||||
# If a pid file is specified, Redis writes it where specified at startup
|
||||
@@ -272,7 +291,7 @@ always-show-logo yes
|
||||
# Will save the DB if both the given number of seconds and the given
|
||||
# number of write operations against the DB occurred.
|
||||
#
|
||||
# In the example below the behaviour will be to save:
|
||||
# In the example below the behavior will be to save:
|
||||
# after 900 sec (15 min) if at least 1 key changed
|
||||
# after 300 sec (5 min) if at least 10 keys changed
|
||||
# after 60 sec if at least 10000 keys changed
|
||||
@@ -305,7 +324,7 @@ save 60 10000
|
||||
stop-writes-on-bgsave-error yes
|
||||
|
||||
# Compress string objects using LZF when dump .rdb databases?
|
||||
# For default that's set to 'yes' as it's almost always a win.
|
||||
# By default compression is enabled as it's almost always a win.
|
||||
# If you want to save some CPU in the saving child set it to 'no' but
|
||||
# the dataset will likely be bigger if you have compressible values or keys.
|
||||
rdbcompression yes
|
||||
@@ -322,6 +341,19 @@ rdbchecksum yes
|
||||
# The filename where to dump the DB
|
||||
dbfilename dump.rdb
|
||||
|
||||
# Remove RDB files used by replication in instances without persistence
|
||||
# enabled. By default this option is disabled, however there are environments
|
||||
# where for regulations or other security concerns, RDB files persisted on
|
||||
# disk by masters in order to feed replicas, or stored on disk by replicas
|
||||
# in order to load them for the initial synchronization, should be deleted
|
||||
# ASAP. Note that this option ONLY WORKS in instances that have both AOF
|
||||
# and RDB persistence disabled, otherwise is completely ignored.
|
||||
#
|
||||
# An alternative (and sometimes better) way to obtain the same effect is
|
||||
# to use diskless replication on both master and replicas instances. However
|
||||
# in the case of replicas, diskless is not always an option.
|
||||
rdb-del-sync-files no
|
||||
|
||||
# The working directory.
|
||||
#
|
||||
# The DB will be written inside this directory, with the filename specified
|
||||
@@ -380,11 +412,11 @@ dir ./
|
||||
# still reply to client requests, possibly with out of date data, or the
|
||||
# data set may just be empty if this is the first synchronization.
|
||||
#
|
||||
# 2) if replica-serve-stale-data is set to 'no' the replica will reply with
|
||||
# an error "SYNC with master in progress" to all the kind of commands
|
||||
# but to INFO, replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG,
|
||||
# SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB,
|
||||
# COMMAND, POST, HOST: and LATENCY.
|
||||
# 2) If replica-serve-stale-data is set to 'no' the replica will reply with
|
||||
# an error "SYNC with master in progress" to all commands except:
|
||||
# INFO, REPLICAOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE,
|
||||
# UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST,
|
||||
# HOST and LATENCY.
|
||||
#
|
||||
replica-serve-stale-data yes
|
||||
|
||||
@@ -455,7 +487,7 @@ repl-diskless-sync-delay 5
|
||||
#
|
||||
# Replica can load the RDB it reads from the replication link directly from the
|
||||
# socket, or store the RDB to a file and read that file after it was completely
|
||||
# recived from the master.
|
||||
# received from the master.
|
||||
#
|
||||
# In many cases the disk is slower than the network, and storing and loading
|
||||
# the RDB file may increase replication time (and even increase the master's
|
||||
@@ -485,7 +517,8 @@ repl-diskless-load disabled
|
||||
#
|
||||
# It is important to make sure that this value is greater than the value
|
||||
# specified for repl-ping-replica-period otherwise a timeout will be detected
|
||||
# every time there is low traffic between the master and the replica.
|
||||
# every time there is low traffic between the master and the replica. The default
|
||||
# value is 60 seconds.
|
||||
#
|
||||
# repl-timeout 60
|
||||
|
||||
@@ -510,21 +543,21 @@ repl-disable-tcp-nodelay no
|
||||
# partial resync is enough, just passing the portion of data the replica
|
||||
# missed while disconnected.
|
||||
#
|
||||
# The bigger the replication backlog, the longer the time the replica can be
|
||||
# disconnected and later be able to perform a partial resynchronization.
|
||||
# The bigger the replication backlog, the longer the replica can endure the
|
||||
# disconnect and later be able to perform a partial resynchronization.
|
||||
#
|
||||
# The backlog is only allocated once there is at least a replica connected.
|
||||
# The backlog is only allocated if there is at least one replica connected.
|
||||
#
|
||||
# repl-backlog-size 1mb
|
||||
|
||||
# After a master has no longer connected replicas for some time, the backlog
|
||||
# will be freed. The following option configures the amount of seconds that
|
||||
# need to elapse, starting from the time the last replica disconnected, for
|
||||
# the backlog buffer to be freed.
|
||||
# After a master has no connected replicas for some time, the backlog will be
|
||||
# freed. The following option configures the amount of seconds that need to
|
||||
# elapse, starting from the time the last replica disconnected, for the backlog
|
||||
# buffer to be freed.
|
||||
#
|
||||
# Note that replicas never free the backlog for timeout, since they may be
|
||||
# promoted to masters later, and should be able to correctly "partially
|
||||
# resynchronize" with the replicas: hence they should always accumulate backlog.
|
||||
# resynchronize" with other replicas: hence they should always accumulate backlog.
|
||||
#
|
||||
# A value of 0 means to never release the backlog.
|
||||
#
|
||||
@@ -574,8 +607,8 @@ replica-priority 100
|
||||
# Another place where this info is available is in the output of the
|
||||
# "ROLE" command of a master.
|
||||
#
|
||||
# The listed IP and address normally reported by a replica is obtained
|
||||
# in the following way:
|
||||
# The listed IP address and port normally reported by a replica is
|
||||
# obtained in the following way:
|
||||
#
|
||||
# IP: The address is auto detected by checking the peer address
|
||||
# of the socket used by the replica to connect with the master.
|
||||
@@ -585,7 +618,7 @@ replica-priority 100
|
||||
# listen for connections.
|
||||
#
|
||||
# However when port forwarding or Network Address Translation (NAT) is
|
||||
# used, the replica may be actually reachable via different IP and port
|
||||
# used, the replica may actually be reachable via different IP and port
|
||||
# pairs. The following two options can be used by a replica in order to
|
||||
# report to its master a specific set of IP and port, so that both INFO
|
||||
# and ROLE will report those values.
|
||||
@@ -602,7 +635,7 @@ replica-priority 100
|
||||
# This is implemented using an invalidation table that remembers, using
|
||||
# 16 millions of slots, what clients may have certain subsets of keys. In turn
|
||||
# this is used in order to send invalidation messages to clients. Please
|
||||
# to understand more about the feature check this page:
|
||||
# check this page to understand more about the feature:
|
||||
#
|
||||
# https://redis.io/topics/client-side-caching
|
||||
#
|
||||
@@ -614,24 +647,27 @@ replica-priority 100
|
||||
# to track the keys fetched by many clients.
|
||||
#
|
||||
# For this reason it is possible to configure a maximum fill value for the
|
||||
# invalidation table. By default it is set to 10%, and once this limit is
|
||||
# reached, Redis will start to evict caching slots in the invalidation table
|
||||
# even if keys are not modified, just to reclaim memory: this will in turn
|
||||
# invalidation table. By default it is set to 1M of keys, and once this limit
|
||||
# is reached, Redis will start to evict keys in the invalidation table
|
||||
# even if they were not modified, just to reclaim memory: this will in turn
|
||||
# force the clients to invalidate the cached values. Basically the table
|
||||
# maximum fill rate is a trade off between the memory you want to spend server
|
||||
# maximum size is a trade off between the memory you want to spend server
|
||||
# side to track information about who cached what, and the ability of clients
|
||||
# to retain cached objects in memory.
|
||||
#
|
||||
# If you set the value to 0, it means there are no limits, and all the 16
|
||||
# millions of caching slots can be used at the same time. In the "stats"
|
||||
# INFO section, you can find information about the amount of caching slots
|
||||
# used at every given moment.
|
||||
# If you set the value to 0, it means there are no limits, and Redis will
|
||||
# retain as many keys as needed in the invalidation table.
|
||||
# In the "stats" INFO section, you can find information about the number of
|
||||
# keys in the invalidation table at every given moment.
|
||||
#
|
||||
# tracking-table-max-fill 10
|
||||
# Note: when key tracking is used in broadcasting mode, no memory is used
|
||||
# in the server side so this setting is useless.
|
||||
#
|
||||
# tracking-table-max-keys 1000000
|
||||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
# Warning: since Redis is pretty fast an outside user can try up to
|
||||
# Warning: since Redis is pretty fast, an outside user can try up to
|
||||
# 1 million passwords per second against a modern box. This means that you
|
||||
# should use very strong passwords, otherwise they will be very easy to break.
|
||||
# Note that because the password is really a shared secret between the client
|
||||
@@ -655,7 +691,7 @@ replica-priority 100
|
||||
# AUTH (or the HELLO command AUTH option) in order to be authenticated and
|
||||
# start to work.
|
||||
#
|
||||
# The ACL rules that describe what an user can do are the following:
|
||||
# The ACL rules that describe what a user can do are the following:
|
||||
#
|
||||
# on Enable the user: it is possible to authenticate as this user.
|
||||
# off Disable the user: it's no longer possible to authenticate
|
||||
@@ -683,7 +719,7 @@ replica-priority 100
|
||||
# It is possible to specify multiple patterns.
|
||||
# allkeys Alias for ~*
|
||||
# resetkeys Flush the list of allowed keys patterns.
|
||||
# ><password> Add this passowrd to the list of valid password for the user.
|
||||
# ><password> Add this password to the list of valid password for the user.
|
||||
# For example >mypass will add "mypass" to the list.
|
||||
# This directive clears the "nopass" flag (see later).
|
||||
# <<password> Remove this password from the list of valid passwords.
|
||||
@@ -725,11 +761,19 @@ replica-priority 100
|
||||
# For more information about ACL configuration please refer to
|
||||
# the Redis web site at https://redis.io/topics/acl
|
||||
|
||||
# ACL LOG
|
||||
#
|
||||
# The ACL Log tracks failed commands and authentication events associated
|
||||
# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked
|
||||
# by ACLs. The ACL Log is stored in memory. You can reclaim memory with
|
||||
# ACL LOG RESET. Define the maximum entry length of the ACL Log below.
|
||||
acllog-max-len 128
|
||||
|
||||
# Using an external ACL file
|
||||
#
|
||||
# Instead of configuring users here in this file, it is possible to use
|
||||
# a stand-alone file just listing users. The two methods cannot be mixed:
|
||||
# if you configure users here and at the same time you activate the exteranl
|
||||
# if you configure users here and at the same time you activate the external
|
||||
# ACL file, the server will refuse to start.
|
||||
#
|
||||
# The format of the external ACL user file is exactly the same as the
|
||||
@@ -737,7 +781,7 @@ replica-priority 100
|
||||
#
|
||||
# aclfile /etc/redis/users.acl
|
||||
|
||||
# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatiblity
|
||||
# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility
|
||||
# layer on top of the new ACL system. The option effect will be just setting
|
||||
# the password for the default user. Clients will still authenticate using
|
||||
# AUTH <password> as usually, or more explicitly with AUTH default <password>
|
||||
@@ -781,6 +825,11 @@ replica-priority 100
|
||||
# Once the limit is reached Redis will close all the new connections sending
|
||||
# an error 'max number of clients reached'.
|
||||
#
|
||||
# IMPORTANT: When Redis Cluster is used, the max number of connections is also
|
||||
# shared with the cluster bus: every node in the cluster will use two
|
||||
# connections, one incoming and another outgoing. It is important to size the
|
||||
# limit accordingly in case of very large clusters.
|
||||
#
|
||||
# maxclients 10000
|
||||
|
||||
############################## MEMORY MANAGEMENT ################################
|
||||
@@ -843,8 +892,8 @@ replica-priority 100
|
||||
|
||||
# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated
|
||||
# algorithms (in order to save memory), so you can tune it for speed or
|
||||
# accuracy. For default Redis will check five keys and pick the one that was
|
||||
# used less recently, you can change the sample size using the following
|
||||
# accuracy. By default Redis will check five keys and pick the one that was
|
||||
# used least recently, you can change the sample size using the following
|
||||
# configuration directive.
|
||||
#
|
||||
# The default of 5 produces good enough results. 10 Approximates very closely
|
||||
@@ -884,8 +933,8 @@ replica-priority 100
|
||||
# it is possible to increase the expire "effort" that is normally set to
|
||||
# "1", to a greater value, up to the value "10". At its maximum value the
|
||||
# system will use more CPU, longer cycles (and technically may introduce
|
||||
# more latency), and will tollerate less already expired keys still present
|
||||
# in the system. It's a tradeoff betweeen memory, CPU and latecy.
|
||||
# more latency), and will tolerate less already expired keys still present
|
||||
# in the system. It's a tradeoff between memory, CPU and latency.
|
||||
#
|
||||
# active-expire-effort 1
|
||||
|
||||
@@ -931,13 +980,97 @@ replica-priority 100
|
||||
# In all the above cases the default is to delete objects in a blocking way,
|
||||
# like if DEL was called. However you can configure each case specifically
|
||||
# in order to instead release memory in a non-blocking way like if UNLINK
|
||||
# was called, using the following configuration directives:
|
||||
# was called, using the following configuration directives.
|
||||
|
||||
lazyfree-lazy-eviction no
|
||||
lazyfree-lazy-expire no
|
||||
lazyfree-lazy-server-del no
|
||||
replica-lazy-flush no
|
||||
|
||||
# It is also possible, for the case when to replace the user code DEL calls
|
||||
# with UNLINK calls is not easy, to modify the default behavior of the DEL
|
||||
# command to act exactly like UNLINK, using the following configuration
|
||||
# directive:
|
||||
|
||||
lazyfree-lazy-user-del no
|
||||
|
||||
################################ THREADED I/O #################################
|
||||
|
||||
# Redis is mostly single threaded, however there are certain threaded
|
||||
# operations such as UNLINK, slow I/O accesses and other things that are
|
||||
# performed on side threads.
|
||||
#
|
||||
# Now it is also possible to handle Redis clients socket reads and writes
|
||||
# in different I/O threads. Since especially writing is so slow, normally
|
||||
# Redis users use pipelining in order to speed up the Redis performances per
|
||||
# core, and spawn multiple instances in order to scale more. Using I/O
|
||||
# threads it is possible to easily speedup two times Redis without resorting
|
||||
# to pipelining nor sharding of the instance.
|
||||
#
|
||||
# By default threading is disabled, we suggest enabling it only in machines
|
||||
# that have at least 4 or more cores, leaving at least one spare core.
|
||||
# Using more than 8 threads is unlikely to help much. We also recommend using
|
||||
# threaded I/O only if you actually have performance problems, with Redis
|
||||
# instances being able to use a quite big percentage of CPU time, otherwise
|
||||
# there is no point in using this feature.
|
||||
#
|
||||
# So for instance if you have a four cores boxes, try to use 2 or 3 I/O
|
||||
# threads, if you have a 8 cores, try to use 6 threads. In order to
|
||||
# enable I/O threads use the following configuration directive:
|
||||
#
|
||||
# io-threads 4
|
||||
#
|
||||
# Setting io-threads to 1 will just use the main thread as usual.
|
||||
# When I/O threads are enabled, we only use threads for writes, that is
|
||||
# to thread the write(2) syscall and transfer the client buffers to the
|
||||
# socket. However it is also possible to enable threading of reads and
|
||||
# protocol parsing using the following configuration directive, by setting
|
||||
# it to yes:
|
||||
#
|
||||
# io-threads-do-reads no
|
||||
#
|
||||
# Usually threading reads doesn't help much.
|
||||
#
|
||||
# NOTE 1: This configuration directive cannot be changed at runtime via
|
||||
# CONFIG SET. Aso this feature currently does not work when SSL is
|
||||
# enabled.
|
||||
#
|
||||
# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make
|
||||
# sure you also run the benchmark itself in threaded mode, using the
|
||||
# --threads option to match the number of Redis threads, otherwise you'll not
|
||||
# be able to notice the improvements.
|
||||
|
||||
############################ KERNEL OOM CONTROL ##############################
|
||||
|
||||
# On Linux, it is possible to hint the kernel OOM killer on what processes
|
||||
# should be killed first when out of memory.
|
||||
#
|
||||
# Enabling this feature makes Redis actively control the oom_score_adj value
|
||||
# for all its processes, depending on their role. The default scores will
|
||||
# attempt to have background child processes killed before all others, and
|
||||
# replicas killed before masters.
|
||||
#
|
||||
# Redis supports three options:
|
||||
#
|
||||
# no: Don't make changes to oom-score-adj (default).
|
||||
# yes: Alias to "relative" see below.
|
||||
# absolute: Values in oom-score-adj-values are written as is to the kernel.
|
||||
# relative: Values are used relative to the initial value of oom_score_adj when
|
||||
# the server starts and are then clamped to a range of -1000 to 1000.
|
||||
# Because typically the initial value is 0, they will often match the
|
||||
# absolute values.
|
||||
oom-score-adj no
|
||||
|
||||
# When oom-score-adj is used, this directive controls the specific values used
|
||||
# for master, replica and background child processes. Values range -2000 to
|
||||
# 2000 (higher means more likely to be killed).
|
||||
#
|
||||
# Unprivileged processes (not root, and without CAP_SYS_RESOURCE capabilities)
|
||||
# can freely increase their value, but not decrease it below its initial
|
||||
# settings. This means that setting oom-score-adj to "relative" and setting the
|
||||
# oom-score-adj-values to positive values will always succeed.
|
||||
oom-score-adj-values 0 200 800
|
||||
|
||||
############################## APPEND ONLY MODE ###############################
|
||||
|
||||
# By default Redis asynchronously dumps the dataset on disk. This mode is
|
||||
@@ -1062,8 +1195,8 @@ aof-load-truncated yes
|
||||
#
|
||||
# [RDB file][AOF tail]
|
||||
#
|
||||
# When loading Redis recognizes that the AOF file starts with the "REDIS"
|
||||
# string and loads the prefixed RDB file, and continues loading the AOF
|
||||
# When loading, Redis recognizes that the AOF file starts with the "REDIS"
|
||||
# string and loads the prefixed RDB file, then continues loading the AOF
|
||||
# tail.
|
||||
aof-use-rdb-preamble yes
|
||||
|
||||
@@ -1077,7 +1210,7 @@ aof-use-rdb-preamble yes
|
||||
#
|
||||
# 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
|
||||
# used to stop a script that did not yet call any write commands. The second
|
||||
# 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.
|
||||
@@ -1103,7 +1236,7 @@ lua-time-limit 5000
|
||||
|
||||
# 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.
|
||||
# Most other internal time limits are a multiple of the node timeout.
|
||||
#
|
||||
# cluster-node-timeout 15000
|
||||
|
||||
@@ -1130,18 +1263,18 @@ lua-time-limit 5000
|
||||
# the failover if, since the last interaction with the master, the time
|
||||
# elapsed is greater than:
|
||||
#
|
||||
# (node-timeout * replica-validity-factor) + repl-ping-replica-period
|
||||
# (node-timeout * cluster-replica-validity-factor) + repl-ping-replica-period
|
||||
#
|
||||
# So for example if node-timeout is 30 seconds, and the replica-validity-factor
|
||||
# So for example if node-timeout is 30 seconds, and the cluster-replica-validity-factor
|
||||
# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the
|
||||
# replica will not try to failover if it was not able to talk with the master
|
||||
# for longer than 310 seconds.
|
||||
#
|
||||
# A large replica-validity-factor may allow replicas with too old data to failover
|
||||
# A large cluster-replica-validity-factor may allow replicas with too old data to failover
|
||||
# a master, while a too small value may prevent the cluster from being able to
|
||||
# elect a replica at all.
|
||||
#
|
||||
# For maximum availability, it is possible to set the replica-validity-factor
|
||||
# For maximum availability, it is possible to set the cluster-replica-validity-factor
|
||||
# to a value of 0, which means, that replicas will always try to failover the
|
||||
# master regardless of the last time they interacted with the master.
|
||||
# (However they'll always try to apply a delay proportional to their
|
||||
@@ -1172,7 +1305,7 @@ 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).
|
||||
# is at least a 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.
|
||||
@@ -1227,7 +1360,7 @@ lua-time-limit 5000
|
||||
# * cluster-announce-port
|
||||
# * cluster-announce-bus-port
|
||||
#
|
||||
# Each instruct the node about its address, client port, and cluster message
|
||||
# Each instructs the node about its address, client port, and cluster message
|
||||
# bus port. The information is then published in the header of the bus packets
|
||||
# so that other nodes will be able to correctly map the address of the node
|
||||
# publishing the information.
|
||||
@@ -1238,7 +1371,7 @@ lua-time-limit 5000
|
||||
# Note that when remapped, the bus port may not be at the fixed offset of
|
||||
# clients port + 10000, so you can specify any port and bus-port depending
|
||||
# on how they get remapped. If the bus-port is not set, a fixed offset of
|
||||
# 10000 will be used as usually.
|
||||
# 10000 will be used as usual.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
@@ -1316,7 +1449,11 @@ latency-monitor-threshold 0
|
||||
# z Sorted set commands
|
||||
# x Expired events (events generated every time a key expires)
|
||||
# e Evicted events (events generated when a key is evicted for maxmemory)
|
||||
# A Alias for g$lshzxe, so that the "AKE" string means all the events.
|
||||
# t Stream commands
|
||||
# m Key-miss events (Note: It is not included in the 'A' class)
|
||||
# A Alias for g$lshzxet, so that the "AKE" string means all the events
|
||||
# (Except key-miss events which are excluded from 'A' due to their
|
||||
# unique nature).
|
||||
#
|
||||
# The "notify-keyspace-events" takes as argument a string that is composed
|
||||
# of zero or multiple characters. The empty string means that notifications
|
||||
@@ -1363,7 +1500,7 @@ notify-keyspace-events ""
|
||||
# two kind of inline requests that were anyway illegal: an empty request
|
||||
# or any request that starts with "/" (there are no Redis commands starting
|
||||
# with such a slash). Normal RESP2/RESP3 requests are completely out of the
|
||||
# path of the Gopher protocol implementation and are served as usually as well.
|
||||
# path of the Gopher protocol implementation and are served as usual as well.
|
||||
#
|
||||
# If you open a connection to Redis when Gopher is enabled and send it
|
||||
# a string like "/foo", if there is a key named "/foo" it is served via the
|
||||
@@ -1387,8 +1524,11 @@ notify-keyspace-events ""
|
||||
#
|
||||
# So use the 'requirepass' option to protect your instance.
|
||||
#
|
||||
# To enable Gopher support uncomment the following line and set
|
||||
# the option from no (the default) to yes.
|
||||
# Note that Gopher is not currently supported when 'io-threads-do-reads'
|
||||
# is enabled.
|
||||
#
|
||||
# To enable Gopher support, uncomment the following line and set the option
|
||||
# from no (the default) to yes.
|
||||
#
|
||||
# gopher-enabled no
|
||||
|
||||
@@ -1535,8 +1675,8 @@ client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
# client-query-buffer-limit 1gb
|
||||
|
||||
# In the Redis protocol, bulk requests, that are, elements representing single
|
||||
# strings, are normally limited ot 512 mb. However you can change this limit
|
||||
# here.
|
||||
# strings, are normally limited to 512 mb. However you can change this limit
|
||||
# here, but must be 1mb or greater
|
||||
#
|
||||
# proto-max-bulk-len 512mb
|
||||
|
||||
@@ -1564,9 +1704,9 @@ hz 10
|
||||
#
|
||||
# Since the default HZ value by default is conservatively set to 10, Redis
|
||||
# offers, and enables by default, the ability to use an adaptive HZ value
|
||||
# which will temporary raise when there are many connected clients.
|
||||
# which will temporarily raise when there are many connected clients.
|
||||
#
|
||||
# When dynamic HZ is enabled, the actual configured HZ will be used as
|
||||
# When dynamic HZ is enabled, the actual configured HZ will be used
|
||||
# as a baseline, but multiples of the configured HZ value will be actually
|
||||
# used as needed once more clients are connected. In this way an idle
|
||||
# instance will use very little CPU time while a busy instance will be
|
||||
@@ -1631,7 +1771,7 @@ rdb-save-incremental-fsync yes
|
||||
# for the key counter to be divided by two (or decremented if it has a value
|
||||
# less <= 10).
|
||||
#
|
||||
# The default value for the lfu-decay-time is 1. A Special value of 0 means to
|
||||
# The default value for the lfu-decay-time is 1. A special value of 0 means to
|
||||
# decay the counter every time it happens to be scanned.
|
||||
#
|
||||
# lfu-log-factor 10
|
||||
@@ -1651,7 +1791,7 @@ rdb-save-incremental-fsync yes
|
||||
# restart is needed in order to lower the fragmentation, or at least to flush
|
||||
# away all the data and create it again. However thanks to this feature
|
||||
# implemented by Oran Agra for Redis 4.0 this process can happen at runtime
|
||||
# in an "hot" way, while the server is running.
|
||||
# in a "hot" way, while the server is running.
|
||||
#
|
||||
# Basically when the fragmentation is over a certain level (see the
|
||||
# configuration options below) Redis will start to create new copies of the
|
||||
@@ -1700,3 +1840,38 @@ rdb-save-incremental-fsync yes
|
||||
# Maximum number of set/hash/zset/list fields that will be processed from
|
||||
# the main dictionary scan
|
||||
# active-defrag-max-scan-fields 1000
|
||||
|
||||
# Jemalloc background thread for purging will be enabled by default
|
||||
jemalloc-bg-thread yes
|
||||
|
||||
# It is possible to pin different threads and processes of Redis to specific
|
||||
# CPUs in your system, in order to maximize the performances of the server.
|
||||
# This is useful both in order to pin different Redis threads in different
|
||||
# CPUs, but also in order to make sure that multiple Redis instances running
|
||||
# in the same host will be pinned to different CPUs.
|
||||
#
|
||||
# Normally you can do this using the "taskset" command, however it is also
|
||||
# possible to this via Redis configuration directly, both in Linux and FreeBSD.
|
||||
#
|
||||
# You can pin the server/IO threads, bio threads, aof rewrite child process, and
|
||||
# the bgsave child process. The syntax to specify the cpu list is the same as
|
||||
# the taskset command:
|
||||
#
|
||||
# Set redis server/io threads to cpu affinity 0,2,4,6:
|
||||
# server_cpulist 0-7:2
|
||||
#
|
||||
# Set bio threads to cpu affinity 1,3:
|
||||
# bio_cpulist 1,3
|
||||
#
|
||||
# Set aof rewrite child process to cpu affinity 8,9,10,11:
|
||||
# aof_rewrite_cpulist 8-11
|
||||
#
|
||||
# Set bgsave child process to cpu affinity 1,10,11
|
||||
# bgsave_cpulist 1,10-11
|
||||
|
||||
# In some cases redis will emit warnings and even refuse to start if it detects
|
||||
# that the system is in bad state, it is possible to suppress these warnings
|
||||
# by setting the following config which takes a space delimited list of warnings
|
||||
# to suppress
|
||||
#
|
||||
# ignore-warnings ARM64-COW-BUG
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
|
||||
+5
-1
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
@@ -25,4 +25,8 @@ $TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/scan \
|
||||
--single unit/moduleapi/datatype \
|
||||
--single unit/moduleapi/auth \
|
||||
--single unit/moduleapi/keyspace_events \
|
||||
--single unit/moduleapi/blockedclient \
|
||||
--single unit/moduleapi/getkeys \
|
||||
--single unit/moduleapi/zset \
|
||||
"${@}"
|
||||
|
||||
+21
-1
@@ -102,6 +102,18 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
#
|
||||
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
|
||||
|
||||
# sentinel auth-user <master-name> <username>
|
||||
#
|
||||
# This is useful in order to authenticate to instances having ACL capabilities,
|
||||
# that is, running Redis 6.0 or greater. When just auth-pass is provided the
|
||||
# Sentinel instance will authenticate to Redis using the old "AUTH <pass>"
|
||||
# method. When also an username is provided, it will use "AUTH <user> <pass>".
|
||||
# In the Redis servers side, the ACL to provide just minimal access to
|
||||
# Sentinel instances, should be configured along the following lines:
|
||||
#
|
||||
# user sentinel-user >somepassword +client +subscribe +publish \
|
||||
# +ping +info +multi +slaveof +config +client +exec on
|
||||
|
||||
# sentinel down-after-milliseconds <master-name> <milliseconds>
|
||||
#
|
||||
# Number of milliseconds the master (or any attached replica or sentinel) should
|
||||
@@ -112,6 +124,14 @@ sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
# Default is 30 seconds.
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
|
||||
# requirepass <password>
|
||||
#
|
||||
# You can configure Sentinel itself to require a password, however when doing
|
||||
# so Sentinel will try to authenticate with the same password to all the
|
||||
# other Sentinels. So you need to configure all your Sentinels in a given
|
||||
# group with the same "requirepass" password. Check the following documentation
|
||||
# for more info: https://redis.io/topics/sentinel
|
||||
|
||||
# sentinel parallel-syncs <master-name> <numreplicas>
|
||||
#
|
||||
# How many replicas we can reconfigure to point to the new replica simultaneously
|
||||
@@ -239,6 +259,6 @@ sentinel deny-scripts-reconfig yes
|
||||
# SENTINEL SET can also be used in order to perform this configuration at runtime.
|
||||
#
|
||||
# In order to set a command back to its original name (undo the renaming), it
|
||||
# is possible to just rename a command to itsef:
|
||||
# is possible to just rename a command to itself:
|
||||
#
|
||||
# SENTINEL rename-command mymaster CONFIG CONFIG
|
||||
|
||||
+60
-15
@@ -121,13 +121,36 @@ ifeq ($(uname_S),OpenBSD)
|
||||
endif
|
||||
|
||||
else
|
||||
ifeq ($(uname_S),NetBSD)
|
||||
# NetBSD
|
||||
FINAL_LIBS+= -lpthread
|
||||
ifeq ($(USE_BACKTRACE),yes)
|
||||
FINAL_CFLAGS+= -DUSE_BACKTRACE -I/usr/pkg/include
|
||||
FINAL_LDFLAGS+= -L/usr/pkg/lib
|
||||
FINAL_LIBS+= -lexecinfo
|
||||
endif
|
||||
else
|
||||
ifeq ($(uname_S),FreeBSD)
|
||||
# FreeBSD
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
ifeq ($(uname_S),DragonFly)
|
||||
# FreeBSD
|
||||
# DragonFly
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
ifeq ($(uname_S),OpenBSD)
|
||||
# OpenBSD
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
ifeq ($(uname_S),NetBSD)
|
||||
# NetBSD
|
||||
FINAL_LIBS+= -lpthread -lexecinfo
|
||||
else
|
||||
ifeq ($(uname_S),Haiku)
|
||||
# Haiku
|
||||
FINAL_CFLAGS+= -DBSD_SOURCE
|
||||
FINAL_LDFLAGS+= -lbsd -lnetwork
|
||||
FINAL_LIBS+= -lpthread
|
||||
else
|
||||
# All the other OSes (notably Linux)
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
@@ -138,6 +161,10 @@ endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
|
||||
|
||||
@@ -182,9 +209,21 @@ ifeq ($(MALLOC),jemalloc)
|
||||
endif
|
||||
|
||||
ifeq ($(BUILD_TLS),yes)
|
||||
FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CFLAGS)
|
||||
FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
|
||||
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a -lssl -lcrypto
|
||||
FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CFLAGS)
|
||||
FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
|
||||
LIBSSL_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libssl && echo $$?)
|
||||
ifeq ($(LIBSSL_PKGCONFIG),0)
|
||||
LIBSSL_LIBS=$(shell $(PKG_CONFIG) --libs libssl)
|
||||
else
|
||||
LIBSSL_LIBS=-lssl
|
||||
endif
|
||||
LIBCRYPTO_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libcrypto && echo $$?)
|
||||
ifeq ($(LIBCRYPTO_PKGCONFIG),0)
|
||||
LIBCRYPTO_LIBS=$(shell $(PKG_CONFIG) --libs libcrypto)
|
||||
else
|
||||
LIBCRYPTO_LIBS=-lcrypto
|
||||
endif
|
||||
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS)
|
||||
endif
|
||||
|
||||
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
|
||||
@@ -204,15 +243,15 @@ QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(EN
|
||||
QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2;
|
||||
endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.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 redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.o
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o
|
||||
REDIS_CHECK_RDB_NAME=redis-check-rdb
|
||||
REDIS_CHECK_AOF_NAME=redis-check-aof
|
||||
REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)
|
||||
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.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 crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.o timeout.o setcpuaffinity.o mt19937-64.o
|
||||
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o crcspeed.o crc64.o siphash.o crc16.o mt19937-64.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o mt19937-64.o
|
||||
REDIS_CHECK_RDB_NAME=redis-check-rdb$(PROG_SUFFIX)
|
||||
REDIS_CHECK_AOF_NAME=redis-check-aof$(PROG_SUFFIX)
|
||||
|
||||
all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
@echo ""
|
||||
@@ -233,6 +272,8 @@ persist-settings: distclean
|
||||
echo WARN=$(WARN) >> .make-settings
|
||||
echo OPT=$(OPT) >> .make-settings
|
||||
echo MALLOC=$(MALLOC) >> .make-settings
|
||||
echo BUILD_TLS=$(BUILD_TLS) >> .make-settings
|
||||
echo USE_SYSTEMD=$(USE_SYSTEMD) >> .make-settings
|
||||
echo CFLAGS=$(CFLAGS) >> .make-settings
|
||||
echo LDFLAGS=$(LDFLAGS) >> .make-settings
|
||||
echo REDIS_CFLAGS=$(REDIS_CFLAGS) >> .make-settings
|
||||
@@ -280,17 +321,21 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
|
||||
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS)
|
||||
|
||||
dict-benchmark: dict.c zmalloc.c sds.c siphash.c
|
||||
dict-benchmark: dict.c zmalloc.c sds.c siphash.c mt19937-64.c
|
||||
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
|
||||
|
||||
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
|
||||
-include $(DEP)
|
||||
|
||||
# Because the jemalloc.h header is generated as a part of the jemalloc build,
|
||||
# building it should complete before building any other object. Instead of
|
||||
# depending on a single artifact, build all dependencies first.
|
||||
%.o: %.c .make-prerequisites
|
||||
$(REDIS_CC) -c $<
|
||||
$(REDIS_CC) -MMD -o $@ -c $<
|
||||
|
||||
clean:
|
||||
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 Makefile.dep dict-benchmark
|
||||
rm -f $(DEP)
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "server.h"
|
||||
#include "sha256.h"
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* =============================================================================
|
||||
* Global state for ACLs
|
||||
@@ -49,6 +50,8 @@ list *UsersToLoad; /* This is a list of users found in the configuration file
|
||||
array of SDS pointers: the first is the user name,
|
||||
all the remaining pointers are ACL rules in the same
|
||||
format as ACLSetUser(). */
|
||||
list *ACLLog; /* Our security log, the user is able to inspect that
|
||||
using the ACL LOG command .*/
|
||||
|
||||
struct ACLCategoryItem {
|
||||
const char *name;
|
||||
@@ -93,6 +96,7 @@ struct ACLUserFlag {
|
||||
void ACLResetSubcommandsForCommand(user *u, unsigned long id);
|
||||
void ACLResetSubcommands(user *u);
|
||||
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
|
||||
void ACLFreeLogEntry(void *le);
|
||||
|
||||
/* The length of the string representation of a hashed password. */
|
||||
#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
|
||||
@@ -162,10 +166,41 @@ sds ACLHashPassword(unsigned char *cleartext, size_t len) {
|
||||
return sdsnewlen(hex,HASH_PASSWORD_LEN);
|
||||
}
|
||||
|
||||
/* Given a hash and the hash length, returns C_OK if it is a valid password
|
||||
* hash, or C_ERR otherwise. */
|
||||
int ACLCheckPasswordHash(unsigned char *hash, int hashlen) {
|
||||
if (hashlen != HASH_PASSWORD_LEN) {
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Password hashes can only be characters that represent
|
||||
* hexadecimal values, which are numbers and lowercase
|
||||
* characters 'a' through 'f'. */
|
||||
for(int i = 0; i < HASH_PASSWORD_LEN; i++) {
|
||||
char c = hash[i];
|
||||
if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) {
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* Low level ACL API
|
||||
* ==========================================================================*/
|
||||
|
||||
/* Return 1 if the specified string contains spaces or null characters.
|
||||
* We do this for usernames and key patterns for simpler rewriting of
|
||||
* ACL rules, presentation on ACL list, and to avoid subtle security bugs
|
||||
* that may arise from parsing the rules in presence of escapes.
|
||||
* The function returns 0 if the string has no spaces. */
|
||||
int ACLStringHasSpaces(const char *s, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (isspace(s[i]) || s[i] == 0) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Given the category name the command returns the corresponding flag, or
|
||||
* zero if there is no match. */
|
||||
uint64_t ACLGetCommandCategoryFlagByName(const char *name) {
|
||||
@@ -183,12 +218,12 @@ int ACLListMatchSds(void *a, void *b) {
|
||||
return sdscmp(a,b) == 0;
|
||||
}
|
||||
|
||||
/* Method to free list elements from ACL users password/ptterns lists. */
|
||||
/* Method to free list elements from ACL users password/patterns lists. */
|
||||
void ACLListFreeSds(void *item) {
|
||||
sdsfree(item);
|
||||
}
|
||||
|
||||
/* Method to duplicate list elements from ACL users password/ptterns lists. */
|
||||
/* Method to duplicate list elements from ACL users password/patterns lists. */
|
||||
void *ACLListDupSds(void *item) {
|
||||
return sdsdup(item);
|
||||
}
|
||||
@@ -254,7 +289,7 @@ void ACLFreeUserAndKillClients(user *u) {
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = listNodeValue(ln);
|
||||
if (c->user == u) {
|
||||
/* We'll free the conenction asynchronously, so
|
||||
/* We'll free the connection asynchronously, so
|
||||
* in theory to set a different user is not needed.
|
||||
* However if there are bugs in Redis, soon or later
|
||||
* this may result in some security hole: it's much
|
||||
@@ -262,7 +297,13 @@ void ACLFreeUserAndKillClients(user *u) {
|
||||
* it in non authenticated mode. */
|
||||
c->user = DefaultUser;
|
||||
c->authenticated = 0;
|
||||
freeClientAsync(c);
|
||||
/* We will write replies to this client later, so we can't
|
||||
* close it directly even if async. */
|
||||
if (c == server.current_client) {
|
||||
c->flags |= CLIENT_CLOSE_AFTER_COMMAND;
|
||||
} else {
|
||||
freeClientAsync(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
ACLFreeUser(u);
|
||||
@@ -340,12 +381,13 @@ int ACLUserCanExecuteFutureCommands(user *u) {
|
||||
* to skip the command bit explicit test. */
|
||||
void ACLSetUserCommandBit(user *u, unsigned long id, int value) {
|
||||
uint64_t word, bit;
|
||||
if (value == 0) u->flags &= ~USER_FLAG_ALLCOMMANDS;
|
||||
if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR) return;
|
||||
if (value)
|
||||
if (value) {
|
||||
u->allowed_commands[word] |= bit;
|
||||
else
|
||||
} else {
|
||||
u->allowed_commands[word] &= ~bit;
|
||||
u->flags &= ~USER_FLAG_ALLCOMMANDS;
|
||||
}
|
||||
}
|
||||
|
||||
/* This is like ACLSetUserCommandBit(), but instead of setting the specified
|
||||
@@ -433,21 +475,68 @@ sds ACLDescribeUserCommandRules(user *u) {
|
||||
ACLSetUser(fakeuser,"-@all",-1);
|
||||
}
|
||||
|
||||
/* Try to add or subtract each category one after the other. Often a
|
||||
* single category will not perfectly match the set of commands into
|
||||
* it, so at the end we do a final pass adding/removing the single commands
|
||||
* needed to make the bitmap exactly match. */
|
||||
for (int j = 0; ACLCommandCategories[j].flag != 0; j++) {
|
||||
unsigned long on, off;
|
||||
ACLCountCategoryBitsForUser(u,&on,&off,ACLCommandCategories[j].name);
|
||||
if ((additive && on > off) || (!additive && off > on)) {
|
||||
sds op = sdsnewlen(additive ? "+@" : "-@", 2);
|
||||
op = sdscat(op,ACLCommandCategories[j].name);
|
||||
ACLSetUser(fakeuser,op,-1);
|
||||
rules = sdscatsds(rules,op);
|
||||
rules = sdscatlen(rules," ",1);
|
||||
sdsfree(op);
|
||||
/* Attempt to find a good approximation for categories and commands
|
||||
* based on the current bits used, by looping over the category list
|
||||
* and applying the best fit each time. Often a set of categories will not
|
||||
* perfectly match the set of commands into it, so at the end we do a
|
||||
* final pass adding/removing the single commands needed to make the bitmap
|
||||
* exactly match. A temp user is maintained to keep track of categories
|
||||
* already applied. */
|
||||
user tu = {0};
|
||||
user *tempuser = &tu;
|
||||
|
||||
/* Keep track of the categories that have been applied, to prevent
|
||||
* applying them twice. */
|
||||
char applied[sizeof(ACLCommandCategories)/sizeof(ACLCommandCategories[0])];
|
||||
memset(applied, 0, sizeof(applied));
|
||||
|
||||
memcpy(tempuser->allowed_commands,
|
||||
u->allowed_commands,
|
||||
sizeof(u->allowed_commands));
|
||||
while (1) {
|
||||
int best = -1;
|
||||
unsigned long mindiff = INT_MAX, maxsame = 0;
|
||||
for (int j = 0; ACLCommandCategories[j].flag != 0; j++) {
|
||||
if (applied[j]) continue;
|
||||
|
||||
unsigned long on, off, diff, same;
|
||||
ACLCountCategoryBitsForUser(tempuser,&on,&off,ACLCommandCategories[j].name);
|
||||
/* Check if the current category is the best this loop:
|
||||
* * It has more commands in common with the user than commands
|
||||
* that are different.
|
||||
* AND EITHER
|
||||
* * It has the fewest number of differences
|
||||
* than the best match we have found so far.
|
||||
* * OR it matches the fewest number of differences
|
||||
* that we've seen but it has more in common. */
|
||||
diff = additive ? off : on;
|
||||
same = additive ? on : off;
|
||||
if (same > diff &&
|
||||
((diff < mindiff) || (diff == mindiff && same > maxsame)))
|
||||
{
|
||||
best = j;
|
||||
mindiff = diff;
|
||||
maxsame = same;
|
||||
}
|
||||
}
|
||||
|
||||
/* We didn't find a match */
|
||||
if (best == -1) break;
|
||||
|
||||
sds op = sdsnewlen(additive ? "+@" : "-@", 2);
|
||||
op = sdscat(op,ACLCommandCategories[best].name);
|
||||
ACLSetUser(fakeuser,op,-1);
|
||||
|
||||
sds invop = sdsnewlen(additive ? "-@" : "+@", 2);
|
||||
invop = sdscat(invop,ACLCommandCategories[best].name);
|
||||
ACLSetUser(tempuser,invop,-1);
|
||||
|
||||
rules = sdscatsds(rules,op);
|
||||
rules = sdscatlen(rules," ",1);
|
||||
sdsfree(op);
|
||||
sdsfree(invop);
|
||||
|
||||
applied[best] = 1;
|
||||
}
|
||||
|
||||
/* Fix the final ACLs with single commands differences. */
|
||||
@@ -687,7 +776,8 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
||||
*
|
||||
* When an error is returned, errno is set to the following values:
|
||||
*
|
||||
* EINVAL: The specified opcode is not understood.
|
||||
* EINVAL: The specified opcode is not understood or the key pattern is
|
||||
* invalid (contains non allowed characters).
|
||||
* ENOENT: The command name or command category provided with + or - is not
|
||||
* known.
|
||||
* EBUSY: The subcommand you want to add is about a command that is currently
|
||||
@@ -695,10 +785,11 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
||||
* EEXIST: You are adding a key pattern after "*" was already added. This is
|
||||
* almost surely an error on the user side.
|
||||
* ENODEV: The password you are trying to remove from the user does not exist.
|
||||
* EBADMSG: The hash you are trying to add is not a valid hash.
|
||||
* EBADMSG: The hash you are trying to add is not a valid hash.
|
||||
*/
|
||||
int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
if (oplen == -1) oplen = strlen(op);
|
||||
if (oplen == 0) return C_OK; /* Empty string is a no-operation. */
|
||||
if (!strcasecmp(op,"on")) {
|
||||
u->flags |= USER_FLAG_ENABLED;
|
||||
u->flags &= ~USER_FLAG_DISABLED;
|
||||
@@ -736,22 +827,10 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
if (op[0] == '>') {
|
||||
newpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
|
||||
} else {
|
||||
if (oplen != HASH_PASSWORD_LEN + 1) {
|
||||
if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR) {
|
||||
errno = EBADMSG;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Password hashes can only be characters that represent
|
||||
* hexadecimal values, which are numbers and lowercase
|
||||
* characters 'a' through 'f'.
|
||||
*/
|
||||
for(int i = 1; i < HASH_PASSWORD_LEN + 1; i++) {
|
||||
char c = op[i];
|
||||
if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) {
|
||||
errno = EBADMSG;
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
newpass = sdsnewlen(op+1,oplen-1);
|
||||
}
|
||||
|
||||
@@ -767,7 +846,7 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
if (op[0] == '<') {
|
||||
delpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
|
||||
} else {
|
||||
if (oplen != HASH_PASSWORD_LEN + 1) {
|
||||
if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR) {
|
||||
errno = EBADMSG;
|
||||
return C_ERR;
|
||||
}
|
||||
@@ -786,6 +865,10 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
errno = EEXIST;
|
||||
return C_ERR;
|
||||
}
|
||||
if (ACLStringHasSpaces(op+1,oplen-1)) {
|
||||
errno = EINVAL;
|
||||
return C_ERR;
|
||||
}
|
||||
sds newpat = sdsnewlen(op+1,oplen-1);
|
||||
listNode *ln = listSearchKey(u->patterns,newpat);
|
||||
/* Avoid re-adding the same pattern multiple times. */
|
||||
@@ -817,7 +900,6 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
errno = ENOENT;
|
||||
return C_ERR;
|
||||
}
|
||||
unsigned long id = ACLGetCommandID(copy);
|
||||
|
||||
/* The subcommand cannot be empty, so things like DEBUG|
|
||||
* are syntax errors of course. */
|
||||
@@ -830,6 +912,7 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
/* The command should not be set right now in the command
|
||||
* bitmap, because adding a subcommand of a fully added
|
||||
* command is probably an error on the user side. */
|
||||
unsigned long id = ACLGetCommandID(copy);
|
||||
if (ACLGetUserCommandBit(u,id) == 1) {
|
||||
zfree(copy);
|
||||
errno = EBUSY;
|
||||
@@ -896,16 +979,6 @@ char *ACLSetUserStringError(void) {
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
/* Return the first password of the default user or NULL.
|
||||
* This function is needed for backward compatibility with the old
|
||||
* directive "requirepass" when Redis supported a single global
|
||||
* password. */
|
||||
sds ACLDefaultUserFirstPassword(void) {
|
||||
if (listLength(DefaultUser->passwords) == 0) return NULL;
|
||||
listNode *first = listFirst(DefaultUser->passwords);
|
||||
return listNodeValue(first);
|
||||
}
|
||||
|
||||
/* Initialize the default user, that will always exist for all the process
|
||||
* lifetime. */
|
||||
void ACLInitDefaultUser(void) {
|
||||
@@ -920,7 +993,9 @@ void ACLInitDefaultUser(void) {
|
||||
void ACLInit(void) {
|
||||
Users = raxNew();
|
||||
UsersToLoad = listCreate();
|
||||
ACLLog = listCreate();
|
||||
ACLInitDefaultUser();
|
||||
server.requirepass = NULL; /* Only used for backward compatibility. */
|
||||
}
|
||||
|
||||
/* Check the username and password pair and return C_OK if they are valid,
|
||||
@@ -978,6 +1053,7 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password) {
|
||||
moduleNotifyUserChanged(c);
|
||||
return C_OK;
|
||||
} else {
|
||||
addACLLogEntry(c,ACL_DENIED_AUTH,0,username->ptr);
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
@@ -1034,7 +1110,7 @@ user *ACLGetUserByName(const char *name, size_t namelen) {
|
||||
* command cannot be executed because the user is not allowed to run such
|
||||
* command, the second if the command is denied because the user is trying
|
||||
* to access keys that are not among the specified patterns. */
|
||||
int ACLCheckCommandPerm(client *c) {
|
||||
int ACLCheckCommandPerm(client *c, int *keyidxptr) {
|
||||
user *u = c->user;
|
||||
uint64_t id = c->cmd->id;
|
||||
|
||||
@@ -1073,8 +1149,9 @@ int ACLCheckCommandPerm(client *c) {
|
||||
if (!(c->user->flags & USER_FLAG_ALLKEYS) &&
|
||||
(c->cmd->getkeys_proc || c->cmd->firstkey))
|
||||
{
|
||||
int numkeys;
|
||||
int *keyidx = getKeysFromCommand(c->cmd,c->argv,c->argc,&numkeys);
|
||||
getKeysResult result = GETKEYS_RESULT_INIT;
|
||||
int numkeys = getKeysFromCommand(c->cmd,c->argv,c->argc,&result);
|
||||
int *keyidx = result.keys;
|
||||
for (int j = 0; j < numkeys; j++) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
@@ -1094,11 +1171,12 @@ int ACLCheckCommandPerm(client *c) {
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
getKeysFreeResult(keyidx);
|
||||
if (keyidxptr) *keyidxptr = keyidx[j];
|
||||
getKeysFreeResult(&result);
|
||||
return ACL_DENIED_KEY;
|
||||
}
|
||||
}
|
||||
getKeysFreeResult(keyidx);
|
||||
getKeysFreeResult(&result);
|
||||
}
|
||||
|
||||
/* If we survived all the above checks, the user can execute the
|
||||
@@ -1167,6 +1245,12 @@ int ACLLoadConfiguredUsers(void) {
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds *aclrules = listNodeValue(ln);
|
||||
sds username = aclrules[0];
|
||||
|
||||
if (ACLStringHasSpaces(aclrules[0],sdslen(aclrules[0]))) {
|
||||
serverLog(LL_WARNING,"Spaces not allowed in ACL usernames");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
user *u = ACLCreateUser(username,sdslen(username));
|
||||
if (!u) {
|
||||
u = ACLGetUserByName(username,sdslen(username));
|
||||
@@ -1268,7 +1352,7 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
if (lines[i][0] == '\0') continue;
|
||||
|
||||
/* Split into arguments */
|
||||
argv = sdssplitargs(lines[i],&argc);
|
||||
argv = sdssplitlen(lines[i],sdslen(lines[i])," ",1,&argc);
|
||||
if (argv == NULL) {
|
||||
errors = sdscatprintf(errors,
|
||||
"%s:%d: unbalanced quotes in acl line. ",
|
||||
@@ -1292,11 +1376,23 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Try to process the line using the fake user to validate iif
|
||||
* the rules are able to apply cleanly. */
|
||||
/* Spaces are not allowed in usernames. */
|
||||
if (ACLStringHasSpaces(argv[1],sdslen(argv[1]))) {
|
||||
errors = sdscatprintf(errors,
|
||||
"'%s:%d: username '%s' contains invalid characters. ",
|
||||
server.acl_filename, linenum, argv[1]);
|
||||
sdsfreesplitres(argv,argc);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Try to process the line using the fake user to validate if
|
||||
* the rules are able to apply cleanly. At this stage we also
|
||||
* trim trailing spaces, so that we don't have to handle that
|
||||
* in ACLSetUser(). */
|
||||
ACLSetUser(fakeuser,"reset",-1);
|
||||
int j;
|
||||
for (j = 2; j < argc; j++) {
|
||||
argv[j] = sdstrim(argv[j],"\t\r\n");
|
||||
if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) != C_OK) {
|
||||
char *errmsg = ACLSetUserStringError();
|
||||
errors = sdscatprintf(errors,
|
||||
@@ -1454,6 +1550,131 @@ void ACLLoadUsersAtStartup(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* ACL log
|
||||
* ==========================================================================*/
|
||||
|
||||
#define ACL_LOG_CTX_TOPLEVEL 0
|
||||
#define ACL_LOG_CTX_LUA 1
|
||||
#define ACL_LOG_CTX_MULTI 2
|
||||
#define ACL_LOG_GROUPING_MAX_TIME_DELTA 60000
|
||||
|
||||
/* This structure defines an entry inside the ACL log. */
|
||||
typedef struct ACLLogEntry {
|
||||
uint64_t count; /* Number of times this happened recently. */
|
||||
int reason; /* Reason for denying the command. ACL_DENIED_*. */
|
||||
int context; /* Toplevel, Lua or MULTI/EXEC? ACL_LOG_CTX_*. */
|
||||
sds object; /* The key name or command name. */
|
||||
sds username; /* User the client is authenticated with. */
|
||||
mstime_t ctime; /* Milliseconds time of last update to this entry. */
|
||||
sds cinfo; /* Client info (last client if updated). */
|
||||
} ACLLogEntry;
|
||||
|
||||
/* This function will check if ACL entries 'a' and 'b' are similar enough
|
||||
* that we should actually update the existing entry in our ACL log instead
|
||||
* of creating a new one. */
|
||||
int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) {
|
||||
if (a->reason != b->reason) return 0;
|
||||
if (a->context != b->context) return 0;
|
||||
mstime_t delta = a->ctime - b->ctime;
|
||||
if (delta < 0) delta = -delta;
|
||||
if (delta > ACL_LOG_GROUPING_MAX_TIME_DELTA) return 0;
|
||||
if (sdscmp(a->object,b->object) != 0) return 0;
|
||||
if (sdscmp(a->username,b->username) != 0) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Release an ACL log entry. */
|
||||
void ACLFreeLogEntry(void *leptr) {
|
||||
ACLLogEntry *le = leptr;
|
||||
sdsfree(le->object);
|
||||
sdsfree(le->username);
|
||||
sdsfree(le->cinfo);
|
||||
zfree(le);
|
||||
}
|
||||
|
||||
/* Adds a new entry in the ACL log, making sure to delete the old entry
|
||||
* if we reach the maximum length allowed for the log. This function attempts
|
||||
* to find similar entries in the current log in order to bump the counter of
|
||||
* the log entry instead of creating many entries for very similar ACL
|
||||
* rules issues.
|
||||
*
|
||||
* The keypos argument is only used when the reason is ACL_DENIED_KEY, since
|
||||
* it allows the function to log the key name that caused the problem.
|
||||
* Similarly the username is only passed when we failed to authenticate the
|
||||
* user with AUTH or HELLO, for the ACL_DENIED_AUTH reason. Otherwise
|
||||
* it will just be NULL.
|
||||
*/
|
||||
void addACLLogEntry(client *c, int reason, int keypos, sds username) {
|
||||
/* Create a new entry. */
|
||||
struct ACLLogEntry *le = zmalloc(sizeof(*le));
|
||||
le->count = 1;
|
||||
le->reason = reason;
|
||||
le->username = sdsdup(reason == ACL_DENIED_AUTH ? username : c->user->name);
|
||||
le->ctime = mstime();
|
||||
|
||||
switch(reason) {
|
||||
case ACL_DENIED_CMD: le->object = sdsnew(c->cmd->name); break;
|
||||
case ACL_DENIED_KEY: le->object = sdsnew(c->argv[keypos]->ptr); break;
|
||||
case ACL_DENIED_AUTH: le->object = sdsnew(c->argv[0]->ptr); break;
|
||||
default: le->object = sdsempty();
|
||||
}
|
||||
|
||||
client *realclient = c;
|
||||
if (realclient->flags & CLIENT_LUA) realclient = server.lua_caller;
|
||||
|
||||
le->cinfo = catClientInfoString(sdsempty(),realclient);
|
||||
if (c->flags & CLIENT_MULTI) {
|
||||
le->context = ACL_LOG_CTX_MULTI;
|
||||
} else if (c->flags & CLIENT_LUA) {
|
||||
le->context = ACL_LOG_CTX_LUA;
|
||||
} else {
|
||||
le->context = ACL_LOG_CTX_TOPLEVEL;
|
||||
}
|
||||
|
||||
/* Try to match this entry with past ones, to see if we can just
|
||||
* update an existing entry instead of creating a new one. */
|
||||
long toscan = 10; /* Do a limited work trying to find duplicated. */
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(ACLLog,&li);
|
||||
ACLLogEntry *match = NULL;
|
||||
while (toscan-- && (ln = listNext(&li)) != NULL) {
|
||||
ACLLogEntry *current = listNodeValue(ln);
|
||||
if (ACLLogMatchEntry(current,le)) {
|
||||
match = current;
|
||||
listDelNode(ACLLog,ln);
|
||||
listAddNodeHead(ACLLog,current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If there is a match update the entry, otherwise add it as a
|
||||
* new one. */
|
||||
if (match) {
|
||||
/* We update a few fields of the existing entry and bump the
|
||||
* counter of events for this entry. */
|
||||
sdsfree(match->cinfo);
|
||||
match->cinfo = le->cinfo;
|
||||
match->ctime = le->ctime;
|
||||
match->count++;
|
||||
|
||||
/* Release the old entry. */
|
||||
le->cinfo = NULL;
|
||||
ACLFreeLogEntry(le);
|
||||
} else {
|
||||
/* Add it to our list of entires. We'll have to trim the list
|
||||
* to its maximum size. */
|
||||
listAddNodeHead(ACLLog, le);
|
||||
while(listLength(ACLLog) > server.acllog_max_len) {
|
||||
listNode *ln = listLast(ACLLog);
|
||||
ACLLogEntry *le = listNodeValue(ln);
|
||||
ACLFreeLogEntry(le);
|
||||
listDelNode(ACLLog,ln);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* ACL related commands
|
||||
* ==========================================================================*/
|
||||
@@ -1461,19 +1682,28 @@ void ACLLoadUsersAtStartup(void) {
|
||||
/* ACL -- show and modify the configuration of ACL users.
|
||||
* ACL HELP
|
||||
* ACL LOAD
|
||||
* ACL SAVE
|
||||
* ACL LIST
|
||||
* ACL USERS
|
||||
* ACL CAT [<category>]
|
||||
* ACL SETUSER <username> ... acl rules ...
|
||||
* ACL DELUSER <username> [...]
|
||||
* ACL GETUSER <username>
|
||||
* ACL GENPASS
|
||||
* ACL GENPASS [<bits>]
|
||||
* ACL WHOAMI
|
||||
* ACL LOG [<count> | RESET]
|
||||
*/
|
||||
void aclCommand(client *c) {
|
||||
char *sub = c->argv[1]->ptr;
|
||||
if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
|
||||
sds username = c->argv[2]->ptr;
|
||||
/* Check username validity. */
|
||||
if (ACLStringHasSpaces(username,sdslen(username))) {
|
||||
addReplyErrorFormat(c,
|
||||
"Usernames can't contain spaces or null characters");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a temporary user to validate and stage all changes against
|
||||
* before applying to an existing user or creating a new user. If all
|
||||
* arguments are valid the user parameters will all be applied together.
|
||||
@@ -1651,22 +1881,105 @@ void aclCommand(client *c) {
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredArrayLen(c,dl,arraylen);
|
||||
} else if (!strcasecmp(sub,"genpass") && c->argc == 2) {
|
||||
char pass[32]; /* 128 bits of actual pseudo random data. */
|
||||
getRandomHexChars(pass,sizeof(pass));
|
||||
addReplyBulkCBuffer(c,pass,sizeof(pass));
|
||||
} else if (!strcasecmp(sub,"help")) {
|
||||
} else if (!strcasecmp(sub,"genpass") && (c->argc == 2 || c->argc == 3)) {
|
||||
#define GENPASS_MAX_BITS 4096
|
||||
char pass[GENPASS_MAX_BITS/8*2]; /* Hex representation. */
|
||||
long bits = 256; /* By default generate 256 bits passwords. */
|
||||
|
||||
if (c->argc == 3 && getLongFromObjectOrReply(c,c->argv[2],&bits,NULL)
|
||||
!= C_OK) return;
|
||||
|
||||
if (bits <= 0 || bits > GENPASS_MAX_BITS) {
|
||||
addReplyErrorFormat(c,
|
||||
"ACL GENPASS argument must be the number of "
|
||||
"bits for the output password, a positive number "
|
||||
"up to %d",GENPASS_MAX_BITS);
|
||||
return;
|
||||
}
|
||||
|
||||
long chars = (bits+3)/4; /* Round to number of characters to emit. */
|
||||
getRandomHexChars(pass,chars);
|
||||
addReplyBulkCBuffer(c,pass,chars);
|
||||
} else if (!strcasecmp(sub,"log") && (c->argc == 2 || c->argc ==3)) {
|
||||
long count = 10; /* Number of entries to emit by default. */
|
||||
|
||||
/* Parse the only argument that LOG may have: it could be either
|
||||
* the number of entries the user wants to display, or alternatively
|
||||
* the "RESET" command in order to flush the old entries. */
|
||||
if (c->argc == 3) {
|
||||
if (!strcasecmp(c->argv[2]->ptr,"reset")) {
|
||||
listSetFreeMethod(ACLLog,ACLFreeLogEntry);
|
||||
listEmpty(ACLLog);
|
||||
listSetFreeMethod(ACLLog,NULL);
|
||||
addReply(c,shared.ok);
|
||||
return;
|
||||
} else if (getLongFromObjectOrReply(c,c->argv[2],&count,NULL)
|
||||
!= C_OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (count < 0) count = 0;
|
||||
}
|
||||
|
||||
/* Fix the count according to the number of entries we got. */
|
||||
if ((size_t)count > listLength(ACLLog))
|
||||
count = listLength(ACLLog);
|
||||
|
||||
addReplyArrayLen(c,count);
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(ACLLog,&li);
|
||||
mstime_t now = mstime();
|
||||
while (count-- && (ln = listNext(&li)) != NULL) {
|
||||
ACLLogEntry *le = listNodeValue(ln);
|
||||
addReplyMapLen(c,7);
|
||||
addReplyBulkCString(c,"count");
|
||||
addReplyLongLong(c,le->count);
|
||||
|
||||
addReplyBulkCString(c,"reason");
|
||||
char *reasonstr;
|
||||
switch(le->reason) {
|
||||
case ACL_DENIED_CMD: reasonstr="command"; break;
|
||||
case ACL_DENIED_KEY: reasonstr="key"; break;
|
||||
case ACL_DENIED_AUTH: reasonstr="auth"; break;
|
||||
default: reasonstr="unknown";
|
||||
}
|
||||
addReplyBulkCString(c,reasonstr);
|
||||
|
||||
addReplyBulkCString(c,"context");
|
||||
char *ctxstr;
|
||||
switch(le->context) {
|
||||
case ACL_LOG_CTX_TOPLEVEL: ctxstr="toplevel"; break;
|
||||
case ACL_LOG_CTX_MULTI: ctxstr="multi"; break;
|
||||
case ACL_LOG_CTX_LUA: ctxstr="lua"; break;
|
||||
default: ctxstr="unknown";
|
||||
}
|
||||
addReplyBulkCString(c,ctxstr);
|
||||
|
||||
addReplyBulkCString(c,"object");
|
||||
addReplyBulkCBuffer(c,le->object,sdslen(le->object));
|
||||
addReplyBulkCString(c,"username");
|
||||
addReplyBulkCBuffer(c,le->username,sdslen(le->username));
|
||||
addReplyBulkCString(c,"age-seconds");
|
||||
double age = (double)(now - le->ctime)/1000;
|
||||
addReplyDouble(c,age);
|
||||
addReplyBulkCString(c,"client-info");
|
||||
addReplyBulkCBuffer(c,le->cinfo,sdslen(le->cinfo));
|
||||
}
|
||||
} else if (c->argc == 2 && !strcasecmp(sub,"help")) {
|
||||
const char *help[] = {
|
||||
"LOAD -- Reload users from the ACL file.",
|
||||
"LIST -- Show user details in config file format.",
|
||||
"USERS -- List all the registered usernames.",
|
||||
"SETUSER <username> [attribs ...] -- Create or modify a user.",
|
||||
"GETUSER <username> -- Get the user details.",
|
||||
"DELUSER <username> [...] -- Delete a list of users.",
|
||||
"CAT -- List available categories.",
|
||||
"CAT <category> -- List commands inside category.",
|
||||
"GENPASS -- Generate a secure user password.",
|
||||
"WHOAMI -- Return the current connection username.",
|
||||
"LOAD -- Reload users from the ACL file.",
|
||||
"SAVE -- Save the current config to the ACL file.",
|
||||
"LIST -- Show user details in config file format.",
|
||||
"USERS -- List all the registered usernames.",
|
||||
"SETUSER <username> [attribs ...] -- Create or modify a user.",
|
||||
"GETUSER <username> -- Get the user details.",
|
||||
"DELUSER <username> [...] -- Delete a list of users.",
|
||||
"CAT -- List available categories.",
|
||||
"CAT <category> -- List commands inside category.",
|
||||
"GENPASS [<bits>] -- Generate a secure user password.",
|
||||
"WHOAMI -- Return the current connection username.",
|
||||
"LOG [<count> | RESET] -- Show the ACL log entries.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c,help);
|
||||
|
||||
+22
-7
@@ -34,8 +34,9 @@
|
||||
#include "zmalloc.h"
|
||||
|
||||
/* Create a new list. The created list can be freed with
|
||||
* AlFreeList(), but private value of every node need to be freed
|
||||
* by the user before to call AlFreeList().
|
||||
* listRelease(), but private value of every node need to be freed
|
||||
* by the user before to call listRelease(), or by setting a free method using
|
||||
* listSetFreeMethod.
|
||||
*
|
||||
* On error, NULL is returned. Otherwise the pointer to the new list. */
|
||||
list *listCreate(void)
|
||||
@@ -217,8 +218,8 @@ void listRewindTail(list *list, listIter *li) {
|
||||
* listDelNode(), but not to remove other elements.
|
||||
*
|
||||
* The function returns a pointer to the next element of the list,
|
||||
* or NULL if there are no more elements, so the classical usage patter
|
||||
* is:
|
||||
* or NULL if there are no more elements, so the classical usage
|
||||
* pattern is:
|
||||
*
|
||||
* iter = listGetIterator(list,<direction>);
|
||||
* while ((node = listNext(iter)) != NULL) {
|
||||
@@ -327,12 +328,11 @@ listNode *listIndex(list *list, long index) {
|
||||
}
|
||||
|
||||
/* Rotate the list removing the tail node and inserting it to the head. */
|
||||
void listRotate(list *list) {
|
||||
listNode *tail = list->tail;
|
||||
|
||||
void listRotateTailToHead(list *list) {
|
||||
if (listLength(list) <= 1) return;
|
||||
|
||||
/* Detach current tail */
|
||||
listNode *tail = list->tail;
|
||||
list->tail = tail->prev;
|
||||
list->tail->next = NULL;
|
||||
/* Move it as head */
|
||||
@@ -342,6 +342,21 @@ void listRotate(list *list) {
|
||||
list->head = tail;
|
||||
}
|
||||
|
||||
/* Rotate the list removing the head node and inserting it to the tail. */
|
||||
void listRotateHeadToTail(list *list) {
|
||||
if (listLength(list) <= 1) return;
|
||||
|
||||
listNode *head = list->head;
|
||||
/* Detach current head */
|
||||
list->head = head->next;
|
||||
list->head->prev = NULL;
|
||||
/* Move it as tail */
|
||||
list->tail->next = head;
|
||||
head->next = NULL;
|
||||
head->prev = list->tail;
|
||||
list->tail = head;
|
||||
}
|
||||
|
||||
/* Add all the elements of the list 'o' at the end of the
|
||||
* list 'l'. The list 'other' remains empty but otherwise valid. */
|
||||
void listJoin(list *l, list *o) {
|
||||
|
||||
+2
-1
@@ -85,7 +85,8 @@ listNode *listSearchKey(list *list, void *key);
|
||||
listNode *listIndex(list *list, long index);
|
||||
void listRewind(list *list, listIter *li);
|
||||
void listRewindTail(list *list, listIter *li);
|
||||
void listRotate(list *list);
|
||||
void listRotateTailToHead(list *list);
|
||||
void listRotateHeadToTail(list *list);
|
||||
void listJoin(list *l, list *o);
|
||||
|
||||
/* Directions for iterators */
|
||||
|
||||
@@ -135,6 +135,14 @@ void aeDeleteEventLoop(aeEventLoop *eventLoop) {
|
||||
aeApiFree(eventLoop);
|
||||
zfree(eventLoop->events);
|
||||
zfree(eventLoop->fired);
|
||||
|
||||
/* Free the time events list. */
|
||||
aeTimeEvent *next_te, *te = eventLoop->timeEventHead;
|
||||
while (te) {
|
||||
next_te = te->next;
|
||||
zfree(te);
|
||||
te = next_te;
|
||||
}
|
||||
zfree(eventLoop);
|
||||
}
|
||||
|
||||
@@ -230,6 +238,7 @@ long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
|
||||
te->clientData = clientData;
|
||||
te->prev = NULL;
|
||||
te->next = eventLoop->timeEventHead;
|
||||
te->refcount = 0;
|
||||
if (te->next)
|
||||
te->next->prev = te;
|
||||
eventLoop->timeEventHead = te;
|
||||
@@ -308,6 +317,13 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
/* Remove events scheduled for deletion. */
|
||||
if (te->id == AE_DELETED_EVENT_ID) {
|
||||
aeTimeEvent *next = te->next;
|
||||
/* If a reference exists for this timer event,
|
||||
* don't free it. This is currently incremented
|
||||
* for recursive timerProc calls */
|
||||
if (te->refcount) {
|
||||
te = next;
|
||||
continue;
|
||||
}
|
||||
if (te->prev)
|
||||
te->prev->next = te->next;
|
||||
else
|
||||
@@ -337,7 +353,9 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
int retval;
|
||||
|
||||
id = te->id;
|
||||
te->refcount++;
|
||||
retval = te->timeProc(eventLoop, id, te->clientData);
|
||||
te->refcount--;
|
||||
processed++;
|
||||
if (retval != AE_NOMORE) {
|
||||
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
|
||||
@@ -362,6 +380,7 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
* if flags has AE_DONT_WAIT set the function returns ASAP until all
|
||||
* the events that's possible to process without to wait are processed.
|
||||
* if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
|
||||
* if flags has AE_CALL_BEFORE_SLEEP set, the beforesleep callback is called.
|
||||
*
|
||||
* The function returns the number of events processed. */
|
||||
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
@@ -420,6 +439,9 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
tvp = &tv;
|
||||
}
|
||||
|
||||
if (eventLoop->beforesleep != NULL && flags & AE_CALL_BEFORE_SLEEP)
|
||||
eventLoop->beforesleep(eventLoop);
|
||||
|
||||
/* Call the multiplexing API, will return only on timeout or when
|
||||
* some event fires. */
|
||||
numevents = aeApiPoll(eventLoop, tvp);
|
||||
@@ -435,7 +457,7 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
int fired = 0; /* Number of events fired for current fd. */
|
||||
|
||||
/* Normally we execute the readable event first, and the writable
|
||||
* event laster. This is useful as sometimes we may be able
|
||||
* event later. This is useful as sometimes we may be able
|
||||
* to serve the reply of a query immediately after processing the
|
||||
* query.
|
||||
*
|
||||
@@ -443,7 +465,7 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
* asking us to do the reverse: never fire the writable event
|
||||
* after the readable. In such a case, we invert the calls.
|
||||
* This is useful when, for instance, we want to do things
|
||||
* in the beforeSleep() hook, like fsynching a file to disk,
|
||||
* in the beforeSleep() hook, like fsyncing a file to disk,
|
||||
* before replying to a client. */
|
||||
int invert = fe->mask & AE_BARRIER;
|
||||
|
||||
@@ -456,6 +478,7 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
if (!invert && fe->mask & mask & AE_READABLE) {
|
||||
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
|
||||
fired++;
|
||||
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
|
||||
}
|
||||
|
||||
/* Fire the writable event. */
|
||||
@@ -468,8 +491,11 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
|
||||
/* If we have to invert the call, fire the readable event now
|
||||
* after the writable one. */
|
||||
if (invert && fe->mask & mask & AE_READABLE) {
|
||||
if (!fired || fe->wfileProc != fe->rfileProc) {
|
||||
if (invert) {
|
||||
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
|
||||
if ((fe->mask & mask & AE_READABLE) &&
|
||||
(!fired || fe->wfileProc != fe->rfileProc))
|
||||
{
|
||||
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
|
||||
fired++;
|
||||
}
|
||||
@@ -510,9 +536,9 @@ int aeWait(int fd, int mask, long long milliseconds) {
|
||||
void aeMain(aeEventLoop *eventLoop) {
|
||||
eventLoop->stop = 0;
|
||||
while (!eventLoop->stop) {
|
||||
if (eventLoop->beforesleep != NULL)
|
||||
eventLoop->beforesleep(eventLoop);
|
||||
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
|
||||
aeProcessEvents(eventLoop, AE_ALL_EVENTS|
|
||||
AE_CALL_BEFORE_SLEEP|
|
||||
AE_CALL_AFTER_SLEEP);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,11 +47,12 @@
|
||||
things to disk before sending replies, and want
|
||||
to do that in a group fashion. */
|
||||
|
||||
#define AE_FILE_EVENTS 1
|
||||
#define AE_TIME_EVENTS 2
|
||||
#define AE_FILE_EVENTS (1<<0)
|
||||
#define AE_TIME_EVENTS (1<<1)
|
||||
#define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS)
|
||||
#define AE_DONT_WAIT 4
|
||||
#define AE_CALL_AFTER_SLEEP 8
|
||||
#define AE_DONT_WAIT (1<<2)
|
||||
#define AE_CALL_BEFORE_SLEEP (1<<3)
|
||||
#define AE_CALL_AFTER_SLEEP (1<<4)
|
||||
|
||||
#define AE_NOMORE -1
|
||||
#define AE_DELETED_EVENT_ID -1
|
||||
@@ -85,6 +86,8 @@ typedef struct aeTimeEvent {
|
||||
void *clientData;
|
||||
struct aeTimeEvent *prev;
|
||||
struct aeTimeEvent *next;
|
||||
int refcount; /* refcount to prevent timer events from being
|
||||
* freed in recursive time event calls. */
|
||||
} aeTimeEvent;
|
||||
|
||||
/* A fired event */
|
||||
|
||||
+1
-1
@@ -232,7 +232,7 @@ static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) {
|
||||
/*
|
||||
* ENOMEM is a potentially transient condition, but the kernel won't
|
||||
* generally return it unless things are really bad. EAGAIN indicates
|
||||
* we've reached an resource limit, for which it doesn't make sense to
|
||||
* we've reached a resource limit, for which it doesn't make sense to
|
||||
* retry (counter-intuitively). All other errors indicate a bug. In any
|
||||
* of these cases, the best we can do is to abort.
|
||||
*/
|
||||
|
||||
+55
-7
@@ -36,8 +36,29 @@
|
||||
typedef struct aeApiState {
|
||||
int kqfd;
|
||||
struct kevent *events;
|
||||
|
||||
/* Events mask for merge read and write event.
|
||||
* To reduce memory consumption, we use 2 bits to store the mask
|
||||
* of an event, so that 1 byte will store the mask of 4 events. */
|
||||
char *eventsMask;
|
||||
} aeApiState;
|
||||
|
||||
#define EVENT_MASK_MALLOC_SIZE(sz) (((sz) + 3) / 4)
|
||||
#define EVENT_MASK_OFFSET(fd) ((fd) % 4 * 2)
|
||||
#define EVENT_MASK_ENCODE(fd, mask) (((mask) & 0x3) << EVENT_MASK_OFFSET(fd))
|
||||
|
||||
static inline int getEventMask(const char *eventsMask, int fd) {
|
||||
return (eventsMask[fd/4] >> EVENT_MASK_OFFSET(fd)) & 0x3;
|
||||
}
|
||||
|
||||
static inline void addEventMask(char *eventsMask, int fd, int mask) {
|
||||
eventsMask[fd/4] |= EVENT_MASK_ENCODE(fd, mask);
|
||||
}
|
||||
|
||||
static inline void resetEventMask(char *eventsMask, int fd) {
|
||||
eventsMask[fd/4] &= ~EVENT_MASK_ENCODE(fd, 0x3);
|
||||
}
|
||||
|
||||
static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
aeApiState *state = zmalloc(sizeof(aeApiState));
|
||||
|
||||
@@ -53,6 +74,8 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
zfree(state);
|
||||
return -1;
|
||||
}
|
||||
state->eventsMask = zmalloc(EVENT_MASK_MALLOC_SIZE(eventLoop->setsize));
|
||||
memset(state->eventsMask, 0, EVENT_MASK_MALLOC_SIZE(eventLoop->setsize));
|
||||
eventLoop->apidata = state;
|
||||
return 0;
|
||||
}
|
||||
@@ -61,6 +84,8 @@ static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
|
||||
aeApiState *state = eventLoop->apidata;
|
||||
|
||||
state->events = zrealloc(state->events, sizeof(struct kevent)*setsize);
|
||||
state->eventsMask = zrealloc(state->eventsMask, EVENT_MASK_MALLOC_SIZE(setsize));
|
||||
memset(state->eventsMask, 0, EVENT_MASK_MALLOC_SIZE(setsize));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -69,6 +94,7 @@ static void aeApiFree(aeEventLoop *eventLoop) {
|
||||
|
||||
close(state->kqfd);
|
||||
zfree(state->events);
|
||||
zfree(state->eventsMask);
|
||||
zfree(state);
|
||||
}
|
||||
|
||||
@@ -119,15 +145,37 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
||||
if (retval > 0) {
|
||||
int j;
|
||||
|
||||
numevents = retval;
|
||||
for(j = 0; j < numevents; j++) {
|
||||
int mask = 0;
|
||||
/* Normally we execute the read event first and then the write event.
|
||||
* When the barrier is set, we will do it reverse.
|
||||
*
|
||||
* However, under kqueue, read and write events would be separate
|
||||
* events, which would make it impossible to control the order of
|
||||
* reads and writes. So we store the event's mask we've got and merge
|
||||
* the same fd events later. */
|
||||
for (j = 0; j < retval; j++) {
|
||||
struct kevent *e = state->events+j;
|
||||
int fd = e->ident;
|
||||
int mask = 0;
|
||||
|
||||
if (e->filter == EVFILT_READ) mask |= AE_READABLE;
|
||||
if (e->filter == EVFILT_WRITE) mask |= AE_WRITABLE;
|
||||
eventLoop->fired[j].fd = e->ident;
|
||||
eventLoop->fired[j].mask = mask;
|
||||
if (e->filter == EVFILT_READ) mask = AE_READABLE;
|
||||
else if (e->filter == EVFILT_WRITE) mask = AE_WRITABLE;
|
||||
addEventMask(state->eventsMask, fd, mask);
|
||||
}
|
||||
|
||||
/* Re-traversal to merge read and write events, and set the fd's mask to
|
||||
* 0 so that events are not added again when the fd is encountered again. */
|
||||
numevents = 0;
|
||||
for (j = 0; j < retval; j++) {
|
||||
struct kevent *e = state->events+j;
|
||||
int fd = e->ident;
|
||||
int mask = getEventMask(state->eventsMask, fd);
|
||||
|
||||
if (mask) {
|
||||
eventLoop->fired[numevents].fd = fd;
|
||||
eventLoop->fired[numevents].mask = mask;
|
||||
resetEventMask(state->eventsMask, fd);
|
||||
numevents++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return numevents;
|
||||
|
||||
@@ -162,7 +162,9 @@ void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
|
||||
|
||||
/* Install a file event to send data to the rewrite child if there is
|
||||
* not one already. */
|
||||
if (aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0) {
|
||||
if (!server.aof_stop_sending_diff &&
|
||||
aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0)
|
||||
{
|
||||
aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child,
|
||||
AE_WRITABLE, aofChildWriteDiffData, NULL);
|
||||
}
|
||||
@@ -242,6 +244,7 @@ void stopAppendOnly(void) {
|
||||
server.aof_fd = -1;
|
||||
server.aof_selected_db = -1;
|
||||
server.aof_state = AOF_OFF;
|
||||
server.aof_rewrite_scheduled = 0;
|
||||
killAppendOnlyChild();
|
||||
}
|
||||
|
||||
@@ -543,7 +546,7 @@ sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) {
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* Create the sds representation of an PEXPIREAT command, using
|
||||
/* Create the sds representation of a PEXPIREAT command, using
|
||||
* 'seconds' as time to live and 'cmd' to understand what command
|
||||
* we are translating into a PEXPIREAT.
|
||||
*
|
||||
@@ -610,19 +613,24 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
||||
} else if (cmd->proc == setCommand && argc > 3) {
|
||||
int i;
|
||||
robj *exarg = NULL, *pxarg = NULL;
|
||||
/* Translate SET [EX seconds][PX milliseconds] to SET and PEXPIREAT */
|
||||
buf = catAppendOnlyGenericCommand(buf,3,argv);
|
||||
for (i = 3; i < argc; i ++) {
|
||||
if (!strcasecmp(argv[i]->ptr, "ex")) exarg = argv[i+1];
|
||||
if (!strcasecmp(argv[i]->ptr, "px")) pxarg = argv[i+1];
|
||||
}
|
||||
serverAssert(!(exarg && pxarg));
|
||||
if (exarg)
|
||||
buf = catAppendOnlyExpireAtCommand(buf,server.expireCommand,argv[1],
|
||||
exarg);
|
||||
if (pxarg)
|
||||
buf = catAppendOnlyExpireAtCommand(buf,server.pexpireCommand,argv[1],
|
||||
pxarg);
|
||||
|
||||
if (exarg || pxarg) {
|
||||
/* Translate SET [EX seconds][PX milliseconds] to SET and PEXPIREAT */
|
||||
buf = catAppendOnlyGenericCommand(buf,3,argv);
|
||||
if (exarg)
|
||||
buf = catAppendOnlyExpireAtCommand(buf,server.expireCommand,argv[1],
|
||||
exarg);
|
||||
if (pxarg)
|
||||
buf = catAppendOnlyExpireAtCommand(buf,server.pexpireCommand,argv[1],
|
||||
pxarg);
|
||||
} else {
|
||||
buf = catAppendOnlyGenericCommand(buf,argc,argv);
|
||||
}
|
||||
} else {
|
||||
/* All the other commands don't need translation or need the
|
||||
* same translation already operated in the command vector
|
||||
@@ -663,6 +671,7 @@ struct client *createAOFClient(void) {
|
||||
c->querybuf_peak = 0;
|
||||
c->argc = 0;
|
||||
c->argv = NULL;
|
||||
c->argv_len_sum = 0;
|
||||
c->bufpos = 0;
|
||||
c->flags = 0;
|
||||
c->btype = BLOCKED_NONE;
|
||||
@@ -688,6 +697,7 @@ void freeFakeClientArgv(struct client *c) {
|
||||
for (j = 0; j < c->argc; j++)
|
||||
decrRefCount(c->argv[j]);
|
||||
zfree(c->argv);
|
||||
c->argv_len_sum = 0;
|
||||
}
|
||||
|
||||
void freeFakeClient(struct client *c) {
|
||||
@@ -781,18 +791,26 @@ int loadAppendOnlyFile(char *filename) {
|
||||
argc = atoi(buf+1);
|
||||
if (argc < 1) goto fmterr;
|
||||
|
||||
/* Load the next command in the AOF as our fake client
|
||||
* argv. */
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
fakeClient->argc = argc;
|
||||
fakeClient->argv = argv;
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
/* Parse the argument len. */
|
||||
char *readres = fgets(buf,sizeof(buf),fp);
|
||||
if (readres == NULL || buf[0] != '$') {
|
||||
fakeClient->argc = j; /* Free up to j-1. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr;
|
||||
if (readres == NULL)
|
||||
goto readerr;
|
||||
else
|
||||
goto fmterr;
|
||||
}
|
||||
if (buf[0] != '$') goto fmterr;
|
||||
len = strtol(buf+1,NULL,10);
|
||||
|
||||
/* Read it into a string object. */
|
||||
argsds = sdsnewlen(SDS_NOINIT,len);
|
||||
if (len && fread(argsds,len,1,fp) == 0) {
|
||||
sdsfree(argsds);
|
||||
@@ -801,10 +819,12 @@ int loadAppendOnlyFile(char *filename) {
|
||||
goto readerr;
|
||||
}
|
||||
argv[j] = createObject(OBJ_STRING,argsds);
|
||||
|
||||
/* Discard CRLF. */
|
||||
if (fread(buf,2,1,fp) == 0) {
|
||||
fakeClient->argc = j+1; /* Free up to j. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr; /* discard CRLF */
|
||||
goto readerr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,7 +840,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
if (cmd == server.multiCommand) valid_before_multi = valid_up_to;
|
||||
|
||||
/* Run the command in the context of a fake client */
|
||||
fakeClient->cmd = cmd;
|
||||
fakeClient->cmd = fakeClient->lastcmd = cmd;
|
||||
if (fakeClient->flags & CLIENT_MULTI &&
|
||||
fakeClient->cmd->proc != execCommand)
|
||||
{
|
||||
@@ -1139,7 +1159,7 @@ int rioWriteBulkStreamID(rio *r,streamID *id) {
|
||||
int retval;
|
||||
|
||||
sds replyid = sdscatfmt(sdsempty(),"%U-%U",id->ms,id->seq);
|
||||
if ((retval = rioWriteBulkString(r,replyid,sdslen(replyid))) == 0) return 0;
|
||||
retval = rioWriteBulkString(r,replyid,sdslen(replyid));
|
||||
sdsfree(replyid);
|
||||
return retval;
|
||||
}
|
||||
@@ -1185,38 +1205,55 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
* the ID, the second is an array of field-value pairs. */
|
||||
|
||||
/* Emit the XADD <key> <id> ...fields... command. */
|
||||
if (rioWriteBulkCount(r,'*',3+numfields*2) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (rioWriteBulkStreamID(r,&id) == 0) return 0;
|
||||
if (!rioWriteBulkCount(r,'*',3+numfields*2) ||
|
||||
!rioWriteBulkString(r,"XADD",4) ||
|
||||
!rioWriteBulkObject(r,key) ||
|
||||
!rioWriteBulkStreamID(r,&id))
|
||||
{
|
||||
streamIteratorStop(&si);
|
||||
return 0;
|
||||
}
|
||||
while(numfields--) {
|
||||
unsigned char *field, *value;
|
||||
int64_t field_len, value_len;
|
||||
streamIteratorGetField(&si,&field,&value,&field_len,&value_len);
|
||||
if (rioWriteBulkString(r,(char*)field,field_len) == 0) return 0;
|
||||
if (rioWriteBulkString(r,(char*)value,value_len) == 0) return 0;
|
||||
if (!rioWriteBulkString(r,(char*)field,field_len) ||
|
||||
!rioWriteBulkString(r,(char*)value,value_len))
|
||||
{
|
||||
streamIteratorStop(&si);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Use the XADD MAXLEN 0 trick to generate an empty stream if
|
||||
* the key we are serializing is an empty string, which is possible
|
||||
* for the Stream type. */
|
||||
if (rioWriteBulkCount(r,'*',7) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"MAXLEN",6) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"0",1) == 0) return 0;
|
||||
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"x",1) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"y",1) == 0) return 0;
|
||||
id.ms = 0; id.seq = 1;
|
||||
if (!rioWriteBulkCount(r,'*',7) ||
|
||||
!rioWriteBulkString(r,"XADD",4) ||
|
||||
!rioWriteBulkObject(r,key) ||
|
||||
!rioWriteBulkString(r,"MAXLEN",6) ||
|
||||
!rioWriteBulkString(r,"0",1) ||
|
||||
!rioWriteBulkStreamID(r,&id) ||
|
||||
!rioWriteBulkString(r,"x",1) ||
|
||||
!rioWriteBulkString(r,"y",1))
|
||||
{
|
||||
streamIteratorStop(&si);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Append XSETID after XADD, make sure lastid is correct,
|
||||
* in case of XDEL lastid. */
|
||||
if (rioWriteBulkCount(r,'*',3) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"XSETID",6) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
|
||||
if (!rioWriteBulkCount(r,'*',3) ||
|
||||
!rioWriteBulkString(r,"XSETID",6) ||
|
||||
!rioWriteBulkObject(r,key) ||
|
||||
!rioWriteBulkStreamID(r,&s->last_id))
|
||||
{
|
||||
streamIteratorStop(&si);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Create all the stream consumer groups. */
|
||||
@@ -1227,12 +1264,17 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
while(raxNext(&ri)) {
|
||||
streamCG *group = ri.data;
|
||||
/* Emit the XGROUP CREATE in order to create the group. */
|
||||
if (rioWriteBulkCount(r,'*',5) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"XGROUP",6) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"CREATE",6) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (rioWriteBulkString(r,(char*)ri.key,ri.key_len) == 0) return 0;
|
||||
if (rioWriteBulkStreamID(r,&group->last_id) == 0) return 0;
|
||||
if (!rioWriteBulkCount(r,'*',5) ||
|
||||
!rioWriteBulkString(r,"XGROUP",6) ||
|
||||
!rioWriteBulkString(r,"CREATE",6) ||
|
||||
!rioWriteBulkObject(r,key) ||
|
||||
!rioWriteBulkString(r,(char*)ri.key,ri.key_len) ||
|
||||
!rioWriteBulkStreamID(r,&group->last_id))
|
||||
{
|
||||
raxStop(&ri);
|
||||
streamIteratorStop(&si);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Generate XCLAIMs for each consumer that happens to
|
||||
* have pending entries. Empty consumers have no semantical
|
||||
@@ -1253,6 +1295,10 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
ri.key_len,consumer,
|
||||
ri_pel.key,nack) == 0)
|
||||
{
|
||||
raxStop(&ri_pel);
|
||||
raxStop(&ri_cons);
|
||||
raxStop(&ri);
|
||||
streamIteratorStop(&si);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1382,7 +1428,7 @@ werr:
|
||||
* are inserted using a single command. */
|
||||
int rewriteAppendOnlyFile(char *filename) {
|
||||
rio aof;
|
||||
FILE *fp;
|
||||
FILE *fp = NULL;
|
||||
char tmpfile[256];
|
||||
char byte;
|
||||
|
||||
@@ -1459,9 +1505,10 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
goto werr;
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
if (fflush(fp) == EOF) goto werr;
|
||||
if (fsync(fileno(fp)) == -1) goto werr;
|
||||
if (fclose(fp) == EOF) goto werr;
|
||||
if (fflush(fp)) goto werr;
|
||||
if (fsync(fileno(fp))) goto werr;
|
||||
if (fclose(fp)) { fp = NULL; goto werr; }
|
||||
fp = NULL;
|
||||
|
||||
/* Use RENAME to make sure the DB file is changed atomically only
|
||||
* if the generate DB file is ok. */
|
||||
@@ -1477,7 +1524,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
|
||||
werr:
|
||||
serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
||||
fclose(fp);
|
||||
if (fp) fclose(fp);
|
||||
unlink(tmpfile);
|
||||
stopSaving(0);
|
||||
return C_ERR;
|
||||
@@ -1579,14 +1626,15 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
if (hasActiveChildProcess()) return C_ERR;
|
||||
if (aofCreatePipes() != C_OK) return C_ERR;
|
||||
openChildInfoPipe();
|
||||
if ((childpid = redisFork()) == 0) {
|
||||
if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
|
||||
char tmpfile[256];
|
||||
|
||||
/* Child */
|
||||
redisSetProcTitle("redis-aof-rewrite");
|
||||
redisSetCpuAffinity(server.aof_rewrite_cpulist);
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
||||
sendChildCOWInfo(CHILD_INFO_TYPE_AOF, "AOF rewrite");
|
||||
sendChildCOWInfo(CHILD_TYPE_AOF, "AOF rewrite");
|
||||
exitFromChild(0);
|
||||
} else {
|
||||
exitFromChild(1);
|
||||
@@ -1606,6 +1654,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
server.aof_rewrite_scheduled = 0;
|
||||
server.aof_rewrite_time_start = time(NULL);
|
||||
server.aof_child_pid = childpid;
|
||||
updateDictResizePolicy();
|
||||
/* We set appendseldb to -1 in order to force the next call to the
|
||||
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
||||
* accumulated by the parent into server.aof_rewrite_buf will start
|
||||
@@ -1635,10 +1684,10 @@ void aofRemoveTempFile(pid_t childpid) {
|
||||
char tmpfile[256];
|
||||
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) childpid);
|
||||
unlink(tmpfile);
|
||||
bg_unlink(tmpfile);
|
||||
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) childpid);
|
||||
unlink(tmpfile);
|
||||
bg_unlink(tmpfile);
|
||||
}
|
||||
|
||||
/* Update the server.aof_current_size field explicitly using stat(2)
|
||||
@@ -1787,14 +1836,15 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
||||
} else if (!bysignal && exitcode != 0) {
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
* tirggering an error condition. */
|
||||
if (bysignal != SIGUSR1)
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated with error");
|
||||
} else {
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
* triggering an error condition. */
|
||||
if (bysignal != SIGUSR1)
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated by signal %d", bysignal);
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
char *ascii_logo =
|
||||
const char *ascii_logo =
|
||||
" _._ \n"
|
||||
" _.-``__ ''-._ \n"
|
||||
" _.-`` `. `_. ''-._ Redis %s (%s/%d) %s bit\n"
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
*
|
||||
* Never use return value from the macros, instead use the AtomicGetIncr()
|
||||
* if you need to get the current value and increment it atomically, like
|
||||
* in the followign example:
|
||||
* in the following example:
|
||||
*
|
||||
* long oldvalue;
|
||||
* atomicGetIncr(myvar,oldvalue,1);
|
||||
|
||||
@@ -154,10 +154,21 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Make the thread killable at any time, so that bioKillThreads()
|
||||
* can work reliably. */
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
|
||||
switch (type) {
|
||||
case BIO_CLOSE_FILE:
|
||||
redis_set_thread_title("bio_close_file");
|
||||
break;
|
||||
case BIO_AOF_FSYNC:
|
||||
redis_set_thread_title("bio_aof_fsync");
|
||||
break;
|
||||
case BIO_LAZY_FREE:
|
||||
redis_set_thread_title("bio_lazy_free");
|
||||
break;
|
||||
}
|
||||
|
||||
redisSetCpuAffinity(server.bio_cpulist);
|
||||
|
||||
makeThreadKillable();
|
||||
|
||||
pthread_mutex_lock(&bio_mutex[type]);
|
||||
/* Block SIGALRM so we are sure that only the main thread will
|
||||
@@ -192,7 +203,7 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
/* What we free changes depending on what arguments are set:
|
||||
* arg1 -> free the object at pointer.
|
||||
* arg2 & arg3 -> free two dictionaries (a Redis DB).
|
||||
* only arg3 -> free the skiplist. */
|
||||
* only arg3 -> free the radix tree. */
|
||||
if (job->arg1)
|
||||
lazyfreeFreeObjectFromBioThread(job->arg1);
|
||||
else if (job->arg2 && job->arg3)
|
||||
@@ -254,10 +265,11 @@ void bioKillThreads(void) {
|
||||
int err, j;
|
||||
|
||||
for (j = 0; j < BIO_NUM_OPS; j++) {
|
||||
if (pthread_cancel(bio_threads[j]) == 0) {
|
||||
if (bio_threads[j] == pthread_self()) continue;
|
||||
if (bio_threads[j] && pthread_cancel(bio_threads[j]) == 0) {
|
||||
if ((err = pthread_join(bio_threads[j],NULL)) != 0) {
|
||||
serverLog(LL_WARNING,
|
||||
"Bio thread for job type #%d can be joined: %s",
|
||||
"Bio thread for job type #%d can not be joined: %s",
|
||||
j, strerror(err));
|
||||
} else {
|
||||
serverLog(LL_WARNING,
|
||||
|
||||
+58
-34
@@ -36,9 +36,9 @@
|
||||
|
||||
/* Count number of bits set in the binary array pointed by 's' and long
|
||||
* 'count' bytes. The implementation of this function is required to
|
||||
* work with a input string length up to 512 MB. */
|
||||
size_t redisPopcount(void *s, long count) {
|
||||
size_t bits = 0;
|
||||
* work with an input string length up to 512 MB. */
|
||||
long long redisPopcount(void *s, long count) {
|
||||
long long bits = 0;
|
||||
unsigned char *p = s;
|
||||
uint32_t *p4;
|
||||
static const unsigned char bitsinbyte[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};
|
||||
@@ -98,16 +98,16 @@ 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, unsigned long count, int bit) {
|
||||
long 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. */
|
||||
long long pos = 0; /* Position of bit, to return to the caller. */
|
||||
unsigned long j;
|
||||
int found;
|
||||
|
||||
/* Process whole words first, seeking for first word that is not
|
||||
* all ones or all zeros respectively if we are lookig for zeros
|
||||
* all ones or all zeros respectively if we are looking for zeros
|
||||
* or ones. This is much faster with large strings having contiguous
|
||||
* blocks of 1 or 0 bits compared to the vanilla bit per bit processing.
|
||||
*
|
||||
@@ -257,7 +257,7 @@ int64_t getSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) {
|
||||
/* If the top significant bit is 1, propagate it to all the
|
||||
* higher bits for two's complement representation of signed
|
||||
* integers. */
|
||||
if (value & ((uint64_t)1 << (bits-1)))
|
||||
if (bits < 64 && (value & ((uint64_t)1 << (bits-1))))
|
||||
value |= ((uint64_t)-1) << bits;
|
||||
return value;
|
||||
}
|
||||
@@ -269,7 +269,7 @@ int64_t getSignedBitfield(unsigned char *p, uint64_t offset, uint64_t bits) {
|
||||
* then zero is returned, otherwise in case of overflow, 1 is returned,
|
||||
* otherwise in case of underflow, -1 is returned.
|
||||
*
|
||||
* When non-zero is returned (oferflow or underflow), if not NULL, *limit is
|
||||
* When non-zero is returned (overflow or underflow), if not NULL, *limit is
|
||||
* set to the value the operation should result when an overflow happens,
|
||||
* depending on the specified overflow semantics:
|
||||
*
|
||||
@@ -328,8 +328,9 @@ int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int
|
||||
|
||||
/* Note that maxincr and minincr could overflow, but we use the values
|
||||
* only after checking 'value' range, so when we use it no overflow
|
||||
* happens. */
|
||||
int64_t maxincr = max-value;
|
||||
* happens. 'uint64_t' cast is there just to prevent undefined behavior on
|
||||
* overflow */
|
||||
int64_t maxincr = (uint64_t)max-value;
|
||||
int64_t minincr = min-value;
|
||||
|
||||
if (value > max || (bits != 64 && incr > maxincr) || (value >= 0 && incr > 0 && incr > maxincr))
|
||||
@@ -356,7 +357,6 @@ int checkSignedBitfieldOverflow(int64_t value, int64_t incr, uint64_t bits, int
|
||||
|
||||
handle_wrap:
|
||||
{
|
||||
uint64_t mask = ((uint64_t)-1) << bits;
|
||||
uint64_t msb = (uint64_t)1 << (bits-1);
|
||||
uint64_t a = value, b = incr, c;
|
||||
c = a+b; /* Perform addition as unsigned so that's defined. */
|
||||
@@ -364,10 +364,13 @@ handle_wrap:
|
||||
/* If the sign bit is set, propagate to all the higher order
|
||||
* bits, to cap the negative value. If it's clear, mask to
|
||||
* the positive integer limit. */
|
||||
if (c & msb) {
|
||||
c |= mask;
|
||||
} else {
|
||||
c &= ~mask;
|
||||
if (bits < 64) {
|
||||
uint64_t mask = ((uint64_t)-1) << bits;
|
||||
if (c & msb) {
|
||||
c |= mask;
|
||||
} else {
|
||||
c &= ~mask;
|
||||
}
|
||||
}
|
||||
*limit = c;
|
||||
}
|
||||
@@ -408,7 +411,7 @@ void printBits(unsigned char *p, unsigned long count) {
|
||||
* If the 'hash' argument is true, and 'bits is positive, then the command
|
||||
* will also parse bit offsets prefixed by "#". In such a case the offset
|
||||
* is multiplied by 'bits'. This is useful for the BITFIELD command. */
|
||||
int getBitOffsetFromArgument(client *c, robj *o, size_t *offset, int hash, int bits) {
|
||||
int getBitOffsetFromArgument(client *c, robj *o, uint64_t *offset, int hash, int bits) {
|
||||
long long loffset;
|
||||
char *err = "bit offset is not an integer or out of range";
|
||||
char *p = o->ptr;
|
||||
@@ -433,7 +436,7 @@ int getBitOffsetFromArgument(client *c, robj *o, size_t *offset, int hash, int b
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
*offset = (size_t)loffset;
|
||||
*offset = loffset;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
@@ -475,7 +478,7 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
|
||||
* so that the 'maxbit' bit can be addressed. The object is finally
|
||||
* returned. Otherwise if the key holds a wrong type NULL is returned and
|
||||
* an error is sent to the client. */
|
||||
robj *lookupStringForBitCommand(client *c, size_t maxbit) {
|
||||
robj *lookupStringForBitCommand(client *c, uint64_t maxbit) {
|
||||
size_t byte = maxbit >> 3;
|
||||
robj *o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
|
||||
@@ -494,7 +497,7 @@ robj *lookupStringForBitCommand(client *c, size_t maxbit) {
|
||||
* in 'len'. The user is required to pass (likely stack allocated) buffer
|
||||
* 'llbuf' of at least LONG_STR_SIZE bytes. Such a buffer is used in the case
|
||||
* the object is integer encoded in order to provide the representation
|
||||
* without usign heap allocation.
|
||||
* without using heap allocation.
|
||||
*
|
||||
* The function returns the pointer to the object array of bytes representing
|
||||
* the string it contains, that may be a pointer to 'llbuf' or to the
|
||||
@@ -525,7 +528,7 @@ unsigned char *getObjectReadOnlyString(robj *o, long *len, char *llbuf) {
|
||||
void setbitCommand(client *c) {
|
||||
robj *o;
|
||||
char *err = "bit is not an integer or out of range";
|
||||
size_t bitoffset;
|
||||
uint64_t bitoffset;
|
||||
ssize_t byte, bit;
|
||||
int byteval, bitval;
|
||||
long on;
|
||||
@@ -554,7 +557,7 @@ void setbitCommand(client *c) {
|
||||
byteval &= ~(1 << bit);
|
||||
byteval |= ((on & 0x1) << bit);
|
||||
((uint8_t*)o->ptr)[byte] = byteval;
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
addReply(c, bitval ? shared.cone : shared.czero);
|
||||
@@ -564,7 +567,7 @@ void setbitCommand(client *c) {
|
||||
void getbitCommand(client *c) {
|
||||
robj *o;
|
||||
char llbuf[32];
|
||||
size_t bitoffset;
|
||||
uint64_t bitoffset;
|
||||
size_t byte, bit;
|
||||
size_t bitval = 0;
|
||||
|
||||
@@ -754,14 +757,15 @@ void bitopCommand(client *c) {
|
||||
/* Store the computed value into the target key */
|
||||
if (maxlen) {
|
||||
o = createObject(OBJ_STRING,res);
|
||||
setKey(c->db,targetkey,o);
|
||||
setKey(c,c->db,targetkey,o);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",targetkey,c->db->id);
|
||||
decrRefCount(o);
|
||||
server.dirty++;
|
||||
} else if (dbDelete(c->db,targetkey)) {
|
||||
signalModifiedKey(c->db,targetkey);
|
||||
signalModifiedKey(c,c->db,targetkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
server.dirty++;
|
||||
addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */
|
||||
}
|
||||
|
||||
@@ -874,7 +878,7 @@ void bitposCommand(client *c) {
|
||||
addReplyLongLong(c, -1);
|
||||
} else {
|
||||
long bytes = end-start+1;
|
||||
long pos = redisBitpos(p+start,bytes,bit);
|
||||
long long pos = redisBitpos(p+start,bytes,bit);
|
||||
|
||||
/* If we are looking for clear bits, and the user specified an exact
|
||||
* range with start-end, we can't consider the right of the range as
|
||||
@@ -883,11 +887,11 @@ void bitposCommand(client *c) {
|
||||
* So if redisBitpos() returns the first bit outside the range,
|
||||
* we return -1 to the caller, to mean, in the specified range there
|
||||
* is not a single "0" bit. */
|
||||
if (end_given && bit == 0 && pos == bytes*8) {
|
||||
if (end_given && bit == 0 && pos == (long long)bytes<<3) {
|
||||
addReplyLongLong(c,-1);
|
||||
return;
|
||||
}
|
||||
if (pos != -1) pos += start*8; /* Adjust for the bytes we skipped. */
|
||||
if (pos != -1) pos += (long long)start<<3; /* Adjust for the bytes we skipped. */
|
||||
addReplyLongLong(c,pos);
|
||||
}
|
||||
}
|
||||
@@ -902,6 +906,9 @@ void bitposCommand(client *c) {
|
||||
* OVERFLOW [WRAP|SAT|FAIL]
|
||||
*/
|
||||
|
||||
#define BITFIELD_FLAG_NONE 0
|
||||
#define BITFIELD_FLAG_READONLY (1<<0)
|
||||
|
||||
struct bitfieldOp {
|
||||
uint64_t offset; /* Bitfield offset. */
|
||||
int64_t i64; /* Increment amount (INCRBY) or SET value */
|
||||
@@ -911,14 +918,17 @@ struct bitfieldOp {
|
||||
int sign; /* True if signed, otherwise unsigned op. */
|
||||
};
|
||||
|
||||
void bitfieldCommand(client *c) {
|
||||
/* This implements both the BITFIELD command and the BITFIELD_RO command
|
||||
* when flags is set to BITFIELD_FLAG_READONLY: in this case only the
|
||||
* GET subcommand is allowed, other subcommands will return an error. */
|
||||
void bitfieldGeneric(client *c, int flags) {
|
||||
robj *o;
|
||||
size_t bitoffset;
|
||||
uint64_t bitoffset;
|
||||
int j, numops = 0, changes = 0;
|
||||
struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
|
||||
int owtype = BFOVERFLOW_WRAP; /* Overflow type. */
|
||||
int readonly = 1;
|
||||
size_t highest_write_offset = 0;
|
||||
uint64_t highest_write_offset = 0;
|
||||
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
int remargs = c->argc-j-1; /* Remaining args other than current. */
|
||||
@@ -999,6 +1009,12 @@ void bitfieldCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (flags & BITFIELD_FLAG_READONLY) {
|
||||
zfree(ops);
|
||||
addReplyError(c, "BITFIELD_RO only supports the GET subcommand");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Lookup by making room up to the farest bit reached by
|
||||
* this operation. */
|
||||
if ((o = lookupStringForBitCommand(c,
|
||||
@@ -1102,9 +1118,9 @@ void bitfieldCommand(client *c) {
|
||||
* object boundaries. */
|
||||
memset(buf,0,9);
|
||||
int i;
|
||||
size_t byte = thisop->offset >> 3;
|
||||
uint64_t byte = thisop->offset >> 3;
|
||||
for (i = 0; i < 9; i++) {
|
||||
if (src == NULL || i+byte >= (size_t)strlen) break;
|
||||
if (src == NULL || i+byte >= (uint64_t)strlen) break;
|
||||
buf[i] = src[i+byte];
|
||||
}
|
||||
|
||||
@@ -1123,9 +1139,17 @@ void bitfieldCommand(client *c) {
|
||||
}
|
||||
|
||||
if (changes) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
|
||||
server.dirty += changes;
|
||||
}
|
||||
zfree(ops);
|
||||
}
|
||||
|
||||
void bitfieldCommand(client *c) {
|
||||
bitfieldGeneric(c, BITFIELD_FLAG_NONE);
|
||||
}
|
||||
|
||||
void bitfieldroCommand(client *c) {
|
||||
bitfieldGeneric(c, BITFIELD_FLAG_READONLY);
|
||||
}
|
||||
|
||||
+52
-64
@@ -31,9 +31,6 @@
|
||||
*
|
||||
* API:
|
||||
*
|
||||
* getTimeoutFromObjectOrReply() is just an utility function to parse a
|
||||
* timeout argument since blocking operations usually require a timeout.
|
||||
*
|
||||
* blockClient() set the CLIENT_BLOCKED flag in the client, and set the
|
||||
* specified block type 'btype' filed to one of BLOCKED_* macros.
|
||||
*
|
||||
@@ -56,7 +53,7 @@
|
||||
* to 0, no timeout is processed).
|
||||
* It usually just needs to send a reply to the client.
|
||||
*
|
||||
* When implementing a new type of blocking opeation, the implementation
|
||||
* When implementing a new type of blocking operation, 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
|
||||
@@ -67,41 +64,20 @@
|
||||
|
||||
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int where);
|
||||
|
||||
/* Get a timeout value from an object and store it into 'timeout'.
|
||||
* The final timeout is always stored as milliseconds as a time where the
|
||||
* timeout will expire, however the parsing is performed according to
|
||||
* the 'unit' that can be seconds or milliseconds.
|
||||
*
|
||||
* Note that if the timeout is zero (usually from the point of view of
|
||||
* commands API this means no timeout) the value stored into 'timeout'
|
||||
* is zero. */
|
||||
int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int unit) {
|
||||
long long tval;
|
||||
long double ftval;
|
||||
|
||||
if (unit == UNIT_SECONDS) {
|
||||
if (getLongDoubleFromObjectOrReply(c,object,&ftval,
|
||||
"timeout is not an float or out of range") != C_OK)
|
||||
return C_ERR;
|
||||
tval = (long long) (ftval * 1000.0);
|
||||
} else {
|
||||
if (getLongLongFromObjectOrReply(c,object,&tval,
|
||||
"timeout is not an integer or out of range") != C_OK)
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (tval < 0) {
|
||||
addReplyError(c,"timeout is negative");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (tval > 0) {
|
||||
tval += mstime();
|
||||
}
|
||||
*timeout = tval;
|
||||
|
||||
return C_OK;
|
||||
}
|
||||
/* This structure represents the blocked key information that we store
|
||||
* in the client structure. Each client blocked on keys, has a
|
||||
* client->bpop.keys hash table. The keys of the hash table are Redis
|
||||
* keys pointers to 'robj' structures. The value is this structure.
|
||||
* The structure has two goals: firstly we store the list node that this
|
||||
* client uses to be listed in the database "blocked clients for this key"
|
||||
* list, so we can later unblock in O(1) without a list scan.
|
||||
* Secondly for certain blocking types, we have additional info. Right now
|
||||
* the only use for additional info we have is when clients are blocked
|
||||
* on streams, as we have to remember the ID it blocked for. */
|
||||
typedef struct bkinfo {
|
||||
listNode *listnode; /* List node for db->blocking_keys[key] list. */
|
||||
streamID stream_id; /* Stream ID if we blocked in a stream. */
|
||||
} bkinfo;
|
||||
|
||||
/* Block a client for the specific operation type. Once the CLIENT_BLOCKED
|
||||
* flag is set client query buffer is not longer processed, but accumulated,
|
||||
@@ -111,6 +87,7 @@ void blockClient(client *c, int btype) {
|
||||
c->btype = btype;
|
||||
server.blocked_clients++;
|
||||
server.blocked_clients_by_type[btype]++;
|
||||
addClientToTimeoutTable(c);
|
||||
}
|
||||
|
||||
/* This function is called in the beforeSleep() function of the event loop
|
||||
@@ -121,6 +98,9 @@ void processUnblockedClients(void) {
|
||||
client *c;
|
||||
|
||||
while (listLength(server.unblocked_clients)) {
|
||||
/* If clients are paused we yield for now, since
|
||||
* we don't want to process any commands later. */
|
||||
if (clientsArePaused()) return;
|
||||
ln = listFirst(server.unblocked_clients);
|
||||
serverAssert(ln != NULL);
|
||||
c = ln->value;
|
||||
@@ -132,8 +112,13 @@ void processUnblockedClients(void) {
|
||||
* client is not blocked before to proceed, but things may change and
|
||||
* the code is conceptually more correct this way. */
|
||||
if (!(c->flags & CLIENT_BLOCKED)) {
|
||||
/* If we have a queued command, execute it now. */
|
||||
if (processPendingCommandsAndResetClient(c) == C_ERR) {
|
||||
continue;
|
||||
}
|
||||
/* Then process client if it has more data in it's buffer. */
|
||||
if (c->querybuf && sdslen(c->querybuf) > 0) {
|
||||
processInputBufferAndReplicate(c);
|
||||
processInputBuffer(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +126,7 @@ void processUnblockedClients(void) {
|
||||
|
||||
/* This function will schedule the client for reprocessing at a safe time.
|
||||
*
|
||||
* This is useful when a client was blocked for some reason (blocking opeation,
|
||||
* This is useful when a client was blocked for some reason (blocking operation,
|
||||
* CLIENT PAUSE, or whatever), because it may end with some accumulated query
|
||||
* buffer that needs to be processed ASAP:
|
||||
*
|
||||
@@ -185,6 +170,7 @@ void unblockClient(client *c) {
|
||||
server.blocked_clients_by_type[c->btype]--;
|
||||
c->flags &= ~CLIENT_BLOCKED;
|
||||
c->btype = BLOCKED_NONE;
|
||||
removeClientFromTimeoutTable(c);
|
||||
queueClientForReprocessing(c);
|
||||
}
|
||||
|
||||
@@ -248,8 +234,7 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
||||
if (receiver->btype != BLOCKED_LIST) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
listRotateHeadToTail(clients);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -310,8 +295,7 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
|
||||
if (receiver->btype != BLOCKED_ZSET) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
listRotateHeadToTail(clients);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -357,8 +341,8 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
while((ln = listNext(&li))) {
|
||||
client *receiver = listNodeValue(ln);
|
||||
if (receiver->btype != BLOCKED_STREAM) continue;
|
||||
streamID *gt = dictFetchValue(receiver->bpop.keys,
|
||||
rl->key);
|
||||
bkinfo *bki = dictFetchValue(receiver->bpop.keys,rl->key);
|
||||
streamID *gt = &bki->stream_id;
|
||||
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and update the
|
||||
@@ -388,16 +372,17 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
|
||||
if (streamCompareID(&s->last_id, gt) > 0) {
|
||||
streamID start = *gt;
|
||||
start.seq++; /* Can't overflow, it's an uint64_t */
|
||||
streamIncrID(&start);
|
||||
|
||||
/* Lookup the consumer for the group, if any. */
|
||||
streamConsumer *consumer = NULL;
|
||||
int noack = 0;
|
||||
|
||||
if (group) {
|
||||
consumer = streamLookupConsumer(group,
|
||||
receiver->bpop.xread_consumer->ptr,
|
||||
1);
|
||||
consumer =
|
||||
streamLookupConsumer(group,
|
||||
receiver->bpop.xread_consumer->ptr,
|
||||
SLC_NONE);
|
||||
noack = receiver->bpop.xread_group_noack;
|
||||
}
|
||||
|
||||
@@ -440,6 +425,10 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
void serveClientsBlockedOnKeyByModule(readyList *rl) {
|
||||
dictEntry *de;
|
||||
|
||||
/* Optimization: If no clients are in type BLOCKED_MODULE,
|
||||
* we can skip this loop. */
|
||||
if (!server.blocked_clients_by_type[BLOCKED_MODULE]) return;
|
||||
|
||||
/* We serve clients in the same order they blocked for
|
||||
* this key, from the first blocked to the last. */
|
||||
de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
@@ -456,8 +445,7 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
|
||||
* ready to be served, so they'll remain in the list
|
||||
* sometimes. We want also be able to skip clients that are
|
||||
* not blocked for the MODULE type safely. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
listRotateHeadToTail(clients);
|
||||
|
||||
if (receiver->btype != BLOCKED_MODULE) continue;
|
||||
|
||||
@@ -524,7 +512,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
server.fixed_time_expire++;
|
||||
updateCachedTime(0);
|
||||
|
||||
/* Serve clients blocked on list key. */
|
||||
/* Serve clients blocked on the key. */
|
||||
robj *o = lookupKeyWrite(rl->db,rl->key);
|
||||
|
||||
if (o != NULL) {
|
||||
@@ -588,17 +576,15 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
|
||||
if (target != NULL) incrRefCount(target);
|
||||
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
/* The value associated with the key name in the bpop.keys dictionary
|
||||
* is NULL for lists and sorted sets, or the stream ID for streams. */
|
||||
void *key_data = NULL;
|
||||
if (btype == BLOCKED_STREAM) {
|
||||
key_data = zmalloc(sizeof(streamID));
|
||||
memcpy(key_data,ids+j,sizeof(streamID));
|
||||
}
|
||||
/* Allocate our bkinfo structure, associated to each key the client
|
||||
* is blocked for. */
|
||||
bkinfo *bki = zmalloc(sizeof(*bki));
|
||||
if (btype == BLOCKED_STREAM)
|
||||
bki->stream_id = ids[j];
|
||||
|
||||
/* If the key already exists in the dictionary ignore it. */
|
||||
if (dictAdd(c->bpop.keys,keys[j],key_data) != DICT_OK) {
|
||||
zfree(key_data);
|
||||
if (dictAdd(c->bpop.keys,keys[j],bki) != DICT_OK) {
|
||||
zfree(bki);
|
||||
continue;
|
||||
}
|
||||
incrRefCount(keys[j]);
|
||||
@@ -617,6 +603,7 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
|
||||
l = dictGetVal(de);
|
||||
}
|
||||
listAddNodeTail(l,c);
|
||||
bki->listnode = listLast(l);
|
||||
}
|
||||
blockClient(c,btype);
|
||||
}
|
||||
@@ -633,11 +620,12 @@ void unblockClientWaitingData(client *c) {
|
||||
/* The client may wait for multiple keys, so unblock it for every key. */
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *key = dictGetKey(de);
|
||||
bkinfo *bki = dictGetVal(de);
|
||||
|
||||
/* Remove this client from the list of clients waiting for this key. */
|
||||
l = dictFetchValue(c->db->blocking_keys,key);
|
||||
serverAssertWithInfo(c,key,l != NULL);
|
||||
listDelNode(l,listSearchKey(l,c));
|
||||
listDelNode(l,bki->listnode);
|
||||
/* If the list is empty we need to remove it to avoid wasting memory */
|
||||
if (listLength(l) == 0)
|
||||
dictDelete(c->db->blocking_keys,key);
|
||||
|
||||
+3
-3
@@ -76,11 +76,11 @@ void receiveChildInfo(void) {
|
||||
if (read(server.child_info_pipe[0],&server.child_info_data,wlen) == wlen &&
|
||||
server.child_info_data.magic == CHILD_INFO_MAGIC)
|
||||
{
|
||||
if (server.child_info_data.process_type == CHILD_INFO_TYPE_RDB) {
|
||||
if (server.child_info_data.process_type == CHILD_TYPE_RDB) {
|
||||
server.stat_rdb_cow_bytes = server.child_info_data.cow_size;
|
||||
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
|
||||
} else if (server.child_info_data.process_type == CHILD_TYPE_AOF) {
|
||||
server.stat_aof_cow_bytes = server.child_info_data.cow_size;
|
||||
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_MODULE) {
|
||||
} else if (server.child_info_data.process_type == CHILD_TYPE_MODULE) {
|
||||
server.stat_module_cow_bytes = server.child_info_data.cow_size;
|
||||
}
|
||||
}
|
||||
|
||||
+239
-97
@@ -77,6 +77,9 @@ uint64_t clusterGetMaxEpoch(void);
|
||||
int clusterBumpConfigEpochWithoutConsensus(void);
|
||||
void moduleCallClusterReceivers(const char *sender_id, uint64_t module_id, uint8_t type, const unsigned char *payload, uint32_t len);
|
||||
|
||||
#define RCVBUF_INIT_LEN 1024
|
||||
#define RCVBUF_MAX_PREALLOC (1<<20) /* 1MB */
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Initialization
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -157,7 +160,10 @@ int clusterLoadConfig(char *filename) {
|
||||
}
|
||||
|
||||
/* Regular config lines have at least eight fields */
|
||||
if (argc < 8) goto fmterr;
|
||||
if (argc < 8) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
|
||||
/* Create this node if it does not exist */
|
||||
n = clusterLookupNode(argv[0]);
|
||||
@@ -166,7 +172,10 @@ int clusterLoadConfig(char *filename) {
|
||||
clusterAddNode(n);
|
||||
}
|
||||
/* Address and port */
|
||||
if ((p = strrchr(argv[1],':')) == NULL) goto fmterr;
|
||||
if ((p = strrchr(argv[1],':')) == NULL) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
*p = '\0';
|
||||
memcpy(n->ip,argv[1],strlen(argv[1])+1);
|
||||
char *port = p+1;
|
||||
@@ -247,7 +256,10 @@ int clusterLoadConfig(char *filename) {
|
||||
*p = '\0';
|
||||
direction = p[1]; /* Either '>' or '<' */
|
||||
slot = atoi(argv[j]+1);
|
||||
if (slot < 0 || slot >= CLUSTER_SLOTS) goto fmterr;
|
||||
if (slot < 0 || slot >= CLUSTER_SLOTS) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
p += 3;
|
||||
cn = clusterLookupNode(p);
|
||||
if (!cn) {
|
||||
@@ -267,8 +279,12 @@ int clusterLoadConfig(char *filename) {
|
||||
} else {
|
||||
start = stop = atoi(argv[j]);
|
||||
}
|
||||
if (start < 0 || start >= CLUSTER_SLOTS) goto fmterr;
|
||||
if (stop < 0 || stop >= CLUSTER_SLOTS) goto fmterr;
|
||||
if (start < 0 || start >= CLUSTER_SLOTS ||
|
||||
stop < 0 || stop >= CLUSTER_SLOTS)
|
||||
{
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
while(start <= stop) clusterAddSlot(n, start++);
|
||||
}
|
||||
|
||||
@@ -364,7 +380,7 @@ void clusterSaveConfigOrDie(int do_fsync) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Lock the cluster config using flock(), and leaks the file descritor used to
|
||||
/* Lock the cluster config using flock(), and leaks the file descriptor used to
|
||||
* acquire the lock so that the file will be locked forever.
|
||||
*
|
||||
* This works because we always update nodes.conf with a new version
|
||||
@@ -405,7 +421,15 @@ int clusterLockConfig(char *filename) {
|
||||
return C_ERR;
|
||||
}
|
||||
/* Lock acquired: leak the 'fd' by not closing it, so that we'll retain the
|
||||
* lock to the file as long as the process exists. */
|
||||
* lock to the file as long as the process exists.
|
||||
*
|
||||
* After fork, the child process will get the fd opened by the parent process,
|
||||
* we need save `fd` to `cluster_config_file_lock_fd`, so that in redisFork(),
|
||||
* it will be closed in the child process.
|
||||
* If it is not closed, when the main process is killed -9, but the child process
|
||||
* (redis-aof-rewrite) is still alive, the fd(lock) will still be held by the
|
||||
* child process, and the main process will fail to get lock, means fail to start. */
|
||||
server.cluster_config_file_lock_fd = fd;
|
||||
#endif /* __sun */
|
||||
|
||||
return C_OK;
|
||||
@@ -455,6 +479,7 @@ void clusterInit(void) {
|
||||
|
||||
/* Lock the cluster config file to make sure every node uses
|
||||
* its own nodes.conf. */
|
||||
server.cluster_config_file_lock_fd = -1;
|
||||
if (clusterLockConfig(server.cluster_configfile) == C_ERR)
|
||||
exit(1);
|
||||
|
||||
@@ -522,13 +547,13 @@ void clusterInit(void) {
|
||||
|
||||
/* Reset a node performing a soft or hard reset:
|
||||
*
|
||||
* 1) All other nodes are forget.
|
||||
* 1) All other nodes are forgotten.
|
||||
* 2) All the assigned / open slots are released.
|
||||
* 3) If the node is a slave, it turns into a master.
|
||||
* 5) Only for hard reset: a new Node ID is generated.
|
||||
* 6) Only for hard reset: currentEpoch and configEpoch are set to 0.
|
||||
* 7) The new configuration is saved and the cluster state updated.
|
||||
* 8) If the node was a slave, the whole data set is flushed away. */
|
||||
* 4) Only for hard reset: a new Node ID is generated.
|
||||
* 5) Only for hard reset: currentEpoch and configEpoch are set to 0.
|
||||
* 6) The new configuration is saved and the cluster state updated.
|
||||
* 7) If the node was a slave, the whole data set is flushed away. */
|
||||
void clusterReset(int hard) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
@@ -591,7 +616,8 @@ clusterLink *createClusterLink(clusterNode *node) {
|
||||
clusterLink *link = zmalloc(sizeof(*link));
|
||||
link->ctime = mstime();
|
||||
link->sndbuf = sdsempty();
|
||||
link->rcvbuf = sdsempty();
|
||||
link->rcvbuf = zmalloc(link->rcvbuf_alloc = RCVBUF_INIT_LEN);
|
||||
link->rcvbuf_len = 0;
|
||||
link->node = node;
|
||||
link->conn = NULL;
|
||||
return link;
|
||||
@@ -606,7 +632,7 @@ void freeClusterLink(clusterLink *link) {
|
||||
link->conn = NULL;
|
||||
}
|
||||
sdsfree(link->sndbuf);
|
||||
sdsfree(link->rcvbuf);
|
||||
zfree(link->rcvbuf);
|
||||
if (link->node)
|
||||
link->node->link = NULL;
|
||||
zfree(link);
|
||||
@@ -624,7 +650,7 @@ static void clusterConnAcceptHandler(connection *conn) {
|
||||
|
||||
/* Create a link object we use to handle the connection.
|
||||
* It gets passed to the readable handler when data is available.
|
||||
* Initiallly the link->node pointer is set to NULL as we don't know
|
||||
* Initially the link->node pointer is set to NULL as we don't know
|
||||
* which node is, but the right node is references once we know the
|
||||
* node identity. */
|
||||
link = createClusterLink(NULL);
|
||||
@@ -657,9 +683,20 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
|
||||
connection *conn = server.tls_cluster ? connCreateAcceptedTLS(cfd,1) : connCreateAcceptedSocket(cfd);
|
||||
connection *conn = server.tls_cluster ?
|
||||
connCreateAcceptedTLS(cfd, TLS_CLIENT_AUTH_YES) : connCreateAcceptedSocket(cfd);
|
||||
|
||||
/* Make sure connection is not in an error state */
|
||||
if (connGetState(conn) != CONN_STATE_ACCEPTING) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error creating an accepting connection for cluster node: %s",
|
||||
connGetLastError(conn));
|
||||
connClose(conn);
|
||||
return;
|
||||
}
|
||||
connNonBlock(conn);
|
||||
connEnableTcpNoDelay(conn);
|
||||
connKeepAlive(conn,server.cluster_node_timeout / 1000 * 2);
|
||||
|
||||
/* Use non-blocking I/O for cluster messages. */
|
||||
serverLog(LL_VERBOSE,"Accepting cluster node connection from %s:%d", cip, cport);
|
||||
@@ -668,15 +705,26 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* or schedule it for later depending on connection implementation.
|
||||
*/
|
||||
if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error accepting cluster node connection: %s",
|
||||
connGetLastError(conn));
|
||||
if (connGetState(conn) == CONN_STATE_ERROR)
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error accepting cluster node connection: %s",
|
||||
connGetLastError(conn));
|
||||
connClose(conn);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the approximated number of sockets we are using in order to
|
||||
* take the cluster bus connections. */
|
||||
unsigned long getClusterConnectionsCount(void) {
|
||||
/* We decrement the number of nodes by one, since there is the
|
||||
* "myself" node too in the list. Each node uses two file descriptors,
|
||||
* one incoming and one outgoing, thus the multiplication by 2. */
|
||||
return server.cluster_enabled ?
|
||||
((dictSize(server.cluster->nodes)-1)*2) : 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Key space handling
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -735,6 +783,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
|
||||
node->slaves = NULL;
|
||||
node->slaveof = NULL;
|
||||
node->ping_sent = node->pong_received = 0;
|
||||
node->data_received = 0;
|
||||
node->fail_time = 0;
|
||||
node->link = NULL;
|
||||
memset(node->ip,0,sizeof(node->ip));
|
||||
@@ -919,7 +968,7 @@ int clusterAddNode(clusterNode *node) {
|
||||
return (retval == DICT_OK) ? C_OK : C_ERR;
|
||||
}
|
||||
|
||||
/* Remove a node from the cluster. The functio performs the high level
|
||||
/* Remove a node from the cluster. The function performs the high level
|
||||
* cleanup, calling freeClusterNode() for the low level cleanup.
|
||||
* Here we do the following:
|
||||
*
|
||||
@@ -1016,7 +1065,7 @@ uint64_t clusterGetMaxEpoch(void) {
|
||||
* 3) Persist the configuration on disk before sending packets with the
|
||||
* new configuration.
|
||||
*
|
||||
* If the new config epoch is generated and assigend, C_OK is returned,
|
||||
* If the new config epoch is generated and assigned, C_OK is returned,
|
||||
* otherwise C_ERR is returned (since the node has already the greatest
|
||||
* configuration around) and no operation is performed.
|
||||
*
|
||||
@@ -1089,7 +1138,7 @@ int clusterBumpConfigEpochWithoutConsensus(void) {
|
||||
*
|
||||
* In general we want a system that eventually always ends with different
|
||||
* masters having different configuration epochs whatever happened, since
|
||||
* nothign is worse than a split-brain condition in a distributed system.
|
||||
* nothing is worse than a split-brain condition in a distributed system.
|
||||
*
|
||||
* BEHAVIOR
|
||||
*
|
||||
@@ -1148,7 +1197,7 @@ void clusterHandleConfigEpochCollision(clusterNode *sender) {
|
||||
* entries from the black list. This is an O(N) operation but it is not a
|
||||
* problem since add / exists operations are called very infrequently and
|
||||
* the hash table is supposed to contain very little elements at max.
|
||||
* However without the cleanup during long uptimes and with some automated
|
||||
* However without the cleanup during long uptime and with some automated
|
||||
* node add/removal procedures, entries could accumulate. */
|
||||
void clusterBlacklistCleanup(void) {
|
||||
dictIterator *di;
|
||||
@@ -1239,8 +1288,11 @@ void markNodeAsFailingIfNeeded(clusterNode *node) {
|
||||
node->fail_time = mstime();
|
||||
|
||||
/* Broadcast the failing node name to everybody, forcing all the other
|
||||
* reachable nodes to flag the node as FAIL. */
|
||||
if (nodeIsMaster(myself)) clusterSendFail(node->name);
|
||||
* reachable nodes to flag the node as FAIL.
|
||||
* We do that even if this node is a replica and not a master: anyway
|
||||
* the failing state is triggered collecting failure reports from masters,
|
||||
* so here the replica is only helping propagating this status. */
|
||||
clusterSendFail(node->name);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
|
||||
}
|
||||
|
||||
@@ -1299,12 +1351,12 @@ int clusterHandshakeInProgress(char *ip, int port, int cport) {
|
||||
return de != NULL;
|
||||
}
|
||||
|
||||
/* Start an handshake with the specified address if there is not one
|
||||
/* Start a handshake with the specified address if there is not one
|
||||
* already in progress. Returns non-zero if the handshake was actually
|
||||
* started. On error zero is returned and errno is set to one of the
|
||||
* following values:
|
||||
*
|
||||
* EAGAIN - There is already an handshake in progress for this address.
|
||||
* EAGAIN - There is already a handshake in progress for this address.
|
||||
* EINVAL - IP or port are not valid. */
|
||||
int clusterStartHandshake(char *ip, int port, int cport) {
|
||||
clusterNode *n;
|
||||
@@ -1448,7 +1500,10 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
|
||||
}
|
||||
} else {
|
||||
/* If it's not in NOADDR state and we don't have it, we
|
||||
* start a handshake process against this IP/PORT pairs.
|
||||
* add it to our trusted dict with exact nodeid and flag.
|
||||
* Note that we cannot simply start a handshake against
|
||||
* this IP/PORT pairs, since IP/PORT can be reused already,
|
||||
* otherwise we risk joining another cluster.
|
||||
*
|
||||
* Note that we require that the sender of this gossip message
|
||||
* is a well known node in our cluster, otherwise we risk
|
||||
@@ -1457,7 +1512,12 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
|
||||
!(flags & CLUSTER_NODE_NOADDR) &&
|
||||
!clusterBlacklistExists(g->nodename))
|
||||
{
|
||||
clusterStartHandshake(g->ip,ntohs(g->port),ntohs(g->cport));
|
||||
clusterNode *node;
|
||||
node = createClusterNode(g->nodename, flags);
|
||||
memcpy(node->ip,g->ip,NET_IP_STR_LEN);
|
||||
node->port = ntohs(g->port);
|
||||
node->cport = ntohs(g->cport);
|
||||
clusterAddNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1664,6 +1724,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
clusterMsg *hdr = (clusterMsg*) link->rcvbuf;
|
||||
uint32_t totlen = ntohl(hdr->totlen);
|
||||
uint16_t type = ntohs(hdr->type);
|
||||
mstime_t now = mstime();
|
||||
|
||||
if (type < CLUSTERMSG_TYPE_COUNT)
|
||||
server.cluster->stats_bus_messages_received[type]++;
|
||||
@@ -1672,7 +1733,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
|
||||
/* Perform sanity checks */
|
||||
if (totlen < 16) return 1; /* At least signature, version, totlen, count. */
|
||||
if (totlen > sdslen(link->rcvbuf)) return 1;
|
||||
if (totlen > link->rcvbuf_len) return 1;
|
||||
|
||||
if (ntohs(hdr->ver) != CLUSTER_PROTO_VER) {
|
||||
/* Can't handle messages of different versions. */
|
||||
@@ -1720,15 +1781,24 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
} else if (type == CLUSTERMSG_TYPE_MODULE) {
|
||||
uint32_t explen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||
|
||||
explen += sizeof(clusterMsgDataPublish) -
|
||||
explen += sizeof(clusterMsgModule) -
|
||||
3 + ntohl(hdr->data.module.msg.len);
|
||||
if (totlen != explen) return 1;
|
||||
}
|
||||
|
||||
/* Check if the sender is a known node. */
|
||||
/* Check if the sender is a known node. Note that for incoming connections
|
||||
* we don't store link->node information, but resolve the node by the
|
||||
* ID in the header each time in the current implementation. */
|
||||
sender = clusterLookupNode(hdr->sender);
|
||||
|
||||
/* Update the last time we saw any data from this node. We
|
||||
* use this in order to avoid detecting a timeout from a node that
|
||||
* is just sending a lot of data in the cluster bus, for instance
|
||||
* because of Pub/Sub. */
|
||||
if (sender) sender->data_received = now;
|
||||
|
||||
if (sender && !nodeInHandshake(sender)) {
|
||||
/* Update our curretEpoch if we see a newer epoch in the cluster. */
|
||||
/* Update our currentEpoch if we see a newer epoch in the cluster. */
|
||||
senderCurrentEpoch = ntohu64(hdr->currentEpoch);
|
||||
senderConfigEpoch = ntohu64(hdr->configEpoch);
|
||||
if (senderCurrentEpoch > server.cluster->currentEpoch)
|
||||
@@ -1741,7 +1811,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
}
|
||||
/* Update the replication offset info for this node. */
|
||||
sender->repl_offset = ntohu64(hdr->offset);
|
||||
sender->repl_offset_time = mstime();
|
||||
sender->repl_offset_time = now;
|
||||
/* If we are a slave performing a manual failover and our master
|
||||
* sent its offset while already paused, populate the MF state. */
|
||||
if (server.cluster->mf_end &&
|
||||
@@ -1855,7 +1925,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
* address. */
|
||||
serverLog(LL_DEBUG,"PONG contains mismatching sender ID. About node %.40s added %d ms ago, having flags %d",
|
||||
link->node->name,
|
||||
(int)(mstime()-(link->node->ctime)),
|
||||
(int)(now-(link->node->ctime)),
|
||||
link->node->flags);
|
||||
link->node->flags |= CLUSTER_NODE_NOADDR;
|
||||
link->node->ip[0] = '\0';
|
||||
@@ -1890,7 +1960,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
|
||||
/* Update our info about the node */
|
||||
if (link->node && type == CLUSTERMSG_TYPE_PONG) {
|
||||
link->node->pong_received = mstime();
|
||||
link->node->pong_received = now;
|
||||
link->node->ping_sent = 0;
|
||||
|
||||
/* The PFAIL condition can be reversed without external
|
||||
@@ -2037,7 +2107,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
"FAIL message received from %.40s about %.40s",
|
||||
hdr->sender, hdr->data.fail.about.nodename);
|
||||
failing->flags |= CLUSTER_NODE_FAIL;
|
||||
failing->fail_time = mstime();
|
||||
failing->fail_time = now;
|
||||
failing->flags &= ~CLUSTER_NODE_PFAIL;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
|
||||
CLUSTER_TODO_UPDATE_STATE);
|
||||
@@ -2090,9 +2160,9 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
/* Manual failover requested from slaves. Initialize the state
|
||||
* accordingly. */
|
||||
resetManualFailover();
|
||||
server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
|
||||
server.cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
|
||||
server.cluster->mf_slave = sender;
|
||||
pauseClients(mstime()+(CLUSTER_MF_TIMEOUT*2));
|
||||
pauseClients(now+(CLUSTER_MF_TIMEOUT*CLUSTER_MF_PAUSE_MULT));
|
||||
serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
|
||||
sender->name);
|
||||
} else if (type == CLUSTERMSG_TYPE_UPDATE) {
|
||||
@@ -2217,7 +2287,7 @@ void clusterReadHandler(connection *conn) {
|
||||
unsigned int readlen, rcvbuflen;
|
||||
|
||||
while(1) { /* Read as long as there is data to read. */
|
||||
rcvbuflen = sdslen(link->rcvbuf);
|
||||
rcvbuflen = link->rcvbuf_len;
|
||||
if (rcvbuflen < 8) {
|
||||
/* First, obtain the first 8 bytes to get the full message
|
||||
* length. */
|
||||
@@ -2253,7 +2323,15 @@ void clusterReadHandler(connection *conn) {
|
||||
return;
|
||||
} else {
|
||||
/* Read data and recast the pointer to the new buffer. */
|
||||
link->rcvbuf = sdscatlen(link->rcvbuf,buf,nread);
|
||||
size_t unused = link->rcvbuf_alloc - link->rcvbuf_len;
|
||||
if ((size_t)nread > unused) {
|
||||
size_t required = link->rcvbuf_len + nread;
|
||||
/* If less than 1mb, grow to twice the needed size, if larger grow by 1mb. */
|
||||
link->rcvbuf_alloc = required < RCVBUF_MAX_PREALLOC ? required * 2: required + RCVBUF_MAX_PREALLOC;
|
||||
link->rcvbuf = zrealloc(link->rcvbuf, link->rcvbuf_alloc);
|
||||
}
|
||||
memcpy(link->rcvbuf + link->rcvbuf_len, buf, nread);
|
||||
link->rcvbuf_len += nread;
|
||||
hdr = (clusterMsg*) link->rcvbuf;
|
||||
rcvbuflen += nread;
|
||||
}
|
||||
@@ -2261,8 +2339,11 @@ void clusterReadHandler(connection *conn) {
|
||||
/* Total length obtained? Process this packet. */
|
||||
if (rcvbuflen >= 8 && rcvbuflen == ntohl(hdr->totlen)) {
|
||||
if (clusterProcessPacket(link)) {
|
||||
sdsfree(link->rcvbuf);
|
||||
link->rcvbuf = sdsempty();
|
||||
if (link->rcvbuf_alloc > RCVBUF_INIT_LEN) {
|
||||
zfree(link->rcvbuf);
|
||||
link->rcvbuf = zmalloc(link->rcvbuf_alloc = RCVBUF_INIT_LEN);
|
||||
}
|
||||
link->rcvbuf_len = 0;
|
||||
} else {
|
||||
return; /* Link no longer valid. */
|
||||
}
|
||||
@@ -2415,7 +2496,7 @@ void clusterSetGossipEntry(clusterMsg *hdr, int i, clusterNode *n) {
|
||||
}
|
||||
|
||||
/* Send a PING or PONG packet to the specified node, making sure to add enough
|
||||
* gossip informations. */
|
||||
* gossip information. */
|
||||
void clusterSendPing(clusterLink *link, int type) {
|
||||
unsigned char *buf;
|
||||
clusterMsg *hdr;
|
||||
@@ -2435,7 +2516,7 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
* node_timeout we exchange with each other node at least 4 packets
|
||||
* (we ping in the worst case in node_timeout/2 time, and we also
|
||||
* receive two pings from the host), we have a total of 8 packets
|
||||
* in the node_timeout*2 falure reports validity time. So we have
|
||||
* in the node_timeout*2 failure reports validity time. So we have
|
||||
* that, for a single PFAIL node, we can expect to receive the following
|
||||
* number of failure reports (in the specified window of time):
|
||||
*
|
||||
@@ -2462,7 +2543,7 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
* faster to propagate to go from PFAIL to FAIL state. */
|
||||
int pfail_wanted = server.cluster->stats_pfail_nodes;
|
||||
|
||||
/* Compute the maxium totlen to allocate our buffer. We'll fix the totlen
|
||||
/* Compute the maximum totlen to allocate our buffer. We'll fix the totlen
|
||||
* later according to the number of gossip sections we really were able
|
||||
* to put inside the packet. */
|
||||
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||
@@ -2499,7 +2580,7 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
if (this->flags & (CLUSTER_NODE_HANDSHAKE|CLUSTER_NODE_NOADDR) ||
|
||||
(this->link == NULL && this->numslots == 0))
|
||||
{
|
||||
freshnodes--; /* Tecnically not correct, but saves CPU. */
|
||||
freshnodes--; /* Technically not correct, but saves CPU. */
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3084,7 +3165,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* If the previous failover attempt timedout and the retry time has
|
||||
/* If the previous failover attempt timeout and the retry time has
|
||||
* elapsed, we can setup a new one. */
|
||||
if (auth_age > auth_retry_time) {
|
||||
server.cluster->failover_auth_time = mstime() +
|
||||
@@ -3190,7 +3271,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
*
|
||||
* Slave migration is the process that allows a slave of a master that is
|
||||
* already covered by at least another slave, to "migrate" to a master that
|
||||
* is orpaned, that is, left with no working slaves.
|
||||
* is orphaned, that is, left with no working slaves.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* This function is responsible to decide if this replica should be migrated
|
||||
@@ -3207,7 +3288,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
* the nodes anyway, so we spend time into clusterHandleSlaveMigration()
|
||||
* if definitely needed.
|
||||
*
|
||||
* The fuction is called with a pre-computed max_slaves, that is the max
|
||||
* The function is called with a pre-computed max_slaves, that is the max
|
||||
* number of working (not in FAIL state) slaves for a single master.
|
||||
*
|
||||
* Additional conditions for migration are examined inside the function.
|
||||
@@ -3326,7 +3407,7 @@ void clusterHandleSlaveMigration(int max_slaves) {
|
||||
* data loss due to the asynchronous master-slave replication.
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Reset the manual failover state. This works for both masters and slavesa
|
||||
/* Reset the manual failover state. This works for both masters and slaves
|
||||
* as all the state about manual failover is cleared.
|
||||
*
|
||||
* The function can be used both to initialize the manual failover state at
|
||||
@@ -3515,7 +3596,6 @@ void clusterCron(void) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = dictGetVal(de);
|
||||
now = mstime(); /* Use an updated time at every iteration. */
|
||||
mstime_t delay;
|
||||
|
||||
if (node->flags &
|
||||
(CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE))
|
||||
@@ -3539,16 +3619,19 @@ void clusterCron(void) {
|
||||
this_slaves = okslaves;
|
||||
}
|
||||
|
||||
/* If we are waiting for the PONG more than half the cluster
|
||||
/* If we are not receiving any data for more than half the cluster
|
||||
* timeout, reconnect the link: maybe there is a connection
|
||||
* issue even if the node is alive. */
|
||||
mstime_t ping_delay = now - node->ping_sent;
|
||||
mstime_t data_delay = now - node->data_received;
|
||||
if (node->link && /* is connected */
|
||||
now - node->link->ctime >
|
||||
server.cluster_node_timeout && /* was not already reconnected */
|
||||
node->ping_sent && /* we already sent a ping */
|
||||
node->pong_received < node->ping_sent && /* still waiting pong */
|
||||
/* and we are waiting for the pong more than timeout/2 */
|
||||
now - node->ping_sent > server.cluster_node_timeout/2)
|
||||
ping_delay > server.cluster_node_timeout/2 &&
|
||||
/* and in such interval we are not seeing any traffic at all. */
|
||||
data_delay > server.cluster_node_timeout/2)
|
||||
{
|
||||
/* Disconnect the link, it will be reconnected automatically. */
|
||||
freeClusterLink(node->link);
|
||||
@@ -3580,12 +3663,18 @@ void clusterCron(void) {
|
||||
/* Check only if we have an active ping for this instance. */
|
||||
if (node->ping_sent == 0) continue;
|
||||
|
||||
/* Compute the delay of the PONG. Note that if we already received
|
||||
* the PONG, then node->ping_sent is zero, so can't reach this
|
||||
* code at all. */
|
||||
delay = now - node->ping_sent;
|
||||
/* Check if this node looks unreachable.
|
||||
* Note that if we already received the PONG, then node->ping_sent
|
||||
* is zero, so can't reach this code at all, so we don't risk of
|
||||
* checking for a PONG delay if we didn't sent the PING.
|
||||
*
|
||||
* We also consider every incoming data as proof of liveness, since
|
||||
* our cluster bus link is also used for data: under heavy data
|
||||
* load pong delays are possible. */
|
||||
mstime_t node_delay = (ping_delay < data_delay) ? ping_delay :
|
||||
data_delay;
|
||||
|
||||
if (delay > server.cluster_node_timeout) {
|
||||
if (node_delay > server.cluster_node_timeout) {
|
||||
/* Timeout reached. Set the node as possibly failing if it is
|
||||
* not already in this state. */
|
||||
if (!(node->flags & (CLUSTER_NODE_PFAIL|CLUSTER_NODE_FAIL))) {
|
||||
@@ -3609,7 +3698,7 @@ void clusterCron(void) {
|
||||
replicationSetMaster(myself->slaveof->ip, myself->slaveof->port);
|
||||
}
|
||||
|
||||
/* Abourt a manual failover if the timeout is reached. */
|
||||
/* Abort a manual failover if the timeout is reached. */
|
||||
manualFailoverCheckTimeout();
|
||||
|
||||
if (nodeIsSlave(myself)) {
|
||||
@@ -3714,12 +3803,12 @@ int clusterNodeSetSlotBit(clusterNode *n, int slot) {
|
||||
* target for replicas migration, if and only if at least one of
|
||||
* the other masters has slaves right now.
|
||||
*
|
||||
* Normally masters are valid targerts of replica migration if:
|
||||
* Normally masters are valid targets of replica migration if:
|
||||
* 1. The used to have slaves (but no longer have).
|
||||
* 2. They are slaves failing over a master that used to have slaves.
|
||||
*
|
||||
* However new masters with slots assigned are considered valid
|
||||
* migration tagets if the rest of the cluster is not a slave-less.
|
||||
* migration targets if the rest of the cluster is not a slave-less.
|
||||
*
|
||||
* See https://github.com/antirez/redis/issues/3043 for more info. */
|
||||
if (n->numslots == 1 && clusterMastersHaveSlaves())
|
||||
@@ -3903,7 +3992,7 @@ void clusterUpdateState(void) {
|
||||
* A) If no other node is in charge according to the current cluster
|
||||
* configuration, we add these slots to our node.
|
||||
* B) If according to our config other nodes are already in charge for
|
||||
* this lots, we set the slots as IMPORTING from our point of view
|
||||
* this slots, we set the slots as IMPORTING from our point of view
|
||||
* in order to justify we have those slots, and in order to make
|
||||
* redis-trib aware of the issue, so that it can try to fix it.
|
||||
* 2) If we find data in a DB different than DB0 we return C_ERR to
|
||||
@@ -4049,11 +4138,15 @@ sds clusterGenNodeDescription(clusterNode *node) {
|
||||
else
|
||||
ci = sdscatlen(ci," - ",3);
|
||||
|
||||
unsigned long long nodeEpoch = node->configEpoch;
|
||||
if (nodeIsSlave(node) && node->slaveof) {
|
||||
nodeEpoch = node->slaveof->configEpoch;
|
||||
}
|
||||
/* Latency from the POV of this node, config epoch, link status */
|
||||
ci = sdscatprintf(ci,"%lld %lld %llu %s",
|
||||
(long long) node->ping_sent,
|
||||
(long long) node->pong_received,
|
||||
(unsigned long long) node->configEpoch,
|
||||
nodeEpoch,
|
||||
(node->link || node->flags & CLUSTER_NODE_MYSELF) ?
|
||||
"connected" : "disconnected");
|
||||
|
||||
@@ -4177,11 +4270,17 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = dictGetVal(de);
|
||||
int j = 0, start = -1;
|
||||
int i, nested_elements = 0;
|
||||
|
||||
/* Skip slaves (that are iterated when producing the output of their
|
||||
* master) and masters not serving any slot. */
|
||||
if (!nodeIsMaster(node) || node->numslots == 0) continue;
|
||||
|
||||
for(i = 0; i < node->numslaves; i++) {
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
nested_elements++;
|
||||
}
|
||||
|
||||
for (j = 0; j < CLUSTER_SLOTS; j++) {
|
||||
int bit, i;
|
||||
|
||||
@@ -4189,8 +4288,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
if (start == -1) start = j;
|
||||
}
|
||||
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
|
||||
int nested_elements = 3; /* slots (2) + master addr (1). */
|
||||
void *nested_replylen = addReplyDeferredLen(c);
|
||||
addReplyArrayLen(c, nested_elements + 3); /* slots (2) + master addr (1). */
|
||||
|
||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||
|
||||
@@ -4220,9 +4318,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
addReplyBulkCString(c, node->slaves[i]->ip);
|
||||
addReplyLongLong(c, node->slaves[i]->port);
|
||||
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
|
||||
nested_elements++;
|
||||
}
|
||||
setDeferredArrayLen(c, nested_replylen, nested_elements);
|
||||
num_masters++;
|
||||
}
|
||||
}
|
||||
@@ -4248,7 +4344,7 @@ void clusterCommand(client *c) {
|
||||
"FORGET <node-id> -- Remove a node from the cluster.",
|
||||
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
|
||||
"FLUSHSLOTS -- Delete current node own slots information.",
|
||||
"INFO - Return onformation about the cluster.",
|
||||
"INFO - Return information about the cluster.",
|
||||
"KEYSLOT <key> -- Return the hash slot for <key>.",
|
||||
"MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
|
||||
"MYID -- Return the node id.",
|
||||
@@ -4259,6 +4355,7 @@ void clusterCommand(client *c) {
|
||||
"SET-config-epoch <epoch> - Set config epoch of current node.",
|
||||
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
|
||||
"REPLICAS <node-id> -- Return <node-id> replicas.",
|
||||
"SAVECONFIG - Force saving cluster configuration on disk.",
|
||||
"SLOTS -- Return information about slots range mappings. Each range is made of:",
|
||||
" start, end, master and replicas IP addresses, ports and ids",
|
||||
NULL
|
||||
@@ -4425,7 +4522,7 @@ NULL
|
||||
}
|
||||
/* If this slot is in migrating status but we have no keys
|
||||
* for it assigning the slot to another node will clear
|
||||
* the migratig status. */
|
||||
* the migrating status. */
|
||||
if (countKeysInSlot(slot) == 0 &&
|
||||
server.cluster->migrating_slots_to[slot])
|
||||
server.cluster->migrating_slots_to[slot] = NULL;
|
||||
@@ -4770,7 +4867,7 @@ NULL
|
||||
server.cluster->currentEpoch = epoch;
|
||||
/* No need to fsync the config here since in the unlucky event
|
||||
* of a failure to persist the config, the conflict resolution code
|
||||
* will assign an unique config to this node. */
|
||||
* will assign a unique config to this node. */
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|
|
||||
CLUSTER_TODO_SAVE_CONFIG);
|
||||
addReply(c,shared.ok);
|
||||
@@ -4818,7 +4915,7 @@ void createDumpPayload(rio *payload, robj *o, robj *key) {
|
||||
unsigned char buf[2];
|
||||
uint64_t crc;
|
||||
|
||||
/* Serialize the object in a RDB-like format. It consist of an object type
|
||||
/* Serialize the object in an RDB-like format. It consist of an object type
|
||||
* byte followed by the serialized object. This is understood by RESTORE. */
|
||||
rioInitWithBuffer(payload,sdsempty());
|
||||
serverAssert(rdbSaveObjectType(payload,o));
|
||||
@@ -4929,7 +5026,8 @@ void restoreCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Make sure this key does not already exist here... */
|
||||
if (!replace && lookupKeyWrite(c->db,c->argv[1]) != NULL) {
|
||||
robj *key = c->argv[1];
|
||||
if (!replace && lookupKeyWrite(c->db,key) != NULL) {
|
||||
addReply(c,shared.busykeyerr);
|
||||
return;
|
||||
}
|
||||
@@ -4951,23 +5049,38 @@ void restoreCommand(client *c) {
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload,c->argv[1])) == NULL))
|
||||
((obj = rdbLoadObject(type,&payload,key->ptr)) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove the old key if needed. */
|
||||
if (replace) dbDelete(c->db,c->argv[1]);
|
||||
int deleted = 0;
|
||||
if (replace)
|
||||
deleted = dbDelete(c->db,key);
|
||||
|
||||
if (ttl && !absttl) ttl+=mstime();
|
||||
if (ttl && checkAlreadyExpired(ttl)) {
|
||||
if (deleted) {
|
||||
rewriteClientCommandVector(c,2,shared.del,key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
decrRefCount(obj);
|
||||
addReply(c, shared.ok);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create the key and set the TTL if any */
|
||||
dbAdd(c->db,c->argv[1],obj);
|
||||
dbAdd(c->db,key,obj);
|
||||
if (ttl) {
|
||||
if (!absttl) ttl+=mstime();
|
||||
setExpire(c,c->db,c->argv[1],ttl);
|
||||
setExpire(c,c->db,key,ttl);
|
||||
}
|
||||
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",key,c->db->id);
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -5082,15 +5195,17 @@ void migrateCloseTimedoutSockets(void) {
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* MIGRATE host port key dbid timeout [COPY | REPLACE | AUTH password]
|
||||
/* MIGRATE host port key dbid timeout [COPY | REPLACE | AUTH password |
|
||||
* AUTH2 username password]
|
||||
*
|
||||
* On in the multiple keys form:
|
||||
*
|
||||
* MIGRATE host port "" dbid timeout [COPY | REPLACE | AUTH password] KEYS key1
|
||||
* key2 ... keyN */
|
||||
* MIGRATE host port "" dbid timeout [COPY | REPLACE | AUTH password |
|
||||
* AUTH2 username password] KEYS key1 key2 ... keyN */
|
||||
void migrateCommand(client *c) {
|
||||
migrateCachedSocket *cs;
|
||||
int copy = 0, replace = 0, j;
|
||||
char *username = NULL;
|
||||
char *password = NULL;
|
||||
long timeout;
|
||||
long dbid;
|
||||
@@ -5108,7 +5223,7 @@ void migrateCommand(client *c) {
|
||||
|
||||
/* Parse additional options */
|
||||
for (j = 6; j < c->argc; j++) {
|
||||
int moreargs = j < c->argc-1;
|
||||
int moreargs = (c->argc-1) - j;
|
||||
if (!strcasecmp(c->argv[j]->ptr,"copy")) {
|
||||
copy = 1;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"replace")) {
|
||||
@@ -5120,6 +5235,13 @@ void migrateCommand(client *c) {
|
||||
}
|
||||
j++;
|
||||
password = c->argv[j]->ptr;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"auth2")) {
|
||||
if (moreargs < 2) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
username = c->argv[++j]->ptr;
|
||||
password = c->argv[++j]->ptr;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"keys")) {
|
||||
if (sdslen(c->argv[3]->ptr) != 0) {
|
||||
addReplyError(c,
|
||||
@@ -5180,8 +5302,13 @@ try_again:
|
||||
|
||||
/* Authentication */
|
||||
if (password) {
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',2));
|
||||
int arity = username ? 3 : 2;
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',arity));
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"AUTH",4));
|
||||
if (username) {
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,username,
|
||||
sdslen(username)));
|
||||
}
|
||||
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,password,
|
||||
sdslen(password)));
|
||||
}
|
||||
@@ -5313,7 +5440,8 @@ try_again:
|
||||
if (!copy) {
|
||||
/* No COPY option: remove the local key, signal the change. */
|
||||
dbDelete(c->db,kv[j]);
|
||||
signalModifiedKey(c->db,kv[j]);
|
||||
signalModifiedKey(c,c->db,kv[j]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",kv[j],c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
/* Populate the argument vector to replace the old one. */
|
||||
@@ -5454,7 +5582,7 @@ void readwriteCommand(client *c) {
|
||||
* resharding in progress).
|
||||
*
|
||||
* On success the function returns the node that is able to serve the request.
|
||||
* If the node is not 'myself' a redirection must be perfomed. The kind of
|
||||
* If the node is not 'myself' a redirection must be performed. The kind of
|
||||
* redirection is specified setting the integer passed by reference
|
||||
* 'error_code', which will be set to CLUSTER_REDIR_ASK or
|
||||
* CLUSTER_REDIR_MOVED.
|
||||
@@ -5476,7 +5604,7 @@ void readwriteCommand(client *c) {
|
||||
* already "down" but it is fragile to rely on the update of the global state,
|
||||
* so we also handle it here.
|
||||
*
|
||||
* CLUSTER_REDIR_DOWN_STATE and CLUSTER_REDIR_DOWN_RO_STATE if the cluster is
|
||||
* CLUSTER_REDIR_DOWN_STATE and CLUSTER_REDIR_DOWN_RO_STATE if the cluster is
|
||||
* down but the user attempts to execute a command that addresses one or more keys. */
|
||||
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *error_code) {
|
||||
clusterNode *n = NULL;
|
||||
@@ -5527,7 +5655,10 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
margc = ms->commands[i].argc;
|
||||
margv = ms->commands[i].argv;
|
||||
|
||||
keyindex = getKeysFromCommand(mcmd,margv,margc,&numkeys);
|
||||
getKeysResult result = GETKEYS_RESULT_INIT;
|
||||
numkeys = getKeysFromCommand(mcmd,margv,margc,&result);
|
||||
keyindex = result.keys;
|
||||
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
robj *thiskey = margv[keyindex[j]];
|
||||
int thisslot = keyHashSlot((char*)thiskey->ptr,
|
||||
@@ -5545,7 +5676,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
* not trapped earlier in processCommand(). Report the same
|
||||
* error to the client. */
|
||||
if (n == NULL) {
|
||||
getKeysFreeResult(keyindex);
|
||||
getKeysFreeResult(&result);
|
||||
if (error_code)
|
||||
*error_code = CLUSTER_REDIR_DOWN_UNBOUND;
|
||||
return NULL;
|
||||
@@ -5569,7 +5700,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
if (!equalStringObjects(firstkey,thiskey)) {
|
||||
if (slot != thisslot) {
|
||||
/* Error: multiple keys from different slots. */
|
||||
getKeysFreeResult(keyindex);
|
||||
getKeysFreeResult(&result);
|
||||
if (error_code)
|
||||
*error_code = CLUSTER_REDIR_CROSS_SLOT;
|
||||
return NULL;
|
||||
@@ -5581,14 +5712,14 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
}
|
||||
}
|
||||
|
||||
/* Migarting / Improrting slot? Count keys we don't have. */
|
||||
/* Migrating / Importing slot? Count keys we don't have. */
|
||||
if ((migrating_slot || importing_slot) &&
|
||||
lookupKeyRead(&server.db[0],thiskey) == NULL)
|
||||
{
|
||||
missing_keys++;
|
||||
}
|
||||
}
|
||||
getKeysFreeResult(keyindex);
|
||||
getKeysFreeResult(&result);
|
||||
}
|
||||
|
||||
/* No key at all in command? then we can serve the request
|
||||
@@ -5650,10 +5781,12 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
}
|
||||
|
||||
/* Handle the read-only client case reading from a slave: if this
|
||||
* node is a slave and the request is about an hash slot our master
|
||||
* node is a slave and the request is about a hash slot our master
|
||||
* is serving, we can reply without redirection. */
|
||||
int is_readonly_command = (c->cmd->flags & CMD_READONLY) ||
|
||||
(c->cmd->proc == execCommand && !(c->mstate.cmd_inv_flags & CMD_READONLY));
|
||||
if (c->flags & CLIENT_READONLY &&
|
||||
(cmd->flags & CMD_READONLY || cmd->proc == evalCommand ||
|
||||
(is_readonly_command || cmd->proc == evalCommand ||
|
||||
cmd->proc == evalShaCommand) &&
|
||||
nodeIsSlave(myself) &&
|
||||
myself->slaveof == n)
|
||||
@@ -5662,7 +5795,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
}
|
||||
|
||||
/* Base case: just return the right node. However if this node is not
|
||||
* myself, set error_code to MOVED since we need to issue a rediretion. */
|
||||
* myself, set error_code to MOVED since we need to issue a redirection. */
|
||||
if (n != myself && error_code) *error_code = CLUSTER_REDIR_MOVED;
|
||||
return n;
|
||||
}
|
||||
@@ -5708,7 +5841,7 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
|
||||
* 3) The client may remain blocked forever (or up to the max timeout time)
|
||||
* waiting for a key change that will never happen.
|
||||
*
|
||||
* If the client is found to be blocked into an hash slot this node no
|
||||
* If the client is found to be blocked into a hash slot this node no
|
||||
* longer handles, the client is sent a redirection error, and the function
|
||||
* returns 1. Otherwise 0 is returned and no operation is performed. */
|
||||
int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
@@ -5736,6 +5869,15 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
int slot = keyHashSlot((char*)key->ptr, sdslen(key->ptr));
|
||||
clusterNode *node = server.cluster->slots[slot];
|
||||
|
||||
/* if the client is read-only and attempting to access key that our
|
||||
* replica can handle, allow it. */
|
||||
if ((c->flags & CLIENT_READONLY) &&
|
||||
(c->lastcmd->flags & CMD_READONLY) &&
|
||||
nodeIsSlave(myself) && myself->slaveof == node)
|
||||
{
|
||||
node = myself;
|
||||
}
|
||||
|
||||
/* We send an error and unblock the client if:
|
||||
* 1) The slot is unassigned, emitting a cluster down error.
|
||||
* 2) The slot is not handled by this node, nor being imported. */
|
||||
|
||||
+9
-5
@@ -38,7 +38,9 @@ typedef struct clusterLink {
|
||||
mstime_t ctime; /* Link creation time */
|
||||
connection *conn; /* Connection to remote node */
|
||||
sds sndbuf; /* Packet send buffer */
|
||||
sds rcvbuf; /* Packet reception buffer */
|
||||
char *rcvbuf; /* Packet reception buffer */
|
||||
size_t rcvbuf_len; /* Used size of rcvbuf */
|
||||
size_t rcvbuf_alloc; /* Used size of rcvbuf */
|
||||
struct clusterNode *node; /* Node related to this link if any, or NULL */
|
||||
} clusterLink;
|
||||
|
||||
@@ -51,8 +53,8 @@ typedef struct clusterLink {
|
||||
#define CLUSTER_NODE_HANDSHAKE 32 /* We have still to exchange the first ping */
|
||||
#define CLUSTER_NODE_NOADDR 64 /* We don't know the address of this node */
|
||||
#define CLUSTER_NODE_MEET 128 /* Send a MEET message to this node */
|
||||
#define CLUSTER_NODE_MIGRATE_TO 256 /* Master elegible for replica migration. */
|
||||
#define CLUSTER_NODE_NOFAILOVER 512 /* Slave will not try to failver. */
|
||||
#define CLUSTER_NODE_MIGRATE_TO 256 /* Master eligible for replica migration. */
|
||||
#define CLUSTER_NODE_NOFAILOVER 512 /* Slave will not try to failover. */
|
||||
#define CLUSTER_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 & CLUSTER_NODE_MASTER)
|
||||
@@ -124,6 +126,7 @@ typedef struct clusterNode {
|
||||
tables. */
|
||||
mstime_t ping_sent; /* Unix time we sent latest ping */
|
||||
mstime_t pong_received; /* Unix time we received the pong */
|
||||
mstime_t data_received; /* Unix time we received any data */
|
||||
mstime_t fail_time; /* Unix time when FAIL flag was set */
|
||||
mstime_t voted_time; /* Last time we voted for a slave of this master */
|
||||
mstime_t repl_offset_time; /* Unix time we received offset for this node */
|
||||
@@ -163,10 +166,10 @@ typedef struct clusterState {
|
||||
clusterNode *mf_slave; /* Slave performing the manual failover. */
|
||||
/* Manual failover state of slave. */
|
||||
long long mf_master_offset; /* Master offset the slave needs to start MF
|
||||
or zero if stil not received. */
|
||||
or zero if still not received. */
|
||||
int mf_can_start; /* If non-zero signal that the manual failover
|
||||
can start requesting masters vote. */
|
||||
/* The followign fields are used by masters to take state on elections. */
|
||||
/* The following 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(). */
|
||||
/* Messages received and sent by type. */
|
||||
@@ -282,5 +285,6 @@ typedef struct {
|
||||
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *ask);
|
||||
int clusterRedirectBlockedClientIfNeeded(client *c);
|
||||
void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code);
|
||||
unsigned long getClusterConnectionsCount(void);
|
||||
|
||||
#endif /* __CLUSTER_H */
|
||||
|
||||
+325
-182
@@ -98,6 +98,21 @@ configEnum repl_diskless_load_enum[] = {
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
configEnum tls_auth_clients_enum[] = {
|
||||
{"no", TLS_CLIENT_AUTH_NO},
|
||||
{"yes", TLS_CLIENT_AUTH_YES},
|
||||
{"optional", TLS_CLIENT_AUTH_OPTIONAL},
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
configEnum oom_score_adj_enum[] = {
|
||||
{"no", OOM_SCORE_ADJ_NO},
|
||||
{"yes", OOM_SCORE_RELATIVE},
|
||||
{"relative", OOM_SCORE_RELATIVE},
|
||||
{"absolute", OOM_SCORE_ADJ_ABSOLUTE},
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
/* Output buffer limits presets. */
|
||||
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
|
||||
{0, 0, 0}, /* normal */
|
||||
@@ -105,15 +120,18 @@ clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
|
||||
{1024*1024*32, 1024*1024*8, 60} /* pubsub */
|
||||
};
|
||||
|
||||
/* OOM Score defaults */
|
||||
int configOOMScoreAdjValuesDefaults[CONFIG_OOM_COUNT] = { 0, 200, 800 };
|
||||
|
||||
/* Generic config infrastructure function pointers
|
||||
* int is_valid_fn(val, err)
|
||||
* Return 1 when val is valid, and 0 when invalid.
|
||||
* Optionslly set err to a static error string.
|
||||
* Optionally set err to a static error string.
|
||||
* int update_fn(val, prev, err)
|
||||
* This function is called only for CONFIG SET command (not at config file parsing)
|
||||
* It is called after the actual config is applied,
|
||||
* Return 1 for success, and 0 for failure.
|
||||
* Optionslly set err to a static error string.
|
||||
* Optionally set err to a static error string.
|
||||
* On failure the config change will be reverted.
|
||||
*/
|
||||
|
||||
@@ -190,8 +208,9 @@ typedef struct typeInterface {
|
||||
void (*init)(typeData data);
|
||||
/* Called on server start, should return 1 on success, 0 on error and should set err */
|
||||
int (*load)(typeData data, sds *argc, int argv, char **err);
|
||||
/* Called on CONFIG SET, returns 1 on success, 0 on error */
|
||||
int (*set)(typeData data, sds value, char **err);
|
||||
/* Called on server startup and CONFIG SET, returns 1 on success, 0 on error
|
||||
* and can set a verbose err string, update is true when called from CONFIG SET */
|
||||
int (*set)(typeData data, sds value, int update, char **err);
|
||||
/* Called on CONFIG GET, required to add output to the client */
|
||||
void (*get)(client *c, typeData data);
|
||||
/* Called on CONFIG REWRITE, required to rewrite the config state */
|
||||
@@ -279,6 +298,63 @@ void queueLoadModule(sds path, sds *argv, int argc) {
|
||||
listAddNodeTail(server.loadmodule_queue,loadmod);
|
||||
}
|
||||
|
||||
/* Parse an array of CONFIG_OOM_COUNT sds strings, validate and populate
|
||||
* server.oom_score_adj_values if valid.
|
||||
*/
|
||||
|
||||
static int updateOOMScoreAdjValues(sds *args, char **err, int apply) {
|
||||
int i;
|
||||
int values[CONFIG_OOM_COUNT];
|
||||
|
||||
for (i = 0; i < CONFIG_OOM_COUNT; i++) {
|
||||
char *eptr;
|
||||
long long val = strtoll(args[i], &eptr, 10);
|
||||
|
||||
if (*eptr != '\0' || val < -2000 || val > 2000) {
|
||||
if (err) *err = "Invalid oom-score-adj-values, elements must be between -2000 and 2000.";
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
values[i] = val;
|
||||
}
|
||||
|
||||
/* Verify that the values make sense. If they don't omit a warning but
|
||||
* keep the configuration, which may still be valid for privileged processes.
|
||||
*/
|
||||
|
||||
if (values[CONFIG_OOM_REPLICA] < values[CONFIG_OOM_MASTER] ||
|
||||
values[CONFIG_OOM_BGCHILD] < values[CONFIG_OOM_REPLICA]) {
|
||||
serverLog(LOG_WARNING,
|
||||
"The oom-score-adj-values configuration may not work for non-privileged processes! "
|
||||
"Please consult the documentation.");
|
||||
}
|
||||
|
||||
/* Store values, retain previous config for rollback in case we fail. */
|
||||
int old_values[CONFIG_OOM_COUNT];
|
||||
for (i = 0; i < CONFIG_OOM_COUNT; i++) {
|
||||
old_values[i] = server.oom_score_adj_values[i];
|
||||
server.oom_score_adj_values[i] = values[i];
|
||||
}
|
||||
|
||||
/* When parsing the config file, we want to apply only when all is done. */
|
||||
if (!apply)
|
||||
return C_OK;
|
||||
|
||||
/* Update */
|
||||
if (setOOMScoreAdj(-1) == C_ERR) {
|
||||
/* Roll back */
|
||||
for (i = 0; i < CONFIG_OOM_COUNT; i++)
|
||||
server.oom_score_adj_values[i] = old_values[i];
|
||||
|
||||
if (err)
|
||||
*err = "Failed to apply oom-score-adj-values configuration, check server logs.";
|
||||
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
void initConfigValues() {
|
||||
for (standardConfig *config = configs; config->name != NULL; config++) {
|
||||
config->interface.init(config->data);
|
||||
@@ -287,6 +363,7 @@ void initConfigValues() {
|
||||
|
||||
void loadServerConfigFromString(char *config) {
|
||||
char *err = NULL;
|
||||
char buf[1024];
|
||||
int linenum = 0, totlines, i;
|
||||
int slaveof_linenum = 0;
|
||||
sds *lines;
|
||||
@@ -323,7 +400,11 @@ void loadServerConfigFromString(char *config) {
|
||||
if ((!strcasecmp(argv[0],config->name) ||
|
||||
(config->alias && !strcasecmp(argv[0],config->alias))))
|
||||
{
|
||||
if (!config->interface.load(config->data, argv, argc, &err)) {
|
||||
if (argc != 2) {
|
||||
err = "wrong number of arguments";
|
||||
goto loaderr;
|
||||
}
|
||||
if (!config->interface.set(config->data, argv[1], 0, &err)) {
|
||||
goto loaderr;
|
||||
}
|
||||
|
||||
@@ -344,6 +425,10 @@ void loadServerConfigFromString(char *config) {
|
||||
if (addresses > CONFIG_BINDADDR_MAX) {
|
||||
err = "Too many bind addresses specified"; goto loaderr;
|
||||
}
|
||||
/* Free old bind addresses */
|
||||
for (j = 0; j < server.bindaddr_count; j++) {
|
||||
zfree(server.bindaddr[j]);
|
||||
}
|
||||
for (j = 0; j < addresses; j++)
|
||||
server.bindaddr[j] = zstrdup(argv[j+1]);
|
||||
server.bindaddr_count = addresses;
|
||||
@@ -393,8 +478,17 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if ((!strcasecmp(argv[0],"slaveof") ||
|
||||
!strcasecmp(argv[0],"replicaof")) && argc == 3) {
|
||||
slaveof_linenum = linenum;
|
||||
sdsfree(server.masterhost);
|
||||
if (!strcasecmp(argv[1], "no") && !strcasecmp(argv[2], "one")) {
|
||||
server.masterhost = NULL;
|
||||
continue;
|
||||
}
|
||||
server.masterhost = sdsnew(argv[1]);
|
||||
server.masterport = atoi(argv[2]);
|
||||
char *ptr;
|
||||
server.masterport = strtol(argv[2], &ptr, 10);
|
||||
if (server.masterport < 0 || server.masterport > 65535 || *ptr != '\0') {
|
||||
err = "Invalid master port"; goto loaderr;
|
||||
}
|
||||
server.repl_state = REPL_STATE_CONNECT;
|
||||
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
|
||||
if (strlen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
|
||||
@@ -402,11 +496,20 @@ void loadServerConfigFromString(char *config) {
|
||||
goto loaderr;
|
||||
}
|
||||
/* The old "requirepass" directive just translates to setting
|
||||
* a password to the default user. */
|
||||
* a password to the default user. The only thing we do
|
||||
* additionally is to remember the cleartext password in this
|
||||
* case, for backward compatibility with Redis <= 5. */
|
||||
ACLSetUser(DefaultUser,"resetpass",-1);
|
||||
sds aclop = sdscatprintf(sdsempty(),">%s",argv[1]);
|
||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||
sdsfree(aclop);
|
||||
sdsfree(server.requirepass);
|
||||
server.requirepass = NULL;
|
||||
if (sdslen(argv[1])) {
|
||||
sds aclop = sdscatprintf(sdsempty(),">%s",argv[1]);
|
||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||
sdsfree(aclop);
|
||||
server.requirepass = sdsnew(argv[1]);
|
||||
} else {
|
||||
ACLSetUser(DefaultUser,"nopass",-1);
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
|
||||
/* DEAD OPTION */
|
||||
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
|
||||
@@ -460,6 +563,8 @@ void loadServerConfigFromString(char *config) {
|
||||
server.client_obuf_limits[class].hard_limit_bytes = hard;
|
||||
server.client_obuf_limits[class].soft_limit_bytes = soft;
|
||||
server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
|
||||
} else if (!strcasecmp(argv[0],"oom-score-adj-values") && argc == 1 + CONFIG_OOM_COUNT) {
|
||||
if (updateOOMScoreAdjValues(&argv[1], &err, 0) == C_ERR) goto loaderr;
|
||||
} else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) {
|
||||
int flags = keyspaceEventsStringToFlags(argv[1]);
|
||||
|
||||
@@ -471,7 +576,6 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if (!strcasecmp(argv[0],"user") && argc >= 2) {
|
||||
int argc_err;
|
||||
if (ACLAppendUserForLoading(argv,argc,&argc_err) == C_ERR) {
|
||||
char buf[1024];
|
||||
char *errmsg = ACLSetUserStringError();
|
||||
snprintf(buf,sizeof(buf),"Error in user declaration '%s': %s",
|
||||
argv[argc_err],errmsg);
|
||||
@@ -509,7 +613,8 @@ void loadServerConfigFromString(char *config) {
|
||||
return;
|
||||
|
||||
loaderr:
|
||||
fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR ***\n");
|
||||
fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR (Redis %s) ***\n",
|
||||
REDIS_VERSION);
|
||||
fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);
|
||||
fprintf(stderr, ">>> '%s'\n", lines[i]);
|
||||
fprintf(stderr, "%s\n", err);
|
||||
@@ -536,7 +641,8 @@ void loadServerConfig(char *filename, char *options) {
|
||||
} else {
|
||||
if ((fp = fopen(filename,"r")) == NULL) {
|
||||
serverLog(LL_WARNING,
|
||||
"Fatal error, can't open config file '%s'", filename);
|
||||
"Fatal error, can't open config file '%s': %s",
|
||||
filename, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -599,7 +705,7 @@ void configSetCommand(client *c) {
|
||||
if(config->modifiable && (!strcasecmp(c->argv[2]->ptr,config->name) ||
|
||||
(config->alias && !strcasecmp(c->argv[2]->ptr,config->alias))))
|
||||
{
|
||||
if (!config->interface.set(config->data,o->ptr, &errstr)) {
|
||||
if (!config->interface.set(config->data,o->ptr,1,&errstr)) {
|
||||
goto badfmt;
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
@@ -613,11 +719,20 @@ void configSetCommand(client *c) {
|
||||
config_set_special_field("requirepass") {
|
||||
if (sdslen(o->ptr) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt;
|
||||
/* The old "requirepass" directive just translates to setting
|
||||
* a password to the default user. */
|
||||
* a password to the default user. The only thing we do
|
||||
* additionally is to remember the cleartext password in this
|
||||
* case, for backward compatibility with Redis <= 5. */
|
||||
ACLSetUser(DefaultUser,"resetpass",-1);
|
||||
sds aclop = sdscatprintf(sdsempty(),">%s",(char*)o->ptr);
|
||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||
sdsfree(aclop);
|
||||
sdsfree(server.requirepass);
|
||||
server.requirepass = NULL;
|
||||
if (sdslen(o->ptr)) {
|
||||
sds aclop = sdscatprintf(sdsempty(),">%s",(char*)o->ptr);
|
||||
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
|
||||
sdsfree(aclop);
|
||||
server.requirepass = sdsnew(o->ptr);
|
||||
} else {
|
||||
ACLSetUser(DefaultUser,"nopass",-1);
|
||||
}
|
||||
} config_set_special_field("save") {
|
||||
int vlen, j;
|
||||
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
|
||||
@@ -703,6 +818,17 @@ void configSetCommand(client *c) {
|
||||
server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
|
||||
}
|
||||
sdsfreesplitres(v,vlen);
|
||||
} config_set_special_field("oom-score-adj-values") {
|
||||
int vlen;
|
||||
int success = 1;
|
||||
|
||||
sds *v = sdssplitlen(o->ptr, sdslen(o->ptr), " ", 1, &vlen);
|
||||
if (vlen != CONFIG_OOM_COUNT || updateOOMScoreAdjValues(v, &errstr, 1) == C_ERR)
|
||||
success = 0;
|
||||
|
||||
sdsfreesplitres(v, vlen);
|
||||
if (!success)
|
||||
goto badfmt;
|
||||
} config_set_special_field("notify-keyspace-events") {
|
||||
int flags = keyspaceEventsStringToFlags(o->ptr);
|
||||
|
||||
@@ -720,7 +846,7 @@ void configSetCommand(client *c) {
|
||||
* config_set_memory_field(name,var) */
|
||||
} config_set_memory_field(
|
||||
"client-query-buffer-limit",server.client_max_querybuf_len) {
|
||||
/* Everyhing else is an error... */
|
||||
/* Everything else is an error... */
|
||||
} config_set_else {
|
||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||
(char*)c->argv[2]->ptr);
|
||||
@@ -889,7 +1015,7 @@ void configGetCommand(client *c) {
|
||||
}
|
||||
if (stringmatch(pattern,"requirepass",1)) {
|
||||
addReplyBulkCString(c,"requirepass");
|
||||
sds password = ACLDefaultUserFirstPassword();
|
||||
sds password = server.requirepass;
|
||||
if (password) {
|
||||
addReplyBulkCBuffer(c,password,sdslen(password));
|
||||
} else {
|
||||
@@ -898,6 +1024,22 @@ void configGetCommand(client *c) {
|
||||
matches++;
|
||||
}
|
||||
|
||||
if (stringmatch(pattern,"oom-score-adj-values",0)) {
|
||||
sds buf = sdsempty();
|
||||
int j;
|
||||
|
||||
for (j = 0; j < CONFIG_OOM_COUNT; j++) {
|
||||
buf = sdscatprintf(buf,"%d", server.oom_score_adj_values[j]);
|
||||
if (j != CONFIG_OOM_COUNT-1)
|
||||
buf = sdscatlen(buf," ",1);
|
||||
}
|
||||
|
||||
addReplyBulkCString(c,"oom-score-adj-values");
|
||||
addReplyBulkCString(c,buf);
|
||||
sdsfree(buf);
|
||||
matches++;
|
||||
}
|
||||
|
||||
setDeferredMapLen(c,replylen,matches);
|
||||
}
|
||||
|
||||
@@ -945,6 +1087,8 @@ struct rewriteConfigState {
|
||||
sds *lines; /* Current lines as an array of sds strings */
|
||||
int has_tail; /* True if we already added directives that were
|
||||
not present in the original config file. */
|
||||
int force_all; /* True if we want all keywords to be force
|
||||
written. Currently only used for testing. */
|
||||
};
|
||||
|
||||
/* Append the new line to the current configuration state. */
|
||||
@@ -991,6 +1135,7 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
|
||||
state->numlines = 0;
|
||||
state->lines = NULL;
|
||||
state->has_tail = 0;
|
||||
state->force_all = 0;
|
||||
if (fp == NULL) return state;
|
||||
|
||||
/* Read the old file line by line, populate the state. */
|
||||
@@ -1069,7 +1214,7 @@ void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *opti
|
||||
|
||||
rewriteConfigMarkAsProcessed(state,option);
|
||||
|
||||
if (!l && !force) {
|
||||
if (!l && !force && !state->force_all) {
|
||||
/* Option not used previously, and we are not forced to use it. */
|
||||
sdsfree(line);
|
||||
sdsfree(o);
|
||||
@@ -1166,7 +1311,7 @@ void rewriteConfigNumericalOption(struct rewriteConfigState *state, const char *
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite a octal option. */
|
||||
/* Rewrite an octal option. */
|
||||
void rewriteConfigOctalOption(struct rewriteConfigState *state, char *option, int value, int defvalue) {
|
||||
int force = value != defvalue;
|
||||
sds line = sdscatprintf(sdsempty(),"%s %o",option,value);
|
||||
@@ -1191,6 +1336,12 @@ void rewriteConfigSaveOption(struct rewriteConfigState *state) {
|
||||
int j;
|
||||
sds line;
|
||||
|
||||
/* In Sentinel mode we don't need to rewrite the save parameters */
|
||||
if (server.sentinel_mode) {
|
||||
rewriteConfigMarkAsProcessed(state,"save");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Note that if there are no save parameters at all, all the current
|
||||
* config line with "save" will be detected as orphaned and deleted,
|
||||
* resulting into no RDB persistence as expected. */
|
||||
@@ -1305,6 +1456,26 @@ void rewriteConfigClientoutputbufferlimitOption(struct rewriteConfigState *state
|
||||
}
|
||||
}
|
||||
|
||||
/* Rewrite the oom-score-adj-values option. */
|
||||
void rewriteConfigOOMScoreAdjValuesOption(struct rewriteConfigState *state) {
|
||||
int force = 0;
|
||||
int j;
|
||||
char *option = "oom-score-adj-values";
|
||||
sds line;
|
||||
|
||||
line = sdsnew(option);
|
||||
line = sdscatlen(line, " ", 1);
|
||||
for (j = 0; j < CONFIG_OOM_COUNT; j++) {
|
||||
if (server.oom_score_adj_values[j] != configOOMScoreAdjValuesDefaults[j])
|
||||
force = 1;
|
||||
|
||||
line = sdscatprintf(line, "%d", server.oom_score_adj_values[j]);
|
||||
if (j+1 != CONFIG_OOM_COUNT)
|
||||
line = sdscatlen(line, " ", 1);
|
||||
}
|
||||
rewriteConfigRewriteLine(state,option,line,force);
|
||||
}
|
||||
|
||||
/* Rewrite the bind option. */
|
||||
void rewriteConfigBindOption(struct rewriteConfigState *state) {
|
||||
int force = 1;
|
||||
@@ -1331,7 +1502,7 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
|
||||
void rewriteConfigRequirepassOption(struct rewriteConfigState *state, char *option) {
|
||||
int force = 1;
|
||||
sds line;
|
||||
sds password = ACLDefaultUserFirstPassword();
|
||||
sds password = server.requirepass;
|
||||
|
||||
/* If there is no password set, we don't want the requirepass option
|
||||
* to be present in the configuration at all. */
|
||||
@@ -1410,60 +1581,62 @@ void rewriteConfigRemoveOrphaned(struct rewriteConfigState *state) {
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* This function overwrites the old configuration file with the new content.
|
||||
*
|
||||
* 1) The old file length is obtained.
|
||||
* 2) If the new content is smaller, padding is added.
|
||||
* 3) A single write(2) call is used to replace the content of the file.
|
||||
* 4) Later the file is truncated to the length of the new content.
|
||||
*
|
||||
* This way we are sure the file is left in a consistent state even if the
|
||||
* process is stopped between any of the four operations.
|
||||
/* This function replaces the old configuration file with the new content
|
||||
* in an atomic manner.
|
||||
*
|
||||
* The function returns 0 on success, otherwise -1 is returned and errno
|
||||
* set accordingly. */
|
||||
* is set accordingly. */
|
||||
int rewriteConfigOverwriteFile(char *configfile, sds content) {
|
||||
int retval = 0;
|
||||
int fd = open(configfile,O_RDWR|O_CREAT,0644);
|
||||
int content_size = sdslen(content), padding = 0;
|
||||
struct stat sb;
|
||||
sds content_padded;
|
||||
int fd = -1;
|
||||
int retval = -1;
|
||||
char tmp_conffile[PATH_MAX];
|
||||
const char *tmp_suffix = ".XXXXXX";
|
||||
size_t offset = 0;
|
||||
ssize_t written_bytes = 0;
|
||||
|
||||
/* 1) Open the old file (or create a new one if it does not
|
||||
* exist), get the size. */
|
||||
if (fd == -1) return -1; /* errno set by open(). */
|
||||
if (fstat(fd,&sb) == -1) {
|
||||
close(fd);
|
||||
return -1; /* errno set by fstat(). */
|
||||
int tmp_path_len = snprintf(tmp_conffile, sizeof(tmp_conffile), "%s%s", configfile, tmp_suffix);
|
||||
if (tmp_path_len <= 0 || (unsigned int)tmp_path_len >= sizeof(tmp_conffile)) {
|
||||
serverLog(LL_WARNING, "Config file full path is too long");
|
||||
errno = ENAMETOOLONG;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* 2) Pad the content at least match the old file size. */
|
||||
content_padded = sdsdup(content);
|
||||
if (content_size < sb.st_size) {
|
||||
/* If the old file was bigger, pad the content with
|
||||
* a newline plus as many "#" chars as required. */
|
||||
padding = sb.st_size - content_size;
|
||||
content_padded = sdsgrowzero(content_padded,sb.st_size);
|
||||
content_padded[content_size] = '\n';
|
||||
memset(content_padded+content_size+1,'#',padding-1);
|
||||
#ifdef _GNU_SOURCE
|
||||
fd = mkostemp(tmp_conffile, O_CLOEXEC);
|
||||
#else
|
||||
/* There's a theoretical chance here to leak the FD if a module thread forks & execv in the middle */
|
||||
fd = mkstemp(tmp_conffile);
|
||||
#endif
|
||||
|
||||
if (fd == -1) {
|
||||
serverLog(LL_WARNING, "Could not create tmp config file (%s)", strerror(errno));
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* 3) Write the new content using a single write(2). */
|
||||
if (write(fd,content_padded,strlen(content_padded)) == -1) {
|
||||
retval = -1;
|
||||
goto cleanup;
|
||||
while (offset < sdslen(content)) {
|
||||
written_bytes = write(fd, content + offset, sdslen(content) - offset);
|
||||
if (written_bytes <= 0) {
|
||||
if (errno == EINTR) continue; /* FD is blocking, no other retryable errors */
|
||||
serverLog(LL_WARNING, "Failed after writing (%zd) bytes to tmp config file (%s)", offset, strerror(errno));
|
||||
goto cleanup;
|
||||
}
|
||||
offset+=written_bytes;
|
||||
}
|
||||
|
||||
/* 4) Truncate the file to the right length if we used padding. */
|
||||
if (padding) {
|
||||
if (ftruncate(fd,content_size) == -1) {
|
||||
/* Non critical error... */
|
||||
}
|
||||
if (fsync(fd))
|
||||
serverLog(LL_WARNING, "Could not sync tmp config file to disk (%s)", strerror(errno));
|
||||
else if (fchmod(fd, 0644 & ~server.umask) == -1)
|
||||
serverLog(LL_WARNING, "Could not chmod config file (%s)", strerror(errno));
|
||||
else if (rename(tmp_conffile, configfile) == -1)
|
||||
serverLog(LL_WARNING, "Could not rename tmp config file (%s)", strerror(errno));
|
||||
else {
|
||||
retval = 0;
|
||||
serverLog(LL_DEBUG, "Rewritten config file (%s) successfully", configfile);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
sdsfree(content_padded);
|
||||
close(fd);
|
||||
if (retval) unlink(tmp_conffile);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -1473,15 +1646,18 @@ cleanup:
|
||||
*
|
||||
* Configuration parameters that are at their default value, unless already
|
||||
* explicitly included in the old configuration file, are not rewritten.
|
||||
* The force_all flag overrides this behavior and forces everything to be
|
||||
* written. This is currently only used for testing purposes.
|
||||
*
|
||||
* On error -1 is returned and errno is set accordingly, otherwise 0. */
|
||||
int rewriteConfig(char *path) {
|
||||
int rewriteConfig(char *path, int force_all) {
|
||||
struct rewriteConfigState *state;
|
||||
sds newcontent;
|
||||
int retval;
|
||||
|
||||
/* Step 1: read the old config into our rewrite state. */
|
||||
if ((state = rewriteConfigReadOldFile(path)) == NULL) return -1;
|
||||
if (force_all) state->force_all = 1;
|
||||
|
||||
/* Step 2: rewrite every single option, replacing or appending it inside
|
||||
* the rewrite state. */
|
||||
@@ -1503,6 +1679,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
rewriteConfigNotifykeyspaceeventsOption(state);
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
rewriteConfigOOMScoreAdjValuesOption(state);
|
||||
|
||||
/* Rewrite Sentinel config if in Sentinel mode. */
|
||||
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
|
||||
@@ -1536,9 +1713,8 @@ static char loadbuf[LOADBUF_SIZE];
|
||||
.alias = (config_alias), \
|
||||
.modifiable = (is_modifiable),
|
||||
|
||||
#define embedConfigInterface(initfn, loadfn, setfn, getfn, rewritefn) .interface = { \
|
||||
#define embedConfigInterface(initfn, setfn, getfn, rewritefn) .interface = { \
|
||||
.init = (initfn), \
|
||||
.load = (loadfn), \
|
||||
.set = (setfn), \
|
||||
.get = (getfn), \
|
||||
.rewrite = (rewritefn) \
|
||||
@@ -1561,30 +1737,17 @@ static void boolConfigInit(typeData data) {
|
||||
*data.yesno.config = data.yesno.default_value;
|
||||
}
|
||||
|
||||
static int boolConfigLoad(typeData data, sds *argv, int argc, char **err) {
|
||||
int yn;
|
||||
if (argc != 2) {
|
||||
*err = "wrong number of arguments";
|
||||
return 0;
|
||||
}
|
||||
if ((yn = yesnotoi(argv[1])) == -1) {
|
||||
static int boolConfigSet(typeData data, sds value, int update, char **err) {
|
||||
int yn = yesnotoi(value);
|
||||
if (yn == -1) {
|
||||
*err = "argument must be 'yes' or 'no'";
|
||||
return 0;
|
||||
}
|
||||
if (data.yesno.is_valid_fn && !data.yesno.is_valid_fn(yn, err))
|
||||
return 0;
|
||||
*data.yesno.config = yn;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int boolConfigSet(typeData data, sds value, char **err) {
|
||||
int yn = yesnotoi(value);
|
||||
if (yn == -1) return 0;
|
||||
if (data.yesno.is_valid_fn && !data.yesno.is_valid_fn(yn, err))
|
||||
return 0;
|
||||
int prev = *(data.yesno.config);
|
||||
*(data.yesno.config) = yn;
|
||||
if (data.yesno.update_fn && !data.yesno.update_fn(yn, prev, err)) {
|
||||
if (update && data.yesno.update_fn && !data.yesno.update_fn(yn, prev, err)) {
|
||||
*(data.yesno.config) = prev;
|
||||
return 0;
|
||||
}
|
||||
@@ -1601,7 +1764,7 @@ static void boolConfigRewrite(typeData data, const char *name, struct rewriteCon
|
||||
|
||||
#define createBoolConfig(name, alias, modifiable, config_addr, default, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, modifiable) \
|
||||
embedConfigInterface(boolConfigInit, boolConfigLoad, boolConfigSet, boolConfigGet, boolConfigRewrite) \
|
||||
embedConfigInterface(boolConfigInit, boolConfigSet, boolConfigGet, boolConfigRewrite) \
|
||||
.data.yesno = { \
|
||||
.config = &(config_addr), \
|
||||
.default_value = (default), \
|
||||
@@ -1619,23 +1782,7 @@ static void stringConfigInit(typeData data) {
|
||||
}
|
||||
}
|
||||
|
||||
static int stringConfigLoad(typeData data, sds *argv, int argc, char **err) {
|
||||
if (argc != 2) {
|
||||
*err = "wrong number of arguments";
|
||||
return 0;
|
||||
}
|
||||
if (data.string.is_valid_fn && !data.string.is_valid_fn(argv[1], err))
|
||||
return 0;
|
||||
zfree(*data.string.config);
|
||||
if (data.string.convert_empty_to_null) {
|
||||
*data.string.config = argv[1][0] ? zstrdup(argv[1]) : NULL;
|
||||
} else {
|
||||
*data.string.config = zstrdup(argv[1]);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int stringConfigSet(typeData data, sds value, char **err) {
|
||||
static int stringConfigSet(typeData data, sds value, int update, char **err) {
|
||||
if (data.string.is_valid_fn && !data.string.is_valid_fn(value, err))
|
||||
return 0;
|
||||
char *prev = *data.string.config;
|
||||
@@ -1644,7 +1791,7 @@ static int stringConfigSet(typeData data, sds value, char **err) {
|
||||
} else {
|
||||
*data.string.config = zstrdup(value);
|
||||
}
|
||||
if (data.string.update_fn && !data.string.update_fn(*data.string.config, prev, err)) {
|
||||
if (update && data.string.update_fn && !data.string.update_fn(*data.string.config, prev, err)) {
|
||||
zfree(*data.string.config);
|
||||
*data.string.config = prev;
|
||||
return 0;
|
||||
@@ -1666,7 +1813,7 @@ static void stringConfigRewrite(typeData data, const char *name, struct rewriteC
|
||||
|
||||
#define createStringConfig(name, alias, modifiable, empty_to_null, config_addr, default, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, modifiable) \
|
||||
embedConfigInterface(stringConfigInit, stringConfigLoad, stringConfigSet, stringConfigGet, stringConfigRewrite) \
|
||||
embedConfigInterface(stringConfigInit, stringConfigSet, stringConfigGet, stringConfigRewrite) \
|
||||
.data.string = { \
|
||||
.config = &(config_addr), \
|
||||
.default_value = (default), \
|
||||
@@ -1677,67 +1824,52 @@ static void stringConfigRewrite(typeData data, const char *name, struct rewriteC
|
||||
}
|
||||
|
||||
/* Enum configs */
|
||||
static void configEnumInit(typeData data) {
|
||||
static void enumConfigInit(typeData data) {
|
||||
*data.enumd.config = data.enumd.default_value;
|
||||
}
|
||||
|
||||
static int configEnumLoad(typeData data, sds *argv, int argc, char **err) {
|
||||
if (argc != 2) {
|
||||
*err = "wrong number of arguments";
|
||||
return 0;
|
||||
}
|
||||
|
||||
int enumval = configEnumGetValue(data.enumd.enum_value, argv[1]);
|
||||
static int enumConfigSet(typeData data, sds value, int update, char **err) {
|
||||
int enumval = configEnumGetValue(data.enumd.enum_value, value);
|
||||
if (enumval == INT_MIN) {
|
||||
sds enumerr = sdsnew("argument must be one of the following: ");
|
||||
configEnum *enumNode = data.enumd.enum_value;
|
||||
while(enumNode->name != NULL) {
|
||||
enumerr = sdscatlen(enumerr, enumNode->name, strlen(enumNode->name));
|
||||
enumerr = sdscatlen(enumerr, enumNode->name,
|
||||
strlen(enumNode->name));
|
||||
enumerr = sdscatlen(enumerr, ", ", 2);
|
||||
enumNode++;
|
||||
}
|
||||
sdsrange(enumerr,0,-3); /* Remove final ", ". */
|
||||
|
||||
enumerr[sdslen(enumerr) - 2] = '\0';
|
||||
|
||||
/* Make sure we don't overrun the fixed buffer */
|
||||
enumerr[LOADBUF_SIZE - 1] = '\0';
|
||||
strncpy(loadbuf, enumerr, LOADBUF_SIZE);
|
||||
loadbuf[LOADBUF_SIZE - 1] = '\0';
|
||||
|
||||
sdsfree(enumerr);
|
||||
*err = loadbuf;
|
||||
return 0;
|
||||
}
|
||||
if (data.enumd.is_valid_fn && !data.enumd.is_valid_fn(enumval, err))
|
||||
return 0;
|
||||
*(data.enumd.config) = enumval;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int configEnumSet(typeData data, sds value, char **err) {
|
||||
int enumval = configEnumGetValue(data.enumd.enum_value, value);
|
||||
if (enumval == INT_MIN) return 0;
|
||||
if (data.enumd.is_valid_fn && !data.enumd.is_valid_fn(enumval, err))
|
||||
return 0;
|
||||
int prev = *(data.enumd.config);
|
||||
*(data.enumd.config) = enumval;
|
||||
if (data.enumd.update_fn && !data.enumd.update_fn(enumval, prev, err)) {
|
||||
if (update && data.enumd.update_fn && !data.enumd.update_fn(enumval, prev, err)) {
|
||||
*(data.enumd.config) = prev;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void configEnumGet(client *c, typeData data) {
|
||||
static void enumConfigGet(client *c, typeData data) {
|
||||
addReplyBulkCString(c, configEnumGetNameOrUnknown(data.enumd.enum_value,*data.enumd.config));
|
||||
}
|
||||
|
||||
static void configEnumRewrite(typeData data, const char *name, struct rewriteConfigState *state) {
|
||||
static void enumConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) {
|
||||
rewriteConfigEnumOption(state, name,*(data.enumd.config), data.enumd.enum_value, data.enumd.default_value);
|
||||
}
|
||||
|
||||
#define createEnumConfig(name, alias, modifiable, enum, config_addr, default, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, modifiable) \
|
||||
embedConfigInterface(configEnumInit, configEnumLoad, configEnumSet, configEnumGet, configEnumRewrite) \
|
||||
embedConfigInterface(enumConfigInit, enumConfigSet, enumConfigGet, enumConfigRewrite) \
|
||||
.data.enumd = { \
|
||||
.config = &(config_addr), \
|
||||
.default_value = (default), \
|
||||
@@ -1832,49 +1964,22 @@ static int numericBoundaryCheck(typeData data, long long ll, char **err) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int numericConfigLoad(typeData data, sds *argv, int argc, char **err) {
|
||||
long long ll;
|
||||
|
||||
if (argc != 2) {
|
||||
*err = "wrong number of arguments";
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int numericConfigSet(typeData data, sds value, int update, char **err) {
|
||||
long long ll, prev = 0;
|
||||
if (data.numeric.is_memory) {
|
||||
int memerr;
|
||||
ll = memtoll(argv[1], &memerr);
|
||||
ll = memtoll(value, &memerr);
|
||||
if (memerr || ll < 0) {
|
||||
*err = "argument must be a memory value";
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (!string2ll(argv[1], sdslen(argv[1]),&ll)) {
|
||||
if (!string2ll(value, sdslen(value),&ll)) {
|
||||
*err = "argument couldn't be parsed into an integer" ;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!numericBoundaryCheck(data, ll, err))
|
||||
return 0;
|
||||
|
||||
if (data.numeric.is_valid_fn && !data.numeric.is_valid_fn(ll, err))
|
||||
return 0;
|
||||
|
||||
SET_NUMERIC_TYPE(ll)
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int numericConfigSet(typeData data, sds value, char **err) {
|
||||
long long ll, prev = 0;
|
||||
if (data.numeric.is_memory) {
|
||||
int memerr;
|
||||
ll = memtoll(value, &memerr);
|
||||
if (memerr || ll < 0) return 0;
|
||||
} else {
|
||||
if (!string2ll(value, sdslen(value),&ll)) return 0;
|
||||
}
|
||||
|
||||
if (!numericBoundaryCheck(data, ll, err))
|
||||
return 0;
|
||||
|
||||
@@ -1884,7 +1989,7 @@ static int numericConfigSet(typeData data, sds value, char **err) {
|
||||
GET_NUMERIC_TYPE(prev)
|
||||
SET_NUMERIC_TYPE(ll)
|
||||
|
||||
if (data.numeric.update_fn && !data.numeric.update_fn(ll, prev, err)) {
|
||||
if (update && data.numeric.update_fn && !data.numeric.update_fn(ll, prev, err)) {
|
||||
SET_NUMERIC_TYPE(prev)
|
||||
return 0;
|
||||
}
|
||||
@@ -1918,7 +2023,7 @@ static void numericConfigRewrite(typeData data, const char *name, struct rewrite
|
||||
|
||||
#define embedCommonNumericalConfig(name, alias, modifiable, lower, upper, config_addr, default, memory, is_valid, update) { \
|
||||
embedCommonConfig(name, alias, modifiable) \
|
||||
embedConfigInterface(numericConfigInit, numericConfigLoad, numericConfigSet, numericConfigGet, numericConfigRewrite) \
|
||||
embedConfigInterface(numericConfigInit, numericConfigSet, numericConfigGet, numericConfigRewrite) \
|
||||
.data.numeric = { \
|
||||
.lower_bound = (lower), \
|
||||
.upper_bound = (upper), \
|
||||
@@ -2032,7 +2137,7 @@ static int isValidAOFfilename(char *val, char **err) {
|
||||
static int updateHZ(long long val, long long prev, char **err) {
|
||||
UNUSED(prev);
|
||||
UNUSED(err);
|
||||
/* Hz is more an hint from the user, so we accept values out of range
|
||||
/* Hz is more a hint from the user, so we accept values out of range
|
||||
* but cap them to reasonable values. */
|
||||
server.config_hz = val;
|
||||
if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ;
|
||||
@@ -2050,7 +2155,7 @@ static int updateJemallocBgThread(int val, int prev, char **err) {
|
||||
|
||||
static int updateReplBacklogSize(long long val, long long prev, char **err) {
|
||||
/* resizeReplicationBacklog sets server.repl_backlog_size, and relies on
|
||||
* being able to tell when the size changes, so restore prev becore calling it. */
|
||||
* being able to tell when the size changes, so restore prev before calling it. */
|
||||
UNUSED(err);
|
||||
server.repl_backlog_size = prev;
|
||||
resizeReplicationBacklog(val);
|
||||
@@ -2061,8 +2166,9 @@ static int updateMaxmemory(long long val, long long prev, char **err) {
|
||||
UNUSED(prev);
|
||||
UNUSED(err);
|
||||
if (val) {
|
||||
if ((unsigned long long)val < zmalloc_used_memory()) {
|
||||
serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.");
|
||||
size_t used = zmalloc_used_memory()-freeMemoryGetNotCountedMemory();
|
||||
if ((unsigned long long)val < used) {
|
||||
serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET (%llu) is smaller than the current memory usage (%zu). This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.", server.maxmemory, used);
|
||||
}
|
||||
freeMemoryIfNeededAndSafe();
|
||||
}
|
||||
@@ -2098,6 +2204,10 @@ static int updateMaxclients(long long val, long long prev, char **err) {
|
||||
static char msg[128];
|
||||
sprintf(msg, "The operating system is not able to handle the specified number of clients, try with %d", server.maxclients);
|
||||
*err = msg;
|
||||
if (server.maxclients > prev) {
|
||||
server.maxclients = prev;
|
||||
adjustOpenFilesLimit();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if ((unsigned int) aeGetSetSize(server.el) <
|
||||
@@ -2114,13 +2224,29 @@ static int updateMaxclients(long long val, long long prev, char **err) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int updateOOMScoreAdj(int val, int prev, char **err) {
|
||||
UNUSED(prev);
|
||||
|
||||
if (val) {
|
||||
if (setOOMScoreAdj(-1) == C_ERR) {
|
||||
*err = "Failed to set current oom_score_adj. Check server logs.";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
static int updateTlsCfg(char *val, char *prev, char **err) {
|
||||
UNUSED(val);
|
||||
UNUSED(prev);
|
||||
UNUSED(err);
|
||||
if (tlsConfigure(&server.tls_ctx_config) == C_ERR) {
|
||||
*err = "Unable to configure tls-cert-file. Check server logs.";
|
||||
|
||||
/* If TLS is enabled, try to configure OpenSSL. */
|
||||
if ((server.tls_port || server.tls_replication || server.tls_cluster)
|
||||
&& tlsConfigure(&server.tls_ctx_config) == C_ERR) {
|
||||
*err = "Unable to update TLS configuration. Check server logs.";
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
@@ -2130,6 +2256,12 @@ static int updateTlsCfgBool(int val, int prev, char **err) {
|
||||
UNUSED(prev);
|
||||
return updateTlsCfg(NULL, NULL, err);
|
||||
}
|
||||
|
||||
static int updateTlsCfgInt(long long val, long long prev, char **err) {
|
||||
UNUSED(val);
|
||||
UNUSED(prev);
|
||||
return updateTlsCfg(NULL, NULL, err);
|
||||
}
|
||||
#endif /* USE_OPENSSL */
|
||||
|
||||
standardConfig configs[] = {
|
||||
@@ -2137,16 +2269,18 @@ standardConfig configs[] = {
|
||||
createBoolConfig("rdbchecksum", NULL, IMMUTABLE_CONFIG, server.rdb_checksum, 1, NULL, NULL),
|
||||
createBoolConfig("daemonize", NULL, IMMUTABLE_CONFIG, server.daemonize, 0, NULL, NULL),
|
||||
createBoolConfig("io-threads-do-reads", NULL, IMMUTABLE_CONFIG, server.io_threads_do_reads, 0,NULL, NULL), /* Read + parse from threads? */
|
||||
createBoolConfig("lua-replicate-commands", NULL, IMMUTABLE_CONFIG, server.lua_always_replicate_commands, 1, NULL, NULL),
|
||||
createBoolConfig("lua-replicate-commands", NULL, MODIFIABLE_CONFIG, server.lua_always_replicate_commands, 1, NULL, NULL),
|
||||
createBoolConfig("always-show-logo", NULL, IMMUTABLE_CONFIG, server.always_show_logo, 0, NULL, NULL),
|
||||
createBoolConfig("protected-mode", NULL, MODIFIABLE_CONFIG, server.protected_mode, 1, NULL, NULL),
|
||||
createBoolConfig("rdbcompression", NULL, MODIFIABLE_CONFIG, server.rdb_compression, 1, NULL, NULL),
|
||||
createBoolConfig("rdb-del-sync-files", NULL, MODIFIABLE_CONFIG, server.rdb_del_sync_files, 0, NULL, NULL),
|
||||
createBoolConfig("activerehashing", NULL, MODIFIABLE_CONFIG, server.activerehashing, 1, NULL, NULL),
|
||||
createBoolConfig("stop-writes-on-bgsave-error", NULL, MODIFIABLE_CONFIG, server.stop_writes_on_bgsave_err, 1, NULL, NULL),
|
||||
createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, server.dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/
|
||||
createBoolConfig("lazyfree-lazy-eviction", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_eviction, 0, NULL, NULL),
|
||||
createBoolConfig("lazyfree-lazy-expire", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_expire, 0, NULL, NULL),
|
||||
createBoolConfig("lazyfree-lazy-server-del", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_server_del, 0, NULL, NULL),
|
||||
createBoolConfig("lazyfree-lazy-user-del", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_user_del , 0, NULL, NULL),
|
||||
createBoolConfig("repl-disable-tcp-nodelay", NULL, MODIFIABLE_CONFIG, server.repl_disable_tcp_nodelay, 0, NULL, NULL),
|
||||
createBoolConfig("repl-diskless-sync", NULL, MODIFIABLE_CONFIG, server.repl_diskless_sync, 0, NULL, NULL),
|
||||
createBoolConfig("gopher-enabled", NULL, MODIFIABLE_CONFIG, server.gopher_enabled, 0, NULL, NULL),
|
||||
@@ -2168,7 +2302,6 @@ standardConfig configs[] = {
|
||||
createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, server.aof_enabled, 0, NULL, updateAppendonly),
|
||||
createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, server.cluster_allow_reads_when_down, 0, NULL, NULL),
|
||||
|
||||
|
||||
/* String Configs */
|
||||
createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.acl_filename, "", NULL, NULL),
|
||||
createStringConfig("unixsocket", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.unixsocket, NULL, NULL, NULL),
|
||||
@@ -2180,6 +2313,11 @@ standardConfig configs[] = {
|
||||
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.syslog_ident, "redis", NULL, NULL),
|
||||
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb", isValidDBfilename, NULL),
|
||||
createStringConfig("appendfilename", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.aof_filename, "appendonly.aof", isValidAOFfilename, NULL),
|
||||
createStringConfig("server_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.server_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bio_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("aof_rewrite_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.aof_rewrite_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("bgsave_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, server.bgsave_cpulist, NULL, NULL, NULL),
|
||||
createStringConfig("ignore-warnings", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, server.ignore_warnings, "ARM64-COW-BUG", NULL, NULL),
|
||||
|
||||
/* Enum Configs */
|
||||
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, server.supervised_mode, SUPERVISED_NONE, NULL, NULL),
|
||||
@@ -2188,11 +2326,12 @@ standardConfig configs[] = {
|
||||
createEnumConfig("loglevel", NULL, MODIFIABLE_CONFIG, loglevel_enum, server.verbosity, LL_NOTICE, NULL, NULL),
|
||||
createEnumConfig("maxmemory-policy", NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, server.maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL),
|
||||
createEnumConfig("appendfsync", NULL, MODIFIABLE_CONFIG, aof_fsync_enum, server.aof_fsync, AOF_FSYNC_EVERYSEC, NULL, NULL),
|
||||
createEnumConfig("oom-score-adj", NULL, MODIFIABLE_CONFIG, oom_score_adj_enum, server.oom_score_adj, OOM_SCORE_ADJ_NO, NULL, updateOOMScoreAdj),
|
||||
|
||||
/* Integer configs */
|
||||
createIntConfig("databases", NULL, IMMUTABLE_CONFIG, 1, INT_MAX, server.dbnum, 16, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("port", NULL, IMMUTABLE_CONFIG, 0, 65535, server.port, 6379, INTEGER_CONFIG, NULL, NULL), /* TCP port. */
|
||||
createIntConfig("io-threads", NULL, IMMUTABLE_CONFIG, 1, 512, server.io_threads_num, 1, INTEGER_CONFIG, NULL, NULL), /* Single threaded by default */
|
||||
createIntConfig("io-threads", NULL, IMMUTABLE_CONFIG, 1, 128, server.io_threads_num, 1, INTEGER_CONFIG, NULL, NULL), /* Single threaded by default */
|
||||
createIntConfig("auto-aof-rewrite-percentage", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.aof_rewrite_perc, 100, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("cluster-replica-validity-factor", "cluster-slave-validity-factor", MODIFIABLE_CONFIG, 0, INT_MAX, server.cluster_slave_validity_factor, 10, INTEGER_CONFIG, NULL, NULL), /* Slave max data age factor. */
|
||||
createIntConfig("list-max-ziplist-size", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, server.list_max_ziplist_size, -2, INTEGER_CONFIG, NULL, NULL),
|
||||
@@ -2209,7 +2348,7 @@ standardConfig configs[] = {
|
||||
createIntConfig("maxmemory-samples", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, server.maxmemory_samples, 5, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.maxidletime, 0, INTEGER_CONFIG, NULL, NULL), /* Default client timeout: infinite */
|
||||
createIntConfig("replica-announce-port", "slave-announce-port", MODIFIABLE_CONFIG, 0, 65535, server.slave_announce_port, 0, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("tcp-backlog", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */
|
||||
createIntConfig("tcp-backlog", NULL, IMMUTABLE_CONFIG, 0, INT_MAX, server.tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */
|
||||
createIntConfig("cluster-announce-bus-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_bus_port, 0, INTEGER_CONFIG, NULL, NULL), /* Default: Use +10000 offset. */
|
||||
createIntConfig("cluster-announce-port", NULL, MODIFIABLE_CONFIG, 0, 65535, server.cluster_announce_port, 0, INTEGER_CONFIG, NULL, NULL), /* Use server.port */
|
||||
createIntConfig("repl-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, server.repl_timeout, 60, INTEGER_CONFIG, NULL, NULL),
|
||||
@@ -2217,7 +2356,6 @@ standardConfig configs[] = {
|
||||
createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("rdb-key-save-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("key-load-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.key_load_delay, 0, INTEGER_CONFIG, NULL, NULL),
|
||||
createIntConfig("tracking-table-max-fill", NULL, MODIFIABLE_CONFIG, 0, 100, server.tracking_table_max_fill, 10, INTEGER_CONFIG, NULL, NULL), /* Default: 10% tracking table max fill. */
|
||||
createIntConfig("active-expire-effort", NULL, MODIFIABLE_CONFIG, 1, 10, server.active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */
|
||||
createIntConfig("hz", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ),
|
||||
createIntConfig("min-replicas-to-write", "min-slaves-to-write", MODIFIABLE_CONFIG, 0, INT_MAX, server.repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves),
|
||||
@@ -2229,15 +2367,16 @@ standardConfig configs[] = {
|
||||
/* Unsigned Long configs */
|
||||
createULongConfig("active-defrag-max-scan-fields", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, server.active_defrag_max_scan_fields, 1000, INTEGER_CONFIG, NULL, NULL), /* Default: keys with more than 1000 fields will be processed separately */
|
||||
createULongConfig("slowlog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.slowlog_max_len, 128, INTEGER_CONFIG, NULL, NULL),
|
||||
createULongConfig("acllog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.acllog_max_len, 128, INTEGER_CONFIG, NULL, NULL),
|
||||
|
||||
/* Long Long configs */
|
||||
createLongLongConfig("lua-time-limit", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.lua_time_limit, 5000, INTEGER_CONFIG, NULL, NULL),/* milliseconds */
|
||||
createLongLongConfig("cluster-node-timeout", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.cluster_node_timeout, 15000, INTEGER_CONFIG, NULL, NULL),
|
||||
createLongLongConfig("slowlog-log-slower-than", NULL, MODIFIABLE_CONFIG, -1, LLONG_MAX, server.slowlog_log_slower_than, 10000, INTEGER_CONFIG, NULL, NULL),
|
||||
createLongLongConfig("latency-monitor-threshold", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.latency_monitor_threshold, 0, INTEGER_CONFIG, NULL, NULL),
|
||||
createLongLongConfig("proto-max-bulk-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.proto_max_bulk_len, 512ll*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Bulk request max size */
|
||||
createLongLongConfig("stream-node-max-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.stream_node_max_entries, 100, INTEGER_CONFIG, NULL, NULL),
|
||||
createLongLongConfig("repl-backlog-size", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, server.repl_backlog_size, 1024*1024, MEMORY_CONFIG, NULL, updateReplBacklogSize), /* Default: 1mb */
|
||||
createLongLongConfig("proto-max-bulk-len", NULL, MODIFIABLE_CONFIG, 1024*1024, LONG_MAX, server.proto_max_bulk_len, 512ll*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Bulk request max size */
|
||||
createLongLongConfig("stream-node-max-entries", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.stream_node_max_entries, 100, INTEGER_CONFIG, NULL, NULL),
|
||||
createLongLongConfig("repl-backlog-size", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, server.repl_backlog_size, 1024*1024, MEMORY_CONFIG, NULL, updateReplBacklogSize), /* Default: 1mb */
|
||||
|
||||
/* Unsigned Long Long configs */
|
||||
createULongLongConfig("maxmemory", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.maxmemory, 0, MEMORY_CONFIG, NULL, updateMaxmemory),
|
||||
@@ -2246,22 +2385,26 @@ standardConfig configs[] = {
|
||||
createSizeTConfig("hash-max-ziplist-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_ziplist_entries, 512, INTEGER_CONFIG, NULL, NULL),
|
||||
createSizeTConfig("set-max-intset-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.set_max_intset_entries, 512, INTEGER_CONFIG, NULL, NULL),
|
||||
createSizeTConfig("zset-max-ziplist-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_ziplist_entries, 128, INTEGER_CONFIG, NULL, NULL),
|
||||
createSizeTConfig("active-defrag-ignore-bytes", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, server.active_defrag_ignore_bytes, 100<<20, MEMORY_CONFIG, NULL, NULL), /* Default: don't defrag if frag overhead is below 100mb */
|
||||
createSizeTConfig("active-defrag-ignore-bytes", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, server.active_defrag_ignore_bytes, 100<<20, MEMORY_CONFIG, NULL, NULL), /* Default: don't defrag if frag overhead is below 100mb */
|
||||
createSizeTConfig("hash-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hash_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
|
||||
createSizeTConfig("stream-node-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.stream_node_max_bytes, 4096, MEMORY_CONFIG, NULL, NULL),
|
||||
createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
|
||||
createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL),
|
||||
createSizeTConfig("tracking-table-max-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.tracking_table_max_keys, 1000000, INTEGER_CONFIG, NULL, NULL), /* Default: 1 million keys max. */
|
||||
|
||||
/* Other configs */
|
||||
createTimeTConfig("repl-backlog-ttl", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.repl_backlog_time_limit, 60*60, INTEGER_CONFIG, NULL, NULL), /* Default: 1 hour */
|
||||
createOffTConfig("auto-aof-rewrite-min-size", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.aof_rewrite_min_size, 64*1024*1024, MEMORY_CONFIG, NULL, NULL),
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
createIntConfig("tls-port", NULL, IMMUTABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, NULL), /* TCP port. */
|
||||
createBoolConfig("tls-cluster", NULL, MODIFIABLE_CONFIG, server.tls_cluster, 0, NULL, NULL),
|
||||
createBoolConfig("tls-replication", NULL, MODIFIABLE_CONFIG, server.tls_replication, 0, NULL, NULL),
|
||||
createBoolConfig("tls-auth-clients", NULL, MODIFIABLE_CONFIG, server.tls_auth_clients, 1, NULL, NULL),
|
||||
createIntConfig("tls-port", NULL, IMMUTABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, updateTlsCfgInt), /* TCP port. */
|
||||
createIntConfig("tls-session-cache-size", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_size, 20*1024, INTEGER_CONFIG, NULL, updateTlsCfgInt),
|
||||
createIntConfig("tls-session-cache-timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_timeout, 300, INTEGER_CONFIG, NULL, updateTlsCfgInt),
|
||||
createBoolConfig("tls-cluster", NULL, MODIFIABLE_CONFIG, server.tls_cluster, 0, NULL, updateTlsCfgBool),
|
||||
createBoolConfig("tls-replication", NULL, MODIFIABLE_CONFIG, server.tls_replication, 0, NULL, updateTlsCfgBool),
|
||||
createEnumConfig("tls-auth-clients", NULL, MODIFIABLE_CONFIG, tls_auth_clients_enum, server.tls_auth_clients, TLS_CLIENT_AUTH_YES, NULL, NULL),
|
||||
createBoolConfig("tls-prefer-server-ciphers", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.prefer_server_ciphers, 0, NULL, updateTlsCfgBool),
|
||||
createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, updateTlsCfgBool),
|
||||
createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, updateTlsCfg),
|
||||
createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, updateTlsCfg),
|
||||
createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, updateTlsCfg),
|
||||
@@ -2309,7 +2452,7 @@ NULL
|
||||
addReplyError(c,"The server is running without a config file");
|
||||
return;
|
||||
}
|
||||
if (rewriteConfig(server.configfile) == -1) {
|
||||
if (rewriteConfig(server.configfile, 0) == -1) {
|
||||
serverLog(LL_WARNING,"CONFIG REWRITE failed: %s", strerror(errno));
|
||||
addReplyErrorFormat(c,"Rewriting config file: %s", strerror(errno));
|
||||
} else {
|
||||
|
||||
+37
-17
@@ -35,8 +35,8 @@
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/version.h>
|
||||
#include <features.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
/* Define redis_fstat to fstat or fstat64() */
|
||||
@@ -54,6 +54,7 @@
|
||||
#define HAVE_PROC_MAPS 1
|
||||
#define HAVE_PROC_SMAPS 1
|
||||
#define HAVE_PROC_SOMAXCONN 1
|
||||
#define HAVE_PROC_OOM_SCORE_ADJ 1
|
||||
#endif
|
||||
|
||||
/* Test for task_info() */
|
||||
@@ -63,7 +64,7 @@
|
||||
|
||||
/* Test for backtrace() */
|
||||
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__)) || \
|
||||
defined(__FreeBSD__) || (defined(__OpenBSD__) && defined(USE_BACKTRACE))\
|
||||
defined(__FreeBSD__) || ((defined(__OpenBSD__) || defined(__NetBSD__)) && defined(USE_BACKTRACE))\
|
||||
|| defined(__DragonFly__)
|
||||
#define HAVE_BACKTRACE 1
|
||||
#endif
|
||||
@@ -98,19 +99,7 @@
|
||||
|
||||
/* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use
|
||||
* the plain fsync() call. */
|
||||
#ifdef __linux__
|
||||
#if defined(__GLIBC__) && defined(__GLIBC_PREREQ)
|
||||
#if (LINUX_VERSION_CODE >= 0x020611 && __GLIBC_PREREQ(2, 6))
|
||||
#define HAVE_SYNC_FILE_RANGE 1
|
||||
#endif
|
||||
#else
|
||||
#if (LINUX_VERSION_CODE >= 0x020611)
|
||||
#define HAVE_SYNC_FILE_RANGE 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SYNC_FILE_RANGE
|
||||
#if (defined(__linux__) && defined(SYNC_FILE_RANGE_WAIT_BEFORE))
|
||||
#define rdb_fsync_range(fd,off,size) sync_file_range(fd,off,size,SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE)
|
||||
#else
|
||||
#define rdb_fsync_range(fd,off,size) fsync(fd)
|
||||
@@ -123,7 +112,11 @@
|
||||
#define USE_SETPROCTITLE
|
||||
#endif
|
||||
|
||||
#if ((defined __linux && defined(__GLIBC__)) || defined __APPLE__)
|
||||
#if defined(__HAIKU__)
|
||||
#define ESOCKTNOSUPPORT 0
|
||||
#endif
|
||||
|
||||
#if (defined __linux || defined __APPLE__)
|
||||
#define USE_SETPROCTITLE
|
||||
#define INIT_SETPROCTITLE_REPLACEMENT
|
||||
void spt_init(int argc, char *argv[]);
|
||||
@@ -165,7 +158,7 @@ void setproctitle(const char *fmt, ...);
|
||||
#endif /* BYTE_ORDER */
|
||||
|
||||
/* Sometimes after including an OS-specific header that defines the
|
||||
* endianess we end with __BYTE_ORDER but not with BYTE_ORDER that is what
|
||||
* endianness we end with __BYTE_ORDER but not with BYTE_ORDER that is what
|
||||
* the Redis code uses. In this case let's define everything without the
|
||||
* underscores. */
|
||||
#ifndef BYTE_ORDER
|
||||
@@ -226,4 +219,31 @@ void setproctitle(const char *fmt, ...);
|
||||
#define USE_ALIGNED_ACCESS
|
||||
#endif
|
||||
|
||||
/* Define for redis_set_thread_title */
|
||||
#ifdef __linux__
|
||||
#define redis_set_thread_title(name) pthread_setname_np(pthread_self(), name)
|
||||
#else
|
||||
#if (defined __FreeBSD__ || defined __OpenBSD__)
|
||||
#include <pthread_np.h>
|
||||
#define redis_set_thread_title(name) pthread_set_name_np(pthread_self(), name)
|
||||
#elif defined __NetBSD__
|
||||
#include <pthread.h>
|
||||
#define redis_set_thread_title(name) pthread_setname_np(pthread_self(), "%s", name)
|
||||
#else
|
||||
#if (defined __APPLE__ && defined(MAC_OS_X_VERSION_10_7))
|
||||
int pthread_setname_np(const char *name);
|
||||
#include <pthread.h>
|
||||
#define redis_set_thread_title(name) pthread_setname_np(name)
|
||||
#else
|
||||
#define redis_set_thread_title(name)
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Check if we can use setcpuaffinity(). */
|
||||
#if (defined __linux || defined __NetBSD__ || defined __FreeBSD__ || defined __DragonFly__)
|
||||
#define USE_SETCPUAFFINITY
|
||||
void setcpuaffinity(const char *cpulist);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
+36
-9
@@ -85,8 +85,12 @@ connection *connCreateSocket() {
|
||||
/* Create a new socket-type connection that is already associated with
|
||||
* an accepted connection.
|
||||
*
|
||||
* The socket is not read for I/O until connAccept() was called and
|
||||
* The socket is not ready for I/O until connAccept() was called and
|
||||
* invoked the connection-level accept handler.
|
||||
*
|
||||
* Callers should use connGetState() and verify the created connection
|
||||
* is not in an error state (which is not possible for a socket connection,
|
||||
* but could but possible with other protocols).
|
||||
*/
|
||||
connection *connCreateAcceptedSocket(int fd) {
|
||||
connection *conn = connCreateSocket();
|
||||
@@ -152,7 +156,7 @@ static void connSocketClose(connection *conn) {
|
||||
/* If called from within a handler, schedule the close but
|
||||
* keep the connection until the handler returns.
|
||||
*/
|
||||
if (conn->flags & CONN_FLAG_IN_HANDLER) {
|
||||
if (connHasRefs(conn)) {
|
||||
conn->flags |= CONN_FLAG_CLOSE_SCHEDULED;
|
||||
return;
|
||||
}
|
||||
@@ -164,7 +168,12 @@ static int connSocketWrite(connection *conn, const void *data, size_t data_len)
|
||||
int ret = write(conn->fd, data, data_len);
|
||||
if (ret < 0 && errno != EAGAIN) {
|
||||
conn->last_errno = errno;
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
|
||||
/* Don't overwrite the state of a connection that is not already
|
||||
* connected, not to mess with handler callbacks.
|
||||
*/
|
||||
if (conn->state == CONN_STATE_CONNECTED)
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -176,17 +185,28 @@ static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
|
||||
conn->state = CONN_STATE_CLOSED;
|
||||
} else if (ret < 0 && errno != EAGAIN) {
|
||||
conn->last_errno = errno;
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
|
||||
/* Don't overwrite the state of a connection that is not already
|
||||
* connected, not to mess with handler callbacks.
|
||||
*/
|
||||
if (conn->state == CONN_STATE_CONNECTED)
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
|
||||
int ret = C_OK;
|
||||
|
||||
if (conn->state != CONN_STATE_ACCEPTING) return C_ERR;
|
||||
conn->state = CONN_STATE_CONNECTED;
|
||||
if (!callHandler(conn, accept_handler)) return C_ERR;
|
||||
return C_OK;
|
||||
|
||||
connIncrRefs(conn);
|
||||
if (!callHandler(conn, accept_handler)) ret = C_ERR;
|
||||
connDecrRefs(conn);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Register a write handler, to be called when the connection is writable.
|
||||
@@ -241,8 +261,9 @@ static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientD
|
||||
if (conn->state == CONN_STATE_CONNECTING &&
|
||||
(mask & AE_WRITABLE) && conn->conn_handler) {
|
||||
|
||||
if (connGetSocketError(conn)) {
|
||||
conn->last_errno = errno;
|
||||
int conn_error = connGetSocketError(conn);
|
||||
if (conn_error) {
|
||||
conn->last_errno = conn_error;
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
} else {
|
||||
conn->state = CONN_STATE_CONNECTED;
|
||||
@@ -319,6 +340,11 @@ static ssize_t connSocketSyncReadLine(connection *conn, char *ptr, ssize_t size,
|
||||
return syncReadLine(conn->fd, ptr, size, timeout);
|
||||
}
|
||||
|
||||
static int connSocketGetType(connection *conn) {
|
||||
(void) conn;
|
||||
|
||||
return CONN_TYPE_SOCKET;
|
||||
}
|
||||
|
||||
ConnectionType CT_Socket = {
|
||||
.ae_handler = connSocketEventHandler,
|
||||
@@ -333,7 +359,8 @@ ConnectionType CT_Socket = {
|
||||
.blocking_connect = connSocketBlockingConnect,
|
||||
.sync_write = connSocketSyncWrite,
|
||||
.sync_read = connSocketSyncRead,
|
||||
.sync_readline = connSocketSyncReadLine
|
||||
.sync_readline = connSocketSyncReadLine,
|
||||
.get_type = connSocketGetType
|
||||
};
|
||||
|
||||
|
||||
|
||||
+24
-7
@@ -45,9 +45,11 @@ typedef enum {
|
||||
CONN_STATE_ERROR
|
||||
} ConnectionState;
|
||||
|
||||
#define CONN_FLAG_IN_HANDLER (1<<0) /* A handler execution is in progress */
|
||||
#define CONN_FLAG_CLOSE_SCHEDULED (1<<1) /* Closed scheduled by a handler */
|
||||
#define CONN_FLAG_WRITE_BARRIER (1<<2) /* Write barrier requested */
|
||||
#define CONN_FLAG_CLOSE_SCHEDULED (1<<0) /* Closed scheduled by a handler */
|
||||
#define CONN_FLAG_WRITE_BARRIER (1<<1) /* Write barrier requested */
|
||||
|
||||
#define CONN_TYPE_SOCKET 1
|
||||
#define CONN_TYPE_TLS 2
|
||||
|
||||
typedef void (*ConnectionCallbackFunc)(struct connection *conn);
|
||||
|
||||
@@ -65,12 +67,14 @@ typedef struct ConnectionType {
|
||||
ssize_t (*sync_write)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
|
||||
ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
|
||||
ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
|
||||
int (*get_type)(struct connection *conn);
|
||||
} ConnectionType;
|
||||
|
||||
struct connection {
|
||||
ConnectionType *type;
|
||||
ConnectionState state;
|
||||
int flags;
|
||||
short int flags;
|
||||
short int refs;
|
||||
int last_errno;
|
||||
void *private_data;
|
||||
ConnectionCallbackFunc conn_handler;
|
||||
@@ -88,6 +92,13 @@ struct connection {
|
||||
* connAccept() may directly call accept_handler(), or return and call it
|
||||
* at a later time. This behavior is a bit awkward but aims to reduce the need
|
||||
* to wait for the next event loop, if no additional handshake is required.
|
||||
*
|
||||
* IMPORTANT: accept_handler may decide to close the connection, calling connClose().
|
||||
* To make this safe, the connection is only marked with CONN_FLAG_CLOSE_SCHEDULED
|
||||
* in this case, and connAccept() returns with an error.
|
||||
*
|
||||
* connAccept() callers must always check the return value and on error (C_ERR)
|
||||
* a connClose() must be called.
|
||||
*/
|
||||
|
||||
static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
|
||||
@@ -95,7 +106,7 @@ static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_han
|
||||
}
|
||||
|
||||
/* Establish a connection. The connect_handler will be called when the connection
|
||||
* is established, or if an error has occured.
|
||||
* is established, or if an error has occurred.
|
||||
*
|
||||
* The connection handler will be responsible to set up any read/write handlers
|
||||
* as needed.
|
||||
@@ -157,7 +168,7 @@ static inline int connSetReadHandler(connection *conn, ConnectionCallbackFunc fu
|
||||
|
||||
/* Set a write handler, and possibly enable a write barrier, this flag is
|
||||
* cleared when write handler is changed or removed.
|
||||
* With barroer enabled, we never fire the event if the read handler already
|
||||
* With barrier enabled, we never fire the event if the read handler already
|
||||
* fired in the same event loop iteration. Useful when you want to persist
|
||||
* things to disk before sending replies, and want to do that in a group fashion. */
|
||||
static inline int connSetWriteHandlerWithBarrier(connection *conn, ConnectionCallbackFunc func, int barrier) {
|
||||
@@ -187,6 +198,11 @@ static inline ssize_t connSyncReadLine(connection *conn, char *ptr, ssize_t size
|
||||
return conn->type->sync_readline(conn, ptr, size, timeout);
|
||||
}
|
||||
|
||||
/* Return CONN_TYPE_* for the specified connection */
|
||||
static inline int connGetType(connection *conn) {
|
||||
return conn->type->get_type(conn);
|
||||
}
|
||||
|
||||
connection *connCreateSocket();
|
||||
connection *connCreateAcceptedSocket(int fd);
|
||||
|
||||
@@ -214,7 +230,8 @@ int connSockName(connection *conn, char *ip, size_t ip_len, int *port);
|
||||
const char *connGetInfo(connection *conn, char *buf, size_t buf_len);
|
||||
|
||||
/* Helpers for tls special considerations */
|
||||
sds connTLSGetPeerCert(connection *conn);
|
||||
int tlsHasPendingData();
|
||||
void tlsProcessPendingData();
|
||||
int tlsProcessPendingData();
|
||||
|
||||
#endif /* __REDIS_CONNECTION_H */
|
||||
|
||||
+28
-25
@@ -37,46 +37,49 @@
|
||||
* implementations (currently sockets in connection.c and TLS in tls.c).
|
||||
*
|
||||
* Currently helpers implement the mechanisms for invoking connection
|
||||
* handlers, tracking in-handler states and dealing with deferred
|
||||
* destruction (if invoked by a handler).
|
||||
* handlers and tracking connection references, to allow safe destruction
|
||||
* of connections from within a handler.
|
||||
*/
|
||||
|
||||
/* Called whenever a handler is invoked on a connection and sets the
|
||||
* CONN_FLAG_IN_HANDLER flag to indicate we're in a handler context.
|
||||
/* Incremenet connection references.
|
||||
*
|
||||
* An attempt to close a connection while CONN_FLAG_IN_HANDLER is
|
||||
* set will result with deferred close, i.e. setting the CONN_FLAG_CLOSE_SCHEDULED
|
||||
* instead of destructing it.
|
||||
* Inside a connection handler, we guarantee refs >= 1 so it is always
|
||||
* safe to connClose().
|
||||
*
|
||||
* In other cases where we don't want to prematurely lose the connection,
|
||||
* it can go beyond 1 as well; currently it is only done by connAccept().
|
||||
*/
|
||||
static inline void enterHandler(connection *conn) {
|
||||
conn->flags |= CONN_FLAG_IN_HANDLER;
|
||||
static inline void connIncrRefs(connection *conn) {
|
||||
conn->refs++;
|
||||
}
|
||||
|
||||
/* Called whenever a handler returns. This unsets the CONN_FLAG_IN_HANDLER
|
||||
* flag and performs actual close/destruction if a deferred close was
|
||||
* scheduled by the handler.
|
||||
/* Decrement connection references.
|
||||
*
|
||||
* Note that this is not intended to provide any automatic free logic!
|
||||
* callHandler() takes care of that for the common flows, and anywhere an
|
||||
* explicit connIncrRefs() is used, the caller is expected to take care of
|
||||
* that.
|
||||
*/
|
||||
static inline int exitHandler(connection *conn) {
|
||||
conn->flags &= ~CONN_FLAG_IN_HANDLER;
|
||||
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
|
||||
connClose(conn);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
|
||||
static inline void connDecrRefs(connection *conn) {
|
||||
conn->refs--;
|
||||
}
|
||||
|
||||
static inline int connHasRefs(connection *conn) {
|
||||
return conn->refs;
|
||||
}
|
||||
|
||||
/* Helper for connection implementations to call handlers:
|
||||
* 1. Mark the handler in use.
|
||||
* 1. Increment refs to protect the connection.
|
||||
* 2. Execute the handler (if set).
|
||||
* 3. Mark the handler as NOT in use and perform deferred close if was
|
||||
* requested by the handler at any time.
|
||||
* 3. Decrement refs and perform deferred close, if refs==0.
|
||||
*/
|
||||
static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) {
|
||||
conn->flags |= CONN_FLAG_IN_HANDLER;
|
||||
connIncrRefs(conn);
|
||||
if (handler) handler(conn);
|
||||
conn->flags &= ~CONN_FLAG_IN_HANDLER;
|
||||
connDecrRefs(conn);
|
||||
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
|
||||
connClose(conn);
|
||||
if (!connHasRefs(conn)) connClose(conn);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
|
||||
+117
-152
@@ -1,16 +1,5 @@
|
||||
/* Redis uses the CRC64 variant with "Jones" coefficients and init value of 0.
|
||||
*
|
||||
* Specification of this CRC64 variant follows:
|
||||
* Name: crc-64-jones
|
||||
* Width: 64 bites
|
||||
* Poly: 0xad93d23594c935a9
|
||||
* Reflected In: True
|
||||
* Xor_In: 0xffffffffffffffff
|
||||
* Reflected_Out: True
|
||||
* Xor_Out: 0x0
|
||||
* Check("123456789"): 0xe9c6d914c4b8d9ca
|
||||
*
|
||||
* Copyright (c) 2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
/* Copyright (c) 2014, Matt Stancliff <matt@genges.com>
|
||||
* Copyright (c) 2020, Amazon Web Services
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -37,147 +26,100 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE. */
|
||||
|
||||
#include <stdint.h>
|
||||
#include "crc64.h"
|
||||
#include "crcspeed.h"
|
||||
static uint64_t crc64_table[8][256] = {{0}};
|
||||
|
||||
static const uint64_t crc64_tab[256] = {
|
||||
UINT64_C(0x0000000000000000), UINT64_C(0x7ad870c830358979),
|
||||
UINT64_C(0xf5b0e190606b12f2), UINT64_C(0x8f689158505e9b8b),
|
||||
UINT64_C(0xc038e5739841b68f), UINT64_C(0xbae095bba8743ff6),
|
||||
UINT64_C(0x358804e3f82aa47d), UINT64_C(0x4f50742bc81f2d04),
|
||||
UINT64_C(0xab28ecb46814fe75), UINT64_C(0xd1f09c7c5821770c),
|
||||
UINT64_C(0x5e980d24087fec87), UINT64_C(0x24407dec384a65fe),
|
||||
UINT64_C(0x6b1009c7f05548fa), UINT64_C(0x11c8790fc060c183),
|
||||
UINT64_C(0x9ea0e857903e5a08), UINT64_C(0xe478989fa00bd371),
|
||||
UINT64_C(0x7d08ff3b88be6f81), UINT64_C(0x07d08ff3b88be6f8),
|
||||
UINT64_C(0x88b81eabe8d57d73), UINT64_C(0xf2606e63d8e0f40a),
|
||||
UINT64_C(0xbd301a4810ffd90e), UINT64_C(0xc7e86a8020ca5077),
|
||||
UINT64_C(0x4880fbd87094cbfc), UINT64_C(0x32588b1040a14285),
|
||||
UINT64_C(0xd620138fe0aa91f4), UINT64_C(0xacf86347d09f188d),
|
||||
UINT64_C(0x2390f21f80c18306), UINT64_C(0x594882d7b0f40a7f),
|
||||
UINT64_C(0x1618f6fc78eb277b), UINT64_C(0x6cc0863448deae02),
|
||||
UINT64_C(0xe3a8176c18803589), UINT64_C(0x997067a428b5bcf0),
|
||||
UINT64_C(0xfa11fe77117cdf02), UINT64_C(0x80c98ebf2149567b),
|
||||
UINT64_C(0x0fa11fe77117cdf0), UINT64_C(0x75796f2f41224489),
|
||||
UINT64_C(0x3a291b04893d698d), UINT64_C(0x40f16bccb908e0f4),
|
||||
UINT64_C(0xcf99fa94e9567b7f), UINT64_C(0xb5418a5cd963f206),
|
||||
UINT64_C(0x513912c379682177), UINT64_C(0x2be1620b495da80e),
|
||||
UINT64_C(0xa489f35319033385), UINT64_C(0xde51839b2936bafc),
|
||||
UINT64_C(0x9101f7b0e12997f8), UINT64_C(0xebd98778d11c1e81),
|
||||
UINT64_C(0x64b116208142850a), UINT64_C(0x1e6966e8b1770c73),
|
||||
UINT64_C(0x8719014c99c2b083), UINT64_C(0xfdc17184a9f739fa),
|
||||
UINT64_C(0x72a9e0dcf9a9a271), UINT64_C(0x08719014c99c2b08),
|
||||
UINT64_C(0x4721e43f0183060c), UINT64_C(0x3df994f731b68f75),
|
||||
UINT64_C(0xb29105af61e814fe), UINT64_C(0xc849756751dd9d87),
|
||||
UINT64_C(0x2c31edf8f1d64ef6), UINT64_C(0x56e99d30c1e3c78f),
|
||||
UINT64_C(0xd9810c6891bd5c04), UINT64_C(0xa3597ca0a188d57d),
|
||||
UINT64_C(0xec09088b6997f879), UINT64_C(0x96d1784359a27100),
|
||||
UINT64_C(0x19b9e91b09fcea8b), UINT64_C(0x636199d339c963f2),
|
||||
UINT64_C(0xdf7adabd7a6e2d6f), UINT64_C(0xa5a2aa754a5ba416),
|
||||
UINT64_C(0x2aca3b2d1a053f9d), UINT64_C(0x50124be52a30b6e4),
|
||||
UINT64_C(0x1f423fcee22f9be0), UINT64_C(0x659a4f06d21a1299),
|
||||
UINT64_C(0xeaf2de5e82448912), UINT64_C(0x902aae96b271006b),
|
||||
UINT64_C(0x74523609127ad31a), UINT64_C(0x0e8a46c1224f5a63),
|
||||
UINT64_C(0x81e2d7997211c1e8), UINT64_C(0xfb3aa75142244891),
|
||||
UINT64_C(0xb46ad37a8a3b6595), UINT64_C(0xceb2a3b2ba0eecec),
|
||||
UINT64_C(0x41da32eaea507767), UINT64_C(0x3b024222da65fe1e),
|
||||
UINT64_C(0xa2722586f2d042ee), UINT64_C(0xd8aa554ec2e5cb97),
|
||||
UINT64_C(0x57c2c41692bb501c), UINT64_C(0x2d1ab4dea28ed965),
|
||||
UINT64_C(0x624ac0f56a91f461), UINT64_C(0x1892b03d5aa47d18),
|
||||
UINT64_C(0x97fa21650afae693), UINT64_C(0xed2251ad3acf6fea),
|
||||
UINT64_C(0x095ac9329ac4bc9b), UINT64_C(0x7382b9faaaf135e2),
|
||||
UINT64_C(0xfcea28a2faafae69), UINT64_C(0x8632586aca9a2710),
|
||||
UINT64_C(0xc9622c4102850a14), UINT64_C(0xb3ba5c8932b0836d),
|
||||
UINT64_C(0x3cd2cdd162ee18e6), UINT64_C(0x460abd1952db919f),
|
||||
UINT64_C(0x256b24ca6b12f26d), UINT64_C(0x5fb354025b277b14),
|
||||
UINT64_C(0xd0dbc55a0b79e09f), UINT64_C(0xaa03b5923b4c69e6),
|
||||
UINT64_C(0xe553c1b9f35344e2), UINT64_C(0x9f8bb171c366cd9b),
|
||||
UINT64_C(0x10e3202993385610), UINT64_C(0x6a3b50e1a30ddf69),
|
||||
UINT64_C(0x8e43c87e03060c18), UINT64_C(0xf49bb8b633338561),
|
||||
UINT64_C(0x7bf329ee636d1eea), UINT64_C(0x012b592653589793),
|
||||
UINT64_C(0x4e7b2d0d9b47ba97), UINT64_C(0x34a35dc5ab7233ee),
|
||||
UINT64_C(0xbbcbcc9dfb2ca865), UINT64_C(0xc113bc55cb19211c),
|
||||
UINT64_C(0x5863dbf1e3ac9dec), UINT64_C(0x22bbab39d3991495),
|
||||
UINT64_C(0xadd33a6183c78f1e), UINT64_C(0xd70b4aa9b3f20667),
|
||||
UINT64_C(0x985b3e827bed2b63), UINT64_C(0xe2834e4a4bd8a21a),
|
||||
UINT64_C(0x6debdf121b863991), UINT64_C(0x1733afda2bb3b0e8),
|
||||
UINT64_C(0xf34b37458bb86399), UINT64_C(0x8993478dbb8deae0),
|
||||
UINT64_C(0x06fbd6d5ebd3716b), UINT64_C(0x7c23a61ddbe6f812),
|
||||
UINT64_C(0x3373d23613f9d516), UINT64_C(0x49aba2fe23cc5c6f),
|
||||
UINT64_C(0xc6c333a67392c7e4), UINT64_C(0xbc1b436e43a74e9d),
|
||||
UINT64_C(0x95ac9329ac4bc9b5), UINT64_C(0xef74e3e19c7e40cc),
|
||||
UINT64_C(0x601c72b9cc20db47), UINT64_C(0x1ac40271fc15523e),
|
||||
UINT64_C(0x5594765a340a7f3a), UINT64_C(0x2f4c0692043ff643),
|
||||
UINT64_C(0xa02497ca54616dc8), UINT64_C(0xdafce7026454e4b1),
|
||||
UINT64_C(0x3e847f9dc45f37c0), UINT64_C(0x445c0f55f46abeb9),
|
||||
UINT64_C(0xcb349e0da4342532), UINT64_C(0xb1eceec59401ac4b),
|
||||
UINT64_C(0xfebc9aee5c1e814f), UINT64_C(0x8464ea266c2b0836),
|
||||
UINT64_C(0x0b0c7b7e3c7593bd), UINT64_C(0x71d40bb60c401ac4),
|
||||
UINT64_C(0xe8a46c1224f5a634), UINT64_C(0x927c1cda14c02f4d),
|
||||
UINT64_C(0x1d148d82449eb4c6), UINT64_C(0x67ccfd4a74ab3dbf),
|
||||
UINT64_C(0x289c8961bcb410bb), UINT64_C(0x5244f9a98c8199c2),
|
||||
UINT64_C(0xdd2c68f1dcdf0249), UINT64_C(0xa7f41839ecea8b30),
|
||||
UINT64_C(0x438c80a64ce15841), UINT64_C(0x3954f06e7cd4d138),
|
||||
UINT64_C(0xb63c61362c8a4ab3), UINT64_C(0xcce411fe1cbfc3ca),
|
||||
UINT64_C(0x83b465d5d4a0eece), UINT64_C(0xf96c151de49567b7),
|
||||
UINT64_C(0x76048445b4cbfc3c), UINT64_C(0x0cdcf48d84fe7545),
|
||||
UINT64_C(0x6fbd6d5ebd3716b7), UINT64_C(0x15651d968d029fce),
|
||||
UINT64_C(0x9a0d8ccedd5c0445), UINT64_C(0xe0d5fc06ed698d3c),
|
||||
UINT64_C(0xaf85882d2576a038), UINT64_C(0xd55df8e515432941),
|
||||
UINT64_C(0x5a3569bd451db2ca), UINT64_C(0x20ed197575283bb3),
|
||||
UINT64_C(0xc49581ead523e8c2), UINT64_C(0xbe4df122e51661bb),
|
||||
UINT64_C(0x3125607ab548fa30), UINT64_C(0x4bfd10b2857d7349),
|
||||
UINT64_C(0x04ad64994d625e4d), UINT64_C(0x7e7514517d57d734),
|
||||
UINT64_C(0xf11d85092d094cbf), UINT64_C(0x8bc5f5c11d3cc5c6),
|
||||
UINT64_C(0x12b5926535897936), UINT64_C(0x686de2ad05bcf04f),
|
||||
UINT64_C(0xe70573f555e26bc4), UINT64_C(0x9ddd033d65d7e2bd),
|
||||
UINT64_C(0xd28d7716adc8cfb9), UINT64_C(0xa85507de9dfd46c0),
|
||||
UINT64_C(0x273d9686cda3dd4b), UINT64_C(0x5de5e64efd965432),
|
||||
UINT64_C(0xb99d7ed15d9d8743), UINT64_C(0xc3450e196da80e3a),
|
||||
UINT64_C(0x4c2d9f413df695b1), UINT64_C(0x36f5ef890dc31cc8),
|
||||
UINT64_C(0x79a59ba2c5dc31cc), UINT64_C(0x037deb6af5e9b8b5),
|
||||
UINT64_C(0x8c157a32a5b7233e), UINT64_C(0xf6cd0afa9582aa47),
|
||||
UINT64_C(0x4ad64994d625e4da), UINT64_C(0x300e395ce6106da3),
|
||||
UINT64_C(0xbf66a804b64ef628), UINT64_C(0xc5bed8cc867b7f51),
|
||||
UINT64_C(0x8aeeace74e645255), UINT64_C(0xf036dc2f7e51db2c),
|
||||
UINT64_C(0x7f5e4d772e0f40a7), UINT64_C(0x05863dbf1e3ac9de),
|
||||
UINT64_C(0xe1fea520be311aaf), UINT64_C(0x9b26d5e88e0493d6),
|
||||
UINT64_C(0x144e44b0de5a085d), UINT64_C(0x6e963478ee6f8124),
|
||||
UINT64_C(0x21c640532670ac20), UINT64_C(0x5b1e309b16452559),
|
||||
UINT64_C(0xd476a1c3461bbed2), UINT64_C(0xaeaed10b762e37ab),
|
||||
UINT64_C(0x37deb6af5e9b8b5b), UINT64_C(0x4d06c6676eae0222),
|
||||
UINT64_C(0xc26e573f3ef099a9), UINT64_C(0xb8b627f70ec510d0),
|
||||
UINT64_C(0xf7e653dcc6da3dd4), UINT64_C(0x8d3e2314f6efb4ad),
|
||||
UINT64_C(0x0256b24ca6b12f26), UINT64_C(0x788ec2849684a65f),
|
||||
UINT64_C(0x9cf65a1b368f752e), UINT64_C(0xe62e2ad306bafc57),
|
||||
UINT64_C(0x6946bb8b56e467dc), UINT64_C(0x139ecb4366d1eea5),
|
||||
UINT64_C(0x5ccebf68aecec3a1), UINT64_C(0x2616cfa09efb4ad8),
|
||||
UINT64_C(0xa97e5ef8cea5d153), UINT64_C(0xd3a62e30fe90582a),
|
||||
UINT64_C(0xb0c7b7e3c7593bd8), UINT64_C(0xca1fc72bf76cb2a1),
|
||||
UINT64_C(0x45775673a732292a), UINT64_C(0x3faf26bb9707a053),
|
||||
UINT64_C(0x70ff52905f188d57), UINT64_C(0x0a2722586f2d042e),
|
||||
UINT64_C(0x854fb3003f739fa5), UINT64_C(0xff97c3c80f4616dc),
|
||||
UINT64_C(0x1bef5b57af4dc5ad), UINT64_C(0x61372b9f9f784cd4),
|
||||
UINT64_C(0xee5fbac7cf26d75f), UINT64_C(0x9487ca0fff135e26),
|
||||
UINT64_C(0xdbd7be24370c7322), UINT64_C(0xa10fceec0739fa5b),
|
||||
UINT64_C(0x2e675fb4576761d0), UINT64_C(0x54bf2f7c6752e8a9),
|
||||
UINT64_C(0xcdcf48d84fe75459), UINT64_C(0xb71738107fd2dd20),
|
||||
UINT64_C(0x387fa9482f8c46ab), UINT64_C(0x42a7d9801fb9cfd2),
|
||||
UINT64_C(0x0df7adabd7a6e2d6), UINT64_C(0x772fdd63e7936baf),
|
||||
UINT64_C(0xf8474c3bb7cdf024), UINT64_C(0x829f3cf387f8795d),
|
||||
UINT64_C(0x66e7a46c27f3aa2c), UINT64_C(0x1c3fd4a417c62355),
|
||||
UINT64_C(0x935745fc4798b8de), UINT64_C(0xe98f353477ad31a7),
|
||||
UINT64_C(0xa6df411fbfb21ca3), UINT64_C(0xdc0731d78f8795da),
|
||||
UINT64_C(0x536fa08fdfd90e51), UINT64_C(0x29b7d047efec8728),
|
||||
};
|
||||
#define POLY UINT64_C(0xad93d23594c935a9)
|
||||
/******************** BEGIN GENERATED PYCRC FUNCTIONS ********************/
|
||||
/**
|
||||
* Generated on Sun Dec 21 14:14:07 2014,
|
||||
* by pycrc v0.8.2, https://www.tty1.net/pycrc/
|
||||
*
|
||||
* LICENSE ON GENERATED CODE:
|
||||
* ==========================
|
||||
* As of version 0.6, pycrc is released under the terms of the MIT licence.
|
||||
* The code generated by pycrc is not considered a substantial portion of the
|
||||
* software, therefore the author of pycrc will not claim any copyright on
|
||||
* the generated code.
|
||||
* ==========================
|
||||
*
|
||||
* CRC configuration:
|
||||
* Width = 64
|
||||
* Poly = 0xad93d23594c935a9
|
||||
* XorIn = 0xffffffffffffffff
|
||||
* ReflectIn = True
|
||||
* XorOut = 0x0000000000000000
|
||||
* ReflectOut = True
|
||||
* Algorithm = bit-by-bit-fast
|
||||
*
|
||||
* Modifications after generation (by matt):
|
||||
* - included finalize step in-line with update for single-call generation
|
||||
* - re-worked some inner variable architectures
|
||||
* - adjusted function parameters to match expected prototypes.
|
||||
*****************************************************************************/
|
||||
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
|
||||
uint64_t j;
|
||||
/**
|
||||
* Reflect all bits of a \a data word of \a data_len bytes.
|
||||
*
|
||||
* \param data The data word to be reflected.
|
||||
* \param data_len The width of \a data expressed in number of bits.
|
||||
* \return The reflected data.
|
||||
*****************************************************************************/
|
||||
static inline uint_fast64_t crc_reflect(uint_fast64_t data, size_t data_len) {
|
||||
uint_fast64_t ret = data & 0x01;
|
||||
|
||||
for (j = 0; j < l; j++) {
|
||||
uint8_t byte = s[j];
|
||||
crc = crc64_tab[(uint8_t)crc ^ byte] ^ (crc >> 8);
|
||||
for (size_t i = 1; i < data_len; i++) {
|
||||
data >>= 1;
|
||||
ret = (ret << 1) | (data & 0x01);
|
||||
}
|
||||
return crc;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the crc value with new data.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \param data Pointer to a buffer of \a data_len bytes.
|
||||
* \param data_len Number of bytes in the \a data buffer.
|
||||
* \return The updated crc value.
|
||||
******************************************************************************/
|
||||
uint64_t _crc64(uint_fast64_t crc, const void *in_data, const uint64_t len) {
|
||||
const uint8_t *data = in_data;
|
||||
unsigned long long bit;
|
||||
|
||||
for (uint64_t offset = 0; offset < len; offset++) {
|
||||
uint8_t c = data[offset];
|
||||
for (uint_fast8_t i = 0x01; i & 0xff; i <<= 1) {
|
||||
bit = crc & 0x8000000000000000;
|
||||
if (c & i) {
|
||||
bit = !bit;
|
||||
}
|
||||
|
||||
crc <<= 1;
|
||||
if (bit) {
|
||||
crc ^= POLY;
|
||||
}
|
||||
}
|
||||
|
||||
crc &= 0xffffffffffffffff;
|
||||
}
|
||||
|
||||
crc = crc & 0xffffffffffffffff;
|
||||
return crc_reflect(crc, 64) ^ 0x0000000000000000;
|
||||
}
|
||||
|
||||
/******************** END GENERATED PYCRC FUNCTIONS ********************/
|
||||
|
||||
/* Initializes the 16KB lookup tables. */
|
||||
void crc64_init(void) {
|
||||
crcspeed64native_init(_crc64, crc64_table);
|
||||
}
|
||||
|
||||
/* Compute crc64 */
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
|
||||
return crcspeed64native(crc64_table, crc, (void *) s, l);
|
||||
}
|
||||
|
||||
/* Test main */
|
||||
@@ -188,8 +130,31 @@ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) {
|
||||
int crc64Test(int argc, char *argv[]) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
printf("e9c6d914c4b8d9ca == %016llx\n",
|
||||
(unsigned long long) crc64(0,(unsigned char*)"123456789",9));
|
||||
crc64_init();
|
||||
printf("[calcula]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
|
||||
(uint64_t)_crc64(0, "123456789", 9));
|
||||
printf("[64speed]: e9c6d914c4b8d9ca == %016" PRIx64 "\n",
|
||||
(uint64_t)crc64(0, "123456789", 9));
|
||||
char li[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed "
|
||||
"do eiusmod tempor incididunt ut labore et dolore magna "
|
||||
"aliqua. Ut enim ad minim veniam, quis nostrud exercitation "
|
||||
"ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis "
|
||||
"aute irure dolor in reprehenderit in voluptate velit esse "
|
||||
"cillum dolore eu fugiat nulla pariatur. Excepteur sint "
|
||||
"occaecat cupidatat non proident, sunt in culpa qui officia "
|
||||
"deserunt mollit anim id est laborum.";
|
||||
printf("[calcula]: c7794709e69683b3 == %016" PRIx64 "\n",
|
||||
(uint64_t)_crc64(0, li, sizeof(li)));
|
||||
printf("[64speed]: c7794709e69683b3 == %016" PRIx64 "\n",
|
||||
(uint64_t)crc64(0, li, sizeof(li)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef REDIS_TEST_MAIN
|
||||
int main(int argc, char *argv[]) {
|
||||
return crc64Test(argc, argv);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void crc64_init(void);
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Mark Adler
|
||||
* Originally by: crc64.c Version 1.4 16 Dec 2013 Mark Adler
|
||||
* Modifications by Matt Stancliff <matt@genges.com>:
|
||||
* - removed CRC64-specific behavior
|
||||
* - added generation of lookup tables by parameters
|
||||
* - removed inversion of CRC input/result
|
||||
* - removed automatic initialization in favor of explicit initialization
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
Mark Adler
|
||||
madler@alumni.caltech.edu
|
||||
*/
|
||||
|
||||
#include "crcspeed.h"
|
||||
|
||||
/* Fill in a CRC constants table. */
|
||||
void crcspeed64little_init(crcfn64 crcfn, uint64_t table[8][256]) {
|
||||
uint64_t crc;
|
||||
|
||||
/* generate CRCs for all single byte sequences */
|
||||
for (int n = 0; n < 256; n++) {
|
||||
unsigned char v = n;
|
||||
table[0][n] = crcfn(0, &v, 1);
|
||||
}
|
||||
|
||||
/* generate nested CRC table for future slice-by-8 lookup */
|
||||
for (int n = 0; n < 256; n++) {
|
||||
crc = table[0][n];
|
||||
for (int k = 1; k < 8; k++) {
|
||||
crc = table[0][crc & 0xff] ^ (crc >> 8);
|
||||
table[k][n] = crc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void crcspeed16little_init(crcfn16 crcfn, uint16_t table[8][256]) {
|
||||
uint16_t crc;
|
||||
|
||||
/* generate CRCs for all single byte sequences */
|
||||
for (int n = 0; n < 256; n++) {
|
||||
table[0][n] = crcfn(0, &n, 1);
|
||||
}
|
||||
|
||||
/* generate nested CRC table for future slice-by-8 lookup */
|
||||
for (int n = 0; n < 256; n++) {
|
||||
crc = table[0][n];
|
||||
for (int k = 1; k < 8; k++) {
|
||||
crc = table[0][(crc >> 8) & 0xff] ^ (crc << 8);
|
||||
table[k][n] = crc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Reverse the bytes in a 64-bit word. */
|
||||
static inline uint64_t rev8(uint64_t a) {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return __builtin_bswap64(a);
|
||||
#else
|
||||
uint64_t m;
|
||||
|
||||
m = UINT64_C(0xff00ff00ff00ff);
|
||||
a = ((a >> 8) & m) | (a & m) << 8;
|
||||
m = UINT64_C(0xffff0000ffff);
|
||||
a = ((a >> 16) & m) | (a & m) << 16;
|
||||
return a >> 32 | a << 32;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* This function is called once to initialize the CRC table for use on a
|
||||
big-endian architecture. */
|
||||
void crcspeed64big_init(crcfn64 fn, uint64_t big_table[8][256]) {
|
||||
/* Create the little endian table then reverse all the entires. */
|
||||
crcspeed64little_init(fn, big_table);
|
||||
for (int k = 0; k < 8; k++) {
|
||||
for (int n = 0; n < 256; n++) {
|
||||
big_table[k][n] = rev8(big_table[k][n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void crcspeed16big_init(crcfn16 fn, uint16_t big_table[8][256]) {
|
||||
/* Create the little endian table then reverse all the entires. */
|
||||
crcspeed16little_init(fn, big_table);
|
||||
for (int k = 0; k < 8; k++) {
|
||||
for (int n = 0; n < 256; n++) {
|
||||
big_table[k][n] = rev8(big_table[k][n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate a non-inverted CRC multiple bytes at a time on a little-endian
|
||||
* architecture. If you need inverted CRC, invert *before* calling and invert
|
||||
* *after* calling.
|
||||
* 64 bit crc = process 8 bytes at once;
|
||||
*/
|
||||
uint64_t crcspeed64little(uint64_t little_table[8][256], uint64_t crc,
|
||||
void *buf, size_t len) {
|
||||
unsigned char *next = buf;
|
||||
|
||||
/* process individual bytes until we reach an 8-byte aligned pointer */
|
||||
while (len && ((uintptr_t)next & 7) != 0) {
|
||||
crc = little_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
/* fast middle processing, 8 bytes (aligned!) per loop */
|
||||
while (len >= 8) {
|
||||
crc ^= *(uint64_t *)next;
|
||||
crc = little_table[7][crc & 0xff] ^
|
||||
little_table[6][(crc >> 8) & 0xff] ^
|
||||
little_table[5][(crc >> 16) & 0xff] ^
|
||||
little_table[4][(crc >> 24) & 0xff] ^
|
||||
little_table[3][(crc >> 32) & 0xff] ^
|
||||
little_table[2][(crc >> 40) & 0xff] ^
|
||||
little_table[1][(crc >> 48) & 0xff] ^
|
||||
little_table[0][crc >> 56];
|
||||
next += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
/* process remaining bytes (can't be larger than 8) */
|
||||
while (len) {
|
||||
crc = little_table[0][(crc ^ *next++) & 0xff] ^ (crc >> 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint16_t crcspeed16little(uint16_t little_table[8][256], uint16_t crc,
|
||||
void *buf, size_t len) {
|
||||
unsigned char *next = buf;
|
||||
|
||||
/* process individual bytes until we reach an 8-byte aligned pointer */
|
||||
while (len && ((uintptr_t)next & 7) != 0) {
|
||||
crc = little_table[0][((crc >> 8) ^ *next++) & 0xff] ^ (crc << 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
/* fast middle processing, 8 bytes (aligned!) per loop */
|
||||
while (len >= 8) {
|
||||
uint64_t n = *(uint64_t *)next;
|
||||
crc = little_table[7][(n & 0xff) ^ ((crc >> 8) & 0xff)] ^
|
||||
little_table[6][((n >> 8) & 0xff) ^ (crc & 0xff)] ^
|
||||
little_table[5][(n >> 16) & 0xff] ^
|
||||
little_table[4][(n >> 24) & 0xff] ^
|
||||
little_table[3][(n >> 32) & 0xff] ^
|
||||
little_table[2][(n >> 40) & 0xff] ^
|
||||
little_table[1][(n >> 48) & 0xff] ^
|
||||
little_table[0][n >> 56];
|
||||
next += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
/* process remaining bytes (can't be larger than 8) */
|
||||
while (len) {
|
||||
crc = little_table[0][((crc >> 8) ^ *next++) & 0xff] ^ (crc << 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
/* Calculate a non-inverted CRC eight bytes at a time on a big-endian
|
||||
* architecture.
|
||||
*/
|
||||
uint64_t crcspeed64big(uint64_t big_table[8][256], uint64_t crc, void *buf,
|
||||
size_t len) {
|
||||
unsigned char *next = buf;
|
||||
|
||||
crc = rev8(crc);
|
||||
while (len && ((uintptr_t)next & 7) != 0) {
|
||||
crc = big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
while (len >= 8) {
|
||||
crc ^= *(uint64_t *)next;
|
||||
crc = big_table[0][crc & 0xff] ^
|
||||
big_table[1][(crc >> 8) & 0xff] ^
|
||||
big_table[2][(crc >> 16) & 0xff] ^
|
||||
big_table[3][(crc >> 24) & 0xff] ^
|
||||
big_table[4][(crc >> 32) & 0xff] ^
|
||||
big_table[5][(crc >> 40) & 0xff] ^
|
||||
big_table[6][(crc >> 48) & 0xff] ^
|
||||
big_table[7][crc >> 56];
|
||||
next += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len) {
|
||||
crc = big_table[0][(crc >> 56) ^ *next++] ^ (crc << 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
return rev8(crc);
|
||||
}
|
||||
|
||||
/* WARNING: Completely untested on big endian architecture. Possibly broken. */
|
||||
uint16_t crcspeed16big(uint16_t big_table[8][256], uint16_t crc_in, void *buf,
|
||||
size_t len) {
|
||||
unsigned char *next = buf;
|
||||
uint64_t crc = crc_in;
|
||||
|
||||
crc = rev8(crc);
|
||||
while (len && ((uintptr_t)next & 7) != 0) {
|
||||
crc = big_table[0][((crc >> (56 - 8)) ^ *next++) & 0xff] ^ (crc >> 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
while (len >= 8) {
|
||||
uint64_t n = *(uint64_t *)next;
|
||||
crc = big_table[0][(n & 0xff) ^ ((crc >> (56 - 8)) & 0xff)] ^
|
||||
big_table[1][((n >> 8) & 0xff) ^ (crc & 0xff)] ^
|
||||
big_table[2][(n >> 16) & 0xff] ^
|
||||
big_table[3][(n >> 24) & 0xff] ^
|
||||
big_table[4][(n >> 32) & 0xff] ^
|
||||
big_table[5][(n >> 40) & 0xff] ^
|
||||
big_table[6][(n >> 48) & 0xff] ^
|
||||
big_table[7][n >> 56];
|
||||
next += 8;
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
while (len) {
|
||||
crc = big_table[0][((crc >> (56 - 8)) ^ *next++) & 0xff] ^ (crc >> 8);
|
||||
len--;
|
||||
}
|
||||
|
||||
return rev8(crc);
|
||||
}
|
||||
|
||||
/* Return the CRC of buf[0..len-1] with initial crc, processing eight bytes
|
||||
at a time using passed-in lookup table.
|
||||
This selects one of two routines depending on the endianess of
|
||||
the architecture. */
|
||||
uint64_t crcspeed64native(uint64_t table[8][256], uint64_t crc, void *buf,
|
||||
size_t len) {
|
||||
uint64_t n = 1;
|
||||
|
||||
return *(char *)&n ? crcspeed64little(table, crc, buf, len)
|
||||
: crcspeed64big(table, crc, buf, len);
|
||||
}
|
||||
|
||||
uint16_t crcspeed16native(uint16_t table[8][256], uint16_t crc, void *buf,
|
||||
size_t len) {
|
||||
uint64_t n = 1;
|
||||
|
||||
return *(char *)&n ? crcspeed16little(table, crc, buf, len)
|
||||
: crcspeed16big(table, crc, buf, len);
|
||||
}
|
||||
|
||||
/* Initialize CRC lookup table in architecture-dependent manner. */
|
||||
void crcspeed64native_init(crcfn64 fn, uint64_t table[8][256]) {
|
||||
uint64_t n = 1;
|
||||
|
||||
*(char *)&n ? crcspeed64little_init(fn, table)
|
||||
: crcspeed64big_init(fn, table);
|
||||
}
|
||||
|
||||
void crcspeed16native_init(crcfn16 fn, uint16_t table[8][256]) {
|
||||
uint64_t n = 1;
|
||||
|
||||
*(char *)&n ? crcspeed16little_init(fn, table)
|
||||
: crcspeed16big_init(fn, table);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/* 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 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 CRCSPEED_H
|
||||
#define CRCSPEED_H
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef uint64_t (*crcfn64)(uint64_t, const void *, const uint64_t);
|
||||
typedef uint16_t (*crcfn16)(uint16_t, const void *, const uint64_t);
|
||||
|
||||
/* CRC-64 */
|
||||
void crcspeed64little_init(crcfn64 fn, uint64_t table[8][256]);
|
||||
void crcspeed64big_init(crcfn64 fn, uint64_t table[8][256]);
|
||||
void crcspeed64native_init(crcfn64 fn, uint64_t table[8][256]);
|
||||
|
||||
uint64_t crcspeed64little(uint64_t table[8][256], uint64_t crc, void *buf,
|
||||
size_t len);
|
||||
uint64_t crcspeed64big(uint64_t table[8][256], uint64_t crc, void *buf,
|
||||
size_t len);
|
||||
uint64_t crcspeed64native(uint64_t table[8][256], uint64_t crc, void *buf,
|
||||
size_t len);
|
||||
|
||||
/* CRC-16 */
|
||||
void crcspeed16little_init(crcfn16 fn, uint16_t table[8][256]);
|
||||
void crcspeed16big_init(crcfn16 fn, uint16_t table[8][256]);
|
||||
void crcspeed16native_init(crcfn16 fn, uint16_t table[8][256]);
|
||||
|
||||
uint16_t crcspeed16little(uint16_t table[8][256], uint16_t crc, void *buf,
|
||||
size_t len);
|
||||
uint16_t crcspeed16big(uint16_t table[8][256], uint16_t crc, void *buf,
|
||||
size_t len);
|
||||
uint16_t crcspeed16native(uint16_t table[8][256], uint16_t crc, void *buf,
|
||||
size_t len);
|
||||
#endif
|
||||
@@ -34,6 +34,13 @@
|
||||
#include <signal.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* Database backup. */
|
||||
struct dbBackup {
|
||||
redisDb *dbarray;
|
||||
rax *slots_to_keys;
|
||||
uint64_t slots_keys_count[CLUSTER_SLOTS];
|
||||
};
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* C-level DB API
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -102,11 +109,8 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
/* 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 safe
|
||||
* to return NULL ASAP. */
|
||||
if (server.masterhost == NULL) {
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
return NULL;
|
||||
}
|
||||
if (server.masterhost == NULL)
|
||||
goto keymiss;
|
||||
|
||||
/* However if we are in the context of a slave, expireIfNeeded() will
|
||||
* not really try to expire the key, it only returns information
|
||||
@@ -116,7 +120,7 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
* 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
|
||||
* to clients accessing expired values in a read-only fashion, that
|
||||
* will say the key as non existing.
|
||||
*
|
||||
* Notably this covers GETs when slaves are used to scale reads. */
|
||||
@@ -125,19 +129,21 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
server.current_client->cmd &&
|
||||
server.current_client->cmd->flags & CMD_READONLY)
|
||||
{
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
return NULL;
|
||||
goto keymiss;
|
||||
}
|
||||
}
|
||||
val = lookupKey(db,key,flags);
|
||||
if (val == NULL) {
|
||||
if (val == NULL)
|
||||
goto keymiss;
|
||||
server.stat_keyspace_hits++;
|
||||
return val;
|
||||
|
||||
keymiss:
|
||||
if (!(flags & LOOKUP_NONOTIFY)) {
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
}
|
||||
else
|
||||
server.stat_keyspace_hits++;
|
||||
return val;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Like lookupKeyReadWithFlags(), but does not use any flag, which is the
|
||||
@@ -182,9 +188,28 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
|
||||
serverAssertWithInfo(NULL,key,retval == DICT_OK);
|
||||
if (val->type == OBJ_LIST ||
|
||||
val->type == OBJ_ZSET)
|
||||
val->type == OBJ_ZSET ||
|
||||
val->type == OBJ_STREAM)
|
||||
signalKeyAsReady(db, key);
|
||||
if (server.cluster_enabled) slotToKeyAdd(key->ptr);
|
||||
}
|
||||
|
||||
/* This is a special version of dbAdd() that is used only when loading
|
||||
* keys from the RDB file: the key is passed as an SDS string that is
|
||||
* retained by the function (and not freed by the caller).
|
||||
*
|
||||
* Moreover this function will not abort if the key is already busy, to
|
||||
* give more control to the caller, nor will signal the key as ready
|
||||
* since it is not useful in this context.
|
||||
*
|
||||
* The function returns 1 if the key was added to the database, taking
|
||||
* ownership of the SDS string, otherwise 0 is returned, and is up to the
|
||||
* caller to free the SDS string. */
|
||||
int dbAddRDBLoad(redisDb *db, sds key, robj *val) {
|
||||
int retval = dictAdd(db->dict, key, val);
|
||||
if (retval != DICT_OK) return 0;
|
||||
if (server.cluster_enabled) slotToKeyAdd(key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Overwrite an existing key with a new value. Incrementing the reference
|
||||
@@ -219,8 +244,10 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
* 3) The expire time of the key is reset (the key is made persistent),
|
||||
* unless 'keepttl' is true.
|
||||
*
|
||||
* All the new keys in the database should be created via this interface. */
|
||||
void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl) {
|
||||
* All the new keys in the database should be created via this interface.
|
||||
* The client 'c' argument may be set to NULL if the operation is performed
|
||||
* in a context where there is no clear client performing the operation. */
|
||||
void genericSetKey(client *c, redisDb *db, robj *key, robj *val, int keepttl, int signal) {
|
||||
if (lookupKeyWrite(db,key) == NULL) {
|
||||
dbAdd(db,key,val);
|
||||
} else {
|
||||
@@ -228,12 +255,12 @@ void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl) {
|
||||
}
|
||||
incrRefCount(val);
|
||||
if (!keepttl) removeExpire(db,key);
|
||||
signalModifiedKey(db,key);
|
||||
if (signal) signalModifiedKey(c,db,key);
|
||||
}
|
||||
|
||||
/* Common case for genericSetKey() where the TTL is not retained. */
|
||||
void setKey(redisDb *db, robj *key, robj *val) {
|
||||
genericSetKey(db,key,val,0);
|
||||
void setKey(client *c, redisDb *db, robj *key, robj *val) {
|
||||
genericSetKey(c,db,key,val,0,1);
|
||||
}
|
||||
|
||||
/* Return true if the specified key exists in the specified database.
|
||||
@@ -287,7 +314,7 @@ int dbSyncDelete(redisDb *db, robj *key) {
|
||||
* the key, because it is shared with the main dictionary. */
|
||||
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
|
||||
if (dictDelete(db->dict,key->ptr) == DICT_OK) {
|
||||
if (server.cluster_enabled) slotToKeyDel(key);
|
||||
if (server.cluster_enabled) slotToKeyDel(key->ptr);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
@@ -339,41 +366,18 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Remove all keys from all the databases in a Redis server.
|
||||
* If callback is given the function is called from time to time to
|
||||
* signal that work is in progress.
|
||||
/* Remove all keys from the database(s) structure. The dbarray argument
|
||||
* may not be the server main DBs (could be a backup).
|
||||
*
|
||||
* The dbnum can be -1 if all the DBs should be flushed, or the specified
|
||||
* DB number if we want to flush only a single Redis database number.
|
||||
*
|
||||
* Flags are be EMPTYDB_NO_FLAGS if no special flags are specified or
|
||||
* EMPTYDB_ASYNC if we want the memory to be freed in a different thread
|
||||
* and the function to return ASAP.
|
||||
*
|
||||
* On success the fuction returns the number of keys removed from the
|
||||
* database(s). Otherwise -1 is returned in the specific case the
|
||||
* DB number is out of range, and errno is set to EINVAL. */
|
||||
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*)) {
|
||||
int async = (flags & EMPTYDB_ASYNC);
|
||||
* The dbnum can be -1 if all the DBs should be emptied, or the specified
|
||||
* DB index if we want to empty only a single database.
|
||||
* The function returns the number of keys removed from the database(s). */
|
||||
long long emptyDbStructure(redisDb *dbarray, int dbnum, int async,
|
||||
void(callback)(void*))
|
||||
{
|
||||
long long removed = 0;
|
||||
|
||||
if (dbnum < -1 || dbnum >= server.dbnum) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Fire the flushdb modules event. */
|
||||
RedisModuleFlushInfoV1 fi = {REDISMODULE_FLUSHINFO_VERSION,!async,dbnum};
|
||||
moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
|
||||
REDISMODULE_SUBEVENT_FLUSHDB_START,
|
||||
&fi);
|
||||
|
||||
/* Make sure the WATCHed keys are affected by the FLUSH* commands.
|
||||
* Note that we need to call the function while the keys are still
|
||||
* there. */
|
||||
signalFlushedDb(dbnum);
|
||||
|
||||
int startdb, enddb;
|
||||
|
||||
if (dbnum == -1) {
|
||||
startdb = 0;
|
||||
enddb = server.dbnum-1;
|
||||
@@ -389,14 +393,56 @@ long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(
|
||||
dictEmpty(dbarray[j].dict,callback);
|
||||
dictEmpty(dbarray[j].expires,callback);
|
||||
}
|
||||
/* Because all keys of database are removed, reset average ttl. */
|
||||
dbarray[j].avg_ttl = 0;
|
||||
dbarray[j].expires_cursor = 0;
|
||||
}
|
||||
if (server.cluster_enabled) {
|
||||
if (async) {
|
||||
slotToKeyFlushAsync();
|
||||
} else {
|
||||
slotToKeyFlush();
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/* Remove all keys from all the databases in a Redis server.
|
||||
* If callback is given the function is called from time to time to
|
||||
* signal that work is in progress.
|
||||
*
|
||||
* The dbnum can be -1 if all the DBs should be flushed, or the specified
|
||||
* DB number if we want to flush only a single Redis database number.
|
||||
*
|
||||
* Flags are be EMPTYDB_NO_FLAGS if no special flags are specified or
|
||||
* EMPTYDB_ASYNC if we want the memory to be freed in a different thread
|
||||
* and the function to return ASAP.
|
||||
*
|
||||
* On success the function returns the number of keys removed from the
|
||||
* database(s). Otherwise -1 is returned in the specific case the
|
||||
* DB number is out of range, and errno is set to EINVAL. */
|
||||
long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
int async = (flags & EMPTYDB_ASYNC);
|
||||
RedisModuleFlushInfoV1 fi = {REDISMODULE_FLUSHINFO_VERSION,!async,dbnum};
|
||||
long long removed = 0;
|
||||
|
||||
if (dbnum < -1 || dbnum >= server.dbnum) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Fire the flushdb modules event. */
|
||||
moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
|
||||
REDISMODULE_SUBEVENT_FLUSHDB_START,
|
||||
&fi);
|
||||
|
||||
/* Make sure the WATCHed keys are affected by the FLUSH* commands.
|
||||
* Note that we need to call the function while the keys are still
|
||||
* there. */
|
||||
signalFlushedDb(dbnum);
|
||||
|
||||
/* Empty redis database structure. */
|
||||
removed = emptyDbStructure(server.db, dbnum, async, callback);
|
||||
|
||||
/* Flush slots to keys map if enable cluster, we can flush entire
|
||||
* slots to keys map whatever dbnum because only support one DB
|
||||
* in cluster mode. */
|
||||
if (server.cluster_enabled) slotToKeyFlush(async);
|
||||
|
||||
if (dbnum == -1) flushSlaveKeysWithExpireList();
|
||||
|
||||
/* Also fire the end event. Note that this event will fire almost
|
||||
@@ -408,8 +454,78 @@ long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(
|
||||
return removed;
|
||||
}
|
||||
|
||||
long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
return emptyDbGeneric(server.db, dbnum, flags, callback);
|
||||
/* Store a backup of the database for later use, and put an empty one
|
||||
* instead of it. */
|
||||
dbBackup *backupDb(void) {
|
||||
dbBackup *backup = zmalloc(sizeof(dbBackup));
|
||||
|
||||
/* Backup main DBs. */
|
||||
backup->dbarray = zmalloc(sizeof(redisDb)*server.dbnum);
|
||||
for (int i=0; i<server.dbnum; i++) {
|
||||
backup->dbarray[i] = server.db[i];
|
||||
server.db[i].dict = dictCreate(&dbDictType,NULL);
|
||||
server.db[i].expires = dictCreate(&keyptrDictType,NULL);
|
||||
}
|
||||
|
||||
/* Backup cluster slots to keys map if enable cluster. */
|
||||
if (server.cluster_enabled) {
|
||||
backup->slots_to_keys = server.cluster->slots_to_keys;
|
||||
memcpy(backup->slots_keys_count, server.cluster->slots_keys_count,
|
||||
sizeof(server.cluster->slots_keys_count));
|
||||
server.cluster->slots_to_keys = raxNew();
|
||||
memset(server.cluster->slots_keys_count, 0,
|
||||
sizeof(server.cluster->slots_keys_count));
|
||||
}
|
||||
|
||||
return backup;
|
||||
}
|
||||
|
||||
/* Discard a previously created backup, this can be slow (similar to FLUSHALL)
|
||||
* Arguments are similar to the ones of emptyDb, see EMPTYDB_ flags. */
|
||||
void discardDbBackup(dbBackup *buckup, int flags, void(callback)(void*)) {
|
||||
int async = (flags & EMPTYDB_ASYNC);
|
||||
|
||||
/* Release main DBs backup . */
|
||||
emptyDbStructure(buckup->dbarray, -1, async, callback);
|
||||
for (int i=0; i<server.dbnum; i++) {
|
||||
dictRelease(buckup->dbarray[i].dict);
|
||||
dictRelease(buckup->dbarray[i].expires);
|
||||
}
|
||||
|
||||
/* Release slots to keys map backup if enable cluster. */
|
||||
if (server.cluster_enabled) freeSlotsToKeysMap(buckup->slots_to_keys, async);
|
||||
|
||||
/* Release buckup. */
|
||||
zfree(buckup->dbarray);
|
||||
zfree(buckup);
|
||||
}
|
||||
|
||||
/* Restore the previously created backup (discarding what currently resides
|
||||
* in the db).
|
||||
* This function should be called after the current contents of the database
|
||||
* was emptied with a previous call to emptyDb (possibly using the async mode). */
|
||||
void restoreDbBackup(dbBackup *buckup) {
|
||||
/* Restore main DBs. */
|
||||
for (int i=0; i<server.dbnum; i++) {
|
||||
serverAssert(dictSize(server.db[i].dict) == 0);
|
||||
serverAssert(dictSize(server.db[i].expires) == 0);
|
||||
dictRelease(server.db[i].dict);
|
||||
dictRelease(server.db[i].expires);
|
||||
server.db[i] = buckup->dbarray[i];
|
||||
}
|
||||
|
||||
/* Restore slots to keys map backup if enable cluster. */
|
||||
if (server.cluster_enabled) {
|
||||
serverAssert(server.cluster->slots_to_keys->numele == 0);
|
||||
raxFree(server.cluster->slots_to_keys);
|
||||
server.cluster->slots_to_keys = buckup->slots_to_keys;
|
||||
memcpy(server.cluster->slots_keys_count, buckup->slots_keys_count,
|
||||
sizeof(server.cluster->slots_keys_count));
|
||||
}
|
||||
|
||||
/* Release buckup. */
|
||||
zfree(buckup->dbarray);
|
||||
zfree(buckup);
|
||||
}
|
||||
|
||||
int selectDb(client *c, int id) {
|
||||
@@ -437,13 +553,26 @@ long long dbTotalServerKeyCount() {
|
||||
* Every time a DB is flushed the function signalFlushDb() is called.
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void signalModifiedKey(redisDb *db, robj *key) {
|
||||
/* Note that the 'c' argument may be NULL if the key was modified out of
|
||||
* a context of a client. */
|
||||
void signalModifiedKey(client *c, redisDb *db, robj *key) {
|
||||
touchWatchedKey(db,key);
|
||||
trackingInvalidateKey(key);
|
||||
trackingInvalidateKey(c,key);
|
||||
}
|
||||
|
||||
void signalFlushedDb(int dbid) {
|
||||
touchWatchedKeysOnFlush(dbid);
|
||||
int startdb, enddb;
|
||||
if (dbid == -1) {
|
||||
startdb = 0;
|
||||
enddb = server.dbnum-1;
|
||||
} else {
|
||||
startdb = enddb = dbid;
|
||||
}
|
||||
|
||||
for (int j = startdb; j <= enddb; j++) {
|
||||
touchAllWatchedKeysInDb(&server.db[j], NULL);
|
||||
}
|
||||
|
||||
trackingInvalidateKeysOnFlush(dbid);
|
||||
}
|
||||
|
||||
@@ -533,7 +662,7 @@ void delGenericCommand(client *c, int lazy) {
|
||||
int deleted = lazy ? dbAsyncDelete(c->db,c->argv[j]) :
|
||||
dbSyncDelete(c->db,c->argv[j]);
|
||||
if (deleted) {
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
signalModifiedKey(c,c->db,c->argv[j]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,
|
||||
"del",c->argv[j],c->db->id);
|
||||
server.dirty++;
|
||||
@@ -544,7 +673,7 @@ void delGenericCommand(client *c, int lazy) {
|
||||
}
|
||||
|
||||
void delCommand(client *c) {
|
||||
delGenericCommand(c,0);
|
||||
delGenericCommand(c,server.lazyfree_lazy_user_del);
|
||||
}
|
||||
|
||||
void unlinkCommand(client *c) {
|
||||
@@ -558,7 +687,7 @@ void existsCommand(client *c) {
|
||||
int j;
|
||||
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
if (lookupKeyRead(c->db,c->argv[j])) count++;
|
||||
if (lookupKeyReadWithFlags(c->db,c->argv[j],LOOKUP_NOTOUCH)) count++;
|
||||
}
|
||||
addReplyLongLong(c,count);
|
||||
}
|
||||
@@ -602,7 +731,7 @@ void keysCommand(client *c) {
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
|
||||
di = dictGetSafeIterator(c->db->dict);
|
||||
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
|
||||
allkeys = (pattern[0] == '*' && plen == 1);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key = dictGetKey(de);
|
||||
robj *keyobj;
|
||||
@@ -832,7 +961,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
/* Filter element if it is an expired key. */
|
||||
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
|
||||
|
||||
/* Remove the element and its associted value if needed. */
|
||||
/* Remove the element and its associated value if needed. */
|
||||
if (filter) {
|
||||
decrRefCount(kobj);
|
||||
listDelNode(keys, node);
|
||||
@@ -929,14 +1058,6 @@ void shutdownCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* When SHUTDOWN is called while the server is loading a dataset in
|
||||
* memory we need to make sure no attempt is performed to save
|
||||
* the dataset on shutdown (otherwise it could overwrite the current DB
|
||||
* with half-read data).
|
||||
*
|
||||
* Also when in Sentinel mode clear the SAVE flag and force NOSAVE. */
|
||||
if (server.loading || server.sentinel_mode)
|
||||
flags = (flags & ~SHUTDOWN_SAVE) | SHUTDOWN_NOSAVE;
|
||||
if (prepareForShutdown(flags) == C_OK) exit(0);
|
||||
addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
|
||||
}
|
||||
@@ -973,8 +1094,8 @@ void renameGenericCommand(client *c, int nx) {
|
||||
dbAdd(c->db,c->argv[2],o);
|
||||
if (expire != -1) setExpire(c,c->db,c->argv[2],expire);
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[2]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_from",
|
||||
c->argv[1],c->db->id);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"rename_to",
|
||||
@@ -1042,8 +1163,8 @@ void moveCommand(client *c) {
|
||||
|
||||
/* OK! key moved, free the entry in the source DB */
|
||||
dbDelete(src,c->argv[1]);
|
||||
signalModifiedKey(src,c->argv[1]);
|
||||
signalModifiedKey(dst,c->argv[1]);
|
||||
signalModifiedKey(c,src,c->argv[1]);
|
||||
signalModifiedKey(c,dst,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,
|
||||
"move_from",c->argv[1],src->id);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,
|
||||
@@ -1107,9 +1228,14 @@ int dbSwapDatabases(long id1, long id2) {
|
||||
* However normally we only do this check for efficiency reasons
|
||||
* in dbAdd() when a list is created. So here we need to rescan
|
||||
* the list of clients blocked on lists and signal lists as ready
|
||||
* if needed. */
|
||||
* if needed.
|
||||
*
|
||||
* Also the swapdb should make transaction fail if there is any
|
||||
* client watching keys */
|
||||
scanDatabaseForReadyLists(db1);
|
||||
touchAllWatchedKeysInDb(db1, db2);
|
||||
scanDatabaseForReadyLists(db2);
|
||||
touchAllWatchedKeysInDb(db2, db1);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
@@ -1137,6 +1263,8 @@ void swapdbCommand(client *c) {
|
||||
addReplyError(c,"DB index is out of range");
|
||||
return;
|
||||
} else {
|
||||
RedisModuleSwapDbInfo si = {REDISMODULE_SWAPDBINFO_VERSION,id1,id2};
|
||||
moduleFireServerEvent(REDISMODULE_EVENT_SWAPDB,0,&si);
|
||||
server.dirty++;
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
@@ -1285,28 +1413,64 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
propagateExpire(db,key,server.lazyfree_lazy_expire);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",key,db->id);
|
||||
return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
if (retval) signalModifiedKey(NULL,db,key);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* API to get key arguments from commands
|
||||
* ---------------------------------------------------------------------------*/
|
||||
|
||||
/* Prepare the getKeysResult struct to hold numkeys, either by using the
|
||||
* pre-allocated keysbuf or by allocating a new array on the heap.
|
||||
*
|
||||
* This function must be called at least once before starting to populate
|
||||
* the result, and can be called repeatedly to enlarge the result array.
|
||||
*/
|
||||
int *getKeysPrepareResult(getKeysResult *result, int numkeys) {
|
||||
/* GETKEYS_RESULT_INIT initializes keys to NULL, point it to the pre-allocated stack
|
||||
* buffer here. */
|
||||
if (!result->keys) {
|
||||
serverAssert(!result->numkeys);
|
||||
result->keys = result->keysbuf;
|
||||
}
|
||||
|
||||
/* Resize if necessary */
|
||||
if (numkeys > result->size) {
|
||||
if (result->keys != result->keysbuf) {
|
||||
/* We're not using a static buffer, just (re)alloc */
|
||||
result->keys = zrealloc(result->keys, numkeys * sizeof(int));
|
||||
} else {
|
||||
/* We are using a static buffer, copy its contents */
|
||||
result->keys = zmalloc(numkeys * sizeof(int));
|
||||
if (result->numkeys)
|
||||
memcpy(result->keys, result->keysbuf, result->numkeys * sizeof(int));
|
||||
}
|
||||
result->size = numkeys;
|
||||
}
|
||||
|
||||
return result->keys;
|
||||
}
|
||||
|
||||
/* The base case is to use the keys position as given in the command table
|
||||
* (firstkey, lastkey, step). */
|
||||
int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) {
|
||||
int getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, getKeysResult *result) {
|
||||
int j, i = 0, last, *keys;
|
||||
UNUSED(argv);
|
||||
|
||||
if (cmd->firstkey == 0) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
result->numkeys = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
last = cmd->lastkey;
|
||||
if (last < 0) last = argc+last;
|
||||
keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1));
|
||||
|
||||
int count = ((last - cmd->firstkey)+1);
|
||||
keys = getKeysPrepareResult(result, count);
|
||||
|
||||
for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
|
||||
if (j >= argc) {
|
||||
/* Modules commands, and standard commands with a not fixed number
|
||||
@@ -1316,23 +1480,23 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
* return no keys and expect the command implementation to report
|
||||
* an arity or syntax error. */
|
||||
if (cmd->flags & CMD_MODULE || cmd->arity < 0) {
|
||||
zfree(keys);
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
getKeysFreeResult(result);
|
||||
result->numkeys = 0;
|
||||
return 0;
|
||||
} else {
|
||||
serverPanic("Redis built-in command declared keys positions not matching the arity requirements.");
|
||||
}
|
||||
}
|
||||
keys[i++] = j;
|
||||
}
|
||||
*numkeys = i;
|
||||
return keys;
|
||||
result->numkeys = i;
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Return all the arguments that are keys in the command passed via argc / argv.
|
||||
*
|
||||
* The command returns the positions of all the key arguments inside the array,
|
||||
* so the actual return value is an heap allocated array of integers. The
|
||||
* so the actual return value is a heap allocated array of integers. The
|
||||
* length of the array is returned by reference into *numkeys.
|
||||
*
|
||||
* 'cmd' must be point to the corresponding entry into the redisCommand
|
||||
@@ -1340,25 +1504,26 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
*
|
||||
* This function uses the command table if a command-specific helper function
|
||||
* is not required, otherwise it calls the command-specific function. */
|
||||
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) {
|
||||
if (cmd->flags & CMD_MODULE_GETKEYS) {
|
||||
return moduleGetCommandKeysViaAPI(cmd,argv,argc,numkeys);
|
||||
return moduleGetCommandKeysViaAPI(cmd,argv,argc,result);
|
||||
} else if (!(cmd->flags & CMD_MODULE) && cmd->getkeys_proc) {
|
||||
return cmd->getkeys_proc(cmd,argv,argc,numkeys);
|
||||
return cmd->getkeys_proc(cmd,argv,argc,result);
|
||||
} else {
|
||||
return getKeysUsingCommandTable(cmd,argv,argc,numkeys);
|
||||
return getKeysUsingCommandTable(cmd,argv,argc,result);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free the result of getKeysFromCommand. */
|
||||
void getKeysFreeResult(int *result) {
|
||||
zfree(result);
|
||||
void getKeysFreeResult(getKeysResult *result) {
|
||||
if (result && result->keys != result->keysbuf)
|
||||
zfree(result->keys);
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from following commands:
|
||||
* ZUNIONSTORE <destkey> <num-keys> <key> <key> ... <key> <options>
|
||||
* ZINTERSTORE <destkey> <num-keys> <key> <key> ... <key> <options> */
|
||||
int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) {
|
||||
int i, num, *keys;
|
||||
UNUSED(cmd);
|
||||
|
||||
@@ -1366,28 +1531,30 @@ int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *nu
|
||||
/* Sanity check. Don't return any key if the command is going to
|
||||
* reply with syntax error. */
|
||||
if (num < 1 || num > (argc-3)) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
result->numkeys = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Keys in z{union,inter}store come from two places:
|
||||
* argv[1] = storage key,
|
||||
* argv[3...n] = keys to intersect */
|
||||
keys = zmalloc(sizeof(int)*(num+1));
|
||||
/* Total keys = {union,inter} keys + storage key */
|
||||
keys = getKeysPrepareResult(result, num+1);
|
||||
result->numkeys = num+1;
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
for (i = 0; i < num; i++) keys[i] = 3+i;
|
||||
|
||||
/* Finally add the argv[1] key position (the storage key target). */
|
||||
keys[num] = 1;
|
||||
*numkeys = num+1; /* Total keys = {union,inter} keys + storage key */
|
||||
return keys;
|
||||
|
||||
return result->numkeys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from the following commands:
|
||||
* EVAL <script> <num-keys> <key> <key> ... <key> [more stuff]
|
||||
* EVALSHA <script> <num-keys> <key> <key> ... <key> [more stuff] */
|
||||
int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) {
|
||||
int i, num, *keys;
|
||||
UNUSED(cmd);
|
||||
|
||||
@@ -1395,17 +1562,17 @@ int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
/* Sanity check. Don't return any key if the command is going to
|
||||
* reply with syntax error. */
|
||||
if (num <= 0 || num > (argc-3)) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
result->numkeys = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
*numkeys = num;
|
||||
keys = getKeysPrepareResult(result, num);
|
||||
result->numkeys = num;
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
for (i = 0; i < num; i++) keys[i] = 3+i;
|
||||
|
||||
return keys;
|
||||
return result->numkeys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from the SORT command.
|
||||
@@ -1415,13 +1582,12 @@ int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
* The first argument of SORT is always a key, however a list of options
|
||||
* follow in SQL-alike style. Here we parse just the minimum in order to
|
||||
* correctly identify keys in the "STORE" option. */
|
||||
int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) {
|
||||
int i, j, num, *keys, found_store = 0;
|
||||
UNUSED(cmd);
|
||||
|
||||
num = 0;
|
||||
keys = zmalloc(sizeof(int)*2); /* Alloc 2 places for the worst case. */
|
||||
|
||||
keys = getKeysPrepareResult(result, 2); /* Alloc 2 places for the worst case. */
|
||||
keys[num++] = 1; /* <sort-key> is always present. */
|
||||
|
||||
/* Search for STORE option. By default we consider options to don't
|
||||
@@ -1453,11 +1619,11 @@ int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
}
|
||||
}
|
||||
}
|
||||
*numkeys = num + found_store;
|
||||
return keys;
|
||||
result->numkeys = num + found_store;
|
||||
return result->numkeys;
|
||||
}
|
||||
|
||||
int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) {
|
||||
int i, num, first, *keys;
|
||||
UNUSED(cmd);
|
||||
|
||||
@@ -1478,17 +1644,17 @@ int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkey
|
||||
}
|
||||
}
|
||||
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
keys = getKeysPrepareResult(result, num);
|
||||
for (i = 0; i < num; i++) keys[i] = first+i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
result->numkeys = num;
|
||||
return num;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from following commands:
|
||||
* GEORADIUS key x y radius unit [WITHDIST] [WITHHASH] [WITHCOORD] [ASC|DESC]
|
||||
* [COUNT count] [STORE key] [STOREDIST key]
|
||||
* GEORADIUSBYMEMBER key member radius unit ... options ... */
|
||||
int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) {
|
||||
int i, num, *keys;
|
||||
UNUSED(cmd);
|
||||
|
||||
@@ -1511,20 +1677,60 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
|
||||
* argv[1] = key,
|
||||
* argv[5...n] = stored key if present
|
||||
*/
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
keys = getKeysPrepareResult(result, num);
|
||||
|
||||
/* Add all key positions to keys[] */
|
||||
keys[0] = 1;
|
||||
if(num > 1) {
|
||||
keys[1] = stored_key;
|
||||
}
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
result->numkeys = num;
|
||||
return num;
|
||||
}
|
||||
|
||||
/* LCS ... [KEYS <key1> <key2>] ... */
|
||||
int lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) {
|
||||
int i;
|
||||
int *keys = getKeysPrepareResult(result, 2);
|
||||
UNUSED(cmd);
|
||||
|
||||
/* We need to parse the options of the command in order to check for the
|
||||
* "KEYS" argument before the "STRINGS" argument. */
|
||||
for (i = 1; i < argc; i++) {
|
||||
char *arg = argv[i]->ptr;
|
||||
int moreargs = (argc-1) - i;
|
||||
|
||||
if (!strcasecmp(arg, "strings")) {
|
||||
break;
|
||||
} else if (!strcasecmp(arg, "keys") && moreargs >= 2) {
|
||||
keys[0] = i+1;
|
||||
keys[1] = i+2;
|
||||
result->numkeys = 2;
|
||||
return result->numkeys;
|
||||
}
|
||||
}
|
||||
result->numkeys = 0;
|
||||
return result->numkeys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from memory command.
|
||||
* MEMORY USAGE <key> */
|
||||
int memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) {
|
||||
UNUSED(cmd);
|
||||
|
||||
getKeysPrepareResult(result, 1);
|
||||
if (argc >= 3 && !strcasecmp(argv[1]->ptr,"usage")) {
|
||||
result->keys[0] = 2;
|
||||
result->numkeys = 1;
|
||||
return result->numkeys;
|
||||
}
|
||||
result->numkeys = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* XREAD [BLOCK <milliseconds>] [COUNT <count>] [GROUP <groupname> <ttl>]
|
||||
* STREAMS key_1 key_2 ... key_N ID_1 ID_2 ... ID_N */
|
||||
int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) {
|
||||
int i, num = 0, *keys;
|
||||
UNUSED(cmd);
|
||||
|
||||
@@ -1554,33 +1760,33 @@ int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
|
||||
/* Syntax error. */
|
||||
if (streams_pos == -1 || num == 0 || num % 2 != 0) {
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
result->numkeys = 0;
|
||||
return 0;
|
||||
}
|
||||
num /= 2; /* We have half the keys as there are arguments because
|
||||
there are also the IDs, one per key. */
|
||||
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
keys = getKeysPrepareResult(result, num);
|
||||
for (i = streams_pos+1; i < argc-num; i++) keys[i-streams_pos-1] = i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
result->numkeys = num;
|
||||
return num;
|
||||
}
|
||||
|
||||
/* Slot to Key API. This is used by Redis Cluster in order to obtain in
|
||||
* a fast way a key that belongs to a specified hash slot. This is useful
|
||||
* while rehashing the cluster and in other conditions when we need to
|
||||
* understand if we have keys for a given hash slot. */
|
||||
void slotToKeyUpdateKey(robj *key, int add) {
|
||||
unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));
|
||||
void slotToKeyUpdateKey(sds key, int add) {
|
||||
size_t keylen = sdslen(key);
|
||||
unsigned int hashslot = keyHashSlot(key,keylen);
|
||||
unsigned char buf[64];
|
||||
unsigned char *indexed = buf;
|
||||
size_t keylen = sdslen(key->ptr);
|
||||
|
||||
server.cluster->slots_keys_count[hashslot] += add ? 1 : -1;
|
||||
if (keylen+2 > 64) indexed = zmalloc(keylen+2);
|
||||
indexed[0] = (hashslot >> 8) & 0xff;
|
||||
indexed[1] = hashslot & 0xff;
|
||||
memcpy(indexed+2,key->ptr,keylen);
|
||||
memcpy(indexed+2,key,keylen);
|
||||
if (add) {
|
||||
raxInsert(server.cluster->slots_to_keys,indexed,keylen+2,NULL,NULL);
|
||||
} else {
|
||||
@@ -1589,19 +1795,33 @@ void slotToKeyUpdateKey(robj *key, int add) {
|
||||
if (indexed != buf) zfree(indexed);
|
||||
}
|
||||
|
||||
void slotToKeyAdd(robj *key) {
|
||||
void slotToKeyAdd(sds key) {
|
||||
slotToKeyUpdateKey(key,1);
|
||||
}
|
||||
|
||||
void slotToKeyDel(robj *key) {
|
||||
void slotToKeyDel(sds key) {
|
||||
slotToKeyUpdateKey(key,0);
|
||||
}
|
||||
|
||||
void slotToKeyFlush(void) {
|
||||
raxFree(server.cluster->slots_to_keys);
|
||||
/* Release the radix tree mapping Redis Cluster keys to slots. If 'async'
|
||||
* is true, we release it asynchronously. */
|
||||
void freeSlotsToKeysMap(rax *rt, int async) {
|
||||
if (async) {
|
||||
freeSlotsToKeysMapAsync(rt);
|
||||
} else {
|
||||
raxFree(rt);
|
||||
}
|
||||
}
|
||||
|
||||
/* Empty the slots-keys map of Redis CLuster by creating a new empty one and
|
||||
* freeing the old one. */
|
||||
void slotToKeyFlush(int async) {
|
||||
rax *old = server.cluster->slots_to_keys;
|
||||
|
||||
server.cluster->slots_to_keys = raxNew();
|
||||
memset(server.cluster->slots_keys_count,0,
|
||||
sizeof(server.cluster->slots_keys_count));
|
||||
freeSlotsToKeysMap(old, async);
|
||||
}
|
||||
|
||||
/* Pupulate the specified array of objects with keys in the specified slot.
|
||||
|
||||
+318
-73
@@ -311,6 +311,13 @@ void mallctl_int(client *c, robj **argv, int argc) {
|
||||
size_t sz = sizeof(old);
|
||||
while (sz > 0) {
|
||||
if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, argc > 1? &val: NULL, argc > 1?sz: 0))) {
|
||||
if (ret == EPERM && argc > 1) {
|
||||
/* if this option is write only, try just writing to it. */
|
||||
if (!(ret=je_mallctl(argv[0]->ptr, NULL, 0, &val, sz))) {
|
||||
addReply(c, shared.ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (ret==EINVAL) {
|
||||
/* size might be wrong, try a smaller one */
|
||||
sz /= 2;
|
||||
@@ -333,17 +340,30 @@ void mallctl_int(client *c, robj **argv, int argc) {
|
||||
}
|
||||
|
||||
void mallctl_string(client *c, robj **argv, int argc) {
|
||||
int ret;
|
||||
int rret, wret;
|
||||
char *old;
|
||||
size_t sz = sizeof(old);
|
||||
/* for strings, it seems we need to first get the old value, before overriding it. */
|
||||
if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, NULL, 0))) {
|
||||
addReplyErrorFormat(c,"%s", strerror(ret));
|
||||
return;
|
||||
if ((rret=je_mallctl(argv[0]->ptr, &old, &sz, NULL, 0))) {
|
||||
/* return error unless this option is write only. */
|
||||
if (!(rret == EPERM && argc > 1)) {
|
||||
addReplyErrorFormat(c,"%s", strerror(rret));
|
||||
return;
|
||||
}
|
||||
}
|
||||
addReplyBulkCString(c, old);
|
||||
if(argc > 1)
|
||||
je_mallctl(argv[0]->ptr, NULL, 0, &argv[1]->ptr, sizeof(char*));
|
||||
if(argc > 1) {
|
||||
char *val = argv[1]->ptr;
|
||||
char **valref = &val;
|
||||
if ((!strcmp(val,"VOID")))
|
||||
valref = NULL, sz = 0;
|
||||
wret = je_mallctl(argv[0]->ptr, NULL, 0, valref, sz);
|
||||
}
|
||||
if (!rret)
|
||||
addReplyBulkCString(c, old);
|
||||
else if (wret)
|
||||
addReplyErrorFormat(c,"%s", strerror(wret));
|
||||
else
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -355,16 +375,19 @@ void debugCommand(client *c) {
|
||||
"CRASH-AND-RECOVER <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
|
||||
"DIGEST -- Output a hex signature representing the current DB content.",
|
||||
"DIGEST-VALUE <key-1> ... <key-N>-- Output a hex signature of the values of all the specified keys.",
|
||||
"DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false]",
|
||||
"ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
|
||||
"LOG <message> -- write message to the server log.",
|
||||
"LEAK <string> -- Create a memory leak of the input string.",
|
||||
"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.",
|
||||
"HTSTATS-KEY <key> -- Like htstats but for the hash table stored as key's value.",
|
||||
"LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.",
|
||||
"LUA-ALWAYS-REPLICATE-COMMANDS <0|1> -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.",
|
||||
"OBJECT <key> -- Show low level info about key and associated value.",
|
||||
"OOM -- Crash the server simulating an out-of-memory error.",
|
||||
"PANIC -- Crash the server simulating a panic.",
|
||||
"POPULATE <count> [prefix] [size] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.",
|
||||
"RELOAD -- Save the RDB on disk and reload it back in memory.",
|
||||
"RELOAD [MERGE] [NOFLUSH] [NOSAVE] -- Save the RDB on disk and reload it back in memory. By default it will save the RDB file and load it back. With the NOFLUSH option the current database is not removed before loading the new one, but conflicts in keys will kill the server with an exception. When MERGE is used, conflicting keys will be loaded (the key in the loaded RDB file will win). When NOSAVE is used, the server will not save the current dataset in the RDB file before loading. Use DEBUG RELOAD NOSAVE when you want just to load the RDB file you placed in the Redis working directory in order to replace the current dataset in memory. Use DEBUG RELOAD NOSAVE NOFLUSH MERGE when you want to add what is in the current RDB file placed in the Redis current directory, with the current memory content. Use DEBUG RELOAD when you want to verify Redis is able to persist the current dataset in the RDB file, flush the memory content, and load it back.",
|
||||
"RESTART -- Graceful restart: save config, db, restart.",
|
||||
"SDSLEN <key> -- Show low level SDS string info representing key and value.",
|
||||
"SEGFAULT -- Crash the server with sigsegv.",
|
||||
@@ -374,6 +397,7 @@ void debugCommand(client *c) {
|
||||
"STRUCTSIZE -- Return the size of different Redis core C structures.",
|
||||
"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
|
||||
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
|
||||
"CONFIG-REWRITE-FORCE-ALL -- Like CONFIG REWRITE but writes all configuration options, including keywords not listed in original configuration file or default values.",
|
||||
#ifdef USE_JEMALLOC
|
||||
"MALLCTL <key> [<val>] -- Get or set a malloc tunning integer.",
|
||||
"MALLCTL-STR <key> [<val>] -- Get or set a malloc tunning string.",
|
||||
@@ -408,16 +432,48 @@ NULL
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"log") && c->argc == 3) {
|
||||
serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"leak") && c->argc == 3) {
|
||||
sdsdup(c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
int flush = 1, save = 1;
|
||||
int flags = RDBFLAGS_NONE;
|
||||
|
||||
/* Parse the additional options that modify the RELOAD
|
||||
* behavior. */
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
char *opt = c->argv[j]->ptr;
|
||||
if (!strcasecmp(opt,"MERGE")) {
|
||||
flags |= RDBFLAGS_ALLOW_DUP;
|
||||
} else if (!strcasecmp(opt,"NOFLUSH")) {
|
||||
flush = 0;
|
||||
} else if (!strcasecmp(opt,"NOSAVE")) {
|
||||
save = 0;
|
||||
} else {
|
||||
addReplyError(c,"DEBUG RELOAD only supports the "
|
||||
"MERGE, NOFLUSH and NOSAVE options.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
|
||||
/* The default behavior is to save the RDB file before loading
|
||||
* it back. */
|
||||
if (save) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* The default behavior is to remove the current dataset from
|
||||
* memory before loading the RDB file, however when MERGE is
|
||||
* used together with NOFLUSH, we are able to merge two datasets. */
|
||||
if (flush) emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
|
||||
protectClient(c);
|
||||
int ret = rdbLoad(server.rdb_filename,NULL,RDBFLAGS_NONE);
|
||||
int ret = rdbLoad(server.rdb_filename,NULL,flags);
|
||||
unprotectClient(c);
|
||||
if (ret != C_OK) {
|
||||
addReplyError(c,"Error trying to load the RDB dump");
|
||||
@@ -488,7 +544,7 @@ NULL
|
||||
"encoding:%s serializedlength:%zu "
|
||||
"lru:%d lru_seconds_idle:%llu%s",
|
||||
(void*)val, val->refcount,
|
||||
strenc, rdbSavedObjectLen(val),
|
||||
strenc, rdbSavedObjectLen(val, c->argv[2]),
|
||||
val->lru, estimateObjectIdleTime(val)/1000, extra);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) {
|
||||
dictEntry *de;
|
||||
@@ -536,14 +592,13 @@ NULL
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != C_OK)
|
||||
return;
|
||||
dictExpand(c->db->dict,keys);
|
||||
long valsize = 0;
|
||||
if ( c->argc == 5 && getLongFromObjectOrReply(c, c->argv[4], &valsize, NULL) != C_OK )
|
||||
return;
|
||||
for (j = 0; j < keys; j++) {
|
||||
long valsize = 0;
|
||||
snprintf(buf,sizeof(buf),"%s:%lu",
|
||||
(c->argc == 3) ? "key" : (char*)c->argv[3]->ptr, j);
|
||||
key = createStringObject(buf,strlen(buf));
|
||||
if (c->argc == 5)
|
||||
if (getLongFromObjectOrReply(c, c->argv[4], &valsize, NULL) != C_OK)
|
||||
return;
|
||||
if (lookupKeyWrite(c->db,key) != NULL) {
|
||||
decrRefCount(key);
|
||||
continue;
|
||||
@@ -557,7 +612,7 @@ NULL
|
||||
memcpy(val->ptr, buf, valsize<=buflen? valsize: buflen);
|
||||
}
|
||||
dbAdd(c->db,key,val);
|
||||
signalModifiedKey(c->db,key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
decrRefCount(key);
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
@@ -576,7 +631,11 @@ NULL
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
unsigned char digest[20];
|
||||
memset(digest,0,20); /* Start with a clean result */
|
||||
robj *o = lookupKeyReadWithFlags(c->db,c->argv[j],LOOKUP_NOTOUCH);
|
||||
|
||||
/* We don't use lookupKey because a debug command should
|
||||
* work on logically expired keys */
|
||||
dictEntry *de;
|
||||
robj *o = ((de = dictFind(c->db->dict,c->argv[j]->ptr)) == NULL) ? NULL : dictGetVal(de);
|
||||
if (o) xorObjectDigest(c->db,c->argv[j],digest,o);
|
||||
|
||||
sds d = sdsempty();
|
||||
@@ -586,7 +645,7 @@ NULL
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"protocol") && c->argc == 3) {
|
||||
/* DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|
|
||||
* attrib|push|verbatim|true|false|state|err|bloberr] */
|
||||
* attrib|push|verbatim|true|false] */
|
||||
char *name = c->argv[2]->ptr;
|
||||
if (!strcasecmp(name,"string")) {
|
||||
addReplyBulkCString(c,"Hello World");
|
||||
@@ -595,7 +654,7 @@ NULL
|
||||
} else if (!strcasecmp(name,"double")) {
|
||||
addReplyDouble(c,3.14159265359);
|
||||
} else if (!strcasecmp(name,"bignum")) {
|
||||
addReplyProto(c,"(1234567999999999999999999999999999999\r\n",40);
|
||||
addReplyBigNum(c,"1234567999999999999999999999999999999",37);
|
||||
} else if (!strcasecmp(name,"null")) {
|
||||
addReplyNull(c);
|
||||
} else if (!strcasecmp(name,"array")) {
|
||||
@@ -611,18 +670,27 @@ NULL
|
||||
addReplyBool(c, j == 1);
|
||||
}
|
||||
} else if (!strcasecmp(name,"attrib")) {
|
||||
addReplyAttributeLen(c,1);
|
||||
addReplyBulkCString(c,"key-popularity");
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,"key:123");
|
||||
addReplyLongLong(c,90);
|
||||
if (c->resp >= 3) {
|
||||
addReplyAttributeLen(c,1);
|
||||
addReplyBulkCString(c,"key-popularity");
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,"key:123");
|
||||
addReplyLongLong(c,90);
|
||||
}
|
||||
/* Attributes are not real replies, so a well formed reply should
|
||||
* also have a normal reply type after the attribute. */
|
||||
addReplyBulkCString(c,"Some real reply following the attribute");
|
||||
} else if (!strcasecmp(name,"push")) {
|
||||
if (c->resp < 3) {
|
||||
addReplyError(c,"RESP2 is not supported by this command");
|
||||
return;
|
||||
}
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
addReplyPushLen(c,2);
|
||||
addReplyBulkCString(c,"server-cpu-usage");
|
||||
addReplyLongLong(c,42);
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
/* Push replies are not synchronous replies, so we emit also a
|
||||
* normal reply in order for blocking clients just discarding the
|
||||
* push reply, to actually consume the reply and continue. */
|
||||
@@ -634,7 +702,7 @@ NULL
|
||||
} else if (!strcasecmp(name,"verbatim")) {
|
||||
addReplyVerbatim(c,"This is a verbatim\nstring",25,"txt");
|
||||
} else {
|
||||
addReplyError(c,"Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false|state|err|bloberr");
|
||||
addReplyError(c,"Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false");
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
|
||||
double dtime = strtod(c->argv[2]->ptr,NULL);
|
||||
@@ -683,9 +751,12 @@ NULL
|
||||
sds stats = sdsempty();
|
||||
char buf[4096];
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK)
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK) {
|
||||
sdsfree(stats);
|
||||
return;
|
||||
}
|
||||
if (dbid < 0 || dbid >= server.dbnum) {
|
||||
sdsfree(stats);
|
||||
addReplyError(c,"Out of range database");
|
||||
return;
|
||||
}
|
||||
@@ -737,6 +808,12 @@ NULL
|
||||
{
|
||||
stringmatchlen_fuzz_test();
|
||||
addReplyStatus(c,"Apparently Redis did not crash: test passed");
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"config-rewrite-force-all") && c->argc == 2)
|
||||
{
|
||||
if (rewriteConfig(server.configfile, 1) == -1)
|
||||
addReplyError(c, "CONFIG-REWRITE-FORCE-ALL failed");
|
||||
else
|
||||
addReply(c, shared.ok);
|
||||
#ifdef USE_JEMALLOC
|
||||
} else if(!strcasecmp(c->argv[1]->ptr,"mallctl") && c->argc >= 3) {
|
||||
mallctl_int(c, c->argv+2, c->argc-2);
|
||||
@@ -812,6 +889,8 @@ void serverLogObjectDebugInfo(const robj *o) {
|
||||
serverLog(LL_WARNING,"Sorted set size: %d", (int) zsetLength(o));
|
||||
if (o->encoding == OBJ_ENCODING_SKIPLIST)
|
||||
serverLog(LL_WARNING,"Skiplist level: %d", (int) ((const zset*)o->ptr)->zsl->level);
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
serverLog(LL_WARNING,"Stream size: %d", (int) streamLength(o));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -868,12 +947,15 @@ static void *getMcontextEip(ucontext_t *uc) {
|
||||
/* OSX >= 10.6 */
|
||||
#if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
|
||||
return (void*) uc->uc_mcontext->__ss.__rip;
|
||||
#else
|
||||
#elif defined(__i386__)
|
||||
return (void*) uc->uc_mcontext->__ss.__eip;
|
||||
#else
|
||||
/* OSX ARM64 */
|
||||
return (void*) arm_thread_state64_get_pc(uc->uc_mcontext->__ss);
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
/* Linux */
|
||||
#if defined(__i386__) || defined(__ILP32__)
|
||||
#if defined(__i386__) || ((defined(__X86_64__) || defined(__x86_64__)) && defined(__ILP32__))
|
||||
return (void*) uc->uc_mcontext.gregs[14]; /* Linux 32 */
|
||||
#elif defined(__X86_64__) || defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext.gregs[16]; /* Linux 64 */
|
||||
@@ -898,6 +980,12 @@ static void *getMcontextEip(ucontext_t *uc) {
|
||||
#elif defined(__x86_64__)
|
||||
return (void*) uc->sc_rip;
|
||||
#endif
|
||||
#elif defined(__NetBSD__)
|
||||
#if defined(__i386__)
|
||||
return (void*) uc->uc_mcontext.__gregs[_REG_EIP];
|
||||
#elif defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext.__gregs[_REG_RIP];
|
||||
#endif
|
||||
#elif defined(__DragonFly__)
|
||||
return (void*) uc->uc_mcontext.mc_rip;
|
||||
#else
|
||||
@@ -955,7 +1043,7 @@ void logRegisters(ucontext_t *uc) {
|
||||
(unsigned long) uc->uc_mcontext->__ss.__gs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext->__ss.__rsp);
|
||||
#else
|
||||
#elif defined(__i386__)
|
||||
/* OSX x86 */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
@@ -981,11 +1069,60 @@ void logRegisters(ucontext_t *uc) {
|
||||
(unsigned long) uc->uc_mcontext->__ss.__gs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext->__ss.__esp);
|
||||
#else
|
||||
/* OSX ARM64 */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"x0:%016lx x1:%016lx x2:%016lx x3:%016lx\n"
|
||||
"x4:%016lx x5:%016lx x6:%016lx x7:%016lx\n"
|
||||
"x8:%016lx x9:%016lx x10:%016lx x11:%016lx\n"
|
||||
"x12:%016lx x13:%016lx x14:%016lx x15:%016lx\n"
|
||||
"x16:%016lx x17:%016lx x18:%016lx x19:%016lx\n"
|
||||
"x20:%016lx x21:%016lx x22:%016lx x23:%016lx\n"
|
||||
"x24:%016lx x25:%016lx x26:%016lx x27:%016lx\n"
|
||||
"x28:%016lx fp:%016lx lr:%016lx\n"
|
||||
"sp:%016lx pc:%016lx cpsr:%08lx\n",
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[0],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[1],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[2],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[3],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[4],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[5],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[6],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[7],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[8],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[9],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[10],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[11],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[12],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[13],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[14],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[15],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[16],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[17],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[18],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[19],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[20],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[21],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[22],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[23],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[24],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[25],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[26],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[27],
|
||||
(unsigned long) uc->uc_mcontext->__ss.__x[28],
|
||||
(unsigned long) arm_thread_state64_get_fp(uc->uc_mcontext->__ss),
|
||||
(unsigned long) arm_thread_state64_get_lr(uc->uc_mcontext->__ss),
|
||||
(unsigned long) arm_thread_state64_get_sp(uc->uc_mcontext->__ss),
|
||||
(unsigned long) arm_thread_state64_get_pc(uc->uc_mcontext->__ss),
|
||||
(unsigned long) uc->uc_mcontext->__ss.__cpsr
|
||||
);
|
||||
logStackContent((void**) arm_thread_state64_get_sp(uc->uc_mcontext->__ss));
|
||||
#endif
|
||||
/* Linux */
|
||||
#elif defined(__linux__)
|
||||
/* Linux x86 */
|
||||
#if defined(__i386__) || defined(__ILP32__)
|
||||
#if defined(__i386__) || ((defined(__X86_64__) || defined(__x86_64__)) && defined(__ILP32__))
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
|
||||
@@ -1040,6 +1177,61 @@ void logRegisters(ucontext_t *uc) {
|
||||
(unsigned long) uc->uc_mcontext.gregs[18]
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.gregs[15]);
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
|
||||
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
|
||||
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
|
||||
"X30:%016lx\n"
|
||||
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
|
||||
(unsigned long) uc->uc_mcontext.regs[18],
|
||||
(unsigned long) uc->uc_mcontext.regs[19],
|
||||
(unsigned long) uc->uc_mcontext.regs[20],
|
||||
(unsigned long) uc->uc_mcontext.regs[21],
|
||||
(unsigned long) uc->uc_mcontext.regs[22],
|
||||
(unsigned long) uc->uc_mcontext.regs[23],
|
||||
(unsigned long) uc->uc_mcontext.regs[24],
|
||||
(unsigned long) uc->uc_mcontext.regs[25],
|
||||
(unsigned long) uc->uc_mcontext.regs[26],
|
||||
(unsigned long) uc->uc_mcontext.regs[27],
|
||||
(unsigned long) uc->uc_mcontext.regs[28],
|
||||
(unsigned long) uc->uc_mcontext.regs[29],
|
||||
(unsigned long) uc->uc_mcontext.regs[30],
|
||||
(unsigned long) uc->uc_mcontext.pc,
|
||||
(unsigned long) uc->uc_mcontext.sp,
|
||||
(unsigned long) uc->uc_mcontext.pstate,
|
||||
(unsigned long) uc->uc_mcontext.fault_address
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.sp);
|
||||
#elif defined(__arm__) /* Linux ARM */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"R10:%016lx R9 :%016lx\nR8 :%016lx R7 :%016lx\n"
|
||||
"R6 :%016lx R5 :%016lx\nR4 :%016lx R3 :%016lx\n"
|
||||
"R2 :%016lx R1 :%016lx\nR0 :%016lx EC :%016lx\n"
|
||||
"fp: %016lx ip:%016lx\n"
|
||||
"pc:%016lx sp:%016lx\ncpsr:%016lx fault_address:%016lx\n",
|
||||
(unsigned long) uc->uc_mcontext.arm_r10,
|
||||
(unsigned long) uc->uc_mcontext.arm_r9,
|
||||
(unsigned long) uc->uc_mcontext.arm_r8,
|
||||
(unsigned long) uc->uc_mcontext.arm_r7,
|
||||
(unsigned long) uc->uc_mcontext.arm_r6,
|
||||
(unsigned long) uc->uc_mcontext.arm_r5,
|
||||
(unsigned long) uc->uc_mcontext.arm_r4,
|
||||
(unsigned long) uc->uc_mcontext.arm_r3,
|
||||
(unsigned long) uc->uc_mcontext.arm_r2,
|
||||
(unsigned long) uc->uc_mcontext.arm_r1,
|
||||
(unsigned long) uc->uc_mcontext.arm_r0,
|
||||
(unsigned long) uc->uc_mcontext.error_code,
|
||||
(unsigned long) uc->uc_mcontext.arm_fp,
|
||||
(unsigned long) uc->uc_mcontext.arm_ip,
|
||||
(unsigned long) uc->uc_mcontext.arm_pc,
|
||||
(unsigned long) uc->uc_mcontext.arm_sp,
|
||||
(unsigned long) uc->uc_mcontext.arm_cpsr,
|
||||
(unsigned long) uc->uc_mcontext.fault_address
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.arm_sp);
|
||||
#endif
|
||||
#elif defined(__FreeBSD__)
|
||||
#if defined(__x86_64__)
|
||||
@@ -1151,6 +1343,59 @@ void logRegisters(ucontext_t *uc) {
|
||||
);
|
||||
logStackContent((void**)uc->sc_esp);
|
||||
#endif
|
||||
#elif defined(__NetBSD__)
|
||||
#if defined(__x86_64__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
|
||||
"RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
|
||||
"R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
|
||||
"R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
|
||||
"RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_RAX],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_RBX],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_RCX],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_RDX],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_RDI],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_RSI],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_RBP],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_RSP],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_R8],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_R9],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_R10],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_R11],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_R12],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_R13],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_R14],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_R15],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_RIP],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_RFLAGS],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_CS]
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.__gregs[_REG_RSP]);
|
||||
#elif defined(__i386__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
|
||||
"EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
|
||||
"SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
|
||||
"DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_EAX],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_EBX],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_EDX],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_EDI],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_ESI],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_EBP],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_ESP],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_SS],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_EFLAGS],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_EIP],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_CS],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_ES],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_FS],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[_REG_GS]
|
||||
);
|
||||
#endif
|
||||
#elif defined(__DragonFly__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
@@ -1180,33 +1425,6 @@ void logRegisters(ucontext_t *uc) {
|
||||
(unsigned long) uc->uc_mcontext.mc_cs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.mc_rsp);
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
|
||||
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
|
||||
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
|
||||
"X30:%016lx\n"
|
||||
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
|
||||
(unsigned long) uc->uc_mcontext.regs[18],
|
||||
(unsigned long) uc->uc_mcontext.regs[19],
|
||||
(unsigned long) uc->uc_mcontext.regs[20],
|
||||
(unsigned long) uc->uc_mcontext.regs[21],
|
||||
(unsigned long) uc->uc_mcontext.regs[22],
|
||||
(unsigned long) uc->uc_mcontext.regs[23],
|
||||
(unsigned long) uc->uc_mcontext.regs[24],
|
||||
(unsigned long) uc->uc_mcontext.regs[25],
|
||||
(unsigned long) uc->uc_mcontext.regs[26],
|
||||
(unsigned long) uc->uc_mcontext.regs[27],
|
||||
(unsigned long) uc->uc_mcontext.regs[28],
|
||||
(unsigned long) uc->uc_mcontext.regs[29],
|
||||
(unsigned long) uc->uc_mcontext.regs[30],
|
||||
(unsigned long) uc->uc_mcontext.pc,
|
||||
(unsigned long) uc->uc_mcontext.sp,
|
||||
(unsigned long) uc->uc_mcontext.pstate,
|
||||
(unsigned long) uc->uc_mcontext.fault_address
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.sp);
|
||||
#else
|
||||
serverLog(LL_WARNING,
|
||||
" Dumping of registers not supported for this OS/arch");
|
||||
@@ -1284,7 +1502,7 @@ void logCurrentClient(void) {
|
||||
}
|
||||
/* Check if the first argument, usually a key, is found inside the
|
||||
* selected DB, and if so print info about the associated object. */
|
||||
if (cc->argc >= 1) {
|
||||
if (cc->argc > 1) {
|
||||
robj *val, *key;
|
||||
dictEntry *de;
|
||||
|
||||
@@ -1303,7 +1521,7 @@ void logCurrentClient(void) {
|
||||
|
||||
#define MEMTEST_MAX_REGIONS 128
|
||||
|
||||
/* A non destructive memory test executed during segfauls. */
|
||||
/* A non destructive memory test executed during segfault. */
|
||||
int memtest_test_linux_anonymous_maps(void) {
|
||||
FILE *fp;
|
||||
char line[1024];
|
||||
@@ -1364,7 +1582,28 @@ int memtest_test_linux_anonymous_maps(void) {
|
||||
closeDirectLogFiledes(fd);
|
||||
return errors;
|
||||
}
|
||||
#endif
|
||||
#endif /* HAVE_PROC_MAPS */
|
||||
|
||||
static void killMainThread(void) {
|
||||
int err;
|
||||
if (pthread_self() != server.main_thread_id && pthread_cancel(server.main_thread_id) == 0) {
|
||||
if ((err = pthread_join(server.main_thread_id,NULL)) != 0) {
|
||||
serverLog(LL_WARNING, "main thread can not be joined: %s", strerror(err));
|
||||
} else {
|
||||
serverLog(LL_WARNING, "main thread terminated");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Kill the running threads (other than current) in an unclean way. This function
|
||||
* should be used only when it's critical to stop the threads for some reason.
|
||||
* Currently Redis does this only on crash (for instance on SIGSEGV) in order
|
||||
* to perform a fast memory check without other threads messing with memory. */
|
||||
void killThreads(void) {
|
||||
killMainThread();
|
||||
bioKillThreads();
|
||||
killIOThreads();
|
||||
}
|
||||
|
||||
/* Scans the (assumed) x86 code starting at addr, for a max of `len`
|
||||
* bytes, searching for E8 (callq) opcodes, and dumping the symbols
|
||||
@@ -1401,7 +1640,7 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
|
||||
bugReportStart();
|
||||
serverLog(LL_WARNING,
|
||||
"Redis %s crashed by signal: %d", REDIS_VERSION, sig);
|
||||
"Redis %s crashed by signal: %d, si_code: %d", REDIS_VERSION, sig, info->si_code);
|
||||
if (eip != NULL) {
|
||||
serverLog(LL_WARNING,
|
||||
"Crashed running the instruction at: %p", eip);
|
||||
@@ -1410,6 +1649,9 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
serverLog(LL_WARNING,
|
||||
"Accessing address: %p", (void*)info->si_addr);
|
||||
}
|
||||
if (info->si_code == SI_USER && info->si_pid != -1) {
|
||||
serverLog(LL_WARNING, "Killed by PID: %ld, UID: %d", (long) info->si_pid, info->si_uid);
|
||||
}
|
||||
serverLog(LL_WARNING,
|
||||
"Failed assertion: %s (%s:%d)", server.assert_failed,
|
||||
server.assert_file, server.assert_line);
|
||||
@@ -1443,7 +1685,7 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
#if defined(HAVE_PROC_MAPS)
|
||||
/* Test memory */
|
||||
serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n");
|
||||
bioKillThreads();
|
||||
killThreads();
|
||||
if (memtest_test_linux_anonymous_maps()) {
|
||||
serverLogRaw(LL_WARNING|LL_RAW,
|
||||
"!!! MEMORY ERROR DETECTED! Check your memory ASAP !!!\n");
|
||||
@@ -1471,13 +1713,14 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
/* Find the address of the next page, which is our "safety"
|
||||
* limit when dumping. Then try to dump just 128 bytes more
|
||||
* than EIP if there is room, or stop sooner. */
|
||||
void *base = (void *)info.dli_saddr;
|
||||
unsigned long next = ((unsigned long)eip + sz) & ~(sz-1);
|
||||
unsigned long end = (unsigned long)eip + 128;
|
||||
if (end > next) end = next;
|
||||
len = end - (unsigned long)info.dli_saddr;
|
||||
len = end - (unsigned long)base;
|
||||
serverLogHexDump(LL_WARNING, "dump of function",
|
||||
info.dli_saddr ,len);
|
||||
dumpX86Calls(info.dli_saddr,len);
|
||||
base ,len);
|
||||
dumpX86Calls(base,len);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1485,12 +1728,14 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
serverLogRaw(LL_WARNING|LL_RAW,
|
||||
"\n=== REDIS BUG REPORT END. Make sure to include from START to END. ===\n\n"
|
||||
" Please report the crash by opening an issue on github:\n\n"
|
||||
" http://github.com/antirez/redis/issues\n\n"
|
||||
" http://github.com/redis/redis/issues\n\n"
|
||||
" If a Redis module was involved, please open in the module's repo instead.\n\n"
|
||||
" Suspect RAM error? Use redis-server --test-memory to verify it.\n\n"
|
||||
" Some other issues could be detected by redis-server --check-system\n"
|
||||
);
|
||||
|
||||
/* free(messages); Don't call free() with possibly corrupted memory. */
|
||||
if (server.daemonize && server.supervised == 0) unlink(server.pidfile);
|
||||
if (server.daemonize && server.supervised == 0 && server.pidfile) unlink(server.pidfile);
|
||||
|
||||
/* Make sure we exit with the right signal at the end. So for instance
|
||||
* the core will be dumped if enabled. */
|
||||
@@ -1572,7 +1817,7 @@ void enableWatchdog(int period) {
|
||||
/* Watchdog was actually disabled, so we have to setup the signal
|
||||
* handler. */
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
||||
act.sa_flags = SA_SIGINFO;
|
||||
act.sa_sigaction = watchdogSignalHandler;
|
||||
sigaction(SIGALRM, &act, NULL);
|
||||
}
|
||||
|
||||
+87
-55
@@ -5,8 +5,8 @@
|
||||
* We do that by scanning the keyspace and for each pointer we have, we can try to
|
||||
* ask the allocator if moving it to a new address will help reduce fragmentation.
|
||||
*
|
||||
* Copyright (c) 2017, Oran Agra
|
||||
* Copyright (c) 2017, Redis Labs, Inc
|
||||
* Copyright (c) 2020, Oran Agra
|
||||
* Copyright (c) 2020, Redis Labs, Inc
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -43,30 +43,23 @@
|
||||
|
||||
/* this method was added to jemalloc in order to help us understand which
|
||||
* pointers are worthwhile moving and which aren't */
|
||||
int je_get_defrag_hint(void* ptr, int *bin_util, int *run_util);
|
||||
int je_get_defrag_hint(void* ptr);
|
||||
|
||||
/* forward declarations*/
|
||||
void defragDictBucketCallback(void *privdata, dictEntry **bucketref);
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged);
|
||||
dictEntry* replaceSatelliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged);
|
||||
|
||||
/* Defrag helper for generic allocations.
|
||||
*
|
||||
* returns NULL in case the allocatoin wasn't moved.
|
||||
* returns NULL in case the allocation wasn't moved.
|
||||
* when it returns a non-null value, the old pointer was already released
|
||||
* and should NOT be accessed. */
|
||||
void* activeDefragAlloc(void *ptr) {
|
||||
int bin_util, run_util;
|
||||
size_t size;
|
||||
void *newptr;
|
||||
if(!je_get_defrag_hint(ptr, &bin_util, &run_util)) {
|
||||
server.stat_active_defrag_misses++;
|
||||
return NULL;
|
||||
}
|
||||
/* if this run is more utilized than the average utilization in this bin
|
||||
* (or it is full), skip it. This will eventually move all the allocations
|
||||
* from relatively empty runs into relatively full runs. */
|
||||
if (run_util > bin_util || run_util == 1<<16) {
|
||||
if(!je_get_defrag_hint(ptr)) {
|
||||
server.stat_active_defrag_misses++;
|
||||
size = zmalloc_size(ptr);
|
||||
return NULL;
|
||||
}
|
||||
/* move this allocation to a new allocation.
|
||||
@@ -81,7 +74,7 @@ void* activeDefragAlloc(void *ptr) {
|
||||
|
||||
/*Defrag helper for sds strings
|
||||
*
|
||||
* returns NULL in case the allocatoin wasn't moved.
|
||||
* returns NULL in case the allocation wasn't moved.
|
||||
* when it returns a non-null value, the old pointer was already released
|
||||
* and should NOT be accessed. */
|
||||
sds activeDefragSds(sds sdsptr) {
|
||||
@@ -97,7 +90,7 @@ sds activeDefragSds(sds sdsptr) {
|
||||
|
||||
/* Defrag helper for robj and/or string objects
|
||||
*
|
||||
* returns NULL in case the allocatoin wasn't moved.
|
||||
* returns NULL in case the allocation wasn't moved.
|
||||
* when it returns a non-null value, the old pointer was already released
|
||||
* and should NOT be accessed. */
|
||||
robj *activeDefragStringOb(robj* ob, long *defragged) {
|
||||
@@ -137,11 +130,11 @@ robj *activeDefragStringOb(robj* ob, long *defragged) {
|
||||
}
|
||||
|
||||
/* Defrag helper for dictEntries to be used during dict iteration (called on
|
||||
* each step). Teturns a stat of how many pointers were moved. */
|
||||
* each step). Returns a stat of how many pointers were moved. */
|
||||
long dictIterDefragEntry(dictIterator *iter) {
|
||||
/* This function is a little bit dirty since it messes with the internals
|
||||
* of the dict and it's iterator, but the benefit is that it is very easy
|
||||
* to use, and require no other chagnes in the dict. */
|
||||
* to use, and require no other changes in the dict. */
|
||||
long defragged = 0;
|
||||
dictht *ht;
|
||||
/* Handle the next entry (if there is one), and update the pointer in the
|
||||
@@ -245,7 +238,7 @@ double *zslDefrag(zskiplist *zsl, double score, sds oldele, sds newele) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Defrag helpler for sorted set.
|
||||
/* Defrag helper for sorted set.
|
||||
* Defrag a single dict entry key name, and corresponding skiplist struct */
|
||||
long activeDefragZsetEntry(zset *zs, dictEntry *de) {
|
||||
sds newsds;
|
||||
@@ -355,8 +348,8 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
||||
sdsele = ln->value;
|
||||
if ((newsds = activeDefragSds(sdsele))) {
|
||||
/* When defragging an sds value, we need to update the dict key */
|
||||
uint64_t hash = dictGetHash(d, sdsele);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
|
||||
uint64_t hash = dictGetHash(d, newsds);
|
||||
replaceSatelliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
|
||||
ln->value = newsds;
|
||||
defragged++;
|
||||
}
|
||||
@@ -392,7 +385,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
||||
* moved. Return value is the the dictEntry if found, or NULL if not found.
|
||||
* NOTE: this is very ugly code, but it let's us avoid the complication of
|
||||
* doing a scan on another dict. */
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged) {
|
||||
dictEntry* replaceSatelliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged) {
|
||||
dictEntry **deref = dictFindEntryRefByPtrAndHash(d, oldkey, hash);
|
||||
if (deref) {
|
||||
dictEntry *de = *deref;
|
||||
@@ -408,44 +401,87 @@ dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sd
|
||||
return NULL;
|
||||
}
|
||||
|
||||
long activeDefragQuickListNodes(quicklist *ql) {
|
||||
quicklistNode *node = ql->head, *newnode;
|
||||
long activeDefragQuickListNode(quicklist *ql, quicklistNode **node_ref) {
|
||||
quicklistNode *newnode, *node = *node_ref;
|
||||
long defragged = 0;
|
||||
unsigned char *newzl;
|
||||
if ((newnode = activeDefragAlloc(node))) {
|
||||
if (newnode->prev)
|
||||
newnode->prev->next = newnode;
|
||||
else
|
||||
ql->head = newnode;
|
||||
if (newnode->next)
|
||||
newnode->next->prev = newnode;
|
||||
else
|
||||
ql->tail = newnode;
|
||||
*node_ref = node = newnode;
|
||||
defragged++;
|
||||
}
|
||||
if ((newzl = activeDefragAlloc(node->zl)))
|
||||
defragged++, node->zl = newzl;
|
||||
return defragged;
|
||||
}
|
||||
|
||||
long activeDefragQuickListNodes(quicklist *ql) {
|
||||
quicklistNode *node = ql->head;
|
||||
long defragged = 0;
|
||||
while (node) {
|
||||
if ((newnode = activeDefragAlloc(node))) {
|
||||
if (newnode->prev)
|
||||
newnode->prev->next = newnode;
|
||||
else
|
||||
ql->head = newnode;
|
||||
if (newnode->next)
|
||||
newnode->next->prev = newnode;
|
||||
else
|
||||
ql->tail = newnode;
|
||||
node = newnode;
|
||||
defragged++;
|
||||
}
|
||||
if ((newzl = activeDefragAlloc(node->zl)))
|
||||
defragged++, node->zl = newzl;
|
||||
defragged += activeDefragQuickListNode(ql, &node);
|
||||
node = node->next;
|
||||
}
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* when the value has lots of elements, we want to handle it later and not as
|
||||
* oart of the main dictionary scan. this is needed in order to prevent latency
|
||||
* part of the main dictionary scan. this is needed in order to prevent latency
|
||||
* spikes when handling large items */
|
||||
void defragLater(redisDb *db, dictEntry *kde) {
|
||||
sds key = sdsdup(dictGetKey(kde));
|
||||
listAddNodeTail(db->defrag_later, key);
|
||||
}
|
||||
|
||||
long scanLaterList(robj *ob) {
|
||||
/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
|
||||
long scanLaterList(robj *ob, unsigned long *cursor, long long endtime, long long *defragged) {
|
||||
quicklist *ql = ob->ptr;
|
||||
quicklistNode *node;
|
||||
long iterations = 0;
|
||||
int bookmark_failed = 0;
|
||||
if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST)
|
||||
return 0;
|
||||
server.stat_active_defrag_scanned+=ql->len;
|
||||
return activeDefragQuickListNodes(ql);
|
||||
|
||||
if (*cursor == 0) {
|
||||
/* if cursor is 0, we start new iteration */
|
||||
node = ql->head;
|
||||
} else {
|
||||
node = quicklistBookmarkFind(ql, "_AD");
|
||||
if (!node) {
|
||||
/* if the bookmark was deleted, it means we reached the end. */
|
||||
*cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
(*cursor)++;
|
||||
while (node) {
|
||||
(*defragged) += activeDefragQuickListNode(ql, &node);
|
||||
server.stat_active_defrag_scanned++;
|
||||
if (++iterations > 128 && !bookmark_failed) {
|
||||
if (ustime() > endtime) {
|
||||
if (!quicklistBookmarkCreate(&ql, "_AD", node)) {
|
||||
bookmark_failed = 1;
|
||||
} else {
|
||||
ob->ptr = ql; /* bookmark creation may have re-allocated the quicklist */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
iterations = 0;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
quicklistBookmarkDelete(ql, "_AD");
|
||||
*cursor = 0;
|
||||
return bookmark_failed? 1: 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
@@ -626,6 +662,7 @@ int scanLaterStraemListpacks(robj *ob, unsigned long *cursor, long long endtime,
|
||||
/* if cursor is non-zero, we seek to the static 'last' */
|
||||
if (!raxSeek(&ri,">", last, sizeof(last))) {
|
||||
*cursor = 0;
|
||||
raxStop(&ri);
|
||||
return 0;
|
||||
}
|
||||
/* assign the iterator node callback after the seek, so that the
|
||||
@@ -638,7 +675,8 @@ int scanLaterStraemListpacks(robj *ob, unsigned long *cursor, long long endtime,
|
||||
void *newdata = activeDefragAlloc(ri.data);
|
||||
if (newdata)
|
||||
raxSetData(ri.node, ri.data=newdata), (*defragged)++;
|
||||
if (++iterations > 16) {
|
||||
server.stat_active_defrag_scanned++;
|
||||
if (++iterations > 128) {
|
||||
if (ustime() > endtime) {
|
||||
serverAssert(ri.key_len==sizeof(last));
|
||||
memcpy(last,ri.key,ri.key_len);
|
||||
@@ -776,7 +814,7 @@ long defragKey(redisDb *db, dictEntry *de) {
|
||||
* I can't search in db->expires for that key after i already released
|
||||
* the pointer it holds it won't be able to do the string compare */
|
||||
uint64_t hash = dictGetHash(db->dict, de->key);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(db->expires, keysds, newsds, hash, &defragged);
|
||||
replaceSatelliteDictKeyPtrAndOrDefragDictEntry(db->expires, keysds, newsds, hash, &defragged);
|
||||
}
|
||||
|
||||
/* Try to defrag robj and / or string value. */
|
||||
@@ -847,7 +885,7 @@ void defragScanCallback(void *privdata, const dictEntry *de) {
|
||||
server.stat_active_defrag_scanned++;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for each hash table bicket,
|
||||
/* Defrag scan callback for each hash table bucket,
|
||||
* used in order to defrag the dictEntry allocations. */
|
||||
void defragDictBucketCallback(void *privdata, dictEntry **bucketref) {
|
||||
UNUSED(privdata); /* NOTE: this function is also used by both activeDefragCycle and scanLaterHash, etc. don't use privdata */
|
||||
@@ -881,7 +919,7 @@ float getAllocatorFragmentation(size_t *out_frag_bytes) {
|
||||
return frag_pct;
|
||||
}
|
||||
|
||||
/* We may need to defrag other globals, one small allcation can hold a full allocator run.
|
||||
/* We may need to defrag other globals, one small allocation can hold a full allocator run.
|
||||
* so although small, it is still important to defrag these */
|
||||
long defragOtherGlobals() {
|
||||
long defragged = 0;
|
||||
@@ -900,8 +938,7 @@ int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime) {
|
||||
if (de) {
|
||||
robj *ob = dictGetVal(de);
|
||||
if (ob->type == OBJ_LIST) {
|
||||
server.stat_active_defrag_hits += scanLaterList(ob);
|
||||
*cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
return scanLaterList(ob, cursor, endtime, &server.stat_active_defrag_hits);
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
server.stat_active_defrag_hits += scanLaterSet(ob, cursor);
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
@@ -961,11 +998,6 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
if (defragLaterItem(de, &defrag_later_cursor, endtime))
|
||||
quit = 1; /* time is up, we didn't finish all the work */
|
||||
|
||||
/* Don't start a new BIG key in this loop, this is because the
|
||||
* next key can be a list, and scanLaterList must be done in once cycle */
|
||||
if (!defrag_later_cursor)
|
||||
quit = 1;
|
||||
|
||||
/* Once in 16 scan iterations, 512 pointer reallocations, or 64 fields
|
||||
* (if we have a lot of pointers in one hash bucket, or rehashing),
|
||||
* check if we reached the time limit. */
|
||||
@@ -1058,7 +1090,7 @@ void activeDefragCycle(void) {
|
||||
if (hasActiveChildProcess())
|
||||
return; /* Defragging memory while there's a fork will just do damage. */
|
||||
|
||||
/* Once a second, check if we the fragmentation justfies starting a scan
|
||||
/* Once a second, check if the fragmentation justfies starting a scan
|
||||
* or making it more aggressive. */
|
||||
run_with_period(1000) {
|
||||
computeDefragCycles();
|
||||
@@ -1128,7 +1160,7 @@ void activeDefragCycle(void) {
|
||||
* (if we have a lot of pointers in one hash bucket or rehasing),
|
||||
* check if we reached the time limit.
|
||||
* But regardless, don't start a new db in this loop, this is because after
|
||||
* the last db we call defragOtherGlobals, which must be done in once cycle */
|
||||
* the last db we call defragOtherGlobals, which must be done in one cycle */
|
||||
if (!cursor || (++iterations > 16 ||
|
||||
server.stat_active_defrag_hits - prev_defragged > 512 ||
|
||||
server.stat_active_defrag_scanned - prev_scanned > 64)) {
|
||||
|
||||
+52
-31
@@ -51,15 +51,15 @@
|
||||
#include <assert.h>
|
||||
#endif
|
||||
|
||||
/* Using dictEnableResize() / dictDisableResize() we make possible to
|
||||
* enable/disable resizing of the hash table as needed. This is very important
|
||||
/* Using dictEnableResize() / dictDisableResize() we make possible to disable
|
||||
* resizing and rehashing of the hash table as needed. This is very important
|
||||
* for Redis, as we use copy-on-write and don't want to move too much memory
|
||||
* around when there is a child performing saving operations.
|
||||
*
|
||||
* Note that even when dict_can_resize is set to 0, not all resizes are
|
||||
* prevented: a hash table is still allowed to grow if the ratio between
|
||||
* the number of elements and the buckets > dict_force_resize_ratio. */
|
||||
static int dict_can_resize = 1;
|
||||
static dictResizeEnable dict_can_resize = DICT_RESIZE_ENABLE;
|
||||
static unsigned int dict_force_resize_ratio = 5;
|
||||
|
||||
/* -------------------------- private prototypes ---------------------------- */
|
||||
@@ -134,9 +134,9 @@ int _dictInit(dict *d, dictType *type,
|
||||
* but with the invariant of a USED/BUCKETS ratio near to <= 1 */
|
||||
int dictResize(dict *d)
|
||||
{
|
||||
int minimal;
|
||||
unsigned long minimal;
|
||||
|
||||
if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;
|
||||
if (dict_can_resize != DICT_RESIZE_ENABLE || dictIsRehashing(d)) return DICT_ERR;
|
||||
minimal = d->ht[0].used;
|
||||
if (minimal < DICT_HT_INITIAL_SIZE)
|
||||
minimal = DICT_HT_INITIAL_SIZE;
|
||||
@@ -187,7 +187,15 @@ int dictExpand(dict *d, unsigned long size)
|
||||
* 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;
|
||||
unsigned long s0 = d->ht[0].size;
|
||||
unsigned long s1 = d->ht[1].size;
|
||||
if (dict_can_resize == DICT_RESIZE_FORBID || !dictIsRehashing(d)) return 0;
|
||||
if (dict_can_resize == DICT_RESIZE_AVOID &&
|
||||
((s1 > s0 && s1 / s0 < dict_force_resize_ratio) ||
|
||||
(s1 < s0 && s0 / s1 < dict_force_resize_ratio)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
while(n-- && d->ht[0].used != 0) {
|
||||
dictEntry *de, *nextde;
|
||||
@@ -237,7 +245,9 @@ long long timeInMilliseconds(void) {
|
||||
return (((long long)tv.tv_sec)*1000)+(tv.tv_usec/1000);
|
||||
}
|
||||
|
||||
/* Rehash for an amount of time between ms milliseconds and ms+1 milliseconds */
|
||||
/* Rehash in ms+"delta" milliseconds. The value of "delta" is larger
|
||||
* than 0, and is smaller than 1 in most cases. The exact upper bound
|
||||
* depends on the running time of dictRehash(d,100).*/
|
||||
int dictRehashMilliseconds(dict *d, int ms) {
|
||||
long long start = timeInMilliseconds();
|
||||
int rehashes = 0;
|
||||
@@ -478,7 +488,7 @@ dictEntry *dictFind(dict *d, const void *key)
|
||||
dictEntry *he;
|
||||
uint64_t h, idx, table;
|
||||
|
||||
if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
|
||||
if (dictSize(d) == 0) return NULL; /* dict is empty */
|
||||
if (dictIsRehashing(d)) _dictRehashStep(d);
|
||||
h = dictHashKey(d, key);
|
||||
for (table = 0; table <= 1; table++) {
|
||||
@@ -507,8 +517,8 @@ void *dictFetchValue(dict *d, const void *key) {
|
||||
* the fingerprint again when the iterator is released.
|
||||
* If the two fingerprints are different it means that the user of the iterator
|
||||
* performed forbidden operations against the dictionary while iterating. */
|
||||
long long dictFingerprint(dict *d) {
|
||||
long long integers[6], hash = 0;
|
||||
unsigned long long dictFingerprint(dict *d) {
|
||||
unsigned long long integers[6], hash = 0;
|
||||
int j;
|
||||
|
||||
integers[0] = (long) d->ht[0].table;
|
||||
@@ -619,15 +629,13 @@ dictEntry *dictGetRandomKey(dict *d)
|
||||
do {
|
||||
/* We are sure there are no elements in indexes from 0
|
||||
* to rehashidx-1 */
|
||||
h = d->rehashidx + (random() % (d->ht[0].size +
|
||||
d->ht[1].size -
|
||||
d->rehashidx));
|
||||
h = d->rehashidx + (randomULong() % (dictSlots(d) - 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);
|
||||
} else {
|
||||
do {
|
||||
h = random() & d->ht[0].sizemask;
|
||||
h = randomULong() & d->ht[0].sizemask;
|
||||
he = d->ht[0].table[h];
|
||||
} while(he == NULL);
|
||||
}
|
||||
@@ -693,7 +701,7 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
maxsizemask = d->ht[1].sizemask;
|
||||
|
||||
/* Pick a random point inside the larger table. */
|
||||
unsigned long i = random() & maxsizemask;
|
||||
unsigned long i = randomULong() & maxsizemask;
|
||||
unsigned long emptylen = 0; /* Continuous empty entries so far. */
|
||||
while(stored < count && maxsteps--) {
|
||||
for (j = 0; j < tables; j++) {
|
||||
@@ -718,7 +726,7 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
if (he == NULL) {
|
||||
emptylen++;
|
||||
if (emptylen >= 5 && emptylen > count) {
|
||||
i = random() & maxsizemask;
|
||||
i = randomULong() & maxsizemask;
|
||||
emptylen = 0;
|
||||
}
|
||||
} else {
|
||||
@@ -749,7 +757,7 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
* this function instead what we do is to consider a "linear" range of the table
|
||||
* that may be constituted of N buckets with chains of different lengths
|
||||
* appearing one after the other. Then we report a random element in the range.
|
||||
* In this way we smooth away the problem of different chain lenghts. */
|
||||
* In this way we smooth away the problem of different chain lengths. */
|
||||
#define GETFAIR_NUM_ENTRIES 15
|
||||
dictEntry *dictGetFairRandomKey(dict *d) {
|
||||
dictEntry *entries[GETFAIR_NUM_ENTRIES];
|
||||
@@ -766,8 +774,8 @@ dictEntry *dictGetFairRandomKey(dict *d) {
|
||||
/* Function to reverse bits. Algorithm from:
|
||||
* http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
|
||||
static unsigned long rev(unsigned long v) {
|
||||
unsigned long s = 8 * sizeof(v); // bit size; must be power of 2
|
||||
unsigned long mask = ~0;
|
||||
unsigned long s = CHAR_BIT * sizeof(v); // bit size; must be power of 2
|
||||
unsigned long mask = ~0UL;
|
||||
while ((s >>= 1) > 0) {
|
||||
mask ^= (mask << s);
|
||||
v = ((v >> s) & mask) | ((v << s) & ~mask);
|
||||
@@ -871,6 +879,10 @@ unsigned long dictScan(dict *d,
|
||||
|
||||
if (dictSize(d) == 0) return 0;
|
||||
|
||||
/* Having a safe iterator means no rehashing can happen, see _dictRehashStep.
|
||||
* This is needed in case the scan callback tries to do dictFind or alike. */
|
||||
d->iterators++;
|
||||
|
||||
if (!dictIsRehashing(d)) {
|
||||
t0 = &(d->ht[0]);
|
||||
m0 = t0->sizemask;
|
||||
@@ -937,6 +949,9 @@ unsigned long dictScan(dict *d,
|
||||
} while (v & (m0 ^ m1));
|
||||
}
|
||||
|
||||
/* undo the ++ at the top */
|
||||
d->iterators--;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -955,9 +970,10 @@ static int _dictExpandIfNeeded(dict *d)
|
||||
* table (global setting) or we should avoid it but the ratio between
|
||||
* elements/buckets is over the "safe" threshold, we resize doubling
|
||||
* the number of buckets. */
|
||||
if (d->ht[0].used >= d->ht[0].size &&
|
||||
(dict_can_resize ||
|
||||
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
|
||||
if ((dict_can_resize == DICT_RESIZE_ENABLE &&
|
||||
d->ht[0].used >= d->ht[0].size) ||
|
||||
(dict_can_resize != DICT_RESIZE_FORBID &&
|
||||
d->ht[0].used / d->ht[0].size > dict_force_resize_ratio))
|
||||
{
|
||||
return dictExpand(d, d->ht[0].used*2);
|
||||
}
|
||||
@@ -1016,12 +1032,8 @@ void dictEmpty(dict *d, void(callback)(void*)) {
|
||||
d->iterators = 0;
|
||||
}
|
||||
|
||||
void dictEnableResize(void) {
|
||||
dict_can_resize = 1;
|
||||
}
|
||||
|
||||
void dictDisableResize(void) {
|
||||
dict_can_resize = 0;
|
||||
void dictSetResizeEnabled(dictResizeEnable enable) {
|
||||
dict_can_resize = enable;
|
||||
}
|
||||
|
||||
uint64_t dictGetHash(dict *d, const void *key) {
|
||||
@@ -1037,7 +1049,7 @@ dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t h
|
||||
dictEntry *he, **heref;
|
||||
unsigned long idx, table;
|
||||
|
||||
if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
|
||||
if (dictSize(d) == 0) return NULL; /* dict is empty */
|
||||
for (table = 0; table <= 1; table++) {
|
||||
idx = hash & d->ht[table].sizemask;
|
||||
heref = &d->ht[table].table[idx];
|
||||
@@ -1064,7 +1076,9 @@ size_t _dictGetStatsHt(char *buf, size_t bufsize, dictht *ht, int tableid) {
|
||||
|
||||
if (ht->used == 0) {
|
||||
return snprintf(buf,bufsize,
|
||||
"No stats available for empty dictionaries\n");
|
||||
"Hash table %d stats (%s):\n"
|
||||
"No stats available for empty dictionaries\n",
|
||||
tableid, (tableid == 0) ? "main hash table" : "rehashing target");
|
||||
}
|
||||
|
||||
/* Compute stats. */
|
||||
@@ -1112,7 +1126,7 @@ size_t _dictGetStatsHt(char *buf, size_t bufsize, dictht *ht, int tableid) {
|
||||
i, clvector[i], ((float)clvector[i]/ht->size)*100);
|
||||
}
|
||||
|
||||
/* Unlike snprintf(), teturn the number of characters actually written. */
|
||||
/* Unlike snprintf(), return the number of characters actually written. */
|
||||
if (bufsize) buf[bufsize-1] = '\0';
|
||||
return strlen(buf);
|
||||
}
|
||||
@@ -1226,6 +1240,13 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
end_benchmark("Random access of existing elements");
|
||||
|
||||
start_benchmark();
|
||||
for (j = 0; j < count; j++) {
|
||||
dictEntry *de = dictGetRandomKey(dict);
|
||||
assert(de != NULL);
|
||||
}
|
||||
end_benchmark("Accessing random keys");
|
||||
|
||||
start_benchmark();
|
||||
for (j = 0; j < count; j++) {
|
||||
sds key = sdsfromlonglong(rand() % count);
|
||||
|
||||
+20
-5
@@ -33,11 +33,14 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef __DICT_H
|
||||
#define __DICT_H
|
||||
|
||||
#include "mt19937-64.h"
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define DICT_OK 0
|
||||
#define DICT_ERR 1
|
||||
|
||||
@@ -91,7 +94,7 @@ typedef struct dictIterator {
|
||||
int table, safe;
|
||||
dictEntry *entry, *nextEntry;
|
||||
/* unsafe iterator fingerprint for misuse detection. */
|
||||
long long fingerprint;
|
||||
unsigned long long fingerprint;
|
||||
} dictIterator;
|
||||
|
||||
typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
@@ -147,6 +150,19 @@ typedef void (dictScanBucketFunction)(void *privdata, dictEntry **bucketref);
|
||||
#define dictSize(d) ((d)->ht[0].used+(d)->ht[1].used)
|
||||
#define dictIsRehashing(d) ((d)->rehashidx != -1)
|
||||
|
||||
/* If our unsigned long type can store a 64 bit number, use a 64 bit PRNG. */
|
||||
#if ULONG_MAX >= 0xffffffffffffffff
|
||||
#define randomULong() ((unsigned long) genrand64_int64())
|
||||
#else
|
||||
#define randomULong() random()
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
DICT_RESIZE_ENABLE,
|
||||
DICT_RESIZE_AVOID,
|
||||
DICT_RESIZE_FORBID,
|
||||
} dictResizeEnable;
|
||||
|
||||
/* API */
|
||||
dict *dictCreate(dictType *type, void *privDataPtr);
|
||||
int dictExpand(dict *d, unsigned long size);
|
||||
@@ -172,8 +188,7 @@ void dictGetStats(char *buf, size_t bufsize, dict *d);
|
||||
uint64_t dictGenHashFunction(const void *key, int len);
|
||||
uint64_t dictGenCaseHashFunction(const unsigned char *buf, int len);
|
||||
void dictEmpty(dict *d, void(callback)(void*));
|
||||
void dictEnableResize(void);
|
||||
void dictDisableResize(void);
|
||||
void dictSetResizeEnabled(dictResizeEnable enable);
|
||||
int dictRehash(dict *d, int n);
|
||||
int dictRehashMilliseconds(dict *d, int ms);
|
||||
void dictSetHashFunctionSeed(uint8_t *seed);
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
* to be backward compatible are still in big endian) because most of the
|
||||
* production environments are little endian, and we have a lot of conversions
|
||||
* in a few places because ziplists, intsets, zipmaps, need to be endian-neutral
|
||||
* even in memory, since they are serialied on RDB files directly with a single
|
||||
* even in memory, since they are serialized on RDB files directly with a single
|
||||
* write(2) without other additional steps.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
|
||||
+25
-17
@@ -41,7 +41,7 @@
|
||||
/* To improve the quality of the LRU approximation we take a set of keys
|
||||
* that are good candidate for eviction across freeMemoryIfNeeded() calls.
|
||||
*
|
||||
* Entries inside the eviciton pool are taken ordered by idle time, putting
|
||||
* Entries inside the eviction pool are taken ordered by idle time, putting
|
||||
* greater idle times to the right (ascending order).
|
||||
*
|
||||
* When an LFU policy is used instead, a reverse frequency indication is used
|
||||
@@ -242,7 +242,7 @@ void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evic
|
||||
/* Try to reuse the cached SDS string allocated in the pool entry,
|
||||
* because allocating and deallocating this object is costly
|
||||
* (according to the profiler, not my fantasy. Remember:
|
||||
* premature optimizbla bla bla bla. */
|
||||
* premature optimization bla bla bla. */
|
||||
int klen = sdslen(key);
|
||||
if (klen > EVPOOL_CACHED_SDS_SIZE) {
|
||||
pool[k].key = sdsdup(key);
|
||||
@@ -342,7 +342,7 @@ unsigned long LFUDecrAndReturn(robj *o) {
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* The external API for eviction: freeMemroyIfNeeded() is called by the
|
||||
* The external API for eviction: freeMemoryIfNeeded() is called by the
|
||||
* server when there is data to add in order to make space if needed.
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
@@ -441,7 +441,7 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
||||
*
|
||||
* The function returns C_OK if we are under the memory limit or if we
|
||||
* were over the limit, but the attempt to free memory was successful.
|
||||
* Otehrwise if we are over the memory limit, but not enough memory
|
||||
* Otherwise if we are over the memory limit, but not enough memory
|
||||
* was freed to return back under the limit, the function returns C_ERR. */
|
||||
int freeMemoryIfNeeded(void) {
|
||||
int keys_freed = 0;
|
||||
@@ -450,9 +450,10 @@ int freeMemoryIfNeeded(void) {
|
||||
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
|
||||
|
||||
size_t mem_reported, mem_tofree, mem_freed;
|
||||
mstime_t latency, eviction_latency;
|
||||
mstime_t latency, eviction_latency, lazyfree_latency;
|
||||
long long delta;
|
||||
int slaves = listLength(server.slaves);
|
||||
int result = C_ERR;
|
||||
|
||||
/* When clients are paused the dataset should be static not just from the
|
||||
* POV of clients not being able to write, but also from the POV of
|
||||
@@ -463,10 +464,10 @@ int freeMemoryIfNeeded(void) {
|
||||
|
||||
mem_freed = 0;
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION)
|
||||
goto cant_free; /* We need to free memory, but policy forbids. */
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
while (mem_freed < mem_tofree) {
|
||||
int j, k, i;
|
||||
static unsigned int next_db = 0;
|
||||
@@ -561,6 +562,8 @@ int freeMemoryIfNeeded(void) {
|
||||
* we are freeing removing the key, but we can't account for
|
||||
* that otherwise we would never exit the loop.
|
||||
*
|
||||
* Same for CSC invalidation messages generated by signalModifiedKey.
|
||||
*
|
||||
* AOF and Output buffer memory will be freed eventually so
|
||||
* we only care about memory used by the key space. */
|
||||
delta = (long long) zmalloc_used_memory();
|
||||
@@ -571,10 +574,10 @@ int freeMemoryIfNeeded(void) {
|
||||
dbSyncDelete(db,keyobj);
|
||||
latencyEndMonitor(eviction_latency);
|
||||
latencyAddSampleIfNeeded("eviction-del",eviction_latency);
|
||||
latencyRemoveNestedEvent(latency,eviction_latency);
|
||||
delta -= (long long) zmalloc_used_memory();
|
||||
mem_freed += delta;
|
||||
server.stat_evictedkeys++;
|
||||
signalModifiedKey(NULL,db,keyobj);
|
||||
notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted",
|
||||
keyobj, db->id);
|
||||
decrRefCount(keyobj);
|
||||
@@ -600,25 +603,30 @@ int freeMemoryIfNeeded(void) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
goto cant_free; /* nothing to free... */
|
||||
}
|
||||
}
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
return C_OK;
|
||||
result = C_OK;
|
||||
|
||||
cant_free:
|
||||
/* We are here if we are not able to reclaim memory. There is only one
|
||||
* last thing we can try: check if the lazyfree thread has jobs in queue
|
||||
* and wait... */
|
||||
while(bioPendingJobsOfType(BIO_LAZY_FREE)) {
|
||||
if (((mem_reported - zmalloc_used_memory()) + mem_freed) >= mem_tofree)
|
||||
break;
|
||||
usleep(1000);
|
||||
if (result != C_OK) {
|
||||
latencyStartMonitor(lazyfree_latency);
|
||||
while(bioPendingJobsOfType(BIO_LAZY_FREE)) {
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
|
||||
result = C_OK;
|
||||
break;
|
||||
}
|
||||
usleep(1000);
|
||||
}
|
||||
latencyEndMonitor(lazyfree_latency);
|
||||
latencyAddSampleIfNeeded("eviction-lazyfree",lazyfree_latency);
|
||||
}
|
||||
return C_ERR;
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* This is a wrapper for freeMemoryIfNeeded() that only really calls the
|
||||
|
||||
+27
-18
@@ -64,7 +64,7 @@ int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
dbSyncDelete(db,keyobj);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",keyobj,db->id);
|
||||
trackingInvalidateKey(keyobj);
|
||||
signalModifiedKey(NULL, db, keyobj);
|
||||
decrRefCount(keyobj);
|
||||
server.stat_expiredkeys++;
|
||||
return 1;
|
||||
@@ -97,7 +97,7 @@ int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
* conditions:
|
||||
*
|
||||
* If type is ACTIVE_EXPIRE_CYCLE_FAST the function will try to run a
|
||||
* "fast" expire cycle that takes no longer than EXPIRE_FAST_CYCLE_DURATION
|
||||
* "fast" expire cycle that takes no longer than ACTIVE_EXPIRE_CYCLE_FAST_DURATION
|
||||
* microseconds, and is not repeated again before the same amount of time.
|
||||
* The cycle will also refuse to run at all if the latest slow cycle did not
|
||||
* terminate because of a time limit condition.
|
||||
@@ -203,8 +203,10 @@ void activeExpireCycle(int type) {
|
||||
* distribute the time evenly across DBs. */
|
||||
current_db++;
|
||||
|
||||
/* Continue to expire if at the end of the cycle more than 25%
|
||||
* of the keys were expired. */
|
||||
/* Continue to expire if at the end of the cycle there are still
|
||||
* a big percentage of keys to expire, compared to the number of keys
|
||||
* we scanned. The percentage, stored in config_cycle_acceptable_stale
|
||||
* is not fixed, but depends on the Redis configured "expire effort". */
|
||||
do {
|
||||
unsigned long num, slots;
|
||||
long long now, ttl_sum;
|
||||
@@ -305,8 +307,9 @@ void activeExpireCycle(int type) {
|
||||
}
|
||||
/* We don't repeat the cycle for the current database if there are
|
||||
* an acceptable amount of stale keys (logically expired but yet
|
||||
* not reclained). */
|
||||
} while ((expired*100/sampled) > config_cycle_acceptable_stale);
|
||||
* not reclaimed). */
|
||||
} while (sampled == 0 ||
|
||||
(expired*100/sampled) > config_cycle_acceptable_stale);
|
||||
}
|
||||
|
||||
elapsed = ustime()-start;
|
||||
@@ -411,7 +414,7 @@ void expireSlaveKeys(void) {
|
||||
else
|
||||
dictDelete(slaveKeysWithExpire,keyname);
|
||||
|
||||
/* Stop conditions: found 3 keys we cna't expire in a row or
|
||||
/* Stop conditions: found 3 keys we can't expire in a row or
|
||||
* time limit was reached. */
|
||||
cycles++;
|
||||
if (noexpire > 3) break;
|
||||
@@ -463,7 +466,7 @@ size_t getSlaveKeyWithExpireCount(void) {
|
||||
*
|
||||
* Note: technically we should handle the case of a single DB being flushed
|
||||
* but it is not worth it since anyway race conditions using the same set
|
||||
* of key names in a wriatable slave and in its master will lead to
|
||||
* of key names in a writable slave and in its master will lead to
|
||||
* inconsistencies. This is just a best-effort thing we do. */
|
||||
void flushSlaveKeysWithExpireList(void) {
|
||||
if (slaveKeysWithExpire) {
|
||||
@@ -472,12 +475,22 @@ void flushSlaveKeysWithExpireList(void) {
|
||||
}
|
||||
}
|
||||
|
||||
int checkAlreadyExpired(long long when) {
|
||||
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
|
||||
* should never be executed as a DEL when load the AOF or in the context
|
||||
* of a slave instance.
|
||||
*
|
||||
* Instead we add the already expired key to the database with expire time
|
||||
* (possibly in the past) and wait for an explicit DEL from the master. */
|
||||
return (when <= mstime() && !server.loading && !server.masterhost);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Expires Commands
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT
|
||||
* and PEXPIREAT. Because the commad second argument may be relative or absolute
|
||||
* and PEXPIREAT. Because the command second argument may be relative or absolute
|
||||
* the "basetime" argument is used to signal what the base time is (either 0
|
||||
* for *AT variants of the command, or the current time for relative expires).
|
||||
*
|
||||
@@ -499,13 +512,7 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
|
||||
* should never be executed as a DEL when load the AOF or in the context
|
||||
* of a slave instance.
|
||||
*
|
||||
* Instead we take the other branch of the IF statement setting an expire
|
||||
* (possibly in the past) and wait for an explicit DEL from the master. */
|
||||
if (when <= mstime() && !server.loading && !server.masterhost) {
|
||||
if (checkAlreadyExpired(when)) {
|
||||
robj *aux;
|
||||
|
||||
int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
|
||||
@@ -516,14 +523,14 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
/* Replicate/AOF this as an explicit DEL or UNLINK. */
|
||||
aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
|
||||
rewriteClientCommandVector(c,2,aux,key);
|
||||
signalModifiedKey(c->db,key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
addReply(c, shared.cone);
|
||||
return;
|
||||
} else {
|
||||
setExpire(c,c->db,key,when);
|
||||
addReply(c,shared.cone);
|
||||
signalModifiedKey(c->db,key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
|
||||
server.dirty++;
|
||||
return;
|
||||
@@ -587,6 +594,8 @@ void pttlCommand(client *c) {
|
||||
void persistCommand(client *c) {
|
||||
if (lookupKeyWrite(c->db,c->argv[1])) {
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id);
|
||||
addReply(c,shared.cone);
|
||||
server.dirty++;
|
||||
} else {
|
||||
|
||||
@@ -58,4 +58,9 @@
|
||||
#define _LARGEFILE_SOURCE
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
|
||||
#ifdef __linux__
|
||||
/* features.h uses the defines above to set feature specific defines. */
|
||||
#include <features.h>
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -143,8 +143,8 @@ double extractUnitOrReply(client *c, robj *unit) {
|
||||
}
|
||||
|
||||
/* 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
|
||||
* Extract the distance from the specified two arguments starting at 'argv'
|
||||
* that should be in the form: <number> <unit>, and return the distance in the
|
||||
* specified unit on success. *conversions is populated with the coefficient
|
||||
* to use in order to convert meters to the unit.
|
||||
*
|
||||
@@ -635,7 +635,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
robj *zobj;
|
||||
zset *zs;
|
||||
int i;
|
||||
size_t maxelelen = 0;
|
||||
size_t maxelelen = 0, totelelen = 0;
|
||||
|
||||
if (returned_items) {
|
||||
zobj = createZsetObject();
|
||||
@@ -650,20 +650,21 @@ void georadiusGeneric(client *c, int flags) {
|
||||
size_t elelen = sdslen(gp->member);
|
||||
|
||||
if (maxelelen < elelen) maxelelen = elelen;
|
||||
totelelen += elelen;
|
||||
znode = zslInsert(zs->zsl,score,gp->member);
|
||||
serverAssert(dictAdd(zs->dict,gp->member,&znode->score) == DICT_OK);
|
||||
gp->member = NULL;
|
||||
}
|
||||
|
||||
if (returned_items) {
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
|
||||
setKey(c->db,storekey,zobj);
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen,totelelen);
|
||||
setKey(c,c->db,storekey,zobj);
|
||||
decrRefCount(zobj);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
|
||||
c->db->id);
|
||||
server.dirty += returned_items;
|
||||
} else if (dbDelete(c->db,storekey)) {
|
||||
signalModifiedKey(c->db,storekey);
|
||||
signalModifiedKey(c,c->db,storekey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",storekey,c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -788,7 +789,7 @@ void geoposCommand(client *c) {
|
||||
|
||||
/* GEODIST key ele1 ele2 [unit]
|
||||
*
|
||||
* Return the distance, in meters by default, otherwise accordig to "unit",
|
||||
* Return the distance, in meters by default, otherwise according to "unit",
|
||||
* between points ele1 and ele2. If one or more elements are missing NULL
|
||||
* is returned. */
|
||||
void geodistCommand(client *c) {
|
||||
|
||||
@@ -206,7 +206,11 @@ int geohashDecodeWGS84(const GeoHashBits hash, GeoHashArea *area) {
|
||||
int geohashDecodeAreaToLongLat(const GeoHashArea *area, double *xy) {
|
||||
if (!xy) return 0;
|
||||
xy[0] = (area->longitude.min + area->longitude.max) / 2;
|
||||
if (xy[0] > GEO_LONG_MAX) xy[0] = GEO_LONG_MAX;
|
||||
if (xy[0] < GEO_LONG_MIN) xy[0] = GEO_LONG_MIN;
|
||||
xy[1] = (area->latitude.min + area->latitude.max) / 2;
|
||||
if (xy[1] > GEO_LAT_MAX) xy[1] = GEO_LAT_MAX;
|
||||
if (xy[1] < GEO_LAT_MIN) xy[1] = GEO_LAT_MIN;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ uint8_t geohashEstimateStepsByRadius(double range_meters, double lat) {
|
||||
}
|
||||
step -= 2; /* Make sure range is included in most of the base cases. */
|
||||
|
||||
/* Wider range torwards the poles... Note: it is possible to do better
|
||||
/* Wider range towards 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 > 66 || lat < -66) {
|
||||
@@ -84,7 +84,7 @@ uint8_t geohashEstimateStepsByRadius(double range_meters, double lat) {
|
||||
|
||||
/* Return the bounding box of the search area centered at latitude,longitude
|
||||
* having a radius of radius_meter. bounds[0] - bounds[2] is the minimum
|
||||
* and maxium longitude, while bounds[1] - bounds[3] is the minimum and
|
||||
* and maximum longitude, while bounds[1] - bounds[3] is the minimum and
|
||||
* maximum latitude.
|
||||
*
|
||||
* This function does not behave correctly with very large radius values, for
|
||||
|
||||
+198
-38
@@ -1,4 +1,4 @@
|
||||
/* Automatically generated by generate-command-help.rb, do not edit. */
|
||||
/* Automatically generated by ./utils/generate-command-help.rb, do not edit. */
|
||||
|
||||
#ifndef __REDIS_HELP_H
|
||||
#define __REDIS_HELP_H
|
||||
@@ -28,13 +28,73 @@ struct commandHelp {
|
||||
int group;
|
||||
char *since;
|
||||
} commandHelp[] = {
|
||||
{ "ACL CAT",
|
||||
"[categoryname]",
|
||||
"List the ACL categories or the commands inside a category",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL DELUSER",
|
||||
"username [username ...]",
|
||||
"Remove the specified ACL users and the associated rules",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL GENPASS",
|
||||
"[bits]",
|
||||
"Generate a pseudorandom secure password to use for ACL users",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL GETUSER",
|
||||
"username",
|
||||
"Get the rules for a specific ACL user",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL HELP",
|
||||
"-",
|
||||
"Show helpful text about the different subcommands",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL LIST",
|
||||
"-",
|
||||
"List the current ACL rules in ACL config file format",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL LOAD",
|
||||
"-",
|
||||
"Reload the ACLs from the configured ACL file",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL LOG",
|
||||
"[count or RESET]",
|
||||
"List latest events denied because of ACLs in place",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL SAVE",
|
||||
"-",
|
||||
"Save the current ACL rules in the configured ACL file",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL SETUSER",
|
||||
"username [rule [rule ...]]",
|
||||
"Modify or create the rules for a specific ACL user",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL USERS",
|
||||
"-",
|
||||
"List the username of all the configured ACL rules",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "ACL WHOAMI",
|
||||
"-",
|
||||
"Return the name of the user associated to the current connection",
|
||||
9,
|
||||
"6.0.0" },
|
||||
{ "APPEND",
|
||||
"key value",
|
||||
"Append a value to a key",
|
||||
1,
|
||||
"2.0.0" },
|
||||
{ "AUTH",
|
||||
"password",
|
||||
"[username] password",
|
||||
"Authenticate to the server",
|
||||
8,
|
||||
"1.0.0" },
|
||||
@@ -44,7 +104,7 @@ struct commandHelp {
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "BGSAVE",
|
||||
"-",
|
||||
"[SCHEDULE]",
|
||||
"Asynchronously save the dataset to disk",
|
||||
9,
|
||||
"1.0.0" },
|
||||
@@ -80,7 +140,7 @@ struct commandHelp {
|
||||
"2.0.0" },
|
||||
{ "BRPOPLPUSH",
|
||||
"source destination timeout",
|
||||
"Pop a value from a list, push it to another list and return it; or block until one is available",
|
||||
"Pop an element from a list, push it to another list and return it; or block until one is available",
|
||||
2,
|
||||
"2.2.0" },
|
||||
{ "BZPOPMAX",
|
||||
@@ -93,51 +153,71 @@ struct commandHelp {
|
||||
"Remove and return the member with the lowest score from one or more sorted sets, or block until one is available",
|
||||
4,
|
||||
"5.0.0" },
|
||||
{ "CLIENT CACHING",
|
||||
"YES|NO",
|
||||
"Instruct the server about tracking or not keys in the next request",
|
||||
8,
|
||||
"6.0.0" },
|
||||
{ "CLIENT GETNAME",
|
||||
"-",
|
||||
"Get the current connection name",
|
||||
9,
|
||||
8,
|
||||
"2.6.9" },
|
||||
{ "CLIENT GETREDIR",
|
||||
"-",
|
||||
"Get tracking notifications redirection client ID if any",
|
||||
8,
|
||||
"6.0.0" },
|
||||
{ "CLIENT ID",
|
||||
"-",
|
||||
"Returns the client ID for the current connection",
|
||||
9,
|
||||
8,
|
||||
"5.0.0" },
|
||||
{ "CLIENT KILL",
|
||||
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [ADDR ip:port] [SKIPME yes/no]",
|
||||
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [USER username] [ADDR ip:port] [SKIPME yes/no]",
|
||||
"Kill the connection of a client",
|
||||
9,
|
||||
8,
|
||||
"2.4.0" },
|
||||
{ "CLIENT LIST",
|
||||
"-",
|
||||
"[TYPE normal|master|replica|pubsub]",
|
||||
"Get the list of client connections",
|
||||
9,
|
||||
8,
|
||||
"2.4.0" },
|
||||
{ "CLIENT PAUSE",
|
||||
"timeout",
|
||||
"Stop processing commands from clients for some time",
|
||||
9,
|
||||
8,
|
||||
"2.9.50" },
|
||||
{ "CLIENT REPLY",
|
||||
"ON|OFF|SKIP",
|
||||
"Instruct the server whether to reply to commands",
|
||||
9,
|
||||
"3.2" },
|
||||
8,
|
||||
"3.2.0" },
|
||||
{ "CLIENT SETNAME",
|
||||
"connection-name",
|
||||
"Set the current connection name",
|
||||
9,
|
||||
8,
|
||||
"2.6.9" },
|
||||
{ "CLIENT TRACKING",
|
||||
"ON|OFF [REDIRECT client-id] [PREFIX prefix [PREFIX prefix ...]] [BCAST] [OPTIN] [OPTOUT] [NOLOOP]",
|
||||
"Enable or disable server assisted client side caching support",
|
||||
8,
|
||||
"6.0.0" },
|
||||
{ "CLIENT UNBLOCK",
|
||||
"client-id [TIMEOUT|ERROR]",
|
||||
"Unblock a client blocked in a blocking command from a different connection",
|
||||
9,
|
||||
8,
|
||||
"5.0.0" },
|
||||
{ "CLUSTER ADDSLOTS",
|
||||
"slot [slot ...]",
|
||||
"Assign new hash slots to receiving node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER BUMPEPOCH",
|
||||
"-",
|
||||
"Advance the cluster config epoch",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER COUNT-FAILURE-REPORTS",
|
||||
"node-id",
|
||||
"Return the number of failure reports active for a given node",
|
||||
@@ -158,6 +238,11 @@ struct commandHelp {
|
||||
"Forces a replica to perform a manual failover of its master.",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER FLUSHSLOTS",
|
||||
"-",
|
||||
"Delete a node's own slots information",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER FORGET",
|
||||
"node-id",
|
||||
"Remove a node from the nodes table",
|
||||
@@ -183,6 +268,11 @@ struct commandHelp {
|
||||
"Force a node cluster to handshake with another node",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER MYID",
|
||||
"-",
|
||||
"Return the node id",
|
||||
12,
|
||||
"3.0.0" },
|
||||
{ "CLUSTER NODES",
|
||||
"-",
|
||||
"Get Cluster config for the node",
|
||||
@@ -359,7 +449,7 @@ struct commandHelp {
|
||||
13,
|
||||
"3.2.0" },
|
||||
{ "GEODIST",
|
||||
"key member1 member2 [unit]",
|
||||
"key member1 member2 [m|km|ft|mi]",
|
||||
"Returns the distance between two members of a geospatial index",
|
||||
13,
|
||||
"3.2.0" },
|
||||
@@ -408,6 +498,11 @@ struct commandHelp {
|
||||
"Delete one or more hash fields",
|
||||
5,
|
||||
"2.0.0" },
|
||||
{ "HELLO",
|
||||
"protover [AUTH username password] [SETNAME clientname]",
|
||||
"switch Redis protocol",
|
||||
8,
|
||||
"6.0.0" },
|
||||
{ "HEXISTS",
|
||||
"key field",
|
||||
"Determine if a hash field exists",
|
||||
@@ -459,7 +554,7 @@ struct commandHelp {
|
||||
5,
|
||||
"2.8.0" },
|
||||
{ "HSET",
|
||||
"key field value",
|
||||
"key field value [field value ...]",
|
||||
"Set the string value of a hash field",
|
||||
5,
|
||||
"2.0.0" },
|
||||
@@ -508,13 +603,43 @@ struct commandHelp {
|
||||
"Get the UNIX time stamp of the last successful save to disk",
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "LATENCY DOCTOR",
|
||||
"-",
|
||||
"Return a human readable latency analysis report.",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "LATENCY GRAPH",
|
||||
"event",
|
||||
"Return a latency graph for the event.",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "LATENCY HELP",
|
||||
"-",
|
||||
"Show helpful text about the different subcommands.",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "LATENCY HISTORY",
|
||||
"event",
|
||||
"Return timestamp-latency samples for the event.",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "LATENCY LATEST",
|
||||
"-",
|
||||
"Return the latest latency samples for all events.",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "LATENCY RESET",
|
||||
"[event [event ...]]",
|
||||
"Reset latency data for one or more events.",
|
||||
9,
|
||||
"2.8.13" },
|
||||
{ "LINDEX",
|
||||
"key index",
|
||||
"Get an element from a list by its index",
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "LINSERT",
|
||||
"key BEFORE|AFTER pivot value",
|
||||
"key BEFORE|AFTER pivot element",
|
||||
"Insert an element before or after another element in a list",
|
||||
2,
|
||||
"2.2.0" },
|
||||
@@ -523,19 +648,29 @@ struct commandHelp {
|
||||
"Get the length of a list",
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "LOLWUT",
|
||||
"[VERSION version]",
|
||||
"Display some computer art and the Redis version",
|
||||
9,
|
||||
"5.0.0" },
|
||||
{ "LPOP",
|
||||
"key",
|
||||
"Remove and get the first element in a list",
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "LPOS",
|
||||
"key element [RANK rank] [COUNT num-matches] [MAXLEN len]",
|
||||
"Return the index of matching elements on a list",
|
||||
2,
|
||||
"6.0.6" },
|
||||
{ "LPUSH",
|
||||
"key value [value ...]",
|
||||
"Prepend one or multiple values to a list",
|
||||
"key element [element ...]",
|
||||
"Prepend one or multiple elements to a list",
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "LPUSHX",
|
||||
"key value",
|
||||
"Prepend a value to a list, only if the list exists",
|
||||
"key element [element ...]",
|
||||
"Prepend an element to a list, only if the list exists",
|
||||
2,
|
||||
"2.2.0" },
|
||||
{ "LRANGE",
|
||||
@@ -544,12 +679,12 @@ struct commandHelp {
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "LREM",
|
||||
"key count value",
|
||||
"key count element",
|
||||
"Remove elements from a list",
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "LSET",
|
||||
"key index value",
|
||||
"key index element",
|
||||
"Set the value of an element in a list by its index",
|
||||
2,
|
||||
"1.0.0" },
|
||||
@@ -594,10 +729,25 @@ struct commandHelp {
|
||||
1,
|
||||
"1.0.0" },
|
||||
{ "MIGRATE",
|
||||
"host port key|"" destination-db timeout [COPY] [REPLACE] [KEYS key]",
|
||||
"host port key|"" destination-db timeout [COPY] [REPLACE] [AUTH password] [AUTH2 username password] [KEYS key]",
|
||||
"Atomically transfer a key from a Redis instance to another one.",
|
||||
0,
|
||||
"2.6.0" },
|
||||
{ "MODULE LIST",
|
||||
"-",
|
||||
"List all modules loaded by the server",
|
||||
9,
|
||||
"4.0.0" },
|
||||
{ "MODULE LOAD",
|
||||
"path [arg]",
|
||||
"Load a module",
|
||||
9,
|
||||
"4.0.0" },
|
||||
{ "MODULE UNLOAD",
|
||||
"name",
|
||||
"Unload a module",
|
||||
9,
|
||||
"4.0.0" },
|
||||
{ "MONITOR",
|
||||
"-",
|
||||
"Listen for all requests received by the server in real time",
|
||||
@@ -673,6 +823,11 @@ struct commandHelp {
|
||||
"Listen for messages published to channels matching the given patterns",
|
||||
6,
|
||||
"2.0.0" },
|
||||
{ "PSYNC",
|
||||
"replicationid offset",
|
||||
"Internal command used for replication",
|
||||
9,
|
||||
"2.8.0" },
|
||||
{ "PTTL",
|
||||
"key",
|
||||
"Get the time to live for a key in milliseconds",
|
||||
@@ -729,7 +884,7 @@ struct commandHelp {
|
||||
9,
|
||||
"5.0.0" },
|
||||
{ "RESTORE",
|
||||
"key ttl serialized-value [REPLACE]",
|
||||
"key ttl serialized-value [REPLACE] [ABSTTL] [IDLETIME seconds] [FREQ frequency]",
|
||||
"Create a key using the provided serialized value, previously obtained using DUMP.",
|
||||
0,
|
||||
"2.6.0" },
|
||||
@@ -749,13 +904,13 @@ struct commandHelp {
|
||||
2,
|
||||
"1.2.0" },
|
||||
{ "RPUSH",
|
||||
"key value [value ...]",
|
||||
"Append one or multiple values to a list",
|
||||
"key element [element ...]",
|
||||
"Append one or multiple elements to a list",
|
||||
2,
|
||||
"1.0.0" },
|
||||
{ "RPUSHX",
|
||||
"key value",
|
||||
"Append a value to a list, only if the list exists",
|
||||
"key element [element ...]",
|
||||
"Append an element to a list, only if the list exists",
|
||||
2,
|
||||
"2.2.0" },
|
||||
{ "SADD",
|
||||
@@ -769,7 +924,7 @@ struct commandHelp {
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "SCAN",
|
||||
"cursor [MATCH pattern] [COUNT count]",
|
||||
"cursor [MATCH pattern] [COUNT count] [TYPE type]",
|
||||
"Incrementally iterate the keys space",
|
||||
0,
|
||||
"2.8.0" },
|
||||
@@ -819,7 +974,7 @@ struct commandHelp {
|
||||
8,
|
||||
"1.0.0" },
|
||||
{ "SET",
|
||||
"key value [expiration EX seconds|PX milliseconds] [NX|XX]",
|
||||
"key value [EX seconds|PX milliseconds|KEEPTTL] [NX|XX]",
|
||||
"Set the string value of a key",
|
||||
1,
|
||||
"1.0.0" },
|
||||
@@ -908,6 +1063,11 @@ struct commandHelp {
|
||||
"Incrementally iterate Set elements",
|
||||
3,
|
||||
"2.8.0" },
|
||||
{ "STRALGO",
|
||||
"LCS algo-specific-argument [algo-specific-argument ...]",
|
||||
"Run algorithms (currently LCS) against strings",
|
||||
1,
|
||||
"6.0.0" },
|
||||
{ "STRLEN",
|
||||
"key",
|
||||
"Get the length of the value stored in a key",
|
||||
@@ -929,9 +1089,9 @@ struct commandHelp {
|
||||
3,
|
||||
"1.0.0" },
|
||||
{ "SWAPDB",
|
||||
"index index",
|
||||
"index1 index2",
|
||||
"Swaps two Redis databases",
|
||||
8,
|
||||
9,
|
||||
"4.0.0" },
|
||||
{ "SYNC",
|
||||
"-",
|
||||
@@ -989,7 +1149,7 @@ struct commandHelp {
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XADD",
|
||||
"key ID field string [field string ...]",
|
||||
"key ID field value [field value ...]",
|
||||
"Appends a new entry to a stream",
|
||||
14,
|
||||
"5.0.0" },
|
||||
@@ -1004,7 +1164,7 @@ struct commandHelp {
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XGROUP",
|
||||
"[CREATE key groupname id-or-$] [SETID key id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]",
|
||||
"[CREATE key groupname id-or-$] [SETID key groupname id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]",
|
||||
"Create, destroy, and manage consumer groups.",
|
||||
14,
|
||||
"5.0.0" },
|
||||
@@ -1029,12 +1189,12 @@ struct commandHelp {
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XREAD",
|
||||
"[COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]",
|
||||
"[COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]",
|
||||
"Return never seen elements in multiple streams, with IDs greater than the ones reported by the caller for each stream. Can block.",
|
||||
14,
|
||||
"5.0.0" },
|
||||
{ "XREADGROUP",
|
||||
"GROUP group consumer [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]",
|
||||
"GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] STREAMS key [key ...] ID [ID ...]",
|
||||
"Return new entries from a stream using a consumer group, or access the history of the pending entries for a given consumer. Can block.",
|
||||
14,
|
||||
"5.0.0" },
|
||||
|
||||
+12
-11
@@ -36,9 +36,9 @@
|
||||
|
||||
/* The Redis HyperLogLog implementation is based on the following ideas:
|
||||
*
|
||||
* * The use of a 64 bit hash function as proposed in [1], in order to don't
|
||||
* limited to cardinalities up to 10^9, at the cost of just 1 additional
|
||||
* bit per register.
|
||||
* * The use of a 64 bit hash function as proposed in [1], in order to estimate
|
||||
* cardinalities larger than 10^9, at the cost of just 1 additional bit per
|
||||
* register.
|
||||
* * The use of 16384 6-bit registers for a great level of accuracy, using
|
||||
* a total of 12k per key.
|
||||
* * The use of the Redis string data type. No new type is introduced.
|
||||
@@ -279,7 +279,7 @@ static char *invalid_hll_err = "-INVALIDOBJ Corrupted HLL object detected\r\n";
|
||||
* So we right shift of 0 bits (no shift in practice) and
|
||||
* left shift the next byte of 8 bits, even if we don't use it,
|
||||
* but this has the effect of clearing the bits so the result
|
||||
* will not be affacted after the OR.
|
||||
* will not be affected after the OR.
|
||||
*
|
||||
* -------------------------------------------------------------------------
|
||||
*
|
||||
@@ -297,7 +297,7 @@ static char *invalid_hll_err = "-INVALIDOBJ Corrupted HLL object detected\r\n";
|
||||
* |11000000| <- Our byte at b0
|
||||
* +--------+
|
||||
*
|
||||
* To create a AND-mask to clear the bits about this position, we just
|
||||
* To create an AND-mask to clear the bits about this position, we just
|
||||
* initialize the mask with the value 63, left shift it of "fs" bits,
|
||||
* and finally invert the result.
|
||||
*
|
||||
@@ -766,7 +766,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
* by a ZERO opcode with len > 1, or by an XZERO opcode.
|
||||
*
|
||||
* In those cases the original opcode must be split into multiple
|
||||
* opcodes. The worst case is an XZERO split in the middle resuling into
|
||||
* opcodes. The worst case is an XZERO split in the middle resulting into
|
||||
* XZERO - VAL - XZERO, so the resulting sequence max length is
|
||||
* 5 bytes.
|
||||
*
|
||||
@@ -899,7 +899,7 @@ promote: /* Promote to dense representation. */
|
||||
* the element belongs to is incremented if needed.
|
||||
*
|
||||
* This function is actually a wrapper for hllSparseSet(), it only performs
|
||||
* the hashshing of the elmenet to obtain the index and zeros run length. */
|
||||
* the hashshing of the element to obtain the index and zeros run length. */
|
||||
int hllSparseAdd(robj *o, unsigned char *ele, size_t elesize) {
|
||||
long index;
|
||||
uint8_t count = hllPatLen(ele,elesize,&index);
|
||||
@@ -1014,7 +1014,7 @@ uint64_t hllCount(struct hllhdr *hdr, int *invalid) {
|
||||
double m = HLL_REGISTERS;
|
||||
double E;
|
||||
int j;
|
||||
/* Note that reghisto size could be just HLL_Q+2, becuase HLL_Q+1 is
|
||||
/* Note that reghisto size could be just HLL_Q+2, because HLL_Q+1 is
|
||||
* the maximum frequency of the "000...1" sequence the hash function is
|
||||
* able to return. However it is slow to check for sanity of the
|
||||
* input: instead we history array at a safe size: overflows will
|
||||
@@ -1209,7 +1209,7 @@ void pfaddCommand(client *c) {
|
||||
}
|
||||
hdr = o->ptr;
|
||||
if (updated) {
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
HLL_INVALIDATE_CACHE(hdr);
|
||||
@@ -1300,7 +1300,7 @@ void pfcountCommand(client *c) {
|
||||
* data structure is not modified, since the cached value
|
||||
* may be modified and given that the HLL is a Redis string
|
||||
* we need to propagate the change. */
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
server.dirty++;
|
||||
}
|
||||
addReplyLongLong(c,card);
|
||||
@@ -1373,7 +1373,7 @@ void pfmergeCommand(client *c) {
|
||||
last hllSparseSet() call. */
|
||||
HLL_INVALIDATE_CACHE(hdr);
|
||||
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
/* We generate a PFADD event for PFMERGE for semantical simplicity
|
||||
* since in theory this is a mass-add of elements. */
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"pfadd",c->argv[1],c->db->id);
|
||||
@@ -1535,6 +1535,7 @@ void pfdebugCommand(client *c) {
|
||||
sds decoded = sdsempty();
|
||||
|
||||
if (hdr->encoding != HLL_SPARSE) {
|
||||
sdsfree(decoded);
|
||||
addReplyError(c,"HLL encoding is not sparse");
|
||||
return;
|
||||
}
|
||||
|
||||
+4
-2
@@ -34,6 +34,7 @@
|
||||
#include "intset.h"
|
||||
#include "zmalloc.h"
|
||||
#include "endianconv.h"
|
||||
#include "redisassert.h"
|
||||
|
||||
/* Note that these encodings are ordered, so:
|
||||
* INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */
|
||||
@@ -103,7 +104,8 @@ intset *intsetNew(void) {
|
||||
|
||||
/* Resize the intset */
|
||||
static intset *intsetResize(intset *is, uint32_t len) {
|
||||
uint32_t size = len*intrev32ifbe(is->encoding);
|
||||
uint64_t size = (uint64_t)len*intrev32ifbe(is->encoding);
|
||||
assert(size <= SIZE_MAX - sizeof(intset));
|
||||
is = zrealloc(is,sizeof(intset)+size);
|
||||
return is;
|
||||
}
|
||||
@@ -278,7 +280,7 @@ uint32_t intsetLen(const intset *is) {
|
||||
|
||||
/* Return intset blob size in bytes. */
|
||||
size_t intsetBlobLen(intset *is) {
|
||||
return sizeof(intset)+intrev32ifbe(is->length)*intrev32ifbe(is->encoding);
|
||||
return sizeof(intset)+(size_t)intrev32ifbe(is->length)*intrev32ifbe(is->encoding);
|
||||
}
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
|
||||
+8
-8
@@ -71,7 +71,7 @@ int THPIsEnabled(void) {
|
||||
return 0;
|
||||
}
|
||||
fclose(fp);
|
||||
return (strstr(buf,"[never]") == NULL) ? 1 : 0;
|
||||
return (strstr(buf,"[always]") != NULL) ? 1 : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -85,7 +85,7 @@ int THPGetAnonHugePagesSize(void) {
|
||||
/* ---------------------------- Latency API --------------------------------- */
|
||||
|
||||
/* Latency monitor initialization. We just need to create the dictionary
|
||||
* of time series, each time serie is craeted on demand in order to avoid
|
||||
* of time series, each time series is created on demand in order to avoid
|
||||
* having a fixed list to maintain. */
|
||||
void latencyMonitorInit(void) {
|
||||
server.latency_events = dictCreate(&latencyTimeSeriesDictType,NULL);
|
||||
@@ -154,7 +154,7 @@ int latencyResetEvent(char *event_to_reset) {
|
||||
|
||||
/* Analyze the samples available for a given event and return a structure
|
||||
* populate with different metrics, average, MAD, min, max, and so forth.
|
||||
* Check latency.h definition of struct latenctStat for more info.
|
||||
* Check latency.h definition of struct latencyStats for more info.
|
||||
* If the specified event has no elements the structure is populate with
|
||||
* zero values. */
|
||||
void analyzeLatencyForEvent(char *event, struct latencyStats *ls) {
|
||||
@@ -343,7 +343,7 @@ sds createLatencyReport(void) {
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"aof-fstat") ||
|
||||
!strcasecmp(event,"rdb-unlik-temp-file")) {
|
||||
!strcasecmp(event,"rdb-unlink-temp-file")) {
|
||||
advise_disk_contention = 1;
|
||||
advise_local_disk = 1;
|
||||
advices += 2;
|
||||
@@ -396,7 +396,7 @@ sds createLatencyReport(void) {
|
||||
/* Better VM. */
|
||||
report = sdscat(report,"\nI have a few advices for you:\n\n");
|
||||
if (advise_better_vm) {
|
||||
report = sdscat(report,"- If you are using a virtual machine, consider upgrading it with a faster one using an hypervisior that provides less latency during fork() calls. Xen is known to have poor fork() performance. Even in the context of the same VM provider, certain kinds of instances can execute fork faster than others.\n");
|
||||
report = sdscat(report,"- If you are using a virtual machine, consider upgrading it with a faster one using a hypervisior that provides less latency during fork() calls. Xen is known to have poor fork() performance. Even in the context of the same VM provider, certain kinds of instances can execute fork faster than others.\n");
|
||||
}
|
||||
|
||||
/* Slow log. */
|
||||
@@ -416,7 +416,7 @@ sds createLatencyReport(void) {
|
||||
if (advise_scheduler) {
|
||||
report = sdscat(report,"- The system is slow to execute Redis code paths not containing system calls. This usually means the system does not provide Redis CPU time to run for long periods. You should try to:\n"
|
||||
" 1) Lower the system load.\n"
|
||||
" 2) Use a computer / VM just for Redis if you are running other softawre in the same system.\n"
|
||||
" 2) Use a computer / VM just for Redis if you are running other software in the same system.\n"
|
||||
" 3) Check if you have a \"noisy neighbour\" problem.\n"
|
||||
" 4) Check with 'redis-cli --intrinsic-latency 100' what is the intrinsic latency in your system.\n"
|
||||
" 5) Check if the problem is allocator-related by recompiling Redis with MALLOC=libc, if you are using Jemalloc. However this may create fragmentation problems.\n");
|
||||
@@ -432,7 +432,7 @@ sds createLatencyReport(void) {
|
||||
}
|
||||
|
||||
if (advise_data_writeback) {
|
||||
report = sdscat(report,"- Mounting ext3/4 filesystems with data=writeback can provide a performance boost compared to data=ordered, however this mode of operation provides less guarantees, and sometimes it can happen that after a hard crash the AOF file will have an half-written command at the end and will require to be repaired before Redis restarts.\n");
|
||||
report = sdscat(report,"- Mounting ext3/4 filesystems with data=writeback can provide a performance boost compared to data=ordered, however this mode of operation provides less guarantees, and sometimes it can happen that after a hard crash the AOF file will have a half-written command at the end and will require to be repaired before Redis restarts.\n");
|
||||
}
|
||||
|
||||
if (advise_disk_contention) {
|
||||
@@ -621,7 +621,7 @@ NULL
|
||||
resets += latencyResetEvent(c->argv[j]->ptr);
|
||||
addReplyLongLong(c,resets);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc >= 2) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
|
||||
addReplyHelp(c, help);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
|
||||
+33
-17
@@ -15,7 +15,7 @@ size_t lazyfreeGetPendingObjectsCount(void) {
|
||||
|
||||
/* Return the amount of work needed in order to free an object.
|
||||
* The return value is not always the actual number of allocations the
|
||||
* object is compoesd of, but a number proportional to it.
|
||||
* object is composed of, but a number proportional to it.
|
||||
*
|
||||
* For strings the function always returns 1.
|
||||
*
|
||||
@@ -41,6 +41,30 @@ size_t lazyfreeGetFreeEffort(robj *obj) {
|
||||
} else if (obj->type == OBJ_HASH && obj->encoding == OBJ_ENCODING_HT) {
|
||||
dict *ht = obj->ptr;
|
||||
return dictSize(ht);
|
||||
} else if (obj->type == OBJ_STREAM) {
|
||||
size_t effort = 0;
|
||||
stream *s = obj->ptr;
|
||||
|
||||
/* Make a best effort estimate to maintain constant runtime. Every macro
|
||||
* node in the Stream is one allocation. */
|
||||
effort += s->rax->numnodes;
|
||||
|
||||
/* Every consumer group is an allocation and so are the entries in its
|
||||
* PEL. We use size of the first group's PEL as an estimate for all
|
||||
* others. */
|
||||
if (s->cgroups && raxSize(s->cgroups)) {
|
||||
raxIterator ri;
|
||||
streamCG *cg;
|
||||
raxStart(&ri,s->cgroups);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
/* There must be at least one group so the following should always
|
||||
* work. */
|
||||
serverAssert(raxNext(&ri));
|
||||
cg = ri.data;
|
||||
effort += raxSize(s->cgroups)*(1+raxSize(cg->pel));
|
||||
raxStop(&ri);
|
||||
}
|
||||
return effort;
|
||||
} else {
|
||||
return 1; /* Everything else is a single allocation. */
|
||||
}
|
||||
@@ -83,7 +107,7 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
* field to NULL in order to lazy free it later. */
|
||||
if (de) {
|
||||
dictFreeUnlinkedEntry(db->dict,de);
|
||||
if (server.cluster_enabled) slotToKeyDel(key);
|
||||
if (server.cluster_enabled) slotToKeyDel(key->ptr);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
@@ -112,16 +136,10 @@ void emptyDbAsync(redisDb *db) {
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,oldht2);
|
||||
}
|
||||
|
||||
/* Empty the slots-keys map of Redis CLuster by creating a new empty one
|
||||
* and scheduiling the old for lazy freeing. */
|
||||
void slotToKeyFlushAsync(void) {
|
||||
rax *old = server.cluster->slots_to_keys;
|
||||
|
||||
server.cluster->slots_to_keys = raxNew();
|
||||
memset(server.cluster->slots_keys_count,0,
|
||||
sizeof(server.cluster->slots_keys_count));
|
||||
atomicIncr(lazyfree_objects,old->numele);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,NULL,old);
|
||||
/* Release the radix tree mapping Redis Cluster keys to slots asynchronously. */
|
||||
void freeSlotsToKeysMapAsync(rax *rt) {
|
||||
atomicIncr(lazyfree_objects,rt->numele);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,NULL,rt);
|
||||
}
|
||||
|
||||
/* Release objects from the lazyfree thread. It's just decrRefCount()
|
||||
@@ -132,10 +150,8 @@ void lazyfreeFreeObjectFromBioThread(robj *o) {
|
||||
}
|
||||
|
||||
/* Release a database from the lazyfree thread. The 'db' pointer is the
|
||||
* database which was substitutied with a fresh one in the main thread
|
||||
* when the database was logically deleted. 'sl' is a skiplist used by
|
||||
* Redis Cluster in order to take the hash slots -> keys mapping. This
|
||||
* may be NULL if Redis Cluster is disabled. */
|
||||
* database which was substituted with a fresh one in the main thread
|
||||
* when the database was logically deleted. */
|
||||
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2) {
|
||||
size_t numkeys = dictSize(ht1);
|
||||
dictRelease(ht1);
|
||||
@@ -143,7 +159,7 @@ void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2) {
|
||||
atomicDecr(lazyfree_objects,numkeys);
|
||||
}
|
||||
|
||||
/* Release the skiplist mapping Redis Cluster keys to slots in the
|
||||
/* Release the radix tree mapping Redis Cluster keys to slots in the
|
||||
* lazyfree thread. */
|
||||
void lazyfreeFreeSlotsMapFromBioThread(rax *rt) {
|
||||
size_t len = rt->numele;
|
||||
|
||||
+6
-6
@@ -283,7 +283,7 @@ int lpEncodeGetType(unsigned char *ele, uint32_t size, unsigned char *intenc, ui
|
||||
} else {
|
||||
if (size < 64) *enclen = 1+size;
|
||||
else if (size < 4096) *enclen = 2+size;
|
||||
else *enclen = 5+size;
|
||||
else *enclen = 5+(uint64_t)size;
|
||||
return LP_ENCODING_STRING;
|
||||
}
|
||||
}
|
||||
@@ -405,7 +405,7 @@ unsigned char *lpNext(unsigned char *lp, unsigned char *p) {
|
||||
}
|
||||
|
||||
/* If 'p' points to an element of the listpack, calling lpPrev() will return
|
||||
* the pointer to the preivous element (the one on the left), or NULL if 'p'
|
||||
* the pointer to the previous element (the one on the left), or NULL if 'p'
|
||||
* already pointed to the first element of the listpack. */
|
||||
unsigned char *lpPrev(unsigned char *lp, unsigned char *p) {
|
||||
if (p-lp == LP_HDR_SIZE) return NULL;
|
||||
@@ -768,18 +768,18 @@ unsigned char *lpSeek(unsigned char *lp, long index) {
|
||||
if (numele != LP_HDR_NUMELE_UNKNOWN) {
|
||||
if (index < 0) index = (long)numele+index;
|
||||
if (index < 0) return NULL; /* Index still < 0 means out of range. */
|
||||
if (index >= numele) return NULL; /* Out of range the other side. */
|
||||
if (index >= (long)numele) return NULL; /* Out of range the other side. */
|
||||
/* We want to scan right-to-left if the element we are looking for
|
||||
* is past the half of the listpack. */
|
||||
if (index > numele/2) {
|
||||
if (index > (long)numele/2) {
|
||||
forward = 0;
|
||||
/* Left to right scanning always expects a negative index. Convert
|
||||
/* Right to left scanning always expects a negative index. Convert
|
||||
* our index to negative form. */
|
||||
index -= numele;
|
||||
}
|
||||
} else {
|
||||
/* If the listpack length is unspecified, for negative indexes we
|
||||
* want to always scan left-to-right. */
|
||||
* want to always scan right-to-left. */
|
||||
if (index < 0) forward = 0;
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -85,7 +85,7 @@ void lolwutCommand(client *c) {
|
||||
}
|
||||
|
||||
/* ========================== LOLWUT Canvase ===============================
|
||||
* Many LOWUT versions will likely print some computer art to the screen.
|
||||
* Many LOLWUT versions will likely print some computer art to the screen.
|
||||
* This is the case with LOLWUT 5 and LOLWUT 6, so here there is a generic
|
||||
* canvas implementation that can be reused. */
|
||||
|
||||
@@ -94,8 +94,8 @@ lwCanvas *lwCreateCanvas(int width, int height, int bgcolor) {
|
||||
lwCanvas *canvas = zmalloc(sizeof(*canvas));
|
||||
canvas->width = width;
|
||||
canvas->height = height;
|
||||
canvas->pixels = zmalloc(width*height);
|
||||
memset(canvas->pixels,bgcolor,width*height);
|
||||
canvas->pixels = zmalloc((size_t)width*height);
|
||||
memset(canvas->pixels,bgcolor,(size_t)width*height);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ void lwFreeCanvas(lwCanvas *canvas) {
|
||||
}
|
||||
|
||||
/* Set a pixel to the specified color. Color is 0 or 1, where zero means no
|
||||
* dot will be displyed, and 1 means dot will be displayed.
|
||||
* dot will be displayed, and 1 means dot will be displayed.
|
||||
* Coordinates are arranged so that left-top corner is 0,0. You can write
|
||||
* out of the size of the canvas without issues. */
|
||||
void lwDrawPixel(lwCanvas *canvas, int x, int y, int color) {
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
/* This represents a very simple generic canvas in order to draw stuff.
|
||||
* It's up to each LOLWUT versions to translate what they draw to the
|
||||
* screen, depending on the result to accomplish. */
|
||||
|
||||
#ifndef __LOLWUT_H
|
||||
#define __LOLWUT_H
|
||||
|
||||
typedef struct lwCanvas {
|
||||
int width;
|
||||
int height;
|
||||
@@ -47,3 +51,5 @@ void lwDrawPixel(lwCanvas *canvas, int x, int y, int color);
|
||||
int lwGetPixel(lwCanvas *canvas, int x, int y);
|
||||
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color);
|
||||
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color);
|
||||
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@ void lolwut5Command(client *c) {
|
||||
return;
|
||||
|
||||
/* Limits. We want LOLWUT to be always reasonably fast and cheap to execute
|
||||
* so we have maximum number of columns, rows, and output resulution. */
|
||||
* so we have maximum number of columns, rows, and output resolution. */
|
||||
if (cols < 1) cols = 1;
|
||||
if (cols > 1000) cols = 1000;
|
||||
if (squares_per_row < 1) squares_per_row = 1;
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@
|
||||
|
||||
/*
|
||||
* Whether to store pointers or offsets inside the hash table. On
|
||||
* 64 bit architetcures, pointers take up twice as much space,
|
||||
* 64 bit architectures, pointers take up twice as much space,
|
||||
* and might also be slower. Default is to autodetect.
|
||||
*/
|
||||
/*#define LZF_USER_OFFSETS autodetect */
|
||||
|
||||
+6
-1
@@ -71,7 +71,7 @@ void memtest_progress_start(char *title, int pass) {
|
||||
printf("\x1b[H\x1b[2K"); /* Cursor home, clear current line. */
|
||||
printf("%s [%d]\n", title, pass); /* Print title. */
|
||||
progress_printed = 0;
|
||||
progress_full = ws.ws_col*(ws.ws_row-3);
|
||||
progress_full = (size_t)ws.ws_col*(ws.ws_row-3);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
@@ -347,10 +347,15 @@ void memtest_alloc_and_test(size_t megabytes, int passes) {
|
||||
}
|
||||
|
||||
void memtest(size_t megabytes, int passes) {
|
||||
#if !defined(__HAIKU__)
|
||||
if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
|
||||
ws.ws_col = 80;
|
||||
ws.ws_row = 20;
|
||||
}
|
||||
#else
|
||||
ws.ws_col = 80;
|
||||
ws.ws_row = 20;
|
||||
#endif
|
||||
memtest_alloc_and_test(megabytes,passes);
|
||||
printf("\nYour memory passed this test.\n");
|
||||
printf("Please if you are still in doubt use the following two tools:\n");
|
||||
|
||||
+652
-141
File diff suppressed because it is too large
Load Diff
@@ -125,7 +125,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
cmd_KEYRANGE,"readonly",1,1,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
/* Create our global dictionray. Here we'll set our keys and values. */
|
||||
/* Create our global dictionary. Here we'll set our keys and values. */
|
||||
Keyspace = RedisModule_CreateDict(NULL);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
/* Timer callback. */
|
||||
void timerHandler(RedisModuleCtx *ctx, void *data) {
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
printf("Fired %s!\n", data);
|
||||
printf("Fired %s!\n", (char *)data);
|
||||
RedisModule_Free(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ int HelloPushCall_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, in
|
||||
}
|
||||
|
||||
/* HELLO.PUSH.CALL2
|
||||
* This is exaxctly as HELLO.PUSH.CALL, but shows how we can reply to the
|
||||
* This is exactly as HELLO.PUSH.CALL, but shows how we can reply to the
|
||||
* client using directly a reply object that Call() returned. */
|
||||
int HelloPushCall2_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
@@ -345,7 +345,7 @@ int HelloToggleCase_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
|
||||
|
||||
/* HELLO.MORE.EXPIRE key milliseconds.
|
||||
*
|
||||
* If they key has already an associated TTL, extends it by "milliseconds"
|
||||
* If the key has already an associated TTL, extends it by "milliseconds"
|
||||
* milliseconds. Otherwise no operation is performed. */
|
||||
int HelloMoreExpire_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
A C-program for MT19937-64 (2004/9/29 version).
|
||||
Coded by Takuji Nishimura and Makoto Matsumoto.
|
||||
|
||||
This is a 64-bit version of Mersenne Twister pseudorandom number
|
||||
generator.
|
||||
|
||||
Before using, initialize the state by using init_genrand64(seed)
|
||||
or init_by_array64(init_key, key_length).
|
||||
|
||||
Copyright (C) 2004, Makoto Matsumoto and Takuji Nishimura,
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
3. The names of its contributors may not 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.
|
||||
|
||||
References:
|
||||
T. Nishimura, ``Tables of 64-bit Mersenne Twisters''
|
||||
ACM Transactions on Modeling and
|
||||
Computer Simulation 10. (2000) 348--357.
|
||||
M. Matsumoto and T. Nishimura,
|
||||
``Mersenne Twister: a 623-dimensionally equidistributed
|
||||
uniform pseudorandom number generator''
|
||||
ACM Transactions on Modeling and
|
||||
Computer Simulation 8. (Jan. 1998) 3--30.
|
||||
|
||||
Any feedback is very welcome.
|
||||
http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html
|
||||
email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces)
|
||||
*/
|
||||
|
||||
|
||||
#include "mt19937-64.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#define NN 312
|
||||
#define MM 156
|
||||
#define MATRIX_A 0xB5026F5AA96619E9ULL
|
||||
#define UM 0xFFFFFFFF80000000ULL /* Most significant 33 bits */
|
||||
#define LM 0x7FFFFFFFULL /* Least significant 31 bits */
|
||||
|
||||
|
||||
/* The array for the state vector */
|
||||
static unsigned long long mt[NN];
|
||||
/* mti==NN+1 means mt[NN] is not initialized */
|
||||
static int mti=NN+1;
|
||||
|
||||
/* initializes mt[NN] with a seed */
|
||||
void init_genrand64(unsigned long long seed)
|
||||
{
|
||||
mt[0] = seed;
|
||||
for (mti=1; mti<NN; mti++)
|
||||
mt[mti] = (6364136223846793005ULL * (mt[mti-1] ^ (mt[mti-1] >> 62)) + mti);
|
||||
}
|
||||
|
||||
/* initialize by an array with array-length */
|
||||
/* init_key is the array for initializing keys */
|
||||
/* key_length is its length */
|
||||
void init_by_array64(unsigned long long init_key[],
|
||||
unsigned long long key_length)
|
||||
{
|
||||
unsigned long long i, j, k;
|
||||
init_genrand64(19650218ULL);
|
||||
i=1; j=0;
|
||||
k = (NN>key_length ? NN : key_length);
|
||||
for (; k; k--) {
|
||||
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 62)) * 3935559000370003845ULL))
|
||||
+ init_key[j] + j; /* non linear */
|
||||
i++; j++;
|
||||
if (i>=NN) { mt[0] = mt[NN-1]; i=1; }
|
||||
if (j>=key_length) j=0;
|
||||
}
|
||||
for (k=NN-1; k; k--) {
|
||||
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 62)) * 2862933555777941757ULL))
|
||||
- i; /* non linear */
|
||||
i++;
|
||||
if (i>=NN) { mt[0] = mt[NN-1]; i=1; }
|
||||
}
|
||||
|
||||
mt[0] = 1ULL << 63; /* MSB is 1; assuring non-zero initial array */
|
||||
}
|
||||
|
||||
/* generates a random number on [0, 2^64-1]-interval */
|
||||
unsigned long long genrand64_int64(void)
|
||||
{
|
||||
int i;
|
||||
unsigned long long x;
|
||||
static unsigned long long mag01[2]={0ULL, MATRIX_A};
|
||||
|
||||
if (mti >= NN) { /* generate NN words at one time */
|
||||
|
||||
/* if init_genrand64() has not been called, */
|
||||
/* a default initial seed is used */
|
||||
if (mti == NN+1)
|
||||
init_genrand64(5489ULL);
|
||||
|
||||
for (i=0;i<NN-MM;i++) {
|
||||
x = (mt[i]&UM)|(mt[i+1]&LM);
|
||||
mt[i] = mt[i+MM] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
|
||||
}
|
||||
for (;i<NN-1;i++) {
|
||||
x = (mt[i]&UM)|(mt[i+1]&LM);
|
||||
mt[i] = mt[i+(MM-NN)] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
|
||||
}
|
||||
x = (mt[NN-1]&UM)|(mt[0]&LM);
|
||||
mt[NN-1] = mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
|
||||
|
||||
mti = 0;
|
||||
}
|
||||
|
||||
x = mt[mti++];
|
||||
|
||||
x ^= (x >> 29) & 0x5555555555555555ULL;
|
||||
x ^= (x << 17) & 0x71D67FFFEDA60000ULL;
|
||||
x ^= (x << 37) & 0xFFF7EEE000000000ULL;
|
||||
x ^= (x >> 43);
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
/* generates a random number on [0, 2^63-1]-interval */
|
||||
long long genrand64_int63(void)
|
||||
{
|
||||
return (long long)(genrand64_int64() >> 1);
|
||||
}
|
||||
|
||||
/* generates a random number on [0,1]-real-interval */
|
||||
double genrand64_real1(void)
|
||||
{
|
||||
return (genrand64_int64() >> 11) * (1.0/9007199254740991.0);
|
||||
}
|
||||
|
||||
/* generates a random number on [0,1)-real-interval */
|
||||
double genrand64_real2(void)
|
||||
{
|
||||
return (genrand64_int64() >> 11) * (1.0/9007199254740992.0);
|
||||
}
|
||||
|
||||
/* generates a random number on (0,1)-real-interval */
|
||||
double genrand64_real3(void)
|
||||
{
|
||||
return ((genrand64_int64() >> 12) + 0.5) * (1.0/4503599627370496.0);
|
||||
}
|
||||
|
||||
#ifdef MT19937_64_MAIN
|
||||
int main(void)
|
||||
{
|
||||
int i;
|
||||
unsigned long long init[4]={0x12345ULL, 0x23456ULL, 0x34567ULL, 0x45678ULL}, length=4;
|
||||
init_by_array64(init, length);
|
||||
printf("1000 outputs of genrand64_int64()\n");
|
||||
for (i=0; i<1000; i++) {
|
||||
printf("%20llu ", genrand64_int64());
|
||||
if (i%5==4) printf("\n");
|
||||
}
|
||||
printf("\n1000 outputs of genrand64_real2()\n");
|
||||
for (i=0; i<1000; i++) {
|
||||
printf("%10.8f ", genrand64_real2());
|
||||
if (i%5==4) printf("\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
A C-program for MT19937-64 (2004/9/29 version).
|
||||
Coded by Takuji Nishimura and Makoto Matsumoto.
|
||||
|
||||
This is a 64-bit version of Mersenne Twister pseudorandom number
|
||||
generator.
|
||||
|
||||
Before using, initialize the state by using init_genrand64(seed)
|
||||
or init_by_array64(init_key, key_length).
|
||||
|
||||
Copyright (C) 2004, Makoto Matsumoto and Takuji Nishimura,
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
3. The names of its contributors may not 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.
|
||||
|
||||
References:
|
||||
T. Nishimura, ``Tables of 64-bit Mersenne Twisters''
|
||||
ACM Transactions on Modeling and
|
||||
Computer Simulation 10. (2000) 348--357.
|
||||
M. Matsumoto and T. Nishimura,
|
||||
``Mersenne Twister: a 623-dimensionally equidistributed
|
||||
uniform pseudorandom number generator''
|
||||
ACM Transactions on Modeling and
|
||||
Computer Simulation 8. (Jan. 1998) 3--30.
|
||||
|
||||
Any feedback is very welcome.
|
||||
http://www.math.hiroshima-u.ac.jp/~m-mat/MT/emt.html
|
||||
email: m-mat @ math.sci.hiroshima-u.ac.jp (remove spaces)
|
||||
*/
|
||||
|
||||
#ifndef __MT19937_64_H
|
||||
#define __MT19937_64_H
|
||||
|
||||
/* initializes mt[NN] with a seed */
|
||||
void init_genrand64(unsigned long long seed);
|
||||
|
||||
/* initialize by an array with array-length */
|
||||
/* init_key is the array for initializing keys */
|
||||
/* key_length is its length */
|
||||
void init_by_array64(unsigned long long init_key[],
|
||||
unsigned long long key_length);
|
||||
|
||||
/* generates a random number on [0, 2^64-1]-interval */
|
||||
unsigned long long genrand64_int64(void);
|
||||
|
||||
|
||||
/* generates a random number on [0, 2^63-1]-interval */
|
||||
long long genrand64_int63(void);
|
||||
|
||||
/* generates a random number on [0,1]-real-interval */
|
||||
double genrand64_real1(void);
|
||||
|
||||
/* generates a random number on [0,1)-real-interval */
|
||||
double genrand64_real2(void);
|
||||
|
||||
/* generates a random number on (0,1)-real-interval */
|
||||
double genrand64_real3(void);
|
||||
|
||||
/* generates a random number on (0,1]-real-interval */
|
||||
double genrand64_real4(void);
|
||||
|
||||
#endif
|
||||
+80
-37
@@ -36,6 +36,7 @@ void initClientMultiState(client *c) {
|
||||
c->mstate.commands = NULL;
|
||||
c->mstate.count = 0;
|
||||
c->mstate.cmd_flags = 0;
|
||||
c->mstate.cmd_inv_flags = 0;
|
||||
}
|
||||
|
||||
/* Release all the resources associated with MULTI/EXEC state */
|
||||
@@ -58,6 +59,13 @@ void queueMultiCommand(client *c) {
|
||||
multiCmd *mc;
|
||||
int j;
|
||||
|
||||
/* No sense to waste memory if the transaction is already aborted.
|
||||
* this is useful in case client sends these in a pipeline, or doesn't
|
||||
* bother to read previous responses and didn't notice the multi was already
|
||||
* aborted. */
|
||||
if (c->flags & CLIENT_DIRTY_EXEC)
|
||||
return;
|
||||
|
||||
c->mstate.commands = zrealloc(c->mstate.commands,
|
||||
sizeof(multiCmd)*(c->mstate.count+1));
|
||||
mc = c->mstate.commands+c->mstate.count;
|
||||
@@ -69,6 +77,7 @@ void queueMultiCommand(client *c) {
|
||||
incrRefCount(mc->argv[j]);
|
||||
c->mstate.count++;
|
||||
c->mstate.cmd_flags |= c->cmd->flags;
|
||||
c->mstate.cmd_inv_flags |= ~c->cmd->flags;
|
||||
}
|
||||
|
||||
void discardTransaction(client *c) {
|
||||
@@ -78,7 +87,7 @@ void discardTransaction(client *c) {
|
||||
unwatchAllKeys(c);
|
||||
}
|
||||
|
||||
/* Flag the transacation as DIRTY_EXEC so that EXEC will fail.
|
||||
/* Flag the transaction as DIRTY_EXEC so that EXEC will fail.
|
||||
* Should be called every time there is an error while queueing a command. */
|
||||
void flagTransaction(client *c) {
|
||||
if (c->flags & CLIENT_MULTI)
|
||||
@@ -115,6 +124,24 @@ void execCommandPropagateExec(client *c) {
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
}
|
||||
|
||||
/* Aborts a transaction, with a specific error message.
|
||||
* The transaction is always aboarted with -EXECABORT so that the client knows
|
||||
* the server exited the multi state, but the actual reason for the abort is
|
||||
* included too.
|
||||
* Note: 'error' may or may not end with \r\n. see addReplyErrorFormat. */
|
||||
void execCommandAbort(client *c, sds error) {
|
||||
discardTransaction(c);
|
||||
|
||||
if (error[0] == '-') error++;
|
||||
addReplyErrorFormat(c, "-EXECABORT Transaction discarded because of: %s", error);
|
||||
|
||||
/* Send EXEC to clients waiting data from MONITOR. We did send a MULTI
|
||||
* already, and didn't send any of the queued commands, now we'll just send
|
||||
* EXEC so it is clear that the transaction is over. */
|
||||
if (listLength(server.monitors) && !server.loading)
|
||||
replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
|
||||
}
|
||||
|
||||
void execCommand(client *c) {
|
||||
int j;
|
||||
robj **orig_argv;
|
||||
@@ -128,6 +155,11 @@ void execCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* EXEC with expired watched key is disallowed*/
|
||||
if (isWatchedKeyExpired(c)) {
|
||||
c->flags |= (CLIENT_DIRTY_CAS);
|
||||
}
|
||||
|
||||
/* Check if we need to abort the EXEC because:
|
||||
* 1) Some WATCHed key was touched.
|
||||
* 2) There was a previous error while queueing commands.
|
||||
@@ -141,21 +173,6 @@ void execCommand(client *c) {
|
||||
goto handle_monitor;
|
||||
}
|
||||
|
||||
/* If there are write commands inside the transaction, and this is a read
|
||||
* only slave, we want to send an error. This happens when the transaction
|
||||
* was initiated when the instance was a master or a writable replica and
|
||||
* then the configuration changed (for example instance was turned into
|
||||
* a replica). */
|
||||
if (!server.loading && server.masterhost && server.repl_slave_ro &&
|
||||
!(c->flags & CLIENT_MASTER) && c->mstate.cmd_flags & CMD_WRITE)
|
||||
{
|
||||
addReplyError(c,
|
||||
"Transaction contains write commands but instance "
|
||||
"is now a read-only replica. EXEC aborted.");
|
||||
discardTransaction(c);
|
||||
goto handle_monitor;
|
||||
}
|
||||
|
||||
/* Exec all the queued commands */
|
||||
unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
|
||||
orig_argv = c->argv;
|
||||
@@ -172,13 +189,18 @@ void execCommand(client *c) {
|
||||
* This way we'll deliver the MULTI/..../EXEC block as a whole and
|
||||
* both the AOF and the replication link will have the same consistency
|
||||
* and atomicity guarantees. */
|
||||
if (!must_propagate && !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN))) {
|
||||
if (!must_propagate &&
|
||||
!server.loading &&
|
||||
!(c->cmd->flags & (CMD_READONLY|CMD_ADMIN)))
|
||||
{
|
||||
execCommandPropagateMulti(c);
|
||||
must_propagate = 1;
|
||||
}
|
||||
|
||||
int acl_retval = ACLCheckCommandPerm(c);
|
||||
int acl_keypos;
|
||||
int acl_retval = ACLCheckCommandPerm(c,&acl_keypos);
|
||||
if (acl_retval != ACL_OK) {
|
||||
addACLLogEntry(c,acl_retval,acl_keypos,NULL);
|
||||
addReplyErrorFormat(c,
|
||||
"-NOPERM ACLs rules changed between the moment the "
|
||||
"transaction was accumulated and the EXEC call. "
|
||||
@@ -302,6 +324,22 @@ void unwatchAllKeys(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* iterates over the watched_keys list and
|
||||
* look for an expired key . */
|
||||
int isWatchedKeyExpired(client *c) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
watchedKey *wk;
|
||||
if (listLength(c->watched_keys) == 0) return 0;
|
||||
listRewind(c->watched_keys,&li);
|
||||
while ((ln = listNext(&li))) {
|
||||
wk = listNodeValue(ln);
|
||||
if (keyIsExpired(wk->db, wk->key)) return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* "Touch" a key, so that if this key is being WATCHed by some client the
|
||||
* next EXEC will fail. */
|
||||
void touchWatchedKey(redisDb *db, robj *key) {
|
||||
@@ -323,31 +361,36 @@ void touchWatchedKey(redisDb *db, robj *key) {
|
||||
}
|
||||
}
|
||||
|
||||
/* On FLUSHDB or FLUSHALL all the watched keys that are present before the
|
||||
* flush but will be deleted as effect of the flushing operation should
|
||||
* be touched. "dbid" is the DB that's getting the flush. -1 if it is
|
||||
* a FLUSHALL operation (all the DBs flushed). */
|
||||
void touchWatchedKeysOnFlush(int dbid) {
|
||||
listIter li1, li2;
|
||||
/* Set CLIENT_DIRTY_CAS to all clients of DB when DB is dirty.
|
||||
* It may happen in the following situations:
|
||||
* FLUSHDB, FLUSHALL, SWAPDB
|
||||
*
|
||||
* replaced_with: for SWAPDB, the WATCH should be invalidated if
|
||||
* the key exists in either of them, and skipped only if it
|
||||
* doesn't exist in both. */
|
||||
void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
dictEntry *de;
|
||||
|
||||
/* For every client, check all the waited keys */
|
||||
listRewind(server.clients,&li1);
|
||||
while((ln = listNext(&li1))) {
|
||||
client *c = listNodeValue(ln);
|
||||
listRewind(c->watched_keys,&li2);
|
||||
while((ln = listNext(&li2))) {
|
||||
watchedKey *wk = listNodeValue(ln);
|
||||
if (dictSize(emptied->watched_keys) == 0) return;
|
||||
|
||||
/* For every watched key matching the specified DB, if the
|
||||
* key exists, mark the client as dirty, as the key will be
|
||||
* removed. */
|
||||
if (dbid == -1 || wk->db->id == dbid) {
|
||||
if (dictFind(wk->db->dict, wk->key->ptr) != NULL)
|
||||
c->flags |= CLIENT_DIRTY_CAS;
|
||||
dictIterator *di = dictGetSafeIterator(emptied->watched_keys);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *key = dictGetKey(de);
|
||||
list *clients = dictGetVal(de);
|
||||
if (!clients) continue;
|
||||
listRewind(clients,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *c = listNodeValue(ln);
|
||||
if (dictFind(emptied->dict, key->ptr)) {
|
||||
c->flags |= CLIENT_DIRTY_CAS;
|
||||
} else if (replaced_with && dictFind(replaced_with->dict, key->ptr)) {
|
||||
c->flags |= CLIENT_DIRTY_CAS;
|
||||
}
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
void watchCommand(client *c) {
|
||||
|
||||
+688
-199
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -62,7 +62,7 @@ int keyspaceEventsStringToFlags(char *classes) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
/* This function does exactly the revese of the function above: it gets
|
||||
/* This function does exactly the reverse of the function above: it gets
|
||||
* as input an integer with the xored flags and returns a string representing
|
||||
* the selected classes. The string returned is an sds string that needs to
|
||||
* be released with sdsfree(). */
|
||||
@@ -82,10 +82,10 @@ sds keyspaceEventsFlagsToString(int flags) {
|
||||
if (flags & NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
|
||||
if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
|
||||
if (flags & NOTIFY_STREAM) res = sdscatlen(res,"t",1);
|
||||
if (flags & NOTIFY_KEY_MISS) res = sdscatlen(res,"m",1);
|
||||
}
|
||||
if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
|
||||
if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
|
||||
if (flags & NOTIFY_KEY_MISS) res = sdscatlen(res,"m",1);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
+32
-58
@@ -126,7 +126,7 @@ robj *createStringObject(const char *ptr, size_t len) {
|
||||
/* Create a string object from a long long value. When possible returns a
|
||||
* shared integer object, or at least an integer encoded one.
|
||||
*
|
||||
* If valueobj is non zero, the function avoids returning a a shared
|
||||
* If valueobj is non zero, the function avoids returning a shared
|
||||
* integer, because the object is going to be used as value in the Redis key
|
||||
* space (for instance when the INCR command is used), so we want LFU/LRU
|
||||
* values specific for each key. */
|
||||
@@ -347,7 +347,15 @@ void freeStreamObject(robj *o) {
|
||||
}
|
||||
|
||||
void incrRefCount(robj *o) {
|
||||
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount++;
|
||||
if (o->refcount < OBJ_FIRST_SPECIAL_REFCOUNT) {
|
||||
o->refcount++;
|
||||
} else {
|
||||
if (o->refcount == OBJ_SHARED_REFCOUNT) {
|
||||
/* Nothing to do: this refcount is immutable. */
|
||||
} else if (o->refcount == OBJ_STATIC_REFCOUNT) {
|
||||
serverPanic("You tried to retain an object allocated in the stack");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decrRefCount(robj *o) {
|
||||
@@ -640,21 +648,13 @@ int getDoubleFromObjectOrReply(client *c, robj *o, double *target, const char *m
|
||||
|
||||
int getLongDoubleFromObject(robj *o, long double *target) {
|
||||
long double value;
|
||||
char *eptr;
|
||||
|
||||
if (o == NULL) {
|
||||
value = 0;
|
||||
} else {
|
||||
serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
|
||||
if (sdsEncodedObject(o)) {
|
||||
errno = 0;
|
||||
value = strtold(o->ptr, &eptr);
|
||||
if (sdslen(o->ptr) == 0 ||
|
||||
isspace(((const char*)o->ptr)[0]) ||
|
||||
(size_t)(eptr-(char*)o->ptr) != sdslen(o->ptr) ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
isnan(value))
|
||||
if (!string2ld(o->ptr, sdslen(o->ptr), &value))
|
||||
return C_ERR;
|
||||
} else if (o->encoding == OBJ_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
@@ -739,6 +739,7 @@ char *strEncoding(int encoding) {
|
||||
case OBJ_ENCODING_INTSET: return "intset";
|
||||
case OBJ_ENCODING_SKIPLIST: return "skiplist";
|
||||
case OBJ_ENCODING_EMBSTR: return "embstr";
|
||||
case OBJ_ENCODING_STREAM: return "stream";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
@@ -785,7 +786,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
if(o->encoding == OBJ_ENCODING_INT) {
|
||||
asize = sizeof(*o);
|
||||
} else if(o->encoding == OBJ_ENCODING_RAW) {
|
||||
asize = sdsAllocSize(o->ptr)+sizeof(*o);
|
||||
asize = sdsZmallocSize(o->ptr)+sizeof(*o);
|
||||
} else if(o->encoding == OBJ_ENCODING_EMBSTR) {
|
||||
asize = sdslen(o->ptr)+2+sizeof(*o);
|
||||
} else {
|
||||
@@ -813,14 +814,14 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
asize = sizeof(*o)+sizeof(dict)+(sizeof(struct dictEntry*)*dictSlots(d));
|
||||
while((de = dictNext(di)) != NULL && samples < sample_size) {
|
||||
ele = dictGetKey(de);
|
||||
elesize += sizeof(struct dictEntry) + sdsAllocSize(ele);
|
||||
elesize += sizeof(struct dictEntry) + sdsZmallocSize(ele);
|
||||
samples++;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
if (samples) asize += (double)elesize/samples*dictSize(d);
|
||||
} else if (o->encoding == OBJ_ENCODING_INTSET) {
|
||||
intset *is = o->ptr;
|
||||
asize = sizeof(*o)+sizeof(*is)+is->encoding*is->length;
|
||||
asize = sizeof(*o)+sizeof(*is)+(size_t)is->encoding*is->length;
|
||||
} else {
|
||||
serverPanic("Unknown set encoding");
|
||||
}
|
||||
@@ -835,7 +836,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
(sizeof(struct dictEntry*)*dictSlots(d))+
|
||||
zmalloc_size(zsl->header);
|
||||
while(znode != NULL && samples < sample_size) {
|
||||
elesize += sdsAllocSize(znode->ele);
|
||||
elesize += sdsZmallocSize(znode->ele);
|
||||
elesize += sizeof(struct dictEntry) + zmalloc_size(znode);
|
||||
samples++;
|
||||
znode = znode->level[0].forward;
|
||||
@@ -854,7 +855,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
while((de = dictNext(di)) != NULL && samples < sample_size) {
|
||||
ele = dictGetKey(de);
|
||||
ele2 = dictGetVal(de);
|
||||
elesize += sdsAllocSize(ele) + sdsAllocSize(ele2);
|
||||
elesize += sdsZmallocSize(ele) + sdsZmallocSize(ele2);
|
||||
elesize += sizeof(struct dictEntry);
|
||||
samples++;
|
||||
}
|
||||
@@ -982,43 +983,19 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
mh->repl_backlog = mem;
|
||||
mem_total += mem;
|
||||
|
||||
mem = 0;
|
||||
if (listLength(server.slaves)) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *c = listNodeValue(ln);
|
||||
mem += getClientOutputBufferMemoryUsage(c);
|
||||
mem += sdsAllocSize(c->querybuf);
|
||||
mem += sizeof(client);
|
||||
}
|
||||
}
|
||||
mh->clients_slaves = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
mem = 0;
|
||||
if (listLength(server.clients)) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.clients,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *c = listNodeValue(ln);
|
||||
if (c->flags & CLIENT_SLAVE && !(c->flags & CLIENT_MONITOR))
|
||||
continue;
|
||||
mem += getClientOutputBufferMemoryUsage(c);
|
||||
mem += sdsAllocSize(c->querybuf);
|
||||
mem += sizeof(client);
|
||||
}
|
||||
}
|
||||
mh->clients_normal = mem;
|
||||
mem_total+=mem;
|
||||
/* Computing the memory used by the clients would be O(N) if done
|
||||
* here online. We use our values computed incrementally by
|
||||
* clientsCronTrackClientsMemUsage(). */
|
||||
mh->clients_slaves = server.stat_clients_type_memory[CLIENT_TYPE_SLAVE];
|
||||
mh->clients_normal = server.stat_clients_type_memory[CLIENT_TYPE_MASTER]+
|
||||
server.stat_clients_type_memory[CLIENT_TYPE_PUBSUB]+
|
||||
server.stat_clients_type_memory[CLIENT_TYPE_NORMAL];
|
||||
mem_total += mh->clients_slaves;
|
||||
mem_total += mh->clients_normal;
|
||||
|
||||
mem = 0;
|
||||
if (server.aof_state != AOF_OFF) {
|
||||
mem += sdsalloc(server.aof_buf);
|
||||
mem += sdsZmallocSize(server.aof_buf);
|
||||
mem += aofRewriteBufferSize();
|
||||
}
|
||||
mh->aof_buffer = mem;
|
||||
@@ -1119,13 +1096,13 @@ sds getMemoryDoctorReport(void) {
|
||||
num_reports++;
|
||||
}
|
||||
|
||||
/* Allocator fss is higher than 1.1 and 10MB ? */
|
||||
/* Allocator rss is higher than 1.1 and 10MB ? */
|
||||
if (mh->allocator_rss > 1.1 && mh->allocator_rss_bytes > 10<<20) {
|
||||
high_alloc_rss = 1;
|
||||
num_reports++;
|
||||
}
|
||||
|
||||
/* Non-Allocator fss is higher than 1.1 and 10MB ? */
|
||||
/* Non-Allocator rss is higher than 1.1 and 10MB ? */
|
||||
if (mh->rss_extra > 1.1 && mh->rss_extra_bytes > 10<<20) {
|
||||
high_proc_rss = 1;
|
||||
num_reports++;
|
||||
@@ -1234,10 +1211,7 @@ int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
/* This is a helper function for the OBJECT command. We need to lookup keys
|
||||
* without any modification of LRU or other parameters. */
|
||||
robj *objectCommandLookup(client *c, robj *key) {
|
||||
dictEntry *de;
|
||||
|
||||
if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL;
|
||||
return (robj*) dictGetVal(de);
|
||||
return lookupKeyReadWithFlags(c->db,key,LOOKUP_NOTOUCH|LOOKUP_NONOTIFY);
|
||||
}
|
||||
|
||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
@@ -1247,7 +1221,7 @@ robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Object command allows to inspect the internals of an Redis Object.
|
||||
/* Object command allows to inspect the internals of a Redis Object.
|
||||
* Usage: OBJECT <refcount|encoding|idletime|freq> <key> */
|
||||
void objectCommand(client *c) {
|
||||
robj *o;
|
||||
@@ -1334,7 +1308,7 @@ NULL
|
||||
return;
|
||||
}
|
||||
size_t usage = objectComputeSize(dictGetVal(de),samples);
|
||||
usage += sdsAllocSize(dictGetKey(de));
|
||||
usage += sdsZmallocSize(dictGetKey(de));
|
||||
usage += sizeof(dictEntry);
|
||||
addReplyLongLong(c,usage);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"stats") && c->argc == 2) {
|
||||
|
||||
+65
-13
@@ -35,21 +35,30 @@ int clientSubscriptionsCount(client *c);
|
||||
* Pubsub client replies API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Send a pubsub message of type "message" to the client. */
|
||||
/* Send a pubsub message of type "message" to the client.
|
||||
* Normally 'msg' is a Redis object containing the string to send as
|
||||
* message. However if the caller sets 'msg' as NULL, it will be able
|
||||
* to send a special message (for instance an Array type) by using the
|
||||
* addReply*() API family. */
|
||||
void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.messagebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,msg);
|
||||
if (msg) addReplyBulk(c,msg);
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send a pubsub message of type "pmessage" to the client. The difference
|
||||
* with the "message" type delivered by addReplyPubsubMessage() is that
|
||||
* this message format also includes the pattern that matched the message. */
|
||||
void addReplyPubsubPatMessage(client *c, robj *pat, robj *channel, robj *msg) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[4]);
|
||||
else
|
||||
@@ -58,10 +67,13 @@ void addReplyPubsubPatMessage(client *c, robj *pat, robj *channel, robj *msg) {
|
||||
addReplyBulk(c,pat);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,msg);
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send the pubsub subscription notification to the client. */
|
||||
void addReplyPubsubSubscribed(client *c, robj *channel) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -69,6 +81,7 @@ void addReplyPubsubSubscribed(client *c, robj *channel) {
|
||||
addReply(c,shared.subscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send the pubsub unsubscription notification to the client.
|
||||
@@ -76,6 +89,8 @@ void addReplyPubsubSubscribed(client *c, robj *channel) {
|
||||
* unsubscribe command but there are no channels to unsubscribe from: we
|
||||
* still send a notification. */
|
||||
void addReplyPubsubUnsubscribed(client *c, robj *channel) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -86,10 +101,13 @@ void addReplyPubsubUnsubscribed(client *c, robj *channel) {
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send the pubsub pattern subscription notification to the client. */
|
||||
void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -97,6 +115,7 @@ void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
|
||||
addReply(c,shared.psubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/* Send the pubsub pattern unsubscription notification to the client.
|
||||
@@ -104,6 +123,8 @@ void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
|
||||
* punsubscribe command but there are no pattern to unsubscribe from: we
|
||||
* still send a notification. */
|
||||
void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
|
||||
uint64_t old_flags = c->flags;
|
||||
c->flags |= CLIENT_PUSHING;
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
@@ -114,6 +135,7 @@ void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
if (!(old_flags & CLIENT_PUSHING)) c->flags &= ~CLIENT_PUSHING;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -202,6 +224,8 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
|
||||
|
||||
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */
|
||||
int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
dictEntry *de;
|
||||
list *clients;
|
||||
int retval = 0;
|
||||
|
||||
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
|
||||
@@ -213,6 +237,16 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
pat->pattern = getDecodedObject(pattern);
|
||||
pat->client = c;
|
||||
listAddNodeTail(server.pubsub_patterns,pat);
|
||||
/* Add the client to the pattern -> list of clients hash table */
|
||||
de = dictFind(server.pubsub_patterns_dict,pattern);
|
||||
if (de == NULL) {
|
||||
clients = listCreate();
|
||||
dictAdd(server.pubsub_patterns_dict,pattern,clients);
|
||||
incrRefCount(pattern);
|
||||
} else {
|
||||
clients = dictGetVal(de);
|
||||
}
|
||||
listAddNodeTail(clients,c);
|
||||
}
|
||||
/* Notify the client */
|
||||
addReplyPubsubPatSubscribed(c,pattern);
|
||||
@@ -222,6 +256,8 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was not subscribed to the specified channel. */
|
||||
int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
dictEntry *de;
|
||||
list *clients;
|
||||
listNode *ln;
|
||||
pubsubPattern pat;
|
||||
int retval = 0;
|
||||
@@ -234,6 +270,18 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
pat.pattern = pattern;
|
||||
ln = listSearchKey(server.pubsub_patterns,&pat);
|
||||
listDelNode(server.pubsub_patterns,ln);
|
||||
/* Remove the client from the pattern -> clients list hash table */
|
||||
de = dictFind(server.pubsub_patterns_dict,pattern);
|
||||
serverAssertWithInfo(c,NULL,de != NULL);
|
||||
clients = dictGetVal(de);
|
||||
ln = listSearchKey(clients,c);
|
||||
serverAssertWithInfo(c,NULL,ln != NULL);
|
||||
listDelNode(clients,ln);
|
||||
if (listLength(clients) == 0) {
|
||||
/* Free the list and associated hash entry at all if this was
|
||||
* the latest client. */
|
||||
dictDelete(server.pubsub_patterns_dict,pattern);
|
||||
}
|
||||
}
|
||||
/* Notify the client */
|
||||
if (notify) addReplyPubsubPatUnsubscribed(c,pattern);
|
||||
@@ -280,6 +328,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) {
|
||||
int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
int receivers = 0;
|
||||
dictEntry *de;
|
||||
dictIterator *di;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
@@ -298,23 +347,26 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
}
|
||||
}
|
||||
/* Send to clients listening to matching channels */
|
||||
if (listLength(server.pubsub_patterns)) {
|
||||
listRewind(server.pubsub_patterns,&li);
|
||||
di = dictGetIterator(server.pubsub_patterns_dict);
|
||||
if (di) {
|
||||
channel = getDecodedObject(channel);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
pubsubPattern *pat = ln->value;
|
||||
|
||||
if (stringmatchlen((char*)pat->pattern->ptr,
|
||||
sdslen(pat->pattern->ptr),
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *pattern = dictGetKey(de);
|
||||
list *clients = dictGetVal(de);
|
||||
if (!stringmatchlen((char*)pattern->ptr,
|
||||
sdslen(pattern->ptr),
|
||||
(char*)channel->ptr,
|
||||
sdslen(channel->ptr),0))
|
||||
{
|
||||
addReplyPubsubPatMessage(pat->client,
|
||||
pat->pattern,channel,message);
|
||||
sdslen(channel->ptr),0)) continue;
|
||||
|
||||
listRewind(clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = listNodeValue(ln);
|
||||
addReplyPubsubPatMessage(c,pattern,channel,message);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
decrRefCount(channel);
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
return receivers;
|
||||
}
|
||||
|
||||
+163
-4
@@ -29,6 +29,7 @@
|
||||
*/
|
||||
|
||||
#include <string.h> /* for memcpy */
|
||||
#include "redisassert.h"
|
||||
#include "quicklist.h"
|
||||
#include "zmalloc.h"
|
||||
#include "ziplist.h"
|
||||
@@ -43,11 +44,16 @@
|
||||
#define REDIS_STATIC static
|
||||
#endif
|
||||
|
||||
/* Optimization levels for size-based filling */
|
||||
/* Optimization levels for size-based filling.
|
||||
* Note that the largest possible limit is 16k, so even if each record takes
|
||||
* just one byte, it still won't overflow the 16 bit count field. */
|
||||
static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};
|
||||
|
||||
/* Maximum size in bytes of any multi-element ziplist.
|
||||
* Larger values will live in their own isolated ziplists. */
|
||||
* Larger values will live in their own isolated ziplists.
|
||||
* This is used only if we're limited by record count. when we're limited by
|
||||
* size, the maximum limit is bigger, but still safe.
|
||||
* 8k is a recommended / default size limit */
|
||||
#define SIZE_SAFETY_LIMIT 8192
|
||||
|
||||
/* Minimum ziplist size in bytes for attempting compression. */
|
||||
@@ -70,6 +76,12 @@ static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};
|
||||
} while (0);
|
||||
#endif
|
||||
|
||||
/* Bookmarks forward declarations */
|
||||
#define QL_MAX_BM ((1 << QL_BM_BITS)-1)
|
||||
quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name);
|
||||
quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node);
|
||||
void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm);
|
||||
|
||||
/* Simple way to give quicklistEntry structs default values with one call. */
|
||||
#define initEntry(e) \
|
||||
do { \
|
||||
@@ -100,10 +112,11 @@ quicklist *quicklistCreate(void) {
|
||||
quicklist->count = 0;
|
||||
quicklist->compress = 0;
|
||||
quicklist->fill = -2;
|
||||
quicklist->bookmark_count = 0;
|
||||
return quicklist;
|
||||
}
|
||||
|
||||
#define COMPRESS_MAX (1 << 16)
|
||||
#define COMPRESS_MAX ((1 << QL_COMP_BITS)-1)
|
||||
void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
|
||||
if (compress > COMPRESS_MAX) {
|
||||
compress = COMPRESS_MAX;
|
||||
@@ -113,7 +126,7 @@ void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
|
||||
quicklist->compress = compress;
|
||||
}
|
||||
|
||||
#define FILL_MAX (1 << 15)
|
||||
#define FILL_MAX ((1 << (QL_FILL_BITS-1))-1)
|
||||
void quicklistSetFill(quicklist *quicklist, int fill) {
|
||||
if (fill > FILL_MAX) {
|
||||
fill = FILL_MAX;
|
||||
@@ -169,6 +182,7 @@ void quicklistRelease(quicklist *quicklist) {
|
||||
quicklist->len--;
|
||||
current = next;
|
||||
}
|
||||
quicklistBookmarksClear(quicklist);
|
||||
zfree(quicklist);
|
||||
}
|
||||
|
||||
@@ -441,6 +455,8 @@ REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node,
|
||||
unsigned int new_sz = node->sz + sz + ziplist_overhead;
|
||||
if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(new_sz, fill)))
|
||||
return 1;
|
||||
/* when we return 1 above we know that the limit is a size limit (which is
|
||||
* safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */
|
||||
else if (!sizeMeetsSafetyLimit(new_sz))
|
||||
return 0;
|
||||
else if ((int)node->count < fill)
|
||||
@@ -460,6 +476,8 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
|
||||
unsigned int merge_sz = a->sz + b->sz - 11;
|
||||
if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(merge_sz, fill)))
|
||||
return 1;
|
||||
/* when we return 1 above we know that the limit is a size limit (which is
|
||||
* safe, see comments next to optimization_level and SIZE_SAFETY_LIMIT) */
|
||||
else if (!sizeMeetsSafetyLimit(merge_sz))
|
||||
return 0;
|
||||
else if ((int)(a->count + b->count) <= fill)
|
||||
@@ -479,6 +497,7 @@ REDIS_STATIC int _quicklistNodeAllowMerge(const quicklistNode *a,
|
||||
* Returns 1 if new head created. */
|
||||
int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
|
||||
quicklistNode *orig_head = quicklist->head;
|
||||
assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
|
||||
if (likely(
|
||||
_quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
|
||||
quicklist->head->zl =
|
||||
@@ -502,6 +521,7 @@ int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
|
||||
* Returns 1 if new tail created. */
|
||||
int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
|
||||
quicklistNode *orig_tail = quicklist->tail;
|
||||
assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
|
||||
if (likely(
|
||||
_quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
|
||||
quicklist->tail->zl =
|
||||
@@ -578,6 +598,15 @@ quicklist *quicklistCreateFromZiplist(int fill, int compress,
|
||||
|
||||
REDIS_STATIC void __quicklistDelNode(quicklist *quicklist,
|
||||
quicklistNode *node) {
|
||||
/* Update the bookmark if any */
|
||||
quicklistBookmark *bm = _quicklistBookmarkFindByNode(quicklist, node);
|
||||
if (bm) {
|
||||
bm->node = node->next;
|
||||
/* if the bookmark was to the last node, delete it. */
|
||||
if (!bm->node)
|
||||
_quicklistBookmarkDelete(quicklist, bm);
|
||||
}
|
||||
|
||||
if (node->next)
|
||||
node->next->prev = node->prev;
|
||||
if (node->prev)
|
||||
@@ -835,6 +864,7 @@ REDIS_STATIC void _quicklistInsert(quicklist *quicklist, quicklistEntry *entry,
|
||||
int fill = quicklist->fill;
|
||||
quicklistNode *node = entry->node;
|
||||
quicklistNode *new_node = NULL;
|
||||
assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
|
||||
|
||||
if (!node) {
|
||||
/* we have no reference node, so let's create only node in the list */
|
||||
@@ -1410,6 +1440,87 @@ void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
|
||||
}
|
||||
}
|
||||
|
||||
/* Create or update a bookmark in the list which will be updated to the next node
|
||||
* automatically when the one referenced gets deleted.
|
||||
* Returns 1 on success (creation of new bookmark or override of an existing one).
|
||||
* Returns 0 on failure (reached the maximum supported number of bookmarks).
|
||||
* NOTE: use short simple names, so that string compare on find is quick.
|
||||
* NOTE: bookmakrk creation may re-allocate the quicklist, so the input pointer
|
||||
may change and it's the caller responsibilty to update the reference.
|
||||
*/
|
||||
int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node) {
|
||||
quicklist *ql = *ql_ref;
|
||||
if (ql->bookmark_count >= QL_MAX_BM)
|
||||
return 0;
|
||||
quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
|
||||
if (bm) {
|
||||
bm->node = node;
|
||||
return 1;
|
||||
}
|
||||
ql = zrealloc(ql, sizeof(quicklist) + (ql->bookmark_count+1) * sizeof(quicklistBookmark));
|
||||
*ql_ref = ql;
|
||||
ql->bookmarks[ql->bookmark_count].node = node;
|
||||
ql->bookmarks[ql->bookmark_count].name = zstrdup(name);
|
||||
ql->bookmark_count++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Find the quicklist node referenced by a named bookmark.
|
||||
* When the bookmarked node is deleted the bookmark is updated to the next node,
|
||||
* and if that's the last node, the bookmark is deleted (so find returns NULL). */
|
||||
quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name) {
|
||||
quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
|
||||
if (!bm) return NULL;
|
||||
return bm->node;
|
||||
}
|
||||
|
||||
/* Delete a named bookmark.
|
||||
* returns 0 if bookmark was not found, and 1 if deleted.
|
||||
* Note that the bookmark memory is not freed yet, and is kept for future use. */
|
||||
int quicklistBookmarkDelete(quicklist *ql, const char *name) {
|
||||
quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
|
||||
if (!bm)
|
||||
return 0;
|
||||
_quicklistBookmarkDelete(ql, bm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name) {
|
||||
unsigned i;
|
||||
for (i=0; i<ql->bookmark_count; i++) {
|
||||
if (!strcmp(ql->bookmarks[i].name, name)) {
|
||||
return &ql->bookmarks[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node) {
|
||||
unsigned i;
|
||||
for (i=0; i<ql->bookmark_count; i++) {
|
||||
if (ql->bookmarks[i].node == node) {
|
||||
return &ql->bookmarks[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm) {
|
||||
int index = bm - ql->bookmarks;
|
||||
zfree(bm->name);
|
||||
ql->bookmark_count--;
|
||||
memmove(bm, bm+1, (ql->bookmark_count - index)* sizeof(*bm));
|
||||
/* NOTE: We do not shrink (realloc) the quicklist yet (to avoid resonance,
|
||||
* it may be re-used later (a call to realloc may NOP). */
|
||||
}
|
||||
|
||||
void quicklistBookmarksClear(quicklist *ql) {
|
||||
while (ql->bookmark_count)
|
||||
zfree(ql->bookmarks[--ql->bookmark_count].name);
|
||||
/* NOTE: We do not shrink (realloc) the quick list. main use case for this
|
||||
* function is just before releasing the allocation. */
|
||||
}
|
||||
|
||||
/* The rest of this file is test cases and test helpers. */
|
||||
#ifdef REDIS_TEST
|
||||
#include <stdint.h>
|
||||
@@ -2641,6 +2752,54 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
printf("Compressions: %0.2f seconds.\n", (float)(stop - start) / 1000);
|
||||
printf("\n");
|
||||
|
||||
TEST("bookmark get updated to next item") {
|
||||
quicklist *ql = quicklistNew(1, 0);
|
||||
quicklistPushTail(ql, "1", 1);
|
||||
quicklistPushTail(ql, "2", 1);
|
||||
quicklistPushTail(ql, "3", 1);
|
||||
quicklistPushTail(ql, "4", 1);
|
||||
quicklistPushTail(ql, "5", 1);
|
||||
assert(ql->len==5);
|
||||
/* add two bookmarks, one pointing to the node before the last. */
|
||||
assert(quicklistBookmarkCreate(&ql, "_dummy", ql->head->next));
|
||||
assert(quicklistBookmarkCreate(&ql, "_test", ql->tail->prev));
|
||||
/* test that the bookmark returns the right node, delete it and see that the bookmark points to the last node */
|
||||
assert(quicklistBookmarkFind(ql, "_test") == ql->tail->prev);
|
||||
assert(quicklistDelRange(ql, -2, 1));
|
||||
assert(quicklistBookmarkFind(ql, "_test") == ql->tail);
|
||||
/* delete the last node, and see that the bookmark was deleted. */
|
||||
assert(quicklistDelRange(ql, -1, 1));
|
||||
assert(quicklistBookmarkFind(ql, "_test") == NULL);
|
||||
/* test that other bookmarks aren't affected */
|
||||
assert(quicklistBookmarkFind(ql, "_dummy") == ql->head->next);
|
||||
assert(quicklistBookmarkFind(ql, "_missing") == NULL);
|
||||
assert(ql->len==3);
|
||||
quicklistBookmarksClear(ql); /* for coverage */
|
||||
assert(quicklistBookmarkFind(ql, "_dummy") == NULL);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
TEST("bookmark limit") {
|
||||
int i;
|
||||
quicklist *ql = quicklistNew(1, 0);
|
||||
quicklistPushHead(ql, "1", 1);
|
||||
for (i=0; i<QL_MAX_BM; i++)
|
||||
assert(quicklistBookmarkCreate(&ql, genstr("",i), ql->head));
|
||||
/* when all bookmarks are used, creation fails */
|
||||
assert(!quicklistBookmarkCreate(&ql, "_test", ql->head));
|
||||
/* delete one and see that we can now create another */
|
||||
assert(quicklistBookmarkDelete(ql, "0"));
|
||||
assert(quicklistBookmarkCreate(&ql, "_test", ql->head));
|
||||
/* delete one and see that the rest survive */
|
||||
assert(quicklistBookmarkDelete(ql, "_test"));
|
||||
for (i=1; i<QL_MAX_BM; i++)
|
||||
assert(quicklistBookmarkFind(ql, genstr("",i)) == ql->head);
|
||||
/* make sure the deleted ones are indeed gone */
|
||||
assert(!quicklistBookmarkFind(ql, "0"));
|
||||
assert(!quicklistBookmarkFind(ql, "_test"));
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
if (!err)
|
||||
printf("ALL TESTS PASSED!\n");
|
||||
else
|
||||
|
||||
+45
-5
@@ -28,6 +28,8 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stdint.h> // for UINTPTR_MAX
|
||||
|
||||
#ifndef __QUICKLIST_H__
|
||||
#define __QUICKLIST_H__
|
||||
|
||||
@@ -38,7 +40,7 @@
|
||||
* 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.
|
||||
* recompress: 1 bit, bool, true if node is temporary decompressed for usage.
|
||||
* attempted_compress: 1 bit, boolean, used for verifying during testing.
|
||||
* extra: 10 bits, free for future use; pads out the remainder of 32 bits */
|
||||
typedef struct quicklistNode {
|
||||
@@ -64,19 +66,51 @@ typedef struct quicklistLZF {
|
||||
char compressed[];
|
||||
} quicklistLZF;
|
||||
|
||||
/* Bookmarks are padded with realloc at the end of of the quicklist struct.
|
||||
* They should only be used for very big lists if thousands of nodes were the
|
||||
* excess memory usage is negligible, and there's a real need to iterate on them
|
||||
* in portions.
|
||||
* When not used, they don't add any memory overhead, but when used and then
|
||||
* deleted, some overhead remains (to avoid resonance).
|
||||
* The number of bookmarks used should be kept to minimum since it also adds
|
||||
* overhead on node deletion (searching for a bookmark to update). */
|
||||
typedef struct quicklistBookmark {
|
||||
quicklistNode *node;
|
||||
char *name;
|
||||
} quicklistBookmark;
|
||||
|
||||
#if UINTPTR_MAX == 0xffffffff
|
||||
/* 32-bit */
|
||||
# define QL_FILL_BITS 14
|
||||
# define QL_COMP_BITS 14
|
||||
# define QL_BM_BITS 4
|
||||
#elif UINTPTR_MAX == 0xffffffffffffffff
|
||||
/* 64-bit */
|
||||
# define QL_FILL_BITS 16
|
||||
# define QL_COMP_BITS 16
|
||||
# define QL_BM_BITS 4 /* we can encode more, but we rather limit the user
|
||||
since they cause performance degradation. */
|
||||
#else
|
||||
# error unknown arch bits count
|
||||
#endif
|
||||
|
||||
/* quicklist is a 40 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
|
||||
* 'compress' is: 0 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. */
|
||||
* 'fill' is the user-requested (or default) fill factor.
|
||||
* 'bookmakrs are an optional feature that is used by realloc this struct,
|
||||
* so that they don't consume memory when not used. */
|
||||
typedef struct quicklist {
|
||||
quicklistNode *head;
|
||||
quicklistNode *tail;
|
||||
unsigned long count; /* total count of all entries in all ziplists */
|
||||
unsigned long 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 */
|
||||
int fill : QL_FILL_BITS; /* fill factor for individual nodes */
|
||||
unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
|
||||
unsigned int bookmark_count: QL_BM_BITS;
|
||||
quicklistBookmark bookmarks[];
|
||||
} quicklist;
|
||||
|
||||
typedef struct quicklistIter {
|
||||
@@ -158,6 +192,12 @@ unsigned long quicklistCount(const quicklist *ql);
|
||||
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
|
||||
size_t quicklistGetLzf(const quicklistNode *node, void **data);
|
||||
|
||||
/* bookmarks */
|
||||
int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node);
|
||||
int quicklistBookmarkDelete(quicklist *ql, const char *name);
|
||||
quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name);
|
||||
void quicklistBookmarksClear(quicklist *ql);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int quicklistTest(int argc, char *argv[]);
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* Rax -- A radix tree implementation.
|
||||
*
|
||||
* Copyright (c) 2017-2018, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Version 1.2 -- 7 February 2019
|
||||
*
|
||||
* Copyright (c) 2017-2019, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -485,8 +487,8 @@ static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode
|
||||
if (h->iscompr) j = 0; /* Compressed node only child is at index 0. */
|
||||
memcpy(&h,children+j,sizeof(h));
|
||||
parentlink = children+j;
|
||||
j = 0; /* If the new node is compressed and we do not
|
||||
iterate again (since i == l) set the split
|
||||
j = 0; /* If the new node is non compressed and we do not
|
||||
iterate again (since i == len) set the split
|
||||
position to 0 to signal this node represents
|
||||
the searched key. */
|
||||
}
|
||||
@@ -626,7 +628,7 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
|
||||
*
|
||||
* 3b. IF $SPLITPOS != 0:
|
||||
* Trim the compressed node (reallocating it as well) in order to
|
||||
* contain $splitpos characters. Change chilid pointer in order to link
|
||||
* contain $splitpos characters. Change child pointer in order to link
|
||||
* to the split node. If new compressed node len is just 1, set
|
||||
* iscompr to 0 (layout is the same). Fix parent's reference.
|
||||
*
|
||||
@@ -1080,7 +1082,7 @@ int raxRemove(rax *rax, unsigned char *s, size_t len, void **old) {
|
||||
}
|
||||
} else if (h->size == 1) {
|
||||
/* If the node had just one child, after the removal of the key
|
||||
* further compression with adjacent nodes is pontentially possible. */
|
||||
* further compression with adjacent nodes is potentially possible. */
|
||||
trycompress = 1;
|
||||
}
|
||||
|
||||
@@ -1268,6 +1270,7 @@ void raxStart(raxIterator *it, rax *rt) {
|
||||
* is a low level function used to implement the iterator, not callable by
|
||||
* the user. Returns 0 on out of memory, otherwise 1 is returned. */
|
||||
int raxIteratorAddChars(raxIterator *it, unsigned char *s, size_t len) {
|
||||
if (len == 0) return 1;
|
||||
if (it->key_max < it->key_len+len) {
|
||||
unsigned char *old = (it->key == it->key_static_string) ? NULL :
|
||||
it->key;
|
||||
@@ -1327,7 +1330,7 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
|
||||
if (!noup && children) {
|
||||
debugf("GO DEEPER\n");
|
||||
/* Seek the lexicographically smaller key in this subtree, which
|
||||
* is the first one found always going torwards the first child
|
||||
* is the first one found always going towards the first child
|
||||
* of every successive node. */
|
||||
if (!raxStackPush(&it->stack,it->node)) return 0;
|
||||
raxNode **cp = raxNodeFirstChildPtr(it->node);
|
||||
@@ -1346,7 +1349,7 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
/* If we finished exporing the previous sub-tree, switch to the
|
||||
/* If we finished exploring the previous sub-tree, switch to the
|
||||
* new one: go upper until a node is found where there are
|
||||
* children representing keys lexicographically greater than the
|
||||
* current key. */
|
||||
@@ -1508,7 +1511,7 @@ int raxIteratorPrevStep(raxIterator *it, int noup) {
|
||||
int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) {
|
||||
int eq = 0, lt = 0, gt = 0, first = 0, last = 0;
|
||||
|
||||
it->stack.items = 0; /* Just resetting. Intialized by raxStart(). */
|
||||
it->stack.items = 0; /* Just resetting. Initialized by raxStart(). */
|
||||
it->flags |= RAX_ITER_JUST_SEEKED;
|
||||
it->flags &= ~RAX_ITER_EOF;
|
||||
it->key_len = 0;
|
||||
@@ -1729,7 +1732,7 @@ int raxPrev(raxIterator *it) {
|
||||
* tree, expect a disappointing distribution. A random walk produces good
|
||||
* random elements if the tree is not sparse, however in the case of a radix
|
||||
* tree certain keys will be reported much more often than others. At least
|
||||
* this function should be able to expore every possible element eventually. */
|
||||
* this function should be able to explore every possible element eventually. */
|
||||
int raxRandomWalk(raxIterator *it, size_t steps) {
|
||||
if (it->rt->numele == 0) {
|
||||
it->flags |= RAX_ITER_EOF;
|
||||
@@ -1737,7 +1740,7 @@ int raxRandomWalk(raxIterator *it, size_t steps) {
|
||||
}
|
||||
|
||||
if (steps == 0) {
|
||||
size_t fle = floor(log(it->rt->numele));
|
||||
size_t fle = 1+floor(log(it->rt->numele));
|
||||
fle *= 2;
|
||||
steps = 1 + rand() % fle;
|
||||
}
|
||||
@@ -1766,6 +1769,7 @@ int raxRandomWalk(raxIterator *it, size_t steps) {
|
||||
if (n->iskey) steps--;
|
||||
}
|
||||
it->node = n;
|
||||
it->data = raxGetData(it->node);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1822,7 +1826,7 @@ uint64_t raxSize(rax *rax) {
|
||||
/* ----------------------------- Introspection ------------------------------ */
|
||||
|
||||
/* This function is mostly used for debugging and learning purposes.
|
||||
* It shows an ASCII representation of a tree on standard output, outling
|
||||
* It shows an ASCII representation of a tree on standard output, outline
|
||||
* all the nodes and the contained keys.
|
||||
*
|
||||
* The representation is as follow:
|
||||
@@ -1832,7 +1836,7 @@ uint64_t raxSize(rax *rax) {
|
||||
* [abc]=0x12345678 (node is a key, pointing to value 0x12345678)
|
||||
* [] (a normal empty node)
|
||||
*
|
||||
* Children are represented in new idented lines, each children prefixed by
|
||||
* Children are represented in new indented lines, each children prefixed by
|
||||
* the "`-(x)" string, where "x" is the edge byte.
|
||||
*
|
||||
* [abc]
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
* successive nodes having a single child are "compressed" into the node
|
||||
* itself as a string of characters, each representing a next-level child,
|
||||
* and only the link to the node representing the last character node is
|
||||
* provided inside the representation. So the above representation is turend
|
||||
* provided inside the representation. So the above representation is turned
|
||||
* into:
|
||||
*
|
||||
* ["foo"] ""
|
||||
@@ -123,7 +123,7 @@ typedef struct raxNode {
|
||||
* nodes).
|
||||
*
|
||||
* If the node has an associated key (iskey=1) and is not NULL
|
||||
* (isnull=0), then after the raxNode pointers poiting to the
|
||||
* (isnull=0), then after the raxNode pointers pointing to the
|
||||
* children, an additional value pointer is present (as you can see
|
||||
* in the representation above as "value-ptr" field).
|
||||
*/
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "stream.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
@@ -52,6 +53,9 @@ extern int rdbCheckMode;
|
||||
void rdbCheckError(const char *fmt, ...);
|
||||
void rdbCheckSetError(const char *fmt, ...);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void rdbReportError(int corruption_error, int linenum, char *reason, ...) __attribute__ ((format (printf, 3, 4)));
|
||||
#endif
|
||||
void rdbReportError(int corruption_error, int linenum, char *reason, ...) {
|
||||
va_list ap;
|
||||
char msg[1024];
|
||||
@@ -80,7 +84,7 @@ void rdbReportError(int corruption_error, int linenum, char *reason, ...) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static int rdbWriteRaw(rio *rdb, void *p, size_t len) {
|
||||
static ssize_t rdbWriteRaw(rio *rdb, void *p, size_t len) {
|
||||
if (rdb && rioWrite(rdb,p,len) == 0)
|
||||
return -1;
|
||||
return len;
|
||||
@@ -274,12 +278,16 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
|
||||
} else if (enctype == RDB_ENC_INT16) {
|
||||
uint16_t v;
|
||||
if (rioRead(rdb,enc,2) == 0) return NULL;
|
||||
v = enc[0]|(enc[1]<<8);
|
||||
v = ((uint32_t)enc[0])|
|
||||
((uint32_t)enc[1]<<8);
|
||||
val = (int16_t)v;
|
||||
} else if (enctype == RDB_ENC_INT32) {
|
||||
uint32_t v;
|
||||
if (rioRead(rdb,enc,4) == 0) return NULL;
|
||||
v = enc[0]|(enc[1]<<8)|(enc[2]<<16)|(enc[3]<<24);
|
||||
v = ((uint32_t)enc[0])|
|
||||
((uint32_t)enc[1]<<8)|
|
||||
((uint32_t)enc[2]<<16)|
|
||||
((uint32_t)enc[3]<<24);
|
||||
val = (int32_t)v;
|
||||
} else {
|
||||
rdbExitReportCorruptRDB("Unknown RDB integer encoding type %d",enctype);
|
||||
@@ -487,7 +495,7 @@ void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr) {
|
||||
int plain = flags & RDB_LOAD_PLAIN;
|
||||
int sds = flags & RDB_LOAD_SDS;
|
||||
int isencoded;
|
||||
uint64_t len;
|
||||
unsigned long long len;
|
||||
|
||||
len = rdbLoadLen(rdb,&isencoded);
|
||||
if (isencoded) {
|
||||
@@ -499,8 +507,8 @@ void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr) {
|
||||
case RDB_ENC_LZF:
|
||||
return rdbLoadLzfStringObject(rdb,flags,lenptr);
|
||||
default:
|
||||
rdbExitReportCorruptRDB("Unknown RDB string encoding type %d",len);
|
||||
return NULL; /* Never reached. */
|
||||
rdbExitReportCorruptRDB("Unknown RDB string encoding type %llu",len);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,15 +705,23 @@ ssize_t rdbSaveStreamPEL(rio *rdb, rax *pel, int nacks) {
|
||||
while(raxNext(&ri)) {
|
||||
/* We store IDs in raw form as 128 big big endian numbers, like
|
||||
* they are inside the radix tree key. */
|
||||
if ((n = rdbWriteRaw(rdb,ri.key,sizeof(streamID))) == -1) return -1;
|
||||
if ((n = rdbWriteRaw(rdb,ri.key,sizeof(streamID))) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
|
||||
if (nacks) {
|
||||
streamNACK *nack = ri.data;
|
||||
if ((n = rdbSaveMillisecondTime(rdb,nack->delivery_time)) == -1)
|
||||
if ((n = rdbSaveMillisecondTime(rdb,nack->delivery_time)) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveLen(rdb,nack->delivery_count)) == -1) return -1;
|
||||
if ((n = rdbSaveLen(rdb,nack->delivery_count)) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
/* We don't save the consumer name: we'll save the pending IDs
|
||||
* for each consumer in the consumer PEL, and resolve the consumer
|
||||
@@ -734,20 +750,27 @@ size_t rdbSaveStreamConsumers(rio *rdb, streamCG *cg) {
|
||||
streamConsumer *consumer = ri.data;
|
||||
|
||||
/* Consumer name. */
|
||||
if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) return -1;
|
||||
if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
|
||||
/* Last seen time. */
|
||||
if ((n = rdbSaveMillisecondTime(rdb,consumer->seen_time)) == -1)
|
||||
if ((n = rdbSaveMillisecondTime(rdb,consumer->seen_time)) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
|
||||
/* Consumer PEL, without the ACKs (see last parameter of the function
|
||||
* passed with value of 0), at loading time we'll lookup the ID
|
||||
* in the consumer group global PEL and will put a reference in the
|
||||
* consumer local PEL. */
|
||||
if ((n = rdbSaveStreamPEL(rdb,consumer->pel,0)) == -1)
|
||||
if ((n = rdbSaveStreamPEL(rdb,consumer->pel,0)) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
}
|
||||
raxStop(&ri);
|
||||
@@ -912,9 +935,15 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
while (raxNext(&ri)) {
|
||||
unsigned char *lp = ri.data;
|
||||
size_t lp_bytes = lpBytes(lp);
|
||||
if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) return -1;
|
||||
if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveRawString(rdb,lp,lp_bytes)) == -1) return -1;
|
||||
if ((n = rdbSaveRawString(rdb,lp,lp_bytes)) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
}
|
||||
raxStop(&ri);
|
||||
@@ -946,22 +975,36 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
streamCG *cg = ri.data;
|
||||
|
||||
/* Save the group name. */
|
||||
if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1)
|
||||
if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
|
||||
/* Last ID. */
|
||||
if ((n = rdbSaveLen(rdb,cg->last_id.ms)) == -1) return -1;
|
||||
if ((n = rdbSaveLen(rdb,cg->last_id.ms)) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveLen(rdb,cg->last_id.seq)) == -1) return -1;
|
||||
if ((n = rdbSaveLen(rdb,cg->last_id.seq)) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
|
||||
/* Save the global PEL. */
|
||||
if ((n = rdbSaveStreamPEL(rdb,cg->pel,1)) == -1) return -1;
|
||||
if ((n = rdbSaveStreamPEL(rdb,cg->pel,1)) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
|
||||
/* Save the consumers of this group. */
|
||||
if ((n = rdbSaveStreamConsumers(rdb,cg)) == -1) return -1;
|
||||
if ((n = rdbSaveStreamConsumers(rdb,cg)) == -1) {
|
||||
raxStop(&ri);
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
}
|
||||
raxStop(&ri);
|
||||
@@ -1002,8 +1045,8 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
* the rdbSaveObject() function. Currently we use a trick to get
|
||||
* this length with very little changes to the code. In the future
|
||||
* we could switch to a faster solution. */
|
||||
size_t rdbSavedObjectLen(robj *o) {
|
||||
ssize_t len = rdbSaveObject(NULL,o,NULL);
|
||||
size_t rdbSavedObjectLen(robj *o, robj *key) {
|
||||
ssize_t len = rdbSaveObject(NULL,o,key);
|
||||
serverAssertWithInfo(NULL,o,len != -1);
|
||||
return len;
|
||||
}
|
||||
@@ -1107,6 +1150,8 @@ ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt) {
|
||||
/* Save a module-specific aux value. */
|
||||
RedisModuleIO io;
|
||||
int retval = rdbSaveType(rdb, RDB_OPCODE_MODULE_AUX);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
|
||||
/* Write the "module" identifier as prefix, so that we'll be able
|
||||
* to call the right module during loading. */
|
||||
@@ -1175,10 +1220,7 @@ int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
|
||||
if (rdbSaveLen(rdb,j) == -1) goto werr;
|
||||
|
||||
/* Write the RESIZE DB opcode. We trim the size to UINT32_MAX, which
|
||||
* is currently the largest type we are able to represent in RDB sizes.
|
||||
* However this does not limit the actual size of the DB to load since
|
||||
* these sizes are just hints to resize the hash tables. */
|
||||
/* Write the RESIZE DB opcode. */
|
||||
uint64_t db_size, expires_size;
|
||||
db_size = dictSize(db->dict);
|
||||
expires_size = dictSize(db->expires);
|
||||
@@ -1276,7 +1318,7 @@ werr: /* Write error. */
|
||||
int rdbSave(char *filename, rdbSaveInfo *rsi) {
|
||||
char tmpfile[256];
|
||||
char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
|
||||
FILE *fp;
|
||||
FILE *fp = NULL;
|
||||
rio rdb;
|
||||
int error = 0;
|
||||
|
||||
@@ -1305,10 +1347,11 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
|
||||
}
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
if (fflush(fp) == EOF) goto werr;
|
||||
if (fsync(fileno(fp)) == -1) goto werr;
|
||||
if (fclose(fp) == EOF) goto werr;
|
||||
|
||||
if (fflush(fp)) goto werr;
|
||||
if (fsync(fileno(fp))) goto werr;
|
||||
if (fclose(fp)) { fp = NULL; goto werr; }
|
||||
fp = NULL;
|
||||
|
||||
/* Use RENAME to make sure the DB file is changed atomically only
|
||||
* if the generate DB file is ok. */
|
||||
if (rename(tmpfile,filename) == -1) {
|
||||
@@ -1334,7 +1377,7 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
|
||||
|
||||
werr:
|
||||
serverLog(LL_WARNING,"Write error saving DB on disk: %s", strerror(errno));
|
||||
fclose(fp);
|
||||
if (fp) fclose(fp);
|
||||
unlink(tmpfile);
|
||||
stopSaving(0);
|
||||
return C_ERR;
|
||||
@@ -1349,14 +1392,15 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
||||
server.lastbgsave_try = time(NULL);
|
||||
openChildInfoPipe();
|
||||
|
||||
if ((childpid = redisFork()) == 0) {
|
||||
if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
|
||||
int retval;
|
||||
|
||||
/* Child */
|
||||
redisSetProcTitle("redis-rdb-bgsave");
|
||||
redisSetCpuAffinity(server.bgsave_cpulist);
|
||||
retval = rdbSave(filename,rsi);
|
||||
if (retval == C_OK) {
|
||||
sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
|
||||
sendChildCOWInfo(CHILD_TYPE_RDB, "RDB");
|
||||
}
|
||||
exitFromChild((retval == C_OK) ? 0 : 1);
|
||||
} else {
|
||||
@@ -1372,16 +1416,35 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
||||
server.rdb_save_time_start = time(NULL);
|
||||
server.rdb_child_pid = childpid;
|
||||
server.rdb_child_type = RDB_CHILD_TYPE_DISK;
|
||||
updateDictResizePolicy();
|
||||
return C_OK;
|
||||
}
|
||||
return C_OK; /* unreached */
|
||||
}
|
||||
|
||||
void rdbRemoveTempFile(pid_t childpid) {
|
||||
/* Note that we may call this function in signal handle 'sigShutdownHandler',
|
||||
* so we need guarantee all functions we call are async-signal-safe.
|
||||
* If we call this function from signal handle, we won't call bg_unlik that
|
||||
* is not async-signal-safe. */
|
||||
void rdbRemoveTempFile(pid_t childpid, int from_signal) {
|
||||
char tmpfile[256];
|
||||
char pid[32];
|
||||
|
||||
snprintf(tmpfile,sizeof(tmpfile),"temp-%d.rdb", (int) childpid);
|
||||
unlink(tmpfile);
|
||||
/* Generate temp rdb file name using aync-signal safe functions. */
|
||||
int pid_len = ll2string(pid, sizeof(pid), childpid);
|
||||
strcpy(tmpfile, "temp-");
|
||||
strncpy(tmpfile+5, pid, pid_len);
|
||||
strcpy(tmpfile+5+pid_len, ".rdb");
|
||||
|
||||
if (from_signal) {
|
||||
/* bg_unlink is not async-signal-safe, but in this case we don't really
|
||||
* need to close the fd, it'll be released when the process exists. */
|
||||
int fd = open(tmpfile, O_RDONLY|O_NONBLOCK);
|
||||
UNUSED(fd);
|
||||
unlink(tmpfile);
|
||||
} else {
|
||||
bg_unlink(tmpfile);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called by rdbLoadObject() when the code is in RDB-check
|
||||
@@ -1425,7 +1488,7 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
|
||||
|
||||
/* Load a Redis object of the specified type from the specified file.
|
||||
* On success a newly allocated object is returned, otherwise NULL. */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
robj *o = NULL, *ele, *dec;
|
||||
uint64_t len;
|
||||
unsigned int i;
|
||||
@@ -1444,7 +1507,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
|
||||
/* Load every single element of the list */
|
||||
while(len--) {
|
||||
if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
|
||||
if ((ele = rdbLoadEncodedStringObject(rdb)) == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
dec = getDecodedObject(ele);
|
||||
size_t len = sdslen(dec->ptr);
|
||||
quicklistPushTail(o->ptr, dec->ptr, len);
|
||||
@@ -1456,7 +1522,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
|
||||
/* Use a regular set when there are too many entries. */
|
||||
if (len > server.set_max_intset_entries) {
|
||||
size_t max_entries = server.set_max_intset_entries;
|
||||
if (max_entries >= 1<<30) max_entries = 1<<30;
|
||||
if (len > max_entries) {
|
||||
o = createSetObject();
|
||||
/* It's faster to expand the dict to the right size asap in order
|
||||
* to avoid rehashing */
|
||||
@@ -1471,8 +1539,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
long long llval;
|
||||
sds sdsele;
|
||||
|
||||
if ((sdsele = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL) return NULL;
|
||||
if ((sdsele = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (o->encoding == OBJ_ENCODING_INTSET) {
|
||||
/* Fetch integer value from element. */
|
||||
@@ -1495,7 +1565,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
} else if (rdbtype == RDB_TYPE_ZSET_2 || rdbtype == RDB_TYPE_ZSET) {
|
||||
/* Read list/set value. */
|
||||
uint64_t zsetlen;
|
||||
size_t maxelelen = 0;
|
||||
size_t maxelelen = 0, totelelen = 0;
|
||||
zset *zs;
|
||||
|
||||
if ((zsetlen = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
@@ -1511,17 +1581,28 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
double score;
|
||||
zskiplistNode *znode;
|
||||
|
||||
if ((sdsele = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL) return NULL;
|
||||
if ((sdsele = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (rdbtype == RDB_TYPE_ZSET_2) {
|
||||
if (rdbLoadBinaryDoubleValue(rdb,&score) == -1) return NULL;
|
||||
if (rdbLoadBinaryDoubleValue(rdb,&score) == -1) {
|
||||
decrRefCount(o);
|
||||
sdsfree(sdsele);
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
if (rdbLoadDoubleValue(rdb,&score) == -1) return NULL;
|
||||
if (rdbLoadDoubleValue(rdb,&score) == -1) {
|
||||
decrRefCount(o);
|
||||
sdsfree(sdsele);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Don't care about integer-encoded strings. */
|
||||
if (sdslen(sdsele) > maxelelen) maxelelen = sdslen(sdsele);
|
||||
totelelen += sdslen(sdsele);
|
||||
|
||||
znode = zslInsert(zs->zsl,score,sdsele);
|
||||
dictAdd(zs->dict,sdsele,&znode->score);
|
||||
@@ -1529,8 +1610,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
|
||||
/* Convert *after* loading, since sorted sets are not stored ordered. */
|
||||
if (zsetLength(o) <= server.zset_max_ziplist_entries &&
|
||||
maxelelen <= server.zset_max_ziplist_value)
|
||||
zsetConvert(o,OBJ_ENCODING_ZIPLIST);
|
||||
maxelelen <= server.zset_max_ziplist_value &&
|
||||
ziplistSafeToAdd(NULL, totelelen))
|
||||
{
|
||||
zsetConvert(o,OBJ_ENCODING_ZIPLIST);
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_HASH) {
|
||||
uint64_t len;
|
||||
int ret;
|
||||
@@ -1549,10 +1633,28 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
while (o->encoding == OBJ_ENCODING_ZIPLIST && len > 0) {
|
||||
len--;
|
||||
/* Load raw strings */
|
||||
if ((field = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL) return NULL;
|
||||
if ((value = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL) return NULL;
|
||||
if ((field = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
if ((value = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||
sdsfree(field);
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Convert to hash table if size threshold is exceeded */
|
||||
if (sdslen(field) > server.hash_max_ziplist_value ||
|
||||
sdslen(value) > server.hash_max_ziplist_value ||
|
||||
!ziplistSafeToAdd(o->ptr, sdslen(field)+sdslen(value)))
|
||||
{
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
ret = dictAdd((dict*)o->ptr, field, value);
|
||||
if (ret == DICT_ERR) {
|
||||
rdbExitReportCorruptRDB("Duplicate hash fields detected");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Add pair to ziplist */
|
||||
o->ptr = ziplistPush(o->ptr, (unsigned char*)field,
|
||||
@@ -1560,15 +1662,6 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
o->ptr = ziplistPush(o->ptr, (unsigned char*)value,
|
||||
sdslen(value), ZIPLIST_TAIL);
|
||||
|
||||
/* Convert to hash table if size threshold is exceeded */
|
||||
if (sdslen(field) > server.hash_max_ziplist_value ||
|
||||
sdslen(value) > server.hash_max_ziplist_value)
|
||||
{
|
||||
sdsfree(field);
|
||||
sdsfree(value);
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
break;
|
||||
}
|
||||
sdsfree(field);
|
||||
sdsfree(value);
|
||||
}
|
||||
@@ -1580,10 +1673,15 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
while (o->encoding == OBJ_ENCODING_HT && len > 0) {
|
||||
len--;
|
||||
/* Load encoded strings */
|
||||
if ((field = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL) return NULL;
|
||||
if ((value = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL))
|
||||
== NULL) return NULL;
|
||||
if ((field = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
if ((value = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL) {
|
||||
sdsfree(field);
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Add pair to hash table */
|
||||
ret = dictAdd((dict*)o->ptr, field, value);
|
||||
@@ -1603,7 +1701,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
while (len--) {
|
||||
unsigned char *zl =
|
||||
rdbGenericLoadStringObject(rdb,RDB_LOAD_PLAIN,NULL);
|
||||
if (zl == NULL) return NULL;
|
||||
if (zl == NULL) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
quicklistAppendZiplist(o->ptr, zl);
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_HASH_ZIPMAP ||
|
||||
@@ -1637,6 +1738,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
while ((zi = zipmapNext(zi, &fstr, &flen, &vstr, &vlen)) != NULL) {
|
||||
if (flen > maxlen) maxlen = flen;
|
||||
if (vlen > maxlen) maxlen = vlen;
|
||||
if (!ziplistSafeToAdd(zl, (size_t)flen + vlen)) {
|
||||
rdbExitReportCorruptRDB("Hash zipmap too big (%u)", flen);
|
||||
}
|
||||
|
||||
zl = ziplistPush(zl, fstr, flen, ZIPLIST_TAIL);
|
||||
zl = ziplistPush(zl, vstr, vlen, ZIPLIST_TAIL);
|
||||
}
|
||||
@@ -1826,8 +1931,8 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
decrRefCount(o);
|
||||
return NULL;
|
||||
}
|
||||
streamConsumer *consumer = streamLookupConsumer(cgroup,cname,
|
||||
1);
|
||||
streamConsumer *consumer =
|
||||
streamLookupConsumer(cgroup,cname,SLC_NONE);
|
||||
sdsfree(cname);
|
||||
consumer->seen_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
|
||||
if (rioGetReadError(rdb)) {
|
||||
@@ -1871,7 +1976,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_MODULE || rdbtype == RDB_TYPE_MODULE_2) {
|
||||
uint64_t moduleid = rdbLoadLen(rdb,NULL);
|
||||
if (rioGetReadError(rdb)) return NULL;
|
||||
if (rioGetReadError(rdb)) {
|
||||
rdbReportReadError("Short read module id");
|
||||
return NULL;
|
||||
}
|
||||
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
||||
char name[10];
|
||||
|
||||
@@ -1886,7 +1994,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
exit(1);
|
||||
}
|
||||
RedisModuleIO io;
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj,key);
|
||||
moduleInitIOContext(io,mt,rdb,&keyobj);
|
||||
io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
@@ -2044,7 +2154,8 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
long long lru_clock = LRU_CLOCK();
|
||||
|
||||
while(1) {
|
||||
robj *key, *val;
|
||||
sds key;
|
||||
robj *val;
|
||||
|
||||
/* Read type. */
|
||||
if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
|
||||
@@ -2105,7 +2216,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
} else if (type == RDB_OPCODE_AUX) {
|
||||
/* AUX: generic string-string fields. Use to add state to RDB
|
||||
* which is backward compatible. Implementations of RDB loading
|
||||
* are requierd to skip AUX fields they don't understand.
|
||||
* are required to skip AUX fields they don't understand.
|
||||
*
|
||||
* An AUX field is composed of two strings: key and value. */
|
||||
robj *auxkey, *auxval;
|
||||
@@ -2133,7 +2244,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
if (luaCreateFunction(NULL,server.lua,auxval) == NULL) {
|
||||
rdbExitReportCorruptRDB(
|
||||
"Can't load Lua script from RDB file! "
|
||||
"BODY: %s", auxval->ptr);
|
||||
"BODY: %s", (char*)auxval->ptr);
|
||||
}
|
||||
} else if (!strcasecmp(auxkey->ptr,"redis-ver")) {
|
||||
serverLog(LL_NOTICE,"Loading RDB produced by version %s",
|
||||
@@ -2170,8 +2281,10 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
int when_opcode = rdbLoadLen(rdb,NULL);
|
||||
int when = rdbLoadLen(rdb,NULL);
|
||||
if (rioGetReadError(rdb)) goto eoferr;
|
||||
if (when_opcode != RDB_MODULE_OPCODE_UINT)
|
||||
if (when_opcode != RDB_MODULE_OPCODE_UINT) {
|
||||
rdbReportReadError("bad when_opcode");
|
||||
goto eoferr;
|
||||
}
|
||||
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
||||
char name[10];
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
@@ -2192,10 +2305,10 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
io.ver = 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
if (mt->aux_load(&io,moduleid&1023, when) || io.error) {
|
||||
if (mt->aux_load(&io,moduleid&1023, when) != REDISMODULE_OK || io.error) {
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
|
||||
exit(1);
|
||||
goto eoferr;
|
||||
}
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
@@ -2204,7 +2317,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
uint64_t eof = rdbLoadLen(rdb,NULL);
|
||||
if (eof != RDB_MODULE_OPCODE_EOF) {
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module '%s' that is not terminated by the proper module value EOF marker", name);
|
||||
exit(1);
|
||||
goto eoferr;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
@@ -2216,10 +2329,11 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
}
|
||||
|
||||
/* Read key */
|
||||
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
if ((key = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL)
|
||||
goto eoferr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,rdb,key)) == NULL) {
|
||||
decrRefCount(key);
|
||||
sdsfree(key);
|
||||
goto eoferr;
|
||||
}
|
||||
|
||||
@@ -2227,26 +2341,51 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
* received from the master. In the latter case, the master is
|
||||
* responsible for key expiry. If we would expire keys here, the
|
||||
* snapshot taken by the master may not be reflected on the slave. */
|
||||
if (server.masterhost == NULL && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now) {
|
||||
decrRefCount(key);
|
||||
* snapshot taken by the master may not be reflected on the slave.
|
||||
* Similarly if the RDB is the preamble of an AOF file, we want to
|
||||
* load all the keys as they are, since the log of operations later
|
||||
* assume to work in an exact keyspace state. */
|
||||
if (iAmMaster() &&
|
||||
!(rdbflags&RDBFLAGS_AOF_PREAMBLE) &&
|
||||
expiretime != -1 && expiretime < now)
|
||||
{
|
||||
sdsfree(key);
|
||||
decrRefCount(val);
|
||||
} else {
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj,key);
|
||||
|
||||
/* Add the new object in the hash table */
|
||||
dbAdd(db,key,val);
|
||||
int added = dbAddRDBLoad(db,key,val);
|
||||
if (!added) {
|
||||
if (rdbflags & RDBFLAGS_ALLOW_DUP) {
|
||||
/* This flag is useful for DEBUG RELOAD special modes.
|
||||
* When it's set we allow new keys to replace the current
|
||||
* keys with the same name. */
|
||||
dbSyncDelete(db,&keyobj);
|
||||
dbAddRDBLoad(db,key,val);
|
||||
} else {
|
||||
serverLog(LL_WARNING,
|
||||
"RDB has duplicated key '%s' in DB %d",key,db->id);
|
||||
serverPanic("Duplicated key found in RDB file");
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the expire time if needed */
|
||||
if (expiretime != -1) setExpire(NULL,db,key,expiretime);
|
||||
if (expiretime != -1) {
|
||||
setExpire(NULL,db,&keyobj,expiretime);
|
||||
}
|
||||
|
||||
/* Set usage information (for eviction). */
|
||||
objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock,1000);
|
||||
|
||||
/* Decrement the key refcount since dbAdd() will take its
|
||||
* own reference. */
|
||||
decrRefCount(key);
|
||||
/* call key space notification on key loaded for modules only */
|
||||
moduleNotifyKeyspaceEvent(NOTIFY_LOADED, "loaded", &keyobj, db->id);
|
||||
}
|
||||
if (server.key_load_delay)
|
||||
usleep(server.key_load_delay);
|
||||
|
||||
/* Loading the database more slowly is useful in order to test
|
||||
* certain edge cases. */
|
||||
if (server.key_load_delay) usleep(server.key_load_delay);
|
||||
|
||||
/* Reset the state that is key-specified and is populated by
|
||||
* opcodes before the key, so that we start from scratch again. */
|
||||
@@ -2264,7 +2403,10 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
if (cksum == 0) {
|
||||
serverLog(LL_WARNING,"RDB file was saved with checksum disabled: no check performed.");
|
||||
} else if (cksum != expected) {
|
||||
serverLog(LL_WARNING,"Wrong RDB checksum. Aborting now.");
|
||||
serverLog(LL_WARNING,"Wrong RDB checksum expected: (%llx) but "
|
||||
"got (%llx). Aborting now.",
|
||||
(unsigned long long)expected,
|
||||
(unsigned long long)cksum);
|
||||
rdbExitReportCorruptRDB("RDB CRC error");
|
||||
}
|
||||
}
|
||||
@@ -2305,7 +2447,7 @@ int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
|
||||
|
||||
/* A background saving child (BGSAVE) terminated its work. Handle this.
|
||||
* This function covers the case of actual BGSAVEs. */
|
||||
void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
|
||||
static void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
|
||||
if (!bysignal && exitcode == 0) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Background saving terminated with success");
|
||||
@@ -2321,27 +2463,20 @@ void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
|
||||
serverLog(LL_WARNING,
|
||||
"Background saving terminated by signal %d", bysignal);
|
||||
latencyStartMonitor(latency);
|
||||
rdbRemoveTempFile(server.rdb_child_pid);
|
||||
rdbRemoveTempFile(server.rdb_child_pid, 0);
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("rdb-unlink-temp-file",latency);
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
* tirggering an error condition. */
|
||||
* triggering an error condition. */
|
||||
if (bysignal != SIGUSR1)
|
||||
server.lastbgsave_status = C_ERR;
|
||||
}
|
||||
server.rdb_child_pid = -1;
|
||||
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||
server.rdb_save_time_last = time(NULL)-server.rdb_save_time_start;
|
||||
server.rdb_save_time_start = -1;
|
||||
/* Possibly there are slaves waiting for a BGSAVE in order to be served
|
||||
* (the first stage of SYNC is a bulk transfer of dump.rdb) */
|
||||
updateSlavesWaitingBgsave((!bysignal && exitcode == 0) ? C_OK : C_ERR, RDB_CHILD_TYPE_DISK);
|
||||
}
|
||||
|
||||
/* A background saving child (BGSAVE) terminated its work. Handle this.
|
||||
* This function covers the case of RDB -> Slaves socket transfers for
|
||||
* diskless replication. */
|
||||
void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
|
||||
static void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
|
||||
if (!bysignal && exitcode == 0) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Background RDB transfer terminated with success");
|
||||
@@ -2351,15 +2486,24 @@ void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
|
||||
serverLog(LL_WARNING,
|
||||
"Background transfer terminated by signal %d", bysignal);
|
||||
}
|
||||
server.rdb_child_pid = -1;
|
||||
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||
server.rdb_save_time_start = -1;
|
||||
|
||||
updateSlavesWaitingBgsave((!bysignal && exitcode == 0) ? C_OK : C_ERR, RDB_CHILD_TYPE_SOCKET);
|
||||
if (server.rdb_child_exit_pipe!=-1)
|
||||
close(server.rdb_child_exit_pipe);
|
||||
aeDeleteFileEvent(server.el, server.rdb_pipe_read, AE_READABLE);
|
||||
close(server.rdb_pipe_read);
|
||||
server.rdb_child_exit_pipe = -1;
|
||||
server.rdb_pipe_read = -1;
|
||||
zfree(server.rdb_pipe_conns);
|
||||
server.rdb_pipe_conns = NULL;
|
||||
server.rdb_pipe_numconns = 0;
|
||||
server.rdb_pipe_numconns_writing = 0;
|
||||
zfree(server.rdb_pipe_buff);
|
||||
server.rdb_pipe_buff = NULL;
|
||||
server.rdb_pipe_bufflen = 0;
|
||||
}
|
||||
|
||||
/* When a background RDB saving/transfer terminates, call the right handler. */
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
||||
int type = server.rdb_child_type;
|
||||
switch(server.rdb_child_type) {
|
||||
case RDB_CHILD_TYPE_DISK:
|
||||
backgroundSaveDoneHandlerDisk(exitcode,bysignal);
|
||||
@@ -2371,6 +2515,14 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
||||
serverPanic("Unknown RDB child type.");
|
||||
break;
|
||||
}
|
||||
|
||||
server.rdb_child_pid = -1;
|
||||
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||
server.rdb_save_time_last = time(NULL)-server.rdb_save_time_start;
|
||||
server.rdb_save_time_start = -1;
|
||||
/* Possibly there are slaves waiting for a BGSAVE in order to be served
|
||||
* (the first stage of SYNC is a bulk transfer of dump.rdb) */
|
||||
updateSlavesWaitingBgsave((!bysignal && exitcode == 0) ? C_OK : C_ERR, type);
|
||||
}
|
||||
|
||||
/* Kill the RDB saving child using SIGUSR1 (so that the parent will know
|
||||
@@ -2378,7 +2530,7 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
||||
* the cleanup needed. */
|
||||
void killRDBChild(void) {
|
||||
kill(server.rdb_child_pid,SIGUSR1);
|
||||
rdbRemoveTempFile(server.rdb_child_pid);
|
||||
rdbRemoveTempFile(server.rdb_child_pid, 0);
|
||||
closeChildInfoPipe();
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
@@ -2389,7 +2541,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
pid_t childpid;
|
||||
int pipefds[2];
|
||||
int pipefds[2], rdb_pipe_write, safe_to_exit_pipe;
|
||||
|
||||
if (hasActiveChildProcess()) return C_ERR;
|
||||
|
||||
@@ -2402,10 +2554,20 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
* of TLS we must let the parent handle a continuous TLS state when the
|
||||
* child terminates and parent takes over. */
|
||||
if (pipe(pipefds) == -1) return C_ERR;
|
||||
server.rdb_pipe_read = pipefds[0];
|
||||
server.rdb_pipe_write = pipefds[1];
|
||||
server.rdb_pipe_read = pipefds[0]; /* read end */
|
||||
rdb_pipe_write = pipefds[1]; /* write end */
|
||||
anetNonBlock(NULL, server.rdb_pipe_read);
|
||||
|
||||
/* create another pipe that is used by the parent to signal to the child
|
||||
* that it can exit. */
|
||||
if (pipe(pipefds) == -1) {
|
||||
close(rdb_pipe_write);
|
||||
close(server.rdb_pipe_read);
|
||||
return C_ERR;
|
||||
}
|
||||
safe_to_exit_pipe = pipefds[0]; /* read end */
|
||||
server.rdb_child_exit_pipe = pipefds[1]; /* write end */
|
||||
|
||||
/* Collect the connections of the replicas we want to transfer
|
||||
* the RDB to, which are i WAIT_BGSAVE_START state. */
|
||||
server.rdb_pipe_conns = zmalloc(sizeof(connection *)*listLength(server.slaves));
|
||||
@@ -2422,28 +2584,36 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
|
||||
/* Create the child process. */
|
||||
openChildInfoPipe();
|
||||
if ((childpid = redisFork()) == 0) {
|
||||
if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
|
||||
/* Child */
|
||||
int retval;
|
||||
int retval, dummy;
|
||||
rio rdb;
|
||||
|
||||
rioInitWithFd(&rdb,server.rdb_pipe_write);
|
||||
rioInitWithFd(&rdb,rdb_pipe_write);
|
||||
|
||||
redisSetProcTitle("redis-rdb-to-slaves");
|
||||
redisSetCpuAffinity(server.bgsave_cpulist);
|
||||
|
||||
retval = rdbSaveRioWithEOFMark(&rdb,NULL,rsi);
|
||||
if (retval == C_OK && rioFlush(&rdb) == 0)
|
||||
retval = C_ERR;
|
||||
|
||||
if (retval == C_OK) {
|
||||
sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
|
||||
sendChildCOWInfo(CHILD_TYPE_RDB, "RDB");
|
||||
}
|
||||
|
||||
rioFreeFd(&rdb);
|
||||
close(server.rdb_pipe_write); /* wake up the reader, tell it we're done. */
|
||||
/* wake up the reader, tell it we're done. */
|
||||
close(rdb_pipe_write);
|
||||
close(server.rdb_child_exit_pipe); /* close write end so that we can detect the close on the parent. */
|
||||
/* hold exit until the parent tells us it's safe. we're not expecting
|
||||
* to read anything, just get the error when the pipe is closed. */
|
||||
dummy = read(safe_to_exit_pipe, pipefds, 1);
|
||||
UNUSED(dummy);
|
||||
exitFromChild((retval == C_OK) ? 0 : 1);
|
||||
} else {
|
||||
/* Parent */
|
||||
close(safe_to_exit_pipe);
|
||||
if (childpid == -1) {
|
||||
serverLog(LL_WARNING,"Can't save in background: fork: %s",
|
||||
strerror(errno));
|
||||
@@ -2458,7 +2628,7 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
slave->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
|
||||
}
|
||||
}
|
||||
close(server.rdb_pipe_write);
|
||||
close(rdb_pipe_write);
|
||||
close(server.rdb_pipe_read);
|
||||
zfree(server.rdb_pipe_conns);
|
||||
server.rdb_pipe_conns = NULL;
|
||||
@@ -2471,7 +2641,8 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
server.rdb_save_time_start = time(NULL);
|
||||
server.rdb_child_pid = childpid;
|
||||
server.rdb_child_type = RDB_CHILD_TYPE_SOCKET;
|
||||
close(server.rdb_pipe_write); /* close write in parent so that it can detect the close on the child. */
|
||||
updateDictResizePolicy();
|
||||
close(rdb_pipe_write); /* close write in parent so that it can detect the close on the child. */
|
||||
if (aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) {
|
||||
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
|
||||
}
|
||||
|
||||
@@ -122,9 +122,10 @@
|
||||
#define RDB_LOAD_SDS (1<<2)
|
||||
|
||||
/* flags on the purpose of rdb save or load */
|
||||
#define RDBFLAGS_NONE 0
|
||||
#define RDBFLAGS_AOF_PREAMBLE (1<<0)
|
||||
#define RDBFLAGS_REPLICATION (1<<1)
|
||||
#define RDBFLAGS_NONE 0 /* No special RDB loading. */
|
||||
#define RDBFLAGS_AOF_PREAMBLE (1<<0) /* Load/save the RDB as AOF preamble. */
|
||||
#define RDBFLAGS_REPLICATION (1<<1) /* Load/save for SYNC. */
|
||||
#define RDBFLAGS_ALLOW_DUP (1<<2) /* Allow duplicated keys when loading.*/
|
||||
|
||||
int rdbSaveType(rio *rdb, unsigned char type);
|
||||
int rdbLoadType(rio *rdb);
|
||||
@@ -140,14 +141,15 @@ int rdbLoadObjectType(rio *rdb);
|
||||
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags);
|
||||
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
void rdbRemoveTempFile(pid_t childpid, int from_signal);
|
||||
int rdbSave(char *filename, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o, robj *key);
|
||||
robj *rdbLoadObject(int type, rio *rdb, sds key);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
||||
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
|
||||
robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename);
|
||||
robj *rdbLoadStringObject(rio *rdb);
|
||||
ssize_t rdbSaveStringObject(rio *rdb, robj *obj);
|
||||
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len);
|
||||
|
||||
+134
-66
@@ -1,4 +1,4 @@
|
||||
/* Redis benchmark utility.
|
||||
/* Redis benchmark utility.
|
||||
*
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
@@ -50,6 +50,7 @@
|
||||
#include "zmalloc.h"
|
||||
#include "atomicvar.h"
|
||||
#include "crc16_slottable.h"
|
||||
#include "mt19937-64.h"
|
||||
|
||||
#define UNUSED(V) ((void) V)
|
||||
#define RANDPTR_INITIAL_SIZE 8
|
||||
@@ -94,6 +95,7 @@ static struct config {
|
||||
sds dbnumstr;
|
||||
char *tests;
|
||||
char *auth;
|
||||
const char *user;
|
||||
int precision;
|
||||
int num_threads;
|
||||
struct benchmarkThread **threads;
|
||||
@@ -182,6 +184,8 @@ static void *execBenchmarkThread(void *ptr);
|
||||
static clusterNode *createClusterNode(char *ip, int port);
|
||||
static redisConfig *getRedisConfig(const char *ip, int port,
|
||||
const char *hostsocket);
|
||||
static redisContext *getRedisContext(const char *ip, int port,
|
||||
const char *hostsocket);
|
||||
static void freeRedisConfig(redisConfig *cfg);
|
||||
static int fetchClusterSlotsConfiguration(client c);
|
||||
static void updateClusterSlotsConfiguration();
|
||||
@@ -237,6 +241,52 @@ void _serverAssert(const char *estr, const char *file, int line) {
|
||||
*((char*)-1) = 'x';
|
||||
}
|
||||
|
||||
static redisContext *getRedisContext(const char *ip, int port,
|
||||
const char *hostsocket)
|
||||
{
|
||||
redisContext *ctx = NULL;
|
||||
redisReply *reply = NULL;
|
||||
if (hostsocket == NULL)
|
||||
ctx = redisConnect(ip, port);
|
||||
else
|
||||
ctx = redisConnectUnix(hostsocket);
|
||||
if (ctx == NULL || ctx->err) {
|
||||
fprintf(stderr,"Could not connect to Redis at ");
|
||||
char *err = (ctx != NULL ? ctx->errstr : "");
|
||||
if (hostsocket == NULL)
|
||||
fprintf(stderr,"%s:%d: %s\n",ip,port,err);
|
||||
else
|
||||
fprintf(stderr,"%s: %s\n",hostsocket,err);
|
||||
goto cleanup;
|
||||
}
|
||||
if (config.auth == NULL)
|
||||
return ctx;
|
||||
if (config.user == NULL)
|
||||
reply = redisCommand(ctx,"AUTH %s", config.auth);
|
||||
else
|
||||
reply = redisCommand(ctx,"AUTH %s %s", config.user, config.auth);
|
||||
if (reply != NULL) {
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
if (hostsocket == NULL)
|
||||
fprintf(stderr, "Node %s:%d replied with error:\n%s\n", ip, port, reply->str);
|
||||
else
|
||||
fprintf(stderr, "Node %s replied with error:\n%s\n", hostsocket, reply->str);
|
||||
goto cleanup;
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
return ctx;
|
||||
}
|
||||
fprintf(stderr, "ERROR: failed to fetch reply from ");
|
||||
if (hostsocket == NULL)
|
||||
fprintf(stderr, "%s:%d\n", ip, port);
|
||||
else
|
||||
fprintf(stderr, "%s\n", hostsocket);
|
||||
cleanup:
|
||||
freeReplyObject(reply);
|
||||
redisFree(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static redisConfig *getRedisConfig(const char *ip, int port,
|
||||
const char *hostsocket)
|
||||
{
|
||||
@@ -244,30 +294,11 @@ static redisConfig *getRedisConfig(const char *ip, int port,
|
||||
if (!cfg) return NULL;
|
||||
redisContext *c = NULL;
|
||||
redisReply *reply = NULL, *sub_reply = NULL;
|
||||
if (hostsocket == NULL)
|
||||
c = redisConnect(ip, port);
|
||||
else
|
||||
c = redisConnectUnix(hostsocket);
|
||||
if (c == NULL || c->err) {
|
||||
fprintf(stderr,"Could not connect to Redis at ");
|
||||
char *err = (c != NULL ? c->errstr : "");
|
||||
if (hostsocket == NULL) fprintf(stderr,"%s:%d: %s\n",ip,port,err);
|
||||
else fprintf(stderr,"%s: %s\n",hostsocket,err);
|
||||
goto fail;
|
||||
c = getRedisContext(ip, port, hostsocket);
|
||||
if (c == NULL) {
|
||||
freeRedisConfig(cfg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(config.auth) {
|
||||
void *authReply = NULL;
|
||||
redisAppendCommand(c, "AUTH %s", config.auth);
|
||||
if (REDIS_OK != redisGetReply(c, &authReply)) goto fail;
|
||||
if (reply) freeReplyObject(reply);
|
||||
reply = ((redisReply *) authReply);
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "ERROR: %s\n", reply->str);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
redisAppendCommand(c, "CONFIG GET %s", "save");
|
||||
redisAppendCommand(c, "CONFIG GET %s", "appendonly");
|
||||
int i = 0;
|
||||
@@ -275,7 +306,7 @@ static redisConfig *getRedisConfig(const char *ip, int port,
|
||||
for (; i < 2; i++) {
|
||||
int res = redisGetReply(c, &r);
|
||||
if (reply) freeReplyObject(reply);
|
||||
reply = ((redisReply *) r);
|
||||
reply = res == REDIS_OK ? ((redisReply *) r) : NULL;
|
||||
if (res != REDIS_OK || !r) goto fail;
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "ERROR: %s\n", reply->str);
|
||||
@@ -299,7 +330,7 @@ fail:
|
||||
else fprintf(stderr, "%s\n", hostsocket);
|
||||
freeReplyObject(reply);
|
||||
redisFree(c);
|
||||
zfree(cfg);
|
||||
freeRedisConfig(cfg);
|
||||
return NULL;
|
||||
}
|
||||
static void freeRedisConfig(redisConfig *cfg) {
|
||||
@@ -497,6 +528,9 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* we need to randomize. */
|
||||
for (j = 0; j < c->randlen; j++)
|
||||
c->randptr[j] -= c->prefixlen;
|
||||
/* Fix the pointers to the slot hash tags */
|
||||
for (j = 0; j < c->staglen; j++)
|
||||
c->stagptr[j] -= c->prefixlen;
|
||||
c->prefixlen = 0;
|
||||
}
|
||||
continue;
|
||||
@@ -628,7 +662,12 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
|
||||
c->prefix_pending = 0;
|
||||
if (config.auth) {
|
||||
char *buf = NULL;
|
||||
int len = redisFormatCommand(&buf, "AUTH %s", config.auth);
|
||||
int len;
|
||||
if (config.user == NULL)
|
||||
len = redisFormatCommand(&buf, "AUTH %s", config.auth);
|
||||
else
|
||||
len = redisFormatCommand(&buf, "AUTH %s %s",
|
||||
config.user, config.auth);
|
||||
c->obuf = sdscatlen(c->obuf, buf, len);
|
||||
free(buf);
|
||||
c->prefix_pending++;
|
||||
@@ -724,7 +763,7 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
|
||||
}
|
||||
c->stagptr[c->staglen++] = p;
|
||||
c->stagfree--;
|
||||
p += 5; /* 12 is strlen("{tag}"). */
|
||||
p += 5; /* 5 is strlen("{tag}"). */
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -985,16 +1024,8 @@ static int fetchClusterConfiguration() {
|
||||
int success = 1;
|
||||
redisContext *ctx = NULL;
|
||||
redisReply *reply = NULL;
|
||||
if (config.hostsocket == NULL)
|
||||
ctx = redisConnect(config.hostip,config.hostport);
|
||||
else
|
||||
ctx = redisConnectUnix(config.hostsocket);
|
||||
if (ctx->err) {
|
||||
fprintf(stderr,"Could not connect to Redis at ");
|
||||
if (config.hostsocket == NULL) {
|
||||
fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,
|
||||
ctx->errstr);
|
||||
} else fprintf(stderr,"%s: %s\n",config.hostsocket,ctx->errstr);
|
||||
ctx = getRedisContext(config.hostip, config.hostport, config.hostsocket);
|
||||
if (ctx == NULL) {
|
||||
exit(1);
|
||||
}
|
||||
clusterNode *firstNode = createClusterNode((char *) config.hostip,
|
||||
@@ -1190,11 +1221,9 @@ static int fetchClusterSlotsConfiguration(client c) {
|
||||
assert(node->port);
|
||||
/* Use first node as entry point to connect to. */
|
||||
if (ctx == NULL) {
|
||||
ctx = redisConnect(node->ip, node->port);
|
||||
if (!ctx || ctx->err) {
|
||||
ctx = getRedisContext(node->ip, node->port, NULL);
|
||||
if (!ctx) {
|
||||
success = 0;
|
||||
if (ctx && ctx->err)
|
||||
fprintf(stderr, "REDIS CONNECTION ERROR: %s\n", ctx->errstr);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
@@ -1269,6 +1298,17 @@ static void updateClusterSlotsConfiguration() {
|
||||
pthread_mutex_unlock(&config.is_updating_slots_mutex);
|
||||
}
|
||||
|
||||
/* Generate random data for redis benchmark. See #7196. */
|
||||
static void genBenchmarkRandomData(char *data, int count) {
|
||||
static uint32_t state = 1234;
|
||||
int i = 0;
|
||||
|
||||
while (count--) {
|
||||
state = (state*1103515245+12345);
|
||||
data[i++] = '0'+((state>>16)&63);
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns number of consumed options. */
|
||||
int parseOptions(int argc, const char **argv) {
|
||||
int i;
|
||||
@@ -1299,6 +1339,9 @@ int parseOptions(int argc, const char **argv) {
|
||||
} else if (!strcmp(argv[i],"-a") ) {
|
||||
if (lastarg) goto invalid;
|
||||
config.auth = strdup(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--user")) {
|
||||
if (lastarg) goto invalid;
|
||||
config.user = argv[++i];
|
||||
} else if (!strcmp(argv[i],"-d")) {
|
||||
if (lastarg) goto invalid;
|
||||
config.datasize = atoi(argv[++i]);
|
||||
@@ -1385,6 +1428,7 @@ usage:
|
||||
" -p <port> Server port (default 6379)\n"
|
||||
" -s <socket> Server socket (overrides host and port)\n"
|
||||
" -a <password> Password for Redis Auth\n"
|
||||
" --user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
|
||||
" -c <clients> Number of parallel connections (default 50)\n"
|
||||
" -n <requests> Total number of requests (default 100000)\n"
|
||||
" -d <size> Data size of SET/GET value in bytes (default 3)\n"
|
||||
@@ -1393,7 +1437,8 @@ usage:
|
||||
" --cluster Enable cluster mode.\n"
|
||||
" --enable-tracking Send CLIENT TRACKING on before starting benchmark.\n"
|
||||
" -k <boolean> 1=keep alive 0=reconnect (default 1)\n"
|
||||
" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD\n"
|
||||
" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD,\n"
|
||||
" random members and scores for ZADD.\n"
|
||||
" Using this option the benchmark will expand the string __rand_int__\n"
|
||||
" inside an argument with a 12 digits number in the specified range\n"
|
||||
" from 0 to keyspacelen-1. The substitution changes every time a command\n"
|
||||
@@ -1474,12 +1519,13 @@ int test_is_selected(char *name) {
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
int i;
|
||||
char *data, *cmd;
|
||||
char *data, *cmd, *tag;
|
||||
int len;
|
||||
|
||||
client c;
|
||||
|
||||
srandom(time(NULL));
|
||||
srandom(time(NULL) ^ getpid());
|
||||
init_genrand64(ustime() ^ getpid());
|
||||
signal(SIGHUP, SIG_IGN);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
@@ -1524,7 +1570,12 @@ int main(int argc, const char **argv) {
|
||||
|
||||
config.latency = zmalloc(sizeof(long long)*config.requests);
|
||||
|
||||
tag = "";
|
||||
|
||||
if (config.cluster_mode) {
|
||||
// We only include the slot placeholder {tag} if cluster mode is enabled
|
||||
tag = ":{tag}";
|
||||
|
||||
/* Fetch cluster configuration. */
|
||||
if (!fetchClusterConfiguration() || !config.cluster_nodes) {
|
||||
if (!config.hostsocket) {
|
||||
@@ -1619,7 +1670,7 @@ int main(int argc, const char **argv) {
|
||||
/* Run default benchmark suite. */
|
||||
data = zmalloc(config.datasize+1);
|
||||
do {
|
||||
memset(data,'x',config.datasize);
|
||||
genBenchmarkRandomData(data, config.datasize);
|
||||
data[config.datasize] = '\0';
|
||||
|
||||
if (test_is_selected("ping_inline") || test_is_selected("ping"))
|
||||
@@ -1632,112 +1683,129 @@ int main(int argc, const char **argv) {
|
||||
}
|
||||
|
||||
if (test_is_selected("set")) {
|
||||
len = redisFormatCommand(&cmd,"SET key:{tag}:__rand_int__ %s",data);
|
||||
len = redisFormatCommand(&cmd,"SET key%s:__rand_int__ %s",tag,data);
|
||||
benchmark("SET",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("get")) {
|
||||
len = redisFormatCommand(&cmd,"GET key:{tag}:__rand_int__");
|
||||
len = redisFormatCommand(&cmd,"GET key%s:__rand_int__",tag);
|
||||
benchmark("GET",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("incr")) {
|
||||
len = redisFormatCommand(&cmd,"INCR counter:{tag}:__rand_int__");
|
||||
len = redisFormatCommand(&cmd,"INCR counter%s:__rand_int__",tag);
|
||||
benchmark("INCR",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("lpush")) {
|
||||
len = redisFormatCommand(&cmd,"LPUSH mylist:{tag} %s",data);
|
||||
len = redisFormatCommand(&cmd,"LPUSH mylist%s %s",tag,data);
|
||||
benchmark("LPUSH",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("rpush")) {
|
||||
len = redisFormatCommand(&cmd,"RPUSH mylist:{tag} %s",data);
|
||||
len = redisFormatCommand(&cmd,"RPUSH mylist%s %s",tag,data);
|
||||
benchmark("RPUSH",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("lpop")) {
|
||||
len = redisFormatCommand(&cmd,"LPOP mylist:{tag}");
|
||||
len = redisFormatCommand(&cmd,"LPOP mylist%s",tag);
|
||||
benchmark("LPOP",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("rpop")) {
|
||||
len = redisFormatCommand(&cmd,"RPOP mylist:{tag}");
|
||||
len = redisFormatCommand(&cmd,"RPOP mylist%s",tag);
|
||||
benchmark("RPOP",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("sadd")) {
|
||||
len = redisFormatCommand(&cmd,
|
||||
"SADD myset:{tag} element:__rand_int__");
|
||||
"SADD myset%s element:__rand_int__",tag);
|
||||
benchmark("SADD",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("hset")) {
|
||||
len = redisFormatCommand(&cmd,
|
||||
"HSET myhash:{tag}:__rand_int__ element:__rand_int__ %s",data);
|
||||
"HSET myhash%s element:__rand_int__ %s",tag,data);
|
||||
benchmark("HSET",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("spop")) {
|
||||
len = redisFormatCommand(&cmd,"SPOP myset:{tag}");
|
||||
len = redisFormatCommand(&cmd,"SPOP myset%s",tag);
|
||||
benchmark("SPOP",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("zadd")) {
|
||||
char *score = "0";
|
||||
if (config.randomkeys) score = "__rand_int__";
|
||||
len = redisFormatCommand(&cmd,
|
||||
"ZADD myzset%s %s element:__rand_int__",tag,score);
|
||||
benchmark("ZADD",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("zpopmin")) {
|
||||
len = redisFormatCommand(&cmd,"ZPOPMIN myzset%s",tag);
|
||||
benchmark("ZPOPMIN",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("lrange") ||
|
||||
test_is_selected("lrange_100") ||
|
||||
test_is_selected("lrange_300") ||
|
||||
test_is_selected("lrange_500") ||
|
||||
test_is_selected("lrange_600"))
|
||||
{
|
||||
len = redisFormatCommand(&cmd,"LPUSH mylist:{tag} %s",data);
|
||||
len = redisFormatCommand(&cmd,"LPUSH mylist%s %s",tag,data);
|
||||
benchmark("LPUSH (needed to benchmark LRANGE)",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("lrange") || test_is_selected("lrange_100")) {
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist:{tag} 0 99");
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 99",tag);
|
||||
benchmark("LRANGE_100 (first 100 elements)",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("lrange") || test_is_selected("lrange_300")) {
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist:{tag} 0 299");
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 299",tag);
|
||||
benchmark("LRANGE_300 (first 300 elements)",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("lrange") || test_is_selected("lrange_500")) {
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist:{tag} 0 449");
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 449",tag);
|
||||
benchmark("LRANGE_500 (first 450 elements)",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("lrange") || test_is_selected("lrange_600")) {
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist:{tag} 0 599");
|
||||
len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 599",tag);
|
||||
benchmark("LRANGE_600 (first 600 elements)",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("mset")) {
|
||||
const char *argv[21];
|
||||
argv[0] = "MSET";
|
||||
const char *cmd_argv[21];
|
||||
cmd_argv[0] = "MSET";
|
||||
sds key_placeholder = sdscatprintf(sdsnew(""),"key%s:__rand_int__",tag);
|
||||
for (i = 1; i < 21; i += 2) {
|
||||
argv[i] = "key:{tag}:__rand_int__";
|
||||
argv[i+1] = data;
|
||||
cmd_argv[i] = key_placeholder;
|
||||
cmd_argv[i+1] = data;
|
||||
}
|
||||
len = redisFormatCommandArgv(&cmd,21,argv,NULL);
|
||||
len = redisFormatCommandArgv(&cmd,21,cmd_argv,NULL);
|
||||
benchmark("MSET (10 keys)",cmd,len);
|
||||
free(cmd);
|
||||
sdsfree(key_placeholder);
|
||||
}
|
||||
|
||||
if (!config.csv) printf("\n");
|
||||
|
||||
+30
-4
@@ -27,10 +27,13 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "mt19937-64.h"
|
||||
#include "server.h"
|
||||
#include "rdb.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void createSharedObjects(void);
|
||||
void rdbLoadProgressCallback(rio *r, const void *buf, size_t len);
|
||||
@@ -58,6 +61,7 @@ struct {
|
||||
#define RDB_CHECK_DOING_CHECK_SUM 5
|
||||
#define RDB_CHECK_DOING_READ_LEN 6
|
||||
#define RDB_CHECK_DOING_READ_AUX 7
|
||||
#define RDB_CHECK_DOING_READ_MODULE_AUX 8
|
||||
|
||||
char *rdb_check_doing_string[] = {
|
||||
"start",
|
||||
@@ -67,7 +71,8 @@ char *rdb_check_doing_string[] = {
|
||||
"read-object-value",
|
||||
"check-sum",
|
||||
"read-len",
|
||||
"read-aux"
|
||||
"read-aux",
|
||||
"read-module-aux"
|
||||
};
|
||||
|
||||
char *rdb_type_string[] = {
|
||||
@@ -244,7 +249,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_LEN;
|
||||
if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR)
|
||||
goto eoferr;
|
||||
rdbCheckInfo("Selecting DB ID %d", dbid);
|
||||
rdbCheckInfo("Selecting DB ID %llu", (unsigned long long)dbid);
|
||||
continue; /* Read type again. */
|
||||
} else if (type == RDB_OPCODE_RESIZEDB) {
|
||||
/* RESIZEDB: Hint about the size of the keys in the currently
|
||||
@@ -272,6 +277,21 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
decrRefCount(auxkey);
|
||||
decrRefCount(auxval);
|
||||
continue; /* Read type again. */
|
||||
} else if (type == RDB_OPCODE_MODULE_AUX) {
|
||||
/* AUX: Auxiliary data for modules. */
|
||||
uint64_t moduleid, when_opcode, when;
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_MODULE_AUX;
|
||||
if ((moduleid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
if ((when_opcode = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
if ((when = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr;
|
||||
|
||||
char name[10];
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
rdbCheckInfo("MODULE AUX for: %s", name);
|
||||
|
||||
robj *o = rdbLoadCheckModuleValue(&rdb,name);
|
||||
decrRefCount(o);
|
||||
continue; /* Read type again. */
|
||||
} else {
|
||||
if (!rdbIsObjectType(type)) {
|
||||
rdbCheckError("Invalid object type: %d", type);
|
||||
@@ -287,7 +307,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.keys++;
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb,key)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,&rdb,key->ptr)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
@@ -331,7 +351,7 @@ err:
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* RDB check main: called form redis.c when Redis is executed with the
|
||||
/* RDB check main: called form server.c when Redis is executed with the
|
||||
* redis-check-rdb alias, on during RDB loading errors.
|
||||
*
|
||||
* The function works in two ways: can be called with argc/argv as a
|
||||
@@ -344,10 +364,16 @@ err:
|
||||
* Otherwise if called with a non NULL fp, the function returns C_OK or
|
||||
* C_ERR depending on the success or failure. */
|
||||
int redis_check_rdb_main(int argc, char **argv, FILE *fp) {
|
||||
struct timeval tv;
|
||||
|
||||
if (argc != 2 && fp == NULL) {
|
||||
fprintf(stderr, "Usage: %s <rdb-file-name>\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
init_genrand64(((long long) tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid());
|
||||
|
||||
/* In order to call the loading functions we need to create the shared
|
||||
* integer objects, however since this function may be called from
|
||||
* an already initialized Redis instance, check if we really need to. */
|
||||
|
||||
+392
-134
File diff suppressed because it is too large
Load Diff
+340
-235
@@ -110,6 +110,15 @@
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE (1<<17)
|
||||
/* There is currently some background process active. */
|
||||
#define REDISMODULE_CTX_FLAGS_ACTIVE_CHILD (1<<18)
|
||||
/* The next EXEC will fail due to dirty CAS (touched keys). */
|
||||
#define REDISMODULE_CTX_FLAGS_MULTI_DIRTY (1<<19)
|
||||
/* Redis is currently running inside background child process. */
|
||||
#define REDISMODULE_CTX_FLAGS_IS_CHILD (1<<20)
|
||||
|
||||
/* Next context flag, must be updated when adding new flags above!
|
||||
This flag should not be used directly by the module.
|
||||
* Use RedisModule_GetContextFlagsAll instead. */
|
||||
#define _REDISMODULE_CTX_FLAGS_NEXT (1<<21)
|
||||
|
||||
/* Keyspace changes notification classes. Every class is associated with a
|
||||
* character for configuration purposes.
|
||||
@@ -125,9 +134,15 @@
|
||||
#define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */
|
||||
#define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */
|
||||
#define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */
|
||||
#define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m */
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_KEY_MISS) /* A */
|
||||
#define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from REDISMODULE_NOTIFY_ALL on purpose) */
|
||||
#define REDISMODULE_NOTIFY_LOADED (1<<12) /* module only key space notification, indicate a key loaded from rdb */
|
||||
|
||||
/* Next notification flag, must be updated when adding new flags above!
|
||||
This flag should not be used directly by the module.
|
||||
* Use RedisModule_GetKeyspaceNotificationFlagsAll instead. */
|
||||
#define _REDISMODULE_NOTIFY_NEXT (1<<13)
|
||||
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */
|
||||
|
||||
/* A special pointer that we can use between the core and the module to signal
|
||||
* field deletion, and that is impossible to be a valid pointer. */
|
||||
@@ -176,7 +191,9 @@ typedef uint64_t RedisModuleTimerID;
|
||||
* are modified from the user's sperspective, to invalidate WATCH. */
|
||||
#define REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED (1<<1)
|
||||
|
||||
/* Server events definitions. */
|
||||
/* Server events definitions.
|
||||
* Those flags should not be used directly by the module, instead
|
||||
* the module should use RedisModuleEvent_* variables */
|
||||
#define REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED 0
|
||||
#define REDISMODULE_EVENT_PERSISTENCE 1
|
||||
#define REDISMODULE_EVENT_FLUSHDB 2
|
||||
@@ -188,6 +205,10 @@ typedef uint64_t RedisModuleTimerID;
|
||||
#define REDISMODULE_EVENT_CRON_LOOP 8
|
||||
#define REDISMODULE_EVENT_MODULE_CHANGE 9
|
||||
#define REDISMODULE_EVENT_LOADING_PROGRESS 10
|
||||
#define REDISMODULE_EVENT_SWAPDB 11
|
||||
|
||||
/* Next event flag, should be updated if a new event added. */
|
||||
#define _REDISMODULE_EVENT_NEXT 12
|
||||
|
||||
typedef struct RedisModuleEvent {
|
||||
uint64_t id; /* REDISMODULE_EVENT_... defines. */
|
||||
@@ -241,6 +262,10 @@ static const RedisModuleEvent
|
||||
RedisModuleEvent_LoadingProgress = {
|
||||
REDISMODULE_EVENT_LOADING_PROGRESS,
|
||||
1
|
||||
},
|
||||
RedisModuleEvent_SwapDB = {
|
||||
REDISMODULE_EVENT_SWAPDB,
|
||||
1
|
||||
};
|
||||
|
||||
/* Those are values that are used for the 'subevent' callback argument. */
|
||||
@@ -249,33 +274,47 @@ static const RedisModuleEvent
|
||||
#define REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START 2
|
||||
#define REDISMODULE_SUBEVENT_PERSISTENCE_ENDED 3
|
||||
#define REDISMODULE_SUBEVENT_PERSISTENCE_FAILED 4
|
||||
#define _REDISMODULE_SUBEVENT_PERSISTENCE_NEXT 5
|
||||
|
||||
#define REDISMODULE_SUBEVENT_LOADING_RDB_START 0
|
||||
#define REDISMODULE_SUBEVENT_LOADING_AOF_START 1
|
||||
#define REDISMODULE_SUBEVENT_LOADING_REPL_START 2
|
||||
#define REDISMODULE_SUBEVENT_LOADING_ENDED 3
|
||||
#define REDISMODULE_SUBEVENT_LOADING_FAILED 4
|
||||
#define _REDISMODULE_SUBEVENT_LOADING_NEXT 5
|
||||
|
||||
#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED 0
|
||||
#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED 1
|
||||
#define _REDISMODULE_SUBEVENT_CLIENT_CHANGE_NEXT 2
|
||||
|
||||
#define REDISMODULE_SUBEVENT_MASTER_LINK_UP 0
|
||||
#define REDISMODULE_SUBEVENT_MASTER_LINK_DOWN 1
|
||||
#define _REDISMODULE_SUBEVENT_MASTER_NEXT 2
|
||||
|
||||
#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE 0
|
||||
#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_OFFLINE 1
|
||||
#define _REDISMODULE_SUBEVENT_REPLICA_CHANGE_NEXT 2
|
||||
|
||||
#define REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER 0
|
||||
#define REDISMODULE_EVENT_REPLROLECHANGED_NOW_REPLICA 1
|
||||
#define _REDISMODULE_EVENT_REPLROLECHANGED_NEXT 2
|
||||
|
||||
#define REDISMODULE_SUBEVENT_FLUSHDB_START 0
|
||||
#define REDISMODULE_SUBEVENT_FLUSHDB_END 1
|
||||
#define _REDISMODULE_SUBEVENT_FLUSHDB_NEXT 2
|
||||
|
||||
#define REDISMODULE_SUBEVENT_MODULE_LOADED 0
|
||||
#define REDISMODULE_SUBEVENT_MODULE_UNLOADED 1
|
||||
#define _REDISMODULE_SUBEVENT_MODULE_NEXT 2
|
||||
|
||||
|
||||
#define REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB 0
|
||||
#define REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF 1
|
||||
#define _REDISMODULE_SUBEVENT_LOADING_PROGRESS_NEXT 2
|
||||
|
||||
#define _REDISMODULE_SUBEVENT_SHUTDOWN_NEXT 0
|
||||
#define _REDISMODULE_SUBEVENT_CRON_LOOP_NEXT 0
|
||||
#define _REDISMODULE_SUBEVENT_SWAPDB_NEXT 0
|
||||
|
||||
/* RedisModuleClientInfo flags. */
|
||||
#define REDISMODULE_CLIENTINFO_FLAG_SSL (1<<0)
|
||||
@@ -372,12 +411,48 @@ typedef struct RedisModuleLoadingProgressInfo {
|
||||
|
||||
#define RedisModuleLoadingProgress RedisModuleLoadingProgressV1
|
||||
|
||||
#define REDISMODULE_SWAPDBINFO_VERSION 1
|
||||
typedef struct RedisModuleSwapDbInfo {
|
||||
uint64_t version; /* Not used since this structure is never passed
|
||||
from the module to the core right now. Here
|
||||
for future compatibility. */
|
||||
int32_t dbnum_first; /* Swap Db first dbnum */
|
||||
int32_t dbnum_second; /* Swap Db second dbnum */
|
||||
} RedisModuleSwapDbInfoV1;
|
||||
|
||||
#define RedisModuleSwapDbInfo RedisModuleSwapDbInfoV1
|
||||
|
||||
/* ------------------------- End of common defines ------------------------ */
|
||||
|
||||
#ifndef REDISMODULE_CORE
|
||||
|
||||
typedef long long mstime_t;
|
||||
|
||||
/* Macro definitions specific to individual compilers */
|
||||
#ifndef REDISMODULE_ATTR_UNUSED
|
||||
# ifdef __GNUC__
|
||||
# define REDISMODULE_ATTR_UNUSED __attribute__((unused))
|
||||
# else
|
||||
# define REDISMODULE_ATTR_UNUSED
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef REDISMODULE_ATTR_PRINTF
|
||||
# ifdef __GNUC__
|
||||
# define REDISMODULE_ATTR_PRINTF(idx,cnt) __attribute__((format(printf,idx,cnt)))
|
||||
# else
|
||||
# define REDISMODULE_ATTR_PRINTF(idx,cnt)
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef REDISMODULE_ATTR_COMMON
|
||||
# if defined(__GNUC__) && !defined(__clang__)
|
||||
# define REDISMODULE_ATTR_COMMON __attribute__((__common__))
|
||||
# else
|
||||
# define REDISMODULE_ATTR_COMMON
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* Incomplete structures for compiler checks but opaque access. */
|
||||
typedef struct RedisModuleCtx RedisModuleCtx;
|
||||
typedef struct RedisModuleKey RedisModuleKey;
|
||||
@@ -434,246 +509,264 @@ typedef struct RedisModuleTypeMethods {
|
||||
#define REDISMODULE_GET_API(name) \
|
||||
RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name))
|
||||
|
||||
#define REDISMODULE_API_FUNC(x) (*x)
|
||||
/* Default API declaration prefix (not 'extern' for backwards compatibility) */
|
||||
#ifndef REDISMODULE_API
|
||||
#define REDISMODULE_API
|
||||
#endif
|
||||
|
||||
/* Default API declaration suffix (compiler attributes) */
|
||||
#ifndef REDISMODULE_ATTR
|
||||
#define REDISMODULE_ATTR REDISMODULE_ATTR_COMMON
|
||||
#endif
|
||||
|
||||
REDISMODULE_API void * (*RedisModule_Alloc)(size_t bytes) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void * (*RedisModule_Realloc)(void *ptr, size_t bytes) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_Free)(void *ptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void * (*RedisModule_Calloc)(size_t nmemb, size_t size) REDISMODULE_ATTR;
|
||||
REDISMODULE_API char * (*RedisModule_Strdup)(const char *str) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetApi)(const char *, void *) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_IsModuleNameBusy)(const char *name) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_WrongArity)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetSelectedDb)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void * (*RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_CloseKey)(RedisModuleKey *kp) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_KeyType)(RedisModuleKey *kp) REDISMODULE_ATTR;
|
||||
REDISMODULE_API size_t (*RedisModule_ValueLength)(RedisModuleKey *kp) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_ListPop)(RedisModuleKey *key, int where) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleCallReply * (*RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const char * (*RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_FreeCallReply)(RedisModuleCallReply *reply) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_CallReplyType)(RedisModuleCallReply *reply) REDISMODULE_ATTR;
|
||||
REDISMODULE_API long long (*RedisModule_CallReplyInteger)(RedisModuleCallReply *reply) REDISMODULE_ATTR;
|
||||
REDISMODULE_API size_t (*RedisModule_CallReplyLength)(RedisModuleCallReply *reply) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleCallReply * (*RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...) REDISMODULE_ATTR_PRINTF(2,3) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const char * (*RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithNullArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithEmptyArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithNull)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StringToDouble)(const RedisModuleString *str, double *d) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StringToLongDouble)(const RedisModuleString *str, long double *d) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_AutoMemory)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const char * (*RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DeleteKey)(RedisModuleKey *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_UnlinkKey)(RedisModuleKey *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str) REDISMODULE_ATTR;
|
||||
REDISMODULE_API char * (*RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen) REDISMODULE_ATTR;
|
||||
REDISMODULE_API mstime_t (*RedisModule_GetExpire)(RedisModuleKey *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_ResetDataset)(int restart_aof, int async) REDISMODULE_ATTR;
|
||||
REDISMODULE_API unsigned long long (*RedisModule_DbSize)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_RandomKey)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_ZsetRangeStop)(RedisModuleKey *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ZsetRangeNext)(RedisModuleKey *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ZsetRangePrev)(RedisModuleKey *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ZsetRangeEndReached)(RedisModuleKey *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_HashSet)(RedisModuleKey *key, int flags, ...) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_HashGet)(RedisModuleKey *key, int flags, ...) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos) REDISMODULE_ATTR;
|
||||
REDISMODULE_API unsigned long long (*RedisModule_GetClientId)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetClientInfoById)(void *ci, uint64_t id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetContextFlags)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_AvoidReplicaTraffic)() REDISMODULE_ATTR;
|
||||
REDISMODULE_API void * (*RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleType * (*RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ModuleTypeReplaceValue)(RedisModuleKey *key, RedisModuleType *mt, void *new_value, void **old_value) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleType * (*RedisModule_ModuleTypeGetType)(RedisModuleKey *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void * (*RedisModule_ModuleTypeGetValue)(RedisModuleKey *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_IsIOError)(RedisModuleIO *io) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SetModuleOptions)(RedisModuleCtx *ctx, int options) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SignalModifiedKey)(RedisModuleCtx *ctx, RedisModuleString *keyname) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value) REDISMODULE_ATTR;
|
||||
REDISMODULE_API uint64_t (*RedisModule_LoadUnsigned)(RedisModuleIO *io) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int64_t (*RedisModule_LoadSigned)(RedisModuleIO *io) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_EmitAOF)(RedisModuleIO *io, const char *cmdname, const char *fmt, ...) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SaveString)(RedisModuleIO *io, RedisModuleString *s) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SaveStringBuffer)(RedisModuleIO *io, const char *str, size_t len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_LoadString)(RedisModuleIO *io) REDISMODULE_ATTR;
|
||||
REDISMODULE_API char * (*RedisModule_LoadStringBuffer)(RedisModuleIO *io, size_t *lenptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SaveDouble)(RedisModuleIO *io, double value) REDISMODULE_ATTR;
|
||||
REDISMODULE_API double (*RedisModule_LoadDouble)(RedisModuleIO *io) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SaveFloat)(RedisModuleIO *io, float value) REDISMODULE_ATTR;
|
||||
REDISMODULE_API float (*RedisModule_LoadFloat)(RedisModuleIO *io) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SaveLongDouble)(RedisModuleIO *io, long double value) REDISMODULE_ATTR;
|
||||
REDISMODULE_API long double (*RedisModule_LoadLongDouble)(RedisModuleIO *io) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void * (*RedisModule_LoadDataTypeFromString)(const RedisModuleString *str, const RedisModuleType *mt) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_SaveDataTypeToString)(RedisModuleCtx *ctx, void *data, const RedisModuleType *mt) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...) REDISMODULE_ATTR REDISMODULE_ATTR_PRINTF(3,4);
|
||||
REDISMODULE_API void (*RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...) REDISMODULE_ATTR REDISMODULE_ATTR_PRINTF(3,4);
|
||||
REDISMODULE_API void (*RedisModule__Assert)(const char *estr, const char *file, int line) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_LatencyAddSample)(const char *event, mstime_t latency) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_HoldString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleCtx * (*RedisModule_GetContextFromIO)(RedisModuleIO *io) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromIO)(RedisModuleIO *io) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromModuleKey)(RedisModuleKey *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API long long (*RedisModule_Milliseconds)(void) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_DigestEndSequence)(RedisModuleDigest *md) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleDict * (*RedisModule_CreateDict)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_FreeDict)(RedisModuleCtx *ctx, RedisModuleDict *d) REDISMODULE_ATTR;
|
||||
REDISMODULE_API uint64_t (*RedisModule_DictSize)(RedisModuleDict *d) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DictSetC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DictReplaceC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DictSet)(RedisModuleDict *d, RedisModuleString *key, void *ptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DictReplace)(RedisModuleDict *d, RedisModuleString *key, void *ptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void * (*RedisModule_DictGetC)(RedisModuleDict *d, void *key, size_t keylen, int *nokey) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void * (*RedisModule_DictGet)(RedisModuleDict *d, RedisModuleString *key, int *nokey) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DictDelC)(RedisModuleDict *d, void *key, size_t keylen, void *oldval) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DictDel)(RedisModuleDict *d, RedisModuleString *key, void *oldval) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleDictIter * (*RedisModule_DictIteratorStartC)(RedisModuleDict *d, const char *op, void *key, size_t keylen) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleDictIter * (*RedisModule_DictIteratorStart)(RedisModuleDict *d, const char *op, RedisModuleString *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_DictIteratorStop)(RedisModuleDictIter *di) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DictIteratorReseekC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DictIteratorReseek)(RedisModuleDictIter *di, const char *op, RedisModuleString *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void * (*RedisModule_DictNextC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void * (*RedisModule_DictPrevC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_DictNext)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_RegisterInfoFunc)(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_InfoAddSection)(RedisModuleInfoCtx *ctx, char *name) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_InfoBeginDictField)(RedisModuleInfoCtx *ctx, char *name) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_InfoEndDictField)(RedisModuleInfoCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_InfoAddFieldString)(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ctx, char *field, char *value) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleServerInfoData * (*RedisModule_GetServerInfo)(RedisModuleCtx *ctx, const char *section) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_FreeServerInfo)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_ServerInfoGetField)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const char * (*RedisModule_ServerInfoGetFieldC)(RedisModuleServerInfoData *data, const char* field) REDISMODULE_ATTR;
|
||||
REDISMODULE_API long long (*RedisModule_ServerInfoGetFieldSigned)(RedisModuleServerInfoData *data, const char* field, int *out_err) REDISMODULE_ATTR;
|
||||
REDISMODULE_API unsigned long long (*RedisModule_ServerInfoGetFieldUnsigned)(RedisModuleServerInfoData *data, const char* field, int *out_err) REDISMODULE_ATTR;
|
||||
REDISMODULE_API double (*RedisModule_ServerInfoGetFieldDouble)(RedisModuleServerInfoData *data, const char* field, int *out_err) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SubscribeToServerEvent)(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SetLRU)(RedisModuleKey *key, mstime_t lru_idle) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetLRU)(RedisModuleKey *key, mstime_t *lru_idle) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SetLFU)(RedisModuleKey *key, long long lfu_freq) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetLFU)(RedisModuleKey *key, long long *lfu_freq) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_BlockClientOnKeys)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SignalKeyAsReady)(RedisModuleCtx *ctx, RedisModuleString *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_GetBlockedClientReadyKey)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleScanCursor * (*RedisModule_ScanCursorCreate)() REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_ScanCursorRestart)(RedisModuleScanCursor *cursor) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_ScanCursorDestroy)(RedisModuleScanCursor *cursor) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_Scan)(RedisModuleCtx *ctx, RedisModuleScanCursor *cursor, RedisModuleScanCB fn, void *privdata) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ScanKey)(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleScanKeyCB fn, void *privdata) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetContextFlagsAll)() REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetKeyspaceNotificationFlagsAll)() REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_IsSubEventSupported)(RedisModuleEvent event, uint64_t subevent) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetServerVersion)() REDISMODULE_ATTR;
|
||||
|
||||
void *REDISMODULE_API_FUNC(RedisModule_Alloc)(size_t bytes);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_Realloc)(void *ptr, size_t bytes);
|
||||
void REDISMODULE_API_FUNC(RedisModule_Free)(void *ptr);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_Calloc)(size_t nmemb, size_t size);
|
||||
char *REDISMODULE_API_FUNC(RedisModule_Strdup)(const char *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetApi)(const char *, void *);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsModuleNameBusy)(const char *name);
|
||||
int REDISMODULE_API_FUNC(RedisModule_WrongArity)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetSelectedDb)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode);
|
||||
void REDISMODULE_API_FUNC(RedisModule_CloseKey)(RedisModuleKey *kp);
|
||||
int REDISMODULE_API_FUNC(RedisModule_KeyType)(RedisModuleKey *kp);
|
||||
size_t REDISMODULE_API_FUNC(RedisModule_ValueLength)(RedisModuleKey *kp);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ListPop)(RedisModuleKey *key, int where);
|
||||
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeCallReply)(RedisModuleCallReply *reply);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CallReplyType)(RedisModuleCallReply *reply);
|
||||
long long REDISMODULE_API_FUNC(RedisModule_CallReplyInteger)(RedisModuleCallReply *reply);
|
||||
size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *reply);
|
||||
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNullArray)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithEmptyArray)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(const RedisModuleString *str, double *d);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringToLongDouble)(const RedisModuleString *str, long double *d);
|
||||
void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DeleteKey)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_UnlinkKey)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str);
|
||||
char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);
|
||||
mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ResetDataset)(int restart_aof, int async);
|
||||
unsigned long long REDISMODULE_API_FUNC(RedisModule_DbSize)(RedisModuleCtx *ctx);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_RandomKey)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ZsetRangeStop)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetRangeNext)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetRangePrev)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ZsetRangeEndReached)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_HashSet)(RedisModuleKey *key, int flags, ...);
|
||||
int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ...);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos);
|
||||
unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetClientInfoById)(void *ci, uint64_t id);
|
||||
int REDISMODULE_API_FUNC(RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AvoidReplicaTraffic)();
|
||||
void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes);
|
||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeReplaceValue)(RedisModuleKey *key, RedisModuleType *mt, void *new_value, void **old_value);
|
||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsIOError)(RedisModuleIO *io);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetModuleOptions)(RedisModuleCtx *ctx, int options);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SignalModifiedKey)(RedisModuleCtx *ctx, RedisModuleString *keyname);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value);
|
||||
uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value);
|
||||
int64_t REDISMODULE_API_FUNC(RedisModule_LoadSigned)(RedisModuleIO *io);
|
||||
void REDISMODULE_API_FUNC(RedisModule_EmitAOF)(RedisModuleIO *io, const char *cmdname, const char *fmt, ...);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveString)(RedisModuleIO *io, RedisModuleString *s);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveStringBuffer)(RedisModuleIO *io, const char *str, size_t len);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_LoadString)(RedisModuleIO *io);
|
||||
char *REDISMODULE_API_FUNC(RedisModule_LoadStringBuffer)(RedisModuleIO *io, size_t *lenptr);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveDouble)(RedisModuleIO *io, double value);
|
||||
double REDISMODULE_API_FUNC(RedisModule_LoadDouble)(RedisModuleIO *io);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value);
|
||||
float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveLongDouble)(RedisModuleIO *io, long double value);
|
||||
long double REDISMODULE_API_FUNC(RedisModule_LoadLongDouble)(RedisModuleIO *io);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_LoadDataTypeFromString)(const RedisModuleString *str, const RedisModuleType *mt);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_SaveDataTypeToString)(RedisModuleCtx *ctx, void *data, const RedisModuleType *mt);
|
||||
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
|
||||
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
|
||||
void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line);
|
||||
void REDISMODULE_API_FUNC(RedisModule_LatencyAddSample)(const char *event, mstime_t latency);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
|
||||
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io);
|
||||
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromIO)(RedisModuleIO *io);
|
||||
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromModuleKey)(RedisModuleKey *key);
|
||||
long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DigestEndSequence)(RedisModuleDigest *md);
|
||||
RedisModuleDict *REDISMODULE_API_FUNC(RedisModule_CreateDict)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeDict)(RedisModuleCtx *ctx, RedisModuleDict *d);
|
||||
uint64_t REDISMODULE_API_FUNC(RedisModule_DictSize)(RedisModuleDict *d);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictSetC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictReplaceC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictSet)(RedisModuleDict *d, RedisModuleString *key, void *ptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictReplace)(RedisModuleDict *d, RedisModuleString *key, void *ptr);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_DictGetC)(RedisModuleDict *d, void *key, size_t keylen, int *nokey);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_DictGet)(RedisModuleDict *d, RedisModuleString *key, int *nokey);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictDelC)(RedisModuleDict *d, void *key, size_t keylen, void *oldval);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictDel)(RedisModuleDict *d, RedisModuleString *key, void *oldval);
|
||||
RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStartC)(RedisModuleDict *d, const char *op, void *key, size_t keylen);
|
||||
RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStart)(RedisModuleDict *d, const char *op, RedisModuleString *key);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DictIteratorStop)(RedisModuleDictIter *di);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseekC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseek)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_DictNextC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_DictPrevC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictNext)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
|
||||
int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_RegisterInfoFunc)(RedisModuleCtx *ctx, RedisModuleInfoFunc cb);
|
||||
int REDISMODULE_API_FUNC(RedisModule_InfoAddSection)(RedisModuleInfoCtx *ctx, char *name);
|
||||
int REDISMODULE_API_FUNC(RedisModule_InfoBeginDictField)(RedisModuleInfoCtx *ctx, char *name);
|
||||
int REDISMODULE_API_FUNC(RedisModule_InfoEndDictField)(RedisModuleInfoCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldString)(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value);
|
||||
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ctx, char *field, char *value);
|
||||
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value);
|
||||
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value);
|
||||
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value);
|
||||
RedisModuleServerInfoData *REDISMODULE_API_FUNC(RedisModule_GetServerInfo)(RedisModuleCtx *ctx, const char *section);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeServerInfo)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ServerInfoGetField)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldC)(RedisModuleServerInfoData *data, const char* field);
|
||||
long long REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldSigned)(RedisModuleServerInfoData *data, const char* field, int *out_err);
|
||||
unsigned long long REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldUnsigned)(RedisModuleServerInfoData *data, const char* field, int *out_err);
|
||||
double REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldDouble)(RedisModuleServerInfoData *data, const char* field, int *out_err);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SubscribeToServerEvent)(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetLRU)(RedisModuleKey *key, mstime_t lru_idle);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetLRU)(RedisModuleKey *key, mstime_t *lru_idle);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetLFU)(RedisModuleKey *key, long long lfu_freq);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetLFU)(RedisModuleKey *key, long long *lfu_freq);
|
||||
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClientOnKeys)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SignalKeyAsReady)(RedisModuleCtx *ctx, RedisModuleString *key);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientReadyKey)(RedisModuleCtx *ctx);
|
||||
RedisModuleScanCursor *REDISMODULE_API_FUNC(RedisModule_ScanCursorCreate)();
|
||||
void REDISMODULE_API_FUNC(RedisModule_ScanCursorRestart)(RedisModuleScanCursor *cursor);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ScanCursorDestroy)(RedisModuleScanCursor *cursor);
|
||||
int REDISMODULE_API_FUNC(RedisModule_Scan)(RedisModuleCtx *ctx, RedisModuleScanCursor *cursor, RedisModuleScanCB fn, void *privdata);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ScanKey)(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleScanKeyCB fn, void *privdata);
|
||||
/* Experimental APIs */
|
||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||
#define REDISMODULE_EXPERIMENTAL_API_VERSION 3
|
||||
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms);
|
||||
int REDISMODULE_API_FUNC(RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx);
|
||||
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientHandle)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AbortBlock)(RedisModuleBlockedClient *bc);
|
||||
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetThreadSafeContext)(RedisModuleBlockedClient *bc);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb);
|
||||
int REDISMODULE_API_FUNC(RedisModule_NotifyKeyspaceEvent)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetNotifyKeyspaceEvents)();
|
||||
int REDISMODULE_API_FUNC(RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetClusterNodeInfo)(RedisModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags);
|
||||
char **REDISMODULE_API_FUNC(RedisModule_GetClusterNodesList)(RedisModuleCtx *ctx, size_t *numnodes);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeClusterNodesList)(char **ids);
|
||||
RedisModuleTimerID REDISMODULE_API_FUNC(RedisModule_CreateTimer)(RedisModuleCtx *ctx, mstime_t period, RedisModuleTimerProc callback, void *data);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StopTimer)(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetTimerInfo)(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_GetMyClusterID)(void);
|
||||
size_t REDISMODULE_API_FUNC(RedisModule_GetClusterSize)(void);
|
||||
void REDISMODULE_API_FUNC(RedisModule_GetRandomBytes)(unsigned char *dst, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_GetRandomHexChars)(char *dst, size_t len);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetDisconnectCallback)(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ExportSharedAPI)(RedisModuleCtx *ctx, const char *apiname, void *func);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_GetSharedAPI)(RedisModuleCtx *ctx, const char *apiname);
|
||||
RedisModuleCommandFilter *REDISMODULE_API_FUNC(RedisModule_RegisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc cb, int flags);
|
||||
int REDISMODULE_API_FUNC(RedisModule_UnregisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgsCount)(RedisModuleCommandFilterCtx *fctx);
|
||||
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos);
|
||||
int REDISMODULE_API_FUNC(RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ExitFromChild)(int retcode);
|
||||
int REDISMODULE_API_FUNC(RedisModule_KillForkChild)(int child_pid);
|
||||
float REDISMODULE_API_FUNC(RedisModule_GetUsedMemoryRatio)();
|
||||
size_t REDISMODULE_API_FUNC(RedisModule_MallocSize)(void* ptr);
|
||||
RedisModuleUser *REDISMODULE_API_FUNC(RedisModule_CreateModuleUser)(const char *name);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeModuleUser)(RedisModuleUser *user);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetModuleUserACL)(RedisModuleUser *user, const char* acl);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id);
|
||||
REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void * (*RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_GetBlockedClientHandle)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_AbortBlock)(RedisModuleBlockedClient *bc) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleCtx * (*RedisModule_GetThreadSafeContext)(RedisModuleBlockedClient *bc) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleCtx * (*RedisModule_GetDetachedThreadSafeContext)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ThreadSafeContextTryLock)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_NotifyKeyspaceEvent)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetNotifyKeyspaceEvents)() REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetClusterNodeInfo)(RedisModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags) REDISMODULE_ATTR;
|
||||
REDISMODULE_API char ** (*RedisModule_GetClusterNodesList)(RedisModuleCtx *ctx, size_t *numnodes) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_FreeClusterNodesList)(char **ids) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleTimerID (*RedisModule_CreateTimer)(RedisModuleCtx *ctx, mstime_t period, RedisModuleTimerProc callback, void *data) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_StopTimer)(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetTimerInfo)(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const char * (*RedisModule_GetMyClusterID)(void) REDISMODULE_ATTR;
|
||||
REDISMODULE_API size_t (*RedisModule_GetClusterSize)(void) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_GetRandomBytes)(unsigned char *dst, size_t len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_GetRandomHexChars)(char *dst, size_t len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SetDisconnectCallback)(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ExportSharedAPI)(RedisModuleCtx *ctx, const char *apiname, void *func) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void * (*RedisModule_GetSharedAPI)(RedisModuleCtx *ctx, const char *apiname) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleCommandFilter * (*RedisModule_RegisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc cb, int flags) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_UnregisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_CommandFilterArgsCount)(RedisModuleCommandFilterCtx *fctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API const RedisModuleString * (*RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ExitFromChild)(int retcode) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_KillForkChild)(int child_pid) REDISMODULE_ATTR;
|
||||
REDISMODULE_API float (*RedisModule_GetUsedMemoryRatio)() REDISMODULE_ATTR;
|
||||
REDISMODULE_API size_t (*RedisModule_MallocSize)(void* ptr) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleUser * (*RedisModule_CreateModuleUser)(const char *name) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_FreeModuleUser)(RedisModuleUser *user) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SetModuleUserACL)(RedisModuleUser *user, const char* acl) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleString * (*RedisModule_GetClientCertificate)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys) REDISMODULE_ATTR;
|
||||
#endif
|
||||
|
||||
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
|
||||
#define RedisModule_IsAOFClient(id) ((id) == CLIENT_ID_AOF)
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused));
|
||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR_UNUSED;
|
||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
|
||||
void *getapifuncptr = ((void**)ctx)[0];
|
||||
RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr;
|
||||
@@ -724,6 +817,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(CreateStringFromCallReply);
|
||||
REDISMODULE_GET_API(CreateString);
|
||||
REDISMODULE_GET_API(CreateStringFromLongLong);
|
||||
REDISMODULE_GET_API(CreateStringFromDouble);
|
||||
REDISMODULE_GET_API(CreateStringFromLongDouble);
|
||||
REDISMODULE_GET_API(CreateStringFromString);
|
||||
REDISMODULE_GET_API(CreateStringPrintf);
|
||||
@@ -794,6 +888,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(LatencyAddSample);
|
||||
REDISMODULE_GET_API(StringAppendBuffer);
|
||||
REDISMODULE_GET_API(RetainString);
|
||||
REDISMODULE_GET_API(HoldString);
|
||||
REDISMODULE_GET_API(StringCompare);
|
||||
REDISMODULE_GET_API(GetContextFromIO);
|
||||
REDISMODULE_GET_API(GetKeyNameFromIO);
|
||||
@@ -855,11 +950,17 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(ScanCursorDestroy);
|
||||
REDISMODULE_GET_API(Scan);
|
||||
REDISMODULE_GET_API(ScanKey);
|
||||
REDISMODULE_GET_API(GetContextFlagsAll);
|
||||
REDISMODULE_GET_API(GetKeyspaceNotificationFlagsAll);
|
||||
REDISMODULE_GET_API(IsSubEventSupported);
|
||||
REDISMODULE_GET_API(GetServerVersion);
|
||||
|
||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||
REDISMODULE_GET_API(GetThreadSafeContext);
|
||||
REDISMODULE_GET_API(GetDetachedThreadSafeContext);
|
||||
REDISMODULE_GET_API(FreeThreadSafeContext);
|
||||
REDISMODULE_GET_API(ThreadSafeContextLock);
|
||||
REDISMODULE_GET_API(ThreadSafeContextTryLock);
|
||||
REDISMODULE_GET_API(ThreadSafeContextUnlock);
|
||||
REDISMODULE_GET_API(BlockClient);
|
||||
REDISMODULE_GET_API(UnblockClient);
|
||||
@@ -906,6 +1007,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(DeauthenticateAndCloseClient);
|
||||
REDISMODULE_GET_API(AuthenticateClientWithACLUser);
|
||||
REDISMODULE_GET_API(AuthenticateClientWithUser);
|
||||
REDISMODULE_GET_API(GetClientCertificate);
|
||||
REDISMODULE_GET_API(GetCommandKeys);
|
||||
#endif
|
||||
|
||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||
@@ -915,6 +1018,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
|
||||
#define RedisModule_Assert(_e) ((_e)?(void)0 : (RedisModule__Assert(#_e,__FILE__,__LINE__),exit(1)))
|
||||
|
||||
#define RMAPI_FUNC_SUPPORTED(func) (func != NULL)
|
||||
|
||||
#else
|
||||
|
||||
/* Things only defined for the modules core, not exported to modules
|
||||
@@ -922,4 +1027,4 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
#define RedisModuleString robj
|
||||
|
||||
#endif /* REDISMODULE_CORE */
|
||||
#endif /* REDISMOUDLE_H */
|
||||
#endif /* REDISMODULE_H */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user