Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90e23adba5 | ||
|
|
896a68f51b | ||
|
|
0f3a54ad79 | ||
|
|
7877715eaf | ||
|
|
9cdb8889c2 | ||
|
|
5b885bb164 | ||
|
|
95f46e6077 | ||
|
|
31f58311e3 | ||
|
|
130613e166 | ||
|
|
50fd4fa7e6 | ||
|
|
e6a9a6b428 | ||
|
|
d6be93537e | ||
|
|
1ef35cf466 | ||
|
|
1eb0e3c1c6 | ||
|
|
e50313b207 | ||
|
|
ac3f8d1ac7 | ||
|
|
202b97eff7 | ||
|
|
f25aebf269 | ||
|
|
9b1e636a66 | ||
|
|
5147f0153a | ||
|
|
88f7739ae0 | ||
|
|
c0c033a0c1 | ||
|
|
73300c5700 | ||
|
|
37033179fe | ||
|
|
3e6a5ac9cf | ||
|
|
dcd297eb5b | ||
|
|
4a9742e696 | ||
|
|
8edbae2e04 | ||
|
|
63c6735ffc | ||
|
|
8a0441c144 | ||
|
|
460c5ed2b3 | ||
|
|
8f44f0937b | ||
|
|
1803b1587b | ||
|
|
a5325061f2 | ||
|
|
a10a0df09f | ||
|
|
031044c7ea | ||
|
|
ca755e4905 | ||
|
|
3063a028b3 | ||
|
|
2c659acc7d | ||
|
|
341a8caef9 | ||
|
|
366f8c2dc6 | ||
|
|
5803540422 | ||
|
|
f17c492a63 | ||
|
|
aad6a7ce15 | ||
|
|
1cb48c7bf6 | ||
|
|
f25c405ad2 | ||
|
|
c9920849dd | ||
|
|
81b240f81d | ||
|
|
6a902b29e1 | ||
|
|
9c31ac7925 | ||
|
|
f0d8d1680f | ||
|
|
6dcbcacb10 | ||
|
|
3196717cee | ||
|
|
f95578ac02 | ||
|
|
57ca6facc3 | ||
|
|
886d287c40 | ||
|
|
59cd4c9f65 | ||
|
|
6c1bb7b194 | ||
|
|
1aee695e52 | ||
|
|
a2ae463520 | ||
|
|
86e562d691 | ||
|
|
ab2984b1e2 | ||
|
|
1ff5a222de | ||
|
|
cc549b46a4 | ||
|
|
377dd0515c | ||
|
|
3f8d113f1b | ||
|
|
d4541349dc | ||
|
|
2112a57028 | ||
|
|
d2eb6e0b47 | ||
|
|
9c1df3b769 | ||
|
|
115ed1911e | ||
|
|
11c748aaca | ||
|
|
8f10137227 | ||
|
|
561a1f8d8a | ||
|
|
1449c8b9fe | ||
|
|
1938af27f5 | ||
|
|
2e591fc4a4 | ||
|
|
cbb51fb8f9 | ||
|
|
e0fc88b4d9 | ||
|
|
e3f864b5fd | ||
|
|
8af1e513f6 | ||
|
|
3c21418cd8 | ||
|
|
123bc125cf | ||
|
|
ecbe168b54 | ||
|
|
00cef78858 | ||
|
|
d05da0fabd | ||
|
|
4db3f2a443 | ||
|
|
fd710d590a | ||
|
|
9b98c9edd3 | ||
|
|
4893eedacb | ||
|
|
fb50163b47 | ||
|
|
3b8463f9d2 | ||
|
|
42c7677690 | ||
|
|
8665f25031 | ||
|
|
1c6b573147 | ||
|
|
90162da1a6 | ||
|
|
1ca6c1dd56 | ||
|
|
2d27c8ecdb | ||
|
|
59cda9cfb9 | ||
|
|
091af64003 | ||
|
|
a12210c875 | ||
|
|
43649fc5b4 | ||
|
|
01cc25653a | ||
|
|
a010e4a4b2 | ||
|
|
817a11ff11 | ||
|
|
c52e1fc78c | ||
|
|
d74871da40 | ||
|
|
a25cf15ecd | ||
|
|
e201f83cee | ||
|
|
58fc456cbd | ||
|
|
3febc5c29e | ||
|
|
8d67385593 | ||
|
|
7a35eec540 | ||
|
|
f93e1417b9 | ||
|
|
d0c9e44545 | ||
|
|
1cde6a0603 | ||
|
|
436be34986 | ||
|
|
f9d2ffdc5d | ||
|
|
d7968ee920 | ||
|
|
a902e6b251 | ||
|
|
9564ed7c39 | ||
|
|
2e41827435 | ||
|
|
eb2c8b2c61 | ||
|
|
928e6976bb | ||
|
|
cdcf5af5aa | ||
|
|
e8b09d2203 | ||
|
|
57b4fb0d84 | ||
|
|
29f25e4111 | ||
|
|
00d8b92b89 | ||
|
|
5e17e6276c | ||
|
|
96e7c011e2 | ||
|
|
c505df8830 | ||
|
|
f214ada545 | ||
|
|
c10aa3b8b0 | ||
|
|
6311d709c3 | ||
|
|
8ab77465db | ||
|
|
201bf73ef7 | ||
|
|
49784c8122 | ||
|
|
3e03f308b5 | ||
|
|
95cecb0229 | ||
|
|
7676f5b15f | ||
|
|
d5b057534a | ||
|
|
7a15f6dfc7 | ||
|
|
712d825c23 | ||
|
|
7803b1148d | ||
|
|
1eab62f7e5 | ||
|
|
ae306a3df6 | ||
|
|
16ba33c05b | ||
|
|
77ae66930c | ||
|
|
389697988a | ||
|
|
2435341d74 | ||
|
|
80c906bd30 | ||
|
|
4073758874 | ||
|
|
74249be4a2 | ||
|
|
8bf660af90 | ||
|
|
9da134cd88 | ||
|
|
f7f219a137 | ||
|
|
693629585b | ||
|
|
e3b5648df0 | ||
|
|
5c41802d55 | ||
|
|
3543a3c763 | ||
|
|
3122912920 | ||
|
|
f11fdf6717 | ||
|
|
572bcca9c2 | ||
|
|
10fa064109 | ||
|
|
7cd779d304 | ||
|
|
628d168049 | ||
|
|
37423757b5 | ||
|
|
58752bb8fe | ||
|
|
7e7f70ff3e | ||
|
|
f77c227b2d | ||
|
|
21d842b0b0 | ||
|
|
a23cdbb94b | ||
|
|
1276058ea8 | ||
|
|
002fcde3da | ||
|
|
960186a71f | ||
|
|
cb683a84f7 | ||
|
|
3672875b4c | ||
|
|
84d9766d6a | ||
|
|
24e12641d5 | ||
|
|
ec1e106ec5 | ||
|
|
c7edffbd56 | ||
|
|
e48c37316e | ||
|
|
8a9c84f4a5 | ||
|
|
1e561cfaaf | ||
|
|
d1af82a883 | ||
|
|
897a360d06 | ||
|
|
3a441c7d95 | ||
|
|
c57d9146f4 | ||
|
|
ac316d8cc5 | ||
|
|
75addb4fe2 | ||
|
|
3d3861dd88 | ||
|
|
fc7bc32045 | ||
|
|
a6e55c096d | ||
|
|
b062fd5235 | ||
|
|
4efb25d9c0 | ||
|
|
d6436eb7cf | ||
|
|
86f9ed6248 | ||
|
|
47ff13637e | ||
|
|
db73d0998f | ||
|
|
7c0fe72714 | ||
|
|
8fe25edc75 | ||
|
|
dcd7b684bb | ||
|
|
17dfd7cabb | ||
|
|
1f9b82bd56 | ||
|
|
3fcffe7d04 | ||
|
|
455d8a05c1 | ||
|
|
70287bbc94 | ||
|
|
7be21139aa | ||
|
|
e1ee1a49d6 | ||
|
|
d56f058c08 | ||
|
|
96dd5fc932 | ||
|
|
5b59d9c5d7 | ||
|
|
c163d4addc | ||
|
|
aab74b7151 | ||
|
|
606134f9de | ||
|
|
ee627bb667 | ||
|
|
47b8a7f9b8 | ||
|
|
6c0bc608a1 | ||
|
|
5bfc18950a | ||
|
|
56d628f851 | ||
|
|
cac9d7cf7e | ||
|
|
f715d0b860 | ||
|
|
98d690b849 | ||
|
|
45535e8a63 | ||
|
|
c78895aaea | ||
|
|
b712fba17c | ||
|
|
ea63aea72d | ||
|
|
64e588bfab | ||
|
|
0d1e8c93bf | ||
|
|
a1bed447b3 | ||
|
|
a75fa3aad1 | ||
|
|
52c75e9db1 | ||
|
|
8110ba8880 | ||
|
|
e4d2bb62b2 | ||
|
|
fea9788cc4 | ||
|
|
2144047e14 | ||
|
|
43329c9b64 | ||
|
|
1e17d3de79 | ||
|
|
d5ede50b04 | ||
|
|
fab6132cb3 | ||
|
|
3722f89f49 | ||
|
|
373ae6061a | ||
|
|
f03f1fad67 | ||
|
|
374ffdf1c1 | ||
|
|
4db38d2efd | ||
|
|
f0a261448c | ||
|
|
fbdef6a9bd | ||
|
|
05a41da75b | ||
|
|
345c3768de | ||
|
|
639c8a1d91 | ||
|
|
321acea038 | ||
|
|
b0920e6e86 | ||
|
|
149b658b56 | ||
|
|
06917e581c | ||
|
|
e434b2ce4f | ||
|
|
f3a1728873 | ||
|
|
e63bb7ec8c | ||
|
|
6791ff0525 | ||
|
|
725b8cc680 | ||
|
|
3575b8706d | ||
|
|
a76c67578c | ||
|
|
3a27064c41 | ||
|
|
e0a8709c09 | ||
|
|
17795fd5ce | ||
|
|
d6738ffa5d | ||
|
|
5c4c73e2c2 | ||
|
|
6148f94939 | ||
|
|
51d3012d48 | ||
|
|
c39f16c422 | ||
|
|
e72642667f | ||
|
|
3785b1efd4 | ||
|
|
7aeae62f84 | ||
|
|
e5e33f8cb7 | ||
|
|
a836d5947d | ||
|
|
7cf0a77d59 | ||
|
|
9f594e2432 | ||
|
|
48781dd95c | ||
|
|
7cdd5d9e41 | ||
|
|
3f5d3fec36 | ||
|
|
90094a2dca | ||
|
|
cc9924ffa6 | ||
|
|
5e6b4ab99e | ||
|
|
2d6d374162 | ||
|
|
3137d4f35c | ||
|
|
6ff9d23fd4 | ||
|
|
d4939c838b | ||
|
|
cd306f1d23 | ||
|
|
5fd07e0889 | ||
|
|
b0796ff5fd | ||
|
|
9dfa074cd6 | ||
|
|
033761c5f9 | ||
|
|
2926ac494e | ||
|
|
06143c2e8c | ||
|
|
f00d28fdab | ||
|
|
4705f29e2f | ||
|
|
371ab0cff8 | ||
|
|
b86140ac5f | ||
|
|
bec68bff29 | ||
|
|
414debfd04 | ||
|
|
a7ee3c3e77 | ||
|
|
0f239e51b0 | ||
|
|
e5b9eb8171 | ||
|
|
f855db61b0 | ||
|
|
0f31bb5c1a | ||
|
|
23094ba015 | ||
|
|
b92d9a895f | ||
|
|
8161a7a3ef | ||
|
|
8f896e57ae | ||
|
|
61b153073f | ||
|
|
8f5157058b | ||
|
|
ddeda9ceb7 | ||
|
|
bde1f0a8e2 | ||
|
|
b2cb10de5f | ||
|
|
1005a725d4 | ||
|
|
50768cd242 | ||
|
|
cc3ebbe519 | ||
|
|
8056e349e0 | ||
|
|
0001f59c8d | ||
|
|
2941792a85 | ||
|
|
320bc3c032 | ||
|
|
2825e51550 | ||
|
|
cbdd47d056 | ||
|
|
516c8c6e23 | ||
|
|
ef1236b600 | ||
|
|
28cbed1d13 | ||
|
|
cde199a797 | ||
|
|
5589a0a69c | ||
|
|
09e8fb17cd | ||
|
|
d71c4c2215 | ||
|
|
6f621e6480 | ||
|
|
8e33c2d301 | ||
|
|
52aca124c3 | ||
|
|
c7159d58f1 | ||
|
|
5d6e8fe3e3 | ||
|
|
99762dac23 | ||
|
|
57956b3fd2 | ||
|
|
b323645227 | ||
|
|
dd9502f373 | ||
|
|
aa8a3077a2 | ||
|
|
8e81e5f29e | ||
|
|
63dd1fb599 | ||
|
|
32561a9912 | ||
|
|
cbcd07777d | ||
|
|
2437455f27 | ||
|
|
35c64b898c | ||
|
|
c3ac717487 | ||
|
|
c8dbcff9db | ||
|
|
abd5156f25 | ||
|
|
5719b3054a | ||
|
|
c89e1f2937 | ||
|
|
0b16f8d444 | ||
|
|
9254a805d7 | ||
|
|
a4c4907035 | ||
|
|
cb92c23de0 | ||
|
|
56a52e804c | ||
|
|
ebb09a5c3b | ||
|
|
a9f8a8cba0 | ||
|
|
4aa24e62a4 | ||
|
|
2b67b6b87e | ||
|
|
420aac7274 | ||
|
|
a518a9a766 | ||
|
|
026cc11b05 | ||
|
|
02b594f6ad | ||
|
|
f695d18306 | ||
|
|
0e42cfc365 | ||
|
|
024c380b9d | ||
|
|
eba28e2cea | ||
|
|
c35a53169f | ||
|
|
91ed9b3c47 | ||
|
|
a38ff404bd | ||
|
|
15c9e79a7d | ||
|
|
6fe66e0969 | ||
|
|
3e0d209625 | ||
|
|
814874d68b | ||
|
|
cd2b5df971 | ||
|
|
240094c9b0 | ||
|
|
061616c1b1 | ||
|
|
8cdc153f58 | ||
|
|
209f3a1eba | ||
|
|
7764996bec | ||
|
|
b35407fa7c | ||
|
|
4395889c9e | ||
|
|
1907e0f18f | ||
|
|
193fc241ca | ||
|
|
3fdc8608f3 | ||
|
|
9e2e2067c6 | ||
|
|
f112c77fcc | ||
|
|
888d69a87a | ||
|
|
bd183cdee1 | ||
|
|
957e917a84 | ||
|
|
ef1b1f01a8 | ||
|
|
9f347fabba | ||
|
|
a509400d58 | ||
|
|
c3b268a0bc | ||
|
|
0f7dfc378c | ||
|
|
805c8c94a9 | ||
|
|
97b80b57e9 | ||
|
|
4f6b6b8008 | ||
|
|
616b1cb7ac | ||
|
|
08fdef4bf6 | ||
|
|
26b79ca18c | ||
|
|
fa41863752 | ||
|
|
bbbc80acae | ||
|
|
67643ead69 | ||
|
|
ad94066ec8 | ||
|
|
a443ec2e9a | ||
|
|
6862fd70da | ||
|
|
30f1df8c48 | ||
|
|
7add0f2490 | ||
|
|
9d6d177994 | ||
|
|
316a8f1545 | ||
|
|
11db53f875 | ||
|
|
95b4f899c5 | ||
|
|
2946155313 | ||
|
|
f2ca2aa1f7 | ||
|
|
b20bf10fda | ||
|
|
208c15bc16 | ||
|
|
c3d3c82f3a | ||
|
|
b22d9cc27d | ||
|
|
908cf5042e | ||
|
|
6ec1641294 | ||
|
|
7b79ec360b | ||
|
|
718aee242d | ||
|
|
56fc6b7deb | ||
|
|
e257f121e1 | ||
|
|
5f72f69688 | ||
|
|
8caa271476 | ||
|
|
e43cd8316f | ||
|
|
34b8983220 | ||
|
|
70a98a43ea | ||
|
|
8783304a2d | ||
|
|
ec9cf002d5 | ||
|
|
b3e4abf06e | ||
|
|
50f8f9504b | ||
|
|
f2f3dc5e73 | ||
|
|
eb80887936 | ||
|
|
cdcab0e820 | ||
|
|
50dcd9f96d | ||
|
|
87dbd8f54c | ||
|
|
81e8686cc7 | ||
|
|
c7524a7e44 | ||
|
|
2dd1ca6af0 | ||
|
|
95324b8190 | ||
|
|
7c07841632 | ||
|
|
b3e0305448 | ||
|
|
e628f94436 | ||
|
|
76d57161d9 | ||
|
|
04c53fa145 | ||
|
|
9cc7038e54 | ||
|
|
5d4c4df3ef | ||
|
|
0e5820d893 | ||
|
|
0578157d56 | ||
|
|
da8c7c49bf | ||
|
|
da14982d1e | ||
|
|
299f1d0258 | ||
|
|
bdb338cf77 | ||
|
|
dc8885a1ca | ||
|
|
61b98f32a2 | ||
|
|
bfb18e5519 | ||
|
|
34ea2f4e1a | ||
|
|
cbbf9b3931 | ||
|
|
9c2e42ddfc | ||
|
|
d387f67dcb | ||
|
|
528a43bd6c | ||
|
|
b357803362 | ||
|
|
448808529a | ||
|
|
a0bc208412 | ||
|
|
6b0172b9bf | ||
|
|
bc1eccb66d | ||
|
|
a9249d4a82 | ||
|
|
3c373494d6 | ||
|
|
40761e4031 | ||
|
|
5aef606d3b | ||
|
|
4671cd6002 | ||
|
|
2ec0b2cd20 | ||
|
|
07effa3925 | ||
|
|
cd76a71de3 | ||
|
|
916dd79f19 | ||
|
|
c0c67c9be3 | ||
|
|
e1c29434b2 | ||
|
|
e5a063bcb4 | ||
|
|
d28cbaf770 | ||
|
|
23d5e8b8e4 | ||
|
|
6173815408 | ||
|
|
70e0e49952 | ||
|
|
b3e4aa6797 | ||
|
|
e74e68c84f | ||
|
|
4af0d7fd94 | ||
|
|
6ef018785b | ||
|
|
fe81d5c8a9 | ||
|
|
c2f01d7f9e | ||
|
|
127e09bca1 | ||
|
|
a20303c623 | ||
|
|
baaf869fc3 | ||
|
|
be4bc1a5be | ||
|
|
7a23b94559 | ||
|
|
07dc1b42fb | ||
|
|
10e71b3d01 | ||
|
|
edc0ed1415 | ||
|
|
c5319612b4 | ||
|
|
fff6b26ae3 | ||
|
|
650484604c | ||
|
|
3144a278dd | ||
|
|
afe0b16c02 | ||
|
|
df152b0ce7 | ||
|
|
e3c1f43952 | ||
|
|
5e63c0955d | ||
|
|
c2a98a60f9 | ||
|
|
6bc92d6d31 | ||
|
|
52fd4445a4 | ||
|
|
fed4bd5269 | ||
|
|
1a7e2e663e | ||
|
|
1b925119b5 | ||
|
|
9dc8f30e47 | ||
|
|
141c0679a0 | ||
|
|
973297336f | ||
|
|
fe902461f4 | ||
|
|
15ea13245a | ||
|
|
9d4219ebac | ||
|
|
4d12c37c54 | ||
|
|
8424306433 | ||
|
|
12626ce9bb | ||
|
|
dafb94db79 | ||
|
|
2ecab0b63a | ||
|
|
ea697b6345 | ||
|
|
635321d47e | ||
|
|
dd4798802c | ||
|
|
0b988fa9ec | ||
|
|
ecf3b2ef32 | ||
|
|
60096bc1a1 | ||
|
|
2966132c52 | ||
|
|
b439542604 | ||
|
|
7277e5d8a8 | ||
|
|
ef3551d149 | ||
|
|
1bb5ee9c68 | ||
|
|
72c053519c | ||
|
|
349aa24511 | ||
|
|
294c9af469 | ||
|
|
b4ddc7b7ba | ||
|
|
ba0270799e | ||
|
|
73806f74ca | ||
|
|
5d0890c0a8 | ||
|
|
b6129f86c1 | ||
|
|
d1f22eaca4 | ||
|
|
762fbcb687 | ||
|
|
f15fb727a0 | ||
|
|
e374573f30 | ||
|
|
73d47d5749 | ||
|
|
b7cb28d501 | ||
|
|
df83892760 | ||
|
|
1db7257107 | ||
|
|
0517da3618 | ||
|
|
fe96e29d5f | ||
|
|
3c16d6b32d | ||
|
|
f21be1ec55 | ||
|
|
3b4f147775 | ||
|
|
6fb1aa2381 | ||
|
|
d4fe79a174 | ||
|
|
abb81c6351 | ||
|
|
77da960815 | ||
|
|
3e8c69a9de | ||
|
|
a788c373e6 | ||
|
|
51ea3cf30c | ||
|
|
32f34f63fd | ||
|
|
5dc84850ad | ||
|
|
45b711f9d1 | ||
|
|
6a71de7835 | ||
|
|
18496b6285 | ||
|
|
4e9d2f08b1 | ||
|
|
30a603ce05 | ||
|
|
5e762d847c | ||
|
|
54f5499aee | ||
|
|
0c952b13d0 | ||
|
|
dd34f70368 | ||
|
|
5e042dbc05 | ||
|
|
dcbe8bfad1 | ||
|
|
36caf2e42b | ||
|
|
3067352a8d | ||
|
|
9baaf858f9 | ||
|
|
4440133e9a | ||
|
|
c957794144 | ||
|
|
1333a46b7e | ||
|
|
8e7282eb3e | ||
|
|
a678390e52 | ||
|
|
f42ce57d0f | ||
|
|
919fbf421d | ||
|
|
df096bc96b | ||
|
|
22e45d46fe | ||
|
|
a55e584706 | ||
|
|
ba28924418 | ||
|
|
5a6cfbf4ca | ||
|
|
fae306b374 | ||
|
|
736309660f | ||
|
|
488e194787 | ||
|
|
c82ccf0670 | ||
|
|
51c1a9f8fb | ||
|
|
ea1e1b12c9 | ||
|
|
7379c78a9b | ||
|
|
9f6e84f6be | ||
|
|
82790e510f | ||
|
|
943008ebac | ||
|
|
e271a61103 | ||
|
|
f1974d5d67 | ||
|
|
d9b153c9f6 | ||
|
|
577fc4388b | ||
|
|
329519d4aa | ||
|
|
18a5f46b61 | ||
|
|
339631530a | ||
|
|
c45f1328ac | ||
|
|
6fe55c2f29 | ||
|
|
f7a94526dd | ||
|
|
bbce3ba974 | ||
|
|
eecfa9793e | ||
|
|
40295fb3fe | ||
|
|
d2509811b7 | ||
|
|
29d4a1502a | ||
|
|
ec0c61da05 | ||
|
|
ef4e11ecb8 | ||
|
|
fd702e43f1 | ||
|
|
249e0fdd36 | ||
|
|
6208118b13 | ||
|
|
73020b6a93 | ||
|
|
a9667e84ad | ||
|
|
ab1524ff1a | ||
|
|
4a980f4ddb | ||
|
|
9fbb9a551e | ||
|
|
c24a7c03c2 | ||
|
|
5a8280d577 | ||
|
|
da83e84125 | ||
|
|
ef9a3cadcf | ||
|
|
721a39ddff | ||
|
|
cbabf779c2 | ||
|
|
ecd17e819c | ||
|
|
8e9d19bc65 | ||
|
|
5be3a15a82 | ||
|
|
658749cc54 | ||
|
|
1927932b43 | ||
|
|
ba146d4c09 | ||
|
|
47988c9666 | ||
|
|
f2df5773b1 | ||
|
|
cc90f79baf | ||
|
|
b3ff8a4b6d | ||
|
|
b1f2c51037 | ||
|
|
7bbafc5639 | ||
|
|
c7f752667e | ||
|
|
ed7ea13a2a | ||
|
|
3be7762388 | ||
|
|
15f6b74814 | ||
|
|
1b5bf40c20 | ||
|
|
954c20edd9 | ||
|
|
11c3afd711 | ||
|
|
b752e83db5 | ||
|
|
e16eb87465 | ||
|
|
35ea9d231b | ||
|
|
3872912679 | ||
|
|
b9a1530345 | ||
|
|
5e7e5e6b61 | ||
|
|
e61dde8806 | ||
|
|
cddf1da2e9 | ||
|
|
52ea44e53b | ||
|
|
0f28ea16a7 | ||
|
|
00e5fefe6d | ||
|
|
baafd30ba6 | ||
|
|
d3a9dff6b9 | ||
|
|
9a7b6a9f51 | ||
|
|
f4b8197060 | ||
|
|
c5bc1c14c0 | ||
|
|
ef0b45ece8 | ||
|
|
5a72c5058c | ||
|
|
e6e58e455c | ||
|
|
5b9d3ac6c6 | ||
|
|
9d1baa0705 | ||
|
|
58554396d6 | ||
|
|
24044f3356 | ||
|
|
b7c78b7651 | ||
|
|
4348d25fc4 | ||
|
|
f3d67b3842 | ||
|
|
7f04a15311 | ||
|
|
a511a37bb7 | ||
|
|
7b3e3d6a13 | ||
|
|
12caffee61 | ||
|
|
576a08908b | ||
|
|
44aa22c635 | ||
|
|
c95a582a74 | ||
|
|
f891b64a83 | ||
|
|
67aa527b22 | ||
|
|
034dcf185c | ||
|
|
e9b99c78df | ||
|
|
901b7469b9 | ||
|
|
aa3f13761f | ||
|
|
276a093753 | ||
|
|
096592506e | ||
|
|
ba28738083 | ||
|
|
0283db5883 | ||
|
|
ef6e263fde | ||
|
|
118db9eeae | ||
|
|
d7a87d0ea4 | ||
|
|
81dc180323 | ||
|
|
476333d192 | ||
|
|
c227f5e7bd | ||
|
|
2968d8e3cb | ||
|
|
b5f3247ca5 | ||
|
|
c6fb9d0963 | ||
|
|
a37cca3644 | ||
|
|
25b36f8d65 | ||
|
|
441cd9713c | ||
|
|
29fddf3291 | ||
|
|
389faae58e | ||
|
|
e4b6070c0f | ||
|
|
14045adf92 | ||
|
|
5941730c37 | ||
|
|
52d83d57f9 | ||
|
|
7e24e219fd | ||
|
|
08c3fe8063 | ||
|
|
2271cf03f5 | ||
|
|
ecb742acc5 | ||
|
|
7b5f4b175b | ||
|
|
89849c8b01 | ||
|
|
5661b19005 | ||
|
|
4092a75d85 | ||
|
|
e5b5f9a2f6 | ||
|
|
ce7ec725e3 | ||
|
|
ff14945e22 | ||
|
|
731f10225b | ||
|
|
ddde700b64 | ||
|
|
1ed5f84a79 | ||
|
|
07b365b7d7 | ||
|
|
6b4366b85b | ||
|
|
f5d48537f1 | ||
|
|
18e72c5cc7 | ||
|
|
28beb05aa3 | ||
|
|
e0cc3c99d2 | ||
|
|
a1b654819c | ||
|
|
a40669893e | ||
|
|
6203ed0474 | ||
|
|
d39afb687d | ||
|
|
ef5186d920 | ||
|
|
a5e4c95cfd | ||
|
|
6b056d29f3 | ||
|
|
2c970532dc | ||
|
|
c73d70fb46 | ||
|
|
37a10cef02 | ||
|
|
e8f4010e27 | ||
|
|
d50ae36e17 | ||
|
|
818a4e0f70 | ||
|
|
519822bcf9 | ||
|
|
73841e8c49 | ||
|
|
097c4a6536 | ||
|
|
b93945585a | ||
|
|
64c2508ee3 | ||
|
|
f1f259de5b | ||
|
|
da47d52c79 | ||
|
|
fef1e63c70 | ||
|
|
70469b76d6 | ||
|
|
bfd8c1fdd2 | ||
|
|
c7b68d10ea | ||
|
|
803d765d49 | ||
|
|
c697edf4f4 | ||
|
|
808394b77d | ||
|
|
99b5696390 | ||
|
|
129d14e143 | ||
|
|
ec5681f0f1 | ||
|
|
5bbc112fb1 | ||
|
|
641c64ada1 | ||
|
|
ed2269762b | ||
|
|
45025713a8 | ||
|
|
e7144fbed8 | ||
|
|
e916058f0b | ||
|
|
fe5aea38c3 | ||
|
|
065872e001 | ||
|
|
446f24e66d | ||
|
|
b311a368e0 | ||
|
|
936e01e5bb | ||
|
|
2151b82be9 | ||
|
|
06a24b9cfa | ||
|
|
8e95064408 | ||
|
|
397a8b57cc | ||
|
|
ce03d68332 | ||
|
|
b42466b925 | ||
|
|
6a9851c7f8 | ||
|
|
2d1e893b3e | ||
|
|
77ad9aaafe | ||
|
|
2f41c8d518 | ||
|
|
656e40eed2 | ||
|
|
5b80a41cad | ||
|
|
1530a3cfc6 | ||
|
|
32a8301073 | ||
|
|
3d89210477 | ||
|
|
a7122f4518 | ||
|
|
70016f7861 | ||
|
|
64a78f5ba3 | ||
|
|
7c95e89ec3 | ||
|
|
4ece8bd7a7 | ||
|
|
3243252cb0 | ||
|
|
e8ceba4e64 | ||
|
|
2ab51a644d | ||
|
|
1ccc270a2c | ||
|
|
84b01f63db | ||
|
|
2766805680 | ||
|
|
ffc7e509aa | ||
|
|
2f6fe5ce3a | ||
|
|
8d50a8327e | ||
|
|
c426bbf3a5 | ||
|
|
2d30afc45f | ||
|
|
9c76875f41 | ||
|
|
4a12047c61 | ||
|
|
253d9d6d12 | ||
|
|
0bc3dab095 | ||
|
|
0f8692b464 | ||
|
|
11c6ce812a | ||
|
|
960c25ac0a | ||
|
|
28c20b4ef9 | ||
|
|
02f21113ab | ||
|
|
7059eceeb0 | ||
|
|
9593ffde2e | ||
|
|
0f026af185 | ||
|
|
e45239e350 | ||
|
|
1833d008b3 | ||
|
|
c032dc45d1 | ||
|
|
aded138a59 | ||
|
|
089ee5979d | ||
|
|
8b2c0f9044 | ||
|
|
824f5f0b7a | ||
|
|
e542132b07 | ||
|
|
5350e7669e | ||
|
|
a15a5d7097 | ||
|
|
a68c19df6c | ||
|
|
060af1858d | ||
|
|
60ec2b78b3 | ||
|
|
3adf10b809 | ||
|
|
576a7b8caf | ||
|
|
bbdbfabf9e | ||
|
|
b12d2f65d6 | ||
|
|
3f14bfd809 | ||
|
|
720d1fd3bb | ||
|
|
18b62a51e2 | ||
|
|
327cb45ed5 | ||
|
|
23c681b51d | ||
|
|
dcc4347c53 | ||
|
|
da44c54897 | ||
|
|
b81f486c2f | ||
|
|
0423309768 | ||
|
|
deebed23e1 | ||
|
|
87332ce524 | ||
|
|
779aebc91c | ||
|
|
4d580438b0 | ||
|
|
fdaea2a7a7 | ||
|
|
1ee195d255 | ||
|
|
dd5feec5e8 | ||
|
|
825adcf3f5 | ||
|
|
9f32d0eed2 | ||
|
|
37bf3e18cb | ||
|
|
66f55bc5c1 | ||
|
|
629081f839 | ||
|
|
91f4bdc9f9 | ||
|
|
4534960b29 | ||
|
|
228bc89ecb | ||
|
|
215b72c0ba | ||
|
|
fb6110ac20 | ||
|
|
3649568ff2 | ||
|
|
5eb2290f68 | ||
|
|
51c3ff8d75 | ||
|
|
a0cfd519e3 | ||
|
|
e978bdf9ef | ||
|
|
764b420f5f | ||
|
|
568dfd4b49 | ||
|
|
94973fd0eb | ||
|
|
90169cdf16 | ||
|
|
43e2a398f9 | ||
|
|
469a6e5f1a | ||
|
|
0399b5a27e | ||
|
|
cee6dead3c | ||
|
|
cfcb475435 | ||
|
|
a12f07792f | ||
|
|
8337e083b6 | ||
|
|
c328c807e7 | ||
|
|
d2658f8387 | ||
|
|
5aab012676 | ||
|
|
e097c99681 | ||
|
|
6f2fc34228 | ||
|
|
c6c65d9ab3 | ||
|
|
d070221707 | ||
|
|
24fdc87fe0 | ||
|
|
56a7c45521 | ||
|
|
c74398e130 | ||
|
|
cb2ca37f88 | ||
|
|
cd02c5944f | ||
|
|
fb1f4f4e7e | ||
|
|
e3b1d6d3ad | ||
|
|
183d264e9e | ||
|
|
5dd1130272 | ||
|
|
6e98214f74 | ||
|
|
39f2ab595c | ||
|
|
be352633a0 | ||
|
|
7cefbeaad5 | ||
|
|
d3266d516b | ||
|
|
dddfde85ef | ||
|
|
61ac2aef2b | ||
|
|
8cccc6b468 | ||
|
|
83de18805c | ||
|
|
89af7e4c85 | ||
|
|
bc1ef48e56 | ||
|
|
ed833c9f99 | ||
|
|
8ec2fc3969 | ||
|
|
db8c9a8520 | ||
|
|
18d1607f5e | ||
|
|
c379fdbae2 | ||
|
|
cf52e0ad44 | ||
|
|
94c50314f1 | ||
|
|
019ac37e9c | ||
|
|
d54652682d | ||
|
|
1e78681df8 | ||
|
|
b9af7e24e4 | ||
|
|
6e56f513b4 | ||
|
|
21d8544128 | ||
|
|
e9b5484ddd | ||
|
|
6ea8641ae0 | ||
|
|
d2d74ca0f3 | ||
|
|
bebb2e1994 | ||
|
|
3170f63330 | ||
|
|
ed2ba31e4c | ||
|
|
0381f14acc | ||
|
|
1ac30300f0 | ||
|
|
673c9d7029 | ||
|
|
97f6e314c1 | ||
|
|
8651e6a10d | ||
|
|
efb6495a44 | ||
|
|
8e29b0b22b | ||
|
|
0db3b0a0ff | ||
|
|
b8e02f2b40 | ||
|
|
c469f6ad9e | ||
|
|
71f10de4de | ||
|
|
0132189007 | ||
|
|
dd29d44136 | ||
|
|
8e3d910d4e | ||
|
|
363604a27d | ||
|
|
1677f4223c | ||
|
|
14a9da0613 | ||
|
|
1d9d27c91a | ||
|
|
509fe9cba1 | ||
|
|
747be463d2 | ||
|
|
2fae0192e8 | ||
|
|
98600c9a11 | ||
|
|
1a292e0634 | ||
|
|
a92921da13 | ||
|
|
17bf0b25c1 | ||
|
|
009862ab7e | ||
|
|
d7f2681a0c | ||
|
|
61733ded14 | ||
|
|
6b6294807c | ||
|
|
5a47794606 | ||
|
|
b087dd1db6 | ||
|
|
3ef6b79c80 | ||
|
|
c65347ab17 | ||
|
|
c448f6fe55 | ||
|
|
58cdc054e3 | ||
|
|
c10889150e | ||
|
|
bea0384f5d | ||
|
|
f019f28e7b | ||
|
|
d1a005ab39 | ||
|
|
bd60447066 | ||
|
|
9073d56eec | ||
|
|
0f1969f16f | ||
|
|
bf759cc9c3 | ||
|
|
ee1cef189f | ||
|
|
264778af0a | ||
|
|
c326f28833 | ||
|
|
3eaff2941e | ||
|
|
2a81e49dde | ||
|
|
1b8b8c029f | ||
|
|
55a3da87f8 | ||
|
|
45cd8e03ca | ||
|
|
1bca62c4b7 | ||
|
|
e938bbc543 | ||
|
|
c549513acd | ||
|
|
2e19b94113 | ||
|
|
758b39be99 | ||
|
|
f49f0a6f72 | ||
|
|
98426e9886 | ||
|
|
40acb4412d | ||
|
|
2e2fe98f9c | ||
|
|
3281ebb495 | ||
|
|
beebb3c013 | ||
|
|
1b4f888109 | ||
|
|
d5c14c70b7 | ||
|
|
a9628142fd | ||
|
|
09041b9359 | ||
|
|
ea7c3fe7fd | ||
|
|
eb2114b9cd | ||
|
|
2478b47b89 | ||
|
|
6a2831c24a | ||
|
|
23f5cb4233 | ||
|
|
ad45d7e407 | ||
|
|
916c4e5d86 | ||
|
|
8b5848a4f5 | ||
|
|
b394817754 | ||
|
|
0a07f8ffee | ||
|
|
721d3c9e0c | ||
|
|
de1f82aa33 | ||
|
|
82845f8d04 | ||
|
|
beb1356b5b | ||
|
|
ae3ef964c1 | ||
|
|
b7c33af4a5 | ||
|
|
6129758558 | ||
|
|
6578119a23 | ||
|
|
fddc4757c8 | ||
|
|
83e87bac76 | ||
|
|
b7b23bdfb8 | ||
|
|
c1ea6175c5 | ||
|
|
959fb5cf68 | ||
|
|
b0a90d8fa8 | ||
|
|
af15b285fa | ||
|
|
388bc55344 | ||
|
|
b3d6cb268a | ||
|
|
f3aaf2b4d8 | ||
|
|
f6cf08e582 | ||
|
|
8a531cedb0 | ||
|
|
0a4d2bbd9c | ||
|
|
733280d9cb | ||
|
|
eda703ab28 | ||
|
|
b21dd082c3 | ||
|
|
cc108057ff | ||
|
|
8ea185ea30 | ||
|
|
bb7546c913 | ||
|
|
5e399d5d33 | ||
|
|
264708427b | ||
|
|
7a73b7f168 | ||
|
|
c3899720f6 | ||
|
|
5a8a005026 | ||
|
|
2e4fa7bb48 | ||
|
|
1dd36347ed | ||
|
|
630638dcde | ||
|
|
f135d6295b | ||
|
|
3976f4c88e | ||
|
|
d0050db236 | ||
|
|
975eb73145 | ||
|
|
0a146a8be4 | ||
|
|
bd90c99f83 | ||
|
|
f42ac40d7c | ||
|
|
a1a65d238c | ||
|
|
30a3644e64 | ||
|
|
74e3a622a2 | ||
|
|
474a923160 | ||
|
|
cb384127d1 | ||
|
|
dd2f695d7e | ||
|
|
1b3cb3b0de | ||
|
|
ff9a5d231b | ||
|
|
aca5482fbf | ||
|
|
68519b7034 | ||
|
|
8ea4bdd91d | ||
|
|
89f929b12a | ||
|
|
19aba4ac78 | ||
|
|
e8e30bc402 | ||
|
|
c1cc5ca767 | ||
|
|
f01f0c02d1 | ||
|
|
6931004969 | ||
|
|
2cc4d0286c | ||
|
|
ca81d49006 | ||
|
|
c792504fab | ||
|
|
dad38c19c8 | ||
|
|
4a30a26f8f | ||
|
|
819a661be5 | ||
|
|
888efc1b36 | ||
|
|
26b6c697d3 | ||
|
|
93c52ff5ff | ||
|
|
b170a01fa7 | ||
|
|
ae5054b476 | ||
|
|
9d2ecf6be3 | ||
|
|
f4d37173fe | ||
|
|
ae8cab3296 | ||
|
|
a092f20d87 | ||
|
|
781f3fd8be | ||
|
|
cc8f06ece9 | ||
|
|
86aade9a02 | ||
|
|
3bfcae247a | ||
|
|
89ad0ca566 | ||
|
|
12c56a8e75 | ||
|
|
f7090f43ad | ||
|
|
66a5c1cd7e | ||
|
|
b2e10131c0 | ||
|
|
06d490342f | ||
|
|
b12ccc840e | ||
|
|
e57067b677 | ||
|
|
2303ba1441 | ||
|
|
ca5ccc58c5 | ||
|
|
16435e02bf | ||
|
|
d7c25806c0 | ||
|
|
5cbc73c657 | ||
|
|
78bbb9b58d | ||
|
|
d5536e0480 | ||
|
|
61853ad8de | ||
|
|
1d6e5dc4dc | ||
|
|
1026d2caf8 | ||
|
|
0a97149dec | ||
|
|
b27f388344 | ||
|
|
0e9b5adbd3 | ||
|
|
5e0faf4959 | ||
|
|
a368209b1d | ||
|
|
d3f7eb0c87 | ||
|
|
583933e2d6 | ||
|
|
2a8a63af44 | ||
|
|
f899c31fbc | ||
|
|
fc5c2052b5 | ||
|
|
e1b13b4d39 | ||
|
|
b2079cb508 | ||
|
|
f0c10d4360 | ||
|
|
40c4183196 | ||
|
|
07b1abab95 | ||
|
|
e5187ad2ae | ||
|
|
4339706e07 | ||
|
|
f42846e8c7 | ||
|
|
ce72cac0f8 | ||
|
|
e4b3c8bbc3 | ||
|
|
cc8125a779 | ||
|
|
d659654f53 | ||
|
|
f1c6c658ac | ||
|
|
505a855000 | ||
|
|
0714581e0c | ||
|
|
5591a14c0a | ||
|
|
e91d9a6fff | ||
|
|
fde5e737dc | ||
|
|
e53a26b5d8 | ||
|
|
0b780a5477 | ||
|
|
32efd9adf8 | ||
|
|
a67d0411e7 | ||
|
|
48f4cfb601 | ||
|
|
dbee7ca007 | ||
|
|
c98e7717bb | ||
|
|
9268493e8d | ||
|
|
47ce1cebeb | ||
|
|
9012e587ca | ||
|
|
7d21754710 | ||
|
|
8fe173452a | ||
|
|
3b6aeea44c | ||
|
|
c41f94d2a3 | ||
|
|
09c06698e9 | ||
|
|
436d02dd7b | ||
|
|
7d3992a80a | ||
|
|
842b44dc46 | ||
|
|
f850201c64 | ||
|
|
e00442eb13 | ||
|
|
004c26ec28 | ||
|
|
d7d028a7a7 | ||
|
|
ff682d7902 | ||
|
|
bc5cb168f5 | ||
|
|
70534846d1 | ||
|
|
5f450e4928 | ||
|
|
bd0f06c18c | ||
|
|
41ed85bd26 | ||
|
|
947319cadd | ||
|
|
f59119aad6 | ||
|
|
42b6305964 | ||
|
|
6b72b04a37 | ||
|
|
5189db3d81 | ||
|
|
48d91cf4cc | ||
|
|
c56b4ddc6f | ||
|
|
56258c6b7d | ||
|
|
6191ea90a1 | ||
|
|
6e07fac405 | ||
|
|
241d18d954 | ||
|
|
5827b69861 | ||
|
|
4b2579a064 | ||
|
|
3f1c84751a | ||
|
|
6100a82a3a | ||
|
|
54c4e7f86c | ||
|
|
2a99f199bc | ||
|
|
dad0e916ff | ||
|
|
7567323071 | ||
|
|
c7aaf8db4d | ||
|
|
923e4fb312 | ||
|
|
7c9f6ebc8f | ||
|
|
3bbb9a1413 | ||
|
|
3300e98ff0 | ||
|
|
dfcbeaf115 | ||
|
|
81b18fa3a0 | ||
|
|
d984732b35 | ||
|
|
2de544cfcc | ||
|
|
41c5ebcab6 | ||
|
|
593f6656c1 | ||
|
|
2d236d7aec | ||
|
|
09f99c2a92 | ||
|
|
2fec7d9c6c | ||
|
|
412eb67d21 | ||
|
|
52686f4866 | ||
|
|
c1455dc060 | ||
|
|
656cd1a00a | ||
|
|
c2b7f14c56 | ||
|
|
3c05bc561d | ||
|
|
a2ac5c3840 | ||
|
|
68fd59056b | ||
|
|
283d6cfd58 | ||
|
|
b00e1891c0 | ||
|
|
6b818efeb6 | ||
|
|
a344c4cfad | ||
|
|
42366959ec | ||
|
|
dda8cc1821 | ||
|
|
dcffca0a31 |
No files matched your search
@@ -0,0 +1,55 @@
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
||||
test-ubuntu-latest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||
make BUILD_TLS=yes -j2
|
||||
- name: gen-cert
|
||||
run: ./utils/gen-test-certs.sh
|
||||
- name: test-tls
|
||||
run: |
|
||||
sudo apt-get -y install tcl8.5 tcl-tls
|
||||
./runtest --clients 2 --verbose --tls
|
||||
- name: cluster-test
|
||||
run: |
|
||||
./runtest-cluster --tls
|
||||
- name: sentinel test
|
||||
run: |
|
||||
./runtest-sentinel
|
||||
- name: module tests
|
||||
run: |
|
||||
./runtest-moduleapi
|
||||
|
||||
build-ubuntu-old:
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make -j2
|
||||
run: |
|
||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||
make -j2
|
||||
|
||||
build-macos-latest:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: make -j2
|
||||
|
||||
build-libc-malloc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||
make MALLOC=libc -j2
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
name: Daily
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 7 * * *'
|
||||
|
||||
jobs:
|
||||
|
||||
test-jemalloc:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1200
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||
make
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.5
|
||||
./runtest --accurate --verbose
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
|
||||
test-libc-malloc:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1200
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||
make MALLOC=libc
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.5
|
||||
./runtest --accurate --verbose
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||
make BUILD_TLS=yes -j2
|
||||
- name: "test (tls)"
|
||||
run: |
|
||||
sudo apt-get install tcl8.5 tcl-tls
|
||||
./utils/gen-test-certs.sh
|
||||
./runtest --accurate --verbose --tls
|
||||
- name: test
|
||||
run: ./runtest --accurate --verbose
|
||||
- name: module api test (tls)
|
||||
run: ./runtest-moduleapi --verbose --tls
|
||||
|
||||
test-ubuntu-arm:
|
||||
runs-on: [self-hosted, linux, arm]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||
make -j4
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get -y install tcl8.5
|
||||
./runtest --clients 2 --verbose
|
||||
- name: module tests
|
||||
run: |
|
||||
./runtest-moduleapi
|
||||
|
||||
test-valgrind:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||
make valgrind
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.5 valgrind -y
|
||||
./runtest --valgrind --verbose --clients 1
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --valgrind --verbose --clients 1
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Endurance
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 * * * *'
|
||||
|
||||
jobs:
|
||||
|
||||
test-endurance:
|
||||
runs-on: octocore
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
|
||||
make -j8
|
||||
- name: test-multithread (5X)
|
||||
run: |
|
||||
sudo apt-get install -y tcl8.5
|
||||
./runtest --loopn 5 --config server-threads 3 --clients 5
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
.*.swp
|
||||
core
|
||||
*.o
|
||||
*.xo
|
||||
*.so
|
||||
*.d
|
||||
*.log
|
||||
dump.rdb
|
||||
@@ -37,3 +39,5 @@ deps/lua/src/liblua.a
|
||||
.prerequisites
|
||||
*.dSYM
|
||||
Makefile.dep
|
||||
.vscode/*
|
||||
.idea/*
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "deps/depot_tools"]
|
||||
path = deps/depot_tools
|
||||
url = https://chromium.googlesource.com/chromium/tools/depot_tools.git
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
language: generic
|
||||
os: osx
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
script: make
|
||||
env: COMPILER_NAME=g++-6 CXX=g++-6 CC=gcc-6
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- g++-6
|
||||
- nasm
|
||||
- uuid-dev
|
||||
sources: &sources
|
||||
- llvm-toolchain-precise-3.8
|
||||
- ubuntu-toolchain-r-test
|
||||
- os: linux
|
||||
script: make MALLOC=libc
|
||||
env: COMPILER_NAME=clang CXX=clang++-3.8 CC=clang-3.8 CXXFLAGS="-I/usr/include/libcxxabi/" LDFLAGS="-lc++"
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libc++abi-dev
|
||||
- clang-3.8
|
||||
- libc++-dev
|
||||
- libc++abi-dev
|
||||
- nasm
|
||||
- uuid-dev
|
||||
sources: *sources
|
||||
+2072
-11
File diff suppressed because it is too large.
Load diff
@@ -1,7 +1,10 @@
|
||||

|
||||
[](https://travis-ci.org/JohnSully/KeyDB) [](https://gitter.im/KeyDB/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||

|
||||
[](https://gitter.im/KeyDB/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://stackshare.io/eq-alpha-technology-inc/eq-alpha-technology-inc)
|
||||
|
||||
##### New! Want to extend KeyDB with Javascript? Try [ModJS](https://github.com/JohnSully/ModJS)
|
||||
|
||||
##### Need Help? Check out our extensive [documentation](https://docs.keydb.dev).
|
||||
|
||||
##### Have feedback? Take our quick survey: https://www.surveymonkey.com/r/Y9XNS93
|
||||
@@ -88,6 +91,11 @@ Compiling 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
|
||||
|
||||
You can enable flash support with:
|
||||
|
||||
% make MALLOC=memkind
|
||||
@@ -95,6 +103,13 @@ You can enable flash support with:
|
||||
***Note that the following dependencies may be needed:
|
||||
% sudo apt-get install autoconf autotools-dev libnuma-dev libtool
|
||||
|
||||
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
|
||||
---------
|
||||
|
||||
@@ -177,6 +192,14 @@ as options using the command line. Examples:
|
||||
All the options in keydb.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 KeyDB
|
||||
------------------
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
TLS Support
|
||||
===========
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
### Building
|
||||
|
||||
To build with TLS support you'll need OpenSSL development libraries (e.g.
|
||||
libssl-dev on Debian/Ubuntu).
|
||||
|
||||
Run `make BUILD_TLS=yes`.
|
||||
|
||||
### Tests
|
||||
|
||||
To run Redis test suite with TLS, you'll need TLS support for TCL (i.e.
|
||||
`tcl-tls` package on Debian/Ubuntu).
|
||||
|
||||
1. Run `./utils/gen-test-certs.sh` to generate a root CA and a server
|
||||
certificate.
|
||||
|
||||
2. Run `./runtest --tls` or `./runtest-cluster --tls` to run Redis and Redis
|
||||
Cluster tests in TLS mode.
|
||||
|
||||
### Running manually
|
||||
|
||||
To manually run a Redis server with TLS mode (assuming `gen-test-certs.sh` was
|
||||
invoked so sample certificates/keys are available):
|
||||
|
||||
./src/redis-server --tls-port 6379 --port 0 \
|
||||
--tls-cert-file ./tests/tls/redis.crt \
|
||||
--tls-key-file ./tests/tls/redis.key \
|
||||
--tls-ca-cert-file ./tests/tls/ca.crt
|
||||
|
||||
To connect to this Redis server with `redis-cli`:
|
||||
|
||||
./src/redis-cli --tls \
|
||||
--cert ./tests/tls/redis.crt \
|
||||
--key ./tests/tls/redis.key \
|
||||
--cacert ./tests/tls/ca.crt
|
||||
|
||||
This will disable TCP and enable TLS on port 6379. It's also possible to have
|
||||
both TCP and TLS available, but you'll need to assign different ports.
|
||||
|
||||
To make a Replica connect to the master using TLS, use `--tls-replication yes`,
|
||||
and to make Redis Cluster use TLS across nodes use `--tls-cluster yes`.
|
||||
|
||||
Connections
|
||||
-----------
|
||||
|
||||
All socket operations now go through a connection abstraction layer that hides
|
||||
I/O and read/write event handling from the caller.
|
||||
|
||||
Note that unlike Redis, KeyDB fully supports multithreading of TLS connections.
|
||||
|
||||
To-Do List
|
||||
----------
|
||||
|
||||
- [ ] Add session caching support. Check if/how it's handled by clients to
|
||||
assess how useful/important it is.
|
||||
- [ ] 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.
|
||||
|
||||
Multi-port
|
||||
----------
|
||||
|
||||
Consider the implications of allowing TLS to be configured on a separate port,
|
||||
making Redis listening on multiple ports:
|
||||
|
||||
1. Startup banner port notification
|
||||
2. Proctitle
|
||||
3. How slaves announce themselves
|
||||
4. Cluster bus port calculation
|
||||
Vendored
+5
-1
@@ -41,9 +41,13 @@ distclean:
|
||||
|
||||
.PHONY: distclean
|
||||
|
||||
ifeq ($(BUILD_TLS),yes)
|
||||
HIREDIS_MAKE_FLAGS = USE_SSL=1
|
||||
endif
|
||||
|
||||
hiredis: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
cd hiredis && $(MAKE) static
|
||||
cd hiredis && $(MAKE) static $(HIREDIS_MAKE_FLAGS)
|
||||
|
||||
.PHONY: hiredis
|
||||
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
/*.dylib
|
||||
/*.a
|
||||
/*.pc
|
||||
*.dSYM
|
||||
Vendored
+63
-11
@@ -26,20 +26,72 @@ addons:
|
||||
- libc6-dev-i386
|
||||
- libc6-dbg:i386
|
||||
- gcc-multilib
|
||||
- g++-multilib
|
||||
- valgrind
|
||||
|
||||
env:
|
||||
- CFLAGS="-Werror"
|
||||
- PRE="valgrind --track-origins=yes --leak-check=full"
|
||||
- TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
|
||||
- TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
|
||||
- BITS="32"
|
||||
- BITS="64"
|
||||
|
||||
script:
|
||||
- EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON";
|
||||
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||
if [ "$BITS" == "32" ]; then
|
||||
CFLAGS="-m32 -Werror";
|
||||
CXXFLAGS="-m32 -Werror";
|
||||
LDFLAGS="-m32";
|
||||
EXTRA_CMAKE_OPTS=;
|
||||
else
|
||||
CFLAGS="-Werror";
|
||||
CXXFLAGS="-Werror";
|
||||
fi;
|
||||
else
|
||||
TEST_PREFIX="valgrind --track-origins=yes --leak-check=full";
|
||||
if [ "$BITS" == "32" ]; then
|
||||
CFLAGS="-m32 -Werror";
|
||||
CXXFLAGS="-m32 -Werror";
|
||||
LDFLAGS="-m32";
|
||||
EXTRA_CMAKE_OPTS=;
|
||||
else
|
||||
CFLAGS="-Werror";
|
||||
CXXFLAGS="-Werror";
|
||||
fi;
|
||||
fi;
|
||||
export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS
|
||||
- mkdir build/ && cd build/
|
||||
- cmake .. ${EXTRA_CMAKE_OPTS}
|
||||
- make VERBOSE=1
|
||||
- ctest -V
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
env: PRE="valgrind --track-origins=yes --leak-check=full"
|
||||
include:
|
||||
# Windows MinGW cross compile on Linux
|
||||
- os: linux
|
||||
dist: xenial
|
||||
compiler: mingw
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- ninja-build
|
||||
- gcc-mingw-w64-x86-64
|
||||
- g++-mingw-w64-x86-64
|
||||
script:
|
||||
- mkdir build && cd build
|
||||
- CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on
|
||||
- ninja -v
|
||||
|
||||
- os: osx
|
||||
env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
|
||||
|
||||
script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example
|
||||
# Windows MSVC 2017
|
||||
- os: windows
|
||||
compiler: msvc
|
||||
env:
|
||||
- MATRIX_EVAL="CC=cl.exe && CXX=cl.exe"
|
||||
before_install:
|
||||
- eval "${MATRIX_EVAL}"
|
||||
install:
|
||||
- choco install ninja
|
||||
script:
|
||||
- mkdir build && cd build
|
||||
- cmd.exe /C '"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64 &&
|
||||
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release &&
|
||||
ninja -v'
|
||||
- ctest -V
|
||||
Vendored
+13
-2
@@ -12,6 +12,16 @@
|
||||
compare to other values, casting might be necessary or can be removed, if
|
||||
casting was applied before.
|
||||
|
||||
### 0.x.x (unreleased)
|
||||
**BREAKING CHANGES**:
|
||||
|
||||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||
|
||||
User code should compare this to `size_t` values as well.
|
||||
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
|
||||
|
||||
* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
|
||||
|
||||
### 0.14.0 (2018-09-25)
|
||||
|
||||
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
|
||||
@@ -50,8 +60,9 @@
|
||||
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
|
||||
* Fix warnings, when compiled with -Wshadow
|
||||
* Make hiredis compile in Cygwin on Windows, now CI-tested
|
||||
|
||||
**BREAKING CHANGES**:
|
||||
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
|
||||
protocol errors. This is consistent with the RESP specification. On 32-bit
|
||||
platforms, the upper bound is lowered to `SIZE_MAX`.
|
||||
|
||||
* Remove backwards compatibility macro's
|
||||
|
||||
|
||||
Vendored
+90
@@ -0,0 +1,90 @@
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0)
|
||||
INCLUDE(GNUInstallDirs)
|
||||
PROJECT(hiredis)
|
||||
|
||||
OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF)
|
||||
|
||||
MACRO(getVersionBit name)
|
||||
SET(VERSION_REGEX "^#define ${name} (.+)$")
|
||||
FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h"
|
||||
VERSION_BIT REGEX ${VERSION_REGEX})
|
||||
STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}")
|
||||
ENDMACRO(getVersionBit)
|
||||
|
||||
getVersionBit(HIREDIS_MAJOR)
|
||||
getVersionBit(HIREDIS_MINOR)
|
||||
getVersionBit(HIREDIS_PATCH)
|
||||
getVersionBit(HIREDIS_SONAME)
|
||||
SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}")
|
||||
MESSAGE("Detected version: ${VERSION}")
|
||||
|
||||
PROJECT(hiredis VERSION "${VERSION}")
|
||||
|
||||
SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples")
|
||||
|
||||
ADD_LIBRARY(hiredis SHARED
|
||||
async.c
|
||||
dict.c
|
||||
hiredis.c
|
||||
net.c
|
||||
read.c
|
||||
sds.c
|
||||
sockcompat.c)
|
||||
|
||||
SET_TARGET_PROPERTIES(hiredis
|
||||
PROPERTIES
|
||||
VERSION "${HIREDIS_SONAME}")
|
||||
IF(WIN32 OR MINGW)
|
||||
TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32)
|
||||
ENDIF()
|
||||
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .)
|
||||
|
||||
CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
|
||||
|
||||
INSTALL(TARGETS hiredis
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||
|
||||
INSTALL(FILES hiredis.h read.h sds.h async.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
||||
|
||||
INSTALL(DIRECTORY adapters
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
||||
|
||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
|
||||
IF(ENABLE_SSL)
|
||||
IF (NOT OPENSSL_ROOT_DIR)
|
||||
IF (APPLE)
|
||||
SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
FIND_PACKAGE(OpenSSL REQUIRED)
|
||||
ADD_LIBRARY(hiredis_ssl SHARED
|
||||
ssl.c)
|
||||
TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}")
|
||||
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES})
|
||||
CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
|
||||
|
||||
INSTALL(TARGETS hiredis_ssl
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||
|
||||
INSTALL(FILES hiredis_ssl.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
||||
|
||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
ENDIF()
|
||||
|
||||
IF(NOT (WIN32 OR MINGW))
|
||||
ENABLE_TESTING()
|
||||
ADD_EXECUTABLE(hiredis-test test.c)
|
||||
TARGET_LINK_LIBRARIES(hiredis-test hiredis)
|
||||
ADD_TEST(NAME hiredis-test
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)
|
||||
ENDIF()
|
||||
|
||||
# Add examples
|
||||
IF(ENABLE_EXAMPLES)
|
||||
ADD_SUBDIRECTORY(examples)
|
||||
ENDIF(ENABLE_EXAMPLES)
|
||||
Vendored
+83
-23
@@ -3,11 +3,17 @@
|
||||
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
|
||||
OBJ=net.o hiredis.o sds.o async.o read.o
|
||||
OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o
|
||||
SSL_OBJ=ssl.o
|
||||
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
|
||||
ifeq ($(USE_SSL),1)
|
||||
EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
|
||||
endif
|
||||
TESTS=hiredis-test
|
||||
LIBNAME=libhiredis
|
||||
SSL_LIBNAME=libhiredis_ssl
|
||||
PKGCONFNAME=hiredis.pc
|
||||
SSL_PKGCONFNAME=hiredis_ssl.pc
|
||||
|
||||
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
|
||||
HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
|
||||
@@ -39,7 +45,7 @@ export REDIS_TEST_CONFIG
|
||||
CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
||||
CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
|
||||
OPTIMIZATION?=-O3
|
||||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
|
||||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers
|
||||
DEBUG_FLAGS?= -g -ggdb
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
|
||||
REAL_LDFLAGS=$(LDFLAGS)
|
||||
@@ -49,12 +55,30 @@ STLIBSUFFIX=a
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
|
||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
||||
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
|
||||
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
|
||||
STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
|
||||
SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
|
||||
STLIB_MAKE_CMD=$(AR) rcs
|
||||
|
||||
# Platform-specific overrides
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
|
||||
USE_SSL?=0
|
||||
|
||||
# This is required for test.c only
|
||||
ifeq ($(USE_SSL),1)
|
||||
CFLAGS+=-DHIREDIS_TEST_SSL
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),Linux)
|
||||
SSL_LDFLAGS=-lssl -lcrypto
|
||||
else
|
||||
OPENSSL_PREFIX?=/usr/local/opt/openssl
|
||||
CFLAGS+=-I$(OPENSSL_PREFIX)/include
|
||||
SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
REAL_LDFLAGS+= -ldl -lnsl -lsocket
|
||||
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
|
||||
@@ -66,40 +90,61 @@ ifeq ($(uname_S),Darwin)
|
||||
endif
|
||||
|
||||
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
|
||||
ifeq ($(USE_SSL),1)
|
||||
all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
|
||||
endif
|
||||
|
||||
# Deps (use make dep to generate this)
|
||||
async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
|
||||
dict.o: dict.c fmacros.h dict.h
|
||||
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h
|
||||
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h
|
||||
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h win32.h
|
||||
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h
|
||||
read.o: read.c fmacros.h read.h sds.h
|
||||
sds.o: sds.c sds.h
|
||||
sockcompat.o: sockcompat.c sockcompat.h
|
||||
ssl.o: ssl.c hiredis.h
|
||||
test.o: test.c fmacros.h hiredis.h read.h sds.h
|
||||
|
||||
$(DYLIBNAME): $(OBJ)
|
||||
$(DYLIB_MAKE_CMD) $(OBJ)
|
||||
$(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
|
||||
|
||||
$(STLIBNAME): $(OBJ)
|
||||
$(STLIB_MAKE_CMD) $(OBJ)
|
||||
$(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
|
||||
|
||||
$(SSL_DYLIBNAME): $(SSL_OBJ)
|
||||
$(DYLIB_MAKE_CMD) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
||||
|
||||
$(SSL_STLIBNAME): $(SSL_OBJ)
|
||||
$(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
|
||||
|
||||
dynamic: $(DYLIBNAME)
|
||||
static: $(STLIBNAME)
|
||||
ifeq ($(USE_SSL),1)
|
||||
dynamic: $(SSL_DYLIBNAME)
|
||||
static: $(SSL_STLIBNAME)
|
||||
endif
|
||||
|
||||
# Binaries:
|
||||
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
||||
|
||||
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
||||
|
||||
ifndef AE_DIR
|
||||
hiredis-example-ae:
|
||||
@@ -116,7 +161,7 @@ hiredis-example-libuv:
|
||||
@false
|
||||
else
|
||||
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
|
||||
@@ -133,32 +178,33 @@ hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
|
||||
endif
|
||||
|
||||
hiredis-example: examples/example.c $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
examples: $(EXAMPLES)
|
||||
|
||||
hiredis-test: test.o $(STLIBNAME)
|
||||
TEST_LIBS = $(STLIBNAME)
|
||||
ifeq ($(USE_SSL),1)
|
||||
TEST_LIBS += $(SSL_STLIBNAME) -lssl -lcrypto -lpthread
|
||||
endif
|
||||
hiredis-test: test.o $(TEST_LIBS)
|
||||
|
||||
hiredis-%: %.o $(STLIBNAME)
|
||||
$(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME)
|
||||
$(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
|
||||
|
||||
test: hiredis-test
|
||||
./hiredis-test
|
||||
|
||||
check: hiredis-test
|
||||
@echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) -
|
||||
$(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \
|
||||
( kill `cat /tmp/hiredis-test-redis.pid` && false )
|
||||
kill `cat /tmp/hiredis-test-redis.pid`
|
||||
TEST_SSL=$(USE_SSL) ./test.sh
|
||||
|
||||
.c.o:
|
||||
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
|
||||
|
||||
clean:
|
||||
rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
|
||||
rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
|
||||
|
||||
INSTALL?= cp -pPR
|
||||
|
||||
@@ -175,6 +221,20 @@ $(PKGCONFNAME): hiredis.h
|
||||
@echo Libs: -L\$${libdir} -lhiredis >> $@
|
||||
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
||||
|
||||
$(SSL_PKGCONFNAME): hiredis.h
|
||||
@echo "Generating $@ for pkgconfig..."
|
||||
@echo prefix=$(PREFIX) > $@
|
||||
@echo exec_prefix=\$${prefix} >> $@
|
||||
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
|
||||
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
|
||||
@echo >> $@
|
||||
@echo Name: hiredis_ssl >> $@
|
||||
@echo Description: SSL Support for hiredis. >> $@
|
||||
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
|
||||
@echo Requires: hiredis >> $@
|
||||
@echo Libs: -L\$${libdir} -lhiredis_ssl >> $@
|
||||
@echo Libs.private: -lssl -lcrypto >> $@
|
||||
|
||||
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
|
||||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
|
||||
$(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH)
|
||||
|
||||
Vendored
+2
-1
@@ -286,6 +286,7 @@ return `REDIS_ERR`. The function to set the disconnect callback has the followin
|
||||
```c
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
```
|
||||
`ac->data` may be used to pass user data to this callback, the same can be done for redisConnectCallback.
|
||||
### Sending commands and their callbacks
|
||||
|
||||
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
|
||||
@@ -406,6 +407,6 @@ as soon as possible in order to prevent allocation of useless memory.
|
||||
## AUTHORS
|
||||
|
||||
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
|
||||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
|
||||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
|
||||
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
|
||||
Jan-Erik Rediger (janerik at fnordig dot com)
|
||||
Vendored
+88
-24
@@ -34,48 +34,113 @@
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
#define REDIS_LIBEVENT_DELETED 0x01
|
||||
#define REDIS_LIBEVENT_ENTERED 0x02
|
||||
|
||||
typedef struct redisLibeventEvents {
|
||||
redisAsyncContext *context;
|
||||
struct event *rev, *wev;
|
||||
struct event *ev;
|
||||
struct event_base *base;
|
||||
struct timeval tv;
|
||||
short flags;
|
||||
short state;
|
||||
} redisLibeventEvents;
|
||||
|
||||
static void redisLibeventReadEvent(int fd, short event, void *arg) {
|
||||
((void)fd); ((void)event);
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)arg;
|
||||
redisAsyncHandleRead(e->context);
|
||||
static void redisLibeventDestroy(redisLibeventEvents *e) {
|
||||
free(e);
|
||||
}
|
||||
|
||||
static void redisLibeventWriteEvent(int fd, short event, void *arg) {
|
||||
((void)fd); ((void)event);
|
||||
static void redisLibeventHandler(int fd, short event, void *arg) {
|
||||
((void)fd);
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)arg;
|
||||
redisAsyncHandleWrite(e->context);
|
||||
e->state |= REDIS_LIBEVENT_ENTERED;
|
||||
|
||||
#define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\
|
||||
redisLibeventDestroy(e);\
|
||||
return; \
|
||||
}
|
||||
|
||||
if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
|
||||
redisAsyncHandleTimeout(e->context);
|
||||
CHECK_DELETED();
|
||||
}
|
||||
|
||||
if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
|
||||
redisAsyncHandleRead(e->context);
|
||||
CHECK_DELETED();
|
||||
}
|
||||
|
||||
if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
|
||||
redisAsyncHandleWrite(e->context);
|
||||
CHECK_DELETED();
|
||||
}
|
||||
|
||||
e->state &= ~REDIS_LIBEVENT_ENTERED;
|
||||
#undef CHECK_DELETED
|
||||
}
|
||||
|
||||
static void redisLibeventUpdate(void *privdata, short flag, int isRemove) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents *)privdata;
|
||||
const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL;
|
||||
|
||||
if (isRemove) {
|
||||
if ((e->flags & flag) == 0) {
|
||||
return;
|
||||
} else {
|
||||
e->flags &= ~flag;
|
||||
}
|
||||
} else {
|
||||
if (e->flags & flag) {
|
||||
return;
|
||||
} else {
|
||||
e->flags |= flag;
|
||||
}
|
||||
}
|
||||
|
||||
event_del(e->ev);
|
||||
event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST,
|
||||
redisLibeventHandler, privdata);
|
||||
event_add(e->ev, tv);
|
||||
}
|
||||
|
||||
static void redisLibeventAddRead(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_add(e->rev,NULL);
|
||||
redisLibeventUpdate(privdata, EV_READ, 0);
|
||||
}
|
||||
|
||||
static void redisLibeventDelRead(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(e->rev);
|
||||
redisLibeventUpdate(privdata, EV_READ, 1);
|
||||
}
|
||||
|
||||
static void redisLibeventAddWrite(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_add(e->wev,NULL);
|
||||
redisLibeventUpdate(privdata, EV_WRITE, 0);
|
||||
}
|
||||
|
||||
static void redisLibeventDelWrite(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(e->wev);
|
||||
redisLibeventUpdate(privdata, EV_WRITE, 1);
|
||||
}
|
||||
|
||||
static void redisLibeventCleanup(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_free(e->rev);
|
||||
event_free(e->wev);
|
||||
free(e);
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
event_del(e->ev);
|
||||
event_free(e->ev);
|
||||
e->ev = NULL;
|
||||
|
||||
if (e->state & REDIS_LIBEVENT_ENTERED) {
|
||||
e->state |= REDIS_LIBEVENT_DELETED;
|
||||
} else {
|
||||
redisLibeventDestroy(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void redisLibeventSetTimeout(void *privdata, struct timeval tv) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents *)privdata;
|
||||
short flags = e->flags;
|
||||
e->flags = 0;
|
||||
e->tv = tv;
|
||||
redisLibeventUpdate(e, flags, 0);
|
||||
}
|
||||
|
||||
static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
||||
@@ -87,7 +152,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Create container for context and r/w events */
|
||||
e = (redisLibeventEvents*)malloc(sizeof(*e));
|
||||
e = (redisLibeventEvents*)calloc(1, sizeof(*e));
|
||||
e->context = ac;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
@@ -96,13 +161,12 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
||||
ac->ev.addWrite = redisLibeventAddWrite;
|
||||
ac->ev.delWrite = redisLibeventDelWrite;
|
||||
ac->ev.cleanup = redisLibeventCleanup;
|
||||
ac->ev.scheduleTimer = redisLibeventSetTimeout;
|
||||
ac->ev.data = e;
|
||||
|
||||
/* Initialize and install read/write events */
|
||||
e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e);
|
||||
e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e);
|
||||
event_add(e->rev, NULL);
|
||||
event_add(e->wev, NULL);
|
||||
e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);
|
||||
e->base = base;
|
||||
return REDIS_OK;
|
||||
}
|
||||
#endif
|
||||
Vendored
+4
-3
@@ -5,8 +5,9 @@ environment:
|
||||
CC: gcc
|
||||
- CYG_BASH: C:\cygwin\bin\bash
|
||||
CC: gcc
|
||||
TARGET: 32bit
|
||||
TARGET_VARS: 32bit-vars
|
||||
CFLAGS: -m32
|
||||
CXXFLAGS: -m32
|
||||
LDFLAGS: -m32
|
||||
|
||||
clone_depth: 1
|
||||
|
||||
@@ -20,4 +21,4 @@ install:
|
||||
|
||||
build_script:
|
||||
- 'echo building...'
|
||||
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make LDFLAGS=$LDFLAGS CC=$CC $TARGET CFLAGS=$CFLAGS && make LDFLAGS=$LDFLAGS CC=$CC $TARGET_VARS hiredis-example"'
|
||||
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; mkdir build && cd build && cmake .. -G \"Unix Makefiles\" && make VERBOSE=1"'
|
||||
Vendored
+113
-63
@@ -32,7 +32,9 @@
|
||||
#include "fmacros.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <strings.h>
|
||||
#endif
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
@@ -40,22 +42,9 @@
|
||||
#include "net.h"
|
||||
#include "dict.c"
|
||||
#include "sds.h"
|
||||
#include "win32.h"
|
||||
|
||||
#define _EL_ADD_READ(ctx) do { \
|
||||
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
|
||||
} while(0)
|
||||
#define _EL_DEL_READ(ctx) do { \
|
||||
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
|
||||
} while(0)
|
||||
#define _EL_ADD_WRITE(ctx) do { \
|
||||
if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
|
||||
} while(0)
|
||||
#define _EL_DEL_WRITE(ctx) do { \
|
||||
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
|
||||
} while(0)
|
||||
#define _EL_CLEANUP(ctx) do { \
|
||||
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
||||
} while(0);
|
||||
#include "async_private.h"
|
||||
|
||||
/* Forward declaration of function in hiredis.c */
|
||||
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
|
||||
@@ -126,6 +115,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
||||
ac->ev.addWrite = NULL;
|
||||
ac->ev.delWrite = NULL;
|
||||
ac->ev.cleanup = NULL;
|
||||
ac->ev.scheduleTimer = NULL;
|
||||
|
||||
ac->onConnect = NULL;
|
||||
ac->onDisconnect = NULL;
|
||||
@@ -150,56 +140,52 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) {
|
||||
ac->errstr = c->errstr;
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
|
||||
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
|
||||
redisOptions myOptions = *options;
|
||||
redisContext *c;
|
||||
redisAsyncContext *ac;
|
||||
|
||||
c = redisConnectNonBlock(ip,port);
|
||||
if (c == NULL)
|
||||
myOptions.options |= REDIS_OPT_NONBLOCK;
|
||||
c = redisConnectWithOptions(&myOptions);
|
||||
if (c == NULL) {
|
||||
return NULL;
|
||||
|
||||
}
|
||||
ac = redisAsyncInitialize(c);
|
||||
if (ac == NULL) {
|
||||
redisFree(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
return redisAsyncConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisContext *c = redisConnectBindNonBlock(ip,port,source_addr);
|
||||
redisAsyncContext *ac = redisAsyncInitialize(c);
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.endpoint.tcp.source_addr = source_addr;
|
||||
return redisAsyncConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr);
|
||||
redisAsyncContext *ac = redisAsyncInitialize(c);
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.options |= REDIS_OPT_REUSEADDR;
|
||||
options.endpoint.tcp.source_addr = source_addr;
|
||||
return redisAsyncConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnectUnix(const char *path) {
|
||||
redisContext *c;
|
||||
redisAsyncContext *ac;
|
||||
|
||||
c = redisConnectUnixNonBlock(path);
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
ac = redisAsyncInitialize(c);
|
||||
if (ac == NULL) {
|
||||
redisFree(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||
return redisAsyncConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
|
||||
@@ -328,7 +314,7 @@ void redisAsyncFree(redisAsyncContext *ac) {
|
||||
}
|
||||
|
||||
/* Helper function to make the disconnect happen and clean up. */
|
||||
static void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
|
||||
/* Make sure error is accessible if there is any */
|
||||
@@ -344,9 +330,15 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
c->flags |= REDIS_DISCONNECTING;
|
||||
}
|
||||
|
||||
/* cleanup event library on disconnect.
|
||||
* this is safe to call multiple times */
|
||||
_EL_CLEANUP(ac);
|
||||
|
||||
/* For non-clean disconnects, __redisAsyncFree() will execute pending
|
||||
* callbacks with a NULL-reply. */
|
||||
__redisAsyncFree(ac);
|
||||
if (!(c->flags & REDIS_NO_AUTO_FREE)) {
|
||||
__redisAsyncFree(ac);
|
||||
}
|
||||
}
|
||||
|
||||
/* Tries to do a clean disconnect from Redis, meaning it stops new commands
|
||||
@@ -358,6 +350,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
c->flags |= REDIS_DISCONNECTING;
|
||||
|
||||
/** unset the auto-free flag here, because disconnect undoes this */
|
||||
c->flags &= ~REDIS_NO_AUTO_FREE;
|
||||
if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
|
||||
__redisAsyncDisconnect(ac);
|
||||
}
|
||||
@@ -408,7 +403,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
||||
assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
|
||||
|
||||
/* Unset subscribed flag only when no pipelined pending subscribe. */
|
||||
if (reply->element[2]->integer == 0
|
||||
if (reply->element[2]->integer == 0
|
||||
&& dictSize(ac->sub.channels) == 0
|
||||
&& dictSize(ac->sub.patterns) == 0)
|
||||
c->flags &= ~REDIS_SUBSCRIBED;
|
||||
@@ -524,6 +519,18 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
|
||||
}
|
||||
}
|
||||
|
||||
void redisAsyncRead(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
|
||||
if (redisBufferRead(c) == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
/* Always re-schedule reads */
|
||||
_EL_ADD_READ(ac);
|
||||
redisProcessCallbacks(ac);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function should be called when the socket is readable.
|
||||
* It processes all replies that can be read and executes their callbacks.
|
||||
*/
|
||||
@@ -539,28 +546,13 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (redisBufferRead(c) == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
/* Always re-schedule reads */
|
||||
_EL_ADD_READ(ac);
|
||||
redisProcessCallbacks(ac);
|
||||
}
|
||||
c->funcs->async_read(ac);
|
||||
}
|
||||
|
||||
void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||
void redisAsyncWrite(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
int done = 0;
|
||||
|
||||
if (!(c->flags & REDIS_CONNECTED)) {
|
||||
/* Abort connect was not successful. */
|
||||
if (__redisAsyncHandleConnect(ac) != REDIS_OK)
|
||||
return;
|
||||
/* Try again later when the context is still not connected. */
|
||||
if (!(c->flags & REDIS_CONNECTED))
|
||||
return;
|
||||
}
|
||||
|
||||
if (redisBufferWrite(c,&done) == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
@@ -575,6 +567,51 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||
}
|
||||
}
|
||||
|
||||
void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
|
||||
if (!(c->flags & REDIS_CONNECTED)) {
|
||||
/* Abort connect was not successful. */
|
||||
if (__redisAsyncHandleConnect(ac) != REDIS_OK)
|
||||
return;
|
||||
/* Try again later when the context is still not connected. */
|
||||
if (!(c->flags & REDIS_CONNECTED))
|
||||
return;
|
||||
}
|
||||
|
||||
c->funcs->async_write(ac);
|
||||
}
|
||||
|
||||
void __redisSetError(redisContext *c, int type, const char *str);
|
||||
|
||||
void redisAsyncHandleTimeout(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
|
||||
if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) {
|
||||
/* Nothing to do - just an idle timeout */
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c->err) {
|
||||
__redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout");
|
||||
}
|
||||
|
||||
if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) {
|
||||
ac->onConnect(ac, REDIS_ERR);
|
||||
}
|
||||
|
||||
while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {
|
||||
__redisRunCallback(ac, &cb, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Don't automatically sever the connection,
|
||||
* rather, allow to ignore <x> responses before the queue is clear
|
||||
*/
|
||||
__redisAsyncDisconnect(ac);
|
||||
}
|
||||
|
||||
/* Sets a pointer to the first argument and its length starting at p. Returns
|
||||
* the number of bytes to skip to get to the following argument. */
|
||||
static const char *nextArgument(const char *start, const char **str, size_t *len) {
|
||||
@@ -714,3 +751,16 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
return status;
|
||||
}
|
||||
|
||||
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
|
||||
if (!ac->c.timeout) {
|
||||
ac->c.timeout = calloc(1, sizeof(tv));
|
||||
}
|
||||
|
||||
if (tv.tv_sec == ac->c.timeout->tv_sec &&
|
||||
tv.tv_usec == ac->c.timeout->tv_usec) {
|
||||
return;
|
||||
}
|
||||
|
||||
*ac->c.timeout = tv;
|
||||
}
|
||||
Vendored
+8
@@ -57,6 +57,7 @@ typedef struct redisCallbackList {
|
||||
/* Connection callback prototypes */
|
||||
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
|
||||
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
|
||||
typedef void(redisTimerCallback)(void *timer, void *privdata);
|
||||
|
||||
/* Context for an async connection to Redis */
|
||||
typedef struct redisAsyncContext {
|
||||
@@ -81,6 +82,7 @@ typedef struct redisAsyncContext {
|
||||
void (*addWrite)(void *privdata);
|
||||
void (*delWrite)(void *privdata);
|
||||
void (*cleanup)(void *privdata);
|
||||
void (*scheduleTimer)(void *privdata, struct timeval tv);
|
||||
} ev;
|
||||
|
||||
/* Called when either the connection is terminated due to an error or per
|
||||
@@ -106,6 +108,7 @@ typedef struct redisAsyncContext {
|
||||
} redisAsyncContext;
|
||||
|
||||
/* Functions that proxy to hiredis */
|
||||
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
|
||||
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
|
||||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
||||
@@ -113,12 +116,17 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
||||
redisAsyncContext *redisAsyncConnectUnix(const char *path);
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
|
||||
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac);
|
||||
void redisAsyncFree(redisAsyncContext *ac);
|
||||
|
||||
/* Handle read/write events */
|
||||
void redisAsyncHandleRead(redisAsyncContext *ac);
|
||||
void redisAsyncHandleWrite(redisAsyncContext *ac);
|
||||
void redisAsyncHandleTimeout(redisAsyncContext *ac);
|
||||
void redisAsyncRead(redisAsyncContext *ac);
|
||||
void redisAsyncWrite(redisAsyncContext *ac);
|
||||
|
||||
/* Command functions for an async context. Write the command to the
|
||||
* output buffer and register the provided callback. */
|
||||
|
||||
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * 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 __HIREDIS_ASYNC_PRIVATE_H
|
||||
#define __HIREDIS_ASYNC_PRIVATE_H
|
||||
|
||||
#define _EL_ADD_READ(ctx) \
|
||||
do { \
|
||||
refreshTimeout(ctx); \
|
||||
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
|
||||
} while (0)
|
||||
#define _EL_DEL_READ(ctx) do { \
|
||||
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
|
||||
} while(0)
|
||||
#define _EL_ADD_WRITE(ctx) \
|
||||
do { \
|
||||
refreshTimeout(ctx); \
|
||||
if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
|
||||
} while (0)
|
||||
#define _EL_DEL_WRITE(ctx) do { \
|
||||
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
|
||||
} while(0)
|
||||
#define _EL_CLEANUP(ctx) do { \
|
||||
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
||||
ctx->ev.cleanup = NULL; \
|
||||
} while(0);
|
||||
|
||||
static inline void refreshTimeout(redisAsyncContext *ctx) {
|
||||
if (ctx->c.timeout && ctx->ev.scheduleTimer &&
|
||||
(ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) {
|
||||
ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout);
|
||||
// } else {
|
||||
// printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout);
|
||||
// if (ctx->c.timeout){
|
||||
// printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec,
|
||||
// ctx->c.timeout->tv_usec);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void __redisAsyncDisconnect(redisAsyncContext *ac);
|
||||
void redisProcessCallbacks(redisAsyncContext *ac);
|
||||
|
||||
#endif /* __HIREDIS_ASYNC_PRIVATE_H */
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
INCLUDE(FindPkgConfig)
|
||||
# Check for GLib
|
||||
|
||||
PKG_CHECK_MODULES(GLIB2 glib-2.0)
|
||||
if (GLIB2_FOUND)
|
||||
INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
|
||||
LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS})
|
||||
ADD_EXECUTABLE(example-glib example-glib.c)
|
||||
TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES})
|
||||
ENDIF(GLIB2_FOUND)
|
||||
|
||||
FIND_PATH(LIBEV ev.h
|
||||
HINTS /usr/local /usr/opt/local
|
||||
ENV LIBEV_INCLUDE_DIR)
|
||||
|
||||
if (LIBEV)
|
||||
# Just compile and link with libev
|
||||
ADD_EXECUTABLE(example-libev example-libev.c)
|
||||
TARGET_LINK_LIBRARIES(example-libev hiredis ev)
|
||||
ENDIF()
|
||||
|
||||
FIND_PATH(LIBEVENT event.h)
|
||||
if (LIBEVENT)
|
||||
ADD_EXECUTABLE(example-libevent example-libevent)
|
||||
TARGET_LINK_LIBRARIES(example-libevent hiredis event)
|
||||
ENDIF()
|
||||
|
||||
FIND_PATH(LIBUV uv.h)
|
||||
IF (LIBUV)
|
||||
ADD_EXECUTABLE(example-libuv example-libuv.c)
|
||||
TARGET_LINK_LIBRARIES(example-libuv hiredis uv)
|
||||
ENDIF()
|
||||
|
||||
IF (APPLE)
|
||||
FIND_LIBRARY(CF CoreFoundation)
|
||||
ADD_EXECUTABLE(example-macosx example-macosx.c)
|
||||
TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF})
|
||||
ENDIF()
|
||||
|
||||
IF (ENABLE_SSL)
|
||||
ADD_EXECUTABLE(example-ssl example-ssl.c)
|
||||
TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl)
|
||||
ENDIF()
|
||||
|
||||
ADD_EXECUTABLE(example example.c)
|
||||
TARGET_LINK_LIBRARIES(example hiredis)
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <hiredis.h>
|
||||
#include <hiredis_ssl.h>
|
||||
#include <async.h>
|
||||
#include <adapters/libevent.h>
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) return;
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* Disconnect after receiving the reply to GET */
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void connectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return;
|
||||
}
|
||||
printf("Connected...\n");
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return;
|
||||
}
|
||||
printf("Disconnected...\n");
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
struct event_base *base = event_base_new();
|
||||
if (argc < 5) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s <key> <host> <port> <cert> <certKey> [ca]\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const char *value = argv[1];
|
||||
size_t nvalue = strlen(value);
|
||||
|
||||
const char *hostname = argv[2];
|
||||
int port = atoi(argv[3]);
|
||||
|
||||
const char *cert = argv[4];
|
||||
const char *certKey = argv[5];
|
||||
const char *caCert = argc > 5 ? argv[6] : NULL;
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect(hostname, port);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) {
|
||||
printf("SSL Error!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
redisLibeventAttach(c,base);
|
||||
redisAsyncSetConnectCallback(c,connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
event_base_dispatch(base);
|
||||
return 0;
|
||||
}
|
||||
+13
-2
@@ -9,7 +9,12 @@
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) return;
|
||||
if (reply == NULL) {
|
||||
if (c->errstr) {
|
||||
printf("errstr: %s\n", c->errstr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* Disconnect after receiving the reply to GET */
|
||||
@@ -35,8 +40,14 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
struct event_base *base = event_base_new();
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
|
||||
struct timeval tv = {0};
|
||||
tv.tv_sec = 1;
|
||||
options.timeout = &tv;
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
|
||||
Vendored
+97
@@ -0,0 +1,97 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <hiredis.h>
|
||||
#include <hiredis_ssl.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
unsigned int j;
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
if (argc < 4) {
|
||||
printf("Usage: %s <host> <port> <cert> <key> [ca]\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
|
||||
int port = atoi(argv[2]);
|
||||
const char *cert = argv[3];
|
||||
const char *key = argv[4];
|
||||
const char *ca = argc > 4 ? argv[5] : NULL;
|
||||
|
||||
struct timeval tv = { 1, 500000 }; // 1.5 seconds
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, hostname, port);
|
||||
options.timeout = &tv;
|
||||
c = redisConnectWithOptions(&options);
|
||||
|
||||
if (c == NULL || c->err) {
|
||||
if (c) {
|
||||
printf("Connection error: %s\n", c->errstr);
|
||||
redisFree(c);
|
||||
} else {
|
||||
printf("Connection error: can't allocate redis context\n");
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) {
|
||||
printf("Couldn't initialize SSL!\n");
|
||||
printf("Error: %s\n", c->errstr);
|
||||
redisFree(c);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* PING server */
|
||||
reply = redisCommand(c,"PING");
|
||||
printf("PING: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Set a key */
|
||||
reply = redisCommand(c,"SET %s %s", "foo", "hello world");
|
||||
printf("SET: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Set a key using binary safe API */
|
||||
reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5);
|
||||
printf("SET (binary API): %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Try a GET and two INCR */
|
||||
reply = redisCommand(c,"GET foo");
|
||||
printf("GET foo: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
reply = redisCommand(c,"INCR counter");
|
||||
printf("INCR counter: %lld\n", reply->integer);
|
||||
freeReplyObject(reply);
|
||||
/* again ... */
|
||||
reply = redisCommand(c,"INCR counter");
|
||||
printf("INCR counter: %lld\n", reply->integer);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Create a list of numbers, from 0 to 9 */
|
||||
reply = redisCommand(c,"DEL mylist");
|
||||
freeReplyObject(reply);
|
||||
for (j = 0; j < 10; j++) {
|
||||
char buf[64];
|
||||
|
||||
snprintf(buf,64,"%u",j);
|
||||
reply = redisCommand(c,"LPUSH mylist element-%s", buf);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
/* Let's check what we have inside the list */
|
||||
reply = redisCommand(c,"LRANGE mylist 0 -1");
|
||||
if (reply->type == REDIS_REPLY_ARRAY) {
|
||||
for (j = 0; j < reply->elements; j++) {
|
||||
printf("%u) %s\n", j, reply->element[j]->str);
|
||||
}
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Disconnects and frees the context */
|
||||
redisFree(c);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Vendored
+15
-2
@@ -5,14 +5,27 @@
|
||||
#include <hiredis.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
unsigned int j;
|
||||
unsigned int j, isunix = 0;
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
|
||||
|
||||
if (argc > 2) {
|
||||
if (*argv[2] == 'u' || *argv[2] == 'U') {
|
||||
isunix = 1;
|
||||
/* in this case, host is the path to the unix socket */
|
||||
printf("Will connect to unix socket @%s\n", hostname);
|
||||
}
|
||||
}
|
||||
|
||||
int port = (argc > 2) ? atoi(argv[2]) : 6379;
|
||||
|
||||
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
|
||||
c = redisConnectWithTimeout(hostname, port, timeout);
|
||||
if (isunix) {
|
||||
c = redisConnectUnixWithTimeout(hostname, timeout);
|
||||
} else {
|
||||
c = redisConnectWithTimeout(hostname, port, timeout);
|
||||
}
|
||||
if (c == NULL || c->err) {
|
||||
if (c) {
|
||||
printf("Connection error: %s\n", c->errstr);
|
||||
|
||||
Vendored
+134
-122
@@ -34,7 +34,6 @@
|
||||
#include "fmacros.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
@@ -42,10 +41,20 @@
|
||||
#include "hiredis.h"
|
||||
#include "net.h"
|
||||
#include "sds.h"
|
||||
#include "async.h"
|
||||
#include "win32.h"
|
||||
|
||||
static redisContextFuncs redisContextDefaultFuncs = {
|
||||
.free_privdata = NULL,
|
||||
.async_read = redisAsyncRead,
|
||||
.async_write = redisAsyncWrite,
|
||||
.read = redisNetRead,
|
||||
.write = redisNetWrite
|
||||
};
|
||||
|
||||
static redisReply *createReplyObject(int type);
|
||||
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
|
||||
static void *createArrayObject(const redisReadTask *task, int elements);
|
||||
static void *createArrayObject(const redisReadTask *task, size_t elements);
|
||||
static void *createIntegerObject(const redisReadTask *task, long long value);
|
||||
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
|
||||
static void *createNilObject(const redisReadTask *task);
|
||||
@@ -112,21 +121,34 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
buf = malloc(len+1);
|
||||
if (buf == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(task->type == REDIS_REPLY_ERROR ||
|
||||
task->type == REDIS_REPLY_STATUS ||
|
||||
task->type == REDIS_REPLY_STRING);
|
||||
task->type == REDIS_REPLY_STRING ||
|
||||
task->type == REDIS_REPLY_VERB);
|
||||
|
||||
/* Copy string value */
|
||||
memcpy(buf,str,len);
|
||||
buf[len] = '\0';
|
||||
if (task->type == REDIS_REPLY_VERB) {
|
||||
buf = malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
|
||||
if (buf == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(r->vtype,str,3);
|
||||
r->vtype[3] = '\0';
|
||||
memcpy(buf,str+4,len-4);
|
||||
buf[len-4] = '\0';
|
||||
r->len = len-4;
|
||||
} else {
|
||||
buf = malloc(len+1);
|
||||
if (buf == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(buf,str,len);
|
||||
buf[len] = '\0';
|
||||
r->len = len;
|
||||
}
|
||||
r->str = buf;
|
||||
r->len = len;
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
@@ -138,7 +160,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
||||
return r;
|
||||
}
|
||||
|
||||
static void *createArrayObject(const redisReadTask *task, int elements) {
|
||||
static void *createArrayObject(const redisReadTask *task, size_t elements) {
|
||||
redisReply *r, *parent;
|
||||
|
||||
r = createReplyObject(task->type);
|
||||
@@ -649,29 +671,30 @@ redisReader *redisReaderCreate(void) {
|
||||
return redisReaderCreateWithFunctions(&defaultFunctions);
|
||||
}
|
||||
|
||||
static redisContext *redisContextInit(void) {
|
||||
static redisContext *redisContextInit(const redisOptions *options) {
|
||||
redisContext *c;
|
||||
|
||||
c = calloc(1,sizeof(redisContext));
|
||||
c = calloc(1, sizeof(*c));
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->funcs = &redisContextDefaultFuncs;
|
||||
c->obuf = sdsempty();
|
||||
c->reader = redisReaderCreate();
|
||||
c->fd = REDIS_INVALID_FD;
|
||||
|
||||
if (c->obuf == NULL || c->reader == NULL) {
|
||||
redisFree(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
(void)options; /* options are used in other functions */
|
||||
return c;
|
||||
}
|
||||
|
||||
void redisFree(redisContext *c) {
|
||||
if (c == NULL)
|
||||
return;
|
||||
if (c->fd > 0)
|
||||
close(c->fd);
|
||||
redisNetClose(c);
|
||||
|
||||
sdsfree(c->obuf);
|
||||
redisReaderFree(c->reader);
|
||||
@@ -680,12 +703,16 @@ void redisFree(redisContext *c) {
|
||||
free(c->unix_sock.path);
|
||||
free(c->timeout);
|
||||
free(c->saddr);
|
||||
if (c->funcs->free_privdata) {
|
||||
c->funcs->free_privdata(c->privdata);
|
||||
}
|
||||
memset(c, 0xff, sizeof(*c));
|
||||
free(c);
|
||||
}
|
||||
|
||||
int redisFreeKeepFd(redisContext *c) {
|
||||
int fd = c->fd;
|
||||
c->fd = -1;
|
||||
redisFD redisFreeKeepFd(redisContext *c) {
|
||||
redisFD fd = c->fd;
|
||||
c->fd = REDIS_INVALID_FD;
|
||||
redisFree(c);
|
||||
return fd;
|
||||
}
|
||||
@@ -694,10 +721,13 @@ int redisReconnect(redisContext *c) {
|
||||
c->err = 0;
|
||||
memset(c->errstr, '\0', strlen(c->errstr));
|
||||
|
||||
if (c->fd > 0) {
|
||||
close(c->fd);
|
||||
if (c->privdata && c->funcs->free_privdata) {
|
||||
c->funcs->free_privdata(c->privdata);
|
||||
c->privdata = NULL;
|
||||
}
|
||||
|
||||
redisNetClose(c);
|
||||
|
||||
sdsfree(c->obuf);
|
||||
redisReaderFree(c->reader);
|
||||
|
||||
@@ -718,112 +748,107 @@ int redisReconnect(redisContext *c) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
redisContext *redisConnectWithOptions(const redisOptions *options) {
|
||||
redisContext *c = redisContextInit(options);
|
||||
if (c == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (!(options->options & REDIS_OPT_NONBLOCK)) {
|
||||
c->flags |= REDIS_BLOCK;
|
||||
}
|
||||
if (options->options & REDIS_OPT_REUSEADDR) {
|
||||
c->flags |= REDIS_REUSEADDR;
|
||||
}
|
||||
if (options->options & REDIS_OPT_NOAUTOFREE) {
|
||||
c->flags |= REDIS_NO_AUTO_FREE;
|
||||
}
|
||||
|
||||
if (options->type == REDIS_CONN_TCP) {
|
||||
redisContextConnectBindTcp(c, options->endpoint.tcp.ip,
|
||||
options->endpoint.tcp.port, options->timeout,
|
||||
options->endpoint.tcp.source_addr);
|
||||
} else if (options->type == REDIS_CONN_UNIX) {
|
||||
redisContextConnectUnix(c, options->endpoint.unix_socket,
|
||||
options->timeout);
|
||||
} else if (options->type == REDIS_CONN_USERFD) {
|
||||
c->fd = options->endpoint.fd;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
} else {
|
||||
// Unknown type - FIXME - FREE
|
||||
return NULL;
|
||||
}
|
||||
if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
|
||||
redisContextSetTimeout(c, *options->timeout);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Connect to a Redis instance. On error the field error in the returned
|
||||
* context will be set to the return value of the error function.
|
||||
* When no set of reply functions is given, the default set will be used. */
|
||||
redisContext *redisConnect(const char *ip, int port) {
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->flags |= REDIS_BLOCK;
|
||||
redisContextConnectTcp(c,ip,port,NULL);
|
||||
return c;
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->flags |= REDIS_BLOCK;
|
||||
redisContextConnectTcp(c,ip,port,&tv);
|
||||
return c;
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.timeout = &tv;
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
redisContext *redisConnectNonBlock(const char *ip, int port) {
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
redisContextConnectTcp(c,ip,port,NULL);
|
||||
return c;
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.options |= REDIS_OPT_NONBLOCK;
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
redisContext *redisConnectBindNonBlock(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisContext *c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
|
||||
return c;
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.endpoint.tcp.source_addr = source_addr;
|
||||
options.options |= REDIS_OPT_NONBLOCK;
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisContext *c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
c->flags |= REDIS_REUSEADDR;
|
||||
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
|
||||
return c;
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.endpoint.tcp.source_addr = source_addr;
|
||||
options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR;
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
redisContext *redisConnectUnix(const char *path) {
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->flags |= REDIS_BLOCK;
|
||||
redisContextConnectUnix(c,path,NULL);
|
||||
return c;
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->flags |= REDIS_BLOCK;
|
||||
redisContextConnectUnix(c,path,&tv);
|
||||
return c;
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||
options.timeout = &tv;
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
redisContext *redisConnectUnixNonBlock(const char *path) {
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
redisContextConnectUnix(c,path,NULL);
|
||||
return c;
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||
options.options |= REDIS_OPT_NONBLOCK;
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
redisContext *redisConnectFd(int fd) {
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->fd = fd;
|
||||
c->flags |= REDIS_BLOCK | REDIS_CONNECTED;
|
||||
return c;
|
||||
redisContext *redisConnectFd(redisFD fd) {
|
||||
redisOptions options = {0};
|
||||
options.type = REDIS_CONN_USERFD;
|
||||
options.endpoint.fd = fd;
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
/* Set read/write timeout on a blocking socket. */
|
||||
@@ -853,22 +878,15 @@ int redisBufferRead(redisContext *c) {
|
||||
if (c->err)
|
||||
return REDIS_ERR;
|
||||
|
||||
nread = read(c->fd,buf,sizeof(buf));
|
||||
if (nread == -1) {
|
||||
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||
/* Try again later */
|
||||
nread = c->funcs->read(c, buf, sizeof(buf));
|
||||
if (nread > 0) {
|
||||
if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
|
||||
__redisSetError(c, c->reader->err, c->reader->errstr);
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
} else if (nread == 0) {
|
||||
__redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
|
||||
} else if (nread < 0) {
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
|
||||
__redisSetError(c,c->reader->err,c->reader->errstr);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
@@ -883,21 +901,15 @@ int redisBufferRead(redisContext *c) {
|
||||
* c->errstr to hold the appropriate error string.
|
||||
*/
|
||||
int redisBufferWrite(redisContext *c, int *done) {
|
||||
int nwritten;
|
||||
|
||||
/* Return early when the context has seen an error. */
|
||||
if (c->err)
|
||||
return REDIS_ERR;
|
||||
|
||||
if (sdslen(c->obuf) > 0) {
|
||||
nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
|
||||
if (nwritten == -1) {
|
||||
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||
/* Try again later */
|
||||
} else {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
int nwritten = c->funcs->write(c);
|
||||
if (nwritten < 0) {
|
||||
return REDIS_ERR;
|
||||
} else if (nwritten > 0) {
|
||||
if (nwritten == (signed)sdslen(c->obuf)) {
|
||||
sdsfree(c->obuf);
|
||||
|
||||
Vendored
+99
-4
@@ -35,7 +35,11 @@
|
||||
#define __HIREDIS_H
|
||||
#include "read.h"
|
||||
#include <stdarg.h> /* for va_list */
|
||||
#ifndef _MSC_VER
|
||||
#include <sys/time.h> /* for struct timeval */
|
||||
#else
|
||||
struct timeval; /* forward declaration */
|
||||
#endif
|
||||
#include <stdint.h> /* uintXX_t, etc */
|
||||
#include "sds.h" /* for sds */
|
||||
|
||||
@@ -74,6 +78,12 @@
|
||||
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
|
||||
#define REDIS_REUSEADDR 0x80
|
||||
|
||||
/**
|
||||
* Flag that indicates the user does not want the context to
|
||||
* be automatically freed upon error
|
||||
*/
|
||||
#define REDIS_NO_AUTO_FREE 0x200
|
||||
|
||||
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||
|
||||
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
|
||||
@@ -92,6 +102,8 @@ typedef struct redisReply {
|
||||
size_t len; /* Length of string */
|
||||
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
||||
and REDIS_REPLY_DOUBLE (in additionl to dval). */
|
||||
char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
|
||||
terminated 3 character content type, such as "txt". */
|
||||
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
|
||||
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
|
||||
} redisReply;
|
||||
@@ -111,14 +123,93 @@ void redisFreeSdsCommand(sds cmd);
|
||||
|
||||
enum redisConnectionType {
|
||||
REDIS_CONN_TCP,
|
||||
REDIS_CONN_UNIX
|
||||
REDIS_CONN_UNIX,
|
||||
REDIS_CONN_USERFD
|
||||
};
|
||||
|
||||
struct redisSsl;
|
||||
|
||||
#define REDIS_OPT_NONBLOCK 0x01
|
||||
#define REDIS_OPT_REUSEADDR 0x02
|
||||
|
||||
/**
|
||||
* Don't automatically free the async object on a connection failure,
|
||||
* or other implicit conditions. Only free on an explicit call to disconnect() or free()
|
||||
*/
|
||||
#define REDIS_OPT_NOAUTOFREE 0x04
|
||||
|
||||
/* In Unix systems a file descriptor is a regular signed int, with -1
|
||||
* representing an invalid descriptor. In Windows it is a SOCKET
|
||||
* (32- or 64-bit unsigned integer depending on the architecture), where
|
||||
* all bits set (~0) is INVALID_SOCKET. */
|
||||
#ifndef _WIN32
|
||||
typedef int redisFD;
|
||||
#define REDIS_INVALID_FD -1
|
||||
#else
|
||||
#ifdef _WIN64
|
||||
typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */
|
||||
#else
|
||||
typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */
|
||||
#endif
|
||||
#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
/*
|
||||
* the type of connection to use. This also indicates which
|
||||
* `endpoint` member field to use
|
||||
*/
|
||||
int type;
|
||||
/* bit field of REDIS_OPT_xxx */
|
||||
int options;
|
||||
/* timeout value. if NULL, no timeout is used */
|
||||
const struct timeval *timeout;
|
||||
union {
|
||||
/** use this field for tcp/ip connections */
|
||||
struct {
|
||||
const char *source_addr;
|
||||
const char *ip;
|
||||
int port;
|
||||
} tcp;
|
||||
/** use this field for unix domain sockets */
|
||||
const char *unix_socket;
|
||||
/**
|
||||
* use this field to have hiredis operate an already-open
|
||||
* file descriptor */
|
||||
redisFD fd;
|
||||
} endpoint;
|
||||
} redisOptions;
|
||||
|
||||
/**
|
||||
* Helper macros to initialize options to their specified fields.
|
||||
*/
|
||||
#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \
|
||||
(opts)->type = REDIS_CONN_TCP; \
|
||||
(opts)->endpoint.tcp.ip = ip_; \
|
||||
(opts)->endpoint.tcp.port = port_;
|
||||
|
||||
#define REDIS_OPTIONS_SET_UNIX(opts, path) \
|
||||
(opts)->type = REDIS_CONN_UNIX; \
|
||||
(opts)->endpoint.unix_socket = path;
|
||||
|
||||
struct redisAsyncContext;
|
||||
struct redisContext;
|
||||
|
||||
typedef struct redisContextFuncs {
|
||||
void (*free_privdata)(void *);
|
||||
void (*async_read)(struct redisAsyncContext *);
|
||||
void (*async_write)(struct redisAsyncContext *);
|
||||
int (*read)(struct redisContext *, char *, size_t);
|
||||
int (*write)(struct redisContext *);
|
||||
} redisContextFuncs;
|
||||
|
||||
/* Context for a connection to Redis */
|
||||
typedef struct redisContext {
|
||||
const redisContextFuncs *funcs; /* Function table */
|
||||
|
||||
int err; /* Error flags, 0 when there is no error */
|
||||
char errstr[128]; /* String representation of error when applicable */
|
||||
int fd;
|
||||
redisFD fd;
|
||||
int flags;
|
||||
char *obuf; /* Write buffer */
|
||||
redisReader *reader; /* Protocol reader */
|
||||
@@ -139,8 +230,12 @@ typedef struct redisContext {
|
||||
/* For non-blocking connect */
|
||||
struct sockadr *saddr;
|
||||
size_t addrlen;
|
||||
|
||||
/* Additional private data for hiredis addons such as SSL */
|
||||
void *privdata;
|
||||
} redisContext;
|
||||
|
||||
redisContext *redisConnectWithOptions(const redisOptions *options);
|
||||
redisContext *redisConnect(const char *ip, int port);
|
||||
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
|
||||
redisContext *redisConnectNonBlock(const char *ip, int port);
|
||||
@@ -151,7 +246,7 @@ redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
||||
redisContext *redisConnectUnix(const char *path);
|
||||
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
|
||||
redisContext *redisConnectUnixNonBlock(const char *path);
|
||||
redisContext *redisConnectFd(int fd);
|
||||
redisContext *redisConnectFd(redisFD fd);
|
||||
|
||||
/**
|
||||
* Reconnect the given context using the saved information.
|
||||
@@ -167,7 +262,7 @@ int redisReconnect(redisContext *c);
|
||||
int redisSetTimeout(redisContext *c, const struct timeval tv);
|
||||
int redisEnableKeepAlive(redisContext *c);
|
||||
void redisFree(redisContext *c);
|
||||
int redisFreeKeepFd(redisContext *c);
|
||||
redisFD redisFreeKeepFd(redisContext *c);
|
||||
int redisBufferRead(redisContext *c);
|
||||
int redisBufferWrite(redisContext *c, int *done);
|
||||
|
||||
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
pkgincludedir=${includedir}/hiredis
|
||||
|
||||
Name: hiredis
|
||||
Description: Minimalistic C client library for Redis.
|
||||
Version: @PROJECT_VERSION@
|
||||
Libs: -L${libdir} -lhiredis
|
||||
Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64
|
||||
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019, Redis Labs
|
||||
*
|
||||
* 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 __HIREDIS_SSL_H
|
||||
#define __HIREDIS_SSL_H
|
||||
|
||||
/* This is the underlying struct for SSL in ssl.h, which is not included to
|
||||
* keep build dependencies short here.
|
||||
*/
|
||||
struct ssl_st;
|
||||
|
||||
/**
|
||||
* Secure the connection using SSL. This should be done before any command is
|
||||
* executed on the connection.
|
||||
*/
|
||||
int redisSecureConnection(redisContext *c, const char *capath, const char *certpath,
|
||||
const char *keypath, const char *servername);
|
||||
|
||||
/**
|
||||
* Initiate SSL/TLS negotiation on a provided context.
|
||||
*/
|
||||
|
||||
int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
|
||||
|
||||
#endif /* __HIREDIS_SSL_H */
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
pkgincludedir=${includedir}/hiredis
|
||||
|
||||
Name: hiredis_ssl
|
||||
Description: SSL Support for hiredis.
|
||||
Version: @PROJECT_VERSION@
|
||||
Requires: hiredis
|
||||
Libs: -L${libdir} -lhiredis_ssl
|
||||
Libs.private: -lssl -lcrypto
|
||||
Vendored
+87
-35
@@ -34,36 +34,64 @@
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <poll.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "net.h"
|
||||
#include "sds.h"
|
||||
#include "sockcompat.h"
|
||||
#include "win32.h"
|
||||
|
||||
/* Defined in hiredis.c */
|
||||
void __redisSetError(redisContext *c, int type, const char *str);
|
||||
|
||||
static void redisContextCloseFd(redisContext *c) {
|
||||
if (c && c->fd >= 0) {
|
||||
void redisNetClose(redisContext *c) {
|
||||
if (c && c->fd != REDIS_INVALID_FD) {
|
||||
close(c->fd);
|
||||
c->fd = -1;
|
||||
c->fd = REDIS_INVALID_FD;
|
||||
}
|
||||
}
|
||||
|
||||
int redisNetRead(redisContext *c, char *buf, size_t bufcap) {
|
||||
int nread = recv(c->fd, buf, bufcap, 0);
|
||||
if (nread == -1) {
|
||||
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||
/* Try again later */
|
||||
return 0;
|
||||
} else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) {
|
||||
/* especially in windows */
|
||||
__redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout");
|
||||
return -1;
|
||||
} else {
|
||||
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||
return -1;
|
||||
}
|
||||
} else if (nread == 0) {
|
||||
__redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
|
||||
return -1;
|
||||
} else {
|
||||
return nread;
|
||||
}
|
||||
}
|
||||
|
||||
int redisNetWrite(redisContext *c) {
|
||||
int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0);
|
||||
if (nwritten < 0) {
|
||||
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||
/* Try again later */
|
||||
} else {
|
||||
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return nwritten;
|
||||
}
|
||||
|
||||
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
|
||||
int errorno = errno; /* snprintf() may change errno */
|
||||
char buf[128] = { 0 };
|
||||
@@ -79,15 +107,15 @@ static int redisSetReuseAddr(redisContext *c) {
|
||||
int on = 1;
|
||||
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||
redisContextCloseFd(c);
|
||||
redisNetClose(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int redisCreateSocket(redisContext *c, int type) {
|
||||
int s;
|
||||
if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
|
||||
redisFD s;
|
||||
if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
@@ -101,6 +129,7 @@ static int redisCreateSocket(redisContext *c, int type) {
|
||||
}
|
||||
|
||||
static int redisSetBlocking(redisContext *c, int blocking) {
|
||||
#ifndef _WIN32
|
||||
int flags;
|
||||
|
||||
/* Set the socket nonblocking.
|
||||
@@ -108,7 +137,7 @@ static int redisSetBlocking(redisContext *c, int blocking) {
|
||||
* interrupted by a signal. */
|
||||
if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
|
||||
redisContextCloseFd(c);
|
||||
redisNetClose(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
@@ -119,15 +148,23 @@ static int redisSetBlocking(redisContext *c, int blocking) {
|
||||
|
||||
if (fcntl(c->fd, F_SETFL, flags) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
|
||||
redisContextCloseFd(c);
|
||||
redisNetClose(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
#else
|
||||
u_long mode = blocking ? 0 : 1;
|
||||
if (ioctl(c->fd, FIONBIO, &mode) == -1) {
|
||||
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)");
|
||||
redisNetClose(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
#endif /* _WIN32 */
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisKeepAlive(redisContext *c, int interval) {
|
||||
int val = 1;
|
||||
int fd = c->fd;
|
||||
redisFD fd = c->fd;
|
||||
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
|
||||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||
@@ -170,7 +207,7 @@ static int redisSetTcpNoDelay(redisContext *c) {
|
||||
int yes = 1;
|
||||
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
|
||||
redisContextCloseFd(c);
|
||||
redisNetClose(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
@@ -212,12 +249,12 @@ static int redisContextWaitReady(redisContext *c, long msec) {
|
||||
|
||||
if ((res = poll(wfd, 1, msec)) == -1) {
|
||||
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
|
||||
redisContextCloseFd(c);
|
||||
redisNetClose(c);
|
||||
return REDIS_ERR;
|
||||
} else if (res == 0) {
|
||||
errno = ETIMEDOUT;
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||
redisContextCloseFd(c);
|
||||
redisNetClose(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
@@ -230,7 +267,7 @@ static int redisContextWaitReady(redisContext *c, long msec) {
|
||||
}
|
||||
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||
redisContextCloseFd(c);
|
||||
redisNetClose(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
@@ -277,11 +314,18 @@ int redisCheckSocketError(redisContext *c) {
|
||||
}
|
||||
|
||||
int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
||||
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
|
||||
const void *to_ptr = &tv;
|
||||
size_t to_sz = sizeof(tv);
|
||||
#ifdef _WIN32
|
||||
DWORD timeout_msec = tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||
to_ptr = &timeout_msec;
|
||||
to_sz = sizeof(timeout_msec);
|
||||
#endif
|
||||
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
|
||||
if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
@@ -291,7 +335,8 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
||||
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
const struct timeval *timeout,
|
||||
const char *source_addr) {
|
||||
int s, rv, n;
|
||||
redisFD s;
|
||||
int rv, n;
|
||||
char _port[6]; /* strlen("65535"); */
|
||||
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
|
||||
int blocking = (c->flags & REDIS_BLOCK);
|
||||
@@ -360,7 +405,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
}
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
addrretry:
|
||||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
|
||||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD)
|
||||
continue;
|
||||
|
||||
c->fd = s;
|
||||
@@ -401,16 +446,14 @@ addrretry:
|
||||
}
|
||||
|
||||
/* For repeat connection */
|
||||
if (c->saddr) {
|
||||
free(c->saddr);
|
||||
}
|
||||
free(c->saddr);
|
||||
c->saddr = malloc(p->ai_addrlen);
|
||||
memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
|
||||
c->addrlen = p->ai_addrlen;
|
||||
|
||||
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
|
||||
if (errno == EHOSTUNREACH) {
|
||||
redisContextCloseFd(c);
|
||||
redisNetClose(c);
|
||||
continue;
|
||||
} else if (errno == EINPROGRESS) {
|
||||
if (blocking) {
|
||||
@@ -424,7 +467,7 @@ addrretry:
|
||||
if (++reuses >= REDIS_CONNECT_RETRIES) {
|
||||
goto error;
|
||||
} else {
|
||||
redisContextCloseFd(c);
|
||||
redisNetClose(c);
|
||||
goto addrretry;
|
||||
}
|
||||
} else {
|
||||
@@ -471,8 +514,9 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
|
||||
}
|
||||
|
||||
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
|
||||
#ifndef _WIN32
|
||||
int blocking = (c->flags & REDIS_BLOCK);
|
||||
struct sockaddr_un sa;
|
||||
struct sockaddr_un *sa;
|
||||
long timeout_msec = -1;
|
||||
|
||||
if (redisCreateSocket(c,AF_UNIX) < 0)
|
||||
@@ -499,9 +543,11 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
||||
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
|
||||
sa.sun_family = AF_UNIX;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
||||
sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un)));
|
||||
c->addrlen = sizeof(struct sockaddr_un);
|
||||
sa->sun_family = AF_UNIX;
|
||||
strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
|
||||
if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) {
|
||||
if (errno == EINPROGRESS && !blocking) {
|
||||
/* This is ok. */
|
||||
} else {
|
||||
@@ -516,4 +562,10 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
||||
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
return REDIS_OK;
|
||||
#else
|
||||
/* We currently do not support Unix sockets for Windows. */
|
||||
/* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */
|
||||
errno = EPROTONOSUPPORT;
|
||||
return REDIS_ERR;
|
||||
#endif /* _WIN32 */
|
||||
}
|
||||
Vendored
+4
@@ -37,6 +37,10 @@
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
void redisNetClose(redisContext *c);
|
||||
int redisNetRead(redisContext *c, char *buf, size_t bufcap);
|
||||
int redisNetWrite(redisContext *c);
|
||||
|
||||
int redisCheckSocketError(redisContext *c);
|
||||
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
|
||||
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
|
||||
|
||||
Vendored
+22
-6
@@ -31,10 +31,10 @@
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <stdlib.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#include <strings.h>
|
||||
#endif
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
#include "read.h"
|
||||
#include "sds.h"
|
||||
#include "win32.h"
|
||||
|
||||
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
||||
size_t len;
|
||||
@@ -294,9 +295,9 @@ static int processLineItem(redisReader *r) {
|
||||
buf[len] = '\0';
|
||||
|
||||
if (strcasecmp(buf,",inf") == 0) {
|
||||
d = 1.0/0.0; /* Positive infinite. */
|
||||
d = INFINITY; /* Positive infinite. */
|
||||
} else if (strcasecmp(buf,",-inf") == 0) {
|
||||
d = -1.0/0.0; /* Nevative infinite. */
|
||||
d = -INFINITY; /* Nevative infinite. */
|
||||
} else {
|
||||
d = strtod((char*)buf,&eptr);
|
||||
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
|
||||
@@ -379,10 +380,18 @@ static int processBulkItem(redisReader *r) {
|
||||
/* Only continue when the buffer contains the entire bulk item. */
|
||||
bytelen += len+2; /* include \r\n */
|
||||
if (r->pos+bytelen <= r->len) {
|
||||
if ((cur->type == REDIS_REPLY_VERB && len < 4) ||
|
||||
(cur->type == REDIS_REPLY_VERB && s[5] != ':'))
|
||||
{
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Verbatim string 4 bytes of content type are "
|
||||
"missing or incorrectly encoded.");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (r->fn && r->fn->createString)
|
||||
obj = r->fn->createString(cur,s+2,len);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_STRING;
|
||||
obj = (void*)(long)cur->type;
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
@@ -430,7 +439,7 @@ static int processAggregateItem(redisReader *r) {
|
||||
|
||||
root = (r->ridx == 0);
|
||||
|
||||
if (elements < -1 || elements > INT_MAX) {
|
||||
if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX)) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Multi-bulk length out of range");
|
||||
return REDIS_ERR;
|
||||
@@ -523,6 +532,9 @@ static int processItem(redisReader *r) {
|
||||
case '#':
|
||||
cur->type = REDIS_REPLY_BOOL;
|
||||
break;
|
||||
case '=':
|
||||
cur->type = REDIS_REPLY_VERB;
|
||||
break;
|
||||
default:
|
||||
__redisReaderSetErrorProtocolByte(r,*p);
|
||||
return REDIS_ERR;
|
||||
@@ -543,6 +555,7 @@ static int processItem(redisReader *r) {
|
||||
case REDIS_REPLY_BOOL:
|
||||
return processLineItem(r);
|
||||
case REDIS_REPLY_STRING:
|
||||
case REDIS_REPLY_VERB:
|
||||
return processBulkItem(r);
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_MAP:
|
||||
@@ -657,8 +670,11 @@ int redisReaderGetReply(redisReader *r, void **reply) {
|
||||
|
||||
/* Emit a reply when there is one. */
|
||||
if (r->ridx == -1) {
|
||||
if (reply != NULL)
|
||||
if (reply != NULL) {
|
||||
*reply = r->reply;
|
||||
} else if (r->reply != NULL && r->fn && r->fn->freeObject) {
|
||||
r->fn->freeObject(r->reply);
|
||||
}
|
||||
r->reply = NULL;
|
||||
}
|
||||
return REDIS_OK;
|
||||
|
||||
Vendored
+3
-2
@@ -45,6 +45,7 @@
|
||||
#define REDIS_ERR_EOF 3 /* End of file */
|
||||
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
|
||||
#define REDIS_ERR_OOM 5 /* Out of memory */
|
||||
#define REDIS_ERR_TIMEOUT 6 /* Timed out */
|
||||
#define REDIS_ERR_OTHER 2 /* Everything else... */
|
||||
|
||||
#define REDIS_REPLY_STRING 1
|
||||
@@ -55,12 +56,12 @@
|
||||
#define REDIS_REPLY_ERROR 6
|
||||
#define REDIS_REPLY_DOUBLE 7
|
||||
#define REDIS_REPLY_BOOL 8
|
||||
#define REDIS_REPLY_VERB 9
|
||||
#define REDIS_REPLY_MAP 9
|
||||
#define REDIS_REPLY_SET 10
|
||||
#define REDIS_REPLY_ATTR 11
|
||||
#define REDIS_REPLY_PUSH 12
|
||||
#define REDIS_REPLY_BIGNUM 13
|
||||
#define REDIS_REPLY_VERB 14
|
||||
|
||||
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
|
||||
|
||||
@@ -79,7 +80,7 @@ typedef struct redisReadTask {
|
||||
|
||||
typedef struct redisReplyObjectFunctions {
|
||||
void *(*createString)(const redisReadTask*, char*, size_t);
|
||||
void *(*createArray)(const redisReadTask*, int);
|
||||
void *(*createArray)(const redisReadTask*, size_t);
|
||||
void *(*createInteger)(const redisReadTask*, long long);
|
||||
void *(*createDouble)(const redisReadTask*, double, char*, size_t);
|
||||
void *(*createNil)(const redisReadTask*);
|
||||
|
||||
Vendored
+1
-1
@@ -1035,7 +1035,7 @@ sds *sdssplitargs(const char *line, int *argc) {
|
||||
s_free(vector);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
vector = new_vector;
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
|
||||
Vendored
+17
-14
@@ -34,6 +34,9 @@
|
||||
#define __SDS_H
|
||||
|
||||
#define SDS_MAX_PREALLOC (1024*1024)
|
||||
#ifdef _MSC_VER
|
||||
#define __attribute__(x)
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
@@ -143,20 +146,20 @@ static inline void sdssetlen(sds s, size_t newlen) {
|
||||
case SDS_TYPE_5:
|
||||
{
|
||||
unsigned char *fp = ((unsigned char*)s)-1;
|
||||
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
|
||||
*fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS));
|
||||
}
|
||||
break;
|
||||
case SDS_TYPE_8:
|
||||
SDS_HDR(8,s)->len = newlen;
|
||||
SDS_HDR(8,s)->len = (uint8_t)newlen;
|
||||
break;
|
||||
case SDS_TYPE_16:
|
||||
SDS_HDR(16,s)->len = newlen;
|
||||
SDS_HDR(16,s)->len = (uint16_t)newlen;
|
||||
break;
|
||||
case SDS_TYPE_32:
|
||||
SDS_HDR(32,s)->len = newlen;
|
||||
SDS_HDR(32,s)->len = (uint32_t)newlen;
|
||||
break;
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->len = newlen;
|
||||
SDS_HDR(64,s)->len = (uint64_t)newlen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -167,21 +170,21 @@ static inline void sdsinclen(sds s, size_t inc) {
|
||||
case SDS_TYPE_5:
|
||||
{
|
||||
unsigned char *fp = ((unsigned char*)s)-1;
|
||||
unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
|
||||
unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
|
||||
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
|
||||
}
|
||||
break;
|
||||
case SDS_TYPE_8:
|
||||
SDS_HDR(8,s)->len += inc;
|
||||
SDS_HDR(8,s)->len += (uint8_t)inc;
|
||||
break;
|
||||
case SDS_TYPE_16:
|
||||
SDS_HDR(16,s)->len += inc;
|
||||
SDS_HDR(16,s)->len += (uint16_t)inc;
|
||||
break;
|
||||
case SDS_TYPE_32:
|
||||
SDS_HDR(32,s)->len += inc;
|
||||
SDS_HDR(32,s)->len += (uint32_t)inc;
|
||||
break;
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->len += inc;
|
||||
SDS_HDR(64,s)->len += (uint64_t)inc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -211,16 +214,16 @@ static inline void sdssetalloc(sds s, size_t newlen) {
|
||||
/* Nothing to do, this type has no total allocation info. */
|
||||
break;
|
||||
case SDS_TYPE_8:
|
||||
SDS_HDR(8,s)->alloc = newlen;
|
||||
SDS_HDR(8,s)->alloc = (uint8_t)newlen;
|
||||
break;
|
||||
case SDS_TYPE_16:
|
||||
SDS_HDR(16,s)->alloc = newlen;
|
||||
SDS_HDR(16,s)->alloc = (uint16_t)newlen;
|
||||
break;
|
||||
case SDS_TYPE_32:
|
||||
SDS_HDR(32,s)->alloc = newlen;
|
||||
SDS_HDR(32,s)->alloc = (uint32_t)newlen;
|
||||
break;
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->alloc = newlen;
|
||||
SDS_HDR(64,s)->alloc = (uint64_t)newlen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+248
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define REDIS_SOCKCOMPAT_IMPLEMENTATION
|
||||
#include "sockcompat.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
static int _wsaErrorToErrno(int err) {
|
||||
switch (err) {
|
||||
case WSAEWOULDBLOCK:
|
||||
return EWOULDBLOCK;
|
||||
case WSAEINPROGRESS:
|
||||
return EINPROGRESS;
|
||||
case WSAEALREADY:
|
||||
return EALREADY;
|
||||
case WSAENOTSOCK:
|
||||
return ENOTSOCK;
|
||||
case WSAEDESTADDRREQ:
|
||||
return EDESTADDRREQ;
|
||||
case WSAEMSGSIZE:
|
||||
return EMSGSIZE;
|
||||
case WSAEPROTOTYPE:
|
||||
return EPROTOTYPE;
|
||||
case WSAENOPROTOOPT:
|
||||
return ENOPROTOOPT;
|
||||
case WSAEPROTONOSUPPORT:
|
||||
return EPROTONOSUPPORT;
|
||||
case WSAEOPNOTSUPP:
|
||||
return EOPNOTSUPP;
|
||||
case WSAEAFNOSUPPORT:
|
||||
return EAFNOSUPPORT;
|
||||
case WSAEADDRINUSE:
|
||||
return EADDRINUSE;
|
||||
case WSAEADDRNOTAVAIL:
|
||||
return EADDRNOTAVAIL;
|
||||
case WSAENETDOWN:
|
||||
return ENETDOWN;
|
||||
case WSAENETUNREACH:
|
||||
return ENETUNREACH;
|
||||
case WSAENETRESET:
|
||||
return ENETRESET;
|
||||
case WSAECONNABORTED:
|
||||
return ECONNABORTED;
|
||||
case WSAECONNRESET:
|
||||
return ECONNRESET;
|
||||
case WSAENOBUFS:
|
||||
return ENOBUFS;
|
||||
case WSAEISCONN:
|
||||
return EISCONN;
|
||||
case WSAENOTCONN:
|
||||
return ENOTCONN;
|
||||
case WSAETIMEDOUT:
|
||||
return ETIMEDOUT;
|
||||
case WSAECONNREFUSED:
|
||||
return ECONNREFUSED;
|
||||
case WSAELOOP:
|
||||
return ELOOP;
|
||||
case WSAENAMETOOLONG:
|
||||
return ENAMETOOLONG;
|
||||
case WSAEHOSTUNREACH:
|
||||
return EHOSTUNREACH;
|
||||
case WSAENOTEMPTY:
|
||||
return ENOTEMPTY;
|
||||
default:
|
||||
/* We just return a generic I/O error if we could not find a relevant error. */
|
||||
return EIO;
|
||||
}
|
||||
}
|
||||
|
||||
static void _updateErrno(int success) {
|
||||
errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError());
|
||||
}
|
||||
|
||||
static int _initWinsock() {
|
||||
static int s_initialized = 0;
|
||||
if (!s_initialized) {
|
||||
static WSADATA wsadata;
|
||||
int err = WSAStartup(MAKEWORD(2,2), &wsadata);
|
||||
if (err != 0) {
|
||||
errno = _wsaErrorToErrno(err);
|
||||
return 0;
|
||||
}
|
||||
s_initialized = 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
|
||||
/* Note: This function is likely to be called before other functions, so run init here. */
|
||||
if (!_initWinsock()) {
|
||||
return EAI_FAIL;
|
||||
}
|
||||
|
||||
switch (getaddrinfo(node, service, hints, res)) {
|
||||
case 0: return 0;
|
||||
case WSATRY_AGAIN: return EAI_AGAIN;
|
||||
case WSAEINVAL: return EAI_BADFLAGS;
|
||||
case WSAEAFNOSUPPORT: return EAI_FAMILY;
|
||||
case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY;
|
||||
case WSAHOST_NOT_FOUND: return EAI_NONAME;
|
||||
case WSATYPE_NOT_FOUND: return EAI_SERVICE;
|
||||
case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE;
|
||||
default: return EAI_FAIL; /* Including WSANO_RECOVERY */
|
||||
}
|
||||
}
|
||||
|
||||
const char *win32_gai_strerror(int errcode) {
|
||||
switch (errcode) {
|
||||
case 0: errcode = 0; break;
|
||||
case EAI_AGAIN: errcode = WSATRY_AGAIN; break;
|
||||
case EAI_BADFLAGS: errcode = WSAEINVAL; break;
|
||||
case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break;
|
||||
case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break;
|
||||
case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break;
|
||||
case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break;
|
||||
case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break;
|
||||
default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */
|
||||
}
|
||||
return gai_strerror(errcode);
|
||||
}
|
||||
|
||||
void win32_freeaddrinfo(struct addrinfo *res) {
|
||||
freeaddrinfo(res);
|
||||
}
|
||||
|
||||
SOCKET win32_socket(int domain, int type, int protocol) {
|
||||
SOCKET s;
|
||||
|
||||
/* Note: This function is likely to be called before other functions, so run init here. */
|
||||
if (!_initWinsock()) {
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
_updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET);
|
||||
return s;
|
||||
}
|
||||
|
||||
int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) {
|
||||
int ret = ioctlsocket(fd, (long)request, argp);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
|
||||
int ret = bind(sockfd, addr, addrlen);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
|
||||
int ret = connect(sockfd, addr, addrlen);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
|
||||
/* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as
|
||||
* EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX
|
||||
* logic consistent. */
|
||||
if (errno == EWOULDBLOCK) {
|
||||
errno = EINPROGRESS;
|
||||
}
|
||||
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) {
|
||||
int ret = 0;
|
||||
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
|
||||
if (*optlen >= sizeof (struct timeval)) {
|
||||
struct timeval *tv = optval;
|
||||
DWORD timeout = 0;
|
||||
socklen_t dwlen = 0;
|
||||
ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen);
|
||||
tv->tv_sec = timeout / 1000;
|
||||
tv->tv_usec = (timeout * 1000) % 1000000;
|
||||
} else {
|
||||
ret = WSAEFAULT;
|
||||
}
|
||||
*optlen = sizeof (struct timeval);
|
||||
} else {
|
||||
ret = getsockopt(sockfd, level, optname, (char*)optval, optlen);
|
||||
}
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {
|
||||
int ret = 0;
|
||||
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
|
||||
struct timeval *tv = optval;
|
||||
DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;
|
||||
ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
|
||||
} else {
|
||||
ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen);
|
||||
}
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_close(SOCKET fd) {
|
||||
int ret = closesocket(fd);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) {
|
||||
int ret = recv(sockfd, (char*)buf, (int)len, flags);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) {
|
||||
int ret = send(sockfd, (const char*)buf, (int)len, flags);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
|
||||
int ret = WSAPoll(fds, nfds, timeout);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
#endif /* _WIN32 */
|
||||
Vendored
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
|
||||
*
|
||||
* 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 __SOCKCOMPAT_H
|
||||
#define __SOCKCOMPAT_H
|
||||
|
||||
#ifndef _WIN32
|
||||
/* For POSIX systems we use the standard BSD socket API. */
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <poll.h>
|
||||
#else
|
||||
/* For Windows we use winsock. */
|
||||
#undef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
typedef signed long ssize_t;
|
||||
#endif
|
||||
|
||||
/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */
|
||||
int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
|
||||
const char *win32_gai_strerror(int errcode);
|
||||
void win32_freeaddrinfo(struct addrinfo *res);
|
||||
SOCKET win32_socket(int domain, int type, int protocol);
|
||||
int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp);
|
||||
int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
||||
int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
||||
int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen);
|
||||
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen);
|
||||
int win32_close(SOCKET fd);
|
||||
ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags);
|
||||
ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags);
|
||||
typedef ULONG nfds_t;
|
||||
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||
|
||||
#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION
|
||||
#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res)
|
||||
#undef gai_strerror
|
||||
#define gai_strerror(errcode) win32_gai_strerror(errcode)
|
||||
#define freeaddrinfo(res) win32_freeaddrinfo(res)
|
||||
#define socket(domain, type, protocol) win32_socket(domain, type, protocol)
|
||||
#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp)
|
||||
#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen)
|
||||
#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen)
|
||||
#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen)
|
||||
#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen)
|
||||
#define close(fd) win32_close(fd)
|
||||
#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags)
|
||||
#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags)
|
||||
#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout)
|
||||
#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#endif /* __SOCKCOMPAT_H */
|
||||
Vendored
+448
@@ -0,0 +1,448 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2019, Redis Labs
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "async.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include "async_private.h"
|
||||
|
||||
void __redisSetError(redisContext *c, int type, const char *str);
|
||||
|
||||
/* The SSL context is attached to SSL/TLS connections as a privdata. */
|
||||
typedef struct redisSSLContext {
|
||||
/**
|
||||
* OpenSSL SSL_CTX; It is optional and will not be set when using
|
||||
* user-supplied SSL.
|
||||
*/
|
||||
SSL_CTX *ssl_ctx;
|
||||
|
||||
/**
|
||||
* OpenSSL SSL object.
|
||||
*/
|
||||
SSL *ssl;
|
||||
|
||||
/**
|
||||
* SSL_write() requires to be called again with the same arguments it was
|
||||
* previously called with in the event of an SSL_read/SSL_write situation
|
||||
*/
|
||||
size_t lastLen;
|
||||
|
||||
/** Whether the SSL layer requires read (possibly before a write) */
|
||||
int wantRead;
|
||||
|
||||
/**
|
||||
* Whether a write was requested prior to a read. If set, the write()
|
||||
* should resume whenever a read takes place, if possible
|
||||
*/
|
||||
int pendingWrite;
|
||||
} redisSSLContext;
|
||||
|
||||
/* Forward declaration */
|
||||
redisContextFuncs redisContextSSLFuncs;
|
||||
|
||||
#ifdef HIREDIS_SSL_TRACE
|
||||
/**
|
||||
* Callback used for debugging
|
||||
*/
|
||||
static void sslLogCallback(const SSL *ssl, int where, int ret) {
|
||||
const char *retstr = "";
|
||||
int should_log = 1;
|
||||
/* Ignore low-level SSL stuff */
|
||||
|
||||
if (where & SSL_CB_ALERT) {
|
||||
should_log = 1;
|
||||
}
|
||||
if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) {
|
||||
should_log = 1;
|
||||
}
|
||||
if ((where & SSL_CB_EXIT) && ret == 0) {
|
||||
should_log = 1;
|
||||
}
|
||||
|
||||
if (!should_log) {
|
||||
return;
|
||||
}
|
||||
|
||||
retstr = SSL_alert_type_string(ret);
|
||||
printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr);
|
||||
|
||||
if (where == SSL_CB_HANDSHAKE_DONE) {
|
||||
printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* OpenSSL global initialization and locking handling callbacks.
|
||||
* Note that this is only required for OpenSSL < 1.1.0.
|
||||
*/
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
#define HIREDIS_USE_CRYPTO_LOCKS
|
||||
#endif
|
||||
|
||||
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
||||
typedef pthread_mutex_t sslLockType;
|
||||
static void sslLockInit(sslLockType *l) {
|
||||
pthread_mutex_init(l, NULL);
|
||||
}
|
||||
static void sslLockAcquire(sslLockType *l) {
|
||||
pthread_mutex_lock(l);
|
||||
}
|
||||
static void sslLockRelease(sslLockType *l) {
|
||||
pthread_mutex_unlock(l);
|
||||
}
|
||||
static pthread_mutex_t *ossl_locks;
|
||||
|
||||
static void opensslDoLock(int mode, int lkid, const char *f, int line) {
|
||||
sslLockType *l = ossl_locks + lkid;
|
||||
|
||||
if (mode & CRYPTO_LOCK) {
|
||||
sslLockAcquire(l);
|
||||
} else {
|
||||
sslLockRelease(l);
|
||||
}
|
||||
|
||||
(void)f;
|
||||
(void)line;
|
||||
}
|
||||
|
||||
static void initOpensslLocks(void) {
|
||||
unsigned ii, nlocks;
|
||||
if (CRYPTO_get_locking_callback() != NULL) {
|
||||
/* Someone already set the callback before us. Don't destroy it! */
|
||||
return;
|
||||
}
|
||||
nlocks = CRYPTO_num_locks();
|
||||
ossl_locks = malloc(sizeof(*ossl_locks) * nlocks);
|
||||
for (ii = 0; ii < nlocks; ii++) {
|
||||
sslLockInit(ossl_locks + ii);
|
||||
}
|
||||
CRYPTO_set_locking_callback(opensslDoLock);
|
||||
}
|
||||
#endif /* HIREDIS_USE_CRYPTO_LOCKS */
|
||||
|
||||
/**
|
||||
* SSL Connection initialization.
|
||||
*/
|
||||
|
||||
static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
|
||||
if (c->privdata) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
c->privdata = calloc(1, sizeof(redisSSLContext));
|
||||
|
||||
c->funcs = &redisContextSSLFuncs;
|
||||
redisSSLContext *rssl = c->privdata;
|
||||
|
||||
rssl->ssl_ctx = ssl_ctx;
|
||||
rssl->ssl = ssl;
|
||||
|
||||
SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
SSL_set_fd(rssl->ssl, c->fd);
|
||||
SSL_set_connect_state(rssl->ssl);
|
||||
|
||||
ERR_clear_error();
|
||||
int rv = SSL_connect(rssl->ssl);
|
||||
if (rv == 1) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
rv = SSL_get_error(rssl->ssl, rv);
|
||||
if (((c->flags & REDIS_BLOCK) == 0) &&
|
||||
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
if (c->err == 0) {
|
||||
char err[512];
|
||||
if (rv == SSL_ERROR_SYSCALL)
|
||||
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
|
||||
else {
|
||||
unsigned long e = ERR_peek_last_error();
|
||||
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
|
||||
ERR_reason_error_string(e));
|
||||
}
|
||||
__redisSetError(c, REDIS_ERR_IO, err);
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
int redisInitiateSSL(redisContext *c, SSL *ssl) {
|
||||
return redisSSLConnect(c, NULL, ssl);
|
||||
}
|
||||
|
||||
int redisSecureConnection(redisContext *c, const char *capath,
|
||||
const char *certpath, const char *keypath, const char *servername) {
|
||||
|
||||
SSL_CTX *ssl_ctx = NULL;
|
||||
SSL *ssl = NULL;
|
||||
|
||||
/* Initialize global OpenSSL stuff */
|
||||
static int isInit = 0;
|
||||
if (!isInit) {
|
||||
isInit = 1;
|
||||
SSL_library_init();
|
||||
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
||||
initOpensslLocks();
|
||||
#endif
|
||||
}
|
||||
|
||||
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
|
||||
if (!ssl_ctx) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX");
|
||||
goto error;
|
||||
}
|
||||
|
||||
#ifdef HIREDIS_SSL_TRACE
|
||||
SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback);
|
||||
#endif
|
||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
||||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
|
||||
if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (capath) {
|
||||
if (!SSL_CTX_load_verify_locations(ssl_ctx, capath, NULL)) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Invalid CA certificate");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (certpath) {
|
||||
if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certpath)) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client certificate");
|
||||
goto error;
|
||||
}
|
||||
if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client key");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
ssl = SSL_new(ssl_ctx);
|
||||
if (!ssl) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
|
||||
goto error;
|
||||
}
|
||||
if (servername) {
|
||||
if (!SSL_set_tlsext_host_name(ssl, servername)) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
return redisSSLConnect(c, ssl_ctx, ssl);
|
||||
|
||||
error:
|
||||
if (ssl) SSL_free(ssl);
|
||||
if (ssl_ctx) SSL_CTX_free(ssl_ctx);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
static int maybeCheckWant(redisSSLContext *rssl, int rv) {
|
||||
/**
|
||||
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
|
||||
* and true is returned. False is returned otherwise
|
||||
*/
|
||||
if (rv == SSL_ERROR_WANT_READ) {
|
||||
rssl->wantRead = 1;
|
||||
return 1;
|
||||
} else if (rv == SSL_ERROR_WANT_WRITE) {
|
||||
rssl->pendingWrite = 1;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of redisContextFuncs for SSL connections.
|
||||
*/
|
||||
|
||||
static void redisSSLFreeContext(void *privdata){
|
||||
redisSSLContext *rsc = privdata;
|
||||
|
||||
if (!rsc) return;
|
||||
if (rsc->ssl) {
|
||||
SSL_free(rsc->ssl);
|
||||
rsc->ssl = NULL;
|
||||
}
|
||||
if (rsc->ssl_ctx) {
|
||||
SSL_CTX_free(rsc->ssl_ctx);
|
||||
rsc->ssl_ctx = NULL;
|
||||
}
|
||||
free(rsc);
|
||||
}
|
||||
|
||||
static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
|
||||
redisSSLContext *rssl = c->privdata;
|
||||
|
||||
int nread = SSL_read(rssl->ssl, buf, bufcap);
|
||||
if (nread > 0) {
|
||||
return nread;
|
||||
} else if (nread == 0) {
|
||||
__redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
|
||||
return -1;
|
||||
} else {
|
||||
int err = SSL_get_error(rssl->ssl, nread);
|
||||
if (c->flags & REDIS_BLOCK) {
|
||||
/**
|
||||
* In blocking mode, we should never end up in a situation where
|
||||
* we get an error without it being an actual error, except
|
||||
* in the case of EINTR, which can be spuriously received from
|
||||
* debuggers or whatever.
|
||||
*/
|
||||
if (errno == EINTR) {
|
||||
return 0;
|
||||
} else {
|
||||
const char *msg = NULL;
|
||||
if (errno == EAGAIN) {
|
||||
msg = "Resource temporarily unavailable";
|
||||
}
|
||||
__redisSetError(c, REDIS_ERR_IO, msg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We can very well get an EWOULDBLOCK/EAGAIN, however
|
||||
*/
|
||||
if (maybeCheckWant(rssl, err)) {
|
||||
return 0;
|
||||
} else {
|
||||
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int redisSSLWrite(redisContext *c) {
|
||||
redisSSLContext *rssl = c->privdata;
|
||||
|
||||
size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
|
||||
int rv = SSL_write(rssl->ssl, c->obuf, len);
|
||||
|
||||
if (rv > 0) {
|
||||
rssl->lastLen = 0;
|
||||
} else if (rv < 0) {
|
||||
rssl->lastLen = len;
|
||||
|
||||
int err = SSL_get_error(rssl->ssl, rv);
|
||||
if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
|
||||
return 0;
|
||||
} else {
|
||||
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void redisSSLAsyncRead(redisAsyncContext *ac) {
|
||||
int rv;
|
||||
redisSSLContext *rssl = ac->c.privdata;
|
||||
redisContext *c = &ac->c;
|
||||
|
||||
rssl->wantRead = 0;
|
||||
|
||||
if (rssl->pendingWrite) {
|
||||
int done;
|
||||
|
||||
/* This is probably just a write event */
|
||||
rssl->pendingWrite = 0;
|
||||
rv = redisBufferWrite(c, &done);
|
||||
if (rv == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
return;
|
||||
} else if (!done) {
|
||||
_EL_ADD_WRITE(ac);
|
||||
}
|
||||
}
|
||||
|
||||
rv = redisBufferRead(c);
|
||||
if (rv == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
_EL_ADD_READ(ac);
|
||||
redisProcessCallbacks(ac);
|
||||
}
|
||||
}
|
||||
|
||||
static void redisSSLAsyncWrite(redisAsyncContext *ac) {
|
||||
int rv, done = 0;
|
||||
redisSSLContext *rssl = ac->c.privdata;
|
||||
redisContext *c = &ac->c;
|
||||
|
||||
rssl->pendingWrite = 0;
|
||||
rv = redisBufferWrite(c, &done);
|
||||
if (rv == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
if (rssl->wantRead) {
|
||||
/* Need to read-before-write */
|
||||
rssl->pendingWrite = 1;
|
||||
_EL_DEL_WRITE(ac);
|
||||
} else {
|
||||
/* No extra reads needed, just need to write more */
|
||||
_EL_ADD_WRITE(ac);
|
||||
}
|
||||
} else {
|
||||
/* Already done! */
|
||||
_EL_DEL_WRITE(ac);
|
||||
}
|
||||
|
||||
/* Always reschedule a read */
|
||||
_EL_ADD_READ(ac);
|
||||
}
|
||||
|
||||
redisContextFuncs redisContextSSLFuncs = {
|
||||
.free_privdata = redisSSLFreeContext,
|
||||
.async_read = redisSSLAsyncRead,
|
||||
.async_write = redisSSLAsyncWrite,
|
||||
.read = redisSSLRead,
|
||||
.write = redisSSLWrite
|
||||
};
|
||||
|
||||
Vendored
+83
-10
@@ -13,12 +13,16 @@
|
||||
#include <limits.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
#include "hiredis_ssl.h"
|
||||
#endif
|
||||
#include "net.h"
|
||||
|
||||
enum connection_type {
|
||||
CONN_TCP,
|
||||
CONN_UNIX,
|
||||
CONN_FD
|
||||
CONN_FD,
|
||||
CONN_SSL
|
||||
};
|
||||
|
||||
struct config {
|
||||
@@ -33,6 +37,14 @@ struct config {
|
||||
struct {
|
||||
const char *path;
|
||||
} unix_sock;
|
||||
|
||||
struct {
|
||||
const char *host;
|
||||
int port;
|
||||
const char *ca_cert;
|
||||
const char *cert;
|
||||
const char *key;
|
||||
} ssl;
|
||||
};
|
||||
|
||||
/* The following lines make up our testing "framework" :) */
|
||||
@@ -93,11 +105,27 @@ static int disconnect(redisContext *c, int keep_fd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void do_ssl_handshake(redisContext *c, struct config config) {
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
redisSecureConnection(c, config.ssl.ca_cert, config.ssl.cert, config.ssl.key, NULL);
|
||||
if (c->err) {
|
||||
printf("SSL error: %s\n", c->errstr);
|
||||
redisFree(c);
|
||||
exit(1);
|
||||
}
|
||||
#else
|
||||
(void) c;
|
||||
(void) config;
|
||||
#endif
|
||||
}
|
||||
|
||||
static redisContext *do_connect(struct config config) {
|
||||
redisContext *c = NULL;
|
||||
|
||||
if (config.type == CONN_TCP) {
|
||||
c = redisConnect(config.tcp.host, config.tcp.port);
|
||||
} else if (config.type == CONN_SSL) {
|
||||
c = redisConnect(config.ssl.host, config.ssl.port);
|
||||
} else if (config.type == CONN_UNIX) {
|
||||
c = redisConnectUnix(config.unix_sock.path);
|
||||
} else if (config.type == CONN_FD) {
|
||||
@@ -121,9 +149,21 @@ static redisContext *do_connect(struct config config) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (config.type == CONN_SSL) {
|
||||
do_ssl_handshake(c, config);
|
||||
}
|
||||
|
||||
return select_database(c);
|
||||
}
|
||||
|
||||
static void do_reconnect(redisContext *c, struct config config) {
|
||||
redisReconnect(c);
|
||||
|
||||
if (config.type == CONN_SSL) {
|
||||
do_ssl_handshake(c, config);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_format_commands(void) {
|
||||
char *cmd;
|
||||
int len;
|
||||
@@ -360,7 +400,8 @@ static void test_reply_reader(void) {
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when array > INT_MAX: ");
|
||||
#if LLONG_MAX > SIZE_MAX
|
||||
test("Set error when array > SIZE_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
@@ -369,7 +410,6 @@ static void test_reply_reader(void) {
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
#if LLONG_MAX > SIZE_MAX
|
||||
test("Set error when bulk > SIZE_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
|
||||
@@ -434,22 +474,23 @@ static void test_free_null(void) {
|
||||
test_cond(reply == NULL);
|
||||
}
|
||||
|
||||
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
|
||||
static void test_blocking_connection_errors(void) {
|
||||
redisContext *c;
|
||||
struct addrinfo hints = {.ai_family = AF_INET};
|
||||
struct addrinfo *ai_tmp = NULL;
|
||||
const char *bad_domain = "idontexist.com";
|
||||
|
||||
int rv = getaddrinfo(bad_domain, "6379", &hints, &ai_tmp);
|
||||
int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
|
||||
if (rv != 0) {
|
||||
// Address does *not* exist
|
||||
test("Returns error when host cannot be resolved: ");
|
||||
// First see if this domain name *actually* resolves to NXDOMAIN
|
||||
c = redisConnect("dontexist.com", 6379);
|
||||
c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);
|
||||
test_cond(
|
||||
c->err == REDIS_ERR_OTHER &&
|
||||
(strcmp(c->errstr, "Name or service not known") == 0 ||
|
||||
strcmp(c->errstr, "Can't resolve: sadkfjaskfjsa.com") == 0 ||
|
||||
strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
|
||||
strcmp(c->errstr, "Name does not resolve") == 0 ||
|
||||
strcmp(c->errstr,
|
||||
"nodename nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr, "No address associated with hostname") == 0 ||
|
||||
@@ -574,7 +615,8 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
|
||||
c = do_connect(config);
|
||||
test("Does not return a reply when the command times out: ");
|
||||
s = write(c->fd, cmd, strlen(cmd));
|
||||
redisAppendFormattedCommand(c, cmd, strlen(cmd));
|
||||
s = c->funcs->write(c);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 10000;
|
||||
redisSetTimeout(c, tv);
|
||||
@@ -583,7 +625,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("Reconnect properly reconnects after a timeout: ");
|
||||
redisReconnect(c);
|
||||
do_reconnect(c, config);
|
||||
reply = redisCommand(c, "PING");
|
||||
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
||||
freeReplyObject(reply);
|
||||
@@ -591,7 +633,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
test("Reconnect properly uses owned parameters: ");
|
||||
config.tcp.host = "foo";
|
||||
config.unix_sock.path = "foo";
|
||||
redisReconnect(c);
|
||||
do_reconnect(c, config);
|
||||
reply = redisCommand(c, "PING");
|
||||
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
||||
freeReplyObject(reply);
|
||||
@@ -894,6 +936,23 @@ int main(int argc, char **argv) {
|
||||
throughput = 0;
|
||||
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
|
||||
test_inherit_fd = 0;
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
|
||||
argv++; argc--;
|
||||
cfg.ssl.port = atoi(argv[0]);
|
||||
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) {
|
||||
argv++; argc--;
|
||||
cfg.ssl.host = argv[0];
|
||||
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) {
|
||||
argv++; argc--;
|
||||
cfg.ssl.ca_cert = argv[0];
|
||||
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) {
|
||||
argv++; argc--;
|
||||
cfg.ssl.cert = argv[0];
|
||||
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) {
|
||||
argv++; argc--;
|
||||
cfg.ssl.key = argv[0];
|
||||
#endif
|
||||
} else {
|
||||
fprintf(stderr, "Invalid argument: %s\n", argv[0]);
|
||||
exit(1);
|
||||
@@ -922,6 +981,20 @@ int main(int argc, char **argv) {
|
||||
test_blocking_io_errors(cfg);
|
||||
if (throughput) test_throughput(cfg);
|
||||
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
if (cfg.ssl.port && cfg.ssl.host) {
|
||||
printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
|
||||
cfg.type = CONN_SSL;
|
||||
|
||||
test_blocking_connection(cfg);
|
||||
test_blocking_connection_timeouts(cfg);
|
||||
test_blocking_io_errors(cfg);
|
||||
test_invalid_timeout_errors(cfg);
|
||||
test_append_formatted_commands(cfg);
|
||||
if (throughput) test_throughput(cfg);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (test_inherit_fd) {
|
||||
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
|
||||
cfg.type = CONN_FD;
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#!/bin/sh -ue
|
||||
|
||||
REDIS_SERVER=${REDIS_SERVER:-redis-server}
|
||||
REDIS_PORT=${REDIS_PORT:-56379}
|
||||
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
|
||||
TEST_SSL=${TEST_SSL:-0}
|
||||
SSL_TEST_ARGS=
|
||||
|
||||
tmpdir=$(mktemp -d)
|
||||
PID_FILE=${tmpdir}/hiredis-test-redis.pid
|
||||
SOCK_FILE=${tmpdir}/hiredis-test-redis.sock
|
||||
|
||||
if [ "$TEST_SSL" = "1" ]; then
|
||||
SSL_CA_CERT=${tmpdir}/ca.crt
|
||||
SSL_CA_KEY=${tmpdir}/ca.key
|
||||
SSL_CERT=${tmpdir}/redis.crt
|
||||
SSL_KEY=${tmpdir}/redis.key
|
||||
|
||||
openssl genrsa -out ${tmpdir}/ca.key 4096
|
||||
openssl req \
|
||||
-x509 -new -nodes -sha256 \
|
||||
-key ${SSL_CA_KEY} \
|
||||
-days 3650 \
|
||||
-subj '/CN=Hiredis Test CA' \
|
||||
-out ${SSL_CA_CERT}
|
||||
openssl genrsa -out ${SSL_KEY} 2048
|
||||
openssl req \
|
||||
-new -sha256 \
|
||||
-key ${SSL_KEY} \
|
||||
-subj '/CN=Hiredis Test Cert' | \
|
||||
openssl x509 \
|
||||
-req -sha256 \
|
||||
-CA ${SSL_CA_CERT} \
|
||||
-CAkey ${SSL_CA_KEY} \
|
||||
-CAserial ${tmpdir}/ca.txt \
|
||||
-CAcreateserial \
|
||||
-days 365 \
|
||||
-out ${SSL_CERT}
|
||||
|
||||
SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}"
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
set +e
|
||||
kill $(cat ${PID_FILE})
|
||||
rm -rf ${tmpdir}
|
||||
}
|
||||
trap cleanup INT TERM EXIT
|
||||
|
||||
cat > ${tmpdir}/redis.conf <<EOF
|
||||
daemonize yes
|
||||
pidfile ${PID_FILE}
|
||||
port ${REDIS_PORT}
|
||||
bind 127.0.0.1
|
||||
unixsocket ${SOCK_FILE}
|
||||
EOF
|
||||
|
||||
if [ "$TEST_SSL" = "1" ]; then
|
||||
cat >> ${tmpdir}/redis.conf <<EOF
|
||||
tls-port ${REDIS_SSL_PORT}
|
||||
tls-ca-cert-file ${SSL_CA_CERT}
|
||||
tls-cert-file ${SSL_CERT}
|
||||
tls-key-file ${SSL_KEY}
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat ${tmpdir}/redis.conf
|
||||
${REDIS_SERVER} ${tmpdir}/redis.conf
|
||||
|
||||
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS}
|
||||
Vendored
+16
-2
@@ -2,10 +2,20 @@
|
||||
#define _WIN32_HELPER_INCLUDE
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#include <winsock2.h> /* for struct timeval */
|
||||
|
||||
#ifndef inline
|
||||
#define inline __inline
|
||||
#endif
|
||||
|
||||
#ifndef strcasecmp
|
||||
#define strcasecmp stricmp
|
||||
#endif
|
||||
|
||||
#ifndef strncasecmp
|
||||
#define strncasecmp strnicmp
|
||||
#endif
|
||||
|
||||
#ifndef va_copy
|
||||
#define va_copy(d,s) ((d) = (s))
|
||||
#endif
|
||||
@@ -37,6 +47,10 @@ __inline int c99_snprintf(char* str, size_t size, const char* format, ...)
|
||||
return count;
|
||||
}
|
||||
#endif
|
||||
#endif /* _MSC_VER */
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#endif /* _WIN32_HELPER_INCLUDE */
|
||||
@@ -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];
|
||||
int 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);
|
||||
}
|
||||
|
||||
+7
-1
@@ -787,7 +787,13 @@ background_thread_stats_read(tsdn_t *tsdn, background_thread_stats_t *stats) {
|
||||
nstime_init(&stats->run_interval, 0);
|
||||
for (unsigned i = 0; i < max_background_threads; i++) {
|
||||
background_thread_info_t *info = &background_thread_info[i];
|
||||
malloc_mutex_lock(tsdn, &info->mtx);
|
||||
if (malloc_mutex_trylock(tsdn, &info->mtx)) {
|
||||
/*
|
||||
* Each background thread run may take a long time;
|
||||
* avoid waiting on the stats if the thread is active.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
if (info->state != background_thread_stopped) {
|
||||
num_runs += info->tot_n_runs;
|
||||
nstime_add(&stats->run_interval, &info->tot_sleep_time);
|
||||
|
||||
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
+29
-3
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
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
+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;
|
||||
|
||||
+324
-55
@@ -129,6 +129,76 @@ timeout 0
|
||||
# KeyDB default starting with Redis 3.2.1.
|
||||
tcp-keepalive 300
|
||||
|
||||
################################# TLS/SSL #####################################
|
||||
|
||||
# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration
|
||||
# directive can be used to define TLS-listening ports. To enable TLS on the
|
||||
# default port, use:
|
||||
#
|
||||
# port 0
|
||||
# tls-port 6379
|
||||
|
||||
# Configure a X.509 certificate and private key to use for authenticating the
|
||||
# server to connected clients, masters or cluster peers. These files should be
|
||||
# PEM formatted.
|
||||
#
|
||||
# tls-cert-file redis.crt
|
||||
# tls-key-file redis.key
|
||||
|
||||
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
|
||||
#
|
||||
# tls-dh-params-file redis.dh
|
||||
|
||||
# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL
|
||||
# clients and peers. Redis requires an explicit configuration of at least one
|
||||
# of these, and will not implicitly use the system wide configuration.
|
||||
#
|
||||
# tls-ca-cert-file ca.crt
|
||||
# tls-ca-cert-dir /etc/ssl/certs
|
||||
|
||||
# By default, clients (including replica servers) on a TLS port are required
|
||||
# to authenticate using valid client side certificates.
|
||||
#
|
||||
# It is possible to disable authentication using this directive.
|
||||
#
|
||||
# tls-auth-clients no
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
# any combination. To enable only TLSv1.2 and TLSv1.3, use:
|
||||
#
|
||||
# tls-protocols "TLSv1.2 TLSv1.3"
|
||||
|
||||
# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information
|
||||
# about the syntax of this string.
|
||||
#
|
||||
# Note: this configuration applies only to <= TLSv1.2.
|
||||
#
|
||||
# tls-ciphers DEFAULT:!MEDIUM
|
||||
|
||||
# Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more
|
||||
# information about the syntax of this string, and specifically for TLSv1.3
|
||||
# ciphersuites.
|
||||
#
|
||||
# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256
|
||||
|
||||
# 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-ciphers yes
|
||||
|
||||
################################# GENERAL #####################################
|
||||
|
||||
# By default KeyDB does not run as a daemon. Use 'yes' if you need it.
|
||||
@@ -252,6 +322,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
|
||||
@@ -294,9 +377,9 @@ dir ./
|
||||
#
|
||||
# However this is not enough if you are using KeyDB ACLs (for Redis version
|
||||
# 6 or greater), and the default user is not capable of running the PSYNC
|
||||
# command and/or other commands needed for replication. In this case it's
|
||||
# better to configure a special user to use with replication, and specify the
|
||||
# masteruser configuration as such:
|
||||
# command and/or other commands needed for replication (gathered in the
|
||||
# @replication group). In this case it's better to configure a special user to
|
||||
# use with replication, and specify the masteruser configuration as such:
|
||||
#
|
||||
# masteruser <username>
|
||||
#
|
||||
@@ -336,13 +419,11 @@ replica-read-only yes
|
||||
|
||||
# Replication SYNC strategy: disk or socket.
|
||||
#
|
||||
# -------------------------------------------------------
|
||||
# WARNING: DISKLESS REPLICATION IS EXPERIMENTAL CURRENTLY
|
||||
# -------------------------------------------------------
|
||||
# New replicas and reconnecting replicas that are not able to continue the
|
||||
# replication process just receiving differences, need to do what is called a
|
||||
# "full synchronization". An RDB file is transmitted from the master to the
|
||||
# replicas.
|
||||
#
|
||||
# New replicas and reconnecting replicas that are not able to continue the replication
|
||||
# process just receiving differences, need to do what is called a "full
|
||||
# synchronization". An RDB file is transmitted from the master to the replicas.
|
||||
# The transmission can happen in two different ways:
|
||||
#
|
||||
# 1) Disk-backed: The KeyDB master creates a new process that writes the RDB
|
||||
@@ -352,14 +433,14 @@ replica-read-only yes
|
||||
# RDB file to replica sockets, without touching the disk at all.
|
||||
#
|
||||
# With disk-backed replication, while the RDB file is generated, more replicas
|
||||
# can be queued and served with the RDB file as soon as the current child producing
|
||||
# the RDB file finishes its work. With diskless replication instead once
|
||||
# the transfer starts, new replicas arriving will be queued and a new transfer
|
||||
# will start when the current one terminates.
|
||||
# can be queued and served with the RDB file as soon as the current child
|
||||
# producing the RDB file finishes its work. With diskless replication instead
|
||||
# once the transfer starts, new replicas arriving will be queued and a new
|
||||
# transfer will start when the current one terminates.
|
||||
#
|
||||
# When diskless replication is used, the master waits a configurable amount of
|
||||
# time (in seconds) before starting the transfer in the hope that multiple replicas
|
||||
# will arrive and the transfer can be parallelized.
|
||||
# time (in seconds) before starting the transfer in the hope that multiple
|
||||
# replicas will arrive and the transfer can be parallelized.
|
||||
#
|
||||
# With slow disks and fast (large bandwidth) networks, diskless replication
|
||||
# works better.
|
||||
@@ -370,16 +451,42 @@ repl-diskless-sync no
|
||||
# to the replicas.
|
||||
#
|
||||
# This is important since once the transfer starts, it is not possible to serve
|
||||
# new replicas arriving, that will be queued for the next RDB transfer, so the server
|
||||
# waits a delay in order to let more replicas arrive.
|
||||
# new replicas arriving, that will be queued for the next RDB transfer, so the
|
||||
# server waits a delay in order to let more replicas arrive.
|
||||
#
|
||||
# The delay is specified in seconds, and by default is 5 seconds. To disable
|
||||
# it entirely just set it to 0 seconds and the transfer will start ASAP.
|
||||
repl-diskless-sync-delay 5
|
||||
|
||||
# Replicas send PINGs to server in a predefined interval. It's possible to change
|
||||
# this interval with the repl_ping_replica_period option. The default value is 10
|
||||
# seconds.
|
||||
# -----------------------------------------------------------------------------
|
||||
# WARNING: RDB diskless load is experimental. Since in this setup the replica
|
||||
# does not immediately store an RDB on disk, it may cause data loss during
|
||||
# failovers. RDB diskless load + Redis modules not handling I/O reads may also
|
||||
# cause Redis to abort in case of I/O errors during the initial synchronization
|
||||
# stage with the master. Use only if your do what you are doing.
|
||||
# -----------------------------------------------------------------------------
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 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
|
||||
# Copy on Write memory and salve buffers).
|
||||
# However, parsing the RDB file directly from the socket may mean that we have
|
||||
# to flush the contents of the current database before the full rdb was
|
||||
# received. For this reason we have the following options:
|
||||
#
|
||||
# "disabled" - Don't use diskless load (store the rdb file to the disk first)
|
||||
# "on-empty-db" - Use diskless load only when it is completely safe.
|
||||
# "swapdb" - Keep a copy of the current db contents in RAM while parsing
|
||||
# the data directly from the socket. note that this requires
|
||||
# sufficient memory, if you don't have it, you risk an OOM kill.
|
||||
repl-diskless-load disabled
|
||||
|
||||
# Replicas send PINGs to server in a predefined interval. It's possible to
|
||||
# change this interval with the repl_ping_replica_period option. The default
|
||||
# value is 10 seconds.
|
||||
#
|
||||
# repl-ping-replica-period 10
|
||||
|
||||
@@ -411,10 +518,10 @@ repl-diskless-sync-delay 5
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# Set the replication backlog size. The backlog is a buffer that accumulates
|
||||
# replica data when replicas are disconnected for some time, so that when a replica
|
||||
# wants to reconnect again, often a full resync is not needed, but a partial
|
||||
# resync is enough, just passing the portion of data the replica missed while
|
||||
# disconnected.
|
||||
# replica data when replicas are disconnected for some time, so that when a
|
||||
# replica wants to reconnect again, often a full resync is not needed, but a
|
||||
# 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.
|
||||
@@ -436,13 +543,13 @@ repl-disable-tcp-nodelay no
|
||||
#
|
||||
# repl-backlog-ttl 3600
|
||||
|
||||
# The replica priority is an integer number published by KeyDB in the INFO output.
|
||||
# It is used by KeyDB Sentinel in order to select a replica to promote into a
|
||||
# master if the master is no longer working correctly.
|
||||
# The replica priority is an integer number published by KeyDB in the INFO
|
||||
# output. It is used by Redis Sentinel in order to select a replica to promote
|
||||
# into a master if the master is no longer working correctly.
|
||||
#
|
||||
# A replica with a low priority number is considered better for promotion, so
|
||||
# for instance if there are three replicas with priority 10, 100, 25 Sentinel will
|
||||
# pick the one with priority 10, that is the lowest.
|
||||
# for instance if there are three replicas with priority 10, 100, 25 Sentinel
|
||||
# will pick the one with priority 10, that is the lowest.
|
||||
#
|
||||
# However a special priority of 0 marks the replica as not able to perform the
|
||||
# role of master, so a replica with priority of 0 will never be selected by
|
||||
@@ -502,6 +609,42 @@ replica-priority 100
|
||||
# replica-announce-ip 5.5.5.5
|
||||
# replica-announce-port 1234
|
||||
|
||||
############################### KEYS TRACKING #################################
|
||||
|
||||
# Redis implements server assisted support for client side caching of values.
|
||||
# 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:
|
||||
#
|
||||
# https://redis.io/topics/client-side-caching
|
||||
#
|
||||
# When tracking is enabled for a client, all the read only queries are assumed
|
||||
# to be cached: this will force Redis to store information in the invalidation
|
||||
# table. When keys are modified, such information is flushed away, and
|
||||
# invalidation messages are sent to the clients. However if the workload is
|
||||
# heavily dominated by reads, Redis could use more and more memory in order
|
||||
# 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 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 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 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.
|
||||
#
|
||||
# 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 KeyDB is pretty fast an outside user can try up to
|
||||
@@ -598,6 +741,15 @@ 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 and consumes memory. There is no limit
|
||||
# to its length.You can reclaim memory with ACL LOG RESET or set a maximum
|
||||
# length below.
|
||||
acllog-max-len 128
|
||||
|
||||
# Using an external ACL file
|
||||
#
|
||||
# Instead of configuring users here in this file, it is possible to use
|
||||
@@ -684,13 +836,13 @@ replica-priority 100
|
||||
# maxmemory <bytes>
|
||||
|
||||
# MAXMEMORY POLICY: how KeyDB will select what to remove when maxmemory
|
||||
# is reached. You can select among five behaviors:
|
||||
# is reached. You can select one from the following behaviors:
|
||||
#
|
||||
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
|
||||
# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
|
||||
# allkeys-lru -> Evict any key using approximated LRU.
|
||||
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
|
||||
# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
|
||||
# allkeys-lfu -> Evict any key using approximated LFU.
|
||||
# volatile-random -> Remove a random key among the ones with an expire set.
|
||||
# volatile-random -> Remove a random key having an expire set.
|
||||
# allkeys-random -> Remove a random key, any key.
|
||||
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
|
||||
# noeviction -> Don't evict anything, just return an error on write operations.
|
||||
@@ -731,20 +883,37 @@ replica-priority 100
|
||||
# DEL commands to the replica as keys evict in the master side.
|
||||
#
|
||||
# This behavior ensures that masters and replicas stay consistent, and is usually
|
||||
# what you want, however if your replica is writable, or you want the replica to have
|
||||
# a different memory setting, and you are sure all the writes performed to the
|
||||
# replica are idempotent, then you may change this default (but be sure to understand
|
||||
# what you are doing).
|
||||
# what you want, however if your replica is writable, or you want the replica
|
||||
# to have a different memory setting, and you are sure all the writes performed
|
||||
# to the replica are idempotent, then you may change this default (but be sure
|
||||
# to understand what you are doing).
|
||||
#
|
||||
# Note that since the replica by default does not evict, it may end using more
|
||||
# memory than the one set via maxmemory (there are certain buffers that may
|
||||
# be larger on the replica, or data structures may sometimes take more memory and so
|
||||
# forth). So make sure you monitor your replicas and make sure they have enough
|
||||
# memory to never hit a real out-of-memory condition before the master hits
|
||||
# the configured maxmemory setting.
|
||||
# be larger on the replica, or data structures may sometimes take more memory
|
||||
# and so forth). So make sure you monitor your replicas and make sure they
|
||||
# have enough memory to never hit a real out-of-memory condition before the
|
||||
# master hits the configured maxmemory setting.
|
||||
#
|
||||
# replica-ignore-maxmemory yes
|
||||
|
||||
# Redis reclaims expired keys in two ways: upon access when those keys are
|
||||
# found to be expired, and also in background, in what is called the
|
||||
# "active expire key". The key space is slowly and interactively scanned
|
||||
# looking for expired keys to reclaim, so that it is possible to free memory
|
||||
# of keys that are expired and will never be accessed again in a short time.
|
||||
#
|
||||
# The default effort of the expire cycle will try to avoid having more than
|
||||
# ten percent of expired keys still in memory, and will try to avoid consuming
|
||||
# more than 25% of total memory and to add latency to the system. However
|
||||
# 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.
|
||||
#
|
||||
# active-expire-effort 1
|
||||
|
||||
############################# LAZY FREEING ####################################
|
||||
|
||||
# KeyDB has two primitives to delete keys. One is called DEL and is a blocking
|
||||
@@ -787,13 +956,66 @@ 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 speedup 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 usually.
|
||||
# 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 theads, otherwise you'll not
|
||||
# be able to notice the improvements.
|
||||
|
||||
############################## APPEND ONLY MODE ###############################
|
||||
|
||||
# By default KeyDB asynchronously dumps the dataset on disk. This mode is
|
||||
@@ -1050,6 +1272,22 @@ lua-time-limit 5000
|
||||
#
|
||||
# cluster-replica-no-failover no
|
||||
|
||||
# This option, when set to yes, allows nodes to serve read traffic while the
|
||||
# the cluster is in a down state, as long as it believes it owns the slots.
|
||||
#
|
||||
# This is useful for two cases. The first case is for when an application
|
||||
# doesn't require consistency of data during node failures or network partitions.
|
||||
# One example of this is a cache, where as long as the node has the data it
|
||||
# should be able to serve it.
|
||||
#
|
||||
# The second use case is for configurations that don't meet the recommended
|
||||
# three shards but want to enable cluster mode and scale later. A
|
||||
# master outage in a 1 or 2 shard configuration causes a read/write outage to the
|
||||
# entire cluster without this option set, with it set there is only a write outage.
|
||||
# Without a quorum of masters, slot ownership will not change automatically.
|
||||
#
|
||||
# cluster-allow-reads-when-down no
|
||||
|
||||
# In order to setup your cluster make sure to read the documentation
|
||||
# available at http://redis.io web site.
|
||||
|
||||
@@ -1156,7 +1394,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
|
||||
@@ -1182,7 +1424,7 @@ notify-keyspace-events ""
|
||||
# KeyDB contains an implementation of the Gopher protocol, as specified in
|
||||
# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt).
|
||||
#
|
||||
# The Gopher protocol was very popular in the late '90s. It is an alternative
|
||||
# The Gopher protocol was very popular in the late '90s. It is an alternative
|
||||
# to the web, and the implementation both server and client side is so simple
|
||||
# that the KeyDB server has just 100 lines of code in order to implement this
|
||||
# support.
|
||||
@@ -1220,7 +1462,7 @@ notify-keyspace-events ""
|
||||
# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance.
|
||||
# Once a password is set:
|
||||
#
|
||||
# 1. The Gopher server (when enabled, not by default) will kill serve
|
||||
# 1. The Gopher server (when enabled, not by default) will still serve
|
||||
# content via Gopher.
|
||||
# 2. However other commands cannot be called before the client will
|
||||
# authenticate.
|
||||
@@ -1406,7 +1648,7 @@ hz 10
|
||||
# offers, and enables by default, the ability to use an adaptive HZ value
|
||||
# which will temporary 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
|
||||
@@ -1479,10 +1721,6 @@ rdb-save-incremental-fsync yes
|
||||
|
||||
########################### ACTIVE DEFRAGMENTATION #######################
|
||||
#
|
||||
# WARNING THIS FEATURE IS EXPERIMENTAL. However it was stress tested
|
||||
# even in production and manually tested by multiple engineers for some
|
||||
# time.
|
||||
#
|
||||
# What is active defragmentation?
|
||||
# -------------------------------
|
||||
#
|
||||
@@ -1522,7 +1760,7 @@ rdb-save-incremental-fsync yes
|
||||
# a good idea to leave the defaults untouched.
|
||||
|
||||
# Enabled active defragmentation
|
||||
# activedefrag yes
|
||||
# activedefrag no
|
||||
|
||||
# Minimum amount of fragmentation waste to start active defrag
|
||||
# active-defrag-ignore-bytes 100mb
|
||||
@@ -1533,16 +1771,47 @@ rdb-save-incremental-fsync yes
|
||||
# Maximum percentage of fragmentation at which we use maximum effort
|
||||
# active-defrag-threshold-upper 100
|
||||
|
||||
# Minimal effort for defrag in CPU percentage
|
||||
# active-defrag-cycle-min 5
|
||||
# Minimal effort for defrag in CPU percentage, to be used when the lower
|
||||
# threshold is reached
|
||||
# active-defrag-cycle-min 1
|
||||
|
||||
# Maximal effort for defrag in CPU percentage
|
||||
# active-defrag-cycle-max 75
|
||||
# Maximal effort for defrag in CPU percentage, to be used when the upper
|
||||
# threshold is reached
|
||||
# active-defrag-cycle-max 25
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
# Path to directory for file backed scratchpad. The file backed scratchpad
|
||||
# reduces memory requirements by storing rarely accessed data on disk
|
||||
# instead of RAM. A temporary file will be created in this directory.
|
||||
|
||||
+13
-1
@@ -13,4 +13,16 @@ then
|
||||
fi
|
||||
|
||||
make -C tests/modules && \
|
||||
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter "${@}"
|
||||
$TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/commandfilter \
|
||||
--single unit/moduleapi/fork \
|
||||
--single unit/moduleapi/testrdb \
|
||||
--single unit/moduleapi/infotest \
|
||||
--single unit/moduleapi/propagate \
|
||||
--single unit/moduleapi/hooks \
|
||||
--single unit/moduleapi/misc \
|
||||
--single unit/moduleapi/blockonkeys \
|
||||
--single unit/moduleapi/scan \
|
||||
--single unit/moduleapi/datatype \
|
||||
--single unit/moduleapi/auth \
|
||||
"${@}"
|
||||
@@ -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
|
||||
|
||||
+58
-9
@@ -33,6 +33,7 @@ OPT=$(OPTIMIZATION)
|
||||
PREFIX?=/usr/local
|
||||
INSTALL_BIN=$(PREFIX)/bin
|
||||
INSTALL=install
|
||||
PKG_CONFIG?=pkg-config
|
||||
|
||||
# Default allocator defaults to Jemalloc if it's not an ARM
|
||||
MALLOC=libc
|
||||
@@ -69,6 +70,7 @@ endif
|
||||
|
||||
ifeq ($(COMPILER_NAME),clang)
|
||||
CXXFLAGS+= -stdlib=libc++
|
||||
LDFLAGS+= -latomic
|
||||
endif
|
||||
|
||||
# To get ARM stack traces if Redis crashes we need a special C flag.
|
||||
@@ -105,9 +107,14 @@ endif
|
||||
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CXXFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LIBS=-lm -lcurl
|
||||
FINAL_LIBS+=-lm -lcurl
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
ifneq ($(uname_S),Darwin)
|
||||
FINAL_LIBS+=-latomic
|
||||
endif
|
||||
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
ifneq ($(@@),32bit)
|
||||
@@ -126,6 +133,8 @@ else
|
||||
ifeq ($(uname_S),Darwin)
|
||||
# Darwin
|
||||
FINAL_LIBS+= -ldl
|
||||
OPENSSL_CFLAGS=-I/usr/local/opt/openssl/include
|
||||
OPENSSL_LDFLAGS=-L/usr/local/opt/openssl/lib
|
||||
else
|
||||
ifeq ($(uname_S),AIX)
|
||||
# AIX
|
||||
@@ -150,6 +159,14 @@ else
|
||||
ifeq ($(uname_S),DragonFly)
|
||||
# FreeBSD
|
||||
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
|
||||
# All the other OSes (notably Linux)
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
@@ -162,10 +179,36 @@ endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
|
||||
FINAL_CXXFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
|
||||
|
||||
# Determine systemd support and/or build preference (defaulting to auto-detection)
|
||||
BUILD_WITH_SYSTEMD=no
|
||||
# If 'USE_SYSTEMD' in the environment is neither "no" nor "yes", try to
|
||||
# auto-detect libsystemd's presence and link accordingly.
|
||||
ifneq ($(USE_SYSTEMD),no)
|
||||
LIBSYSTEMD_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libsystemd && echo $$?)
|
||||
# If libsystemd cannot be detected, continue building without support for it
|
||||
# (unless a later check tells us otherwise)
|
||||
ifeq ($(LIBSYSTEMD_PKGCONFIG),0)
|
||||
BUILD_WITH_SYSTEMD=yes
|
||||
endif
|
||||
endif
|
||||
ifeq ($(USE_SYSTEMD),yes)
|
||||
ifneq ($(LIBSYSTEMD_PKGCONFIG),0)
|
||||
$(error USE_SYSTEMD is set to "$(USE_SYSTEMD)", but $(PKG_CONFIG) cannot find libsystemd)
|
||||
endif
|
||||
# Force building with libsystemd
|
||||
BUILD_WITH_SYSTEMD=yes
|
||||
endif
|
||||
ifeq ($(BUILD_WITH_SYSTEMD),yes)
|
||||
FINAL_LIBS+=$(shell $(PKG_CONFIG) --libs libsystemd)
|
||||
FINAL_CFLAGS+= -DHAVE_LIBSYSTEMD
|
||||
endif
|
||||
|
||||
ifeq ($(MALLOC),tcmalloc)
|
||||
FINAL_CFLAGS+= -DUSE_TCMALLOC
|
||||
FINAL_CXXFLAGS+= -DUSE_TCMALLOC
|
||||
@@ -192,6 +235,13 @@ ifeq ($(MALLOC),memkind)
|
||||
FINAL_LIBS := ../deps/memkind/src/.libs/libmemkind.a -lnuma $(FINAL_LIBS)
|
||||
endif
|
||||
|
||||
ifeq ($(BUILD_TLS),yes)
|
||||
FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS)
|
||||
FINAL_CXXFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS)
|
||||
FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
|
||||
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a -lssl -lcrypto
|
||||
endif
|
||||
|
||||
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
|
||||
REDIS_CXX=$(QUIET_CC)$(CXX) $(FINAL_CXXFLAGS)
|
||||
KEYDB_AS=$(QUIET_CC) as --64 -g
|
||||
@@ -206,16 +256,16 @@ MAKECOLOR="\033[32;1m"
|
||||
ENDCOLOR="\033[0m"
|
||||
|
||||
ifndef V
|
||||
QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2;
|
||||
QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2;
|
||||
QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2;
|
||||
QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR);
|
||||
QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR);
|
||||
QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR);
|
||||
endif
|
||||
|
||||
REDIS_SERVER_NAME=keydb-server
|
||||
REDIS_SENTINEL_NAME=keydb-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 acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o motd.o $(ASM_OBJ)
|
||||
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 acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o motd.o timeout.o setcpuaffinity.o $(ASM_OBJ)
|
||||
REDIS_CLI_NAME=keydb-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ)
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crcspeed.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ)
|
||||
REDIS_BENCHMARK_NAME=keydb-benchmark
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o storage-lite.o fastlock.o new.o $(ASM_OBJ)
|
||||
REDIS_CHECK_RDB_NAME=keydb-check-rdb
|
||||
@@ -294,7 +344,6 @@ $(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
||||
dict-benchmark: dict.cpp zmalloc.cpp sds.c siphash.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)
|
||||
|
||||
@@ -302,10 +351,10 @@ DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ
|
||||
# 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) -MMD -c $<
|
||||
$(REDIS_CC) -MMD -o $@ -c $<
|
||||
|
||||
%.o: %.cpp .make-prerequisites
|
||||
$(REDIS_CXX) -MMD -c $<
|
||||
$(REDIS_CXX) -MMD -o $@ -c $<
|
||||
|
||||
%.o: %.asm .make-prerequisites
|
||||
$(KEYDB_AS) $< -o $@
|
||||
|
||||
+375
-42
@@ -28,7 +28,11 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
extern "C" {
|
||||
#include "sha256.h"
|
||||
}
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* =============================================================================
|
||||
* Global state for ACLs
|
||||
@@ -48,6 +52,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;
|
||||
@@ -74,6 +80,7 @@ struct ACLCategoryItem {
|
||||
{"connection", CMD_CATEGORY_CONNECTION},
|
||||
{"transaction", CMD_CATEGORY_TRANSACTION},
|
||||
{"scripting", CMD_CATEGORY_SCRIPTING},
|
||||
{"replication", CMD_CATEGORY_REPLICATION},
|
||||
{NULL,0} /* Terminator. */
|
||||
};
|
||||
|
||||
@@ -92,6 +99,10 @@ 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(struct ACLLogEntry *le);
|
||||
|
||||
/* The length of the string representation of a hashed password. */
|
||||
#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
|
||||
|
||||
/* =============================================================================
|
||||
* Helper functions for the rest of the ACL implementation
|
||||
@@ -139,10 +150,60 @@ int time_independent_strcmp(char *a, char *b) {
|
||||
return diff; /* If zero strings are the same. */
|
||||
}
|
||||
|
||||
/* Given an SDS string, returns the SHA256 hex representation as a
|
||||
* new SDS string. */
|
||||
sds ACLHashPassword(unsigned char *cleartext, size_t len) {
|
||||
SHA256_CTX ctx;
|
||||
unsigned char hash[SHA256_BLOCK_SIZE];
|
||||
char hex[HASH_PASSWORD_LEN];
|
||||
const char *cset = "0123456789abcdef";
|
||||
|
||||
sha256_init(&ctx);
|
||||
sha256_update(&ctx,(unsigned char*)cleartext,len);
|
||||
sha256_final(&ctx,hash);
|
||||
|
||||
for (int j = 0; j < SHA256_BLOCK_SIZE; j++) {
|
||||
hex[j*2] = cset[((hash[j]&0xF0)>>4)];
|
||||
hex[j*2+1] = cset[(hash[j]&0xF)];
|
||||
}
|
||||
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) {
|
||||
@@ -160,12 +221,12 @@ int ACLListMatchSds(void *a, void *b) {
|
||||
return sdscmp((sds)a,(sds)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(const void *item) {
|
||||
sdsfree((sds)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((sds)item);
|
||||
}
|
||||
@@ -317,12 +378,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
|
||||
@@ -502,7 +564,7 @@ sds ACLDescribeUser(user *u) {
|
||||
listRewind(u->passwords,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
sds thispass = (sds)listNodeValue(ln);
|
||||
res = sdscatlen(res,">",1);
|
||||
res = sdscatlen(res,"#",1);
|
||||
res = sdscatsds(res,thispass);
|
||||
res = sdscatlen(res," ",1);
|
||||
}
|
||||
@@ -629,7 +691,14 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
||||
* ><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).
|
||||
* #<hash> Add this password hash to the list of valid hashes for
|
||||
* the user. This is useful if you have previously computed
|
||||
* the hash, and don't want to store it in plaintext.
|
||||
* This directive clears the "nopass" flag (see later).
|
||||
* <<password> Remove this password from the list of valid passwords.
|
||||
* !<hash> Remove this hashed password from the list of valid passwords.
|
||||
* This is useful when you want to remove a password just by
|
||||
* hash without knowing its plaintext version at all.
|
||||
* nopass All the set passwords of the user are removed, and the user
|
||||
* is flagged as requiring no password: it means that every
|
||||
* password will work against this user. If this directive is
|
||||
@@ -657,7 +726,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
|
||||
@@ -665,6 +735,7 @@ 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.
|
||||
*/
|
||||
int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
if (oplen == -1) oplen = strlen(op);
|
||||
@@ -700,14 +771,36 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
} else if (!strcasecmp(op,"resetpass")) {
|
||||
u->flags &= ~USER_FLAG_NOPASS;
|
||||
listEmpty(u->passwords);
|
||||
} else if (op[0] == '>') {
|
||||
sds newpass = sdsnewlen(op+1,oplen-1);
|
||||
} else if (op[0] == '>' || op[0] == '#') {
|
||||
sds newpass;
|
||||
if (op[0] == '>') {
|
||||
newpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
|
||||
} else {
|
||||
if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR) {
|
||||
errno = EBADMSG;
|
||||
return C_ERR;
|
||||
}
|
||||
newpass = sdsnewlen(op+1,oplen-1);
|
||||
}
|
||||
|
||||
listNode *ln = listSearchKey(u->passwords,newpass);
|
||||
/* Avoid re-adding the same password multiple times. */
|
||||
if (ln == NULL) listAddNodeTail(u->passwords,newpass);
|
||||
if (ln == NULL)
|
||||
listAddNodeTail(u->passwords,newpass);
|
||||
else
|
||||
sdsfree(newpass);
|
||||
u->flags &= ~USER_FLAG_NOPASS;
|
||||
} else if (op[0] == '<') {
|
||||
sds delpass = sdsnewlen(op+1,oplen-1);
|
||||
} else if (op[0] == '<' || op[0] == '!') {
|
||||
sds delpass;
|
||||
if (op[0] == '<') {
|
||||
delpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
|
||||
} else {
|
||||
if (ACLCheckPasswordHash((unsigned char*)op+1,oplen-1) == C_ERR) {
|
||||
errno = EBADMSG;
|
||||
return C_ERR;
|
||||
}
|
||||
delpass = sdsnewlen(op+1,oplen-1);
|
||||
}
|
||||
listNode *ln = listSearchKey(u->passwords,delpass);
|
||||
sdsfree(delpass);
|
||||
if (ln) {
|
||||
@@ -721,10 +814,17 @@ 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. */
|
||||
if (ln == NULL) listAddNodeTail(u->patterns,newpat);
|
||||
if (ln == NULL)
|
||||
listAddNodeTail(u->patterns,newpat);
|
||||
else
|
||||
sdsfree(newpat);
|
||||
u->flags &= ~USER_FLAG_ALLKEYS;
|
||||
} else if (op[0] == '+' && op[1] != '@') {
|
||||
if (strchr(op,'|') == NULL) {
|
||||
@@ -749,7 +849,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. */
|
||||
@@ -762,6 +861,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;
|
||||
@@ -822,19 +922,12 @@ const char *ACLSetUserStringError(void) {
|
||||
else if (errno == ENODEV)
|
||||
errmsg = "The password you are trying to remove from the user does "
|
||||
"not exist";
|
||||
else if (errno == EBADMSG)
|
||||
errmsg = "The password hash must be exactly 64 characters and contain "
|
||||
"only lowercase hexadecimal characters";
|
||||
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 (sds)listNodeValue(first);
|
||||
}
|
||||
|
||||
/* Initialize the default user, that will always exist for all the process
|
||||
* lifetime. */
|
||||
void ACLInitDefaultUser(void) {
|
||||
@@ -849,7 +942,9 @@ void ACLInitDefaultUser(void) {
|
||||
void ACLInit(void) {
|
||||
Users = raxNew();
|
||||
UsersToLoad = listCreate();
|
||||
ACLLog = listCreate();
|
||||
ACLInitDefaultUser();
|
||||
g_pserver->requirepass = NULL; /* Only used for backward compatibility. */
|
||||
}
|
||||
|
||||
/* Check the username and password pair and return C_OK if they are valid,
|
||||
@@ -879,11 +974,15 @@ int ACLCheckUserCredentials(robj *username, robj *password) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(u->passwords,&li);
|
||||
sds hashed = ACLHashPassword((unsigned char*)szFromObj(password),sdslen(szFromObj(password)));
|
||||
while((ln = listNext(&li))) {
|
||||
sds thispass = (sds)listNodeValue(ln);
|
||||
if (!time_independent_strcmp(szFromObj(password), thispass))
|
||||
if (!time_independent_strcmp(hashed, thispass)) {
|
||||
sdsfree(hashed);
|
||||
return C_OK;
|
||||
}
|
||||
}
|
||||
sdsfree(hashed);
|
||||
|
||||
/* If we reached this point, no password matched. */
|
||||
errno = EINVAL;
|
||||
@@ -900,8 +999,10 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password) {
|
||||
if (ACLCheckUserCredentials(username,password) == C_OK) {
|
||||
c->authenticated = 1;
|
||||
c->puser = ACLGetUserByName((sds)ptrFromObj(username),sdslen((sds)ptrFromObj(username)));
|
||||
moduleNotifyUserChanged(c);
|
||||
return C_OK;
|
||||
} else {
|
||||
addACLLogEntry(c,ACL_DENIED_AUTH,0,szFromObj(username));
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
@@ -951,14 +1052,14 @@ user *ACLGetUserByName(const char *name, size_t namelen) {
|
||||
|
||||
/* Check if the command is ready to be executed in the client 'c', already
|
||||
* referenced by c->cmd, and can be executed by this client according to the
|
||||
* ACLs associated to the client user c->user.
|
||||
* ACLs associated to the client user c->puser.
|
||||
*
|
||||
* If the user can execute the command ACL_OK is returned, otherwise
|
||||
* ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the
|
||||
* 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->puser;
|
||||
uint64_t id = c->cmd->id;
|
||||
|
||||
@@ -1018,6 +1119,7 @@ int ACLCheckCommandPerm(client *c) {
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
if (keyidxptr) *keyidxptr = keyidx[j];
|
||||
getKeysFreeResult(keyidx);
|
||||
return ACL_DENIED_KEY;
|
||||
}
|
||||
@@ -1091,6 +1193,12 @@ int ACLLoadConfiguredUsers(void) {
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds *aclrules = (sds*)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));
|
||||
@@ -1216,6 +1324,14 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Spaces are not allowed in usernames. */
|
||||
if (ACLStringHasSpaces(argv[1],sdslen(argv[1]))) {
|
||||
errors = sdscatprintf(errors,
|
||||
"'%s:%d: username '%s' contains invalid characters. ",
|
||||
g_pserver->acl_filename, linenum, argv[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Try to process the line using the fake user to validate iif
|
||||
* the rules are able to apply cleanly. */
|
||||
ACLSetUser(fakeuser,"reset",-1);
|
||||
@@ -1378,6 +1494,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(ACLLogEntry *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 = (ACLLogEntry*)zmalloc(sizeof(*le));
|
||||
le->count = 1;
|
||||
le->reason = reason;
|
||||
le->username = sdsdup(reason == ACL_DENIED_AUTH ? username : c->puser->name);
|
||||
le->ctime = mstime();
|
||||
|
||||
switch(reason) {
|
||||
case ACL_DENIED_CMD: le->object = sdsnew(c->cmd->name); break;
|
||||
case ACL_DENIED_KEY: le->object = sdsnew(szFromObj(c->argv[keypos])); break;
|
||||
case ACL_DENIED_AUTH: le->object = sdsnew(szFromObj(c->argv[0])); break;
|
||||
default: le->object = sdsempty();
|
||||
}
|
||||
|
||||
client *realclient = c;
|
||||
if (realclient->flags & CLIENT_LUA) realclient = g_pserver->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 = (ACLLogEntry*)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) > g_pserver->acllog_max_len) {
|
||||
listNode *ln = listLast(ACLLog);
|
||||
ACLLogEntry *le = (ACLLogEntry*)listNodeValue(ln);
|
||||
ACLFreeLogEntry(le);
|
||||
listDelNode(ACLLog,ln);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* ACL related commands
|
||||
* ==========================================================================*/
|
||||
@@ -1385,19 +1626,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 = szFromObj(c->argv[1]);
|
||||
if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
|
||||
sds username = szFromObj(c->argv[2]);
|
||||
/* 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.
|
||||
@@ -1575,22 +1825,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,"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(szFromObj(c->argv[2]),"reset")) {
|
||||
listSetFreeMethod(ACLLog,(void(*)(const void*))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 = (ACLLogEntry*)listNodeValue(ln);
|
||||
addReplyMapLen(c,7);
|
||||
addReplyBulkCString(c,"count");
|
||||
addReplyLongLong(c,le->count);
|
||||
|
||||
addReplyBulkCString(c,"reason");
|
||||
const char *reasonstr = "INVALID_REASON";
|
||||
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");
|
||||
const 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 (!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);
|
||||
|
||||
+17
-3
@@ -327,12 +327,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 +341,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) {
|
||||
|
||||
+3
-2
@@ -70,7 +70,7 @@ typedef struct list {
|
||||
#define listSetMatchMethod(l,m) ((l)->match = (m))
|
||||
|
||||
#define listGetDupMethod(l) ((l)->dup)
|
||||
#define listGetFree(l) ((l)->free)
|
||||
#define listGetFreeMethod(l) ((l)->free)
|
||||
#define listGetMatchMethod(l) ((l)->match)
|
||||
|
||||
/* Prototypes */
|
||||
@@ -89,7 +89,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 */
|
||||
|
||||
+95
-12
@@ -30,6 +30,7 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
@@ -48,6 +49,7 @@
|
||||
#include "fastlock.h"
|
||||
#include "zmalloc.h"
|
||||
#include "config.h"
|
||||
#include "mcs_lock.h"
|
||||
|
||||
#ifdef USE_MUTEX
|
||||
thread_local int cOwnLock = 0;
|
||||
@@ -80,7 +82,45 @@ public:
|
||||
mutex_wrapper g_lock;
|
||||
|
||||
#else
|
||||
fastlock g_lock("AE (global)");
|
||||
//fastlock g_lock("AE (global)");
|
||||
McsLock g_lock;
|
||||
|
||||
thread_local McsLock::node m_node;
|
||||
class McsLockGuard
|
||||
{
|
||||
McsLock &m_lock;
|
||||
bool fReleased = false;
|
||||
public:
|
||||
|
||||
McsLockGuard(McsLock &lock)
|
||||
: m_lock(lock)
|
||||
{
|
||||
m_lock.lock(&m_node);
|
||||
}
|
||||
|
||||
McsLockGuard(McsLock &lock, std::defer_lock_t)
|
||||
: m_lock(lock)
|
||||
{
|
||||
fReleased = true;
|
||||
}
|
||||
|
||||
void lock() {
|
||||
m_lock.lock(&m_node);
|
||||
fReleased = false;
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
m_lock.unlock(&m_node);
|
||||
fReleased = true;
|
||||
}
|
||||
|
||||
~McsLockGuard()
|
||||
{
|
||||
if (!fReleased)
|
||||
m_lock.unlock(&m_node);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
thread_local aeEventLoop *g_eventLoopThisThread = NULL;
|
||||
|
||||
@@ -122,6 +162,7 @@ struct aeCommand
|
||||
AE_ASYNC_OP op;
|
||||
int fd;
|
||||
int mask;
|
||||
bool fLock = true;
|
||||
union {
|
||||
aePostFunctionProc *proc;
|
||||
aeFileProc *fproc;
|
||||
@@ -166,7 +207,9 @@ void aeProcessCmd(aeEventLoop *eventLoop, int fd, void *, int )
|
||||
|
||||
case AE_ASYNC_OP::PostFunction:
|
||||
{
|
||||
std::unique_lock<decltype(g_lock)> ulock(g_lock);
|
||||
McsLockGuard ulock(g_lock, std::defer_lock);
|
||||
if (cmd.fLock)
|
||||
ulock.lock();
|
||||
((aePostFunctionProc*)cmd.proc)(cmd.clientData);
|
||||
break;
|
||||
}
|
||||
@@ -176,7 +219,9 @@ void aeProcessCmd(aeEventLoop *eventLoop, int fd, void *, int )
|
||||
if (cmd.pctl != nullptr)
|
||||
cmd.pctl->mutexcv.lock();
|
||||
|
||||
std::unique_lock<decltype(g_lock)> ulock(g_lock);
|
||||
McsLockGuard ulock(g_lock, std::defer_lock);
|
||||
if (cmd.fLock)
|
||||
ulock.lock();
|
||||
(*cmd.pfn)();
|
||||
|
||||
if (cmd.pctl != nullptr)
|
||||
@@ -236,6 +281,7 @@ int aeCreateRemoteFileEvent(aeEventLoop *eventLoop, int fd, int mask,
|
||||
cmd.fproc = proc;
|
||||
cmd.clientData = clientData;
|
||||
cmd.pctl = nullptr;
|
||||
cmd.fLock = true;
|
||||
if (fSynchronous)
|
||||
{
|
||||
cmd.pctl = new (MALLOC_LOCAL) aeCommandControl();
|
||||
@@ -272,13 +318,14 @@ int aePostFunction(aeEventLoop *eventLoop, aePostFunctionProc *proc, void *arg)
|
||||
cmd.op = AE_ASYNC_OP::PostFunction;
|
||||
cmd.proc = proc;
|
||||
cmd.clientData = arg;
|
||||
cmd.fLock = true;
|
||||
auto size = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
|
||||
if (size != sizeof(cmd))
|
||||
return AE_ERR;
|
||||
return AE_OK;
|
||||
}
|
||||
|
||||
int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fSynchronous)
|
||||
int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fSynchronous, bool fLock)
|
||||
{
|
||||
if (eventLoop == g_eventLoopThisThread)
|
||||
{
|
||||
@@ -290,6 +337,7 @@ int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fSynch
|
||||
cmd.op = AE_ASYNC_OP::PostCppFunction;
|
||||
cmd.pfn = new (MALLOC_LOCAL) std::function<void()>(fn);
|
||||
cmd.pctl = nullptr;
|
||||
cmd.fLock = fLock;
|
||||
if (fSynchronous)
|
||||
{
|
||||
cmd.pctl = new (MALLOC_LOCAL) aeCommandControl();
|
||||
@@ -327,6 +375,7 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
|
||||
eventLoop->maxfd = -1;
|
||||
eventLoop->beforesleep = NULL;
|
||||
eventLoop->aftersleep = NULL;
|
||||
eventLoop->flags = 0;
|
||||
if (aeApiCreate(eventLoop) == -1) goto err;
|
||||
/* Events with mask == AE_NONE are not set. So let's initialize the
|
||||
* vector with it. */
|
||||
@@ -360,6 +409,14 @@ int aeGetSetSize(aeEventLoop *eventLoop) {
|
||||
return eventLoop->setsize;
|
||||
}
|
||||
|
||||
/* Tells the next iteration/s of the event processing to set timeout of 0. */
|
||||
void aeSetDontWait(aeEventLoop *eventLoop, int noWait) {
|
||||
if (noWait)
|
||||
eventLoop->flags |= AE_DONT_WAIT;
|
||||
else
|
||||
eventLoop->flags &= ~AE_DONT_WAIT;
|
||||
}
|
||||
|
||||
/* Resize the maximum set size of the event loop.
|
||||
* If the requested set size is smaller than the current set size, but
|
||||
* there is already a file descriptor in use that is >= the requested
|
||||
@@ -394,6 +451,7 @@ extern "C" void aeDeleteEventLoop(aeEventLoop *eventLoop) {
|
||||
close(eventLoop->fdCmdRead);
|
||||
close(eventLoop->fdCmdWrite);
|
||||
|
||||
/* Free the time events list. */
|
||||
auto *te = eventLoop->timeEventHead;
|
||||
while (te)
|
||||
{
|
||||
@@ -439,6 +497,7 @@ void aeDeleteFileEventAsync(aeEventLoop *eventLoop, int fd, int mask)
|
||||
cmd.op = AE_ASYNC_OP::DeleteFileEvent;
|
||||
cmd.fd = fd;
|
||||
cmd.mask = mask;
|
||||
cmd.fLock = true;
|
||||
auto cb = write(eventLoop->fdCmdWrite, &cmd, sizeof(cmd));
|
||||
AE_ASSERT(cb == sizeof(cmd));
|
||||
}
|
||||
@@ -516,6 +575,7 @@ extern "C" long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long millise
|
||||
te->clientData = clientData;
|
||||
te->prev = NULL;
|
||||
te->next = eventLoop->timeEventHead;
|
||||
te->refcount = 0;
|
||||
if (te->next)
|
||||
te->next->prev = te;
|
||||
eventLoop->timeEventHead = te;
|
||||
@@ -565,7 +625,7 @@ static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
|
||||
|
||||
/* Process time events */
|
||||
static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
std::unique_lock<decltype(g_lock)> ulock(g_lock);
|
||||
McsLockGuard ulock(g_lock);
|
||||
int processed = 0;
|
||||
aeTimeEvent *te;
|
||||
long long maxId;
|
||||
@@ -597,6 +657,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
|
||||
@@ -626,7 +693,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);
|
||||
@@ -642,7 +711,7 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
extern "C" void ProcessEventCore(aeEventLoop *eventLoop, aeFileEvent *fe, int mask, int fd)
|
||||
{
|
||||
#define LOCK_IF_NECESSARY(fe, tsmask) \
|
||||
std::unique_lock<decltype(g_lock)> ulock(g_lock, std::defer_lock); \
|
||||
McsLockGuard ulock(g_lock, std::defer_lock); \
|
||||
if (!(fe->mask & tsmask)) \
|
||||
ulock.lock()
|
||||
|
||||
@@ -671,6 +740,7 @@ extern "C" void ProcessEventCore(aeEventLoop *eventLoop, aeFileEvent *fe, int ma
|
||||
LOCK_IF_NECESSARY(fe, AE_READ_THREADSAFE);
|
||||
fe->rfileProc(eventLoop,fd,fe->clientData,mask | (fe->mask & AE_READ_THREADSAFE));
|
||||
fired++;
|
||||
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
|
||||
}
|
||||
|
||||
/* Fire the writable event. */
|
||||
@@ -707,6 +777,7 @@ extern "C" void ProcessEventCore(aeEventLoop *eventLoop, aeFileEvent *fe, int ma
|
||||
* 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)
|
||||
@@ -761,13 +832,25 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
}
|
||||
}
|
||||
|
||||
if (eventLoop->flags & AE_DONT_WAIT) {
|
||||
tv.tv_sec = tv.tv_usec = 0;
|
||||
tvp = &tv;
|
||||
}
|
||||
|
||||
if (eventLoop->beforesleep != NULL && flags & AE_CALL_BEFORE_SLEEP) {
|
||||
McsLockGuard ulock(g_lock, std::defer_lock);
|
||||
if (!(eventLoop->beforesleepFlags & AE_SLEEP_THREADSAFE))
|
||||
ulock.lock();
|
||||
eventLoop->beforesleep(eventLoop);
|
||||
}
|
||||
|
||||
/* Call the multiplexing API, will return only on timeout or when
|
||||
* some event fires. */
|
||||
numevents = aeApiPoll(eventLoop, tvp);
|
||||
|
||||
/* After sleep callback. */
|
||||
if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP) {
|
||||
std::unique_lock<decltype(g_lock)> ulock(g_lock, std::defer_lock);
|
||||
McsLockGuard ulock(g_lock, std::defer_lock);
|
||||
if (!(eventLoop->aftersleepFlags & AE_SLEEP_THREADSAFE))
|
||||
ulock.lock();
|
||||
eventLoop->aftersleep(eventLoop);
|
||||
@@ -818,7 +901,7 @@ void aeMain(aeEventLoop *eventLoop) {
|
||||
g_eventLoopThisThread = eventLoop;
|
||||
while (!eventLoop->stop) {
|
||||
if (eventLoop->beforesleep != NULL) {
|
||||
std::unique_lock<decltype(g_lock)> ulock(g_lock, std::defer_lock);
|
||||
McsLockGuard ulock(g_lock, std::defer_lock);
|
||||
if (!(eventLoop->beforesleepFlags & AE_SLEEP_THREADSAFE))
|
||||
ulock.lock();
|
||||
eventLoop->beforesleep(eventLoop);
|
||||
@@ -845,20 +928,20 @@ void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep,
|
||||
|
||||
void aeAcquireLock()
|
||||
{
|
||||
g_lock.lock();
|
||||
g_lock.lock(&m_node);
|
||||
}
|
||||
|
||||
int aeTryAcquireLock(int fWeak)
|
||||
{
|
||||
return g_lock.try_lock(!!fWeak);
|
||||
return g_lock.try_lock(&m_node, fWeak);
|
||||
}
|
||||
|
||||
void aeReleaseLock()
|
||||
{
|
||||
g_lock.unlock();
|
||||
g_lock.unlock(&m_node);
|
||||
}
|
||||
|
||||
int aeThreadOwnsLock()
|
||||
{
|
||||
return g_lock.fOwnLock();
|
||||
return m_node.depth > 0;
|
||||
}
|
||||
@@ -58,11 +58,12 @@ extern "C" {
|
||||
#define AE_WRITE_THREADSAFE 16
|
||||
#define AE_SLEEP_THREADSAFE 32
|
||||
|
||||
#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
|
||||
@@ -97,6 +98,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 */
|
||||
@@ -124,6 +127,7 @@ typedef struct aeEventLoop {
|
||||
int fdCmdWrite;
|
||||
int fdCmdRead;
|
||||
int cevents;
|
||||
int flags;
|
||||
} aeEventLoop;
|
||||
|
||||
/* Prototypes */
|
||||
@@ -131,7 +135,7 @@ aeEventLoop *aeCreateEventLoop(int setsize);
|
||||
int aePostFunction(aeEventLoop *eventLoop, aePostFunctionProc *proc, void *arg);
|
||||
#ifdef __cplusplus
|
||||
} // EXTERN C
|
||||
int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fSynchronous = false);
|
||||
int aePostFunction(aeEventLoop *eventLoop, std::function<void()> fn, bool fSynchronous = false, bool fLock = true);
|
||||
extern "C" {
|
||||
#endif
|
||||
void aeDeleteEventLoop(aeEventLoop *eventLoop);
|
||||
@@ -157,6 +161,7 @@ void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep
|
||||
void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep, int flags);
|
||||
int aeGetSetSize(aeEventLoop *eventLoop);
|
||||
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
|
||||
void aeSetDontWait(aeEventLoop *eventLoop, int noWait);
|
||||
|
||||
void aeAcquireLock();
|
||||
int aeTryAcquireLock(int fWeak);
|
||||
|
||||
+2
-2
@@ -125,8 +125,8 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
||||
|
||||
if (e->events & EPOLLIN) mask |= AE_READABLE;
|
||||
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
|
||||
if (e->events & EPOLLERR) mask |= AE_WRITABLE;
|
||||
if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
|
||||
if (e->events & EPOLLERR) mask |= AE_WRITABLE|AE_READABLE;
|
||||
if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE;
|
||||
eventLoop->fired[j].fd = e->data.fd;
|
||||
eventLoop->fired[j].mask = mask;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ public:
|
||||
|
||||
void arm(client *c) // if a client is passed, then the client is already locked
|
||||
{
|
||||
if (m_fArmed)
|
||||
return;
|
||||
|
||||
serverAssertDebug(!GlobalLocksAcquired());
|
||||
|
||||
if (c != nullptr)
|
||||
{
|
||||
serverAssert(!m_fArmed);
|
||||
|
||||
+25
-11
@@ -193,6 +193,20 @@ int anetSendTimeout(char *err, int fd, long long ms) {
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
/* Set the socket receive timeout (SO_RCVTIMEO socket option) to the specified
|
||||
* number of milliseconds, or disable it if the 'ms' argument is zero. */
|
||||
int anetRecvTimeout(char *err, int fd, long long ms) {
|
||||
struct timeval tv;
|
||||
|
||||
tv.tv_sec = ms/1000;
|
||||
tv.tv_usec = (ms%1000)*1000;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_RCVTIMEO: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to
|
||||
* do the actual work. It resolves the hostname "host" and set the string
|
||||
* representation of the IP address into the buffer pointed by "ipbuf".
|
||||
@@ -276,8 +290,8 @@ static int anetCreateSocket(char *err, int domain) {
|
||||
#define ANET_CONNECT_NONBLOCK 1
|
||||
#define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */
|
||||
#define ANET_CONNECT_REUSEPORT 4
|
||||
static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
char *source_addr, int flags)
|
||||
static int anetTcpGenericConnect(char *err, const char *addr, int port,
|
||||
const char *source_addr, int flags)
|
||||
{
|
||||
int s = ANET_ERR, rv;
|
||||
char portstr[6]; /* strlen("65535") + 1; */
|
||||
@@ -359,31 +373,31 @@ end:
|
||||
}
|
||||
}
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port)
|
||||
int anetTcpConnect(char *err, const char *addr, int port)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port)
|
||||
int anetTcpNonBlockConnect(char *err, const char *addr, int port)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port,
|
||||
char *source_addr)
|
||||
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port,
|
||||
const char *source_addr)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,
|
||||
ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port,
|
||||
char *source_addr)
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
|
||||
const char *source_addr)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,
|
||||
ANET_CONNECT_NONBLOCK|ANET_CONNECT_BE_BINDING);
|
||||
}
|
||||
|
||||
int anetUnixGenericConnect(char *err, char *path, int flags)
|
||||
int anetUnixGenericConnect(char *err, const char *path, int flags)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_un sa;
|
||||
@@ -411,12 +425,12 @@ int anetUnixGenericConnect(char *err, char *path, int flags)
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetUnixConnect(char *err, char *path)
|
||||
int anetUnixConnect(char *err, const char *path)
|
||||
{
|
||||
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE);
|
||||
}
|
||||
|
||||
int anetUnixNonBlockConnect(char *err, char *path)
|
||||
int anetUnixNonBlockConnect(char *err, const char *path)
|
||||
{
|
||||
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
+7
-6
@@ -53,12 +53,12 @@ extern "C" {
|
||||
#undef ip_len
|
||||
#endif
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr);
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port, char *source_addr);
|
||||
int anetUnixConnect(char *err, char *path);
|
||||
int anetUnixNonBlockConnect(char *err, char *path);
|
||||
int anetTcpConnect(char *err, const char *addr, int port);
|
||||
int anetTcpNonBlockConnect(char *err, const char *addr, int port);
|
||||
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr);
|
||||
int anetUnixConnect(char *err, const char *path);
|
||||
int anetUnixNonBlockConnect(char *err, const char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
@@ -74,6 +74,7 @@ int anetEnableTcpNoDelay(char *err, int fd);
|
||||
int anetDisableTcpNoDelay(char *err, int fd);
|
||||
int anetTcpKeepAlive(char *err, int fd);
|
||||
int anetSendTimeout(char *err, int fd, long long ms);
|
||||
int anetRecvTimeout(char *err, int fd, long long ms);
|
||||
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port);
|
||||
int anetKeepAlive(char *err, int fd, int interval);
|
||||
int anetSockName(int fd, char *ip, size_t ip_len, int *port);
|
||||
|
||||
+146
-67
@@ -257,6 +257,7 @@ void stopAppendOnly(void) {
|
||||
g_pserver->aof_fd = -1;
|
||||
g_pserver->aof_selected_db = -1;
|
||||
g_pserver->aof_state = AOF_OFF;
|
||||
g_pserver->aof_rewrite_scheduled = 0;
|
||||
killAppendOnlyChild();
|
||||
}
|
||||
|
||||
@@ -279,9 +280,9 @@ int startAppendOnly(void) {
|
||||
strerror(errno));
|
||||
return C_ERR;
|
||||
}
|
||||
if (g_pserver->rdb_child_pid != -1) {
|
||||
if (hasActiveChildProcess() && g_pserver->aof_child_pid == -1) {
|
||||
g_pserver->aof_rewrite_scheduled = 1;
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible.");
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
|
||||
} else {
|
||||
/* If there is a pending AOF rewrite, we need to switch it off and
|
||||
* start a new one: the old one cannot be reused because it is not
|
||||
@@ -318,9 +319,7 @@ ssize_t aofWrite(int fd, const char *buf, size_t len) {
|
||||
nwritten = write(fd, buf, len);
|
||||
|
||||
if (nwritten < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
if (errno == EINTR) continue;
|
||||
return totwritten ? totwritten : -1;
|
||||
}
|
||||
|
||||
@@ -405,6 +404,10 @@ void flushAppendOnlyFile(int force) {
|
||||
* there is much to do about the whole server stopping for power problems
|
||||
* or alike */
|
||||
|
||||
if (g_pserver->aof_flush_sleep && sdslen(g_pserver->aof_buf)) {
|
||||
usleep(g_pserver->aof_flush_sleep);
|
||||
}
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
nwritten = aofWrite(g_pserver->aof_fd,g_pserver->aof_buf,sdslen(g_pserver->aof_buf));
|
||||
latencyEndMonitor(latency);
|
||||
@@ -415,7 +418,7 @@ void flushAppendOnlyFile(int force) {
|
||||
* useful for graphing / monitoring purposes. */
|
||||
if (sync_in_progress) {
|
||||
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
|
||||
} else if (g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1) {
|
||||
} else if (hasActiveChildProcess()) {
|
||||
latencyAddSampleIfNeeded("aof-write-active-child",latency);
|
||||
} else {
|
||||
latencyAddSampleIfNeeded("aof-write-alone",latency);
|
||||
@@ -511,9 +514,8 @@ void flushAppendOnlyFile(int force) {
|
||||
try_fsync:
|
||||
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
|
||||
* children doing I/O in the background. */
|
||||
if (g_pserver->aof_no_fsync_on_rewrite &&
|
||||
(g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1))
|
||||
return;
|
||||
if (g_pserver->aof_no_fsync_on_rewrite && hasActiveChildProcess())
|
||||
return;
|
||||
|
||||
/* Perform the fsync if needed. */
|
||||
if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
@@ -547,7 +549,10 @@ sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) {
|
||||
dst = sdscatlen(dst,buf,len);
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
o = getDecodedObject(argv[j]);
|
||||
if (sdsEncodedObject(argv[j]))
|
||||
o = argv[j];
|
||||
else
|
||||
o = getDecodedObject(argv[j]);
|
||||
buf[0] = '$';
|
||||
len = 1+ll2string(buf+1,sizeof(buf)-1,sdslen(szFromObj(o)));
|
||||
buf[len++] = '\r';
|
||||
@@ -555,7 +560,8 @@ sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) {
|
||||
dst = sdscatlen(dst,buf,len);
|
||||
dst = sdscatlen(dst,ptrFromObj(o),sdslen(szFromObj(o)));
|
||||
dst = sdscatlen(dst,"\r\n",2);
|
||||
decrRefCount(o);
|
||||
if (o != argv[j])
|
||||
decrRefCount(o);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
@@ -597,21 +603,59 @@ sds catAppendOnlyExpireAtCommand(sds buf, struct redisCommand *cmd, robj *key, r
|
||||
return buf;
|
||||
}
|
||||
|
||||
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
|
||||
sds buf = sdsempty();
|
||||
robj *tmpargv[3];
|
||||
|
||||
/* The DB this command was targeting is not the same as the last command
|
||||
* we appended. To issue a SELECT command is needed. */
|
||||
if (dictid != g_pserver->aof_selected_db) {
|
||||
char seldb[64];
|
||||
|
||||
snprintf(seldb,sizeof(seldb),"%d",dictid);
|
||||
buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",
|
||||
(unsigned long)strlen(seldb),seldb);
|
||||
g_pserver->aof_selected_db = dictid;
|
||||
sds catAppendOnlyExpireMemberAtCommand(sds buf, struct redisCommand *cmd, robj **argv, const size_t argc) {
|
||||
long long when = 0;
|
||||
int unit = UNIT_SECONDS;
|
||||
bool fAbsolute = false;
|
||||
|
||||
if (cmd->proc == expireMemberCommand) {
|
||||
if (getLongLongFromObject(argv[3], &when) != C_OK)
|
||||
serverPanic("propogating invalid EXPIREMEMBER command");
|
||||
|
||||
if (argc == 5) {
|
||||
unit = parseUnitString(szFromObj(argv[4]));
|
||||
}
|
||||
} else if (cmd->proc == expireMemberAtCommand) {
|
||||
if (getLongLongFromObject(argv[3], &when) != C_OK)
|
||||
serverPanic("propogating invalid EXPIREMEMBERAT command");
|
||||
fAbsolute = true;
|
||||
} else if (cmd->proc == pexpireMemberAtCommand) {
|
||||
if (getLongLongFromObject(argv[3], &when) != C_OK)
|
||||
serverPanic("propogating invalid PEXPIREMEMBERAT command");
|
||||
fAbsolute = true;
|
||||
unit = UNIT_MILLISECONDS;
|
||||
} else {
|
||||
serverPanic("Unknown expiremember command");
|
||||
}
|
||||
|
||||
switch (unit)
|
||||
{
|
||||
case UNIT_SECONDS:
|
||||
when *= 1000;
|
||||
break;
|
||||
|
||||
case UNIT_MILLISECONDS:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!fAbsolute)
|
||||
when += mstime();
|
||||
|
||||
robj *argvNew[4];
|
||||
argvNew[0] = createStringObject("PEXPIREMEMBERAT",15);
|
||||
argvNew[1] = argv[1];
|
||||
argvNew[2] = argv[2];
|
||||
argvNew[3] = createStringObjectFromLongLong(when);
|
||||
buf = catAppendOnlyGenericCommand(buf, 4, argvNew);
|
||||
decrRefCount(argvNew[0]);
|
||||
decrRefCount(argvNew[3]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
sds catCommandForAofAndActiveReplication(sds buf, struct redisCommand *cmd, robj **argv, int argc)
|
||||
{
|
||||
robj *tmpargv[3];
|
||||
|
||||
if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
|
||||
cmd->proc == expireatCommand) {
|
||||
/* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
|
||||
@@ -640,6 +684,10 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
||||
if (pxarg)
|
||||
buf = catAppendOnlyExpireAtCommand(buf,cserver.pexpireCommand,argv[1],
|
||||
pxarg);
|
||||
} else if (cmd->proc == expireMemberCommand || cmd->proc == expireMemberAtCommand ||
|
||||
cmd->proc == pexpireMemberAtCommand) {
|
||||
/* Translate subkey expire commands to PEXPIREMEMBERAT */
|
||||
buf = catAppendOnlyExpireMemberAtCommand(buf, cmd, argv, argc);
|
||||
} else {
|
||||
/* All the other commands don't need translation or need the
|
||||
* same translation already operated in the command vector
|
||||
@@ -647,6 +695,25 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
||||
buf = catAppendOnlyGenericCommand(buf,argc,argv);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
|
||||
sds buf = sdsempty();
|
||||
|
||||
/* The DB this command was targeting is not the same as the last command
|
||||
* we appended. To issue a SELECT command is needed. */
|
||||
if (dictid != g_pserver->aof_selected_db) {
|
||||
char seldb[64];
|
||||
|
||||
snprintf(seldb,sizeof(seldb),"%d",dictid);
|
||||
buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",
|
||||
(unsigned long)strlen(seldb),seldb);
|
||||
g_pserver->aof_selected_db = dictid;
|
||||
}
|
||||
|
||||
buf = catCommandForAofAndActiveReplication(buf, cmd, argv, argc);
|
||||
|
||||
/* Append to the AOF buffer. This will be flushed on disk just before
|
||||
* of re-entering the event loop, so before the client will get a
|
||||
* positive reply about the operation performed. */
|
||||
@@ -669,11 +736,12 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
||||
|
||||
/* In Redis commands are always executed in the context of a client, so in
|
||||
* order to load the append only file we need to create a fake client. */
|
||||
client *createFakeClient(void) {
|
||||
client *c = (client*)zmalloc(sizeof(*c), MALLOC_LOCAL);
|
||||
struct client *createAOFClient(void) {
|
||||
struct client *c =(client*) zmalloc(sizeof(*c), MALLOC_LOCAL);
|
||||
|
||||
selectDb(c,0);
|
||||
c->fd = -1;
|
||||
c->id = CLIENT_ID_AOF; /* So modules can identify it's the AOF client. */
|
||||
c->conn = NULL;
|
||||
c->iel = IDX_EVENT_LOOP_MAIN;
|
||||
c->name = NULL;
|
||||
c->querybuf = sdsempty();
|
||||
@@ -683,6 +751,7 @@ client *createFakeClient(void) {
|
||||
c->bufpos = 0;
|
||||
c->flags = 0;
|
||||
c->fPendingAsyncWrite = FALSE;
|
||||
c->fPendingAsyncWriteHandler = FALSE;
|
||||
c->btype = BLOCKED_NONE;
|
||||
/* We set the fake client as a replica waiting for the synchronization
|
||||
* so that Redis will not try to send replies to this client. */
|
||||
@@ -753,8 +822,8 @@ int loadAppendOnlyFile(char *filename) {
|
||||
* to the same file we're about to read. */
|
||||
g_pserver->aof_state = AOF_OFF;
|
||||
|
||||
fakeClient = createFakeClient();
|
||||
startLoading(fp);
|
||||
fakeClient = createAOFClient();
|
||||
startLoadingFile(fp, filename, RDBFLAGS_AOF_PREAMBLE);
|
||||
|
||||
/* Check if this AOF file has an RDB preamble. In that case we need to
|
||||
* load the RDB file and later continue loading the AOF tail. */
|
||||
@@ -765,12 +834,12 @@ int loadAppendOnlyFile(char *filename) {
|
||||
} else {
|
||||
/* RDB preamble. Pass loading the RDB functions. */
|
||||
rio rdb;
|
||||
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
||||
|
||||
serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
|
||||
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
|
||||
rioInitWithFile(&rdb,fp);
|
||||
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
||||
if (rdbLoadRio(&rdb,&rsi,1) != C_OK) {
|
||||
if (rdbLoadRio(&rdb,RDBFLAGS_AOF_PREAMBLE,&rsi) != C_OK) {
|
||||
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
|
||||
goto readerr;
|
||||
} else {
|
||||
@@ -791,6 +860,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
if (!(loops++ % 1000)) {
|
||||
loadingProgress(ftello(fp));
|
||||
processEventsWhileBlocked(serverTL - g_pserver->rgthreadvar);
|
||||
processModuleLoadingProgressEvent(1);
|
||||
}
|
||||
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
@@ -804,18 +874,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 = (robj**)zmalloc(sizeof(robj*)*argc, MALLOC_LOCAL);
|
||||
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);
|
||||
@@ -824,10 +902,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -843,7 +923,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
if (cmd == cserver.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)
|
||||
{
|
||||
@@ -864,6 +944,8 @@ int loadAppendOnlyFile(char *filename) {
|
||||
freeFakeClientArgv(fakeClient);
|
||||
fakeClient->cmd = NULL;
|
||||
if (g_pserver->aof_load_truncated) valid_up_to = ftello(fp);
|
||||
if (g_pserver->key_load_delay)
|
||||
usleep(g_pserver->key_load_delay);
|
||||
}
|
||||
|
||||
/* This point can only be reached when EOF is reached without errors.
|
||||
@@ -881,7 +963,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
||||
fclose(fp);
|
||||
freeFakeClient(fakeClient);
|
||||
g_pserver->aof_state = old_aof_state;
|
||||
stopLoading();
|
||||
stopLoading(1);
|
||||
aofUpdateCurrentSize();
|
||||
g_pserver->aof_rewrite_base_size = g_pserver->aof_current_size;
|
||||
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
|
||||
@@ -890,6 +972,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
||||
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
|
||||
if (!feof(fp)) {
|
||||
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
|
||||
fclose(fp);
|
||||
serverLog(LL_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
@@ -920,11 +1003,13 @@ uxeof: /* Unexpected AOF end of file. */
|
||||
}
|
||||
}
|
||||
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
|
||||
serverLog(LL_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./keydb-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the g_pserver->");
|
||||
fclose(fp);
|
||||
serverLog(LL_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./keydb-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.");
|
||||
exit(1);
|
||||
|
||||
fmterr: /* Format error. */
|
||||
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
|
||||
fclose(fp);
|
||||
serverLog(LL_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./keydb-check-aof --fix <filename>");
|
||||
exit(1);
|
||||
}
|
||||
@@ -1157,7 +1242,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;
|
||||
}
|
||||
@@ -1219,12 +1304,13 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
/* 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. */
|
||||
id.ms = 0; id.seq = 1;
|
||||
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 (rioWriteBulkStreamID(r,&id) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"x",1) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"y",1) == 0) return 0;
|
||||
}
|
||||
@@ -1378,7 +1464,7 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
}
|
||||
else
|
||||
{
|
||||
char cmd[]="*4\r\n$12\r\nEXPIREMEMBER\r\n";
|
||||
char cmd[]="*4\r\n$12\r\nPEXPIREMEMBERAT\r\n";
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) goto werr;
|
||||
if (rioWrite(aof,subExpire.subkey(),sdslen(subExpire.subkey())) == 0) goto werr;
|
||||
@@ -1432,9 +1518,11 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
if (g_pserver->aof_rewrite_incremental_fsync)
|
||||
rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
|
||||
|
||||
startSaving(RDBFLAGS_AOF_PREAMBLE);
|
||||
|
||||
if (g_pserver->aof_use_rdb_preamble) {
|
||||
int error;
|
||||
if (rdbSaveRio(&aof,&error,RDB_SAVE_AOF_PREAMBLE,NULL) == C_ERR) {
|
||||
if (rdbSaveRio(&aof,&error,RDBFLAGS_AOF_PREAMBLE,NULL) == C_ERR) {
|
||||
errno = error;
|
||||
goto werr;
|
||||
}
|
||||
@@ -1496,15 +1584,18 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
if (rename(tmpfile,filename) == -1) {
|
||||
serverLog(LL_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));
|
||||
unlink(tmpfile);
|
||||
stopSaving(0);
|
||||
return C_ERR;
|
||||
}
|
||||
serverLog(LL_NOTICE,"SYNC append only file rewrite performed");
|
||||
stopSaving(1);
|
||||
return C_OK;
|
||||
|
||||
werr:
|
||||
serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
||||
fclose(fp);
|
||||
unlink(tmpfile);
|
||||
stopSaving(0);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
@@ -1612,39 +1703,25 @@ void aofClosePipes(void) {
|
||||
*/
|
||||
int rewriteAppendOnlyFileBackground(void) {
|
||||
pid_t childpid;
|
||||
long long start;
|
||||
|
||||
if (g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1) return C_ERR;
|
||||
if (hasActiveChildProcess()) return C_ERR;
|
||||
if (aofCreatePipes() != C_OK) return C_ERR;
|
||||
openChildInfoPipe();
|
||||
start = ustime();
|
||||
if ((childpid = fork()) == 0) {
|
||||
if ((childpid = redisFork()) == 0) {
|
||||
char tmpfile[256];
|
||||
|
||||
/* Child */
|
||||
closeListeningSockets(0);
|
||||
redisSetProcTitle("keydb-aof-rewrite");
|
||||
redisSetCpuAffinity(g_pserver->aof_rewrite_cpulist);
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
||||
|
||||
if (private_dirty) {
|
||||
serverLog(LL_NOTICE,
|
||||
"AOF rewrite: %zu MB of memory used by copy-on-write",
|
||||
private_dirty/(1024*1024));
|
||||
}
|
||||
|
||||
g_pserver->child_info_data.cow_size = private_dirty;
|
||||
sendChildInfo(CHILD_INFO_TYPE_AOF);
|
||||
sendChildCOWInfo(CHILD_INFO_TYPE_AOF, "AOF rewrite");
|
||||
exitFromChild(0);
|
||||
} else {
|
||||
exitFromChild(1);
|
||||
}
|
||||
} else {
|
||||
/* Parent */
|
||||
g_pserver->stat_fork_time = ustime()-start;
|
||||
g_pserver->stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / g_pserver->stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",g_pserver->stat_fork_time/1000);
|
||||
if (childpid == -1) {
|
||||
closeChildInfoPipe();
|
||||
serverLog(LL_WARNING,
|
||||
@@ -1658,7 +1735,6 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
g_pserver->aof_rewrite_scheduled = 0;
|
||||
g_pserver->aof_rewrite_time_start = time(NULL);
|
||||
g_pserver->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 g_pserver->aof_rewrite_buf will start
|
||||
@@ -1673,13 +1749,14 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
void bgrewriteaofCommand(client *c) {
|
||||
if (g_pserver->aof_child_pid != -1) {
|
||||
addReplyError(c,"Background append only file rewriting already in progress");
|
||||
} else if (g_pserver->rdb_child_pid != -1) {
|
||||
} else if (hasActiveChildProcess()) {
|
||||
g_pserver->aof_rewrite_scheduled = 1;
|
||||
addReplyStatus(c,"Background append only file rewriting scheduled");
|
||||
} else if (rewriteAppendOnlyFileBackground() == C_OK) {
|
||||
addReplyStatus(c,"Background append only file rewriting started");
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
addReplyError(c,"Can't execute an AOF background rewriting. "
|
||||
"Please check the server logs for more information.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1818,6 +1895,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
g_pserver->aof_selected_db = -1; /* Make sure SELECT is re-issued */
|
||||
aofUpdateCurrentSize();
|
||||
g_pserver->aof_rewrite_base_size = g_pserver->aof_current_size;
|
||||
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
|
||||
|
||||
/* Clear regular AOF buffer since its contents was just written to
|
||||
* the new AOF from the background rewrite buffer. */
|
||||
@@ -1838,14 +1916,15 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
||||
} else if (!bysignal && exitcode != 0) {
|
||||
g_pserver->aof_lastbgrewrite_status = C_ERR;
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated with error");
|
||||
} else {
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
* tirggering an error condition. */
|
||||
if (bysignal != SIGUSR1)
|
||||
g_pserver->aof_lastbgrewrite_status = C_ERR;
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated with error");
|
||||
} else {
|
||||
g_pserver->aof_lastbgrewrite_status = C_ERR;
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated by signal %d", bysignal);
|
||||
|
||||
+15
-1
@@ -154,6 +154,20 @@ void *bioProcessBackgroundJobs(void *arg) {
|
||||
return 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(g_pserver->bio_cpulist);
|
||||
|
||||
/* Make the thread killable at any time, so that bioKillThreads()
|
||||
* can work reliably. */
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
@@ -254,7 +268,7 @@ 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_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",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#pragma once
|
||||
/*
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
|
||||
+26
-6
@@ -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:
|
||||
*
|
||||
@@ -556,7 +556,7 @@ void setbitCommand(client *c) {
|
||||
byteval &= ~(1 << bit);
|
||||
byteval |= ((on & 0x1) << bit);
|
||||
((uint8_t*)ptrFromObj(o))[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);
|
||||
g_pserver->dirty++;
|
||||
addReply(c, bitval ? shared.cone : shared.czero);
|
||||
@@ -828,11 +828,11 @@ void bitopCommand(client *c) {
|
||||
/* Store the computed value into the target key */
|
||||
if (maxlen) {
|
||||
robj *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);
|
||||
} else if (dbDelete(c->db,targetkey)) {
|
||||
signalModifiedKey(c->db,targetkey);
|
||||
signalModifiedKey(c,c->db,targetkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id);
|
||||
}
|
||||
g_pserver->dirty++;
|
||||
@@ -976,6 +976,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 */
|
||||
@@ -985,7 +988,10 @@ 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_roptr o;
|
||||
size_t bitoffset;
|
||||
int j, numops = 0, changes = 0;
|
||||
@@ -1073,6 +1079,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,
|
||||
@@ -1197,9 +1209,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);
|
||||
g_pserver->dirty += changes;
|
||||
}
|
||||
zfree(ops);
|
||||
}
|
||||
|
||||
void bitfieldCommand(client *c) {
|
||||
bitfieldGeneric(c, BITFIELD_FLAG_NONE);
|
||||
}
|
||||
|
||||
void bitfieldroCommand(client *c) {
|
||||
bitfieldGeneric(c, BITFIELD_FLAG_READONLY);
|
||||
}
|
||||
+298
-257
@@ -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.
|
||||
*
|
||||
@@ -68,41 +65,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,
|
||||
@@ -113,6 +89,7 @@ void blockClient(client *c, int btype) {
|
||||
c->btype = btype;
|
||||
g_pserver->blocked_clients++;
|
||||
g_pserver->blocked_clients_by_type[btype]++;
|
||||
addClientToTimeoutTable(c);
|
||||
}
|
||||
|
||||
/* This function is called in the beforeSleep() function of the event loop
|
||||
@@ -142,7 +119,7 @@ void processUnblockedClients(int iel) {
|
||||
* the code is conceptually more correct this way. */
|
||||
if (!(c->flags & CLIENT_BLOCKED)) {
|
||||
if (c->querybuf && sdslen(c->querybuf) > 0) {
|
||||
processInputBufferAndReplicate(c);
|
||||
processInputBuffer(c, CMD_CALL_FULL);
|
||||
}
|
||||
}
|
||||
fastlock_unlock(&c->lock);
|
||||
@@ -189,6 +166,7 @@ void unblockClient(client *c) {
|
||||
} else if (c->btype == BLOCKED_WAIT) {
|
||||
unblockClientWaitingReplicas(c);
|
||||
} else if (c->btype == BLOCKED_MODULE) {
|
||||
if (moduleClientIsBlockedOnKeys(c)) unblockClientWaitingData(c);
|
||||
unblockClientFromModule(c);
|
||||
} else {
|
||||
serverPanic("Unknown btype in unblockClient().");
|
||||
@@ -199,6 +177,7 @@ void unblockClient(client *c) {
|
||||
g_pserver->blocked_clients_by_type[c->btype]--;
|
||||
c->flags &= ~CLIENT_BLOCKED;
|
||||
c->btype = BLOCKED_NONE;
|
||||
removeClientFromTimeoutTable(c);
|
||||
queueClientForReprocessing(c);
|
||||
}
|
||||
|
||||
@@ -209,9 +188,9 @@ void replyToBlockedClientTimedOut(client *c) {
|
||||
if (c->btype == BLOCKED_LIST ||
|
||||
c->btype == BLOCKED_ZSET ||
|
||||
c->btype == BLOCKED_STREAM) {
|
||||
addReplyNullArray(c);
|
||||
addReplyNullArrayAsync(c);
|
||||
} else if (c->btype == BLOCKED_WAIT) {
|
||||
addReplyLongLong(c,replicationCountAcksByOffset(c->bpop.reploffset));
|
||||
addReplyLongLongAsync(c,replicationCountAcksByOffset(c->bpop.reploffset));
|
||||
} else if (c->btype == BLOCKED_MODULE) {
|
||||
moduleBlockedClientTimedOut(c);
|
||||
} else {
|
||||
@@ -247,6 +226,251 @@ void disconnectAllBlockedClients(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function for handleClientsBlockedOnKeys(). This function is called
|
||||
* when there may be clients blocked on a list key, and there may be new
|
||||
* data to fetch (the key is ready). */
|
||||
void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
||||
/* We serve clients in the same order they blocked for
|
||||
* this key, from the first blocked to the last. */
|
||||
dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
if (de) {
|
||||
list *clients = (list*)dictGetVal(de);
|
||||
int numclients = listLength(clients);
|
||||
|
||||
while(numclients--) {
|
||||
listNode *clientnode = listFirst(clients);
|
||||
client *receiver = (client*)clientnode->value;
|
||||
std::unique_lock<decltype(receiver->lock)> lock(receiver->lock);
|
||||
|
||||
if (receiver->btype != BLOCKED_LIST) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listRotateHeadToTail(clients);
|
||||
continue;
|
||||
}
|
||||
|
||||
robj *dstkey = receiver->bpop.target;
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == blpopCommand) ?
|
||||
LIST_HEAD : LIST_TAIL;
|
||||
robj *value = listTypePop(o,where);
|
||||
|
||||
if (value) {
|
||||
/* Protect receiver->bpop.target, that will be
|
||||
* freed by the next unblockClient()
|
||||
* call. */
|
||||
if (dstkey) incrRefCount(dstkey);
|
||||
unblockClient(receiver);
|
||||
|
||||
if (serveClientBlockedOnList(receiver,
|
||||
rl->key,dstkey,rl->db,value,
|
||||
where) == C_ERR)
|
||||
{
|
||||
/* If we failed serving the client we need
|
||||
* to also undo the POP operation. */
|
||||
listTypePush(o,value,where);
|
||||
}
|
||||
|
||||
if (dstkey) decrRefCount(dstkey);
|
||||
decrRefCount(value);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (listTypeLength(o) == 0) {
|
||||
dbDelete(rl->db,rl->key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",rl->key,rl->db->id);
|
||||
}
|
||||
/* We don't call signalModifiedKey() as it was already called
|
||||
* when an element was pushed on the list. */
|
||||
}
|
||||
|
||||
/* Helper function for handleClientsBlockedOnKeys(). This function is called
|
||||
* when there may be clients blocked on a sorted set key, and there may be new
|
||||
* data to fetch (the key is ready). */
|
||||
void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
|
||||
/* We serve clients in the same order they blocked for
|
||||
* this key, from the first blocked to the last. */
|
||||
dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
if (de) {
|
||||
list *clients = (list*)dictGetVal(de);
|
||||
int numclients = listLength(clients);
|
||||
unsigned long zcard = zsetLength(o);
|
||||
|
||||
while(numclients-- && zcard) {
|
||||
listNode *clientnode = listFirst(clients);
|
||||
client *receiver = (client*)clientnode->value;
|
||||
std::unique_lock<decltype(receiver->lock)> lock(receiver->lock);
|
||||
|
||||
if (receiver->btype != BLOCKED_ZSET) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listRotateHeadToTail(clients);
|
||||
continue;
|
||||
}
|
||||
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == bzpopminCommand)
|
||||
? ZSET_MIN : ZSET_MAX;
|
||||
unblockClient(receiver);
|
||||
genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
|
||||
zcard--;
|
||||
|
||||
/* Replicate the command. */
|
||||
robj *argv[2];
|
||||
struct redisCommand *cmd = where == ZSET_MIN ?
|
||||
cserver.zpopminCommand :
|
||||
cserver.zpopmaxCommand;
|
||||
argv[0] = createStringObject(cmd->name,strlen(cmd->name));
|
||||
argv[1] = rl->key;
|
||||
incrRefCount(rl->key);
|
||||
propagate(cmd,receiver->db->id,
|
||||
argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function for handleClientsBlockedOnKeys(). This function is called
|
||||
* when there may be clients blocked on a stream key, and there may be new
|
||||
* data to fetch (the key is ready). */
|
||||
void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
stream *s = (stream*)ptrFromObj(o);
|
||||
|
||||
/* We need to provide the new data arrived on the stream
|
||||
* to all the clients that are waiting for an offset smaller
|
||||
* than the current top item. */
|
||||
if (de) {
|
||||
list *clients = (list*)dictGetVal(de);
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
listRewind(clients,&li);
|
||||
|
||||
while((ln = listNext(&li))) {
|
||||
client *receiver = (client*)listNodeValue(ln);
|
||||
if (receiver->btype != BLOCKED_STREAM) continue;
|
||||
std::unique_lock<decltype(receiver->lock)> lock(receiver->lock);
|
||||
bkinfo *bki = (bkinfo*)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
|
||||
* last ID the client is blocked for: this is needed
|
||||
* because serving other clients in the same consumer
|
||||
* group will alter the "last ID" of the consumer
|
||||
* group, and clients blocked in a consumer group are
|
||||
* always blocked for the ">" ID: we need to deliver
|
||||
* only new messages and avoid unblocking the client
|
||||
* otherwise. */
|
||||
streamCG *group = NULL;
|
||||
if (receiver->bpop.xread_group) {
|
||||
group = streamLookupCG(s,
|
||||
szFromObj(receiver->bpop.xread_group));
|
||||
/* If the group was not found, send an error
|
||||
* to the consumer. */
|
||||
if (!group) {
|
||||
addReplyErrorAsync(receiver,
|
||||
"-NOGROUP the consumer group this client "
|
||||
"was blocked on no longer exists");
|
||||
unblockClient(receiver);
|
||||
continue;
|
||||
} else {
|
||||
*gt = group->last_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (streamCompareID(&s->last_id, gt) > 0) {
|
||||
streamID start = *gt;
|
||||
streamIncrID(&start);
|
||||
|
||||
/* Lookup the consumer for the group, if any. */
|
||||
streamConsumer *consumer = NULL;
|
||||
int noack = 0;
|
||||
|
||||
if (group) {
|
||||
consumer =
|
||||
streamLookupConsumer(group,
|
||||
szFromObj(receiver->bpop.xread_consumer),
|
||||
SLC_NONE);
|
||||
noack = receiver->bpop.xread_group_noack;
|
||||
}
|
||||
|
||||
/* Emit the two elements sub-array consisting of
|
||||
* the name of the stream and the data we
|
||||
* extracted from it. Wrapped in a single-item
|
||||
* array, since we have just one key. */
|
||||
if (receiver->resp == 2) {
|
||||
addReplyArrayLenAsync(receiver,1);
|
||||
addReplyArrayLenAsync(receiver,2);
|
||||
} else {
|
||||
addReplyMapLenAsync(receiver,1);
|
||||
}
|
||||
addReplyBulkAsync(receiver,rl->key);
|
||||
|
||||
streamPropInfo pi = {
|
||||
rl->key,
|
||||
receiver->bpop.xread_group
|
||||
};
|
||||
streamReplyWithRange(receiver,s,&start,NULL,
|
||||
receiver->bpop.xread_count,
|
||||
0, group, consumer, noack, &pi);
|
||||
|
||||
/* Note that after we unblock the client, 'gt'
|
||||
* and other receiver->bpop stuff are no longer
|
||||
* valid, so we must do the setup above before
|
||||
* this call. */
|
||||
unblockClient(receiver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function for handleClientsBlockedOnKeys(). This function is called
|
||||
* in order to check if we can serve clients blocked by modules using
|
||||
* RM_BlockClientOnKeys(), when the corresponding key was signaled as ready:
|
||||
* our goal here is to call the RedisModuleBlockedClient reply() callback to
|
||||
* see if the key is really able to serve the client, and in that case,
|
||||
* unblock it. */
|
||||
void serveClientsBlockedOnKeyByModule(readyList *rl) {
|
||||
dictEntry *de;
|
||||
|
||||
/* 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);
|
||||
if (de) {
|
||||
list *clients = (list*)dictGetVal(de);
|
||||
int numclients = listLength(clients);
|
||||
|
||||
while(numclients--) {
|
||||
listNode *clientnode = listFirst(clients);
|
||||
client *receiver = (client*)clientnode->value;
|
||||
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again: clients here may not be
|
||||
* 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. */
|
||||
listRotateHeadToTail(clients);
|
||||
|
||||
if (receiver->btype != BLOCKED_MODULE) continue;
|
||||
|
||||
/* Note that if *this* client cannot be served by this key,
|
||||
* it does not mean that another client that is next into the
|
||||
* list cannot be served as well: they may be blocked by
|
||||
* different modules with different triggers to consider if a key
|
||||
* is ready or not. This means we can't exit the loop but need
|
||||
* to continue after the first failure. */
|
||||
if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue;
|
||||
|
||||
moduleUnblockClient(receiver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This function should be called by Redis every time a single command,
|
||||
* a MULTI/EXEC block, or a Lua script, terminated its execution after
|
||||
* being called by a client. It handles serving clients blocked in
|
||||
@@ -288,215 +512,32 @@ void handleClientsBlockedOnKeys(void) {
|
||||
* we can safely call signalKeyAsReady() against this key. */
|
||||
dictDelete(rl->db->ready_keys,rl->key);
|
||||
|
||||
/* Even if we are not inside call(), increment the call depth
|
||||
* in order to make sure that keys are expired against a fixed
|
||||
* reference time, and not against the wallclock time. This
|
||||
* way we can lookup an object multiple times (BRPOPLPUSH does
|
||||
* that) without the risk of it being freed in the second
|
||||
* lookup, invalidating the first one.
|
||||
* See https://github.com/antirez/redis/pull/6554. */
|
||||
serverTL->fixed_time_expire++;
|
||||
updateCachedTime(0);
|
||||
|
||||
/* Serve clients blocked on list key. */
|
||||
robj *o = lookupKeyWrite(rl->db,rl->key);
|
||||
if (o != NULL && o->type == OBJ_LIST) {
|
||||
dictEntry *de;
|
||||
|
||||
/* 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);
|
||||
if (de) {
|
||||
list *clients = (list*)dictGetVal(de);
|
||||
int numclients = listLength(clients);
|
||||
|
||||
while(numclients--) {
|
||||
listNode *clientnode = listFirst(clients);
|
||||
client *receiver = (client*)clientnode->value;
|
||||
std::unique_lock<decltype(client::lock)> lock(receiver->lock);
|
||||
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
|
||||
robj *dstkey = receiver->bpop.target;
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == blpopCommand) ?
|
||||
LIST_HEAD : LIST_TAIL;
|
||||
robj *value = listTypePop(o,where);
|
||||
|
||||
if (value) {
|
||||
/* Protect receiver->bpop.target, that will be
|
||||
* freed by the next unblockClient()
|
||||
* call. */
|
||||
if (dstkey) incrRefCount(dstkey);
|
||||
fastlock_lock(&receiver->lock);
|
||||
unblockClient(receiver);
|
||||
|
||||
if (serveClientBlockedOnList(receiver,
|
||||
rl->key,dstkey,rl->db,value,
|
||||
where) == C_ERR)
|
||||
{
|
||||
/* If we failed serving the client we need
|
||||
* to also undo the POP operation. */
|
||||
listTypePush(o,value,where);
|
||||
}
|
||||
|
||||
if (dstkey) decrRefCount(dstkey);
|
||||
fastlock_unlock(&receiver->lock);
|
||||
decrRefCount(value);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (listTypeLength(o) == 0) {
|
||||
dbDelete(rl->db,rl->key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",rl->key,rl->db->id);
|
||||
}
|
||||
/* We don't call signalModifiedKey() as it was already called
|
||||
* when an element was pushed on the list. */
|
||||
}
|
||||
|
||||
/* Serve clients blocked on sorted set key. */
|
||||
else if (o != NULL && o->type == OBJ_ZSET) {
|
||||
dictEntry *de;
|
||||
|
||||
/* 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);
|
||||
if (de) {
|
||||
list *clients = (list*)dictGetVal(de);
|
||||
int numclients = listLength(clients);
|
||||
unsigned long zcard = zsetLength(o);
|
||||
|
||||
while(numclients-- && zcard) {
|
||||
listNode *clientnode = listFirst(clients);
|
||||
client *receiver = (client*)clientnode->value;
|
||||
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
|
||||
fastlock_lock(&receiver->lock);
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == bzpopminCommand)
|
||||
? ZSET_MIN : ZSET_MAX;
|
||||
unblockClient(receiver);
|
||||
genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
|
||||
zcard--;
|
||||
|
||||
/* Replicate the command. */
|
||||
robj *argv[2];
|
||||
struct redisCommand *cmd = where == ZSET_MIN ?
|
||||
cserver.zpopminCommand :
|
||||
cserver.zpopmaxCommand;
|
||||
argv[0] = createStringObject(cmd->name,strlen(cmd->name));
|
||||
argv[1] = rl->key;
|
||||
incrRefCount(rl->key);
|
||||
propagate(cmd,receiver->db->id,
|
||||
argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
fastlock_unlock(&receiver->lock);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Serve clients blocked on stream key. */
|
||||
else if (o != NULL && o->type == OBJ_STREAM) {
|
||||
dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
stream *s = (stream*)ptrFromObj(o);
|
||||
|
||||
/* We need to provide the new data arrived on the stream
|
||||
* to all the clients that are waiting for an offset smaller
|
||||
* than the current top item. */
|
||||
if (de) {
|
||||
list *clients = (list*)dictGetVal(de);
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
listRewind(clients,&li);
|
||||
|
||||
while((ln = listNext(&li))) {
|
||||
client *receiver = (client*)listNodeValue(ln);
|
||||
if (receiver->btype != BLOCKED_STREAM) continue;
|
||||
streamID *gt = (streamID*)dictFetchValue(receiver->bpop.keys,
|
||||
rl->key);
|
||||
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and update the
|
||||
* last ID the client is blocked for: this is needed
|
||||
* because serving other clients in the same consumer
|
||||
* group will alter the "last ID" of the consumer
|
||||
* group, and clients blocked in a consumer group are
|
||||
* always blocked for the ">" ID: we need to deliver
|
||||
* only new messages and avoid unblocking the client
|
||||
* otherwise. */
|
||||
streamCG *group = NULL;
|
||||
if (receiver->bpop.xread_group) {
|
||||
group = streamLookupCG(s,
|
||||
szFromObj(receiver->bpop.xread_group));
|
||||
/* If the group was not found, send an error
|
||||
* to the consumer. */
|
||||
if (!group) {
|
||||
fastlock_lock(&receiver->lock);
|
||||
addReplyErrorAsync(receiver,
|
||||
"-NOGROUP the consumer group this client "
|
||||
"was blocked on no longer exists");
|
||||
unblockClient(receiver);
|
||||
fastlock_unlock(&receiver->lock);
|
||||
continue;
|
||||
} else {
|
||||
*gt = group->last_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (streamCompareID(&s->last_id, gt) > 0) {
|
||||
streamID start = *gt;
|
||||
start.seq++; /* Can't overflow, it's an uint64_t */
|
||||
|
||||
/* Lookup the consumer for the group, if any. */
|
||||
streamConsumer *consumer = NULL;
|
||||
int noack = 0;
|
||||
|
||||
if (group) {
|
||||
consumer = streamLookupConsumer(group,
|
||||
szFromObj(receiver->bpop.xread_consumer),
|
||||
1);
|
||||
noack = receiver->bpop.xread_group_noack;
|
||||
}
|
||||
|
||||
fastlock_lock(&receiver->lock);
|
||||
|
||||
/* Emit the two elements sub-array consisting of
|
||||
* the name of the stream and the data we
|
||||
* extracted from it. Wrapped in a single-item
|
||||
* array, since we have just one key. */
|
||||
if (receiver->resp == 2) {
|
||||
addReplyArrayLenAsync(receiver,1);
|
||||
addReplyArrayLenAsync(receiver,2);
|
||||
} else {
|
||||
addReplyMapLenAsync(receiver,1);
|
||||
}
|
||||
addReplyBulkAsync(receiver,rl->key);
|
||||
|
||||
streamPropInfo pi = {
|
||||
rl->key,
|
||||
receiver->bpop.xread_group
|
||||
};
|
||||
streamReplyWithRange(receiver,s,&start,NULL,
|
||||
receiver->bpop.xread_count,
|
||||
0, group, consumer, noack, &pi);
|
||||
|
||||
/* Note that after we unblock the client, 'gt'
|
||||
* and other receiver->bpop stuff are no longer
|
||||
* valid, so we must do the setup above before
|
||||
* this call. */
|
||||
unblockClient(receiver);
|
||||
fastlock_unlock(&receiver->lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (o != NULL) {
|
||||
if (o->type == OBJ_LIST)
|
||||
serveClientsBlockedOnListKey(o,rl);
|
||||
else if (o->type == OBJ_ZSET)
|
||||
serveClientsBlockedOnSortedSetKey(o,rl);
|
||||
else if (o->type == OBJ_STREAM)
|
||||
serveClientsBlockedOnStreamKey(o,rl);
|
||||
/* We want to serve clients blocked on module keys
|
||||
* regardless of the object type: we don't know what the
|
||||
* module is trying to accomplish right now. */
|
||||
serveClientsBlockedOnKeyByModule(rl);
|
||||
}
|
||||
serverTL->fixed_time_expire--;
|
||||
|
||||
/* Free this item. */
|
||||
decrRefCount(rl->key);
|
||||
@@ -545,17 +586,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), MALLOC_SHARED);
|
||||
memcpy(key_data,ids+j,sizeof(streamID));
|
||||
}
|
||||
/* Allocate our bkinfo structure, associated to each key the client
|
||||
* is blocked for. */
|
||||
bkinfo *bki = (bkinfo*)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]);
|
||||
@@ -574,6 +613,7 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
|
||||
l = (list*)dictGetVal(de);
|
||||
}
|
||||
listAddNodeTail(l,c);
|
||||
bki->listnode = listLast(l);
|
||||
}
|
||||
blockClient(c,btype);
|
||||
}
|
||||
@@ -590,11 +630,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 = (robj*)dictGetKey(de);
|
||||
bkinfo *bki = (bkinfo*)dictGetVal(de);
|
||||
|
||||
/* Remove this client from the list of clients waiting for this key. */
|
||||
l = (list*)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);
|
||||
@@ -621,7 +662,7 @@ void unblockClientWaitingData(client *c) {
|
||||
* the same key again and again in the list in case of multiple pushes
|
||||
* made by a script or in the context of MULTI/EXEC.
|
||||
*
|
||||
* The list will be finally processed by handleClientsBlockedOnLists() */
|
||||
* The list will be finally processed by handleClientsBlockedOnKeys() */
|
||||
void signalKeyAsReady(redisDb *db, robj *key) {
|
||||
readyList *rl;
|
||||
|
||||
|
||||
@@ -80,6 +80,8 @@ void receiveChildInfo(void) {
|
||||
g_pserver->stat_rdb_cow_bytes = g_pserver->child_info_data.cow_size;
|
||||
} else if (g_pserver->child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
|
||||
g_pserver->stat_aof_cow_bytes = g_pserver->child_info_data.cow_size;
|
||||
} else if (g_pserver->child_info_data.process_type == CHILD_INFO_TYPE_MODULE) {
|
||||
g_pserver->stat_module_cow_bytes = g_pserver->child_info_data.cow_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
+316
-237
File diff suppressed because it is too large.
Load diff
+3
-6
@@ -17,15 +17,10 @@ extern "C" {
|
||||
|
||||
/* The following defines are amount of time, sometimes expressed as
|
||||
* multiplicators of the node timeout value (when ending with MULT). */
|
||||
#define CLUSTER_DEFAULT_NODE_TIMEOUT 15000
|
||||
#define CLUSTER_DEFAULT_SLAVE_VALIDITY 10 /* Slave max data age factor. */
|
||||
#define CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE 1
|
||||
#define CLUSTER_DEFAULT_SLAVE_NO_FAILOVER 0 /* Failover by default. */
|
||||
#define CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */
|
||||
#define CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if master is back. */
|
||||
#define CLUSTER_FAIL_UNDO_TIME_ADD 10 /* Some additional time. */
|
||||
#define CLUSTER_FAILOVER_DELAY 5 /* Seconds */
|
||||
#define CLUSTER_DEFAULT_MIGRATION_BARRIER 1
|
||||
#define CLUSTER_MF_TIMEOUT 5000 /* Milliseconds to do a manual failover. */
|
||||
#define CLUSTER_MF_PAUSE_MULT 2 /* Master pause manual failover mult. */
|
||||
#define CLUSTER_SLAVE_MIGRATION_DELAY 5000 /* Delay for slave migration. */
|
||||
@@ -38,13 +33,14 @@ extern "C" {
|
||||
#define CLUSTER_REDIR_MOVED 4 /* -MOVED redirection required. */
|
||||
#define CLUSTER_REDIR_DOWN_STATE 5 /* -CLUSTERDOWN, global state. */
|
||||
#define CLUSTER_REDIR_DOWN_UNBOUND 6 /* -CLUSTERDOWN, unbound slot. */
|
||||
#define CLUSTER_REDIR_DOWN_RO_STATE 7 /* -CLUSTERDOWN, allow reads. */
|
||||
|
||||
struct clusterNode;
|
||||
|
||||
/* clusterLink encapsulates everything needed to talk with a remote node. */
|
||||
typedef struct clusterLink {
|
||||
mstime_t ctime; /* Link creation time */
|
||||
int fd; /* TCP socket file descriptor */
|
||||
connection *conn; /* Connection to remote node */
|
||||
sds sndbuf; /* Packet send buffer */
|
||||
sds rcvbuf; /* Packet reception buffer */
|
||||
struct clusterNode *node; /* Node related to this link if any, or NULL */
|
||||
@@ -132,6 +128,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 */
|
||||
|
||||
+910
-825
File diff suppressed because it is too large.
Load diff
@@ -232,4 +232,37 @@ 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(), name, NULL)
|
||||
#else
|
||||
#if (defined __APPLE__ && defined(MAC_OS_X_VERSION_10_7))
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
#endif
|
||||
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__)
|
||||
#define USE_SETCPUAFFINITY
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
#endif
|
||||
void setcpuaffinity(const char *cpulist);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,431 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Redis Labs
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "connhelpers.h"
|
||||
|
||||
/* The connections module provides a lean abstraction of network connections
|
||||
* to avoid direct socket and async event management across the Redis code base.
|
||||
*
|
||||
* It does NOT provide advanced connection features commonly found in similar
|
||||
* libraries such as complete in/out buffer management, throttling, etc. These
|
||||
* functions remain in networking.c.
|
||||
*
|
||||
* The primary goal is to allow transparent handling of TCP and TLS based
|
||||
* connections. To do so, connections have the following properties:
|
||||
*
|
||||
* 1. A connection may live before its corresponding socket exists. This
|
||||
* allows various context and configuration setting to be handled before
|
||||
* establishing the actual connection.
|
||||
* 2. The caller may register/unregister logical read/write handlers to be
|
||||
* called when the connection has data to read from/can accept writes.
|
||||
* These logical handlers may or may not correspond to actual AE events,
|
||||
* depending on the implementation (for TCP they are; for TLS they aren't).
|
||||
*/
|
||||
|
||||
extern ConnectionType CT_Socket;
|
||||
|
||||
/* When a connection is created we must know its type already, but the
|
||||
* underlying socket may or may not exist:
|
||||
*
|
||||
* - For accepted connections, it exists as we do not model the listen/accept
|
||||
* part; So caller calls connCreateSocket() followed by connAccept().
|
||||
* - For outgoing connections, the socket is created by the connection module
|
||||
* itself; So caller calls connCreateSocket() followed by connConnect(),
|
||||
* which registers a connect callback that fires on connected/error state
|
||||
* (and after any transport level handshake was done).
|
||||
*
|
||||
* NOTE: An earlier version relied on connections being part of other structs
|
||||
* and not independently allocated. This could lead to further optimizations
|
||||
* like using container_of(), etc. However it was discontinued in favor of
|
||||
* this approach for these reasons:
|
||||
*
|
||||
* 1. In some cases conns are created/handled outside the context of the
|
||||
* containing struct, in which case it gets a bit awkward to copy them.
|
||||
* 2. Future implementations may wish to allocate arbitrary data for the
|
||||
* connection.
|
||||
* 3. The container_of() approach is anyway risky because connections may
|
||||
* be embedded in different structs, not just client.
|
||||
*/
|
||||
|
||||
connection *connCreateSocket() {
|
||||
connection *conn = (connection*)zcalloc(sizeof(connection), MALLOC_LOCAL);
|
||||
conn->type = &CT_Socket;
|
||||
conn->fd = -1;
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
/* 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
|
||||
* invoked the connection-level accept handler.
|
||||
*/
|
||||
connection *connCreateAcceptedSocket(int fd) {
|
||||
connection *conn = connCreateSocket();
|
||||
conn->fd = fd;
|
||||
conn->state = CONN_STATE_ACCEPTING;
|
||||
return conn;
|
||||
}
|
||||
|
||||
static int connSocketConnect(connection *conn, const char *addr, int port, const char *src_addr,
|
||||
ConnectionCallbackFunc connect_handler) {
|
||||
int fd = anetTcpNonBlockBestEffortBindConnect(NULL,addr,port,src_addr);
|
||||
if (fd == -1) {
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
conn->last_errno = errno;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
conn->fd = fd;
|
||||
conn->state = CONN_STATE_CONNECTING;
|
||||
|
||||
conn->conn_handler = connect_handler;
|
||||
aeCreateFileEvent(serverTL->el, conn->fd, AE_WRITABLE,
|
||||
conn->type->ae_handler, conn);
|
||||
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Returns true if a write handler is registered */
|
||||
int connHasWriteHandler(connection *conn) {
|
||||
return conn->write_handler != NULL;
|
||||
}
|
||||
|
||||
/* Returns true if a read handler is registered */
|
||||
int connHasReadHandler(connection *conn) {
|
||||
return conn->read_handler != NULL;
|
||||
}
|
||||
|
||||
/* Associate a private data pointer with the connection */
|
||||
void connSetPrivateData(connection *conn, void *data) {
|
||||
conn->private_data = data;
|
||||
}
|
||||
|
||||
/* Get the associated private data pointer */
|
||||
void *connGetPrivateData(connection *conn) {
|
||||
return conn->private_data;
|
||||
}
|
||||
|
||||
/* ------ Pure socket connections ------- */
|
||||
|
||||
/* A very incomplete list of implementation-specific calls. Much of the above shall
|
||||
* move here as we implement additional connection types.
|
||||
*/
|
||||
|
||||
/* Close the connection and free resources. */
|
||||
static void connSocketClose(connection *conn) {
|
||||
if (conn->fd != -1) {
|
||||
aeDeleteFileEvent(serverTL->el,conn->fd,AE_READABLE);
|
||||
aeDeleteFileEvent(serverTL->el,conn->fd,AE_WRITABLE);
|
||||
close(conn->fd);
|
||||
conn->fd = -1;
|
||||
}
|
||||
|
||||
/* If called from within a handler, schedule the close but
|
||||
* keep the connection until the handler returns.
|
||||
*/
|
||||
if (connHasRefs(conn)) {
|
||||
conn->flags |= CONN_FLAG_CLOSE_SCHEDULED;
|
||||
return;
|
||||
}
|
||||
|
||||
zfree(conn);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
|
||||
int ret = read(conn->fd, buf, buf_len);
|
||||
if (!ret) {
|
||||
conn->state = CONN_STATE_CLOSED;
|
||||
} else if (ret < 0 && errno != EAGAIN) {
|
||||
conn->last_errno = errno;
|
||||
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;
|
||||
|
||||
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.
|
||||
* If NULL, the existing handler is removed.
|
||||
*
|
||||
* The barrier flag indicates a write barrier is requested, resulting with
|
||||
* CONN_FLAG_WRITE_BARRIER set. This will ensure that the write handler is
|
||||
* always called before and not after the read handler in a single event
|
||||
* loop.
|
||||
*/
|
||||
static int connSocketSetWriteHandler(connection *conn, ConnectionCallbackFunc func, int barrier, bool fThreadSafe) {
|
||||
if (func == conn->write_handler) return C_OK;
|
||||
|
||||
conn->write_handler = func;
|
||||
if (barrier)
|
||||
conn->flags |= CONN_FLAG_WRITE_BARRIER;
|
||||
else
|
||||
conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
|
||||
|
||||
int flags = AE_WRITABLE;
|
||||
if (fThreadSafe)
|
||||
flags |= AE_WRITE_THREADSAFE;
|
||||
|
||||
if (!conn->write_handler)
|
||||
aeDeleteFileEvent(serverTL->el,conn->fd,AE_WRITABLE);
|
||||
else
|
||||
if (aeCreateFileEvent(serverTL->el,conn->fd,flags,
|
||||
conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Register a read handler, to be called when the connection is readable.
|
||||
* If NULL, the existing handler is removed.
|
||||
*/
|
||||
static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc func, bool fThreadSafe) {
|
||||
if (func == conn->read_handler) return C_OK;
|
||||
|
||||
int flags = AE_READABLE;
|
||||
if (fThreadSafe)
|
||||
flags |= AE_READ_THREADSAFE;
|
||||
|
||||
conn->read_handler = func;
|
||||
if (!conn->read_handler)
|
||||
aeDeleteFileEvent(serverTL->el,conn->fd,AE_READABLE);
|
||||
else
|
||||
if (aeCreateFileEvent(serverTL->el,conn->fd,
|
||||
flags,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
static const char *connSocketGetLastError(connection *conn) {
|
||||
return strerror(conn->last_errno);
|
||||
}
|
||||
|
||||
static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask)
|
||||
{
|
||||
UNUSED(el);
|
||||
UNUSED(fd);
|
||||
connection *conn = (connection*)clientData;
|
||||
|
||||
if (conn->state == CONN_STATE_CONNECTING &&
|
||||
(mask & AE_WRITABLE) && conn->conn_handler) {
|
||||
|
||||
if (connGetSocketError(conn)) {
|
||||
conn->last_errno = errno;
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
} else {
|
||||
conn->state = CONN_STATE_CONNECTED;
|
||||
}
|
||||
|
||||
if (!conn->write_handler) aeDeleteFileEvent(serverTL->el,conn->fd,AE_WRITABLE);
|
||||
|
||||
if (!callHandler(conn, conn->conn_handler)) return;
|
||||
conn->conn_handler = NULL;
|
||||
}
|
||||
|
||||
/* Normally we execute the readable event first, and the writable
|
||||
* event later. This is useful as sometimes we may be able
|
||||
* to serve the reply of a query immediately after processing the
|
||||
* query.
|
||||
*
|
||||
* However if WRITE_BARRIER is set in the mask, our application is
|
||||
* 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 fsync'ing a file to disk,
|
||||
* before replying to a client. */
|
||||
int invert = conn->flags & CONN_FLAG_WRITE_BARRIER;
|
||||
|
||||
int call_write = (mask & AE_WRITABLE) && conn->write_handler;
|
||||
int call_read = (mask & AE_READABLE) && conn->read_handler;
|
||||
|
||||
/* Handle normal I/O flows */
|
||||
if (!invert && call_read) {
|
||||
if (!callHandler(conn, conn->read_handler)) return;
|
||||
}
|
||||
/* Fire the writable event. */
|
||||
if (call_write) {
|
||||
if (!callHandler(conn, conn->write_handler)) return;
|
||||
}
|
||||
/* If we have to invert the call, fire the readable event now
|
||||
* after the writable one. */
|
||||
if (invert && call_read) {
|
||||
if (!callHandler(conn, conn->read_handler)) return;
|
||||
}
|
||||
}
|
||||
|
||||
static int connSocketBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
|
||||
int fd = anetTcpNonBlockConnect(NULL,addr,port);
|
||||
if (fd == -1) {
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
conn->last_errno = errno;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if ((aeWait(fd, AE_WRITABLE, timeout) & AE_WRITABLE) == 0) {
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
conn->last_errno = ETIMEDOUT;
|
||||
}
|
||||
|
||||
conn->fd = fd;
|
||||
conn->state = CONN_STATE_CONNECTED;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Connection-based versions of syncio.c functions.
|
||||
* NOTE: This should ideally be refactored out in favor of pure async work.
|
||||
*/
|
||||
|
||||
static ssize_t connSocketSyncWrite(connection *conn, const char *ptr, ssize_t size, long long timeout) {
|
||||
return syncWrite(conn->fd, ptr, size, timeout);
|
||||
}
|
||||
|
||||
static ssize_t connSocketSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||
return syncRead(conn->fd, ptr, size, timeout);
|
||||
}
|
||||
|
||||
static ssize_t connSocketSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||
return syncReadLine(conn->fd, ptr, size, timeout);
|
||||
}
|
||||
|
||||
|
||||
ConnectionType CT_Socket = {
|
||||
connSocketEventHandler,
|
||||
connSocketConnect,
|
||||
connSocketWrite,
|
||||
connSocketRead,
|
||||
connSocketClose,
|
||||
connSocketAccept,
|
||||
connSocketSetWriteHandler,
|
||||
connSocketSetReadHandler,
|
||||
connSocketGetLastError,
|
||||
connSocketBlockingConnect,
|
||||
connSocketSyncWrite,
|
||||
connSocketSyncRead,
|
||||
connSocketSyncReadLine
|
||||
};
|
||||
|
||||
|
||||
int connGetSocketError(connection *conn) {
|
||||
int sockerr = 0;
|
||||
socklen_t errlen = sizeof(sockerr);
|
||||
|
||||
if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
|
||||
sockerr = errno;
|
||||
return sockerr;
|
||||
}
|
||||
|
||||
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
|
||||
return anetPeerToString(conn ? conn->fd : -1, ip, ip_len, port);
|
||||
}
|
||||
|
||||
int connFormatPeer(connection *conn, char *buf, size_t buf_len) {
|
||||
return anetFormatPeer(conn ? conn->fd : -1, buf, buf_len);
|
||||
}
|
||||
|
||||
int connSockName(connection *conn, char *ip, size_t ip_len, int *port) {
|
||||
return anetSockName(conn->fd, ip, ip_len, port);
|
||||
}
|
||||
|
||||
int connBlock(connection *conn) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetBlock(NULL, conn->fd);
|
||||
}
|
||||
|
||||
int connNonBlock(connection *conn) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetNonBlock(NULL, conn->fd);
|
||||
}
|
||||
|
||||
int connEnableTcpNoDelay(connection *conn) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetEnableTcpNoDelay(NULL, conn->fd);
|
||||
}
|
||||
|
||||
int connDisableTcpNoDelay(connection *conn) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetDisableTcpNoDelay(NULL, conn->fd);
|
||||
}
|
||||
|
||||
int connKeepAlive(connection *conn, int interval) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetKeepAlive(NULL, conn->fd, interval);
|
||||
}
|
||||
|
||||
int connSendTimeout(connection *conn, long long ms) {
|
||||
return anetSendTimeout(NULL, conn->fd, ms);
|
||||
}
|
||||
|
||||
int connRecvTimeout(connection *conn, long long ms) {
|
||||
return anetRecvTimeout(NULL, conn->fd, ms);
|
||||
}
|
||||
|
||||
int connGetState(connection *conn) {
|
||||
return conn->state;
|
||||
}
|
||||
|
||||
void connSetThreadAffinity(connection *conn, int cpu) {
|
||||
#ifdef HAVE_SO_INCOMING_CPU
|
||||
if (setsockopt(conn->fd, SOL_SOCKET, SO_INCOMING_CPU, &cpu, sizeof(cpu)) != 0)
|
||||
{
|
||||
serverLog(LL_WARNING, "Failed to set socket affinity");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Return a text that describes the connection, suitable for inclusion
|
||||
* in CLIENT LIST and similar outputs.
|
||||
*
|
||||
* For sockets, we always return "fd=<fdnum>" to maintain compatibility.
|
||||
*/
|
||||
const char *connGetInfo(connection *conn, char *buf, size_t buf_len) {
|
||||
snprintf(buf, buf_len-1, "fd=%i", conn->fd);
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019, Redis Labs
|
||||
* 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 __REDIS_CONNECTION_H
|
||||
#define __REDIS_CONNECTION_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#define CONN_INFO_LEN 32
|
||||
|
||||
struct aeEventLoop;
|
||||
typedef struct connection connection;
|
||||
|
||||
typedef enum {
|
||||
CONN_STATE_NONE = 0,
|
||||
CONN_STATE_CONNECTING,
|
||||
CONN_STATE_ACCEPTING,
|
||||
CONN_STATE_CONNECTED,
|
||||
CONN_STATE_CLOSED,
|
||||
CONN_STATE_ERROR
|
||||
} ConnectionState;
|
||||
|
||||
#define CONN_FLAG_CLOSE_SCHEDULED (1<<0) /* Closed scheduled by a handler */
|
||||
#define CONN_FLAG_WRITE_BARRIER (1<<1) /* Write barrier requested */
|
||||
#define CONN_FLAG_READ_THREADSAFE (1<<2)
|
||||
#define CONN_FLAG_WRITE_THREADSAFE (1<<3)
|
||||
|
||||
typedef void (*ConnectionCallbackFunc)(struct connection *conn);
|
||||
|
||||
typedef struct ConnectionType {
|
||||
void (*ae_handler)(struct aeEventLoop *el, int fd, void *clientData, int mask);
|
||||
int (*connect)(struct connection *conn, const char *addr, int port, const char *source_addr, ConnectionCallbackFunc connect_handler);
|
||||
int (*write)(struct connection *conn, const void *data, size_t data_len);
|
||||
int (*read)(struct connection *conn, void *buf, size_t buf_len);
|
||||
void (*close)(struct connection *conn);
|
||||
int (*accept)(struct connection *conn, ConnectionCallbackFunc accept_handler);
|
||||
int (*set_write_handler)(struct connection *conn, ConnectionCallbackFunc handler, int barrier, bool fThreadSafe);
|
||||
int (*set_read_handler)(struct connection *conn, ConnectionCallbackFunc handler, bool fThreadSafe);
|
||||
const char *(*get_last_error)(struct connection *conn);
|
||||
int (*blocking_connect)(struct connection *conn, const char *addr, int port, long long timeout);
|
||||
ssize_t (*sync_write)(struct connection *conn, const 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);
|
||||
void (*marshal_thread)(struct connection *conn);
|
||||
} ConnectionType;
|
||||
|
||||
struct connection {
|
||||
ConnectionType *type;
|
||||
std::atomic<ConnectionState> state;
|
||||
short int flags;
|
||||
short int refs;
|
||||
int last_errno;
|
||||
void *private_data;
|
||||
ConnectionCallbackFunc conn_handler;
|
||||
ConnectionCallbackFunc write_handler;
|
||||
ConnectionCallbackFunc read_handler;
|
||||
int fd;
|
||||
};
|
||||
|
||||
/* The connection module does not deal with listening and accepting sockets,
|
||||
* so we assume we have a socket when an incoming connection is created.
|
||||
*
|
||||
* The fd supplied should therefore be associated with an already accept()ed
|
||||
* socket.
|
||||
*
|
||||
* 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) {
|
||||
return conn->type->accept(conn, accept_handler);
|
||||
}
|
||||
|
||||
/* Establish a connection. The connect_handler will be called when the connection
|
||||
* is established, or if an error has occured.
|
||||
*
|
||||
* The connection handler will be responsible to set up any read/write handlers
|
||||
* as needed.
|
||||
*
|
||||
* If C_ERR is returned, the operation failed and the connection handler shall
|
||||
* not be expected.
|
||||
*/
|
||||
static inline int connConnect(connection *conn, const char *addr, int port, const char *src_addr,
|
||||
ConnectionCallbackFunc connect_handler) {
|
||||
return conn->type->connect(conn, addr, port, src_addr, connect_handler);
|
||||
}
|
||||
|
||||
/* Blocking connect.
|
||||
*
|
||||
* NOTE: This is implemented in order to simplify the transition to the abstract
|
||||
* connections, but should probably be refactored out of cluster.c and replication.c,
|
||||
* in favor of a pure async implementation.
|
||||
*/
|
||||
static inline int connBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
|
||||
return conn->type->blocking_connect(conn, addr, port, timeout);
|
||||
}
|
||||
|
||||
/* Write to connection, behaves the same as write(2).
|
||||
*
|
||||
* Like write(2), a short write is possible. A -1 return indicates an error.
|
||||
*
|
||||
* The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
|
||||
* connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
|
||||
*/
|
||||
static inline int connWrite(connection *conn, const void *data, size_t data_len) {
|
||||
return conn->type->write(conn, data, data_len);
|
||||
}
|
||||
|
||||
/* Read from the connection, behaves the same as read(2).
|
||||
*
|
||||
* Like read(2), a short read is possible. A return value of 0 will indicate the
|
||||
* connection was closed, and -1 will indicate an error.
|
||||
*
|
||||
* The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
|
||||
* connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
|
||||
*/
|
||||
static inline int connRead(connection *conn, void *buf, size_t buf_len) {
|
||||
return conn->type->read(conn, buf, buf_len);
|
||||
}
|
||||
|
||||
/* Register a write handler, to be called when the connection is writable.
|
||||
* If NULL, the existing handler is removed.
|
||||
*/
|
||||
static inline int connSetWriteHandler(connection *conn, ConnectionCallbackFunc func, bool fThreadSafe = false) {
|
||||
return conn->type->set_write_handler(conn, func, 0, fThreadSafe);
|
||||
}
|
||||
|
||||
/* Register a read handler, to be called when the connection is readable.
|
||||
* If NULL, the existing handler is removed.
|
||||
*/
|
||||
static inline int connSetReadHandler(connection *conn, ConnectionCallbackFunc func, bool fThreadSafe = false) {
|
||||
return conn->type->set_read_handler(conn, func, fThreadSafe);
|
||||
}
|
||||
|
||||
/* 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
|
||||
* 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, bool fThreadSafe = false) {
|
||||
return conn->type->set_write_handler(conn, func, barrier, fThreadSafe);
|
||||
}
|
||||
|
||||
static inline void connClose(connection *conn) {
|
||||
conn->type->close(conn);
|
||||
}
|
||||
|
||||
/* Returns the last error encountered by the connection, as a string. If no error,
|
||||
* a NULL is returned.
|
||||
*/
|
||||
static inline const char *connGetLastError(connection *conn) {
|
||||
return conn->type->get_last_error(conn);
|
||||
}
|
||||
|
||||
static inline ssize_t connSyncWrite(connection *conn, const char *ptr, ssize_t size, long long timeout) {
|
||||
return conn->type->sync_write(conn, ptr, size, timeout);
|
||||
}
|
||||
|
||||
static inline ssize_t connSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||
return conn->type->sync_read(conn, ptr, size, timeout);
|
||||
}
|
||||
|
||||
static inline ssize_t connSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||
return conn->type->sync_readline(conn, ptr, size, timeout);
|
||||
}
|
||||
|
||||
static inline void connMarshalThread(connection *conn) {
|
||||
if (conn->type->marshal_thread != nullptr)
|
||||
conn->type->marshal_thread(conn);
|
||||
}
|
||||
|
||||
connection *connCreateSocket();
|
||||
connection *connCreateAcceptedSocket(int fd);
|
||||
|
||||
connection *connCreateTLS();
|
||||
connection *connCreateAcceptedTLS(int fd, int require_auth);
|
||||
|
||||
void connSetPrivateData(connection *conn, void *data);
|
||||
void *connGetPrivateData(connection *conn);
|
||||
int connGetState(connection *conn);
|
||||
int connHasWriteHandler(connection *conn);
|
||||
int connHasReadHandler(connection *conn);
|
||||
int connGetSocketError(connection *conn);
|
||||
void connSetThreadAffinity(connection *conn, int cpu);
|
||||
|
||||
/* anet-style wrappers to conns */
|
||||
int connBlock(connection *conn);
|
||||
int connNonBlock(connection *conn);
|
||||
int connEnableTcpNoDelay(connection *conn);
|
||||
int connDisableTcpNoDelay(connection *conn);
|
||||
int connKeepAlive(connection *conn, int interval);
|
||||
int connSendTimeout(connection *conn, long long ms);
|
||||
int connRecvTimeout(connection *conn, long long ms);
|
||||
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port);
|
||||
int connFormatPeer(connection *conn, char *buf, size_t buf_len);
|
||||
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 */
|
||||
int tlsHasPendingData();
|
||||
int tlsProcessPendingData();
|
||||
|
||||
#endif /* __REDIS_CONNECTION_H */
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019, Redis Labs
|
||||
* 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 __REDIS_CONNHELPERS_H
|
||||
#define __REDIS_CONNHELPERS_H
|
||||
|
||||
#include "connection.h"
|
||||
|
||||
/* These are helper functions that are common to different connection
|
||||
* implementations (currently sockets in connection.c and TLS in tls.c).
|
||||
*
|
||||
* Currently helpers implement the mechanisms for invoking connection
|
||||
* handlers and tracking connection references, to allow safe destruction
|
||||
* of connections from within a handler.
|
||||
*/
|
||||
|
||||
/* Incremenet connection references.
|
||||
*
|
||||
* 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 connIncrRefs(connection *conn) {
|
||||
conn->refs++;
|
||||
}
|
||||
|
||||
/* 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 void connDecrRefs(connection *conn) {
|
||||
conn->refs--;
|
||||
}
|
||||
|
||||
static inline int connHasRefs(connection *conn) {
|
||||
return conn->refs;
|
||||
}
|
||||
|
||||
/* Helper for connection implementations to call handlers:
|
||||
* 1. Increment refs to protect the connection.
|
||||
* 2. Execute the handler (if set).
|
||||
* 3. Decrement refs and perform deferred close, if refs==0.
|
||||
*/
|
||||
static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) {
|
||||
connIncrRefs(conn);
|
||||
if (handler) handler(conn);
|
||||
connDecrRefs(conn);
|
||||
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
|
||||
if (!connHasRefs(conn)) connClose(conn);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* __REDIS_CONNHELPERS_H */
|
||||
+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
|
||||
@@ -7,6 +7,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void crc64_init(void);
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* 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++) {
|
||||
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 & 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
|
||||
+14
-2
@@ -66,10 +66,11 @@ void cronCommand(client *c)
|
||||
spjob->vecargs.emplace_back(sdsdup(szFromObj(c->argv[i])));
|
||||
|
||||
robj *o = createObject(OBJ_CRON, spjob.release());
|
||||
setKey(c->db, c->argv[ARG_NAME], o);
|
||||
setKey(c, c->db, c->argv[ARG_NAME], o);
|
||||
decrRefCount(o);
|
||||
// use an expire to trigger execution. Note: We use a subkey expire here so legacy clients don't delete it.
|
||||
setExpire(c, c->db, c->argv[ARG_NAME], c->argv[ARG_NAME], base + interval);
|
||||
++g_pserver->dirty;
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
|
||||
@@ -78,7 +79,7 @@ void executeCronJobExpireHook(const char *key, robj *o)
|
||||
serverAssert(o->type == OBJ_CRON);
|
||||
cronjob *job = (cronjob*)ptrFromObj(o);
|
||||
|
||||
client *cFake = createClient(-1, IDX_EVENT_LOOP_MAIN);
|
||||
client *cFake = createClient(nullptr, IDX_EVENT_LOOP_MAIN);
|
||||
cFake->lock.lock();
|
||||
cFake->authenticated = 1;
|
||||
cFake->puser = nullptr;
|
||||
@@ -86,6 +87,7 @@ void executeCronJobExpireHook(const char *key, robj *o)
|
||||
serverAssert(cFake->argc == 0);
|
||||
|
||||
// Setup the args for the EVAL command
|
||||
cFake->cmd = lookupCommandByCString("EVAL");
|
||||
cFake->argc = 3 + job->veckeys.size() + job->vecargs.size();
|
||||
cFake->argv = (robj**)zmalloc(sizeof(robj*) * cFake->argc, MALLOC_LOCAL);
|
||||
cFake->argv[0] = createStringObject("EVAL", 4);
|
||||
@@ -96,7 +98,17 @@ void executeCronJobExpireHook(const char *key, robj *o)
|
||||
for (size_t i = 0; i < job->vecargs.size(); ++i)
|
||||
cFake->argv[3+job->veckeys.size()+i] = createStringObject(job->vecargs[i].get(), job->vecargs[i].size());
|
||||
|
||||
int lua_replicate_backup = g_pserver->lua_always_replicate_commands;
|
||||
g_pserver->lua_always_replicate_commands = 0;
|
||||
evalCommand(cFake);
|
||||
g_pserver->lua_always_replicate_commands = lua_replicate_backup;
|
||||
|
||||
if (g_pserver->aof_state != AOF_OFF)
|
||||
feedAppendOnlyFile(cFake->cmd,cFake->db->id,cFake->argv,cFake->argc);
|
||||
// Active replicas do their own expiries, do not propogate
|
||||
if (!g_pserver->fActiveReplica)
|
||||
replicationFeedSlaves(g_pserver->slaves,cFake->db->id,cFake->argv,cFake->argc);
|
||||
|
||||
resetClient(cFake);
|
||||
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
|
||||
+310
-87
@@ -65,6 +65,22 @@ void updateExpire(redisDb *db, sds key, robj *valOld, robj *valNew)
|
||||
return;
|
||||
}
|
||||
|
||||
void updateDbValAccess(dictEntry *de, int flags)
|
||||
{
|
||||
robj *val = (robj*)dictGetVal(de);
|
||||
|
||||
/* Update the access time for the ageing algorithm.
|
||||
* Don't do it if we have a saving child, as this will trigger
|
||||
* a copy on write madness. */
|
||||
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
updateLFU(val);
|
||||
} else {
|
||||
val->lru = LRU_CLOCK();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Low level key lookup API, not actually called directly from commands
|
||||
* implementations that should instead rely on lookupKeyRead(),
|
||||
@@ -74,19 +90,7 @@ static robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
if (de) {
|
||||
robj *val = (robj*)dictGetVal(de);
|
||||
|
||||
/* Update the access time for the ageing algorithm.
|
||||
* Don't do it if we have a saving child, as this will trigger
|
||||
* a copy on write madness. */
|
||||
if (g_pserver->rdb_child_pid == -1 &&
|
||||
g_pserver->aof_child_pid == -1 &&
|
||||
!(flags & LOOKUP_NOTOUCH))
|
||||
{
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
updateLFU(val);
|
||||
} else {
|
||||
val->lru = LRU_CLOCK();
|
||||
}
|
||||
}
|
||||
updateDbValAccess(de, flags);
|
||||
|
||||
if (flags & LOOKUP_UPDATEMVCC) {
|
||||
val->mvcc_tstamp = getMvccTstamp();
|
||||
@@ -176,12 +180,16 @@ robj_roptr lookupKeyRead(redisDb *db, robj *key) {
|
||||
*
|
||||
* Returns the linked value object if the key exists or NULL if the key
|
||||
* does not exist in the specified DB. */
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
||||
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
|
||||
expireIfNeeded(db,key);
|
||||
robj *o = lookupKey(db,key,LOOKUP_UPDATEMVCC);
|
||||
robj *o = lookupKey(db,key,flags|LOOKUP_UPDATEMVCC);
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
||||
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
|
||||
}
|
||||
|
||||
robj_roptr lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
|
||||
robj_roptr o = lookupKeyRead(c->db, key);
|
||||
if (!o) addReply(c,reply);
|
||||
@@ -203,9 +211,10 @@ int dbAddCore(redisDb *db, robj *key, robj *val) {
|
||||
if (retval == DICT_OK)
|
||||
{
|
||||
if (val->type == OBJ_LIST ||
|
||||
val->type == OBJ_ZSET)
|
||||
signalKeyAsReady(db, key);
|
||||
if (g_pserver->cluster_enabled) slotToKeyAdd(key);
|
||||
val->type == OBJ_ZSET ||
|
||||
val->type == OBJ_STREAM)
|
||||
signalKeyAsReady(db, key);
|
||||
if (g_pserver->cluster_enabled) slotToKeyAdd(szFromObj(key));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -301,20 +310,31 @@ int dbMerge(redisDb *db, robj *key, robj *val, int fReplace)
|
||||
*
|
||||
* 1) The ref count of the value object is incremented.
|
||||
* 2) clients WATCHing for the destination key notified.
|
||||
* 3) The expire time of the key is reset (the key is made persistent).
|
||||
* 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 setKey(redisDb *db, robj *key, robj *val) {
|
||||
* 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) {
|
||||
dictEntry *de = dictFind(db->pdict, ptrFromObj(key));
|
||||
if (de == NULL) {
|
||||
dbAdd(db,key,val);
|
||||
} else {
|
||||
dbOverwriteCore(db,de,key,val,!!g_pserver->fActiveReplica,true);
|
||||
updateDbValAccess(de, LOOKUP_NONE);
|
||||
dbOverwriteCore(db,de,key,val,!!g_pserver->fActiveReplica,!keepttl);
|
||||
}
|
||||
incrRefCount(val);
|
||||
signalModifiedKey(db,key);
|
||||
if (signal) signalModifiedKey(c,db,key);
|
||||
}
|
||||
|
||||
/* Common case for genericSetKey() where the TTL is not retained. */
|
||||
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.
|
||||
* LRU/LFU info is not updated in any way. */
|
||||
int dbExists(redisDb *db, robj *key) {
|
||||
return dictFind(db->pdict,ptrFromObj(key)) != NULL;
|
||||
}
|
||||
@@ -374,7 +394,7 @@ int dbSyncDelete(redisDb *db, robj *key) {
|
||||
if (de != nullptr && ((robj*)dictGetVal(de))->FExpires())
|
||||
removeExpireCore(db, key, de);
|
||||
if (dictDelete(db->pdict,ptrFromObj(key)) == DICT_OK) {
|
||||
if (g_pserver->cluster_enabled) slotToKeyDel(key);
|
||||
if (g_pserver->cluster_enabled) slotToKeyDel(szFromObj(key));
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
@@ -434,14 +454,19 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
|
||||
* 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
|
||||
* 1. EMPTYDB_ASYNC if we want the memory to be freed in a different thread.
|
||||
* 2. EMPTYDB_BACKUP if we want to empty the backup dictionaries created by
|
||||
* disklessLoadMakeBackups. In that case we only free memory and avoid
|
||||
* firing module events.
|
||||
* 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 emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*)) {
|
||||
int async = (flags & EMPTYDB_ASYNC);
|
||||
int backup = (flags & EMPTYDB_BACKUP); /* Just free the memory, nothing else */
|
||||
RedisModuleFlushInfoV1 fi = {REDISMODULE_FLUSHINFO_VERSION,!async,dbnum};
|
||||
long long removed = 0;
|
||||
|
||||
if (dbnum < -1 || dbnum >= cserver.dbnum) {
|
||||
@@ -449,6 +474,19 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Pre-flush actions */
|
||||
if (!backup) {
|
||||
/* 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);
|
||||
}
|
||||
|
||||
int startdb, enddb;
|
||||
if (dbnum == -1) {
|
||||
startdb = 0;
|
||||
@@ -458,27 +496,40 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
}
|
||||
|
||||
for (int j = startdb; j <= enddb; j++) {
|
||||
removed += dictSize(g_pserver->db[j].pdict);
|
||||
removed += dictSize(dbarray[j].pdict);
|
||||
if (async) {
|
||||
emptyDbAsync(&g_pserver->db[j]);
|
||||
emptyDbAsync(&dbarray[j]);
|
||||
} else {
|
||||
dictEmpty(g_pserver->db[j].pdict,callback);
|
||||
delete g_pserver->db[j].setexpire;
|
||||
g_pserver->db[j].setexpire = new (MALLOC_LOCAL) expireset();
|
||||
g_pserver->db[j].expireitr = g_pserver->db[j].setexpire->end();
|
||||
dictEmpty(dbarray[j].pdict,callback);
|
||||
dbarray[j].setexpire->clear();
|
||||
}
|
||||
}
|
||||
if (g_pserver->cluster_enabled) {
|
||||
if (async) {
|
||||
slotToKeyFlushAsync();
|
||||
} else {
|
||||
slotToKeyFlush();
|
||||
|
||||
/* Post-flush actions */
|
||||
if (!backup) {
|
||||
if (g_pserver->cluster_enabled) {
|
||||
if (async) {
|
||||
slotToKeyFlushAsync();
|
||||
} else {
|
||||
slotToKeyFlush();
|
||||
}
|
||||
}
|
||||
if (dbnum == -1) flushSlaveKeysWithExpireList();
|
||||
|
||||
/* Also fire the end event. Note that this event will fire almost
|
||||
* immediately after the start event if the flush is asynchronous. */
|
||||
moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
|
||||
REDISMODULE_SUBEVENT_FLUSHDB_END,
|
||||
&fi);
|
||||
}
|
||||
if (dbnum == -1) flushSlaveKeysWithExpireList();
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
return emptyDbGeneric(g_pserver->db, dbnum, flags, callback);
|
||||
}
|
||||
|
||||
int selectDb(client *c, int id) {
|
||||
if (id < 0 || id >= cserver.dbnum)
|
||||
return C_ERR;
|
||||
@@ -486,6 +537,15 @@ int selectDb(client *c, int id) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
long long dbTotalServerKeyCount() {
|
||||
long long total = 0;
|
||||
int j;
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
total += dictSize(g_pserver->db[j].pdict);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Hooks for key space changes.
|
||||
*
|
||||
@@ -495,13 +555,16 @@ int selectDb(client *c, int id) {
|
||||
* 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);
|
||||
if (g_pserver->tracking_clients) trackingInvalidateKey(key);
|
||||
trackingInvalidateKey(c,key);
|
||||
}
|
||||
|
||||
void signalFlushedDb(int dbid) {
|
||||
touchWatchedKeysOnFlush(dbid);
|
||||
trackingInvalidateKeysOnFlush(dbid);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -530,28 +593,9 @@ int getFlushCommandFlags(client *c, int *flags) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* FLUSHDB [ASYNC]
|
||||
*
|
||||
* Flushes the currently SELECTed Redis DB. */
|
||||
void flushdbCommand(client *c) {
|
||||
int flags;
|
||||
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
signalFlushedDb(c->db->id);
|
||||
g_pserver->dirty += emptyDb(c->db->id,flags,NULL);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
/* FLUSHALL [ASYNC]
|
||||
*
|
||||
* Flushes the whole server data set. */
|
||||
void flushallCommand(client *c) {
|
||||
int flags;
|
||||
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
signalFlushedDb(-1);
|
||||
/* Flushes the whole server data set. */
|
||||
void flushAllDataAndResetRDB(int flags) {
|
||||
g_pserver->dirty += emptyDb(-1,flags,NULL);
|
||||
addReply(c,shared.ok);
|
||||
if (g_pserver->rdb_child_pid != -1) killRDBChild();
|
||||
if (g_pserver->saveparamslen > 0) {
|
||||
/* Normally rdbSave() will reset dirty, but we don't want this here
|
||||
@@ -563,6 +607,41 @@ void flushallCommand(client *c) {
|
||||
g_pserver->dirty = saved_dirty;
|
||||
}
|
||||
g_pserver->dirty++;
|
||||
#if defined(USE_JEMALLOC)
|
||||
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
||||
* for large databases, flushdb blocks for long anyway, so a bit more won't
|
||||
* harm and this way the flush and purge will be synchroneus. */
|
||||
if (!(flags & EMPTYDB_ASYNC))
|
||||
jemalloc_purge();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* FLUSHDB [ASYNC]
|
||||
*
|
||||
* Flushes the currently SELECTed Redis DB. */
|
||||
void flushdbCommand(client *c) {
|
||||
int flags;
|
||||
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
g_pserver->dirty += emptyDb(c->db->id,flags,NULL);
|
||||
addReply(c,shared.ok);
|
||||
#if defined(USE_JEMALLOC)
|
||||
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
||||
* for large databases, flushdb blocks for long anyway, so a bit more won't
|
||||
* harm and this way the flush and purge will be synchroneus. */
|
||||
if (!(flags & EMPTYDB_ASYNC))
|
||||
jemalloc_purge();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* FLUSHALL [ASYNC]
|
||||
*
|
||||
* Flushes the whole server data set. */
|
||||
void flushallCommand(client *c) {
|
||||
int flags;
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
flushAllDataAndResetRDB(flags);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
/* This command implements DEL and LAZYDEL. */
|
||||
@@ -574,7 +653,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);
|
||||
g_pserver->dirty++;
|
||||
@@ -585,7 +664,7 @@ void delGenericCommand(client *c, int lazy) {
|
||||
}
|
||||
|
||||
void delCommand(client *c) {
|
||||
delGenericCommand(c,0);
|
||||
delGenericCommand(c,g_pserver->lazyfree_lazy_user_del);
|
||||
}
|
||||
|
||||
void unlinkCommand(client *c) {
|
||||
@@ -643,7 +722,7 @@ void keysCommand(client *c) {
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
|
||||
di = dictGetSafeIterator(c->db->pdict);
|
||||
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
|
||||
allkeys = (pattern[0] == '*' && plen == 1);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key = (sds)dictGetKey(de);
|
||||
robj *keyobj;
|
||||
@@ -1021,8 +1100,8 @@ void renameGenericCommand(client *c, int nx) {
|
||||
dbAdd(c->db,c->argv[2],o);
|
||||
if (spexpire != nullptr)
|
||||
setExpire(c,c->db,c->argv[2],std::move(*spexpire));
|
||||
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",
|
||||
@@ -1099,6 +1178,13 @@ void moveCommand(client *c) {
|
||||
dbAdd(dst,c->argv[1],o);
|
||||
if (spexpire != nullptr) setExpire(c,dst,c->argv[1],std::move(*spexpire));
|
||||
|
||||
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,
|
||||
"move_to",c->argv[1],dst->id);
|
||||
|
||||
addReply(c,shared.cone);
|
||||
}
|
||||
|
||||
@@ -1128,7 +1214,7 @@ void scanDatabaseForReadyLists(redisDb *db) {
|
||||
*
|
||||
* Returns C_ERR if at least one of the DB ids are out of range, otherwise
|
||||
* C_OK is returned. */
|
||||
int dbSwapDatabases(int id1, int id2) {
|
||||
int dbSwapDatabases(long id1, long id2) {
|
||||
if (id1 < 0 || id1 >= cserver.dbnum ||
|
||||
id2 < 0 || id2 >= cserver.dbnum) return C_ERR;
|
||||
if (id1 == id2) return C_OK;
|
||||
@@ -1372,6 +1458,7 @@ expireEntry *getExpire(redisDb *db, robj_roptr key) {
|
||||
* keys. */
|
||||
void propagateExpire(redisDb *db, robj *key, int lazy) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
|
||||
robj *argv[2];
|
||||
|
||||
argv[0] = lazy ? shared.unlink : shared.del;
|
||||
@@ -1389,9 +1476,52 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
|
||||
decrRefCount(argv[1]);
|
||||
}
|
||||
|
||||
void propagateSubkeyExpire(redisDb *db, int type, robj *key, robj *subkey)
|
||||
{
|
||||
robj *argv[3];
|
||||
robj objT;
|
||||
redisCommand *cmd = nullptr;
|
||||
switch (type)
|
||||
{
|
||||
case OBJ_SET:
|
||||
argv[0] = shared.srem;
|
||||
argv[1] = key;
|
||||
argv[2] = subkey;
|
||||
cmd = cserver.sremCommand;
|
||||
break;
|
||||
|
||||
case OBJ_HASH:
|
||||
argv[0] = shared.hdel;
|
||||
argv[1] = key;
|
||||
argv[2] = subkey;
|
||||
cmd = cserver.hdelCommand;
|
||||
break;
|
||||
|
||||
case OBJ_ZSET:
|
||||
argv[0] = shared.zrem;
|
||||
argv[1] = key;
|
||||
argv[2] = subkey;
|
||||
cmd = cserver.zremCommand;
|
||||
break;
|
||||
|
||||
case OBJ_CRON:
|
||||
return; // CRON jobs replicate in their own handler
|
||||
|
||||
default:
|
||||
serverPanic("Unknown subkey type");
|
||||
}
|
||||
|
||||
if (g_pserver->aof_state != AOF_OFF)
|
||||
feedAppendOnlyFile(cmd,db->id,argv,3);
|
||||
// Active replicas do their own expiries, do not propogate
|
||||
if (!g_pserver->fActiveReplica)
|
||||
replicationFeedSlaves(g_pserver->slaves,db->id,argv,3);
|
||||
}
|
||||
|
||||
/* Check if the key is expired. Note, this does not check subexpires */
|
||||
int keyIsExpired(redisDb *db, robj *key) {
|
||||
expireEntry *pexpire = getExpire(db,key);
|
||||
mstime_t now;
|
||||
|
||||
if (pexpire == nullptr) return 0; /* No expire for this key */
|
||||
|
||||
@@ -1416,8 +1546,26 @@ int keyIsExpired(redisDb *db, robj *key) {
|
||||
* only the first time it is accessed and not in the middle of the
|
||||
* script execution, making propagation to slaves / AOF consistent.
|
||||
* See issue #1525 on Github for more information. */
|
||||
mstime_t now = g_pserver->lua_caller ? g_pserver->lua_time_start : mstime();
|
||||
if (g_pserver->lua_caller) {
|
||||
now = g_pserver->lua_time_start;
|
||||
}
|
||||
/* If we are in the middle of a command execution, we still want to use
|
||||
* a reference time that does not change: in that case we just use the
|
||||
* cached time, that we update before each call in the call() function.
|
||||
* This way we avoid that commands such as RPOPLPUSH or similar, that
|
||||
* may re-open the same key multiple times, can invalidate an already
|
||||
* open object in a next call, if the next call will see the key expired,
|
||||
* while the first did not. */
|
||||
else if (serverTL->fixed_time_expire > 0) {
|
||||
now = g_pserver->mstime;
|
||||
}
|
||||
/* For the other cases, we want to use the most fresh time we have. */
|
||||
else {
|
||||
now = mstime();
|
||||
}
|
||||
|
||||
/* The key expired if the current (virtual or real) time is greater
|
||||
* than the expire time of the key. */
|
||||
return now > when;
|
||||
}
|
||||
|
||||
@@ -1458,13 +1606,17 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
propagateExpire(db,key,g_pserver->lazyfree_lazy_expire);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",key,db->id);
|
||||
return g_pserver->lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
int retval = g_pserver->lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
if (retval) signalModifiedKey(NULL,db,key);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* API to get key arguments from commands
|
||||
* ---------------------------------------------------------------------------*/
|
||||
#define MAX_KEYS_BUFFER 256
|
||||
thread_local static int getKeysTempBuffer[MAX_KEYS_BUFFER];
|
||||
|
||||
/* The base case is to use the keys position as given in the command table
|
||||
* (firstkey, lastkey, step). */
|
||||
@@ -1479,7 +1631,12 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
|
||||
last = cmd->lastkey;
|
||||
if (last < 0) last = argc+last;
|
||||
keys = (int*)zmalloc(sizeof(int)*((last - cmd->firstkey)+1), MALLOC_SHARED);
|
||||
|
||||
int count = ((last - cmd->firstkey)+1);
|
||||
keys = getKeysTempBuffer;
|
||||
if (count > MAX_KEYS_BUFFER)
|
||||
keys = (int*)zmalloc(sizeof(int)*count);
|
||||
|
||||
for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
|
||||
if (j >= argc) {
|
||||
/* Modules commands, and standard commands with a not fixed number
|
||||
@@ -1489,7 +1646,7 @@ 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);
|
||||
getKeysFreeResult(keys);
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
} else {
|
||||
@@ -1525,7 +1682,8 @@ int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *nu
|
||||
|
||||
/* Free the result of getKeysFromCommand. */
|
||||
void getKeysFreeResult(int *result) {
|
||||
zfree(result);
|
||||
if (result != getKeysTempBuffer)
|
||||
zfree(result);
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from following commands:
|
||||
@@ -1546,7 +1704,9 @@ int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *nu
|
||||
/* Keys in z{union,inter}store come from two places:
|
||||
* argv[1] = storage key,
|
||||
* argv[3...n] = keys to intersect */
|
||||
keys = (int*)zmalloc(sizeof(int)*(num+1), MALLOC_SHARED);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num+1>MAX_KEYS_BUFFER)
|
||||
keys = (int*)zmalloc(sizeof(int)*(num+1));
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
for (i = 0; i < num; i++) keys[i] = 3+i;
|
||||
@@ -1572,7 +1732,10 @@ int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
keys = (int*)zmalloc(sizeof(int)*num, MALLOC_SHARED);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = (int*)zmalloc(sizeof(int)*num);
|
||||
|
||||
*numkeys = num;
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
@@ -1593,7 +1756,7 @@ int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
UNUSED(cmd);
|
||||
|
||||
num = 0;
|
||||
keys = (int*)zmalloc(sizeof(int)*2, MALLOC_SHARED); /* Alloc 2 places for the worst case. */
|
||||
keys = getKeysTempBuffer; /* Alloc 2 places for the worst case. */
|
||||
|
||||
keys[num++] = 1; /* <sort-key> is always present. */
|
||||
|
||||
@@ -1651,7 +1814,10 @@ int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkey
|
||||
}
|
||||
}
|
||||
|
||||
keys = (int*)zmalloc(sizeof(int)*num, MALLOC_SHARED);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = (int*)zmalloc(sizeof(int)*num);
|
||||
|
||||
for (i = 0; i < num; i++) keys[i] = first+i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
@@ -1684,7 +1850,9 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
|
||||
* argv[1] = key,
|
||||
* argv[5...n] = stored key if present
|
||||
*/
|
||||
keys = (int*)zmalloc(sizeof(int) * num, MALLOC_SHARED);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = (int*)zmalloc(sizeof(int) * num);
|
||||
|
||||
/* Add all key positions to keys[] */
|
||||
keys[0] = 1;
|
||||
@@ -1695,6 +1863,48 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* LCS ... [KEYS <key1> <key2>] ... */
|
||||
int *lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
{
|
||||
int i;
|
||||
int *keys = getKeysTempBuffer;
|
||||
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 = szFromObj(argv[i]);
|
||||
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;
|
||||
*numkeys = 2;
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
*numkeys = 0;
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from memory command.
|
||||
* MEMORY USAGE <key> */
|
||||
int *memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int *keys;
|
||||
UNUSED(cmd);
|
||||
|
||||
if (argc >= 3 && !strcasecmp(szFromObj(argv[1]),"usage")) {
|
||||
keys = getKeysTempBuffer;
|
||||
keys[0] = 2;
|
||||
*numkeys = 1;
|
||||
return keys;
|
||||
}
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
@@ -1733,7 +1943,10 @@ int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
num /= 2; /* We have half the keys as there are arguments because
|
||||
there are also the IDs, one per key. */
|
||||
|
||||
keys = (int*)zmalloc(sizeof(int) * num, MALLOC_SHARED);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = (int*)zmalloc(sizeof(int) * num);
|
||||
|
||||
for (i = streams_pos+1; i < argc-num; i++) keys[i-streams_pos-1] = i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
@@ -1743,9 +1956,11 @@ int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
* 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) {
|
||||
size_t keylen = sdslen(szFromObj(key));
|
||||
unsigned int hashslot = keyHashSlot(szFromObj(key),keylen);
|
||||
void slotToKeyUpdateKey(sds key, int add) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
|
||||
size_t keylen = sdslen(key);
|
||||
unsigned int hashslot = keyHashSlot(key,keylen);
|
||||
unsigned char buf[64];
|
||||
unsigned char *indexed = buf;
|
||||
|
||||
@@ -1753,24 +1968,28 @@ void slotToKeyUpdateKey(robj *key, int add) {
|
||||
if (keylen+2 > 64) indexed = (unsigned char*)zmalloc(keylen+2, MALLOC_SHARED);
|
||||
indexed[0] = (hashslot >> 8) & 0xff;
|
||||
indexed[1] = hashslot & 0xff;
|
||||
memcpy(indexed+2,ptrFromObj(key),keylen);
|
||||
memcpy(indexed+2,key,keylen);
|
||||
int fModified = false;
|
||||
if (add) {
|
||||
raxInsert(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL,NULL);
|
||||
fModified = raxInsert(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL,NULL);
|
||||
} else {
|
||||
raxRemove(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL);
|
||||
fModified = raxRemove(g_pserver->cluster->slots_to_keys,indexed,keylen+2,NULL);
|
||||
}
|
||||
serverAssert(fModified);
|
||||
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) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
|
||||
raxFree(g_pserver->cluster->slots_to_keys);
|
||||
g_pserver->cluster->slots_to_keys = raxNew();
|
||||
memset(g_pserver->cluster->slots_keys_count,0,
|
||||
@@ -1800,6 +2019,8 @@ unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int coun
|
||||
/* Remove all the keys in the specified hash slot.
|
||||
* The number of removed items is returned. */
|
||||
unsigned int delKeysInSlot(unsigned int hashslot) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
|
||||
raxIterator iter;
|
||||
int j = 0;
|
||||
unsigned char indexed[2];
|
||||
@@ -1811,8 +2032,10 @@ unsigned int delKeysInSlot(unsigned int hashslot) {
|
||||
raxSeek(&iter,">=",indexed,2);
|
||||
raxNext(&iter);
|
||||
|
||||
auto count = g_pserver->cluster->slots_keys_count[hashslot];
|
||||
robj *key = createStringObject((char*)iter.key+2,iter.key_len-2);
|
||||
dbDelete(&g_pserver->db[0],key);
|
||||
serverAssert(count > g_pserver->cluster->slots_keys_count[hashslot]); // we should have deleted something or we will be in an infinite loop
|
||||
decrRefCount(key);
|
||||
j++;
|
||||
}
|
||||
|
||||
+211
-20
@@ -31,6 +31,7 @@
|
||||
#include "server.h"
|
||||
#include "sha1.h" /* SHA1 is used for DEBUG DIGEST */
|
||||
#include "crc64.h"
|
||||
#include "cron.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <signal.h>
|
||||
@@ -251,6 +252,10 @@ void xorObjectDigest(redisDb *db, robj_roptr keyobj, unsigned char *digest, robj
|
||||
mt->digest(&md,mv->value);
|
||||
xorDigest(digest,md.x,sizeof(md.x));
|
||||
}
|
||||
} else if (o->type == OBJ_CRON) {
|
||||
cronjob *job = (cronjob*)ptrFromObj(o);
|
||||
mixDigest(digest, &job->interval, sizeof(job->interval));
|
||||
mixDigest(digest, job->script.get(), job->script.size());
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
@@ -306,6 +311,76 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_JEMALLOC
|
||||
void mallctl_int(client *c, robj **argv, int argc) {
|
||||
int ret;
|
||||
/* start with the biggest size (int64), and if that fails, try smaller sizes (int32, bool) */
|
||||
int64_t old = 0, val;
|
||||
if (argc > 1) {
|
||||
long long ll;
|
||||
if (getLongLongFromObjectOrReply(c, argv[1], &ll, NULL) != C_OK)
|
||||
return;
|
||||
val = ll;
|
||||
}
|
||||
size_t sz = sizeof(old);
|
||||
while (sz > 0) {
|
||||
if ((ret=je_mallctl(szFromObj(argv[0]), &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(szFromObj(argv[0]), NULL, 0, &val, sz))) {
|
||||
addReply(c, shared.ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (ret==EINVAL) {
|
||||
/* size might be wrong, try a smaller one */
|
||||
sz /= 2;
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
val <<= 8*sz;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
addReplyErrorFormat(c,"%s", strerror(ret));
|
||||
return;
|
||||
} else {
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
old >>= 64 - 8*sz;
|
||||
#endif
|
||||
addReplyLongLong(c, old);
|
||||
return;
|
||||
}
|
||||
}
|
||||
addReplyErrorFormat(c,"%s", strerror(EINVAL));
|
||||
}
|
||||
|
||||
void mallctl_string(client *c, robj **argv, int argc) {
|
||||
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 ((rret=je_mallctl(szFromObj(argv[0]), &old, &sz, NULL, 0))) {
|
||||
/* return error unless this option is write only. */
|
||||
if (!(rret == EPERM && argc > 1)) {
|
||||
addReplyErrorFormat(c,"%s", strerror(rret));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(argc > 1) {
|
||||
char *val = szFromObj(argv[1]);
|
||||
char **valref = &val;
|
||||
if ((!strcmp(val,"VOID")))
|
||||
valref = NULL, sz = 0;
|
||||
wret = je_mallctl(szFromObj(argv[0]), NULL, 0, valref, sz);
|
||||
}
|
||||
if (!rret)
|
||||
addReplyBulkCString(c, old);
|
||||
else if (wret)
|
||||
addReplyErrorFormat(c,"%s", strerror(wret));
|
||||
else
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
#endif
|
||||
|
||||
void debugCommand(client *c) {
|
||||
if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"help")) {
|
||||
const char *help[] = {
|
||||
@@ -314,6 +389,7 @@ 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.",
|
||||
"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.",
|
||||
@@ -321,17 +397,23 @@ void debugCommand(client *c) {
|
||||
"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 conficts 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.",
|
||||
"SET-ACTIVE-EXPIRE <0|1> -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.",
|
||||
"AOF-FLUSH-SLEEP <microsec> -- Server will sleep before flushing the AOF, this is used for testing",
|
||||
"SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.",
|
||||
"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.",
|
||||
#ifdef USE_JEMALLOC
|
||||
"MALLCTL <key> [<val>] -- Get or set a malloc tunning integer.",
|
||||
"MALLCTL-STR <key> [<val>] -- Get or set a malloc tunning string.",
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@@ -363,16 +445,44 @@ NULL
|
||||
serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)ptrFromObj(c->argv[2]));
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"reload")) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(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 = szFromObj(c->argv[j]);
|
||||
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 beahvior is to save the RDB file before loading
|
||||
* it back. */
|
||||
if (save) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSaveFile(g_pserver->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);
|
||||
rdbSaveInfo rsiDft = RDB_SAVE_INFO_INIT;
|
||||
int ret = rdbLoad(&rsiDft);
|
||||
int ret = rdbLoadFile(g_pserver->rdb_filename,NULL,flags);
|
||||
unprotectClient(c);
|
||||
if (ret != C_OK) {
|
||||
addReplyError(c,"Error trying to load the RDB dump");
|
||||
@@ -443,7 +553,7 @@ NULL
|
||||
"encoding:%s serializedlength:%zu "
|
||||
"lru:%d lru_seconds_idle:%llu%s",
|
||||
(void*)val, static_cast<int>(val->getrefcount(std::memory_order_relaxed)),
|
||||
strenc, rdbSavedObjectLen(val),
|
||||
strenc, rdbSavedObjectLen(val, c->argv[2]),
|
||||
val->lru, estimateObjectIdleTime(val)/1000, extra);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"sdslen") && c->argc == 3) {
|
||||
dictEntry *de;
|
||||
@@ -512,7 +622,7 @@ NULL
|
||||
memcpy(ptrFromObj(val), 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);
|
||||
@@ -541,8 +651,8 @@ NULL
|
||||
}
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"protocol") && c->argc == 3) {
|
||||
/* DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|
|
||||
* attrib|push|verbatim|true|false|state|err|bloberr] */
|
||||
char *name = szFromObj(c->argv[2]);
|
||||
* attrib|push|verbatim|true|false] */
|
||||
const char *name = szFromObj(c->argv[2]);
|
||||
if (!strcasecmp(name,"string")) {
|
||||
addReplyBulkCString(c,"Hello World");
|
||||
} else if (!strcasecmp(name,"integer")) {
|
||||
@@ -589,7 +699,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(szFromObj(c->argv[1]),"sleep") && c->argc == 3) {
|
||||
double dtime = strtod(szFromObj(c->argv[2]),NULL);
|
||||
@@ -605,6 +715,11 @@ NULL
|
||||
{
|
||||
g_pserver->active_expire_enabled = atoi(szFromObj(c->argv[2]));
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"aof-flush-sleep") &&
|
||||
c->argc == 3)
|
||||
{
|
||||
g_pserver->aof_flush_sleep = atoi(szFromObj(c->argv[2]));
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"lua-always-replicate-commands") &&
|
||||
c->argc == 3)
|
||||
{
|
||||
@@ -633,9 +748,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 >= cserver.dbnum) {
|
||||
sdsfree(stats);
|
||||
addReplyError(c,"Out of range database");
|
||||
return;
|
||||
}
|
||||
@@ -648,7 +766,8 @@ NULL
|
||||
g_pserver->db[dbid].setexpire->getstats(buf, sizeof(buf));
|
||||
stats = sdscat(stats, buf);
|
||||
|
||||
addReplyBulkSds(c,stats);
|
||||
addReplyVerbatim(c,stats,sdslen(stats),"txt");
|
||||
sdsfree(stats);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"htstats-key") && c->argc == 3) {
|
||||
robj *o;
|
||||
dict *ht = NULL;
|
||||
@@ -675,7 +794,7 @@ NULL
|
||||
} else {
|
||||
char buf[4096];
|
||||
dictGetStats(buf,sizeof(buf),ht);
|
||||
addReplyBulkCString(c,buf);
|
||||
addReplyVerbatim(c,buf,strlen(buf),"txt");
|
||||
}
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"change-repl-id") && c->argc == 2) {
|
||||
serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id");
|
||||
@@ -706,6 +825,14 @@ NULL
|
||||
c->flags &= ~(CLIENT_MASTER | CLIENT_MASTER_FORCE_REPLY);
|
||||
}
|
||||
addReply(c, shared.ok);
|
||||
#ifdef USE_JEMALLOC
|
||||
} else if(!strcasecmp(szFromObj(c->argv[1]),"mallctl") && c->argc >= 3) {
|
||||
mallctl_int(c, c->argv+2, c->argc-2);
|
||||
return;
|
||||
} else if(!strcasecmp(szFromObj(c->argv[1]),"mallctl-str") && c->argc >= 3) {
|
||||
mallctl_string(c, c->argv+2, c->argc-2);
|
||||
return;
|
||||
#endif
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
@@ -729,11 +856,12 @@ void _serverAssert(const char *estr, const char *file, int line) {
|
||||
|
||||
void _serverAssertPrintClientInfo(const client *c) {
|
||||
int j;
|
||||
char conninfo[CONN_INFO_LEN];
|
||||
|
||||
bugReportStart();
|
||||
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
|
||||
serverLog(LL_WARNING,"client->flags = %llu", static_cast<unsigned long long>(c->flags));
|
||||
serverLog(LL_WARNING,"client->fd = %d", c->fd);
|
||||
serverLog(LL_WARNING,"client->flags = %llu", (unsigned long long) c->flags);
|
||||
serverLog(LL_WARNING,"client->conn = %s", connGetInfo(c->conn, conninfo, sizeof(conninfo)));
|
||||
serverLog(LL_WARNING,"client->argc = %d", c->argc);
|
||||
for (j=0; j < c->argc; j++) {
|
||||
char buf[128];
|
||||
@@ -772,6 +900,8 @@ void serverLogObjectDebugInfo(robj_roptr 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*)ptrFromObj(o))->zsl->level);
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
serverLog(LL_WARNING,"Stream size: %d", (int) streamLength(o));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1000,6 +1130,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__)
|
||||
@@ -1423,6 +1608,12 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
/* Log dump of processor registers */
|
||||
logRegisters(uc);
|
||||
|
||||
/* Log Modules INFO */
|
||||
serverLogRaw(LL_WARNING|LL_RAW, "\n------ MODULES INFO OUTPUT ------\n");
|
||||
infostring = modulesCollectInfo(sdsempty(), NULL, 1, 0);
|
||||
serverLogRaw(LL_WARNING|LL_RAW, infostring);
|
||||
sdsfree(infostring);
|
||||
|
||||
#if defined(HAVE_PROC_MAPS)
|
||||
/* Test memory */
|
||||
serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n");
|
||||
@@ -1555,7 +1746,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);
|
||||
}
|
||||
|
||||
+101
-54
@@ -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,7 +43,7 @@
|
||||
|
||||
/* this method was added to jemalloc in order to help us understand which
|
||||
* pointers are worthwhile moving and which aren't */
|
||||
extern "C" int je_get_defrag_hint(void* ptr, int *bin_util, int *run_util);
|
||||
extern "C" int je_get_defrag_hint(void* ptr);
|
||||
|
||||
/* forward declarations*/
|
||||
void defragDictBucketCallback(void *privdata, dictEntry **bucketref);
|
||||
@@ -56,18 +56,11 @@ bool replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey);
|
||||
* 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)) {
|
||||
g_pserver->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)) {
|
||||
g_pserver->stat_active_defrag_misses++;
|
||||
size = zmalloc_size(ptr);
|
||||
return NULL;
|
||||
}
|
||||
/* move this allocation to a new allocation.
|
||||
@@ -373,7 +366,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
||||
if ((newele = activeDefragStringOb(ele, &defragged)))
|
||||
de->v.val = newele, defragged++;
|
||||
} else if (dict_val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) {
|
||||
void *newptr, *ptr = ln->value;
|
||||
void *newptr, *ptr = dictGetVal(de);
|
||||
if ((newptr = activeDefragAlloc(ptr)))
|
||||
ln->value = newptr, defragged++;
|
||||
}
|
||||
@@ -421,25 +414,32 @@ bool replaceSateliteOSetKeyPtr(expireset &set, sds oldkey, sds newkey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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 = (quicklistNode*)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 = (unsigned char*)activeDefragAlloc(node->zl)))
|
||||
defragged++, node->zl = newzl;
|
||||
return defragged;
|
||||
}
|
||||
|
||||
long activeDefragQuickListNodes(quicklist *ql) {
|
||||
quicklistNode *node = ql->head;
|
||||
long defragged = 0;
|
||||
while (node) {
|
||||
if ((newnode = (quicklistNode*)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 = (unsigned char*)activeDefragAlloc(node->zl)))
|
||||
defragged++, node->zl = newzl;
|
||||
defragged += activeDefragQuickListNode(ql, &node);
|
||||
node = node->next;
|
||||
}
|
||||
return defragged;
|
||||
@@ -453,12 +453,48 @@ void defragLater(redisDb *db, dictEntry *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 = (quicklist*)ptrFromObj(ob);
|
||||
quicklistNode *node;
|
||||
long iterations = 0;
|
||||
int bookmark_failed = 0;
|
||||
if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST)
|
||||
return 0;
|
||||
g_pserver->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);
|
||||
g_pserver->stat_active_defrag_scanned++;
|
||||
if (++iterations > 128 && !bookmark_failed) {
|
||||
if (ustime() > endtime) {
|
||||
if (!quicklistBookmarkCreate(&ql, "_AD", node)) {
|
||||
bookmark_failed = 1;
|
||||
} else {
|
||||
ob->m_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 {
|
||||
@@ -651,7 +687,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) {
|
||||
g_pserver->stat_active_defrag_scanned++;
|
||||
if (++iterations > 128) {
|
||||
if (ustime() > endtime) {
|
||||
serverAssert(ri.key_len==sizeof(last));
|
||||
memcpy(last,ri.key,ri.key_len);
|
||||
@@ -916,8 +953,7 @@ int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime) {
|
||||
if (de) {
|
||||
robj *ob = (robj*)dictGetVal(de);
|
||||
if (ob->type == OBJ_LIST) {
|
||||
g_pserver->stat_active_defrag_hits += scanLaterList(ob);
|
||||
*cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
return scanLaterList(ob, cursor, endtime, &g_pserver->stat_active_defrag_hits);
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
g_pserver->stat_active_defrag_hits += scanLaterSet(ob, cursor);
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
@@ -935,10 +971,12 @@ int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* static variables serving defragLaterStep to continue scanning a key from were we stopped last time. */
|
||||
static sds defrag_later_current_key = NULL;
|
||||
static unsigned long defrag_later_cursor = 0;
|
||||
|
||||
/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
|
||||
int defragLaterStep(redisDb *db, long long endtime) {
|
||||
static sds current_key = NULL;
|
||||
static unsigned long cursor = 0;
|
||||
unsigned int iterations = 0;
|
||||
unsigned long long prev_defragged = g_pserver->stat_active_defrag_hits;
|
||||
unsigned long long prev_scanned = g_pserver->stat_active_defrag_scanned;
|
||||
@@ -946,16 +984,15 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
|
||||
do {
|
||||
/* if we're not continuing a scan from the last call or loop, start a new one */
|
||||
if (!cursor) {
|
||||
if (!defrag_later_cursor) {
|
||||
listNode *head = listFirst(db->defrag_later);
|
||||
|
||||
/* Move on to next key */
|
||||
if (current_key) {
|
||||
serverAssert(current_key == head->value);
|
||||
sdsfree((sds)head->value);
|
||||
if (defrag_later_current_key) {
|
||||
serverAssert(defrag_later_current_key == head->value);
|
||||
listDelNode(db->defrag_later, head);
|
||||
cursor = 0;
|
||||
current_key = NULL;
|
||||
defrag_later_cursor = 0;
|
||||
defrag_later_current_key = NULL;
|
||||
}
|
||||
|
||||
/* stop if we reached the last one. */
|
||||
@@ -964,23 +1001,18 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
return 0;
|
||||
|
||||
/* start a new key */
|
||||
current_key = (sds)head->value;
|
||||
cursor = 0;
|
||||
defrag_later_current_key = (sds)head->value;
|
||||
defrag_later_cursor = 0;
|
||||
}
|
||||
|
||||
/* each time we enter this function we need to fetch the key from the dict again (if it still exists) */
|
||||
dictEntry *de = dictFind(db->pdict, current_key);
|
||||
dictEntry *de = dictFind(db->pdict, defrag_later_current_key);
|
||||
key_defragged = g_pserver->stat_active_defrag_hits;
|
||||
do {
|
||||
int quit = 0;
|
||||
if (defragLaterItem(de, &cursor, 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 (!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. */
|
||||
@@ -998,7 +1030,7 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
prev_defragged = g_pserver->stat_active_defrag_hits;
|
||||
prev_scanned = g_pserver->stat_active_defrag_scanned;
|
||||
}
|
||||
} while(cursor);
|
||||
} while(defrag_later_cursor);
|
||||
if(key_defragged != g_pserver->stat_active_defrag_hits)
|
||||
g_pserver->stat_active_defrag_key_hits++;
|
||||
else
|
||||
@@ -1055,7 +1087,22 @@ void activeDefragCycle(void) {
|
||||
mstime_t latency;
|
||||
int quit = 0;
|
||||
|
||||
if (g_pserver->aof_child_pid!=-1 || g_pserver->rdb_child_pid!=-1)
|
||||
if (!cserver.active_defrag_enabled) {
|
||||
if (g_pserver->active_defrag_running) {
|
||||
/* if active defrag was disabled mid-run, start from fresh next time. */
|
||||
g_pserver->active_defrag_running = 0;
|
||||
if (db)
|
||||
listEmpty(db->defrag_later);
|
||||
defrag_later_current_key = NULL;
|
||||
defrag_later_cursor = 0;
|
||||
current_db = -1;
|
||||
cursor = 0;
|
||||
db = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
+12
-5
@@ -134,7 +134,7 @@ 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;
|
||||
minimal = d->ht[0].used;
|
||||
@@ -478,7 +478,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++) {
|
||||
@@ -766,8 +766,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 +871,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 +941,9 @@ unsigned long dictScan(dict *d,
|
||||
} while (v & (m0 ^ m1));
|
||||
}
|
||||
|
||||
/* undo the ++ at the top */
|
||||
d->iterators--;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -1037,7 +1044,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];
|
||||
|
||||
+25
-18
@@ -33,6 +33,7 @@
|
||||
#include "server.h"
|
||||
#include "bio.h"
|
||||
#include "atomicvar.h"
|
||||
#include <mutex>
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Data structures
|
||||
@@ -375,6 +376,7 @@ size_t freeMemoryGetNotCountedMemory(void) {
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *replica = (client*)listNodeValue(ln);
|
||||
std::unique_lock<fastlock>(replica->lock);
|
||||
overhead += getClientOutputBufferMemoryUsage(replica);
|
||||
}
|
||||
}
|
||||
@@ -460,14 +462,16 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
||||
* was freed to return back under the limit, the function returns C_ERR. */
|
||||
int freeMemoryIfNeeded(void) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
int keys_freed = 0;
|
||||
/* By default replicas should ignore maxmemory
|
||||
* and just be masters exact copies. */
|
||||
if (listLength(g_pserver->masters) && g_pserver->repl_slave_ignore_maxmemory) return C_OK;
|
||||
if (listLength(g_pserver->masters) && g_pserver->repl_slave_ignore_maxmemory && !g_pserver->fActiveReplica) 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(g_pserver->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
|
||||
@@ -478,12 +482,12 @@ int freeMemoryIfNeeded(void) {
|
||||
|
||||
mem_freed = 0;
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
if (g_pserver->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, keys_freed = 0;
|
||||
int j, k, i;
|
||||
static unsigned int next_db = 0;
|
||||
sds bestkey = NULL;
|
||||
int bestdbid;
|
||||
@@ -597,9 +601,9 @@ int freeMemoryIfNeeded(void) {
|
||||
dbAsyncDelete(db,keyobj);
|
||||
else
|
||||
dbSyncDelete(db,keyobj);
|
||||
signalModifiedKey(NULL,db,keyobj);
|
||||
latencyEndMonitor(eviction_latency);
|
||||
latencyAddSampleIfNeeded("eviction-del",eviction_latency);
|
||||
latencyRemoveNestedEvent(latency,eviction_latency);
|
||||
delta -= (long long) zmalloc_used_memory();
|
||||
mem_freed += delta;
|
||||
g_pserver->stat_evictedkeys++;
|
||||
@@ -627,28 +631,31 @@ int freeMemoryIfNeeded(void) {
|
||||
mem_freed = mem_tofree;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!keys_freed) {
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
} else {
|
||||
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
|
||||
|
||||
+48
-22
@@ -33,6 +33,17 @@
|
||||
#include "server.h"
|
||||
#include "cron.h"
|
||||
|
||||
/* Helper function for the activeExpireCycle() function.
|
||||
* This function will try to expire the key that is stored in the hash table
|
||||
* entry 'de' of the 'expires' hash table of a Redis database.
|
||||
*
|
||||
* If the key is found to be expired, it is removed from the database and
|
||||
* 1 is returned. Otherwise no operation is performed and 0 is returned.
|
||||
*
|
||||
* When a key is expired, g_pserver->stat_expiredkeys is incremented.
|
||||
*
|
||||
* The parameter 'now' is the current time in milliseconds as is passed
|
||||
* to the function to avoid too many gettimeofday() syscalls. */
|
||||
void activeExpireCycleExpireFullKey(redisDb *db, const char *key) {
|
||||
robj *keyobj = createStringObject(key,sdslen(key));
|
||||
|
||||
@@ -43,7 +54,7 @@ void activeExpireCycleExpireFullKey(redisDb *db, const char *key) {
|
||||
dbSyncDelete(db,keyobj);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",keyobj,db->id);
|
||||
if (g_pserver->tracking_clients) trackingInvalidateKey(keyobj);
|
||||
trackingInvalidateKey(NULL, keyobj);
|
||||
decrRefCount(keyobj);
|
||||
g_pserver->stat_expiredkeys++;
|
||||
}
|
||||
@@ -56,17 +67,7 @@ void activeExpireCycleExpireFullKey(redisDb *db, const char *key) {
|
||||
* if no access is performed on them.
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Helper function for the activeExpireCycle() function.
|
||||
* This function will try to expire the key that is stored in the hash table
|
||||
* entry 'de' of the 'expires' hash table of a Redis database.
|
||||
*
|
||||
* If the key is found to be expired, it is removed from the database and
|
||||
* 1 is returned. Otherwise no operation is performed and 0 is returned.
|
||||
*
|
||||
* When a key is expired, g_pserver->stat_expiredkeys is incremented.
|
||||
*
|
||||
* The parameter 'now' is the current time in milliseconds as is passed
|
||||
* to the function to avoid too many gettimeofday() syscalls. */
|
||||
|
||||
void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
|
||||
if (!e.FFat())
|
||||
{
|
||||
@@ -78,6 +79,10 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
|
||||
dictEntry *de = dictFind(db->pdict, e.key());
|
||||
robj *val = (robj*)dictGetVal(de);
|
||||
int deleted = 0;
|
||||
|
||||
robj objKey;
|
||||
initStaticStringObject(objKey, (char*)e.key());
|
||||
|
||||
while (!pfat->FEmpty())
|
||||
{
|
||||
if (pfat->nextExpireEntry().when > now)
|
||||
@@ -130,27 +135,37 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
|
||||
default:
|
||||
serverAssert(false);
|
||||
}
|
||||
|
||||
robj objSubkey;
|
||||
initStaticStringObject(objSubkey, (char*)pfat->nextExpireEntry().spsubkey.get());
|
||||
propagateSubkeyExpire(db, val->type, &objKey, &objSubkey);
|
||||
|
||||
pfat->popfrontExpireEntry();
|
||||
}
|
||||
|
||||
if (deleted)
|
||||
{
|
||||
robj objT;
|
||||
if (!pfat->FEmpty())
|
||||
{
|
||||
// We need to resort the expire entry since it may no longer be in the correct position
|
||||
auto itr = db->setexpire->find(e.key());
|
||||
expireEntry eT = std::move(e);
|
||||
db->setexpire->erase(itr);
|
||||
db->setexpire->insert(eT);
|
||||
}
|
||||
|
||||
switch (val->type)
|
||||
{
|
||||
case OBJ_SET:
|
||||
initStaticStringObject(objT, (char*)e.key());
|
||||
signalModifiedKey(db,&objT);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"srem",&objT,db->id);
|
||||
signalModifiedKey(nullptr, db,&objKey);
|
||||
notifyKeyspaceEvent(NOTIFY_SET,"srem",&objKey,db->id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pfat->FEmpty())
|
||||
{
|
||||
robj *keyobj = createStringObject(e.key(),sdslen(e.key()));
|
||||
removeExpire(db, keyobj);
|
||||
decrRefCount(keyobj);
|
||||
removeExpire(db, &objKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +230,8 @@ void expireMemberCore(client *c, robj *key, robj *subkey, long long basetime, lo
|
||||
}
|
||||
|
||||
setExpire(c, c->db, key, subkey, when);
|
||||
|
||||
signalModifiedKey(c, c->db, key);
|
||||
g_pserver->dirty++;
|
||||
addReply(c, shared.cone);
|
||||
}
|
||||
|
||||
@@ -247,6 +263,14 @@ void expireMemberAtCommand(client *c)
|
||||
expireMemberCore(c, c->argv[1], c->argv[2], 0, when, UNIT_SECONDS);
|
||||
}
|
||||
|
||||
void pexpireMemberAtCommand(client *c)
|
||||
{
|
||||
long long when;
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[3], &when, NULL) != C_OK)
|
||||
return;
|
||||
|
||||
expireMemberCore(c, c->argv[1], c->argv[2], 0, when, UNIT_MILLISECONDS);
|
||||
}
|
||||
|
||||
/* Try to expire a few timed out keys. The algorithm used is adaptive and
|
||||
* will use few CPU cycles if there are few expiring keys, otherwise
|
||||
@@ -585,14 +609,14 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
/* Replicate/AOF this as an explicit DEL or UNLINK. */
|
||||
aux = g_pserver->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,nullptr,when);
|
||||
addReply(c,shared.cone);
|
||||
signalModifiedKey(c->db,key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
|
||||
g_pserver->dirty++;
|
||||
return;
|
||||
@@ -681,6 +705,7 @@ void persistCommand(client *c) {
|
||||
if (lookupKeyWrite(c->db,c->argv[1])) {
|
||||
if (c->argc == 2) {
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id);
|
||||
addReply(c,shared.cone);
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
@@ -688,6 +713,7 @@ void persistCommand(client *c) {
|
||||
}
|
||||
} else if (c->argc == 3) {
|
||||
if (removeSubkeyExpire(c->db, c->argv[1], c->argv[2])) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id);
|
||||
addReply(c,shared.cone);
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
|
||||
+238
-44
@@ -27,6 +27,7 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
#include "fastlock.h"
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
@@ -39,10 +40,14 @@
|
||||
#include <map>
|
||||
#ifdef __linux__
|
||||
#include <linux/futex.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include "config.h"
|
||||
#include "serverassert.h"
|
||||
#include "mcs_lock.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
@@ -60,7 +65,20 @@
|
||||
#define UNUSED(x) ((void)x)
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
#include <ucontext.h>
|
||||
__attribute__((weak)) void logStackTrace(ucontext_t *) {}
|
||||
#endif
|
||||
|
||||
extern int g_fInCrash;
|
||||
extern int g_fTestMode;
|
||||
int g_fHighCpuPressure = false;
|
||||
|
||||
#if defined(__i386__) || defined(__amd64__)
|
||||
#define asm_yield() __asm__ __volatile__ ("pause");
|
||||
#elif defined(__aarch64__)
|
||||
#define asm_yield() __asm__ __volatile__ ("yield");
|
||||
#endif
|
||||
|
||||
/****************************************************
|
||||
*
|
||||
@@ -133,13 +151,11 @@ extern "C" void unlock_futex(struct fastlock *lock, uint16_t ifutex);
|
||||
|
||||
#endif
|
||||
|
||||
#pragma weak _serverPanic
|
||||
extern "C" __attribute__((weak)) void _serverPanic(const char * /*file*/, int /*line*/, const char * /*msg*/, ...)
|
||||
{
|
||||
*((char*)-1) = 'x';
|
||||
}
|
||||
|
||||
#pragma weak serverLog
|
||||
__attribute__((weak)) void serverLog(int , const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
@@ -149,16 +165,64 @@ __attribute__((weak)) void serverLog(int , const char *fmt, ...)
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
extern "C" pid_t gettid()
|
||||
{
|
||||
static thread_local int pidCache = -1;
|
||||
#ifdef __linux__
|
||||
if (pidCache == -1)
|
||||
pidCache = syscall(SYS_gettid);
|
||||
#else
|
||||
if (pidCache == -1) {
|
||||
uint64_t tidT;
|
||||
pthread_threadid_np(nullptr, &tidT);
|
||||
serverAssert(tidT < UINT_MAX);
|
||||
pidCache = (int)tidT;
|
||||
}
|
||||
#endif
|
||||
return pidCache;
|
||||
}
|
||||
|
||||
void printTrace()
|
||||
{
|
||||
#ifdef HAVE_BACKTRACE
|
||||
serverLog(3 /*LL_WARNING*/, "printing backtrace for thread %d", gettid());
|
||||
ucontext_t ctxt;
|
||||
getcontext(&ctxt);
|
||||
logStackTrace(&ctxt);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifdef __linux__
|
||||
static int futex(volatile unsigned *uaddr, int futex_op, int val,
|
||||
const struct timespec *timeout, int val3)
|
||||
{
|
||||
return syscall(SYS_futex, uaddr, futex_op, val,
|
||||
timeout, uaddr, val3);
|
||||
}
|
||||
#endif
|
||||
|
||||
class DeadlockDetector
|
||||
{
|
||||
fastlock m_lock { "deadlock detector" }; // destruct this first
|
||||
std::map<pid_t, fastlock *> m_mapwait;
|
||||
fastlock m_lock { "deadlock detector" };
|
||||
|
||||
public:
|
||||
void registerwait(fastlock *lock, pid_t thispid)
|
||||
{
|
||||
static volatile bool fInDeadlock = false;
|
||||
|
||||
if (lock == &m_lock || g_fInCrash)
|
||||
return;
|
||||
fastlock_lock(&m_lock);
|
||||
|
||||
if (fInDeadlock)
|
||||
{
|
||||
printTrace();
|
||||
fastlock_unlock(&m_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
m_mapwait.insert(std::make_pair(thispid, lock));
|
||||
|
||||
// Detect cycles
|
||||
@@ -169,7 +233,8 @@ public:
|
||||
auto itr = m_mapwait.find(pidCheck);
|
||||
if (itr == m_mapwait.end())
|
||||
break;
|
||||
pidCheck = itr->second->m_pidOwner;
|
||||
|
||||
__atomic_load(&itr->second->m_pidOwner, &pidCheck, __ATOMIC_RELAXED);
|
||||
if (pidCheck == thispid)
|
||||
{
|
||||
// Deadlock detected, printout some debugging info and crash
|
||||
@@ -180,10 +245,23 @@ public:
|
||||
{
|
||||
auto itr = m_mapwait.find(pidCheck);
|
||||
serverLog(3 /* LL_WARNING */, "\t%d: (%p) %s", pidCheck, itr->second, itr->second->szName);
|
||||
pidCheck = itr->second->m_pidOwner;
|
||||
__atomic_load(&itr->second->m_pidOwner, &pidCheck, __ATOMIC_RELAXED);
|
||||
if (pidCheck == thispid)
|
||||
break;
|
||||
}
|
||||
// Wake All sleeping threads so they can print their callstacks
|
||||
#ifdef HAVE_BACKTRACE
|
||||
#ifdef __linux__
|
||||
int mask = -1;
|
||||
fInDeadlock = true;
|
||||
fastlock_unlock(&m_lock);
|
||||
futex(&lock->m_ticket.u, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, mask);
|
||||
futex(&itr->second->m_ticket.u, FUTEX_WAKE_BITSET_PRIVATE, INT_MAX, nullptr, mask);
|
||||
sleep(2);
|
||||
fastlock_lock(&m_lock);
|
||||
printTrace();
|
||||
#endif
|
||||
#endif
|
||||
serverLog(3 /*LL_WARNING*/, "!!! KeyDB Will Now Crash !!!");
|
||||
_serverPanic(__FILE__, __LINE__, "Deadlock detected");
|
||||
}
|
||||
@@ -222,40 +300,28 @@ uint64_t fastlock_getlongwaitcount()
|
||||
return rval;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static int futex(volatile unsigned *uaddr, int futex_op, int val,
|
||||
const struct timespec *timeout, int val3)
|
||||
{
|
||||
return syscall(SYS_futex, uaddr, futex_op, val,
|
||||
timeout, uaddr, val3);
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" pid_t gettid()
|
||||
{
|
||||
static thread_local int pidCache = -1;
|
||||
#ifdef __linux__
|
||||
if (pidCache == -1)
|
||||
pidCache = syscall(SYS_gettid);
|
||||
#else
|
||||
if (pidCache == -1) {
|
||||
uint64_t tidT;
|
||||
pthread_threadid_np(nullptr, &tidT);
|
||||
assert(tidT < UINT_MAX);
|
||||
pidCache = (int)tidT;
|
||||
}
|
||||
#endif
|
||||
return pidCache;
|
||||
}
|
||||
|
||||
extern "C" void fastlock_sleep(fastlock *lock, pid_t pid, unsigned wake, unsigned mask)
|
||||
extern "C" void fastlock_sleep(fastlock *lock, pid_t pid, unsigned wake, unsigned myticket)
|
||||
{
|
||||
#ifdef __linux__
|
||||
g_dlock.registerwait(lock, pid);
|
||||
unsigned mask = (1U << (myticket % 32));
|
||||
__atomic_fetch_or(&lock->futex, mask, __ATOMIC_ACQUIRE);
|
||||
futex(&lock->m_ticket.u, FUTEX_WAIT_BITSET_PRIVATE, wake, nullptr, mask);
|
||||
|
||||
// double check the lock wasn't release between the last check and us setting the futex mask
|
||||
uint32_t u;
|
||||
__atomic_load(&lock->m_ticket.u, &u, __ATOMIC_ACQUIRE);
|
||||
if ((u & 0xffff) != myticket)
|
||||
{
|
||||
futex(&lock->m_ticket.u, FUTEX_WAIT_BITSET_PRIVATE, wake, nullptr, mask);
|
||||
}
|
||||
|
||||
__atomic_fetch_and(&lock->futex, ~mask, __ATOMIC_RELEASE);
|
||||
g_dlock.clearwait(lock, pid);
|
||||
#else
|
||||
UNUSED(lock);
|
||||
UNUSED(pid);
|
||||
UNUSED(wake);
|
||||
UNUSED(myticket);
|
||||
#endif
|
||||
__atomic_fetch_add(&g_longwaits, 1, __ATOMIC_RELAXED);
|
||||
}
|
||||
@@ -287,9 +353,9 @@ extern "C" void fastlock_lock(struct fastlock *lock)
|
||||
|
||||
int tid = gettid();
|
||||
unsigned myticket = __atomic_fetch_add(&lock->m_ticket.m_avail, 1, __ATOMIC_RELEASE);
|
||||
unsigned mask = (1U << (myticket % 32));
|
||||
unsigned cloops = 0;
|
||||
ticket ticketT;
|
||||
unsigned loopLimit = g_fHighCpuPressure ? 0x10000 : 0x100000;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
@@ -297,14 +363,10 @@ extern "C" void fastlock_lock(struct fastlock *lock)
|
||||
if ((ticketT.u & 0xffff) == myticket)
|
||||
break;
|
||||
|
||||
#if defined(__i386__) || defined(__amd64__)
|
||||
__asm__ __volatile__ ("pause");
|
||||
#elif defined(__aarch64__)
|
||||
__asm__ __volatile__ ("yield");
|
||||
#endif
|
||||
if ((++cloops % 0x100000) == 0)
|
||||
asm_yield();
|
||||
if ((++cloops % loopLimit) == 0)
|
||||
{
|
||||
fastlock_sleep(lock, tid, ticketT.u, mask);
|
||||
fastlock_sleep(lock, tid, ticketT.u, myticket);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +415,7 @@ extern "C" void fastlock_unlock(struct fastlock *lock)
|
||||
{
|
||||
int pidT;
|
||||
__atomic_load(&lock->m_pidOwner, &pidT, __ATOMIC_RELAXED);
|
||||
assert(pidT >= 0); // unlock after free
|
||||
serverAssert(pidT >= 0); // unlock after free
|
||||
int t = -1;
|
||||
__atomic_store(&lock->m_pidOwner, &t, __ATOMIC_RELEASE);
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
@@ -391,8 +453,9 @@ extern "C" void unlock_futex(struct fastlock *lock, uint16_t ifutex)
|
||||
extern "C" void fastlock_free(struct fastlock *lock)
|
||||
{
|
||||
// NOP
|
||||
assert((lock->m_ticket.m_active == lock->m_ticket.m_avail) // Asser the lock is unlocked
|
||||
|| (lock->m_pidOwner == gettid() && (lock->m_ticket.m_active == lock->m_ticket.m_avail-1))); // OR we own the lock and nobody else is waiting
|
||||
serverAssert((lock->m_ticket.m_active == lock->m_ticket.m_avail) // Assert the lock is unlocked
|
||||
|| (lock->m_pidOwner == gettid()
|
||||
&& (lock->m_ticket.m_active == static_cast<uint16_t>(lock->m_ticket.m_avail-1U)))); // OR we own the lock and nobody else is waiting
|
||||
lock->m_pidOwner = -2; // sentinal value indicating free
|
||||
ANNOTATE_RWLOCK_DESTROY(lock);
|
||||
}
|
||||
@@ -418,3 +481,134 @@ void fastlock_lock_recursive(struct fastlock *lock, int nesting)
|
||||
fastlock_lock(lock);
|
||||
lock->m_depth = nesting;
|
||||
}
|
||||
|
||||
void fastlock_auto_adjust_waits()
|
||||
{
|
||||
#ifdef __linux__
|
||||
struct sysinfo sysinf;
|
||||
auto fHighPressurePrev = g_fHighCpuPressure;
|
||||
memset(&sysinf, 0, sizeof sysinf);
|
||||
if (!sysinfo(&sysinf)) {
|
||||
auto avgCoreLoad = sysinf.loads[0] / get_nprocs();
|
||||
g_fHighCpuPressure = (avgCoreLoad > ((1 << SI_LOAD_SHIFT) * 0.9));
|
||||
if (g_fHighCpuPressure)
|
||||
serverLog(!fHighPressurePrev ? 3 /*LL_WARNING*/ : 1 /* LL_VERBOSE */, "NOTICE: Detuning locks due to high load per core: %.2f%%", avgCoreLoad / (double)(1 << SI_LOAD_SHIFT)*100.0);
|
||||
}
|
||||
|
||||
if (!g_fHighCpuPressure && fHighPressurePrev) {
|
||||
serverLog(3 /*LL_WARNING*/, "NOTICE: CPU pressure reduced");
|
||||
}
|
||||
#else
|
||||
g_fHighCpuPressure = g_fTestMode;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ASM_SPINLOCK
|
||||
extern "C" int mcs_spin_core(unsigned *locked, unsigned limit);
|
||||
#endif
|
||||
|
||||
void McsLock::lock(node *pnode)
|
||||
{
|
||||
serverAssert(pnode->depth >= 0);
|
||||
if (pnode->depth > 0) {
|
||||
++pnode->depth;
|
||||
return;
|
||||
}
|
||||
|
||||
pnode->pnext = nullptr;
|
||||
pnode->depth = 0;
|
||||
|
||||
McsLock::node *predecessor = nullptr;
|
||||
__atomic_exchange(&m_root, &pnode, &predecessor, __ATOMIC_ACQ_REL);
|
||||
|
||||
//if its null, we now own the lock and can leave, else....
|
||||
if (predecessor != nullptr){
|
||||
//when the predecessor unlocks, it will give us the lock
|
||||
pnode->locked = MCS_LOCKED;
|
||||
__atomic_store(&predecessor->pnext, &pnode, __ATOMIC_RELEASE);
|
||||
|
||||
#ifdef __linux__
|
||||
unsigned loopLimit = g_fHighCpuPressure ? 0x10000 : 0x100000;
|
||||
unsigned loopIter = 0;
|
||||
#endif
|
||||
|
||||
for (;;) {
|
||||
unsigned locked;
|
||||
__atomic_load(&pnode->locked, &locked, __ATOMIC_ACQUIRE);
|
||||
if (!locked)
|
||||
break;
|
||||
#ifdef __linux__
|
||||
++loopIter;
|
||||
if (loopIter == loopLimit) {
|
||||
unsigned lockedSet = MCS_FUTEX_LOCKED;
|
||||
unsigned lockedExpect = MCS_LOCKED;
|
||||
if (__atomic_compare_exchange(&pnode->locked, &lockedExpect, &lockedSet, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED) || lockedExpect == MCS_FUTEX_LOCKED) {
|
||||
futex(&pnode->locked, FUTEX_WAIT, MCS_FUTEX_LOCKED, nullptr, 0);
|
||||
}
|
||||
loopIter = 0;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
asm_yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
ANNOTATE_RWLOCK_ACQUIRED(this, true);
|
||||
pnode->depth = 1;
|
||||
}
|
||||
|
||||
bool McsLock::try_lock(node *pnode, bool fWeak)
|
||||
{
|
||||
if (pnode->depth > 0) {
|
||||
++pnode->depth;
|
||||
return true;
|
||||
}
|
||||
|
||||
pnode->pnext = nullptr;
|
||||
pnode->depth = 0;
|
||||
unsigned lockedSet = MCS_LOCKED;
|
||||
__atomic_store(&pnode->locked, &lockedSet, __ATOMIC_RELEASE);
|
||||
|
||||
McsLock::node *expected = nullptr;
|
||||
if (!__atomic_compare_exchange(&m_root, &expected, &pnode, fWeak, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pnode->depth = 1;
|
||||
ANNOTATE_RWLOCK_ACQUIRED(this, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void McsLock::unlock(node *pnode)
|
||||
{
|
||||
serverAssert(pnode->depth > 0);
|
||||
pnode->depth--;
|
||||
if (pnode->depth == 0)
|
||||
{
|
||||
McsLock::node *pnext;
|
||||
__atomic_load(&pnode->pnext, &pnext, __ATOMIC_ACQUIRE);
|
||||
if (pnext == nullptr) {
|
||||
node *desired = nullptr;
|
||||
auto *nodeT = pnode;
|
||||
if (__atomic_compare_exchange(&m_root, &nodeT, &desired, false, __ATOMIC_RELEASE, __ATOMIC_ACQUIRE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
__atomic_load(&pnode->pnext, &pnext, __ATOMIC_ACQUIRE);
|
||||
} while(pnext == nullptr);
|
||||
}
|
||||
|
||||
unsigned lockedSet = MCS_UNLOCKED;
|
||||
unsigned lockedActual;
|
||||
__atomic_exchange(&pnext->locked, &lockedSet, &lockedActual, __ATOMIC_RELEASE);
|
||||
ANNOTATE_RWLOCK_RELEASED(this, true);
|
||||
|
||||
#ifdef __linux__
|
||||
if (lockedActual == MCS_FUTEX_LOCKED) {
|
||||
futex(&pnext->locked, FUTEX_WAKE, INT_MAX, nullptr, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -15,6 +15,7 @@ void fastlock_unlock(struct fastlock *lock);
|
||||
void fastlock_free(struct fastlock *lock);
|
||||
int fastlock_unlock_recursive(struct fastlock *lock);
|
||||
void fastlock_lock_recursive(struct fastlock *lock, int nesting);
|
||||
void fastlock_auto_adjust_waits();
|
||||
|
||||
uint64_t fastlock_getlongwaitcount(); // this is a global value
|
||||
|
||||
@@ -84,4 +85,4 @@ struct fastlock
|
||||
#endif
|
||||
};
|
||||
|
||||
static_assert(offsetof(struct fastlock, m_ticket) == 64, "ensure padding is correct");
|
||||
static_assert(offsetof(struct fastlock, m_ticket) == 64, "ensure padding is correct");
|
||||
+10
-3
@@ -3,6 +3,7 @@
|
||||
|
||||
.extern gettid
|
||||
.extern fastlock_sleep
|
||||
.extern g_fHighCpuPressure
|
||||
|
||||
# This is the first use of assembly in this codebase, a valid question is WHY?
|
||||
# The spinlock we implement here is performance critical, and simply put GCC
|
||||
@@ -33,6 +34,13 @@ fastlock_lock:
|
||||
cmp [rdi], esi # Is the TID we got back the owner of the lock?
|
||||
je .LLocked # Don't spin in that case
|
||||
|
||||
mov r9d, 0x1000 # 1000h is set so we overflow on the 1024*1024'th iteration (like the C code)
|
||||
mov eax, [rip+g_fHighCpuPressure]
|
||||
test eax, eax
|
||||
jz .LNoTestMode
|
||||
mov r9d, 0x10000
|
||||
.LNoTestMode:
|
||||
|
||||
xor eax, eax # eliminate partial register dependency
|
||||
inc eax # we want to add one
|
||||
lock xadd [rdi+66], ax # do the xadd, ax contains the value before the addition
|
||||
@@ -45,8 +53,7 @@ fastlock_lock:
|
||||
cmp dx, ax # is our ticket up?
|
||||
je .LLocked # leave the loop
|
||||
pause
|
||||
add ecx, 0x1000 # Have we been waiting a long time? (oflow if we have)
|
||||
# 1000h is set so we overflow on the 1024*1024'th iteration (like the C code)
|
||||
add ecx, r9d # Have we been waiting a long time? (oflow if we have)
|
||||
jnc .LLoop # If so, give up our timeslice to someone who's doing real work
|
||||
# Like the compiler, you're probably thinking: "Hey! I should take these pushs out of the loop"
|
||||
# But the compiler doesn't know that we rarely hit this, and when we do we know the lock is
|
||||
@@ -62,7 +69,7 @@ fastlock_lock:
|
||||
# rdi ARG1 futex (already in rdi)
|
||||
# rsi ARG2 tid (already in esi)
|
||||
# rdx ARG3 ticketT.u (already in edx)
|
||||
bts ecx, eax # rcx ARG4 mask
|
||||
mov ecx, eax # rcx ARG4 myticket
|
||||
call fastlock_sleep
|
||||
# cleanup and continue
|
||||
pop rax
|
||||
|
||||
+14
-6
@@ -465,8 +465,8 @@ void georadiusGeneric(client *c, int flags) {
|
||||
int storedist = 0; /* 0 for STORE, 1 for STOREDIST. */
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj_roptr zobj;
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.null[c->resp])) == nullptr ||
|
||||
robj_roptr zobj = nullptr;
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.emptyarray)) == nullptr ||
|
||||
checkType(c, zobj, OBJ_ZSET)) {
|
||||
return;
|
||||
}
|
||||
@@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
|
||||
/* If no matching results, the user gets an empty reply. */
|
||||
if (ga->used == 0 && storekey == NULL) {
|
||||
addReplyNull(c);
|
||||
addReply(c,shared.emptyarray);
|
||||
geoArrayFree(ga);
|
||||
return;
|
||||
}
|
||||
@@ -657,13 +657,13 @@ void georadiusGeneric(client *c, int flags) {
|
||||
|
||||
if (returned_items) {
|
||||
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
|
||||
setKey(c->db,storekey,zobj);
|
||||
setKey(c,c->db,storekey,zobj);
|
||||
decrRefCount(zobj);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
|
||||
c->db->id);
|
||||
g_pserver->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);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
@@ -737,7 +737,15 @@ void geohashCommand(client *c) {
|
||||
char buf[12];
|
||||
int i;
|
||||
for (i = 0; i < 11; i++) {
|
||||
int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
|
||||
int idx;
|
||||
if (i == 10) {
|
||||
/* We have just 52 bits, but the API used to output
|
||||
* an 11 bytes geohash. For compatibility we assume
|
||||
* zero. */
|
||||
idx = 0;
|
||||
} else {
|
||||
idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
|
||||
}
|
||||
buf[i] = geoalphabet[idx];
|
||||
}
|
||||
buf[11] = '\0';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,13 +46,10 @@
|
||||
#define ECCENT (sqrt(1.0 - (RATIO *RATIO)))
|
||||
#define COM (0.5 * ECCENT)
|
||||
|
||||
/// @brief The usual PI/180 constant
|
||||
const double DEG_TO_RAD = 0.017453292519943295769236907684886;
|
||||
/// @brief Earth's quatratic mean radius for WGS-84
|
||||
const double EARTH_RADIUS_IN_METERS = 6372797.560856;
|
||||
|
||||
const double MERCATOR_MAX = 20037726.37;
|
||||
const double MERCATOR_MIN = -20037726.37;
|
||||
|
||||
static inline double deg_rad(double ang) { return ang * D_R; }
|
||||
static inline double rad_deg(double ang) { return ang / D_R; }
|
||||
|
||||
+187
-36
@@ -18,7 +18,8 @@ static char *commandGroups[] = {
|
||||
"hyperloglog",
|
||||
"cluster",
|
||||
"geo",
|
||||
"stream"
|
||||
"stream",
|
||||
"replication"
|
||||
};
|
||||
|
||||
struct commandHelp {
|
||||
@@ -28,6 +29,56 @@ 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 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",
|
||||
"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",
|
||||
@@ -44,7 +95,7 @@ struct commandHelp {
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "BGSAVE",
|
||||
"-",
|
||||
"[SCHEDULE]",
|
||||
"Asynchronously save the dataset to disk",
|
||||
9,
|
||||
"1.0.0" },
|
||||
@@ -80,7 +131,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 +144,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]",
|
||||
"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,
|
||||
8,
|
||||
"3.2" },
|
||||
{ "CLIENT SETNAME",
|
||||
"connection-name",
|
||||
"Set the current connection name",
|
||||
9,
|
||||
8,
|
||||
"2.6.9" },
|
||||
{ "CLIENT TRACKING",
|
||||
"ON|OFF [REDIRECT client-id] [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 +229,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 +259,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",
|
||||
@@ -365,7 +446,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" },
|
||||
@@ -414,6 +495,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",
|
||||
@@ -465,7 +551,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" },
|
||||
@@ -514,13 +600,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]",
|
||||
"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" },
|
||||
@@ -529,19 +645,24 @@ 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" },
|
||||
{ "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",
|
||||
@@ -550,12 +671,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" },
|
||||
@@ -600,10 +721,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] [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",
|
||||
@@ -679,6 +815,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 [subkey]",
|
||||
"Get the time to live for a key or subkey in milliseconds",
|
||||
@@ -735,7 +876,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" },
|
||||
@@ -755,13 +896,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",
|
||||
@@ -775,7 +916,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" },
|
||||
@@ -825,7 +966,7 @@ struct commandHelp {
|
||||
8,
|
||||
"1.0.0" },
|
||||
{ "SET",
|
||||
"key value [expiration EX seconds|PX milliseconds] [NX|XX]",
|
||||
"key value [EX seconds|PX milliseconds] [NX|XX] [KEEPTTL]",
|
||||
"Set the string value of a key",
|
||||
1,
|
||||
"1.0.0" },
|
||||
@@ -914,6 +1055,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",
|
||||
@@ -935,9 +1081,9 @@ struct commandHelp {
|
||||
3,
|
||||
"1.0.0" },
|
||||
{ "SWAPDB",
|
||||
"index index",
|
||||
"index1 index2",
|
||||
"Swaps two Redis databases",
|
||||
8,
|
||||
9,
|
||||
"4.0.0" },
|
||||
{ "SYNC",
|
||||
"-",
|
||||
@@ -995,7 +1141,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" },
|
||||
@@ -1010,7 +1156,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" },
|
||||
@@ -1035,12 +1181,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" },
|
||||
@@ -1173,7 +1319,12 @@ struct commandHelp {
|
||||
"name [single/repeat] [optional: start] delay script numkeys [key N] [arg N]",
|
||||
"Run a specified script after start + delay, optionally repeating every delay interval. The job may be cancelled by deleting the key associated with the job (name parameter)",
|
||||
10,
|
||||
"6.5.2"
|
||||
"6.5.2"},
|
||||
{ "KEYDB.HRENAME",
|
||||
"key [src hash key] [dst hash key]",
|
||||
"Rename a hash key, copying the value.",
|
||||
4,
|
||||
"6.5.3"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+7
-7
@@ -709,8 +709,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
|
||||
p += oplen;
|
||||
first += span;
|
||||
}
|
||||
if (span == 0) return -1; /* Invalid format. */
|
||||
if (p >= end) return -1; /* Invalid format. */
|
||||
if (span == 0 || p >= end) return -1; /* Invalid format. */
|
||||
|
||||
next = HLL_SPARSE_IS_XZERO(p) ? p+2 : p+1;
|
||||
if (next >= end) next = NULL;
|
||||
@@ -1220,7 +1219,7 @@ void pfaddCommand(client *c) {
|
||||
}
|
||||
hdr = (hllhdr*)ptrFromObj(o);
|
||||
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);
|
||||
g_pserver->dirty++;
|
||||
HLL_INVALIDATE_CACHE(hdr);
|
||||
@@ -1253,7 +1252,7 @@ void pfcountCommand(client *c) {
|
||||
if (o == nullptr) continue; /* Assume empty HLL for non existing var.*/
|
||||
if (isHLLObjectOrReply(c,o) != C_OK) return;
|
||||
|
||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
||||
/* Merge with this HLL with our 'max' HLL by setting max[i]
|
||||
* to MAX(max[i],hll[i]). */
|
||||
if (hllMerge(registers,HLL_REGISTERS,o) == C_ERR) {
|
||||
addReplySds(c,sdsnew(invalid_hll_err));
|
||||
@@ -1311,7 +1310,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]);
|
||||
g_pserver->dirty++;
|
||||
}
|
||||
addReplyLongLong(c,card);
|
||||
@@ -1340,7 +1339,7 @@ void pfmergeCommand(client *c) {
|
||||
hdr = (hllhdr*)ptrFromObj(o);
|
||||
if (hdr->encoding == HLL_DENSE) use_dense = 1;
|
||||
|
||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
||||
/* Merge with this HLL with our 'max' HLL by setting max[i]
|
||||
* to MAX(max[i],hll[i]). */
|
||||
if (hllMerge(max,sizeof(max),o) == C_ERR) {
|
||||
addReplySds(c,sdsnew(invalid_hll_err));
|
||||
@@ -1384,7 +1383,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);
|
||||
@@ -1549,6 +1548,7 @@ void pfdebugCommand(client *c) {
|
||||
sds decoded = sdsempty();
|
||||
|
||||
if (hdr->encoding != HLL_SPARSE) {
|
||||
sdsfree(decoded);
|
||||
addReplyError(c,"HLL encoding is not sparse");
|
||||
return;
|
||||
}
|
||||
|
||||
+3
-3
@@ -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 serie is created on demand in order to avoid
|
||||
* having a fixed list to maintain. */
|
||||
void latencyMonitorInit(void) {
|
||||
g_pserver->latency_events = dictCreate(&latencyTimeSeriesDictType,NULL);
|
||||
@@ -599,7 +599,7 @@ NULL
|
||||
event = (char*)dictGetKey(de);
|
||||
|
||||
graph = latencyCommandGenSparkeline(event,ts);
|
||||
addReplyBulkCString(c,graph);
|
||||
addReplyVerbatim(c,graph,sdslen(graph),"txt");
|
||||
sdsfree(graph);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"latest") && c->argc == 2) {
|
||||
/* LATENCY LATEST */
|
||||
@@ -608,7 +608,7 @@ NULL
|
||||
/* LATENCY DOCTOR */
|
||||
sds report = createLatencyReport();
|
||||
|
||||
addReplyBulkCBuffer(c,report,sdslen(report));
|
||||
addReplyVerbatim(c,report,sdslen(report),"txt");
|
||||
sdsfree(report);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"reset") && c->argc >= 2) {
|
||||
/* LATENCY RESET */
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
* field to NULL in order to lazy free it later. */
|
||||
if (de) {
|
||||
dictFreeUnlinkedEntry(db->pdict,de);
|
||||
if (g_pserver->cluster_enabled) slotToKeyDel(key);
|
||||
if (g_pserver->cluster_enabled) slotToKeyDel(szFromObj(key));
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
|
||||
+2
-2
@@ -52,8 +52,8 @@
|
||||
static int is_leap_year(time_t year) {
|
||||
if (year % 4) return 0; /* A year not divisible by 4 is not leap. */
|
||||
else if (year % 100) return 1; /* If div by 4 and not 100 is surely leap. */
|
||||
else if (year % 400) return 0; /* If div by 100 *and* 400 is not leap. */
|
||||
else return 1; /* If div by 100 and not by 400 is leap. */
|
||||
else if (year % 400) return 0; /* If div by 100 *and* not by 400 is not leap. */
|
||||
else return 1; /* If div by 100 and 400 is leap. */
|
||||
}
|
||||
|
||||
void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst) {
|
||||
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* This file implements the LOLWUT command. The command should do something
|
||||
* fun and interesting, and should be replaced by a new implementation at
|
||||
* each new version of Redis.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "lolwut.h"
|
||||
#include <math.h>
|
||||
|
||||
void lolwut5Command(client *c);
|
||||
void lolwut6Command(client *c);
|
||||
|
||||
/* The default target for LOLWUT if no matching version was found.
|
||||
* This is what unstable versions of Redis will display. */
|
||||
void lolwutUnstableCommand(client *c) {
|
||||
sds rendered = sdsnew("Redis ver. ");
|
||||
rendered = sdscat(rendered,REDIS_VERSION);
|
||||
rendered = sdscatlen(rendered,"\n",1);
|
||||
addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
|
||||
sdsfree(rendered);
|
||||
}
|
||||
|
||||
/* LOLWUT [VERSION <version>] [... version specific arguments ...] */
|
||||
void lolwutCommand(client *c) {
|
||||
char *v = REDIS_VERSION;
|
||||
char verstr[64];
|
||||
|
||||
if (c->argc >= 3 && !strcasecmp(c->argv[1]->ptr,"version")) {
|
||||
long ver;
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&ver,NULL) != C_OK) return;
|
||||
snprintf(verstr,sizeof(verstr),"%u.0.0",(unsigned int)ver);
|
||||
v = verstr;
|
||||
|
||||
/* Adjust argv/argc to filter the "VERSION ..." option, since the
|
||||
* specific LOLWUT version implementations don't know about it
|
||||
* and expect their arguments. */
|
||||
c->argv += 2;
|
||||
c->argc -= 2;
|
||||
}
|
||||
|
||||
if ((v[0] == '5' && v[1] == '.' && v[2] != '9') ||
|
||||
(v[0] == '4' && v[1] == '.' && v[2] == '9'))
|
||||
lolwut5Command(c);
|
||||
else if ((v[0] == '6' && v[1] == '.' && v[2] != '9') ||
|
||||
(v[0] == '5' && v[1] == '.' && v[2] == '9'))
|
||||
lolwut6Command(c);
|
||||
else
|
||||
lolwutUnstableCommand(c);
|
||||
|
||||
/* Fix back argc/argv in case of VERSION argument. */
|
||||
if (v == verstr) {
|
||||
c->argv -= 2;
|
||||
c->argc += 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================== LOLWUT Canvase ===============================
|
||||
* Many LOWUT 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. */
|
||||
|
||||
/* Allocate and return a new canvas of the specified size. */
|
||||
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);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/* Free the canvas created by lwCreateCanvas(). */
|
||||
void lwFreeCanvas(lwCanvas *canvas) {
|
||||
zfree(canvas->pixels);
|
||||
zfree(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.
|
||||
* 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) {
|
||||
if (x < 0 || x >= canvas->width ||
|
||||
y < 0 || y >= canvas->height) return;
|
||||
canvas->pixels[x+y*canvas->width] = color;
|
||||
}
|
||||
|
||||
/* Return the value of the specified pixel on the canvas. */
|
||||
int lwGetPixel(lwCanvas *canvas, int x, int y) {
|
||||
if (x < 0 || x >= canvas->width ||
|
||||
y < 0 || y >= canvas->height) return 0;
|
||||
return canvas->pixels[x+y*canvas->width];
|
||||
}
|
||||
|
||||
/* Draw a line from x1,y1 to x2,y2 using the Bresenham algorithm. */
|
||||
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color) {
|
||||
int dx = abs(x2-x1);
|
||||
int dy = abs(y2-y1);
|
||||
int sx = (x1 < x2) ? 1 : -1;
|
||||
int sy = (y1 < y2) ? 1 : -1;
|
||||
int err = dx-dy, e2;
|
||||
|
||||
while(1) {
|
||||
lwDrawPixel(canvas,x1,y1,color);
|
||||
if (x1 == x2 && y1 == y2) break;
|
||||
e2 = err*2;
|
||||
if (e2 > -dy) {
|
||||
err -= dy;
|
||||
x1 += sx;
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx;
|
||||
y1 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw a square centered at the specified x,y coordinates, with the specified
|
||||
* rotation angle and size. In order to write a rotated square, we use the
|
||||
* trivial fact that the parametric equation:
|
||||
*
|
||||
* x = sin(k)
|
||||
* y = cos(k)
|
||||
*
|
||||
* Describes a circle for values going from 0 to 2*PI. So basically if we start
|
||||
* at 45 degrees, that is k = PI/4, with the first point, and then we find
|
||||
* the other three points incrementing K by PI/2 (90 degrees), we'll have the
|
||||
* points of the square. In order to rotate the square, we just start with
|
||||
* k = PI/4 + rotation_angle, and we are done.
|
||||
*
|
||||
* Of course the vanilla equations above will describe the square inside a
|
||||
* circle of radius 1, so in order to draw larger squares we'll have to
|
||||
* multiply the obtained coordinates, and then translate them. However this
|
||||
* is much simpler than implementing the abstract concept of 2D shape and then
|
||||
* performing the rotation/translation transformation, so for LOLWUT it's
|
||||
* a good approach. */
|
||||
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color) {
|
||||
int px[4], py[4];
|
||||
|
||||
/* Adjust the desired size according to the fact that the square inscribed
|
||||
* into a circle of radius 1 has the side of length SQRT(2). This way
|
||||
* size becomes a simple multiplication factor we can use with our
|
||||
* coordinates to magnify them. */
|
||||
size /= 1.4142135623;
|
||||
size = round(size);
|
||||
|
||||
/* Compute the four points. */
|
||||
float k = M_PI/4 + angle;
|
||||
for (int j = 0; j < 4; j++) {
|
||||
px[j] = round(sin(k) * size + x);
|
||||
py[j] = round(cos(k) * size + y);
|
||||
k += M_PI/2;
|
||||
}
|
||||
|
||||
/* Draw the square. */
|
||||
for (int j = 0; j < 4; j++)
|
||||
lwDrawLine(canvas,px[j],py[j],px[(j+1)%4],py[(j+1)%4],color);
|
||||
}
|
||||
Loaded 100 of 251 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user