Compare commits
586
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ad44b76b9 | ||
|
|
d88f52ee7d | ||
|
|
399a6b2b47 | ||
|
|
451872527c | ||
|
|
3d5b2d41b5 | ||
|
|
30adc62232 | ||
|
|
96688aa646 | ||
|
|
a6350f717d | ||
|
|
f69876280c | ||
|
|
e93dcba46e | ||
|
|
9a9109431b | ||
|
|
bb30b9f913 | ||
|
|
094b47391d | ||
|
|
2c42f6a8b8 | ||
|
|
121c51f4f3 | ||
|
|
af3c722fec | ||
|
|
ca8d6f1072 | ||
|
|
8dc28b6c75 | ||
|
|
7bd5ccaad0 | ||
|
|
7261a5550f | ||
|
|
25063f75d7 | ||
|
|
af5c11874c | ||
|
|
4bc4d2c767 | ||
|
|
cf3789f045 | ||
|
|
b0bbfed238 | ||
|
|
c76d0e7be9 | ||
|
|
1b0d30aeb7 | ||
|
|
a76a961cdd | ||
|
|
f80b946bea | ||
|
|
a8857320a5 | ||
|
|
cbf212f981 | ||
|
|
4158664d2b | ||
|
|
4e8e7fb067 | ||
|
|
ef610802c7 | ||
|
|
5b73ea3cf6 | ||
|
|
eb9ca33414 | ||
|
|
78df016584 | ||
|
|
4665b3ebfb | ||
|
|
85a0d29d04 | ||
|
|
416d394cd9 | ||
|
|
0062194d74 | ||
|
|
10b626b3d5 | ||
|
|
2f53ad30ee | ||
|
|
5a3d85745a | ||
|
|
3f96e1623d | ||
|
|
88351275fe | ||
|
|
fa4f50739a | ||
|
|
dfef407499 | ||
|
|
8d6dc136e1 | ||
|
|
b73d87f5e5 | ||
|
|
babada8b5f | ||
|
|
80ec0431e8 | ||
|
|
545a5046f5 | ||
|
|
10afb9639a | ||
|
|
88e66ecf94 | ||
|
|
8cdc15c309 | ||
|
|
4cbf3f5ddd | ||
|
|
c9c03c3ee6 | ||
|
|
b3400559be | ||
|
|
1010c1b43e | ||
|
|
c4dc5b80b2 | ||
|
|
38076fd6ba | ||
|
|
1d13ff0b39 | ||
|
|
0c52ce6c8e | ||
|
|
a20c206de7 | ||
|
|
6c8221580c | ||
|
|
d6eb3afd13 | ||
|
|
1b4bc60999 | ||
|
|
4379b8b411 | ||
|
|
95f154985c | ||
|
|
9dcf878f1b | ||
|
|
7698316fda | ||
|
|
f6d5996ead | ||
|
|
3ef59b50c4 | ||
|
|
12d7479144 | ||
|
|
86c76cad74 | ||
|
|
1ef44f8243 | ||
|
|
3f0afab301 | ||
|
|
aca7f36b57 | ||
|
|
6d2f1188ac | ||
|
|
e8afadd52c | ||
|
|
36ee294e83 | ||
|
|
dd7e61d77f | ||
|
|
0e22cb2680 | ||
|
|
13e4c2a9b1 | ||
|
|
aa9d92d94a | ||
|
|
077f965426 | ||
|
|
8d11e0df7a | ||
|
|
324a8c91d0 | ||
|
|
8d116cc8a2 | ||
|
|
97f1c808ce | ||
|
|
9a1843ef2d | ||
|
|
1547d72cf3 | ||
|
|
2ea7f0ecad | ||
|
|
3b29556a0c | ||
|
|
c4d7f30e25 | ||
|
|
57fa355e56 | ||
|
|
f15042dbf0 | ||
|
|
643bc48a00 | ||
|
|
ec007559ff | ||
|
|
c80d81c80a | ||
|
|
61de1c1146 | ||
|
|
918086e253 | ||
|
|
38514e3c8d | ||
|
|
493a7f9823 | ||
|
|
19f5be231d | ||
|
|
7c5dc07016 | ||
|
|
b9e5be5f56 | ||
|
|
89f46f0fa1 | ||
|
|
d1788a5ddb | ||
|
|
34d5982bd5 | ||
|
|
a2732291cd | ||
|
|
2dab5015b7 | ||
|
|
4c08ae3ff6 | ||
|
|
fa9aa90813 | ||
|
|
5634ee973c | ||
|
|
93bb42a0b5 | ||
|
|
1e16b9384d | ||
|
|
5497a44037 | ||
|
|
f9c56dbb09 | ||
|
|
262262fbb2 | ||
|
|
86691ccff5 | ||
|
|
f16eaadd4f | ||
|
|
c1295bb9f1 | ||
|
|
c52415b219 | ||
|
|
82e1807769 | ||
|
|
f6029fb925 | ||
|
|
af5167b7f3 | ||
|
|
d9de9d5478 | ||
|
|
b3a97004f4 | ||
|
|
29b9d0a245 | ||
|
|
771df8a436 | ||
|
|
9321c7871f | ||
|
|
bce9a68b39 | ||
|
|
8609e68161 | ||
|
|
15338ab694 | ||
|
|
f1e4af2c29 | ||
|
|
c46c76a399 | ||
|
|
a6a0e05a1a | ||
|
|
573c4673ee | ||
|
|
681c6f21b7 | ||
|
|
606a01df70 | ||
|
|
453e01a091 | ||
|
|
ebf1acd33c | ||
|
|
0628030bf4 | ||
|
|
cf6cbbb881 | ||
|
|
3c95c92b0f | ||
|
|
22ae0ddf15 | ||
|
|
b1db7acf72 | ||
|
|
5babacad9b | ||
|
|
a5f5091041 | ||
|
|
2091d73ef5 | ||
|
|
513931dfea | ||
|
|
63c4697b46 | ||
|
|
7d703de7e9 | ||
|
|
336458d4b5 | ||
|
|
bd28dbee0e | ||
|
|
2bd360feca | ||
|
|
374b1192a3 | ||
|
|
106a57c5ae | ||
|
|
6548157293 | ||
|
|
13707f988b | ||
|
|
af347a8c86 | ||
|
|
27641ee490 | ||
|
|
1b72f4b749 | ||
|
|
2b7b74ad68 | ||
|
|
45ee620e9c | ||
|
|
4983eb6a23 | ||
|
|
3bab69e921 | ||
|
|
94376f46ad | ||
|
|
d16bb64fa4 | ||
|
|
ff024e3064 | ||
|
|
f0acdee4c5 | ||
|
|
2f3bfd1c13 | ||
|
|
5e2319c326 | ||
|
|
f88f8661ac | ||
|
|
d3e8f3fb62 | ||
|
|
bd07f121b9 | ||
|
|
6dd8de1774 | ||
|
|
7ca81170c7 | ||
|
|
98b23cce29 | ||
|
|
c8ae90fef6 | ||
|
|
8a3e9a6d44 | ||
|
|
86cbddc475 | ||
|
|
4ccb56d0e6 | ||
|
|
d7f18f5019 | ||
|
|
f0d9a4e1a4 | ||
|
|
d4e375fc1e | ||
|
|
45f318d2ff | ||
|
|
cf31602bce | ||
|
|
7e64aa80d8 | ||
|
|
9abdc089f8 | ||
|
|
43f39f2e37 | ||
|
|
ec73881a4d | ||
|
|
d8ab7e8012 | ||
|
|
e77db59660 | ||
|
|
652c532292 | ||
|
|
a477b68109 | ||
|
|
ca825701dc | ||
|
|
f7e05dc755 | ||
|
|
4f29753726 | ||
|
|
37612118c8 | ||
|
|
049969971e | ||
|
|
d2c5f80e2e | ||
|
|
79de8f3c25 | ||
|
|
685dcaa1e6 | ||
|
|
2f1a1c3835 | ||
|
|
c913be0464 | ||
|
|
537893420b | ||
|
|
1ab5508324 | ||
|
|
319a6831a9 | ||
|
|
07acdd4aa8 | ||
|
|
b6378edcd6 | ||
|
|
3fbb41ecc9 | ||
|
|
73305861f5 | ||
|
|
0a643efa0c | ||
|
|
376a806bfb | ||
|
|
a6c7e61e61 | ||
|
|
62adabd0e0 | ||
|
|
28bde89172 | ||
|
|
743cfc0ad6 | ||
|
|
9947956d7b | ||
|
|
8341a7d472 | ||
|
|
e78c4e813c | ||
|
|
c6954de3ea | ||
|
|
8a14fff545 | ||
|
|
a8d70ac568 | ||
|
|
ab8c383082 | ||
|
|
c552fad6d4 | ||
|
|
155526a44e | ||
|
|
e741b0c257 | ||
|
|
9fe02ce1cd | ||
|
|
42c4d6aec7 | ||
|
|
c8c78bd4b9 | ||
|
|
770cb0ba97 | ||
|
|
485425cec7 | ||
|
|
7d4ebe114f | ||
|
|
6fff2cf9b6 | ||
|
|
c1b5220ee7 | ||
|
|
d63a435362 | ||
|
|
df45fed050 | ||
|
|
20eeddfb8a | ||
|
|
ddb80bb3d4 | ||
|
|
8a44b2cc7e | ||
|
|
090bc0c1a3 | ||
|
|
8ea7a3ee68 | ||
|
|
47177c9edc | ||
|
|
f6e32a832f | ||
|
|
c21c23bbba | ||
|
|
6922ccc0b9 | ||
|
|
40194a2a68 | ||
|
|
8aa0b19d83 | ||
|
|
f54bb2a330 | ||
|
|
dc8f947d7c | ||
|
|
71f3f3f1af | ||
|
|
3f7ba86255 | ||
|
|
dfe126f3e9 | ||
|
|
f53cc00c09 | ||
|
|
67dbc5f365 | ||
|
|
256ec6c52f | ||
|
|
ef764dde1c | ||
|
|
85e4777d5c | ||
|
|
1ea6672430 | ||
|
|
d933d6f2a4 | ||
|
|
92357b2d61 | ||
|
|
46216b0e83 | ||
|
|
5c73a6e206 | ||
|
|
c8c3281f06 | ||
|
|
9c00bdd86e | ||
|
|
92dc5e1fa4 | ||
|
|
50d4326e3b | ||
|
|
d5c6a833c8 | ||
|
|
1012514e0f | ||
|
|
28ef18a894 | ||
|
|
5bfba06a3b | ||
|
|
2e1dd00c80 | ||
|
|
730cacf672 | ||
|
|
4abba65ec7 | ||
|
|
33f613bf87 | ||
|
|
4aafdb185a | ||
|
|
be520829b8 | ||
|
|
13741fb99d | ||
|
|
08b218bfa5 | ||
|
|
5558c0e4cf | ||
|
|
7cf53252ee | ||
|
|
44266d6d92 | ||
|
|
58a3a3fa68 | ||
|
|
711b13801e | ||
|
|
f25962337d | ||
|
|
15c7f1317e | ||
|
|
91c41b6dde | ||
|
|
31ffbf1133 | ||
|
|
e33fffbde1 | ||
|
|
6d29c34da7 | ||
|
|
69e8ea7143 | ||
|
|
aac6a4cf13 | ||
|
|
86e302f5f3 | ||
|
|
485d5d4a18 | ||
|
|
85cc696f50 | ||
|
|
d454d5a4f5 | ||
|
|
a17bddf2a1 | ||
|
|
7e1d5954e5 | ||
|
|
ac2c96f5b1 | ||
|
|
bb3d45a386 | ||
|
|
fe7e8dc955 | ||
|
|
edfe1b2f8b | ||
|
|
1e02d599dc | ||
|
|
3795aaf42a | ||
|
|
774d8cd721 | ||
|
|
44ac202fbf | ||
|
|
4abf0e1e10 | ||
|
|
ffdd0620d0 | ||
|
|
9ac6cb9ce4 | ||
|
|
1c7a2269d6 | ||
|
|
90fae58b49 | ||
|
|
64a73e9293 | ||
|
|
0c1a4b5576 | ||
|
|
540b917a26 | ||
|
|
cfe39b7859 | ||
|
|
6ca1ad1e27 | ||
|
|
eb2196f5ce | ||
|
|
01eaf53bb3 | ||
|
|
2400f5782e | ||
|
|
726abe4deb | ||
|
|
138ed120dd | ||
|
|
2fda5f5c98 | ||
|
|
560e1e6c4b | ||
|
|
53ac8c7df2 | ||
|
|
d72b7ed4fb | ||
|
|
2deb55512f | ||
|
|
b189a21974 | ||
|
|
396161765b | ||
|
|
93edb3ff3a | ||
|
|
30a466ba38 | ||
|
|
6671032faf | ||
|
|
17ff3173d6 | ||
|
|
bf53f9280a | ||
|
|
61dffd8669 | ||
|
|
e8d0057710 | ||
|
|
3e9e27e98f | ||
|
|
6f2240aefd | ||
|
|
bd60c11bd8 | ||
|
|
38f6207f88 | ||
|
|
dd05b7f67f | ||
|
|
67ee87522a | ||
|
|
3f6d00d387 | ||
|
|
7ef2270ee7 | ||
|
|
456bbc02c8 | ||
|
|
1d4ea00d12 | ||
|
|
2d7dc71b6f | ||
|
|
7d5fb5df3f | ||
|
|
28d146bed6 | ||
|
|
9506c860f6 | ||
|
|
baa88a1c14 | ||
|
|
d28948b143 | ||
|
|
a8c912ead6 | ||
|
|
24896427fc | ||
|
|
b5f52bf01c | ||
|
|
8105f91a02 | ||
|
|
b8b8dd5c15 | ||
|
|
9466dae4e5 | ||
|
|
5234bff579 | ||
|
|
51a01ca8a6 | ||
|
|
b8d315ff76 | ||
|
|
bb93686754 | ||
|
|
64ef0ad71a | ||
|
|
0f3c353dd3 | ||
|
|
f616ecf361 | ||
|
|
2ee4f9f34e | ||
|
|
c89ec5af1e | ||
|
|
0e38c5f39c | ||
|
|
7080a28efc | ||
|
|
fda2bf9c92 | ||
|
|
63e46e5f8d | ||
|
|
d6a13f4440 | ||
|
|
18c2676084 | ||
|
|
9387f7333e | ||
|
|
2f8134a7ff | ||
|
|
408e8e9f44 | ||
|
|
0af467d18f | ||
|
|
e5565a793e | ||
|
|
2bc8db9ca5 | ||
|
|
3848849013 | ||
|
|
0992ada2fe | ||
|
|
a3b0d8631f | ||
|
|
d7d13721d3 | ||
|
|
a351e74fe9 | ||
|
|
6e4f70b817 | ||
|
|
faea46710c | ||
|
|
5521910de7 | ||
|
|
3ff95d9074 | ||
|
|
1bae5dfff6 | ||
|
|
0c3fe52ef7 | ||
|
|
1f75ce30df | ||
|
|
0c898bdb07 | ||
|
|
660645c742 | ||
|
|
af02478ba0 | ||
|
|
337dcde9fa | ||
|
|
324e22accf | ||
|
|
dfb598cf33 | ||
|
|
d3a9dff6b9 | ||
|
|
9a7b6a9f51 | ||
|
|
f4b8197060 | ||
|
|
c5bc1c14c0 | ||
|
|
ef0b45ece8 | ||
|
|
5a72c5058c | ||
|
|
e6e58e455c | ||
|
|
5b9d3ac6c6 | ||
|
|
9d1baa0705 | ||
|
|
3c0ed0309a | ||
|
|
58554396d6 | ||
|
|
24044f3356 | ||
|
|
5e0fe4c77c | ||
|
|
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 | ||
|
|
173cca5413 | ||
|
|
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 | ||
|
|
8beec4f0e7 | ||
|
|
824f5f0b7a | ||
|
|
e542132b07 | ||
|
|
5350e7669e | ||
|
|
a68c19df6c | ||
|
|
060af1858d | ||
|
|
60ec2b78b3 | ||
|
|
0423309768 | ||
|
|
deebed23e1 | ||
|
|
4d580438b0 | ||
|
|
9f32d0eed2 | ||
|
|
5eb2290f68 | ||
|
|
a0cfd519e3 | ||
|
|
45af2b1742 | ||
|
|
5a8a005026 | ||
|
|
819a661be5 | ||
|
|
ff682d7902 | ||
|
|
70534846d1 | ||
|
|
412eb67d21 | ||
|
|
1398fac3f1 | ||
|
|
8b33975944 | ||
|
|
a344c4cfad | ||
|
|
42366959ec | ||
|
|
dfb12f0628 |
+13
-10
@@ -3,11 +3,8 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build-ubuntu:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest, ubuntu-16.04]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
test-ubuntu-latest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
@@ -15,13 +12,19 @@ jobs:
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.5
|
||||
make test
|
||||
./runtest --clients 2 --verbose
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --clients 2 --verbose
|
||||
|
||||
build-ubuntu-old:
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: make
|
||||
|
||||
build-macos-latest:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [macos-latest, macOS-10.14]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
.*.swp
|
||||
*.o
|
||||
*.xo
|
||||
*.so
|
||||
*.d
|
||||
*.log
|
||||
dump.rdb
|
||||
redis-benchmark
|
||||
@@ -28,3 +31,5 @@ deps/lua/src/liblua.a
|
||||
.prerequisites
|
||||
*.dSYM
|
||||
Makefile.dep
|
||||
.vscode/*
|
||||
.idea/*
|
||||
|
||||
@@ -35,6 +35,11 @@ It is as simple as:
|
||||
|
||||
% make
|
||||
|
||||
To build with TLS support, you'll need OpenSSL development libraries (e.g.
|
||||
libssl-dev on Debian/Ubuntu) and run:
|
||||
|
||||
% make BUILD_TLS=yes
|
||||
|
||||
You can run a 32 bit Redis binary using:
|
||||
|
||||
% make 32bit
|
||||
@@ -43,6 +48,13 @@ After building Redis, it is a good idea to test it using:
|
||||
|
||||
% make test
|
||||
|
||||
If TLS is built, running the tests with TLS enabled (you will need `tcl-tls`
|
||||
installed):
|
||||
|
||||
% ./utils/gen-test-certs.sh
|
||||
% ./runtest --tls
|
||||
|
||||
|
||||
Fixing build problems with dependencies or cached build options
|
||||
---------
|
||||
|
||||
@@ -125,6 +137,12 @@ as options using the command line. Examples:
|
||||
All the options in redis.conf are also supported as options using the command
|
||||
line, with exactly the same name.
|
||||
|
||||
Running Redis with TLS:
|
||||
------------------
|
||||
|
||||
Please consult the [TLS.md](TLS.md) file for more information on
|
||||
how to use Redis with TLS.
|
||||
|
||||
Playing with Redis
|
||||
------------------
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
TLS Support -- Work In Progress
|
||||
===============================
|
||||
|
||||
This is a brief note to capture current thoughts/ideas and track pending action
|
||||
items.
|
||||
TLS Support
|
||||
===========
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
@@ -69,37 +66,23 @@ probably not be so hard. For cluster keys migration it might be more difficult,
|
||||
but there are probably other good reasons to improve that part anyway.
|
||||
|
||||
To-Do List
|
||||
==========
|
||||
----------
|
||||
|
||||
Additional TLS Features
|
||||
-----------------------
|
||||
- [ ] 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.
|
||||
|
||||
1. Add metrics to INFO?
|
||||
2. Add session caching support. Check if/how it's handled by clients to assess
|
||||
how useful/important it is.
|
||||
|
||||
redis-benchmark
|
||||
---------------
|
||||
|
||||
The current implementation is a mix of using hiredis for parsing and basic
|
||||
networking (establishing connections), but directly manipulating sockets for
|
||||
most actions.
|
||||
|
||||
This will need to be cleaned up for proper TLS support. The best approach is
|
||||
probably to migrate to hiredis async mode.
|
||||
|
||||
redis-cli
|
||||
---------
|
||||
|
||||
1. Add support for TLS in --slave and --rdb modes.
|
||||
|
||||
Others
|
||||
------
|
||||
Multi-port
|
||||
----------
|
||||
|
||||
Consider the implications of allowing TLS to be configured on a separate port,
|
||||
making Redis listening on multiple ports.
|
||||
making Redis listening on multiple ports:
|
||||
|
||||
This impacts many things, like
|
||||
1. Startup banner port notification
|
||||
2. Proctitle
|
||||
3. How slaves announce themselves
|
||||
|
||||
Vendored
+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;
|
||||
|
||||
+131
-31
@@ -142,7 +142,8 @@ tcp-keepalive 300
|
||||
# server to connected clients, masters or cluster peers. These files should be
|
||||
# PEM formatted.
|
||||
#
|
||||
# tls-cert-file redis.crt tls-key-file redis.key
|
||||
# tls-cert-file redis.crt
|
||||
# tls-key-file redis.key
|
||||
|
||||
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
|
||||
#
|
||||
@@ -155,29 +156,27 @@ tcp-keepalive 300
|
||||
# tls-ca-cert-file ca.crt
|
||||
# tls-ca-cert-dir /etc/ssl/certs
|
||||
|
||||
# If TLS/SSL clients are required to authenticate using a client side
|
||||
# certificate, use this directive.
|
||||
# By default, clients (including replica servers) on a TLS port are required
|
||||
# to authenticate using valid client side certificates.
|
||||
#
|
||||
# Note: this applies to all incoming clients, including replicas.
|
||||
# It is possible to disable authentication using this directive.
|
||||
#
|
||||
# tls-auth-clients yes
|
||||
# tls-auth-clients no
|
||||
|
||||
# If TLS/SSL should be used when connecting as a replica to a master, enable
|
||||
# this configuration directive:
|
||||
# By default, a Redis replica does not attempt to establish a TLS connection
|
||||
# with its master.
|
||||
#
|
||||
# Use the following directive to enable TLS on replication links.
|
||||
#
|
||||
# tls-replication yes
|
||||
|
||||
# If TLS/SSL should be used for the Redis Cluster bus, enable this configuration
|
||||
# directive.
|
||||
#
|
||||
# NOTE: If TLS/SSL is enabled for Cluster Bus, mutual authentication is always
|
||||
# enforced.
|
||||
# By default, the Redis Cluster bus uses a plain TCP connection. To enable
|
||||
# TLS for the bus protocol, use the following directive:
|
||||
#
|
||||
# tls-cluster yes
|
||||
|
||||
# Explicitly specify TLS versions to support. Allowed values are case insensitive
|
||||
# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or
|
||||
# "default" which is currently >= TLSv1.1.
|
||||
# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1)
|
||||
#
|
||||
# tls-protocols TLSv1.2
|
||||
|
||||
@@ -197,7 +196,7 @@ tcp-keepalive 300
|
||||
# When choosing a cipher, use the server's preference instead of the client
|
||||
# preference. By default, the server follows the client's preference.
|
||||
#
|
||||
# tls-prefer-server-cipher yes
|
||||
# tls-prefer-server-ciphers yes
|
||||
|
||||
################################# GENERAL #####################################
|
||||
|
||||
@@ -322,6 +321,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
|
||||
@@ -811,13 +823,13 @@ replica-priority 100
|
||||
# maxmemory <bytes>
|
||||
|
||||
# MAXMEMORY POLICY: how Redis 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.
|
||||
@@ -872,6 +884,23 @@ replica-priority 100
|
||||
#
|
||||
# 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 ####################################
|
||||
|
||||
# Redis has two primitives to delete keys. One is called DEL and is a blocking
|
||||
@@ -914,13 +943,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 Redis asynchronously dumps the dataset on disk. This mode is
|
||||
@@ -1177,6 +1259,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.
|
||||
|
||||
@@ -1283,7 +1381,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
|
||||
@@ -1533,7 +1635,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
|
||||
@@ -1606,10 +1708,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?
|
||||
# -------------------------------
|
||||
#
|
||||
@@ -1649,7 +1747,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
|
||||
@@ -1660,11 +1758,13 @@ 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
|
||||
|
||||
@@ -22,4 +22,7 @@ $TCLSH tests/test_helper.tcl \
|
||||
--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
|
||||
|
||||
+42
-4
@@ -32,6 +32,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
|
||||
@@ -77,6 +78,15 @@ FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LIBS=-lm
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
# Linux ARM needs -latomic at linking time
|
||||
ifneq (,$(filter aarch64 armv,$(uname_M)))
|
||||
FINAL_LIBS+=-latomic
|
||||
else
|
||||
ifneq (,$(findstring armv,$(uname_M)))
|
||||
FINAL_LIBS+=-latomic
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
ifneq ($(@@),32bit)
|
||||
@@ -131,6 +141,30 @@ endif
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -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_LIBS+= -ltcmalloc
|
||||
@@ -172,11 +206,11 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.o
|
||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.o timeout.o
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o crc64.o siphash.o crc16.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o
|
||||
REDIS_CHECK_RDB_NAME=redis-check-rdb
|
||||
REDIS_CHECK_AOF_NAME=redis-check-aof
|
||||
|
||||
@@ -249,14 +283,18 @@ $(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
||||
dict-benchmark: dict.c zmalloc.c 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)
|
||||
|
||||
# Because the jemalloc.h header is generated as a part of the jemalloc build,
|
||||
# building it should complete before building any other object. Instead of
|
||||
# depending on a single artifact, build all dependencies first.
|
||||
%.o: %.c .make-prerequisites
|
||||
$(REDIS_CC) -c $<
|
||||
$(REDIS_CC) -MMD -o $@ -c $<
|
||||
|
||||
clean:
|
||||
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep dict-benchmark
|
||||
rm -f $(DEP)
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ list *UsersToLoad; /* This is a list of users found in the configuration file
|
||||
array of SDS pointers: the first is the user name,
|
||||
all the remaining pointers are ACL rules in the same
|
||||
format as ACLSetUser(). */
|
||||
list *ACLLog; /* Our security log, the user is able to inspect that
|
||||
using the ACL LOG command .*/
|
||||
|
||||
struct ACLCategoryItem {
|
||||
const char *name;
|
||||
@@ -93,6 +95,7 @@ struct ACLUserFlag {
|
||||
void ACLResetSubcommandsForCommand(user *u, unsigned long id);
|
||||
void ACLResetSubcommands(user *u);
|
||||
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
|
||||
void ACLFreeLogEntry(void *le);
|
||||
|
||||
/* The length of the string representation of a hashed password. */
|
||||
#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
|
||||
@@ -183,12 +186,12 @@ int ACLListMatchSds(void *a, void *b) {
|
||||
return sdscmp(a,b) == 0;
|
||||
}
|
||||
|
||||
/* Method to free list elements from ACL users password/ptterns lists. */
|
||||
/* Method to free list elements from ACL users password/patterns lists. */
|
||||
void ACLListFreeSds(void *item) {
|
||||
sdsfree(item);
|
||||
}
|
||||
|
||||
/* Method to duplicate list elements from ACL users password/ptterns lists. */
|
||||
/* Method to duplicate list elements from ACL users password/patterns lists. */
|
||||
void *ACLListDupSds(void *item) {
|
||||
return sdsdup(item);
|
||||
}
|
||||
@@ -896,16 +899,6 @@ char *ACLSetUserStringError(void) {
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
/* Return the first password of the default user or NULL.
|
||||
* This function is needed for backward compatibility with the old
|
||||
* directive "requirepass" when Redis supported a single global
|
||||
* password. */
|
||||
sds ACLDefaultUserFirstPassword(void) {
|
||||
if (listLength(DefaultUser->passwords) == 0) return NULL;
|
||||
listNode *first = listFirst(DefaultUser->passwords);
|
||||
return listNodeValue(first);
|
||||
}
|
||||
|
||||
/* Initialize the default user, that will always exist for all the process
|
||||
* lifetime. */
|
||||
void ACLInitDefaultUser(void) {
|
||||
@@ -920,7 +913,9 @@ void ACLInitDefaultUser(void) {
|
||||
void ACLInit(void) {
|
||||
Users = raxNew();
|
||||
UsersToLoad = listCreate();
|
||||
ACLLog = listCreate();
|
||||
ACLInitDefaultUser();
|
||||
server.requirepass = NULL; /* Only used for backward compatibility. */
|
||||
}
|
||||
|
||||
/* Check the username and password pair and return C_OK if they are valid,
|
||||
@@ -978,6 +973,7 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password) {
|
||||
moduleNotifyUserChanged(c);
|
||||
return C_OK;
|
||||
} else {
|
||||
addACLLogEntry(c,ACL_DENIED_AUTH,0,username->ptr);
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
@@ -1034,7 +1030,7 @@ user *ACLGetUserByName(const char *name, size_t namelen) {
|
||||
* command cannot be executed because the user is not allowed to run such
|
||||
* command, the second if the command is denied because the user is trying
|
||||
* to access keys that are not among the specified patterns. */
|
||||
int ACLCheckCommandPerm(client *c) {
|
||||
int ACLCheckCommandPerm(client *c, int *keyidxptr) {
|
||||
user *u = c->user;
|
||||
uint64_t id = c->cmd->id;
|
||||
|
||||
@@ -1094,6 +1090,7 @@ int ACLCheckCommandPerm(client *c) {
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
if (keyidxptr) *keyidxptr = keyidx[j];
|
||||
getKeysFreeResult(keyidx);
|
||||
return ACL_DENIED_KEY;
|
||||
}
|
||||
@@ -1454,6 +1451,131 @@ void ACLLoadUsersAtStartup(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* ACL log
|
||||
* ==========================================================================*/
|
||||
|
||||
#define ACL_LOG_CTX_TOPLEVEL 0
|
||||
#define ACL_LOG_CTX_LUA 1
|
||||
#define ACL_LOG_CTX_MULTI 2
|
||||
#define ACL_LOG_GROUPING_MAX_TIME_DELTA 60000
|
||||
|
||||
/* This structure defines an entry inside the ACL log. */
|
||||
typedef struct ACLLogEntry {
|
||||
uint64_t count; /* Number of times this happened recently. */
|
||||
int reason; /* Reason for denying the command. ACL_DENIED_*. */
|
||||
int context; /* Toplevel, Lua or MULTI/EXEC? ACL_LOG_CTX_*. */
|
||||
sds object; /* The key name or command name. */
|
||||
sds username; /* User the client is authenticated with. */
|
||||
mstime_t ctime; /* Milliseconds time of last update to this entry. */
|
||||
sds cinfo; /* Client info (last client if updated). */
|
||||
} ACLLogEntry;
|
||||
|
||||
/* This function will check if ACL entries 'a' and 'b' are similar enough
|
||||
* that we should actually update the existing entry in our ACL log instead
|
||||
* of creating a new one. */
|
||||
int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) {
|
||||
if (a->reason != b->reason) return 0;
|
||||
if (a->context != b->context) return 0;
|
||||
mstime_t delta = a->ctime - b->ctime;
|
||||
if (delta < 0) delta = -delta;
|
||||
if (delta > ACL_LOG_GROUPING_MAX_TIME_DELTA) return 0;
|
||||
if (sdscmp(a->object,b->object) != 0) return 0;
|
||||
if (sdscmp(a->username,b->username) != 0) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Release an ACL log entry. */
|
||||
void ACLFreeLogEntry(void *leptr) {
|
||||
ACLLogEntry *le = leptr;
|
||||
sdsfree(le->object);
|
||||
sdsfree(le->username);
|
||||
sdsfree(le->cinfo);
|
||||
zfree(le);
|
||||
}
|
||||
|
||||
/* Adds a new entry in the ACL log, making sure to delete the old entry
|
||||
* if we reach the maximum length allowed for the log. This function attempts
|
||||
* to find similar entries in the current log in order to bump the counter of
|
||||
* the log entry instead of creating many entries for very similar ACL
|
||||
* rules issues.
|
||||
*
|
||||
* The keypos argument is only used when the reason is ACL_DENIED_KEY, since
|
||||
* it allows the function to log the key name that caused the problem.
|
||||
* Similarly the username is only passed when we failed to authenticate the
|
||||
* user with AUTH or HELLO, for the ACL_DENIED_AUTH reason. Otherwise
|
||||
* it will just be NULL.
|
||||
*/
|
||||
void addACLLogEntry(client *c, int reason, int keypos, sds username) {
|
||||
/* Create a new entry. */
|
||||
struct ACLLogEntry *le = zmalloc(sizeof(*le));
|
||||
le->count = 1;
|
||||
le->reason = reason;
|
||||
le->username = sdsdup(reason == ACL_DENIED_AUTH ? username : c->user->name);
|
||||
le->ctime = mstime();
|
||||
|
||||
switch(reason) {
|
||||
case ACL_DENIED_CMD: le->object = sdsnew(c->cmd->name); break;
|
||||
case ACL_DENIED_KEY: le->object = sdsnew(c->argv[keypos]->ptr); break;
|
||||
case ACL_DENIED_AUTH: le->object = sdsnew(c->argv[0]->ptr); break;
|
||||
default: le->object = sdsempty();
|
||||
}
|
||||
|
||||
client *realclient = c;
|
||||
if (realclient->flags & CLIENT_LUA) realclient = server.lua_caller;
|
||||
|
||||
le->cinfo = catClientInfoString(sdsempty(),realclient);
|
||||
if (c->flags & CLIENT_MULTI) {
|
||||
le->context = ACL_LOG_CTX_MULTI;
|
||||
} else if (c->flags & CLIENT_LUA) {
|
||||
le->context = ACL_LOG_CTX_LUA;
|
||||
} else {
|
||||
le->context = ACL_LOG_CTX_TOPLEVEL;
|
||||
}
|
||||
|
||||
/* Try to match this entry with past ones, to see if we can just
|
||||
* update an existing entry instead of creating a new one. */
|
||||
long toscan = 10; /* Do a limited work trying to find duplicated. */
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(ACLLog,&li);
|
||||
ACLLogEntry *match = NULL;
|
||||
while (toscan-- && (ln = listNext(&li)) != NULL) {
|
||||
ACLLogEntry *current = listNodeValue(ln);
|
||||
if (ACLLogMatchEntry(current,le)) {
|
||||
match = current;
|
||||
listDelNode(ACLLog,ln);
|
||||
listAddNodeHead(ACLLog,current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If there is a match update the entry, otherwise add it as a
|
||||
* new one. */
|
||||
if (match) {
|
||||
/* We update a few fields of the existing entry and bump the
|
||||
* counter of events for this entry. */
|
||||
sdsfree(match->cinfo);
|
||||
match->cinfo = le->cinfo;
|
||||
match->ctime = le->ctime;
|
||||
match->count++;
|
||||
|
||||
/* Release the old entry. */
|
||||
le->cinfo = NULL;
|
||||
ACLFreeLogEntry(le);
|
||||
} else {
|
||||
/* Add it to our list of entires. We'll have to trim the list
|
||||
* to its maximum size. */
|
||||
listAddNodeHead(ACLLog, le);
|
||||
while(listLength(ACLLog) > server.acllog_max_len) {
|
||||
listNode *ln = listLast(ACLLog);
|
||||
ACLLogEntry *le = listNodeValue(ln);
|
||||
ACLFreeLogEntry(le);
|
||||
listDelNode(ACLLog,ln);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* ACL related commands
|
||||
* ==========================================================================*/
|
||||
@@ -1461,6 +1583,7 @@ 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>]
|
||||
@@ -1469,6 +1592,7 @@ void ACLLoadUsersAtStartup(void) {
|
||||
* ACL GETUSER <username>
|
||||
* ACL GENPASS
|
||||
* ACL WHOAMI
|
||||
* ACL LOG [<count> | RESET]
|
||||
*/
|
||||
void aclCommand(client *c) {
|
||||
char *sub = c->argv[1]->ptr;
|
||||
@@ -1655,18 +1779,86 @@ void aclCommand(client *c) {
|
||||
char pass[32]; /* 128 bits of actual pseudo random data. */
|
||||
getRandomHexChars(pass,sizeof(pass));
|
||||
addReplyBulkCBuffer(c,pass,sizeof(pass));
|
||||
} 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 entires the user wants to display, or alternatively
|
||||
* the "RESET" command in order to flush the old entires. */
|
||||
if (c->argc == 3) {
|
||||
if (!strcasecmp(c->argv[2]->ptr,"reset")) {
|
||||
listSetFreeMethod(ACLLog,ACLFreeLogEntry);
|
||||
listEmpty(ACLLog);
|
||||
listSetFreeMethod(ACLLog,NULL);
|
||||
addReply(c,shared.ok);
|
||||
return;
|
||||
} else if (getLongFromObjectOrReply(c,c->argv[2],&count,NULL)
|
||||
!= C_OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (count < 0) count = 0;
|
||||
}
|
||||
|
||||
/* Fix the count according to the number of entries we got. */
|
||||
if ((size_t)count > listLength(ACLLog))
|
||||
count = listLength(ACLLog);
|
||||
|
||||
addReplyArrayLen(c,count);
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(ACLLog,&li);
|
||||
mstime_t now = mstime();
|
||||
while (count-- && (ln = listNext(&li)) != NULL) {
|
||||
ACLLogEntry *le = listNodeValue(ln);
|
||||
addReplyMapLen(c,7);
|
||||
addReplyBulkCString(c,"count");
|
||||
addReplyLongLong(c,le->count);
|
||||
|
||||
addReplyBulkCString(c,"reason");
|
||||
char *reasonstr;
|
||||
switch(le->reason) {
|
||||
case ACL_DENIED_CMD: reasonstr="command"; break;
|
||||
case ACL_DENIED_KEY: reasonstr="key"; break;
|
||||
case ACL_DENIED_AUTH: reasonstr="auth"; break;
|
||||
default: reasonstr="unknown";
|
||||
}
|
||||
addReplyBulkCString(c,reasonstr);
|
||||
|
||||
addReplyBulkCString(c,"context");
|
||||
char *ctxstr;
|
||||
switch(le->context) {
|
||||
case ACL_LOG_CTX_TOPLEVEL: ctxstr="toplevel"; break;
|
||||
case ACL_LOG_CTX_MULTI: ctxstr="multi"; break;
|
||||
case ACL_LOG_CTX_LUA: ctxstr="lua"; break;
|
||||
default: ctxstr="unknown";
|
||||
}
|
||||
addReplyBulkCString(c,ctxstr);
|
||||
|
||||
addReplyBulkCString(c,"object");
|
||||
addReplyBulkCBuffer(c,le->object,sdslen(le->object));
|
||||
addReplyBulkCString(c,"username");
|
||||
addReplyBulkCBuffer(c,le->username,sdslen(le->username));
|
||||
addReplyBulkCString(c,"age-seconds");
|
||||
double age = (double)(now - le->ctime)/1000;
|
||||
addReplyDouble(c,age);
|
||||
addReplyBulkCString(c,"client-info");
|
||||
addReplyBulkCBuffer(c,le->cinfo,sdslen(le->cinfo));
|
||||
}
|
||||
} else if (!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 -- Generate a secure user password.",
|
||||
"WHOAMI -- Return the current connection username.",
|
||||
"LOG [<count> | RESET] -- Show the ACL log entries.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c,help);
|
||||
|
||||
+1
-1
@@ -66,7 +66,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 */
|
||||
|
||||
@@ -135,6 +135,14 @@ void aeDeleteEventLoop(aeEventLoop *eventLoop) {
|
||||
aeApiFree(eventLoop);
|
||||
zfree(eventLoop->events);
|
||||
zfree(eventLoop->fired);
|
||||
|
||||
/* Free the time events list. */
|
||||
aeTimeEvent *next_te, *te = eventLoop->timeEventHead;
|
||||
while (te) {
|
||||
next_te = te->next;
|
||||
zfree(te);
|
||||
te = next_te;
|
||||
}
|
||||
zfree(eventLoop);
|
||||
}
|
||||
|
||||
@@ -456,6 +464,7 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
if (!invert && fe->mask & mask & AE_READABLE) {
|
||||
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
|
||||
fired++;
|
||||
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
|
||||
}
|
||||
|
||||
/* Fire the writable event. */
|
||||
@@ -468,8 +477,11 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
|
||||
/* If we have to invert the call, fire the readable event now
|
||||
* after the writable one. */
|
||||
if (invert && fe->mask & mask & AE_READABLE) {
|
||||
if (!fired || fe->wfileProc != fe->rfileProc) {
|
||||
if (invert) {
|
||||
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
|
||||
if ((fe->mask & mask & AE_READABLE) &&
|
||||
(!fired || fe->wfileProc != fe->rfileProc))
|
||||
{
|
||||
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
|
||||
fired++;
|
||||
}
|
||||
|
||||
@@ -242,6 +242,7 @@ void stopAppendOnly(void) {
|
||||
server.aof_fd = -1;
|
||||
server.aof_selected_db = -1;
|
||||
server.aof_state = AOF_OFF;
|
||||
server.aof_rewrite_scheduled = 0;
|
||||
killAppendOnlyChild();
|
||||
}
|
||||
|
||||
@@ -781,18 +782,26 @@ int loadAppendOnlyFile(char *filename) {
|
||||
argc = atoi(buf+1);
|
||||
if (argc < 1) goto fmterr;
|
||||
|
||||
/* Load the next command in the AOF as our fake client
|
||||
* argv. */
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
fakeClient->argc = argc;
|
||||
fakeClient->argv = argv;
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
/* Parse the argument len. */
|
||||
char *readres = fgets(buf,sizeof(buf),fp);
|
||||
if (readres == NULL || buf[0] != '$') {
|
||||
fakeClient->argc = j; /* Free up to j-1. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr;
|
||||
if (readres == NULL)
|
||||
goto readerr;
|
||||
else
|
||||
goto fmterr;
|
||||
}
|
||||
if (buf[0] != '$') goto fmterr;
|
||||
len = strtol(buf+1,NULL,10);
|
||||
|
||||
/* Read it into a string object. */
|
||||
argsds = sdsnewlen(SDS_NOINIT,len);
|
||||
if (len && fread(argsds,len,1,fp) == 0) {
|
||||
sdsfree(argsds);
|
||||
@@ -801,10 +810,12 @@ int loadAppendOnlyFile(char *filename) {
|
||||
goto readerr;
|
||||
}
|
||||
argv[j] = createObject(OBJ_STRING,argsds);
|
||||
|
||||
/* Discard CRLF. */
|
||||
if (fread(buf,2,1,fp) == 0) {
|
||||
fakeClient->argc = j+1; /* Free up to j. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr; /* discard CRLF */
|
||||
goto readerr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -820,7 +831,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
if (cmd == server.multiCommand) valid_before_multi = valid_up_to;
|
||||
|
||||
/* Run the command in the context of a fake client */
|
||||
fakeClient->cmd = cmd;
|
||||
fakeClient->cmd = fakeClient->lastcmd = cmd;
|
||||
if (fakeClient->flags & CLIENT_MULTI &&
|
||||
fakeClient->cmd->proc != execCommand)
|
||||
{
|
||||
@@ -1139,7 +1150,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;
|
||||
}
|
||||
@@ -1201,12 +1212,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;
|
||||
}
|
||||
@@ -1766,7 +1778,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
server.aof_selected_db = -1; /* Make sure SELECT is re-issued */
|
||||
aofUpdateCurrentSize();
|
||||
server.aof_rewrite_base_size = server.aof_current_size;
|
||||
server.aof_current_size = server.aof_current_size;
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
|
||||
/* Clear regular AOF buffer since its contents was just written to
|
||||
* the new AOF from the background rewrite buffer. */
|
||||
@@ -1787,14 +1799,15 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Background AOF rewrite signal handler took %lldus", ustime()-now);
|
||||
} else if (!bysignal && exitcode != 0) {
|
||||
server.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)
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated with error");
|
||||
} else {
|
||||
server.aof_lastbgrewrite_status = C_ERR;
|
||||
|
||||
serverLog(LL_WARNING,
|
||||
"Background AOF rewrite terminated by signal %d", bysignal);
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
char *ascii_logo =
|
||||
const char *ascii_logo =
|
||||
" _._ \n"
|
||||
" _.-``__ ''-._ \n"
|
||||
" _.-`` `. `_. ''-._ Redis %s (%s/%d) %s bit\n"
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __BIO_H
|
||||
#define __BIO_H
|
||||
|
||||
/* Exported API */
|
||||
void bioInit(void);
|
||||
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3);
|
||||
@@ -40,3 +43,5 @@ void bioKillThreads(void);
|
||||
#define BIO_AOF_FSYNC 1 /* Deferred AOF fsync. */
|
||||
#define BIO_LAZY_FREE 2 /* Deferred objects freeing. */
|
||||
#define BIO_NUM_OPS 3
|
||||
|
||||
#endif
|
||||
|
||||
+21
-1
@@ -902,6 +902,9 @@ void bitposCommand(client *c) {
|
||||
* OVERFLOW [WRAP|SAT|FAIL]
|
||||
*/
|
||||
|
||||
#define BITFIELD_FLAG_NONE 0
|
||||
#define BITFIELD_FLAG_READONLY (1<<0)
|
||||
|
||||
struct bitfieldOp {
|
||||
uint64_t offset; /* Bitfield offset. */
|
||||
int64_t i64; /* Increment amount (INCRBY) or SET value */
|
||||
@@ -911,7 +914,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 *o;
|
||||
size_t bitoffset;
|
||||
int j, numops = 0, changes = 0;
|
||||
@@ -999,6 +1005,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,
|
||||
@@ -1129,3 +1141,11 @@ void bitfieldCommand(client *c) {
|
||||
}
|
||||
zfree(ops);
|
||||
}
|
||||
|
||||
void bitfieldCommand(client *c) {
|
||||
bitfieldGeneric(c, BITFIELD_FLAG_NONE);
|
||||
}
|
||||
|
||||
void bitfieldroCommand(client *c) {
|
||||
bitfieldGeneric(c, BITFIELD_FLAG_READONLY);
|
||||
}
|
||||
|
||||
+14
-40
@@ -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.
|
||||
*
|
||||
@@ -67,42 +64,6 @@
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* Block a client for the specific operation type. Once the CLIENT_BLOCKED
|
||||
* flag is set client query buffer is not longer processed, but accumulated,
|
||||
* and will be processed when the client is unblocked. */
|
||||
@@ -111,6 +72,7 @@ void blockClient(client *c, int btype) {
|
||||
c->btype = btype;
|
||||
server.blocked_clients++;
|
||||
server.blocked_clients_by_type[btype]++;
|
||||
addClientToTimeoutTable(c);
|
||||
}
|
||||
|
||||
/* This function is called in the beforeSleep() function of the event loop
|
||||
@@ -185,6 +147,7 @@ void unblockClient(client *c) {
|
||||
server.blocked_clients_by_type[c->btype]--;
|
||||
c->flags &= ~CLIENT_BLOCKED;
|
||||
c->btype = BLOCKED_NONE;
|
||||
removeClientFromTimeoutTable(c);
|
||||
queueClientForReprocessing(c);
|
||||
}
|
||||
|
||||
@@ -388,7 +351,7 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
|
||||
if (streamCompareID(&s->last_id, gt) > 0) {
|
||||
streamID start = *gt;
|
||||
start.seq++; /* Can't overflow, it's an uint64_t */
|
||||
streamIncrID(&start);
|
||||
|
||||
/* Lookup the consumer for the group, if any. */
|
||||
streamConsumer *consumer = NULL;
|
||||
@@ -514,6 +477,16 @@ 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. */
|
||||
server.fixed_time_expire++;
|
||||
updateCachedTime(0);
|
||||
|
||||
/* Serve clients blocked on list key. */
|
||||
robj *o = lookupKeyWrite(rl->db,rl->key);
|
||||
|
||||
@@ -529,6 +502,7 @@ void handleClientsBlockedOnKeys(void) {
|
||||
* module is trying to accomplish right now. */
|
||||
serveClientsBlockedOnKeyByModule(rl);
|
||||
}
|
||||
server.fixed_time_expire--;
|
||||
|
||||
/* Free this item. */
|
||||
decrRefCount(rl->key);
|
||||
|
||||
+57
-18
@@ -157,7 +157,10 @@ int clusterLoadConfig(char *filename) {
|
||||
}
|
||||
|
||||
/* Regular config lines have at least eight fields */
|
||||
if (argc < 8) goto fmterr;
|
||||
if (argc < 8) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
|
||||
/* Create this node if it does not exist */
|
||||
n = clusterLookupNode(argv[0]);
|
||||
@@ -166,7 +169,10 @@ int clusterLoadConfig(char *filename) {
|
||||
clusterAddNode(n);
|
||||
}
|
||||
/* Address and port */
|
||||
if ((p = strrchr(argv[1],':')) == NULL) goto fmterr;
|
||||
if ((p = strrchr(argv[1],':')) == NULL) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
*p = '\0';
|
||||
memcpy(n->ip,argv[1],strlen(argv[1])+1);
|
||||
char *port = p+1;
|
||||
@@ -247,7 +253,10 @@ int clusterLoadConfig(char *filename) {
|
||||
*p = '\0';
|
||||
direction = p[1]; /* Either '>' or '<' */
|
||||
slot = atoi(argv[j]+1);
|
||||
if (slot < 0 || slot >= CLUSTER_SLOTS) goto fmterr;
|
||||
if (slot < 0 || slot >= CLUSTER_SLOTS) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
p += 3;
|
||||
cn = clusterLookupNode(p);
|
||||
if (!cn) {
|
||||
@@ -267,8 +276,12 @@ int clusterLoadConfig(char *filename) {
|
||||
} else {
|
||||
start = stop = atoi(argv[j]);
|
||||
}
|
||||
if (start < 0 || start >= CLUSTER_SLOTS) goto fmterr;
|
||||
if (stop < 0 || stop >= CLUSTER_SLOTS) goto fmterr;
|
||||
if (start < 0 || start >= CLUSTER_SLOTS ||
|
||||
stop < 0 || stop >= CLUSTER_SLOTS)
|
||||
{
|
||||
sdsfreesplitres(argv,argc);
|
||||
goto fmterr;
|
||||
}
|
||||
while(start <= stop) clusterAddSlot(n, start++);
|
||||
}
|
||||
|
||||
@@ -668,9 +681,10 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
* or schedule it for later depending on connection implementation.
|
||||
*/
|
||||
if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error accepting cluster node connection: %s",
|
||||
connGetLastError(conn));
|
||||
if (connGetState(conn) == CONN_STATE_ERROR)
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error accepting cluster node connection: %s",
|
||||
connGetLastError(conn));
|
||||
connClose(conn);
|
||||
return;
|
||||
}
|
||||
@@ -919,7 +933,7 @@ int clusterAddNode(clusterNode *node) {
|
||||
return (retval == DICT_OK) ? C_OK : C_ERR;
|
||||
}
|
||||
|
||||
/* Remove a node from the cluster. The functio performs the high level
|
||||
/* Remove a node from the cluster. The function performs the high level
|
||||
* cleanup, calling freeClusterNode() for the low level cleanup.
|
||||
* Here we do the following:
|
||||
*
|
||||
@@ -4248,7 +4262,7 @@ void clusterCommand(client *c) {
|
||||
"FORGET <node-id> -- Remove a node from the cluster.",
|
||||
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
|
||||
"FLUSHSLOTS -- Delete current node own slots information.",
|
||||
"INFO - Return onformation about the cluster.",
|
||||
"INFO - Return information about the cluster.",
|
||||
"KEYSLOT <key> -- Return the hash slot for <key>.",
|
||||
"MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
|
||||
"MYID -- Return the node id.",
|
||||
@@ -4259,6 +4273,7 @@ void clusterCommand(client *c) {
|
||||
"SET-config-epoch <epoch> - Set config epoch of current node.",
|
||||
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
|
||||
"REPLICAS <node-id> -- Return <node-id> replicas.",
|
||||
"SAVECONFIG - Force saving cluster configuration on disk.",
|
||||
"SLOTS -- Return information about slots range mappings. Each range is made of:",
|
||||
" start, end, master and replicas IP addresses, ports and ids",
|
||||
NULL
|
||||
@@ -4951,7 +4966,7 @@ void restoreCommand(client *c) {
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload,c->argv[1])) == NULL))
|
||||
((obj = rdbLoadObject(type,&payload,c->argv[1]->ptr)) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
@@ -4966,8 +4981,9 @@ void restoreCommand(client *c) {
|
||||
if (!absttl) ttl+=mstime();
|
||||
setExpire(c,c->db,c->argv[1],ttl);
|
||||
}
|
||||
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock);
|
||||
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",c->argv[1],c->db->id);
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -5314,6 +5330,7 @@ try_again:
|
||||
/* No COPY option: remove the local key, signal the change. */
|
||||
dbDelete(c->db,kv[j]);
|
||||
signalModifiedKey(c->db,kv[j]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",kv[j],c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
/* Populate the argument vector to replace the old one. */
|
||||
@@ -5476,8 +5493,8 @@ void readwriteCommand(client *c) {
|
||||
* already "down" but it is fragile to rely on the update of the global state,
|
||||
* so we also handle it here.
|
||||
*
|
||||
* CLUSTER_REDIR_DOWN_STATE if the cluster is down but the user attempts to
|
||||
* execute a command that addresses one or more keys. */
|
||||
* CLUSTER_REDIR_DOWN_STATE and CLUSTER_REDIR_DOWN_RO_STATE if the cluster is
|
||||
* down but the user attempts to execute a command that addresses one or more keys. */
|
||||
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *error_code) {
|
||||
clusterNode *n = NULL;
|
||||
robj *firstkey = NULL;
|
||||
@@ -5595,10 +5612,27 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
* without redirections or errors in all the cases. */
|
||||
if (n == NULL) return myself;
|
||||
|
||||
/* Cluster is globally down but we got keys? We can't serve the request. */
|
||||
/* Cluster is globally down but we got keys? We only serve the request
|
||||
* if it is a read command and when allow_reads_when_down is enabled. */
|
||||
if (server.cluster->state != CLUSTER_OK) {
|
||||
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
|
||||
return NULL;
|
||||
if (!server.cluster_allow_reads_when_down) {
|
||||
/* The cluster is configured to block commands when the
|
||||
* cluster is down. */
|
||||
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
|
||||
return NULL;
|
||||
} else if (!(cmd->flags & CMD_READONLY) && !(cmd->proc == evalCommand)
|
||||
&& !(cmd->proc == evalShaCommand))
|
||||
{
|
||||
/* The cluster is configured to allow read only commands
|
||||
* but this command is neither readonly, nor EVAL or
|
||||
* EVALSHA. */
|
||||
if (error_code) *error_code = CLUSTER_REDIR_DOWN_RO_STATE;
|
||||
return NULL;
|
||||
} else {
|
||||
/* Fall through and allow the command to be executed:
|
||||
* this happens when server.cluster_allow_reads_when_down is
|
||||
* true and the command is a readonly command or EVAL / EVALSHA. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the hashslot by reference. */
|
||||
@@ -5667,6 +5701,8 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
|
||||
addReplySds(c,sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n"));
|
||||
} else if (error_code == CLUSTER_REDIR_DOWN_STATE) {
|
||||
addReplySds(c,sdsnew("-CLUSTERDOWN The cluster is down\r\n"));
|
||||
} else if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) {
|
||||
addReplySds(c,sdsnew("-CLUSTERDOWN The cluster is down and only accepts read commands\r\n"));
|
||||
} else if (error_code == CLUSTER_REDIR_DOWN_UNBOUND) {
|
||||
addReplySds(c,sdsnew("-CLUSTERDOWN Hash slot not served\r\n"));
|
||||
} else if (error_code == CLUSTER_REDIR_MOVED ||
|
||||
@@ -5701,7 +5737,10 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
dictEntry *de;
|
||||
dictIterator *di;
|
||||
|
||||
/* If the cluster is down, unblock the client with the right error. */
|
||||
/* If the cluster is down, unblock the client with the right error.
|
||||
* If the cluster is configured to allow reads on cluster down, we
|
||||
* still want to emit this error since a write will be required
|
||||
* to unblock them which may never come. */
|
||||
if (server.cluster->state == CLUSTER_FAIL) {
|
||||
clusterRedirectClient(c,NULL,0,CLUSTER_REDIR_DOWN_STATE);
|
||||
return 1;
|
||||
|
||||
+1
-5
@@ -13,15 +13,10 @@
|
||||
|
||||
/* 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. */
|
||||
@@ -34,6 +29,7 @@
|
||||
#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;
|
||||
|
||||
|
||||
+864
-1026
File diff suppressed because it is too large
Load Diff
+9
-3
@@ -152,7 +152,7 @@ static void connSocketClose(connection *conn) {
|
||||
/* If called from within a handler, schedule the close but
|
||||
* keep the connection until the handler returns.
|
||||
*/
|
||||
if (conn->flags & CONN_FLAG_IN_HANDLER) {
|
||||
if (connHasRefs(conn)) {
|
||||
conn->flags |= CONN_FLAG_CLOSE_SCHEDULED;
|
||||
return;
|
||||
}
|
||||
@@ -183,10 +183,16 @@ static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
|
||||
}
|
||||
|
||||
static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
|
||||
int ret = C_OK;
|
||||
|
||||
if (conn->state != CONN_STATE_ACCEPTING) return C_ERR;
|
||||
conn->state = CONN_STATE_CONNECTED;
|
||||
if (!callHandler(conn, accept_handler)) return C_ERR;
|
||||
return C_OK;
|
||||
|
||||
connIncrRefs(conn);
|
||||
if (!callHandler(conn, accept_handler)) ret = C_ERR;
|
||||
connDecrRefs(conn);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Register a write handler, to be called when the connection is writable.
|
||||
|
||||
+11
-4
@@ -45,9 +45,8 @@ typedef enum {
|
||||
CONN_STATE_ERROR
|
||||
} ConnectionState;
|
||||
|
||||
#define CONN_FLAG_IN_HANDLER (1<<0) /* A handler execution is in progress */
|
||||
#define CONN_FLAG_CLOSE_SCHEDULED (1<<1) /* Closed scheduled by a handler */
|
||||
#define CONN_FLAG_WRITE_BARRIER (1<<2) /* Write barrier requested */
|
||||
#define CONN_FLAG_CLOSE_SCHEDULED (1<<0) /* Closed scheduled by a handler */
|
||||
#define CONN_FLAG_WRITE_BARRIER (1<<1) /* Write barrier requested */
|
||||
|
||||
typedef void (*ConnectionCallbackFunc)(struct connection *conn);
|
||||
|
||||
@@ -70,7 +69,8 @@ typedef struct ConnectionType {
|
||||
struct connection {
|
||||
ConnectionType *type;
|
||||
ConnectionState state;
|
||||
int flags;
|
||||
short int flags;
|
||||
short int refs;
|
||||
int last_errno;
|
||||
void *private_data;
|
||||
ConnectionCallbackFunc conn_handler;
|
||||
@@ -88,6 +88,13 @@ struct connection {
|
||||
* connAccept() may directly call accept_handler(), or return and call it
|
||||
* at a later time. This behavior is a bit awkward but aims to reduce the need
|
||||
* to wait for the next event loop, if no additional handshake is required.
|
||||
*
|
||||
* IMPORTANT: accept_handler may decide to close the connection, calling connClose().
|
||||
* To make this safe, the connection is only marked with CONN_FLAG_CLOSE_SCHEDULED
|
||||
* in this case, and connAccept() returns with an error.
|
||||
*
|
||||
* connAccept() callers must always check the return value and on error (C_ERR)
|
||||
* a connClose() must be called.
|
||||
*/
|
||||
|
||||
static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
|
||||
|
||||
+28
-25
@@ -37,46 +37,49 @@
|
||||
* implementations (currently sockets in connection.c and TLS in tls.c).
|
||||
*
|
||||
* Currently helpers implement the mechanisms for invoking connection
|
||||
* handlers, tracking in-handler states and dealing with deferred
|
||||
* destruction (if invoked by a handler).
|
||||
* handlers and tracking connection references, to allow safe destruction
|
||||
* of connections from within a handler.
|
||||
*/
|
||||
|
||||
/* Called whenever a handler is invoked on a connection and sets the
|
||||
* CONN_FLAG_IN_HANDLER flag to indicate we're in a handler context.
|
||||
/* Incremenet connection references.
|
||||
*
|
||||
* An attempt to close a connection while CONN_FLAG_IN_HANDLER is
|
||||
* set will result with deferred close, i.e. setting the CONN_FLAG_CLOSE_SCHEDULED
|
||||
* instead of destructing it.
|
||||
* Inside a connection handler, we guarantee refs >= 1 so it is always
|
||||
* safe to connClose().
|
||||
*
|
||||
* In other cases where we don't want to prematurely lose the connection,
|
||||
* it can go beyond 1 as well; currently it is only done by connAccept().
|
||||
*/
|
||||
static inline void enterHandler(connection *conn) {
|
||||
conn->flags |= CONN_FLAG_IN_HANDLER;
|
||||
static inline void connIncrRefs(connection *conn) {
|
||||
conn->refs++;
|
||||
}
|
||||
|
||||
/* Called whenever a handler returns. This unsets the CONN_FLAG_IN_HANDLER
|
||||
* flag and performs actual close/destruction if a deferred close was
|
||||
* scheduled by the handler.
|
||||
/* Decrement connection references.
|
||||
*
|
||||
* Note that this is not intended to provide any automatic free logic!
|
||||
* callHandler() takes care of that for the common flows, and anywhere an
|
||||
* explicit connIncrRefs() is used, the caller is expected to take care of
|
||||
* that.
|
||||
*/
|
||||
static inline int exitHandler(connection *conn) {
|
||||
conn->flags &= ~CONN_FLAG_IN_HANDLER;
|
||||
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
|
||||
connClose(conn);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
|
||||
static inline void connDecrRefs(connection *conn) {
|
||||
conn->refs--;
|
||||
}
|
||||
|
||||
static inline int connHasRefs(connection *conn) {
|
||||
return conn->refs;
|
||||
}
|
||||
|
||||
/* Helper for connection implementations to call handlers:
|
||||
* 1. Mark the handler in use.
|
||||
* 1. Increment refs to protect the connection.
|
||||
* 2. Execute the handler (if set).
|
||||
* 3. Mark the handler as NOT in use and perform deferred close if was
|
||||
* requested by the handler at any time.
|
||||
* 3. Decrement refs and perform deferred close, if refs==0.
|
||||
*/
|
||||
static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) {
|
||||
conn->flags |= CONN_FLAG_IN_HANDLER;
|
||||
connIncrRefs(conn);
|
||||
if (handler) handler(conn);
|
||||
conn->flags &= ~CONN_FLAG_IN_HANDLER;
|
||||
connDecrRefs(conn);
|
||||
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
|
||||
connClose(conn);
|
||||
if (!connHasRefs(conn)) connClose(conn);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
|
||||
@@ -182,9 +182,28 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
|
||||
serverAssertWithInfo(NULL,key,retval == DICT_OK);
|
||||
if (val->type == OBJ_LIST ||
|
||||
val->type == OBJ_ZSET)
|
||||
val->type == OBJ_ZSET ||
|
||||
val->type == OBJ_STREAM)
|
||||
signalKeyAsReady(db, key);
|
||||
if (server.cluster_enabled) slotToKeyAdd(key->ptr);
|
||||
}
|
||||
|
||||
/* This is a special version of dbAdd() that is used only when loading
|
||||
* keys from the RDB file: the key is passed as an SDS string that is
|
||||
* retained by the function (and not freed by the caller).
|
||||
*
|
||||
* Moreover this function will not abort if the key is already busy, to
|
||||
* give more control to the caller, nor will signal the key as ready
|
||||
* since it is not useful in this context.
|
||||
*
|
||||
* The function returns 1 if the key was added to the database, taking
|
||||
* ownership of the SDS string, otherwise 0 is returned, and is up to the
|
||||
* caller to free the SDS string. */
|
||||
int dbAddRDBLoad(redisDb *db, sds key, robj *val) {
|
||||
int retval = dictAdd(db->dict, key, val);
|
||||
if (retval != DICT_OK) return 0;
|
||||
if (server.cluster_enabled) slotToKeyAdd(key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Overwrite an existing key with a new value. Incrementing the reference
|
||||
@@ -216,20 +235,28 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
*
|
||||
* 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) {
|
||||
void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl, int signal) {
|
||||
if (lookupKeyWrite(db,key) == NULL) {
|
||||
dbAdd(db,key,val);
|
||||
} else {
|
||||
dbOverwrite(db,key,val);
|
||||
}
|
||||
incrRefCount(val);
|
||||
removeExpire(db,key);
|
||||
signalModifiedKey(db,key);
|
||||
if (!keepttl) removeExpire(db,key);
|
||||
if (signal) signalModifiedKey(db,key);
|
||||
}
|
||||
|
||||
/* Common case for genericSetKey() where the TTL is not retained. */
|
||||
void setKey(redisDb *db, robj *key, robj *val) {
|
||||
genericSetKey(db,key,val,0,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->dict,key->ptr) != NULL;
|
||||
}
|
||||
@@ -279,7 +306,7 @@ int dbSyncDelete(redisDb *db, robj *key) {
|
||||
* the key, because it is shared with the main dictionary. */
|
||||
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
|
||||
if (dictDelete(db->dict,key->ptr) == DICT_OK) {
|
||||
if (server.cluster_enabled) slotToKeyDel(key);
|
||||
if (server.cluster_enabled) slotToKeyDel(key->ptr);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
@@ -339,7 +366,10 @@ 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
|
||||
@@ -347,6 +377,8 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
|
||||
* DB number is out of range, and errno is set to EINVAL. */
|
||||
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*)) {
|
||||
int async = (flags & EMPTYDB_ASYNC);
|
||||
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 >= server.dbnum) {
|
||||
@@ -354,16 +386,18 @@ long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Fire the flushdb modules event. */
|
||||
RedisModuleFlushInfoV1 fi = {REDISMODULE_FLUSHINFO_VERSION,!async,dbnum};
|
||||
moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
|
||||
REDISMODULE_SUBEVENT_FLUSHDB_START,
|
||||
&fi);
|
||||
/* 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);
|
||||
/* 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) {
|
||||
@@ -382,20 +416,24 @@ long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(
|
||||
dictEmpty(dbarray[j].expires,callback);
|
||||
}
|
||||
}
|
||||
if (server.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);
|
||||
/* Post-flush actions */
|
||||
if (!backup) {
|
||||
if (server.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);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
@@ -536,7 +574,7 @@ void delGenericCommand(client *c, int lazy) {
|
||||
}
|
||||
|
||||
void delCommand(client *c) {
|
||||
delGenericCommand(c,0);
|
||||
delGenericCommand(c,server.lazyfree_lazy_user_del);
|
||||
}
|
||||
|
||||
void unlinkCommand(client *c) {
|
||||
@@ -594,7 +632,7 @@ void keysCommand(client *c) {
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
|
||||
di = dictGetSafeIterator(c->db->dict);
|
||||
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
|
||||
allkeys = (pattern[0] == '*' && plen == 1);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key = dictGetKey(de);
|
||||
robj *keyobj;
|
||||
@@ -1034,6 +1072,13 @@ void moveCommand(client *c) {
|
||||
|
||||
/* OK! key moved, free the entry in the source DB */
|
||||
dbDelete(src,c->argv[1]);
|
||||
signalModifiedKey(src,c->argv[1]);
|
||||
signalModifiedKey(dst,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,
|
||||
"move_from",c->argv[1],src->id);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,
|
||||
"move_to",c->argv[1],dst->id);
|
||||
|
||||
server.dirty++;
|
||||
addReply(c,shared.cone);
|
||||
}
|
||||
@@ -1077,10 +1122,12 @@ int dbSwapDatabases(long id1, long id2) {
|
||||
db1->dict = db2->dict;
|
||||
db1->expires = db2->expires;
|
||||
db1->avg_ttl = db2->avg_ttl;
|
||||
db1->expires_cursor = db2->expires_cursor;
|
||||
|
||||
db2->dict = aux.dict;
|
||||
db2->expires = aux.expires;
|
||||
db2->avg_ttl = aux.avg_ttl;
|
||||
db2->expires_cursor = aux.expires_cursor;
|
||||
|
||||
/* Now we need to handle clients blocked on lists: as an effect
|
||||
* of swapping the two DBs, a client that was waiting for list
|
||||
@@ -1196,6 +1243,7 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
|
||||
/* Check if the key is expired. */
|
||||
int keyIsExpired(redisDb *db, robj *key) {
|
||||
mstime_t when = getExpire(db,key);
|
||||
mstime_t now;
|
||||
|
||||
if (when < 0) return 0; /* No expire for this key */
|
||||
|
||||
@@ -1207,8 +1255,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 = server.lua_caller ? server.lua_time_start : mstime();
|
||||
if (server.lua_caller) {
|
||||
now = server.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 (server.fixed_time_expire > 0) {
|
||||
now = server.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;
|
||||
}
|
||||
|
||||
@@ -1249,13 +1315,17 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
propagateExpire(db,key,server.lazyfree_lazy_expire);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",key,db->id);
|
||||
return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
if (retval) signalModifiedKey(db,key);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* API to get key arguments from commands
|
||||
* ---------------------------------------------------------------------------*/
|
||||
#define MAX_KEYS_BUFFER 256
|
||||
static int getKeysTempBuffer[MAX_KEYS_BUFFER];
|
||||
|
||||
/* The base case is to use the keys position as given in the command table
|
||||
* (firstkey, lastkey, step). */
|
||||
@@ -1270,7 +1340,12 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
|
||||
last = cmd->lastkey;
|
||||
if (last < 0) last = argc+last;
|
||||
keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1));
|
||||
|
||||
int count = ((last - cmd->firstkey)+1);
|
||||
keys = getKeysTempBuffer;
|
||||
if (count > MAX_KEYS_BUFFER)
|
||||
keys = 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
|
||||
@@ -1280,7 +1355,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 {
|
||||
@@ -1316,7 +1391,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:
|
||||
@@ -1337,7 +1413,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 = zmalloc(sizeof(int)*(num+1));
|
||||
keys = getKeysTempBuffer;
|
||||
if (num+1>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int)*(num+1));
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
for (i = 0; i < num; i++) keys[i] = 3+i;
|
||||
@@ -1363,7 +1441,10 @@ int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
|
||||
*numkeys = num;
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
@@ -1384,7 +1465,7 @@ int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
UNUSED(cmd);
|
||||
|
||||
num = 0;
|
||||
keys = zmalloc(sizeof(int)*2); /* Alloc 2 places for the worst case. */
|
||||
keys = getKeysTempBuffer; /* Alloc 2 places for the worst case. */
|
||||
|
||||
keys[num++] = 1; /* <sort-key> is always present. */
|
||||
|
||||
@@ -1442,7 +1523,10 @@ int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkey
|
||||
}
|
||||
}
|
||||
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
|
||||
for (i = 0; i < num; i++) keys[i] = first+i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
@@ -1475,7 +1559,9 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
|
||||
* argv[1] = key,
|
||||
* argv[5...n] = stored key if present
|
||||
*/
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
|
||||
/* Add all key positions to keys[] */
|
||||
keys[0] = 1;
|
||||
@@ -1486,6 +1572,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 = argv[i]->ptr;
|
||||
int moreargs = (argc-1) - i;
|
||||
|
||||
if (!strcasecmp(arg, "strings")) {
|
||||
break;
|
||||
} else if (!strcasecmp(arg, "keys") && moreargs >= 2) {
|
||||
keys[0] = i+1;
|
||||
keys[1] = i+2;
|
||||
*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(argv[1]->ptr,"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) {
|
||||
@@ -1524,7 +1652,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 = zmalloc(sizeof(int) * num);
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
|
||||
for (i = streams_pos+1; i < argc-num; i++) keys[i-streams_pos-1] = i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
@@ -1534,17 +1665,17 @@ 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) {
|
||||
unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));
|
||||
void slotToKeyUpdateKey(sds key, int add) {
|
||||
size_t keylen = sdslen(key);
|
||||
unsigned int hashslot = keyHashSlot(key,keylen);
|
||||
unsigned char buf[64];
|
||||
unsigned char *indexed = buf;
|
||||
size_t keylen = sdslen(key->ptr);
|
||||
|
||||
server.cluster->slots_keys_count[hashslot] += add ? 1 : -1;
|
||||
if (keylen+2 > 64) indexed = zmalloc(keylen+2);
|
||||
indexed[0] = (hashslot >> 8) & 0xff;
|
||||
indexed[1] = hashslot & 0xff;
|
||||
memcpy(indexed+2,key->ptr,keylen);
|
||||
memcpy(indexed+2,key,keylen);
|
||||
if (add) {
|
||||
raxInsert(server.cluster->slots_to_keys,indexed,keylen+2,NULL,NULL);
|
||||
} else {
|
||||
@@ -1553,11 +1684,11 @@ void slotToKeyUpdateKey(robj *key, int add) {
|
||||
if (indexed != buf) zfree(indexed);
|
||||
}
|
||||
|
||||
void slotToKeyAdd(robj *key) {
|
||||
void slotToKeyAdd(sds key) {
|
||||
slotToKeyUpdateKey(key,1);
|
||||
}
|
||||
|
||||
void slotToKeyDel(robj *key) {
|
||||
void slotToKeyDel(sds key) {
|
||||
slotToKeyUpdateKey(key,0);
|
||||
}
|
||||
|
||||
|
||||
+103
-39
@@ -355,6 +355,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.",
|
||||
@@ -362,9 +363,10 @@ 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.",
|
||||
@@ -409,15 +411,44 @@ NULL
|
||||
serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
int flush = 1, save = 1;
|
||||
int flags = RDBFLAGS_NONE;
|
||||
|
||||
/* Parse the additional options that modify the RELOAD
|
||||
* behavior. */
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
char *opt = c->argv[j]->ptr;
|
||||
if (!strcasecmp(opt,"MERGE")) {
|
||||
flags |= RDBFLAGS_ALLOW_DUP;
|
||||
} else if (!strcasecmp(opt,"NOFLUSH")) {
|
||||
flush = 0;
|
||||
} else if (!strcasecmp(opt,"NOSAVE")) {
|
||||
save = 0;
|
||||
} else {
|
||||
addReplyError(c,"DEBUG RELOAD only supports the "
|
||||
"MERGE, NOFLUSH and NOSAVE options.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
|
||||
/* The default beahvior is to save the RDB file before loading
|
||||
* it back. */
|
||||
if (save) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* The default behavior is to remove the current dataset from
|
||||
* memory before loading the RDB file, however when MERGE is
|
||||
* used together with NOFLUSH, we are able to merge two datasets. */
|
||||
if (flush) emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
|
||||
protectClient(c);
|
||||
int ret = rdbLoad(server.rdb_filename,NULL,RDBFLAGS_NONE);
|
||||
int ret = rdbLoad(server.rdb_filename,NULL,flags);
|
||||
unprotectClient(c);
|
||||
if (ret != C_OK) {
|
||||
addReplyError(c,"Error trying to load the RDB dump");
|
||||
@@ -488,7 +519,7 @@ NULL
|
||||
"encoding:%s serializedlength:%zu "
|
||||
"lru:%d lru_seconds_idle:%llu%s",
|
||||
(void*)val, val->refcount,
|
||||
strenc, rdbSavedObjectLen(val),
|
||||
strenc, rdbSavedObjectLen(val, c->argv[2]),
|
||||
val->lru, estimateObjectIdleTime(val)/1000, extra);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) {
|
||||
dictEntry *de;
|
||||
@@ -586,7 +617,7 @@ NULL
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"protocol") && c->argc == 3) {
|
||||
/* DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|
|
||||
* attrib|push|verbatim|true|false|state|err|bloberr] */
|
||||
* attrib|push|verbatim|true|false] */
|
||||
char *name = c->argv[2]->ptr;
|
||||
if (!strcasecmp(name,"string")) {
|
||||
addReplyBulkCString(c,"Hello World");
|
||||
@@ -634,7 +665,7 @@ NULL
|
||||
} else if (!strcasecmp(name,"verbatim")) {
|
||||
addReplyVerbatim(c,"This is a verbatim\nstring",25,"txt");
|
||||
} else {
|
||||
addReplyError(c,"Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false|state|err|bloberr");
|
||||
addReplyError(c,"Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false");
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
|
||||
double dtime = strtod(c->argv[2]->ptr,NULL);
|
||||
@@ -683,9 +714,12 @@ NULL
|
||||
sds stats = sdsempty();
|
||||
char buf[4096];
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK)
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK) {
|
||||
sdsfree(stats);
|
||||
return;
|
||||
}
|
||||
if (dbid < 0 || dbid >= server.dbnum) {
|
||||
sdsfree(stats);
|
||||
addReplyError(c,"Out of range database");
|
||||
return;
|
||||
}
|
||||
@@ -812,6 +846,8 @@ void serverLogObjectDebugInfo(const robj *o) {
|
||||
serverLog(LL_WARNING,"Sorted set size: %d", (int) zsetLength(o));
|
||||
if (o->encoding == OBJ_ENCODING_SKIPLIST)
|
||||
serverLog(LL_WARNING,"Skiplist level: %d", (int) ((const zset*)o->ptr)->zsl->level);
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
serverLog(LL_WARNING,"Stream size: %d", (int) streamLength(o));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1040,6 +1076,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__)
|
||||
@@ -1180,33 +1271,6 @@ void logRegisters(ucontext_t *uc) {
|
||||
(unsigned long) uc->uc_mcontext.mc_cs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.mc_rsp);
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
|
||||
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
|
||||
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
|
||||
"X30:%016lx\n"
|
||||
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
|
||||
(unsigned long) uc->uc_mcontext.regs[18],
|
||||
(unsigned long) uc->uc_mcontext.regs[19],
|
||||
(unsigned long) uc->uc_mcontext.regs[20],
|
||||
(unsigned long) uc->uc_mcontext.regs[21],
|
||||
(unsigned long) uc->uc_mcontext.regs[22],
|
||||
(unsigned long) uc->uc_mcontext.regs[23],
|
||||
(unsigned long) uc->uc_mcontext.regs[24],
|
||||
(unsigned long) uc->uc_mcontext.regs[25],
|
||||
(unsigned long) uc->uc_mcontext.regs[26],
|
||||
(unsigned long) uc->uc_mcontext.regs[27],
|
||||
(unsigned long) uc->uc_mcontext.regs[28],
|
||||
(unsigned long) uc->uc_mcontext.regs[29],
|
||||
(unsigned long) uc->uc_mcontext.regs[30],
|
||||
(unsigned long) uc->uc_mcontext.pc,
|
||||
(unsigned long) uc->uc_mcontext.sp,
|
||||
(unsigned long) uc->uc_mcontext.pstate,
|
||||
(unsigned long) uc->uc_mcontext.fault_address
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.sp);
|
||||
#else
|
||||
serverLog(LL_WARNING,
|
||||
" Dumping of registers not supported for this OS/arch");
|
||||
|
||||
+96
-42
@@ -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
|
||||
@@ -408,25 +408,32 @@ dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sd
|
||||
return NULL;
|
||||
}
|
||||
|
||||
long activeDefragQuickListNodes(quicklist *ql) {
|
||||
quicklistNode *node = ql->head, *newnode;
|
||||
long activeDefragQuickListNode(quicklist *ql, quicklistNode **node_ref) {
|
||||
quicklistNode *newnode, *node = *node_ref;
|
||||
long defragged = 0;
|
||||
unsigned char *newzl;
|
||||
if ((newnode = activeDefragAlloc(node))) {
|
||||
if (newnode->prev)
|
||||
newnode->prev->next = newnode;
|
||||
else
|
||||
ql->head = newnode;
|
||||
if (newnode->next)
|
||||
newnode->next->prev = newnode;
|
||||
else
|
||||
ql->tail = newnode;
|
||||
*node_ref = node = newnode;
|
||||
defragged++;
|
||||
}
|
||||
if ((newzl = activeDefragAlloc(node->zl)))
|
||||
defragged++, node->zl = newzl;
|
||||
return defragged;
|
||||
}
|
||||
|
||||
long activeDefragQuickListNodes(quicklist *ql) {
|
||||
quicklistNode *node = ql->head;
|
||||
long defragged = 0;
|
||||
while (node) {
|
||||
if ((newnode = activeDefragAlloc(node))) {
|
||||
if (newnode->prev)
|
||||
newnode->prev->next = newnode;
|
||||
else
|
||||
ql->head = newnode;
|
||||
if (newnode->next)
|
||||
newnode->next->prev = newnode;
|
||||
else
|
||||
ql->tail = newnode;
|
||||
node = newnode;
|
||||
defragged++;
|
||||
}
|
||||
if ((newzl = activeDefragAlloc(node->zl)))
|
||||
defragged++, node->zl = newzl;
|
||||
defragged += activeDefragQuickListNode(ql, &node);
|
||||
node = node->next;
|
||||
}
|
||||
return defragged;
|
||||
@@ -440,12 +447,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 = ob->ptr;
|
||||
quicklistNode *node;
|
||||
long iterations = 0;
|
||||
int bookmark_failed = 0;
|
||||
if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST)
|
||||
return 0;
|
||||
server.stat_active_defrag_scanned+=ql->len;
|
||||
return activeDefragQuickListNodes(ql);
|
||||
|
||||
if (*cursor == 0) {
|
||||
/* if cursor is 0, we start new iteration */
|
||||
node = ql->head;
|
||||
} else {
|
||||
node = quicklistBookmarkFind(ql, "_AD");
|
||||
if (!node) {
|
||||
/* if the bookmark was deleted, it means we reached the end. */
|
||||
*cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
(*cursor)++;
|
||||
while (node) {
|
||||
(*defragged) += activeDefragQuickListNode(ql, &node);
|
||||
server.stat_active_defrag_scanned++;
|
||||
if (++iterations > 128 && !bookmark_failed) {
|
||||
if (ustime() > endtime) {
|
||||
if (!quicklistBookmarkCreate(&ql, "_AD", node)) {
|
||||
bookmark_failed = 1;
|
||||
} else {
|
||||
ob->ptr = ql; /* bookmark creation may have re-allocated the quicklist */
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
iterations = 0;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
quicklistBookmarkDelete(ql, "_AD");
|
||||
*cursor = 0;
|
||||
return bookmark_failed? 1: 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
@@ -638,7 +681,8 @@ int scanLaterStraemListpacks(robj *ob, unsigned long *cursor, long long endtime,
|
||||
void *newdata = activeDefragAlloc(ri.data);
|
||||
if (newdata)
|
||||
raxSetData(ri.node, ri.data=newdata), (*defragged)++;
|
||||
if (++iterations > 16) {
|
||||
server.stat_active_defrag_scanned++;
|
||||
if (++iterations > 128) {
|
||||
if (ustime() > endtime) {
|
||||
serverAssert(ri.key_len==sizeof(last));
|
||||
memcpy(last,ri.key,ri.key_len);
|
||||
@@ -900,8 +944,7 @@ int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime) {
|
||||
if (de) {
|
||||
robj *ob = dictGetVal(de);
|
||||
if (ob->type == OBJ_LIST) {
|
||||
server.stat_active_defrag_hits += scanLaterList(ob);
|
||||
*cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
return scanLaterList(ob, cursor, endtime, &server.stat_active_defrag_hits);
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
server.stat_active_defrag_hits += scanLaterSet(ob, cursor);
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
@@ -919,10 +962,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 = server.stat_active_defrag_hits;
|
||||
unsigned long long prev_scanned = server.stat_active_defrag_scanned;
|
||||
@@ -930,16 +975,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(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. */
|
||||
@@ -948,23 +992,18 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
return 0;
|
||||
|
||||
/* start a new key */
|
||||
current_key = head->value;
|
||||
cursor = 0;
|
||||
defrag_later_current_key = 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->dict, current_key);
|
||||
dictEntry *de = dictFind(db->dict, defrag_later_current_key);
|
||||
key_defragged = server.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. */
|
||||
@@ -982,7 +1021,7 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
prev_defragged = server.stat_active_defrag_hits;
|
||||
prev_scanned = server.stat_active_defrag_scanned;
|
||||
}
|
||||
} while(cursor);
|
||||
} while(defrag_later_cursor);
|
||||
if(key_defragged != server.stat_active_defrag_hits)
|
||||
server.stat_active_defrag_key_hits++;
|
||||
else
|
||||
@@ -1039,6 +1078,21 @@ void activeDefragCycle(void) {
|
||||
mstime_t latency;
|
||||
int quit = 0;
|
||||
|
||||
if (!server.active_defrag_enabled) {
|
||||
if (server.active_defrag_running) {
|
||||
/* if active defrag was disabled mid-run, start from fresh next time. */
|
||||
server.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. */
|
||||
|
||||
|
||||
+8
-1
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+119
-35
@@ -78,24 +78,63 @@ int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
* it will get more aggressive to avoid that too much memory is used by
|
||||
* keys that can be removed from the keyspace.
|
||||
*
|
||||
* No more than CRON_DBS_PER_CALL databases are tested at every
|
||||
* iteration.
|
||||
* Every expire cycle tests multiple databases: the next call will start
|
||||
* again from the next db, with the exception of exists for time limit: in that
|
||||
* case we restart again from the last database we were processing. Anyway
|
||||
* no more than CRON_DBS_PER_CALL databases are tested at every iteration.
|
||||
*
|
||||
* This kind of call is used when Redis detects that timelimit_exit is
|
||||
* true, so there is more work to do, and we do it more incrementally from
|
||||
* the beforeSleep() function of the event loop.
|
||||
* The function can perform more or less work, depending on the "type"
|
||||
* argument. It can execute a "fast cycle" or a "slow cycle". The slow
|
||||
* cycle is the main way we collect expired cycles: this happens with
|
||||
* the "server.hz" frequency (usually 10 hertz).
|
||||
*
|
||||
* Expire cycle type:
|
||||
* However the slow cycle can exit for timeout, since it used too much time.
|
||||
* For this reason the function is also invoked to perform a fast cycle
|
||||
* at every event loop cycle, in the beforeSleep() function. The fast cycle
|
||||
* will try to perform less work, but will do it much more often.
|
||||
*
|
||||
* The following are the details of the two expire cycles and their stop
|
||||
* conditions:
|
||||
*
|
||||
* If type is ACTIVE_EXPIRE_CYCLE_FAST the function will try to run a
|
||||
* "fast" expire cycle that takes no longer than EXPIRE_FAST_CYCLE_DURATION
|
||||
* microseconds, and is not repeated again before the same amount of time.
|
||||
* The cycle will also refuse to run at all if the latest slow cycle did not
|
||||
* terminate because of a time limit condition.
|
||||
*
|
||||
* If type is ACTIVE_EXPIRE_CYCLE_SLOW, that normal expire cycle is
|
||||
* executed, where the time limit is a percentage of the REDIS_HZ period
|
||||
* as specified by the ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC define. */
|
||||
* as specified by the ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC define. In the
|
||||
* fast cycle, the check of every database is interrupted once the number
|
||||
* of already expired keys in the database is estimated to be lower than
|
||||
* a given percentage, in order to avoid doing too much work to gain too
|
||||
* little memory.
|
||||
*
|
||||
* The configured expire "effort" will modify the baseline parameters in
|
||||
* order to do more work in both the fast and slow expire cycles.
|
||||
*/
|
||||
|
||||
#define ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP 20 /* Keys for each DB loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* Max % of CPU to use. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE 10 /* % of stale keys after which
|
||||
we do extra efforts. */
|
||||
|
||||
void activeExpireCycle(int type) {
|
||||
/* Adjust the running parameters according to the configured expire
|
||||
* effort. The default effort is 1, and the maximum configurable effort
|
||||
* is 10. */
|
||||
unsigned long
|
||||
effort = server.active_expire_effort-1, /* Rescale from 0 to 9. */
|
||||
config_keys_per_loop = ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP +
|
||||
ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort,
|
||||
config_cycle_fast_duration = ACTIVE_EXPIRE_CYCLE_FAST_DURATION +
|
||||
ACTIVE_EXPIRE_CYCLE_FAST_DURATION/4*effort,
|
||||
config_cycle_slow_time_perc = ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC +
|
||||
2*effort,
|
||||
config_cycle_acceptable_stale = ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE-
|
||||
effort;
|
||||
|
||||
/* This function has some global state in order to continue the work
|
||||
* incrementally across calls. */
|
||||
static unsigned int current_db = 0; /* Last DB tested. */
|
||||
@@ -113,10 +152,16 @@ void activeExpireCycle(int type) {
|
||||
|
||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
|
||||
/* Don't start a fast cycle if the previous cycle did not exit
|
||||
* for time limit. Also don't repeat a fast cycle for the same period
|
||||
* for time limit, unless the percentage of estimated stale keys is
|
||||
* too high. Also never repeat a fast cycle for the same period
|
||||
* as the fast cycle total duration itself. */
|
||||
if (!timelimit_exit) return;
|
||||
if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;
|
||||
if (!timelimit_exit &&
|
||||
server.stat_expired_stale_perc < config_cycle_acceptable_stale)
|
||||
return;
|
||||
|
||||
if (start < last_fast_cycle + (long long)config_cycle_fast_duration*2)
|
||||
return;
|
||||
|
||||
last_fast_cycle = start;
|
||||
}
|
||||
|
||||
@@ -130,16 +175,16 @@ void activeExpireCycle(int type) {
|
||||
if (dbs_per_call > server.dbnum || timelimit_exit)
|
||||
dbs_per_call = server.dbnum;
|
||||
|
||||
/* We can use at max ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC percentage of CPU time
|
||||
* per iteration. Since this function gets called with a frequency of
|
||||
/* We can use at max 'config_cycle_slow_time_perc' percentage of CPU
|
||||
* time per iteration. Since this function gets called with a frequency of
|
||||
* server.hz times per second, the following is the max amount of
|
||||
* microseconds we can spend in this function. */
|
||||
timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
|
||||
timelimit = config_cycle_slow_time_perc*1000000/server.hz/100;
|
||||
timelimit_exit = 0;
|
||||
if (timelimit <= 0) timelimit = 1;
|
||||
|
||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST)
|
||||
timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* in microseconds. */
|
||||
timelimit = config_cycle_fast_duration; /* in microseconds. */
|
||||
|
||||
/* Accumulate some global stats as we expire keys, to have some idea
|
||||
* about the number of keys that are already logically expired, but still
|
||||
@@ -148,7 +193,9 @@ void activeExpireCycle(int type) {
|
||||
long total_expired = 0;
|
||||
|
||||
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
|
||||
int expired;
|
||||
/* Expired and checked in a single loop. */
|
||||
unsigned long expired, sampled;
|
||||
|
||||
redisDb *db = server.db+(current_db % server.dbnum);
|
||||
|
||||
/* Increment the DB now so we are sure if we run out of time
|
||||
@@ -156,8 +203,10 @@ void activeExpireCycle(int type) {
|
||||
* distribute the time evenly across DBs. */
|
||||
current_db++;
|
||||
|
||||
/* Continue to expire if at the end of the cycle more than 25%
|
||||
* of the keys were expired. */
|
||||
/* Continue to expire if at the end of the cycle there are still
|
||||
* a big percentage of keys to expire, compared to the number of keys
|
||||
* we scanned. The percentage, stored in config_cycle_acceptable_stale
|
||||
* is not fixed, but depends on the Redis configured "expire effort". */
|
||||
do {
|
||||
unsigned long num, slots;
|
||||
long long now, ttl_sum;
|
||||
@@ -172,8 +221,8 @@ void activeExpireCycle(int type) {
|
||||
slots = dictSlots(db->expires);
|
||||
now = mstime();
|
||||
|
||||
/* When there are less than 1% filled slots getting random
|
||||
* keys is expensive, so stop here waiting for better times...
|
||||
/* When there are less than 1% filled slots, sampling the key
|
||||
* space is expensive, so stop here waiting for better times...
|
||||
* The dictionary will be resized asap. */
|
||||
if (num && slots > DICT_HT_INITIAL_SIZE &&
|
||||
(num*100/slots < 1)) break;
|
||||
@@ -181,27 +230,58 @@ void activeExpireCycle(int type) {
|
||||
/* The main collection cycle. Sample random keys among keys
|
||||
* with an expire set, checking for expired ones. */
|
||||
expired = 0;
|
||||
sampled = 0;
|
||||
ttl_sum = 0;
|
||||
ttl_samples = 0;
|
||||
|
||||
if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
|
||||
num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
|
||||
if (num > config_keys_per_loop)
|
||||
num = config_keys_per_loop;
|
||||
|
||||
while (num--) {
|
||||
dictEntry *de;
|
||||
long long ttl;
|
||||
/* Here we access the low level representation of the hash table
|
||||
* for speed concerns: this makes this code coupled with dict.c,
|
||||
* but it hardly changed in ten years.
|
||||
*
|
||||
* Note that certain places of the hash table may be empty,
|
||||
* so we want also a stop condition about the number of
|
||||
* buckets that we scanned. However scanning for free buckets
|
||||
* is very fast: we are in the cache line scanning a sequential
|
||||
* array of NULL pointers, so we can scan a lot more buckets
|
||||
* than keys in the same time. */
|
||||
long max_buckets = num*20;
|
||||
long checked_buckets = 0;
|
||||
|
||||
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
|
||||
ttl = dictGetSignedIntegerVal(de)-now;
|
||||
if (activeExpireCycleTryExpire(db,de,now)) expired++;
|
||||
if (ttl > 0) {
|
||||
/* We want the average TTL of keys yet not expired. */
|
||||
ttl_sum += ttl;
|
||||
ttl_samples++;
|
||||
while (sampled < num && checked_buckets < max_buckets) {
|
||||
for (int table = 0; table < 2; table++) {
|
||||
if (table == 1 && !dictIsRehashing(db->expires)) break;
|
||||
|
||||
unsigned long idx = db->expires_cursor;
|
||||
idx &= db->expires->ht[table].sizemask;
|
||||
dictEntry *de = db->expires->ht[table].table[idx];
|
||||
long long ttl;
|
||||
|
||||
/* Scan the current bucket of the current table. */
|
||||
checked_buckets++;
|
||||
while(de) {
|
||||
/* Get the next entry now since this entry may get
|
||||
* deleted. */
|
||||
dictEntry *e = de;
|
||||
de = de->next;
|
||||
|
||||
ttl = dictGetSignedIntegerVal(e)-now;
|
||||
if (activeExpireCycleTryExpire(db,e,now)) expired++;
|
||||
if (ttl > 0) {
|
||||
/* We want the average TTL of keys yet
|
||||
* not expired. */
|
||||
ttl_sum += ttl;
|
||||
ttl_samples++;
|
||||
}
|
||||
sampled++;
|
||||
}
|
||||
}
|
||||
total_sampled++;
|
||||
db->expires_cursor++;
|
||||
}
|
||||
total_expired += expired;
|
||||
total_sampled += sampled;
|
||||
|
||||
/* Update the average TTL stats for this database. */
|
||||
if (ttl_samples) {
|
||||
@@ -225,12 +305,15 @@ void activeExpireCycle(int type) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* We don't repeat the cycle if there are less than 25% of keys
|
||||
* found expired in the current DB. */
|
||||
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
|
||||
/* We don't repeat the cycle for the current database if there are
|
||||
* an acceptable amount of stale keys (logically expired but yet
|
||||
* not reclaimed). */
|
||||
} while (sampled == 0 ||
|
||||
(expired*100/sampled) > config_cycle_acceptable_stale);
|
||||
}
|
||||
|
||||
elapsed = ustime()-start;
|
||||
server.stat_expire_cycle_time_used += elapsed;
|
||||
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
|
||||
|
||||
/* Update our estimate of keys existing but yet to be expired.
|
||||
@@ -507,6 +590,7 @@ void pttlCommand(client *c) {
|
||||
void persistCommand(client *c) {
|
||||
if (lookupKeyWrite(c->db,c->argv[1])) {
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id);
|
||||
addReply(c,shared.cone);
|
||||
server.dirty++;
|
||||
} else {
|
||||
|
||||
@@ -734,14 +734,22 @@ void geohashCommand(client *c) {
|
||||
r[1].max = 90;
|
||||
geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
|
||||
|
||||
char buf[11];
|
||||
char buf[12];
|
||||
int i;
|
||||
for (i = 0; i < 10; i++) {
|
||||
int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
|
||||
for (i = 0; i < 11; i++) {
|
||||
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[10] = '\0';
|
||||
addReplyBulkCBuffer(c,buf,10);
|
||||
buf[11] = '\0';
|
||||
addReplyBulkCBuffer(c,buf,11);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1535,6 +1535,7 @@ void pfdebugCommand(client *c) {
|
||||
sds decoded = sdsempty();
|
||||
|
||||
if (hdr->encoding != HLL_SPARSE) {
|
||||
sdsfree(decoded);
|
||||
addReplyError(c,"HLL encoding is not sparse");
|
||||
return;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
server.latency_events = dictCreate(&latencyTimeSeriesDictType,NULL);
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
* field to NULL in order to lazy free it later. */
|
||||
if (de) {
|
||||
dictFreeUnlinkedEntry(db->dict,de);
|
||||
if (server.cluster_enabled) slotToKeyDel(key);
|
||||
if (server.cluster_enabled) slotToKeyDel(key->ptr);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
|
||||
+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) {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n
|
||||
GIT_DIRTY=`git diff --no-ext-diff 2> /dev/null | wc -l`
|
||||
BUILD_ID=`uname -n`"-"`date +%s`
|
||||
if [ -n "$SOURCE_DATE_EPOCH" ]; then
|
||||
BUILD_ID=$(date -u -d "@$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u %s)
|
||||
BUILD_ID=$(date -u -d "@$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u +%s)
|
||||
fi
|
||||
test -f release.h || touch release.h
|
||||
(cat release.h | grep SHA1 | grep $GIT_SHA1) && \
|
||||
|
||||
+935
-240
File diff suppressed because it is too large
Load Diff
+23
-34
@@ -1,4 +1,5 @@
|
||||
/* ACL API example - An example of performing custom password authentication
|
||||
/* ACL API example - An example for performing custom synchronous and
|
||||
* asynchronous password authentication.
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
*
|
||||
@@ -32,17 +33,12 @@
|
||||
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "../redismodule.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// A simple global user
|
||||
static RedisModuleUser *global;
|
||||
static RedisModuleAuthCtx *global_auth_ctx;
|
||||
static uint64_t global_auth_client_id = 0;
|
||||
|
||||
/* HELLOACL.REVOKE
|
||||
* Synchronously revoke access from a user. */
|
||||
@@ -50,8 +46,8 @@ int RevokeCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, in
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (global_auth_ctx) {
|
||||
RedisModule_RevokeAuthentication(global_auth_ctx);
|
||||
if (global_auth_client_id) {
|
||||
RedisModule_DeauthenticateAndCloseClient(ctx, global_auth_client_id);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
} else {
|
||||
return RedisModule_ReplyWithError(ctx, "Global user currently not used");
|
||||
@@ -75,29 +71,23 @@ int ResetCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
|
||||
|
||||
/* Callback handler for user changes, use this to notify a module of
|
||||
* changes to users authenticated by the module */
|
||||
void HelloACL_UserChanged(RedisModuleCtx *ctx, void *privdata) {
|
||||
void HelloACL_UserChanged(uint64_t client_id, void *privdata) {
|
||||
REDISMODULE_NOT_USED(privdata);
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
global_auth_ctx = NULL;
|
||||
REDISMODULE_NOT_USED(client_id);
|
||||
global_auth_client_id = 0;
|
||||
}
|
||||
|
||||
/* HELLOACL.AUTHGLOBAL
|
||||
* Synchronously assigns a module user to the current context client. */
|
||||
* Synchronously assigns a module user to the current context. */
|
||||
int AuthGlobalCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (global_auth_ctx) {
|
||||
if (global_auth_client_id) {
|
||||
return RedisModule_ReplyWithError(ctx, "Global user currently used");
|
||||
}
|
||||
|
||||
RedisModuleAuthCtx *auth_ctx = RedisModule_CreateAuthCtx(HelloACL_UserChanged, NULL);
|
||||
if (RedisModule_AuthClientWithUser(ctx, global, auth_ctx) ==
|
||||
REDISMODULE_ERR) {
|
||||
return RedisModule_ReplyWithError(ctx, "Couldn't authenticate client");
|
||||
}
|
||||
|
||||
global_auth_ctx = auth_ctx;
|
||||
RedisModule_AuthenticateClientWithUser(ctx, global, HelloACL_UserChanged, NULL, &global_auth_client_id);
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
@@ -113,7 +103,7 @@ int HelloACL_Reply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
RedisModuleString *user_string = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
const char *name = RedisModule_StringPtrLen(user_string, &length);
|
||||
|
||||
if (RedisModule_AuthClientWithACLUser(ctx, name, length, NULL) ==
|
||||
if (RedisModule_AuthenticateClientWithACLUser(ctx, name, length, NULL, NULL, NULL) ==
|
||||
REDISMODULE_ERR) {
|
||||
return RedisModule_ReplyWithError(ctx, "Invalid Username or password");
|
||||
}
|
||||
@@ -127,7 +117,7 @@ int HelloACL_Timeout(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
|
||||
}
|
||||
|
||||
/* FreeData callback frees private data for HELLOACL.AUTHASYNC command. */
|
||||
/* Private data frees data for HELLOACL.AUTHASYNC command. */
|
||||
void HelloACL_FreeData(RedisModuleCtx *ctx, void *privdata) {
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
RedisModule_FreeString(NULL, privdata);
|
||||
@@ -150,8 +140,7 @@ int AuthAsyncCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
|
||||
pthread_t tid;
|
||||
RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, HelloACL_Reply,
|
||||
HelloACL_Timeout, HelloACL_FreeData, TIMEOUT_TIME);
|
||||
RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, HelloACL_Reply, HelloACL_Timeout, HelloACL_FreeData, TIMEOUT_TIME);
|
||||
|
||||
|
||||
void **targs = RedisModule_Alloc(sizeof(void*)*2);
|
||||
@@ -172,23 +161,23 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (RedisModule_Init(ctx, "helloacl", 1, REDISMODULE_APIVER_1)
|
||||
if (RedisModule_Init(ctx,"helloacl",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "helloacl.reset",
|
||||
ResetCommand_RedisCommand, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||
if (RedisModule_CreateCommand(ctx,"helloacl.reset",
|
||||
ResetCommand_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "helloacl.revoke",
|
||||
RevokeCommand_RedisCommand, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||
if (RedisModule_CreateCommand(ctx,"helloacl.revoke",
|
||||
RevokeCommand_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "helloacl.authglobal",
|
||||
AuthGlobalCommand_RedisCommand, "no-auth", 0, 0, 0) == REDISMODULE_ERR)
|
||||
if (RedisModule_CreateCommand(ctx,"helloacl.authglobal",
|
||||
AuthGlobalCommand_RedisCommand,"no-auth",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"helloacl.authasync",
|
||||
AuthAsyncCommand_RedisCommand, "no-auth", 0, 0, 0) == REDISMODULE_ERR)
|
||||
AuthAsyncCommand_RedisCommand,"no-auth",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
global = RedisModule_CreateModuleUser("global");
|
||||
@@ -196,7 +185,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
RedisModule_SetModuleUserACL(global, "allkeys");
|
||||
RedisModule_SetModuleUserACL(global, "on");
|
||||
|
||||
global_auth_ctx = NULL;
|
||||
global_auth_client_id = 0;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
+8
-4
@@ -106,11 +106,13 @@ void discardCommand(client *c) {
|
||||
/* Send a MULTI command to all the slaves and AOF file. Check the execCommand
|
||||
* implementation for more information. */
|
||||
void execCommandPropagateMulti(client *c) {
|
||||
robj *multistring = createStringObject("MULTI",5);
|
||||
propagate(server.multiCommand,c->db->id,&shared.multi,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
}
|
||||
|
||||
propagate(server.multiCommand,c->db->id,&multistring,1,
|
||||
void execCommandPropagateExec(client *c) {
|
||||
propagate(server.execCommand,c->db->id,&shared.exec,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(multistring);
|
||||
}
|
||||
|
||||
void execCommand(client *c) {
|
||||
@@ -175,8 +177,10 @@ void execCommand(client *c) {
|
||||
must_propagate = 1;
|
||||
}
|
||||
|
||||
int acl_retval = ACLCheckCommandPerm(c);
|
||||
int acl_keypos;
|
||||
int acl_retval = ACLCheckCommandPerm(c,&acl_keypos);
|
||||
if (acl_retval != ACL_OK) {
|
||||
addACLLogEntry(c,acl_retval,acl_keypos,NULL);
|
||||
addReplyErrorFormat(c,
|
||||
"-NOPERM ACLs rules changed between the moment the "
|
||||
"transaction was accumulated and the EXEC call. "
|
||||
|
||||
+240
-57
@@ -36,6 +36,7 @@
|
||||
|
||||
static void setProtocolError(const char *errstr, client *c);
|
||||
int postponeClientRead(client *c);
|
||||
int ProcessingEventsWhileBlocked = 0; /* See processEventsWhileBlocked(). */
|
||||
|
||||
/* Return the size consumed from the allocator, for the specified SDS string,
|
||||
* including internal fragmentation. This function is used in order to compute
|
||||
@@ -123,7 +124,8 @@ client *createClient(connection *conn) {
|
||||
c->ctime = c->lastinteraction = server.unixtime;
|
||||
/* If the default user does not require authentication, the user is
|
||||
* directly authenticated. */
|
||||
c->authenticated = (c->user->flags & USER_FLAG_NOPASS) != 0;
|
||||
c->authenticated = (c->user->flags & USER_FLAG_NOPASS) &&
|
||||
!(c->user->flags & USER_FLAG_DISABLED);
|
||||
c->replstate = REPL_STATE_NONE;
|
||||
c->repl_put_online_on_ack = 0;
|
||||
c->reploff = 0;
|
||||
@@ -154,7 +156,12 @@ client *createClient(connection *conn) {
|
||||
c->peerid = NULL;
|
||||
c->client_list_node = NULL;
|
||||
c->client_tracking_redirection = 0;
|
||||
c->auth_ctx = NULL;
|
||||
c->client_tracking_prefixes = NULL;
|
||||
c->client_cron_last_memory_usage = 0;
|
||||
c->client_cron_last_memory_type = CLIENT_TYPE_NORMAL;
|
||||
c->auth_callback = NULL;
|
||||
c->auth_callback_privdata = NULL;
|
||||
c->auth_module = NULL;
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||
if (conn) linkClient(c);
|
||||
@@ -367,13 +374,26 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
|
||||
* Where the master must propagate the first change even if the second
|
||||
* will produce an error. However it is useful to log such events since
|
||||
* they are rare and may hint at errors in a script or a bug in Redis. */
|
||||
if (c->flags & (CLIENT_MASTER|CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) {
|
||||
char* to = c->flags & CLIENT_MASTER? "master": "replica";
|
||||
char* from = c->flags & CLIENT_MASTER? "replica": "master";
|
||||
int ctype = getClientType(c);
|
||||
if (ctype == CLIENT_TYPE_MASTER || ctype == CLIENT_TYPE_SLAVE || c->id == CLIENT_ID_AOF) {
|
||||
char *to, *from;
|
||||
|
||||
if (c->id == CLIENT_ID_AOF) {
|
||||
to = "AOF-loading-client";
|
||||
from = "server";
|
||||
} else if (ctype == CLIENT_TYPE_MASTER) {
|
||||
to = "master";
|
||||
from = "replica";
|
||||
} else {
|
||||
to = "replica";
|
||||
from = "master";
|
||||
}
|
||||
|
||||
char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
|
||||
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
|
||||
"to its %s: '%s' after processing the command "
|
||||
"'%s'", from, to, s, cmdname);
|
||||
server.stat_unexpected_error_replies++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -780,7 +800,7 @@ void clientAcceptHandler(connection *conn) {
|
||||
serverLog(LL_WARNING,
|
||||
"Error accepting a client connection: %s",
|
||||
connGetLastError(conn));
|
||||
freeClient(c);
|
||||
freeClientAsync(c);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -822,7 +842,7 @@ void clientAcceptHandler(connection *conn) {
|
||||
/* Nothing to do, Just to avoid the warning... */
|
||||
}
|
||||
server.stat_rejected_conn++;
|
||||
freeClient(c);
|
||||
freeClientAsync(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -881,9 +901,10 @@ static void acceptCommonHandler(connection *conn, int flags, char *ip) {
|
||||
*/
|
||||
if (connAccept(conn, clientAcceptHandler) == C_ERR) {
|
||||
char conninfo[100];
|
||||
serverLog(LL_WARNING,
|
||||
"Error accepting a client connection: %s (conn: %s)",
|
||||
connGetLastError(conn), connGetInfo(conn, conninfo, sizeof(conninfo)));
|
||||
if (connGetState(conn) == CONN_STATE_ERROR)
|
||||
serverLog(LL_WARNING,
|
||||
"Error accepting a client connection: %s (conn: %s)",
|
||||
connGetLastError(conn), connGetInfo(conn, conninfo, sizeof(conninfo)));
|
||||
freeClient(connGetPrivateData(conn));
|
||||
return;
|
||||
}
|
||||
@@ -1072,7 +1093,7 @@ void freeClient(client *c) {
|
||||
}
|
||||
|
||||
/* Log link disconnection with slave */
|
||||
if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) {
|
||||
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
|
||||
serverLog(LL_WARNING,"Connection with replica %s lost.",
|
||||
replicationGetSlaveName(c));
|
||||
}
|
||||
@@ -1119,7 +1140,7 @@ void freeClient(client *c) {
|
||||
/* We need to remember the time when we started to have zero
|
||||
* attached slaves, as after some time we'll free the replication
|
||||
* backlog. */
|
||||
if (c->flags & CLIENT_SLAVE && listLength(server.slaves) == 0)
|
||||
if (getClientType(c) == CLIENT_TYPE_SLAVE && listLength(server.slaves) == 0)
|
||||
server.repl_no_slaves_since = server.unixtime;
|
||||
refreshGoodSlavesCount();
|
||||
/* Fire the replica change modules event. */
|
||||
@@ -1141,6 +1162,11 @@ void freeClient(client *c) {
|
||||
listDelNode(server.clients_to_close,ln);
|
||||
}
|
||||
|
||||
/* Remove the contribution that this client gave to our
|
||||
* incrementally computed memory usage. */
|
||||
server.stat_clients_type_memory[c->client_cron_last_memory_type] -=
|
||||
c->client_cron_last_memory_usage;
|
||||
|
||||
/* Release other dynamically allocated client structure fields,
|
||||
* and finally release the client structure itself. */
|
||||
if (c->name) decrRefCount(c->name);
|
||||
@@ -1160,9 +1186,14 @@ void freeClientAsync(client *c) {
|
||||
* may access the list while Redis uses I/O threads. All the other accesses
|
||||
* are in the context of the main thread while the other threads are
|
||||
* idle. */
|
||||
static pthread_mutex_t async_free_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
if (c->flags & CLIENT_CLOSE_ASAP || c->flags & CLIENT_LUA) return;
|
||||
c->flags |= CLIENT_CLOSE_ASAP;
|
||||
if (server.io_threads_num == 1) {
|
||||
/* no need to bother with locking if there's just one thread (the main thread) */
|
||||
listAddNodeTail(server.clients_to_close,c);
|
||||
return;
|
||||
}
|
||||
static pthread_mutex_t async_free_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_lock(&async_free_queue_mutex);
|
||||
listAddNodeTail(server.clients_to_close,c);
|
||||
pthread_mutex_unlock(&async_free_queue_mutex);
|
||||
@@ -1250,8 +1281,8 @@ int writeToClient(client *c, int handler_installed) {
|
||||
* just deliver as much data as it is possible to deliver.
|
||||
*
|
||||
* Moreover, we also send as much as possible if the client is
|
||||
* a slave (otherwise, on high-speed traffic, the replication
|
||||
* buffer will grow indefinitely) */
|
||||
* a slave or a monitor (otherwise, on high-speed traffic, the
|
||||
* replication/output buffer will grow indefinitely) */
|
||||
if (totwritten > NET_MAX_WRITES_PER_EVENT &&
|
||||
(server.maxmemory == 0 ||
|
||||
zmalloc_used_memory() < server.maxmemory) &&
|
||||
@@ -1356,6 +1387,12 @@ void resetClient(client *c) {
|
||||
if (!(c->flags & CLIENT_MULTI) && prevcmd != askingCommand)
|
||||
c->flags &= ~CLIENT_ASKING;
|
||||
|
||||
/* We do the same for the CACHING command as well. It also affects
|
||||
* the next command or transaction executed, in a way very similar
|
||||
* to ASKING. */
|
||||
if (!(c->flags & CLIENT_MULTI) && prevcmd != clientCommand)
|
||||
c->flags &= ~CLIENT_TRACKING_CACHING;
|
||||
|
||||
/* Remove the CLIENT_REPLY_SKIP flag if any so that the reply
|
||||
* to the next command will be sent, but set the flag if the command
|
||||
* we just processed was "CLIENT REPLY SKIP". */
|
||||
@@ -1437,7 +1474,7 @@ int processInlineBuffer(client *c) {
|
||||
/* Newline from slaves can be used to refresh the last ACK time.
|
||||
* This is useful for a slave to ping back while loading a big
|
||||
* RDB file. */
|
||||
if (querylen == 0 && c->flags & CLIENT_SLAVE)
|
||||
if (querylen == 0 && getClientType(c) == CLIENT_TYPE_SLAVE)
|
||||
c->repl_ack_time = server.unixtime;
|
||||
|
||||
/* Move querybuffer position to the next query in the buffer. */
|
||||
@@ -1451,12 +1488,8 @@ int processInlineBuffer(client *c) {
|
||||
|
||||
/* Create redis objects for all arguments. */
|
||||
for (c->argc = 0, j = 0; j < argc; j++) {
|
||||
if (sdslen(argv[j])) {
|
||||
c->argv[c->argc] = createObject(OBJ_STRING,argv[j]);
|
||||
c->argc++;
|
||||
} else {
|
||||
sdsfree(argv[j]);
|
||||
}
|
||||
c->argv[c->argc] = createObject(OBJ_STRING,argv[j]);
|
||||
c->argc++;
|
||||
}
|
||||
zfree(argv);
|
||||
return C_OK;
|
||||
@@ -2000,7 +2033,6 @@ int clientSetNameOrReply(client *c, robj *name) {
|
||||
if (len == 0) {
|
||||
if (c->name) decrRefCount(c->name);
|
||||
c->name = NULL;
|
||||
addReply(c,shared.ok);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
@@ -2024,7 +2056,6 @@ int clientSetNameOrReply(client *c, robj *name) {
|
||||
void clientCommand(client *c) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
client *client;
|
||||
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
@@ -2041,7 +2072,7 @@ void clientCommand(client *c) {
|
||||
"REPLY (on|off|skip) -- Control the replies sent to the current connection.",
|
||||
"SETNAME <name> -- Assign the name <name> to the current connection.",
|
||||
"UNBLOCK <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.",
|
||||
"TRACKING (on|off) [REDIRECT <id>] -- Enable client keys tracking for client side caching.",
|
||||
"TRACKING (on|off) [REDIRECT <id>] [BCAST] [PREFIX first] [PREFIX second] [OPTIN] [OPTOUT]... -- Enable client keys tracking for client side caching.",
|
||||
"GETREDIR -- Return the client ID we are redirecting to when tracking is enabled.",
|
||||
NULL
|
||||
};
|
||||
@@ -2138,7 +2169,7 @@ NULL
|
||||
/* Iterate clients killing all the matching clients. */
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client = listNodeValue(ln);
|
||||
client *client = listNodeValue(ln);
|
||||
if (addr && strcmp(getClientPeerId(client),addr) != 0) continue;
|
||||
if (type != -1 && getClientType(client) != type) continue;
|
||||
if (id != 0 && client->id != id) continue;
|
||||
@@ -2216,38 +2247,150 @@ NULL
|
||||
UNIT_MILLISECONDS) != C_OK) return;
|
||||
pauseClients(duration);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"tracking") &&
|
||||
(c->argc == 3 || c->argc == 5))
|
||||
{
|
||||
/* CLIENT TRACKING (on|off) [REDIRECT <id>] */
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"tracking") && c->argc >= 3) {
|
||||
/* CLIENT TRACKING (on|off) [REDIRECT <id>] [BCAST] [PREFIX first]
|
||||
* [PREFIX second] [OPTIN] [OPTOUT] ... */
|
||||
long long redir = 0;
|
||||
uint64_t options = 0;
|
||||
robj **prefix = NULL;
|
||||
size_t numprefix = 0;
|
||||
|
||||
/* Parse the redirection option: we'll require the client with
|
||||
* the specified ID to exist right now, even if it is possible
|
||||
* it will get disconnected later. */
|
||||
if (c->argc == 5) {
|
||||
if (strcasecmp(c->argv[3]->ptr,"redirect") != 0) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
} else {
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[4],&redir,NULL) !=
|
||||
C_OK) return;
|
||||
/* Parse the options. */
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
int moreargs = (c->argc-1) - j;
|
||||
|
||||
if (!strcasecmp(c->argv[j]->ptr,"redirect") && moreargs) {
|
||||
j++;
|
||||
if (redir != 0) {
|
||||
addReplyError(c,"A client can only redirect to a single "
|
||||
"other client");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j],&redir,NULL) !=
|
||||
C_OK)
|
||||
{
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
/* We will require the client with the specified ID to exist
|
||||
* right now, even if it is possible that it gets disconnected
|
||||
* later. Still a valid sanity check. */
|
||||
if (lookupClientByID(redir) == NULL) {
|
||||
addReplyError(c,"The client ID you want redirect to "
|
||||
"does not exist");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"bcast")) {
|
||||
options |= CLIENT_TRACKING_BCAST;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"optin")) {
|
||||
options |= CLIENT_TRACKING_OPTIN;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"optout")) {
|
||||
options |= CLIENT_TRACKING_OPTOUT;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"prefix") && moreargs) {
|
||||
j++;
|
||||
prefix = zrealloc(prefix,sizeof(robj*)*(numprefix+1));
|
||||
prefix[numprefix++] = c->argv[j];
|
||||
} else {
|
||||
zfree(prefix);
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Options are ok: enable or disable the tracking for this client. */
|
||||
if (!strcasecmp(c->argv[2]->ptr,"on")) {
|
||||
enableTracking(c,redir);
|
||||
/* Before enabling tracking, make sure options are compatible
|
||||
* among each other and with the current state of the client. */
|
||||
if (!(options & CLIENT_TRACKING_BCAST) && numprefix) {
|
||||
addReplyError(c,
|
||||
"PREFIX option requires BCAST mode to be enabled");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c->flags & CLIENT_TRACKING) {
|
||||
int oldbcast = !!(c->flags & CLIENT_TRACKING_BCAST);
|
||||
int newbcast = !!(options & CLIENT_TRACKING_BCAST);
|
||||
if (oldbcast != newbcast) {
|
||||
addReplyError(c,
|
||||
"You can't switch BCAST mode on/off before disabling "
|
||||
"tracking for this client, and then re-enabling it with "
|
||||
"a different mode.");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (options & CLIENT_TRACKING_BCAST &&
|
||||
options & (CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT))
|
||||
{
|
||||
addReplyError(c,
|
||||
"OPTIN and OPTOUT are not compatible with BCAST");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options & CLIENT_TRACKING_OPTIN && options & CLIENT_TRACKING_OPTOUT)
|
||||
{
|
||||
addReplyError(c,
|
||||
"You can't specify both OPTIN mode and OPTOUT mode");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((options & CLIENT_TRACKING_OPTIN && c->flags & CLIENT_TRACKING_OPTOUT) ||
|
||||
(options & CLIENT_TRACKING_OPTOUT && c->flags & CLIENT_TRACKING_OPTIN))
|
||||
{
|
||||
addReplyError(c,
|
||||
"You can't switch OPTIN/OPTOUT mode before disabling "
|
||||
"tracking for this client, and then re-enabling it with "
|
||||
"a different mode.");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
enableTracking(c,redir,options,prefix,numprefix);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"off")) {
|
||||
disableTracking(c);
|
||||
} else {
|
||||
zfree(prefix);
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
zfree(prefix);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"caching") && c->argc >= 3) {
|
||||
if (!(c->flags & CLIENT_TRACKING)) {
|
||||
addReplyError(c,"CLIENT CACHING can be called only when the "
|
||||
"client is in tracking mode with OPTIN or "
|
||||
"OPTOUT mode enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
char *opt = c->argv[2]->ptr;
|
||||
if (!strcasecmp(opt,"yes")) {
|
||||
if (c->flags & CLIENT_TRACKING_OPTIN) {
|
||||
c->flags |= CLIENT_TRACKING_CACHING;
|
||||
} else {
|
||||
addReplyError(c,"CLIENT CACHING YES is only valid when tracking is enabled in OPTIN mode.");
|
||||
return;
|
||||
}
|
||||
} else if (!strcasecmp(opt,"no")) {
|
||||
if (c->flags & CLIENT_TRACKING_OPTOUT) {
|
||||
c->flags |= CLIENT_TRACKING_CACHING;
|
||||
} else {
|
||||
addReplyError(c,"CLIENT CACHING NO is only valid when tracking is enabled in OPTOUT mode.");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Common reply for when we succeeded. */
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"getredir") && c->argc == 2) {
|
||||
/* CLIENT GETREDIR */
|
||||
@@ -2436,12 +2579,14 @@ unsigned long getClientOutputBufferMemoryUsage(client *c) {
|
||||
*
|
||||
* The function will return one of the following:
|
||||
* CLIENT_TYPE_NORMAL -> Normal client
|
||||
* CLIENT_TYPE_SLAVE -> Slave or client executing MONITOR command
|
||||
* CLIENT_TYPE_SLAVE -> Slave
|
||||
* CLIENT_TYPE_PUBSUB -> Client subscribed to Pub/Sub channels
|
||||
* CLIENT_TYPE_MASTER -> The client representing our replication master.
|
||||
*/
|
||||
int getClientType(client *c) {
|
||||
if (c->flags & CLIENT_MASTER) return CLIENT_TYPE_MASTER;
|
||||
/* Even though MONITOR clients are marked as replicas, we
|
||||
* want the expose them as normal clients. */
|
||||
if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR))
|
||||
return CLIENT_TYPE_SLAVE;
|
||||
if (c->flags & CLIENT_PUBSUB) return CLIENT_TYPE_PUBSUB;
|
||||
@@ -2634,6 +2779,12 @@ int clientsArePaused(void) {
|
||||
int processEventsWhileBlocked(void) {
|
||||
int iterations = 4; /* See the function top-comment. */
|
||||
int count = 0;
|
||||
|
||||
/* Note: when we are processing events while blocked (for instance during
|
||||
* busy Lua scripts), we set a global flag. When such flag is set, we
|
||||
* avoid handling the read part of clients using threaded I/O.
|
||||
* See https://github.com/antirez/redis/issues/6988 for more info. */
|
||||
ProcessingEventsWhileBlocked = 1;
|
||||
while (iterations--) {
|
||||
int events = 0;
|
||||
events += aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
@@ -2641,6 +2792,7 @@ int processEventsWhileBlocked(void) {
|
||||
if (!events) break;
|
||||
count += events;
|
||||
}
|
||||
ProcessingEventsWhileBlocked = 0;
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -2659,6 +2811,10 @@ pthread_mutex_t io_threads_mutex[IO_THREADS_MAX_NUM];
|
||||
_Atomic unsigned long io_threads_pending[IO_THREADS_MAX_NUM];
|
||||
int io_threads_active; /* Are the threads currently spinning waiting I/O? */
|
||||
int io_threads_op; /* IO_THREADS_OP_WRITE or IO_THREADS_OP_READ. */
|
||||
|
||||
/* This is the list of clients each thread will serve when threaded I/O is
|
||||
* used. We spawn io_threads_num-1 threads, since one is the main thread
|
||||
* itself. */
|
||||
list *io_threads_list[IO_THREADS_MAX_NUM];
|
||||
|
||||
void *IOThreadMain(void *myid) {
|
||||
@@ -2719,12 +2875,16 @@ void initThreadedIO(void) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Spawn the I/O threads. */
|
||||
/* Spawn and initialize the I/O threads. */
|
||||
for (int i = 0; i < server.io_threads_num; i++) {
|
||||
/* Things we do for all the threads including the main thread. */
|
||||
io_threads_list[i] = listCreate();
|
||||
if (i == 0) continue; /* Thread 0 is the main thread. */
|
||||
|
||||
/* Things we do only for the additional threads. */
|
||||
pthread_t tid;
|
||||
pthread_mutex_init(&io_threads_mutex[i],NULL);
|
||||
io_threads_pending[i] = 0;
|
||||
io_threads_list[i] = listCreate();
|
||||
pthread_mutex_lock(&io_threads_mutex[i]); /* Thread will be stopped. */
|
||||
if (pthread_create(&tid,NULL,IOThreadMain,(void*)(long)i) != 0) {
|
||||
serverLog(LL_WARNING,"Fatal: Can't initialize IO thread.");
|
||||
@@ -2738,7 +2898,7 @@ void startThreadedIO(void) {
|
||||
if (tio_debug) { printf("S"); fflush(stdout); }
|
||||
if (tio_debug) printf("--- STARTING THREADED IO ---\n");
|
||||
serverAssert(io_threads_active == 0);
|
||||
for (int j = 0; j < server.io_threads_num; j++)
|
||||
for (int j = 1; j < server.io_threads_num; j++)
|
||||
pthread_mutex_unlock(&io_threads_mutex[j]);
|
||||
io_threads_active = 1;
|
||||
}
|
||||
@@ -2752,7 +2912,7 @@ void stopThreadedIO(void) {
|
||||
(int) listLength(server.clients_pending_read),
|
||||
(int) listLength(server.clients_pending_write));
|
||||
serverAssert(io_threads_active == 1);
|
||||
for (int j = 0; j < server.io_threads_num; j++)
|
||||
for (int j = 1; j < server.io_threads_num; j++)
|
||||
pthread_mutex_lock(&io_threads_mutex[j]);
|
||||
io_threads_active = 0;
|
||||
}
|
||||
@@ -2811,15 +2971,23 @@ int handleClientsWithPendingWritesUsingThreads(void) {
|
||||
/* Give the start condition to the waiting threads, by setting the
|
||||
* start condition atomic var. */
|
||||
io_threads_op = IO_THREADS_OP_WRITE;
|
||||
for (int j = 0; j < server.io_threads_num; j++) {
|
||||
for (int j = 1; j < server.io_threads_num; j++) {
|
||||
int count = listLength(io_threads_list[j]);
|
||||
io_threads_pending[j] = count;
|
||||
}
|
||||
|
||||
/* Wait for all threads to end their work. */
|
||||
/* Also use the main thread to process a slice of clients. */
|
||||
listRewind(io_threads_list[0],&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *c = listNodeValue(ln);
|
||||
writeToClient(c,0);
|
||||
}
|
||||
listEmpty(io_threads_list[0]);
|
||||
|
||||
/* Wait for all the other threads to end their work. */
|
||||
while(1) {
|
||||
unsigned long pending = 0;
|
||||
for (int j = 0; j < server.io_threads_num; j++)
|
||||
for (int j = 1; j < server.io_threads_num; j++)
|
||||
pending += io_threads_pending[j];
|
||||
if (pending == 0) break;
|
||||
}
|
||||
@@ -2850,6 +3018,7 @@ int handleClientsWithPendingWritesUsingThreads(void) {
|
||||
int postponeClientRead(client *c) {
|
||||
if (io_threads_active &&
|
||||
server.io_threads_do_reads &&
|
||||
!ProcessingEventsWhileBlocked &&
|
||||
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ)))
|
||||
{
|
||||
c->flags |= CLIENT_PENDING_READ;
|
||||
@@ -2888,31 +3057,45 @@ int handleClientsWithPendingReadsUsingThreads(void) {
|
||||
/* Give the start condition to the waiting threads, by setting the
|
||||
* start condition atomic var. */
|
||||
io_threads_op = IO_THREADS_OP_READ;
|
||||
for (int j = 0; j < server.io_threads_num; j++) {
|
||||
for (int j = 1; j < server.io_threads_num; j++) {
|
||||
int count = listLength(io_threads_list[j]);
|
||||
io_threads_pending[j] = count;
|
||||
}
|
||||
|
||||
/* Wait for all threads to end their work. */
|
||||
/* Also use the main thread to process a slice of clients. */
|
||||
listRewind(io_threads_list[0],&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *c = listNodeValue(ln);
|
||||
readQueryFromClient(c->conn);
|
||||
}
|
||||
listEmpty(io_threads_list[0]);
|
||||
|
||||
/* Wait for all the other threads to end their work. */
|
||||
while(1) {
|
||||
unsigned long pending = 0;
|
||||
for (int j = 0; j < server.io_threads_num; j++)
|
||||
for (int j = 1; j < server.io_threads_num; j++)
|
||||
pending += io_threads_pending[j];
|
||||
if (pending == 0) break;
|
||||
}
|
||||
if (tio_debug) printf("I/O READ All threads finshed\n");
|
||||
|
||||
/* Run the list of clients again to process the new buffers. */
|
||||
listRewind(server.clients_pending_read,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
while(listLength(server.clients_pending_read)) {
|
||||
ln = listFirst(server.clients_pending_read);
|
||||
client *c = listNodeValue(ln);
|
||||
c->flags &= ~CLIENT_PENDING_READ;
|
||||
listDelNode(server.clients_pending_read,ln);
|
||||
|
||||
if (c->flags & CLIENT_PENDING_COMMAND) {
|
||||
c->flags &= ~ CLIENT_PENDING_COMMAND;
|
||||
processCommandAndResetClient(c);
|
||||
c->flags &= ~CLIENT_PENDING_COMMAND;
|
||||
if (processCommandAndResetClient(c) == C_ERR) {
|
||||
/* If the client is no longer valid, we avoid
|
||||
* processing the client later. So we just go
|
||||
* to the next. */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
processInputBufferAndReplicate(c);
|
||||
}
|
||||
listEmpty(server.clients_pending_read);
|
||||
return processed;
|
||||
}
|
||||
|
||||
+1
-1
@@ -82,10 +82,10 @@ sds keyspaceEventsFlagsToString(int flags) {
|
||||
if (flags & NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
|
||||
if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
|
||||
if (flags & NOTIFY_STREAM) res = sdscatlen(res,"t",1);
|
||||
if (flags & NOTIFY_KEY_MISS) res = sdscatlen(res,"m",1);
|
||||
}
|
||||
if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
|
||||
if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
|
||||
if (flags & NOTIFY_KEY_MISS) res = sdscatlen(res,"m",1);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
+24
-56
@@ -347,7 +347,15 @@ void freeStreamObject(robj *o) {
|
||||
}
|
||||
|
||||
void incrRefCount(robj *o) {
|
||||
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount++;
|
||||
if (o->refcount < OBJ_FIRST_SPECIAL_REFCOUNT) {
|
||||
o->refcount++;
|
||||
} else {
|
||||
if (o->refcount == OBJ_SHARED_REFCOUNT) {
|
||||
/* Nothing to do: this refcount is immutable. */
|
||||
} else if (o->refcount == OBJ_STATIC_REFCOUNT) {
|
||||
serverPanic("You tried to retain an object allocated in the stack");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decrRefCount(robj *o) {
|
||||
@@ -606,21 +614,13 @@ size_t stringObjectLen(robj *o) {
|
||||
|
||||
int getDoubleFromObject(const robj *o, double *target) {
|
||||
double value;
|
||||
char *eptr;
|
||||
|
||||
if (o == NULL) {
|
||||
value = 0;
|
||||
} else {
|
||||
serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
|
||||
if (sdsEncodedObject(o)) {
|
||||
errno = 0;
|
||||
value = strtod(o->ptr, &eptr);
|
||||
if (sdslen(o->ptr) == 0 ||
|
||||
isspace(((const char*)o->ptr)[0]) ||
|
||||
(size_t)(eptr-(char*)o->ptr) != sdslen(o->ptr) ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
isnan(value))
|
||||
if (!string2d(o->ptr, sdslen(o->ptr), &value))
|
||||
return C_ERR;
|
||||
} else if (o->encoding == OBJ_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
@@ -648,21 +648,13 @@ int getDoubleFromObjectOrReply(client *c, robj *o, double *target, const char *m
|
||||
|
||||
int getLongDoubleFromObject(robj *o, long double *target) {
|
||||
long double value;
|
||||
char *eptr;
|
||||
|
||||
if (o == NULL) {
|
||||
value = 0;
|
||||
} else {
|
||||
serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
|
||||
if (sdsEncodedObject(o)) {
|
||||
errno = 0;
|
||||
value = strtold(o->ptr, &eptr);
|
||||
if (sdslen(o->ptr) == 0 ||
|
||||
isspace(((const char*)o->ptr)[0]) ||
|
||||
(size_t)(eptr-(char*)o->ptr) != sdslen(o->ptr) ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
isnan(value))
|
||||
if (!string2ld(o->ptr, sdslen(o->ptr), &value))
|
||||
return C_ERR;
|
||||
} else if (o->encoding == OBJ_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
@@ -990,39 +982,15 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
mh->repl_backlog = mem;
|
||||
mem_total += mem;
|
||||
|
||||
mem = 0;
|
||||
if (listLength(server.slaves)) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *c = listNodeValue(ln);
|
||||
mem += getClientOutputBufferMemoryUsage(c);
|
||||
mem += sdsAllocSize(c->querybuf);
|
||||
mem += sizeof(client);
|
||||
}
|
||||
}
|
||||
mh->clients_slaves = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
mem = 0;
|
||||
if (listLength(server.clients)) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.clients,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *c = listNodeValue(ln);
|
||||
if (c->flags & CLIENT_SLAVE && !(c->flags & CLIENT_MONITOR))
|
||||
continue;
|
||||
mem += getClientOutputBufferMemoryUsage(c);
|
||||
mem += sdsAllocSize(c->querybuf);
|
||||
mem += sizeof(client);
|
||||
}
|
||||
}
|
||||
mh->clients_normal = mem;
|
||||
mem_total+=mem;
|
||||
/* Computing the memory used by the clients would be O(N) if done
|
||||
* here online. We use our values computed incrementally by
|
||||
* clientsCronTrackClientsMemUsage(). */
|
||||
mh->clients_slaves = server.stat_clients_type_memory[CLIENT_TYPE_SLAVE];
|
||||
mh->clients_normal = server.stat_clients_type_memory[CLIENT_TYPE_MASTER]+
|
||||
server.stat_clients_type_memory[CLIENT_TYPE_PUBSUB]+
|
||||
server.stat_clients_type_memory[CLIENT_TYPE_NORMAL];
|
||||
mem_total += mh->clients_slaves;
|
||||
mem_total += mh->clients_normal;
|
||||
|
||||
mem = 0;
|
||||
if (server.aof_state != AOF_OFF) {
|
||||
@@ -1127,13 +1095,13 @@ sds getMemoryDoctorReport(void) {
|
||||
num_reports++;
|
||||
}
|
||||
|
||||
/* Allocator fss is higher than 1.1 and 10MB ? */
|
||||
/* Allocator rss is higher than 1.1 and 10MB ? */
|
||||
if (mh->allocator_rss > 1.1 && mh->allocator_rss_bytes > 10<<20) {
|
||||
high_alloc_rss = 1;
|
||||
num_reports++;
|
||||
}
|
||||
|
||||
/* Non-Allocator fss is higher than 1.1 and 10MB ? */
|
||||
/* Non-Allocator rss is higher than 1.1 and 10MB ? */
|
||||
if (mh->rss_extra > 1.1 && mh->rss_extra_bytes > 10<<20) {
|
||||
high_proc_rss = 1;
|
||||
num_reports++;
|
||||
@@ -1210,7 +1178,7 @@ sds getMemoryDoctorReport(void) {
|
||||
* is MAXMEMORY_FLAG_LRU.
|
||||
* Either or both of them may be <0, in that case, nothing is set. */
|
||||
int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
long long lru_clock) {
|
||||
long long lru_clock, int lru_multiplier) {
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
if (lfu_freq >= 0) {
|
||||
serverAssert(lfu_freq <= 255);
|
||||
@@ -1222,7 +1190,7 @@ int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
* according to the LRU clock resolution this Redis
|
||||
* instance was compiled with (normally 1000 ms, so the
|
||||
* below statement will expand to lru_idle*1000/1000. */
|
||||
lru_idle = lru_idle*1000/LRU_CLOCK_RESOLUTION;
|
||||
lru_idle = lru_idle*lru_multiplier/LRU_CLOCK_RESOLUTION;
|
||||
long lru_abs = lru_clock - lru_idle; /* Absolute access time. */
|
||||
/* If the LRU field underflows (since LRU it is a wrapping
|
||||
* clock), the best we can do is to provide a large enough LRU
|
||||
|
||||
+47
-13
@@ -35,7 +35,11 @@ int clientSubscriptionsCount(client *c);
|
||||
* Pubsub client replies API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Send a pubsub message of type "message" to the client. */
|
||||
/* Send a pubsub message of type "message" to the client.
|
||||
* Normally 'msg' is a Redis object containing the string to send as
|
||||
* message. However if the caller sets 'msg' as NULL, it will be able
|
||||
* to send a special message (for instance an Array type) by using the
|
||||
* addReply*() API family. */
|
||||
void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
@@ -43,7 +47,7 @@ void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.messagebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,msg);
|
||||
if (msg) addReplyBulk(c,msg);
|
||||
}
|
||||
|
||||
/* Send a pubsub message of type "pmessage" to the client. The difference
|
||||
@@ -202,6 +206,8 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
|
||||
|
||||
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */
|
||||
int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
dictEntry *de;
|
||||
list *clients;
|
||||
int retval = 0;
|
||||
|
||||
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
|
||||
@@ -213,6 +219,16 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
pat->pattern = getDecodedObject(pattern);
|
||||
pat->client = c;
|
||||
listAddNodeTail(server.pubsub_patterns,pat);
|
||||
/* Add the client to the pattern -> list of clients hash table */
|
||||
de = dictFind(server.pubsub_patterns_dict,pattern);
|
||||
if (de == NULL) {
|
||||
clients = listCreate();
|
||||
dictAdd(server.pubsub_patterns_dict,pattern,clients);
|
||||
incrRefCount(pattern);
|
||||
} else {
|
||||
clients = dictGetVal(de);
|
||||
}
|
||||
listAddNodeTail(clients,c);
|
||||
}
|
||||
/* Notify the client */
|
||||
addReplyPubsubPatSubscribed(c,pattern);
|
||||
@@ -222,6 +238,8 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was not subscribed to the specified channel. */
|
||||
int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
dictEntry *de;
|
||||
list *clients;
|
||||
listNode *ln;
|
||||
pubsubPattern pat;
|
||||
int retval = 0;
|
||||
@@ -234,6 +252,18 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
pat.pattern = pattern;
|
||||
ln = listSearchKey(server.pubsub_patterns,&pat);
|
||||
listDelNode(server.pubsub_patterns,ln);
|
||||
/* Remove the client from the pattern -> clients list hash table */
|
||||
de = dictFind(server.pubsub_patterns_dict,pattern);
|
||||
serverAssertWithInfo(c,NULL,de != NULL);
|
||||
clients = dictGetVal(de);
|
||||
ln = listSearchKey(clients,c);
|
||||
serverAssertWithInfo(c,NULL,ln != NULL);
|
||||
listDelNode(clients,ln);
|
||||
if (listLength(clients) == 0) {
|
||||
/* Free the list and associated hash entry at all if this was
|
||||
* the latest client. */
|
||||
dictDelete(server.pubsub_patterns_dict,pattern);
|
||||
}
|
||||
}
|
||||
/* Notify the client */
|
||||
if (notify) addReplyPubsubPatUnsubscribed(c,pattern);
|
||||
@@ -280,6 +310,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) {
|
||||
int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
int receivers = 0;
|
||||
dictEntry *de;
|
||||
dictIterator *di;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
@@ -298,23 +329,26 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
}
|
||||
}
|
||||
/* Send to clients listening to matching channels */
|
||||
if (listLength(server.pubsub_patterns)) {
|
||||
listRewind(server.pubsub_patterns,&li);
|
||||
di = dictGetIterator(server.pubsub_patterns_dict);
|
||||
if (di) {
|
||||
channel = getDecodedObject(channel);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
pubsubPattern *pat = ln->value;
|
||||
|
||||
if (stringmatchlen((char*)pat->pattern->ptr,
|
||||
sdslen(pat->pattern->ptr),
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *pattern = dictGetKey(de);
|
||||
list *clients = dictGetVal(de);
|
||||
if (!stringmatchlen((char*)pattern->ptr,
|
||||
sdslen(pattern->ptr),
|
||||
(char*)channel->ptr,
|
||||
sdslen(channel->ptr),0))
|
||||
{
|
||||
addReplyPubsubPatMessage(pat->client,
|
||||
pat->pattern,channel,message);
|
||||
sdslen(channel->ptr),0)) continue;
|
||||
|
||||
listRewind(clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = listNodeValue(ln);
|
||||
addReplyPubsubPatMessage(c,pattern,channel,message);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
decrRefCount(channel);
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
return receivers;
|
||||
}
|
||||
|
||||
+148
-2
@@ -70,6 +70,12 @@ static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};
|
||||
} while (0);
|
||||
#endif
|
||||
|
||||
/* Bookmarks forward declarations */
|
||||
#define QL_MAX_BM ((1 << QL_BM_BITS)-1)
|
||||
quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name);
|
||||
quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node);
|
||||
void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm);
|
||||
|
||||
/* Simple way to give quicklistEntry structs default values with one call. */
|
||||
#define initEntry(e) \
|
||||
do { \
|
||||
@@ -100,10 +106,11 @@ quicklist *quicklistCreate(void) {
|
||||
quicklist->count = 0;
|
||||
quicklist->compress = 0;
|
||||
quicklist->fill = -2;
|
||||
quicklist->bookmark_count = 0;
|
||||
return quicklist;
|
||||
}
|
||||
|
||||
#define COMPRESS_MAX (1 << 16)
|
||||
#define COMPRESS_MAX ((1 << QL_COMP_BITS)-1)
|
||||
void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
|
||||
if (compress > COMPRESS_MAX) {
|
||||
compress = COMPRESS_MAX;
|
||||
@@ -113,7 +120,7 @@ void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
|
||||
quicklist->compress = compress;
|
||||
}
|
||||
|
||||
#define FILL_MAX (1 << 15)
|
||||
#define FILL_MAX ((1 << (QL_FILL_BITS-1))-1)
|
||||
void quicklistSetFill(quicklist *quicklist, int fill) {
|
||||
if (fill > FILL_MAX) {
|
||||
fill = FILL_MAX;
|
||||
@@ -169,6 +176,7 @@ void quicklistRelease(quicklist *quicklist) {
|
||||
quicklist->len--;
|
||||
current = next;
|
||||
}
|
||||
quicklistBookmarksClear(quicklist);
|
||||
zfree(quicklist);
|
||||
}
|
||||
|
||||
@@ -578,6 +586,15 @@ quicklist *quicklistCreateFromZiplist(int fill, int compress,
|
||||
|
||||
REDIS_STATIC void __quicklistDelNode(quicklist *quicklist,
|
||||
quicklistNode *node) {
|
||||
/* Update the bookmark if any */
|
||||
quicklistBookmark *bm = _quicklistBookmarkFindByNode(quicklist, node);
|
||||
if (bm) {
|
||||
bm->node = node->next;
|
||||
/* if the bookmark was to the last node, delete it. */
|
||||
if (!bm->node)
|
||||
_quicklistBookmarkDelete(quicklist, bm);
|
||||
}
|
||||
|
||||
if (node->next)
|
||||
node->next->prev = node->prev;
|
||||
if (node->prev)
|
||||
@@ -1410,6 +1427,87 @@ void quicklistPush(quicklist *quicklist, void *value, const size_t sz,
|
||||
}
|
||||
}
|
||||
|
||||
/* Create or update a bookmark in the list which will be updated to the next node
|
||||
* automatically when the one referenced gets deleted.
|
||||
* Returns 1 on success (creation of new bookmark or override of an existing one).
|
||||
* Returns 0 on failure (reached the maximum supported number of bookmarks).
|
||||
* NOTE: use short simple names, so that string compare on find is quick.
|
||||
* NOTE: bookmakrk creation may re-allocate the quicklist, so the input pointer
|
||||
may change and it's the caller responsibilty to update the reference.
|
||||
*/
|
||||
int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node) {
|
||||
quicklist *ql = *ql_ref;
|
||||
if (ql->bookmark_count >= QL_MAX_BM)
|
||||
return 0;
|
||||
quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
|
||||
if (bm) {
|
||||
bm->node = node;
|
||||
return 1;
|
||||
}
|
||||
ql = zrealloc(ql, sizeof(quicklist) + (ql->bookmark_count+1) * sizeof(quicklistBookmark));
|
||||
*ql_ref = ql;
|
||||
ql->bookmarks[ql->bookmark_count].node = node;
|
||||
ql->bookmarks[ql->bookmark_count].name = zstrdup(name);
|
||||
ql->bookmark_count++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Find the quicklist node referenced by a named bookmark.
|
||||
* When the bookmarked node is deleted the bookmark is updated to the next node,
|
||||
* and if that's the last node, the bookmark is deleted (so find returns NULL). */
|
||||
quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name) {
|
||||
quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
|
||||
if (!bm) return NULL;
|
||||
return bm->node;
|
||||
}
|
||||
|
||||
/* Delete a named bookmark.
|
||||
* returns 0 if bookmark was not found, and 1 if deleted.
|
||||
* Note that the bookmark memory is not freed yet, and is kept for future use. */
|
||||
int quicklistBookmarkDelete(quicklist *ql, const char *name) {
|
||||
quicklistBookmark *bm = _quicklistBookmarkFindByName(ql, name);
|
||||
if (!bm)
|
||||
return 0;
|
||||
_quicklistBookmarkDelete(ql, bm);
|
||||
return 1;
|
||||
}
|
||||
|
||||
quicklistBookmark *_quicklistBookmarkFindByName(quicklist *ql, const char *name) {
|
||||
unsigned i;
|
||||
for (i=0; i<ql->bookmark_count; i++) {
|
||||
if (!strcmp(ql->bookmarks[i].name, name)) {
|
||||
return &ql->bookmarks[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
quicklistBookmark *_quicklistBookmarkFindByNode(quicklist *ql, quicklistNode *node) {
|
||||
unsigned i;
|
||||
for (i=0; i<ql->bookmark_count; i++) {
|
||||
if (ql->bookmarks[i].node == node) {
|
||||
return &ql->bookmarks[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void _quicklistBookmarkDelete(quicklist *ql, quicklistBookmark *bm) {
|
||||
int index = bm - ql->bookmarks;
|
||||
zfree(bm->name);
|
||||
ql->bookmark_count--;
|
||||
memmove(bm, bm+1, (ql->bookmark_count - index)* sizeof(*bm));
|
||||
/* NOTE: We do not shrink (realloc) the quicklist yet (to avoid resonance,
|
||||
* it may be re-used later (a call to realloc may NOP). */
|
||||
}
|
||||
|
||||
void quicklistBookmarksClear(quicklist *ql) {
|
||||
while (ql->bookmark_count)
|
||||
zfree(ql->bookmarks[--ql->bookmark_count].name);
|
||||
/* NOTE: We do not shrink (realloc) the quick list. main use case for this
|
||||
* function is just before releasing the allocation. */
|
||||
}
|
||||
|
||||
/* The rest of this file is test cases and test helpers. */
|
||||
#ifdef REDIS_TEST
|
||||
#include <stdint.h>
|
||||
@@ -2641,6 +2739,54 @@ int quicklistTest(int argc, char *argv[]) {
|
||||
printf("Compressions: %0.2f seconds.\n", (float)(stop - start) / 1000);
|
||||
printf("\n");
|
||||
|
||||
TEST("bookmark get updated to next item") {
|
||||
quicklist *ql = quicklistNew(1, 0);
|
||||
quicklistPushTail(ql, "1", 1);
|
||||
quicklistPushTail(ql, "2", 1);
|
||||
quicklistPushTail(ql, "3", 1);
|
||||
quicklistPushTail(ql, "4", 1);
|
||||
quicklistPushTail(ql, "5", 1);
|
||||
assert(ql->len==5);
|
||||
/* add two bookmarks, one pointing to the node before the last. */
|
||||
assert(quicklistBookmarkCreate(&ql, "_dummy", ql->head->next));
|
||||
assert(quicklistBookmarkCreate(&ql, "_test", ql->tail->prev));
|
||||
/* test that the bookmark returns the right node, delete it and see that the bookmark points to the last node */
|
||||
assert(quicklistBookmarkFind(ql, "_test") == ql->tail->prev);
|
||||
assert(quicklistDelRange(ql, -2, 1));
|
||||
assert(quicklistBookmarkFind(ql, "_test") == ql->tail);
|
||||
/* delete the last node, and see that the bookmark was deleted. */
|
||||
assert(quicklistDelRange(ql, -1, 1));
|
||||
assert(quicklistBookmarkFind(ql, "_test") == NULL);
|
||||
/* test that other bookmarks aren't affected */
|
||||
assert(quicklistBookmarkFind(ql, "_dummy") == ql->head->next);
|
||||
assert(quicklistBookmarkFind(ql, "_missing") == NULL);
|
||||
assert(ql->len==3);
|
||||
quicklistBookmarksClear(ql); /* for coverage */
|
||||
assert(quicklistBookmarkFind(ql, "_dummy") == NULL);
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
TEST("bookmark limit") {
|
||||
int i;
|
||||
quicklist *ql = quicklistNew(1, 0);
|
||||
quicklistPushHead(ql, "1", 1);
|
||||
for (i=0; i<QL_MAX_BM; i++)
|
||||
assert(quicklistBookmarkCreate(&ql, genstr("",i), ql->head));
|
||||
/* when all bookmarks are used, creation fails */
|
||||
assert(!quicklistBookmarkCreate(&ql, "_test", ql->head));
|
||||
/* delete one and see that we can now create another */
|
||||
assert(quicklistBookmarkDelete(ql, "0"));
|
||||
assert(quicklistBookmarkCreate(&ql, "_test", ql->head));
|
||||
/* delete one and see that the rest survive */
|
||||
assert(quicklistBookmarkDelete(ql, "_test"));
|
||||
for (i=1; i<QL_MAX_BM; i++)
|
||||
assert(quicklistBookmarkFind(ql, genstr("",i)) == ql->head);
|
||||
/* make sure the deleted ones are indeed gone */
|
||||
assert(!quicklistBookmarkFind(ql, "0"));
|
||||
assert(!quicklistBookmarkFind(ql, "_test"));
|
||||
quicklistRelease(ql);
|
||||
}
|
||||
|
||||
if (!err)
|
||||
printf("ALL TESTS PASSED!\n");
|
||||
else
|
||||
|
||||
+43
-3
@@ -28,6 +28,8 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <stdint.h> // for UINTPTR_MAX
|
||||
|
||||
#ifndef __QUICKLIST_H__
|
||||
#define __QUICKLIST_H__
|
||||
|
||||
@@ -64,19 +66,51 @@ typedef struct quicklistLZF {
|
||||
char compressed[];
|
||||
} quicklistLZF;
|
||||
|
||||
/* Bookmarks are padded with realloc at the end of of the quicklist struct.
|
||||
* They should only be used for very big lists if thousands of nodes were the
|
||||
* excess memory usage is negligible, and there's a real need to iterate on them
|
||||
* in portions.
|
||||
* When not used, they don't add any memory overhead, but when used and then
|
||||
* deleted, some overhead remains (to avoid resonance).
|
||||
* The number of bookmarks used should be kept to minimum since it also adds
|
||||
* overhead on node deletion (searching for a bookmark to update). */
|
||||
typedef struct quicklistBookmark {
|
||||
quicklistNode *node;
|
||||
char *name;
|
||||
} quicklistBookmark;
|
||||
|
||||
#if UINTPTR_MAX == 0xffffffff
|
||||
/* 32-bit */
|
||||
# define QL_FILL_BITS 14
|
||||
# define QL_COMP_BITS 14
|
||||
# define QL_BM_BITS 4
|
||||
#elif UINTPTR_MAX == 0xffffffffffffffff
|
||||
/* 64-bit */
|
||||
# define QL_FILL_BITS 16
|
||||
# define QL_COMP_BITS 16
|
||||
# define QL_BM_BITS 4 /* we can encode more, but we rather limit the user
|
||||
since they cause performance degradation. */
|
||||
#else
|
||||
# error unknown arch bits count
|
||||
#endif
|
||||
|
||||
/* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist.
|
||||
* 'count' is the number of total entries.
|
||||
* 'len' is the number of quicklist nodes.
|
||||
* 'compress' is: -1 if compression disabled, otherwise it's the number
|
||||
* of quicklistNodes to leave uncompressed at ends of quicklist.
|
||||
* 'fill' is the user-requested (or default) fill factor. */
|
||||
* 'fill' is the user-requested (or default) fill factor.
|
||||
* 'bookmakrs are an optional feature that is used by realloc this struct,
|
||||
* so that they don't consume memory when not used. */
|
||||
typedef struct quicklist {
|
||||
quicklistNode *head;
|
||||
quicklistNode *tail;
|
||||
unsigned long count; /* total count of all entries in all ziplists */
|
||||
unsigned long len; /* number of quicklistNodes */
|
||||
int fill : 16; /* fill factor for individual nodes */
|
||||
unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
|
||||
int fill : QL_FILL_BITS; /* fill factor for individual nodes */
|
||||
unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
|
||||
unsigned int bookmark_count: QL_BM_BITS;
|
||||
quicklistBookmark bookmarks[];
|
||||
} quicklist;
|
||||
|
||||
typedef struct quicklistIter {
|
||||
@@ -158,6 +192,12 @@ unsigned long quicklistCount(const quicklist *ql);
|
||||
int quicklistCompare(unsigned char *p1, unsigned char *p2, int p2_len);
|
||||
size_t quicklistGetLzf(const quicklistNode *node, void **data);
|
||||
|
||||
/* bookmarks */
|
||||
int quicklistBookmarkCreate(quicklist **ql_ref, const char *name, quicklistNode *node);
|
||||
int quicklistBookmarkDelete(quicklist *ql, const char *name);
|
||||
quicklistNode *quicklistBookmarkFind(quicklist *ql, const char *name);
|
||||
void quicklistBookmarksClear(quicklist *ql);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
int quicklistTest(int argc, char *argv[]);
|
||||
#endif
|
||||
|
||||
@@ -1673,6 +1673,7 @@ int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) {
|
||||
* node, but will be our match, representing the key "f".
|
||||
*
|
||||
* So in that case, we don't seek backward. */
|
||||
it->data = raxGetData(it->node);
|
||||
} else {
|
||||
if (gt && !raxIteratorNextStep(it,0)) return 0;
|
||||
if (lt && !raxIteratorPrevStep(it,0)) return 0;
|
||||
@@ -1765,6 +1766,7 @@ int raxRandomWalk(raxIterator *it, size_t steps) {
|
||||
if (n->iskey) steps--;
|
||||
}
|
||||
it->node = n;
|
||||
it->data = raxGetData(it->node);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1791,7 +1793,7 @@ int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key
|
||||
if (eq && key_len == iter->key_len) return 1;
|
||||
else if (lt) return iter->key_len < key_len;
|
||||
else if (gt) return iter->key_len > key_len;
|
||||
return 0;
|
||||
else return 0; /* Avoid warning, just 'eq' is handled before. */
|
||||
} else if (cmp > 0) {
|
||||
return gt ? 1 : 0;
|
||||
} else /* (cmp < 0) */ {
|
||||
|
||||
@@ -1002,8 +1002,8 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
* the rdbSaveObject() function. Currently we use a trick to get
|
||||
* this length with very little changes to the code. In the future
|
||||
* we could switch to a faster solution. */
|
||||
size_t rdbSavedObjectLen(robj *o) {
|
||||
ssize_t len = rdbSaveObject(NULL,o,NULL);
|
||||
size_t rdbSavedObjectLen(robj *o, robj *key) {
|
||||
ssize_t len = rdbSaveObject(NULL,o,key);
|
||||
serverAssertWithInfo(NULL,o,len != -1);
|
||||
return len;
|
||||
}
|
||||
@@ -1175,10 +1175,7 @@ int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
|
||||
if (rdbSaveLen(rdb,j) == -1) goto werr;
|
||||
|
||||
/* Write the RESIZE DB opcode. We trim the size to UINT32_MAX, which
|
||||
* is currently the largest type we are able to represent in RDB sizes.
|
||||
* However this does not limit the actual size of the DB to load since
|
||||
* these sizes are just hints to resize the hash tables. */
|
||||
/* Write the RESIZE DB opcode. */
|
||||
uint64_t db_size, expires_size;
|
||||
db_size = dictSize(db->dict);
|
||||
expires_size = dictSize(db->expires);
|
||||
@@ -1425,7 +1422,7 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
|
||||
|
||||
/* Load a Redis object of the specified type from the specified file.
|
||||
* On success a newly allocated object is returned, otherwise NULL. */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
robj *o = NULL, *ele, *dec;
|
||||
uint64_t len;
|
||||
unsigned int i;
|
||||
@@ -1871,7 +1868,10 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_MODULE || rdbtype == RDB_TYPE_MODULE_2) {
|
||||
uint64_t moduleid = rdbLoadLen(rdb,NULL);
|
||||
if (rioGetReadError(rdb)) return NULL;
|
||||
if (rioGetReadError(rdb)) {
|
||||
rdbReportReadError("Short read module id");
|
||||
return NULL;
|
||||
}
|
||||
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
||||
char name[10];
|
||||
|
||||
@@ -1886,7 +1886,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
exit(1);
|
||||
}
|
||||
RedisModuleIO io;
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj,key);
|
||||
moduleInitIOContext(io,mt,rdb,&keyobj);
|
||||
io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
@@ -2006,7 +2008,7 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
||||
/* The DB can take some non trivial amount of time to load. Update
|
||||
* our cached time since it is used to create and update the last
|
||||
* interaction time with clients and for other important things. */
|
||||
updateCachedTime();
|
||||
updateCachedTime(0);
|
||||
if (server.masterhost && server.repl_state == REPL_STATE_TRANSFER)
|
||||
replicationSendNewlineToMaster();
|
||||
loadingProgress(r->processed_bytes);
|
||||
@@ -2044,7 +2046,8 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
long long lru_clock = LRU_CLOCK();
|
||||
|
||||
while(1) {
|
||||
robj *key, *val;
|
||||
sds key;
|
||||
robj *val;
|
||||
|
||||
/* Read type. */
|
||||
if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
|
||||
@@ -2192,7 +2195,7 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
io.ver = 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
if (mt->aux_load(&io,moduleid&1023, when) || io.error) {
|
||||
if (mt->aux_load(&io,moduleid&1023, when) != REDISMODULE_OK || io.error) {
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
|
||||
exit(1);
|
||||
@@ -2216,10 +2219,11 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
}
|
||||
|
||||
/* Read key */
|
||||
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
if ((key = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL)
|
||||
goto eoferr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,rdb,key)) == NULL) {
|
||||
decrRefCount(key);
|
||||
sdsfree(key);
|
||||
goto eoferr;
|
||||
}
|
||||
|
||||
@@ -2227,26 +2231,49 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
* received from the master. In the latter case, the master is
|
||||
* responsible for key expiry. If we would expire keys here, the
|
||||
* snapshot taken by the master may not be reflected on the slave. */
|
||||
if (server.masterhost == NULL && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now) {
|
||||
decrRefCount(key);
|
||||
* snapshot taken by the master may not be reflected on the slave.
|
||||
* Similarly if the RDB is the preamble of an AOF file, we want to
|
||||
* load all the keys as they are, since the log of operations later
|
||||
* assume to work in an exact keyspace state. */
|
||||
if (iAmMaster() &&
|
||||
!(rdbflags&RDBFLAGS_AOF_PREAMBLE) &&
|
||||
expiretime != -1 && expiretime < now)
|
||||
{
|
||||
sdsfree(key);
|
||||
decrRefCount(val);
|
||||
} else {
|
||||
robj keyobj;
|
||||
|
||||
/* Add the new object in the hash table */
|
||||
dbAdd(db,key,val);
|
||||
int added = dbAddRDBLoad(db,key,val);
|
||||
if (!added) {
|
||||
if (rdbflags & RDBFLAGS_ALLOW_DUP) {
|
||||
/* This flag is useful for DEBUG RELOAD special modes.
|
||||
* When it's set we allow new keys to replace the current
|
||||
* keys with the same name. */
|
||||
initStaticStringObject(keyobj,key);
|
||||
dbSyncDelete(db,&keyobj);
|
||||
dbAddRDBLoad(db,key,val);
|
||||
} else {
|
||||
serverLog(LL_WARNING,
|
||||
"RDB has duplicated key '%s' in DB %d",key,db->id);
|
||||
serverPanic("Duplicated key found in RDB file");
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the expire time if needed */
|
||||
if (expiretime != -1) setExpire(NULL,db,key,expiretime);
|
||||
if (expiretime != -1) {
|
||||
initStaticStringObject(keyobj,key);
|
||||
setExpire(NULL,db,&keyobj,expiretime);
|
||||
}
|
||||
|
||||
/* Set usage information (for eviction). */
|
||||
objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock);
|
||||
|
||||
/* Decrement the key refcount since dbAdd() will take its
|
||||
* own reference. */
|
||||
decrRefCount(key);
|
||||
objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock,1000);
|
||||
}
|
||||
if (server.key_load_delay)
|
||||
usleep(server.key_load_delay);
|
||||
|
||||
/* Loading the database more slowly is useful in order to test
|
||||
* certain edge cases. */
|
||||
if (server.key_load_delay) usleep(server.key_load_delay);
|
||||
|
||||
/* Reset the state that is key-specified and is populated by
|
||||
* opcodes before the key, so that we start from scratch again. */
|
||||
|
||||
@@ -122,9 +122,10 @@
|
||||
#define RDB_LOAD_SDS (1<<2)
|
||||
|
||||
/* flags on the purpose of rdb save or load */
|
||||
#define RDBFLAGS_NONE 0
|
||||
#define RDBFLAGS_AOF_PREAMBLE (1<<0)
|
||||
#define RDBFLAGS_REPLICATION (1<<1)
|
||||
#define RDBFLAGS_NONE 0 /* No special RDB loading. */
|
||||
#define RDBFLAGS_AOF_PREAMBLE (1<<0) /* Load/save the RDB as AOF preamble. */
|
||||
#define RDBFLAGS_REPLICATION (1<<1) /* Load/save for SYNC. */
|
||||
#define RDBFLAGS_ALLOW_DUP (1<<2) /* Allow duplicated keys when loading.*/
|
||||
|
||||
int rdbSaveType(rio *rdb, unsigned char type);
|
||||
int rdbLoadType(rio *rdb);
|
||||
@@ -143,8 +144,8 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
int rdbSave(char *filename, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o, robj *key);
|
||||
robj *rdbLoadObject(int type, rio *rdb, sds key);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
||||
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
|
||||
|
||||
@@ -287,7 +287,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.keys++;
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb,key)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,&rdb,key->ptr)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
|
||||
+62
-15
@@ -49,6 +49,7 @@
|
||||
#include <hiredis.h>
|
||||
#ifdef USE_OPENSSL
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <hiredis_ssl.h>
|
||||
#endif
|
||||
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
|
||||
@@ -228,6 +229,7 @@ static struct config {
|
||||
int hotkeys;
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
char *auth;
|
||||
int askpass;
|
||||
char *user;
|
||||
int output; /* output mode, see OUTPUT_* defines */
|
||||
sds mb_delim;
|
||||
@@ -1290,7 +1292,11 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
|
||||
(argc == 3 && !strcasecmp(command,"latency") &&
|
||||
!strcasecmp(argv[1],"graph")) ||
|
||||
(argc == 2 && !strcasecmp(command,"latency") &&
|
||||
!strcasecmp(argv[1],"doctor")))
|
||||
!strcasecmp(argv[1],"doctor")) ||
|
||||
/* Format PROXY INFO command for Redis Cluster Proxy:
|
||||
* https://github.com/artix75/redis-cluster-proxy */
|
||||
(argc >= 2 && !strcasecmp(command,"proxy") &&
|
||||
!strcasecmp(argv[1],"info")))
|
||||
{
|
||||
output_raw = 1;
|
||||
}
|
||||
@@ -1449,6 +1455,8 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.dbnum = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i], "--no-auth-warning")) {
|
||||
config.no_auth_warning = 1;
|
||||
} else if (!strcmp(argv[i], "--askpass")) {
|
||||
config.askpass = 1;
|
||||
} else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass"))
|
||||
&& !lastarg)
|
||||
{
|
||||
@@ -1594,15 +1602,15 @@ static int parseOptions(int argc, char **argv) {
|
||||
#ifdef USE_OPENSSL
|
||||
} else if (!strcmp(argv[i],"--tls")) {
|
||||
config.tls = 1;
|
||||
} else if (!strcmp(argv[i],"--sni")) {
|
||||
} else if (!strcmp(argv[i],"--sni") && !lastarg) {
|
||||
config.sni = argv[++i];
|
||||
} else if (!strcmp(argv[i],"--cacertdir")) {
|
||||
} else if (!strcmp(argv[i],"--cacertdir") && !lastarg) {
|
||||
config.cacertdir = argv[++i];
|
||||
} else if (!strcmp(argv[i],"--cacert")) {
|
||||
} else if (!strcmp(argv[i],"--cacert") && !lastarg) {
|
||||
config.cacert = argv[++i];
|
||||
} else if (!strcmp(argv[i],"--cert")) {
|
||||
} else if (!strcmp(argv[i],"--cert") && !lastarg) {
|
||||
config.cert = argv[++i];
|
||||
} else if (!strcmp(argv[i],"--key")) {
|
||||
} else if (!strcmp(argv[i],"--key") && !lastarg) {
|
||||
config.key = argv[++i];
|
||||
#endif
|
||||
} else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
|
||||
@@ -1687,8 +1695,11 @@ static void usage(void) {
|
||||
" You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
|
||||
" variable to pass this password more safely\n"
|
||||
" (if both are used, this argument takes predecence).\n"
|
||||
" -user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
|
||||
" -pass <password> Alias of -a for consistency with the new --user option.\n"
|
||||
" --user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
|
||||
" --pass <password> Alias of -a for consistency with the new --user option.\n"
|
||||
" --askpass Force user to input password with mask from STDIN.\n"
|
||||
" If this argument is used, '-a' and " REDIS_CLI_AUTH_ENV "\n"
|
||||
" environment variable will be ignored.\n"
|
||||
" -u <uri> Server URI.\n"
|
||||
" -r <repeat> Execute specified command N times.\n"
|
||||
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
|
||||
@@ -1700,12 +1711,13 @@ static void usage(void) {
|
||||
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
|
||||
#ifdef USE_OPENSSL
|
||||
" --tls Establish a secure TLS connection.\n"
|
||||
" --cacert CA Certificate file to verify with.\n"
|
||||
" --cacertdir Directory where trusted CA certificates are stored.\n"
|
||||
" --sni <host> Server name indication for TLS.\n"
|
||||
" --cacert <file> CA Certificate file to verify with.\n"
|
||||
" --cacertdir <dir> Directory where trusted CA certificates are stored.\n"
|
||||
" If neither cacert nor cacertdir are specified, the default\n"
|
||||
" system-wide trusted root certs configuration will apply.\n"
|
||||
" --cert Client certificate to authenticate with.\n"
|
||||
" --key Private key file to authenticate with.\n"
|
||||
" --cert <file> Client certificate to authenticate with.\n"
|
||||
" --key <file> Private key file to authenticate with.\n"
|
||||
#endif
|
||||
" --raw Use raw formatting for replies (default when STDOUT is\n"
|
||||
" not a tty).\n"
|
||||
@@ -1945,8 +1957,20 @@ static void repl(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Won't save auth command in history file */
|
||||
if (!(argv && argc > 0 && !strcasecmp(argv[0+skipargs], "auth"))) {
|
||||
/* Won't save auth or acl setuser commands in history file */
|
||||
int dangerous = 0;
|
||||
if (argv && argc > 0) {
|
||||
if (!strcasecmp(argv[skipargs], "auth")) {
|
||||
dangerous = 1;
|
||||
} else if (skipargs+1 < argc &&
|
||||
!strcasecmp(argv[skipargs], "acl") &&
|
||||
!strcasecmp(argv[skipargs+1], "setuser"))
|
||||
{
|
||||
dangerous = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dangerous) {
|
||||
if (history) linenoiseHistoryAdd(line);
|
||||
if (historyfile) linenoiseHistorySave(historyfile);
|
||||
}
|
||||
@@ -1969,6 +1993,8 @@ static void repl(void) {
|
||||
if (config.eval) {
|
||||
config.eval_ldb = 1;
|
||||
config.output = OUTPUT_RAW;
|
||||
sdsfreesplitres(argv,argc);
|
||||
linenoiseFree(line);
|
||||
return; /* Return to evalMode to restart the session. */
|
||||
} else {
|
||||
printf("Use 'restart' only in Lua debugging mode.");
|
||||
@@ -7723,7 +7749,7 @@ static void LRUTestMode(void) {
|
||||
* to fill the target instance easily. */
|
||||
start_cycle = mstime();
|
||||
long long hits = 0, misses = 0;
|
||||
while(mstime() - start_cycle < 1000) {
|
||||
while(mstime() - start_cycle < LRU_CYCLE_PERIOD) {
|
||||
/* Write cycle. */
|
||||
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
|
||||
char val[6];
|
||||
@@ -7844,6 +7870,13 @@ static void intrinsicLatencyMode(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static sds askPassword() {
|
||||
linenoiseMaskModeEnable();
|
||||
sds auth = linenoise("Please input password: ");
|
||||
linenoiseMaskModeDisable();
|
||||
return auth;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* Program main()
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -7880,6 +7913,7 @@ int main(int argc, char **argv) {
|
||||
config.hotkeys = 0;
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
config.askpass = 0;
|
||||
config.user = NULL;
|
||||
config.eval = NULL;
|
||||
config.eval_ldb = 0;
|
||||
@@ -7921,6 +7955,18 @@ int main(int argc, char **argv) {
|
||||
|
||||
parseEnv();
|
||||
|
||||
if (config.askpass) {
|
||||
config.auth = askPassword();
|
||||
}
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
if (config.tls) {
|
||||
ERR_load_crypto_strings();
|
||||
SSL_load_error_strings();
|
||||
SSL_library_init();
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Cluster Manager mode */
|
||||
if (CLUSTER_MANAGER_MODE()) {
|
||||
clusterManagerCommandProc *proc = validateClusterManagerCommand();
|
||||
@@ -8023,3 +8069,4 @@ int main(int argc, char **argv) {
|
||||
return noninteractive(argc,convertToSds(argc,argv));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+78
-19
@@ -33,6 +33,7 @@
|
||||
#define REDISMODULE_KEYTYPE_SET 4
|
||||
#define REDISMODULE_KEYTYPE_ZSET 5
|
||||
#define REDISMODULE_KEYTYPE_MODULE 6
|
||||
#define REDISMODULE_KEYTYPE_STREAM 7
|
||||
|
||||
/* Reply types. */
|
||||
#define REDISMODULE_REPLY_UNKNOWN -1
|
||||
@@ -109,6 +110,8 @@
|
||||
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE (1<<17)
|
||||
/* There is currently some background process active. */
|
||||
#define REDISMODULE_CTX_FLAGS_ACTIVE_CHILD (1<<18)
|
||||
/* The next EXEC will fail due to dirty CAS (touched keys). */
|
||||
#define REDISMODULE_CTX_FLAGS_MULTI_DIRTY (1<<19)
|
||||
|
||||
/* Keyspace changes notification classes. Every class is associated with a
|
||||
* character for configuration purposes.
|
||||
@@ -124,8 +127,8 @@
|
||||
#define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */
|
||||
#define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */
|
||||
#define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */
|
||||
#define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m */
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_KEY_MISS) /* A */
|
||||
#define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from REDISMODULE_NOTIFY_ALL on purpose) */
|
||||
#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */
|
||||
|
||||
|
||||
/* A special pointer that we can use between the core and the module to signal
|
||||
@@ -392,8 +395,9 @@ typedef struct RedisModuleDictIter RedisModuleDictIter;
|
||||
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
|
||||
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
||||
typedef struct RedisModuleInfoCtx RedisModuleInfoCtx;
|
||||
typedef struct RedisModuleServerInfoData RedisModuleServerInfoData;
|
||||
typedef struct RedisModuleScanCursor RedisModuleScanCursor;
|
||||
typedef struct RedisModuleUser RedisModuleUser;
|
||||
typedef struct RedisModuleAuthCtx RedisModuleAuthCtx;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
||||
@@ -411,7 +415,9 @@ typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
||||
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
||||
typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data);
|
||||
typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
|
||||
typedef void (*RedisModuleUserChangedFunc)(RedisModuleCtx *ctx, void *privdata);
|
||||
typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keyname, RedisModuleKey *key, void *privdata);
|
||||
typedef void (*RedisModuleScanKeyCB)(RedisModuleKey *key, RedisModuleString *field, RedisModuleString *value, void *privdata);
|
||||
typedef void (*RedisModuleUserChangedFunc) (uint64_t client_id, void *privdata);
|
||||
|
||||
#define REDISMODULE_TYPE_METHOD_VERSION 2
|
||||
typedef struct RedisModuleTypeMethods {
|
||||
@@ -461,8 +467,14 @@ size_t REDISMODULE_API_FUNC(RedisModule_CallReplyLength)(RedisModuleCallReply *r
|
||||
RedisModuleCallReply *REDISMODULE_API_FUNC(RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromDouble)(RedisModuleCtx *ctx, double d);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str);
|
||||
#ifdef __GNUC__
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
#else
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...);
|
||||
#endif
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err);
|
||||
@@ -478,9 +490,11 @@ int REDISMODULE_API_FUNC(RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringToDouble)(const RedisModuleString *str, double *d);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringToLongDouble)(const RedisModuleString *str, long double *d);
|
||||
void REDISMODULE_API_FUNC(RedisModule_AutoMemory)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx);
|
||||
@@ -517,9 +531,11 @@ unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetClientInfoById)(void *ci, uint64_t id);
|
||||
int REDISMODULE_API_FUNC(RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AvoidReplicaTraffic)();
|
||||
void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes);
|
||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeReplaceValue)(RedisModuleKey *key, RedisModuleType *mt, void *new_value, void **old_value);
|
||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key);
|
||||
int REDISMODULE_API_FUNC(RedisModule_IsIOError)(RedisModuleIO *io);
|
||||
@@ -540,8 +556,15 @@ void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value)
|
||||
float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SaveLongDouble)(RedisModuleIO *io, long double value);
|
||||
long double REDISMODULE_API_FUNC(RedisModule_LoadLongDouble)(RedisModuleIO *io);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_LoadDataTypeFromString)(const RedisModuleString *str, const RedisModuleType *mt);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_SaveDataTypeToString)(RedisModuleCtx *ctx, void *data, const RedisModuleType *mt);
|
||||
#ifdef __GNUC__
|
||||
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
|
||||
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
|
||||
#else
|
||||
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
|
||||
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
|
||||
#endif
|
||||
void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line);
|
||||
void REDISMODULE_API_FUNC(RedisModule_LatencyAddSample)(const char *event, mstime_t latency);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len);
|
||||
@@ -585,13 +608,26 @@ int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ct
|
||||
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value);
|
||||
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value);
|
||||
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value);
|
||||
RedisModuleServerInfoData *REDISMODULE_API_FUNC(RedisModule_GetServerInfo)(RedisModuleCtx *ctx, const char *section);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeServerInfo)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_ServerInfoGetField)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldC)(RedisModuleServerInfoData *data, const char* field);
|
||||
long long REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldSigned)(RedisModuleServerInfoData *data, const char* field, int *out_err);
|
||||
unsigned long long REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldUnsigned)(RedisModuleServerInfoData *data, const char* field, int *out_err);
|
||||
double REDISMODULE_API_FUNC(RedisModule_ServerInfoGetFieldDouble)(RedisModuleServerInfoData *data, const char* field, int *out_err);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SubscribeToServerEvent)(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetLRUOrLFU)(RedisModuleKey *key, long long lfu_freq, long long lru_idle);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetLRUOrLFU)(RedisModuleKey *key, long long *lfu_freq, long long *lru_idle);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetLRU)(RedisModuleKey *key, mstime_t lru_idle);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetLRU)(RedisModuleKey *key, mstime_t *lru_idle);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetLFU)(RedisModuleKey *key, long long lfu_freq);
|
||||
int REDISMODULE_API_FUNC(RedisModule_GetLFU)(RedisModuleKey *key, long long *lfu_freq);
|
||||
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClientOnKeys)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata);
|
||||
void REDISMODULE_API_FUNC(RedisModule_SignalKeyAsReady)(RedisModuleCtx *ctx, RedisModuleString *key);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientReadyKey)(RedisModuleCtx *ctx);
|
||||
|
||||
RedisModuleScanCursor *REDISMODULE_API_FUNC(RedisModule_ScanCursorCreate)();
|
||||
void REDISMODULE_API_FUNC(RedisModule_ScanCursorRestart)(RedisModuleScanCursor *cursor);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ScanCursorDestroy)(RedisModuleScanCursor *cursor);
|
||||
int REDISMODULE_API_FUNC(RedisModule_Scan)(RedisModuleCtx *ctx, RedisModuleScanCursor *cursor, RedisModuleScanCB fn, void *privdata);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ScanKey)(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleScanKeyCB fn, void *privdata);
|
||||
/* Experimental APIs */
|
||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||
#define REDISMODULE_EXPERIMENTAL_API_VERSION 3
|
||||
@@ -636,16 +672,17 @@ int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandF
|
||||
int REDISMODULE_API_FUNC(RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ExitFromChild)(int retcode);
|
||||
int REDISMODULE_API_FUNC(RedisModule_KillForkChild)(int child_pid);
|
||||
float REDISMODULE_API_FUNC(RedisModule_GetUsedMemoryRatio)();
|
||||
size_t REDISMODULE_API_FUNC(RedisModule_MallocSize)(void* ptr);
|
||||
RedisModuleUser *REDISMODULE_API_FUNC(RedisModule_CreateModuleUser)(const char *name);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeModuleUser)(RedisModuleUser *user);
|
||||
int REDISMODULE_API_FUNC(RedisModule_SetModuleUserACL)(RedisModuleUser *user, const char* acl);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AuthClientWithACLUser)(RedisModuleCtx *ctx, const char* name, size_t len, RedisModuleAuthCtx *auth_ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AuthClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *module_user, RedisModuleAuthCtx *auth_ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_RevokeAuthentication)(RedisModuleAuthCtx *ctx);
|
||||
RedisModuleAuthCtx *REDISMODULE_API_FUNC(RedisModule_CreateAuthCtx)(RedisModuleUserChangedFunc callback, void *privdata);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id);
|
||||
void REDISMODULE_API_FUNC(RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id);
|
||||
#endif
|
||||
|
||||
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
|
||||
#define RedisModule_IsAOFClient(id) ((id) == CLIENT_ID_AOF)
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused));
|
||||
@@ -676,6 +713,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(ReplyWithNull);
|
||||
REDISMODULE_GET_API(ReplyWithCallReply);
|
||||
REDISMODULE_GET_API(ReplyWithDouble);
|
||||
REDISMODULE_GET_API(ReplyWithLongDouble);
|
||||
REDISMODULE_GET_API(GetSelectedDb);
|
||||
REDISMODULE_GET_API(SelectDb);
|
||||
REDISMODULE_GET_API(OpenKey);
|
||||
@@ -686,6 +724,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(ListPop);
|
||||
REDISMODULE_GET_API(StringToLongLong);
|
||||
REDISMODULE_GET_API(StringToDouble);
|
||||
REDISMODULE_GET_API(StringToLongDouble);
|
||||
REDISMODULE_GET_API(Call);
|
||||
REDISMODULE_GET_API(CallReplyProto);
|
||||
REDISMODULE_GET_API(FreeCallReply);
|
||||
@@ -697,6 +736,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(CreateStringFromCallReply);
|
||||
REDISMODULE_GET_API(CreateString);
|
||||
REDISMODULE_GET_API(CreateStringFromLongLong);
|
||||
REDISMODULE_GET_API(CreateStringFromDouble);
|
||||
REDISMODULE_GET_API(CreateStringFromLongDouble);
|
||||
REDISMODULE_GET_API(CreateStringFromString);
|
||||
REDISMODULE_GET_API(CreateStringPrintf);
|
||||
REDISMODULE_GET_API(FreeString);
|
||||
@@ -733,9 +774,11 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(KeyAtPos);
|
||||
REDISMODULE_GET_API(GetClientId);
|
||||
REDISMODULE_GET_API(GetContextFlags);
|
||||
REDISMODULE_GET_API(AvoidReplicaTraffic);
|
||||
REDISMODULE_GET_API(PoolAlloc);
|
||||
REDISMODULE_GET_API(CreateDataType);
|
||||
REDISMODULE_GET_API(ModuleTypeSetValue);
|
||||
REDISMODULE_GET_API(ModuleTypeReplaceValue);
|
||||
REDISMODULE_GET_API(ModuleTypeGetType);
|
||||
REDISMODULE_GET_API(ModuleTypeGetValue);
|
||||
REDISMODULE_GET_API(IsIOError);
|
||||
@@ -755,6 +798,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(LoadFloat);
|
||||
REDISMODULE_GET_API(SaveLongDouble);
|
||||
REDISMODULE_GET_API(LoadLongDouble);
|
||||
REDISMODULE_GET_API(SaveDataTypeToString);
|
||||
REDISMODULE_GET_API(LoadDataTypeFromString);
|
||||
REDISMODULE_GET_API(EmitAOF);
|
||||
REDISMODULE_GET_API(Log);
|
||||
REDISMODULE_GET_API(LogIOError);
|
||||
@@ -801,14 +846,28 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(InfoAddFieldDouble);
|
||||
REDISMODULE_GET_API(InfoAddFieldLongLong);
|
||||
REDISMODULE_GET_API(InfoAddFieldULongLong);
|
||||
REDISMODULE_GET_API(GetServerInfo);
|
||||
REDISMODULE_GET_API(FreeServerInfo);
|
||||
REDISMODULE_GET_API(ServerInfoGetField);
|
||||
REDISMODULE_GET_API(ServerInfoGetFieldC);
|
||||
REDISMODULE_GET_API(ServerInfoGetFieldSigned);
|
||||
REDISMODULE_GET_API(ServerInfoGetFieldUnsigned);
|
||||
REDISMODULE_GET_API(ServerInfoGetFieldDouble);
|
||||
REDISMODULE_GET_API(GetClientInfoById);
|
||||
REDISMODULE_GET_API(PublishMessage);
|
||||
REDISMODULE_GET_API(SubscribeToServerEvent);
|
||||
REDISMODULE_GET_API(SetLRUOrLFU);
|
||||
REDISMODULE_GET_API(GetLRUOrLFU);
|
||||
REDISMODULE_GET_API(SetLRU);
|
||||
REDISMODULE_GET_API(GetLRU);
|
||||
REDISMODULE_GET_API(SetLFU);
|
||||
REDISMODULE_GET_API(GetLFU);
|
||||
REDISMODULE_GET_API(BlockClientOnKeys);
|
||||
REDISMODULE_GET_API(SignalKeyAsReady);
|
||||
REDISMODULE_GET_API(GetBlockedClientReadyKey);
|
||||
REDISMODULE_GET_API(ScanCursorCreate);
|
||||
REDISMODULE_GET_API(ScanCursorRestart);
|
||||
REDISMODULE_GET_API(ScanCursorDestroy);
|
||||
REDISMODULE_GET_API(Scan);
|
||||
REDISMODULE_GET_API(ScanKey);
|
||||
|
||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||
REDISMODULE_GET_API(GetThreadSafeContext);
|
||||
@@ -852,14 +911,14 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(Fork);
|
||||
REDISMODULE_GET_API(ExitFromChild);
|
||||
REDISMODULE_GET_API(KillForkChild);
|
||||
|
||||
REDISMODULE_GET_API(GetUsedMemoryRatio);
|
||||
REDISMODULE_GET_API(MallocSize);
|
||||
REDISMODULE_GET_API(CreateModuleUser);
|
||||
REDISMODULE_GET_API(FreeModuleUser);
|
||||
REDISMODULE_GET_API(SetModuleUserACL);
|
||||
REDISMODULE_GET_API(RevokeAuthentication);
|
||||
REDISMODULE_GET_API(CreateAuthCtx);
|
||||
REDISMODULE_GET_API(AuthClientWithACLUser);
|
||||
REDISMODULE_GET_API(AuthClientWithUser);
|
||||
REDISMODULE_GET_API(DeauthenticateAndCloseClient);
|
||||
REDISMODULE_GET_API(AuthenticateClientWithACLUser);
|
||||
REDISMODULE_GET_API(AuthenticateClientWithUser);
|
||||
#endif
|
||||
|
||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||
|
||||
+189
-16
@@ -31,6 +31,7 @@
|
||||
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
#include "bio.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
@@ -44,6 +45,11 @@ void replicationSendAck(void);
|
||||
void putSlaveOnline(client *slave);
|
||||
int cancelReplicationHandshake(void);
|
||||
|
||||
/* We take a global flag to remember if this instance generated an RDB
|
||||
* because of replication, so that we can remove the RDB file in case
|
||||
* the instance is configured to have no persistence. */
|
||||
int RDBGeneratedByReplication = 0;
|
||||
|
||||
/* --------------------------- Utility functions ---------------------------- */
|
||||
|
||||
/* Return the pointer to a string representing the slave ip:listening_port
|
||||
@@ -73,6 +79,34 @@ char *replicationGetSlaveName(client *c) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Plain unlink() can block for quite some time in order to actually apply
|
||||
* the file deletion to the filesystem. This call removes the file in a
|
||||
* background thread instead. We actually just do close() in the thread,
|
||||
* by using the fact that if there is another instance of the same file open,
|
||||
* the foreground unlink() will not really do anything, and deleting the
|
||||
* file will only happen once the last reference is lost. */
|
||||
int bg_unlink(const char *filename) {
|
||||
int fd = open(filename,O_RDONLY|O_NONBLOCK);
|
||||
if (fd == -1) {
|
||||
/* Can't open the file? Fall back to unlinking in the main thread. */
|
||||
return unlink(filename);
|
||||
} else {
|
||||
/* The following unlink() will not do anything since file
|
||||
* is still open. */
|
||||
int retval = unlink(filename);
|
||||
if (retval == -1) {
|
||||
/* If we got an unlink error, we just return it, closing the
|
||||
* new reference we have to the file. */
|
||||
int old_errno = errno;
|
||||
close(fd); /* This would overwrite our errno. So we saved it. */
|
||||
errno = old_errno;
|
||||
return -1;
|
||||
}
|
||||
bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)fd,NULL,NULL);
|
||||
return 0; /* Success. */
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------- MASTER -------------------------------- */
|
||||
|
||||
void createReplicationBacklog(void) {
|
||||
@@ -128,6 +162,7 @@ void feedReplicationBacklog(void *ptr, size_t len) {
|
||||
unsigned char *p = ptr;
|
||||
|
||||
server.master_repl_offset += len;
|
||||
server.master_repl_meaningful_offset = server.master_repl_offset;
|
||||
|
||||
/* This is a circular buffer, so write as much data we can at every
|
||||
* iteration and rewind the "idx" index if we reach the limit. */
|
||||
@@ -590,6 +625,14 @@ int startBgsaveForReplication(int mincapa) {
|
||||
retval = C_ERR;
|
||||
}
|
||||
|
||||
/* If we succeeded to start a BGSAVE with disk target, let's remember
|
||||
* this fact, so that we can later delete the file if needed. Note
|
||||
* that we don't set the flag to 1 if the feature is disabled, otherwise
|
||||
* it would never be cleared: the file is not deleted. This way if
|
||||
* the user enables it later with CONFIG SET, we are fine. */
|
||||
if (retval == C_OK && !socket_target && server.rdb_del_sync_files)
|
||||
RDBGeneratedByReplication = 1;
|
||||
|
||||
/* If we failed to BGSAVE, remove the slaves waiting for a full
|
||||
* resynchronization from the list of slaves, inform them with
|
||||
* an error about what happened, close the connection ASAP. */
|
||||
@@ -882,6 +925,53 @@ void putSlaveOnline(client *slave) {
|
||||
replicationGetSlaveName(slave));
|
||||
}
|
||||
|
||||
/* We call this function periodically to remove an RDB file that was
|
||||
* generated because of replication, in an instance that is otherwise
|
||||
* without any persistence. We don't want instances without persistence
|
||||
* to take RDB files around, this violates certain policies in certain
|
||||
* environments. */
|
||||
void removeRDBUsedToSyncReplicas(void) {
|
||||
/* If the feature is disabled, return ASAP but also clear the
|
||||
* RDBGeneratedByReplication flag in case it was set. Otherwise if the
|
||||
* feature was enabled, but gets disabled later with CONFIG SET, the
|
||||
* flag may remain set to one: then next time the feature is re-enabled
|
||||
* via CONFIG SET we have have it set even if no RDB was generated
|
||||
* because of replication recently. */
|
||||
if (!server.rdb_del_sync_files) {
|
||||
RDBGeneratedByReplication = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (allPersistenceDisabled() && RDBGeneratedByReplication) {
|
||||
client *slave;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
int delrdb = 1;
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
slave = ln->value;
|
||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START ||
|
||||
slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END ||
|
||||
slave->replstate == SLAVE_STATE_SEND_BULK)
|
||||
{
|
||||
delrdb = 0;
|
||||
break; /* No need to check the other replicas. */
|
||||
}
|
||||
}
|
||||
if (delrdb) {
|
||||
struct stat sb;
|
||||
if (lstat(server.rdb_filename,&sb) != -1) {
|
||||
RDBGeneratedByReplication = 0;
|
||||
serverLog(LL_NOTICE,
|
||||
"Removing the RDB file used to feed replicas "
|
||||
"in a persistence-less instance");
|
||||
bg_unlink(server.rdb_filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendBulkToSlave(connection *conn) {
|
||||
client *slave = connGetPrivateData(conn);
|
||||
char buf[PROTO_IOBUF_LEN];
|
||||
@@ -893,7 +983,8 @@ void sendBulkToSlave(connection *conn) {
|
||||
if (slave->replpreamble) {
|
||||
nwritten = connWrite(conn,slave->replpreamble,sdslen(slave->replpreamble));
|
||||
if (nwritten == -1) {
|
||||
serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s",
|
||||
serverLog(LL_VERBOSE,
|
||||
"Write error sending RDB preamble to replica: %s",
|
||||
connGetLastError(conn));
|
||||
freeClient(slave);
|
||||
return;
|
||||
@@ -1338,8 +1429,8 @@ void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags
|
||||
server.db[i] = backup[i];
|
||||
}
|
||||
} else {
|
||||
/* Delete. */
|
||||
emptyDbGeneric(backup,-1,empty_db_flags,replicationEmptyDbCallback);
|
||||
/* Delete (Pass EMPTYDB_BACKUP in order to avoid firing module events) . */
|
||||
emptyDbGeneric(backup,-1,empty_db_flags|EMPTYDB_BACKUP,replicationEmptyDbCallback);
|
||||
for (int i=0; i<server.dbnum; i++) {
|
||||
dictRelease(backup[i].dict);
|
||||
dictRelease(backup[i].expires);
|
||||
@@ -1351,9 +1442,9 @@ void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags
|
||||
/* Asynchronously read the SYNC payload we receive from a master */
|
||||
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
|
||||
void readSyncBulkPayload(connection *conn) {
|
||||
char buf[4096];
|
||||
char buf[PROTO_IOBUF_LEN];
|
||||
ssize_t nread, readlen, nwritten;
|
||||
int use_diskless_load;
|
||||
int use_diskless_load = useDisklessLoad();
|
||||
redisDb *diskless_load_backup = NULL;
|
||||
int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
|
||||
EMPTYDB_NO_FLAGS;
|
||||
@@ -1410,19 +1501,18 @@ void readSyncBulkPayload(connection *conn) {
|
||||
server.repl_transfer_size = 0;
|
||||
serverLog(LL_NOTICE,
|
||||
"MASTER <-> REPLICA sync: receiving streamed RDB from master with EOF %s",
|
||||
useDisklessLoad()? "to parser":"to disk");
|
||||
use_diskless_load? "to parser":"to disk");
|
||||
} else {
|
||||
usemark = 0;
|
||||
server.repl_transfer_size = strtol(buf+1,NULL,10);
|
||||
serverLog(LL_NOTICE,
|
||||
"MASTER <-> REPLICA sync: receiving %lld bytes from master %s",
|
||||
(long long) server.repl_transfer_size,
|
||||
useDisklessLoad()? "to parser":"to disk");
|
||||
use_diskless_load? "to parser":"to disk");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
use_diskless_load = useDisklessLoad();
|
||||
if (!use_diskless_load) {
|
||||
/* Read the data from the socket, store it to a file and search
|
||||
* for the EOF. */
|
||||
@@ -1524,7 +1614,6 @@ void readSyncBulkPayload(connection *conn) {
|
||||
/* We need to stop any AOF rewriting child before flusing and parsing
|
||||
* the RDB, otherwise we'll create a copy-on-write disaster. */
|
||||
if (server.aof_state != AOF_OFF) stopAppendOnly();
|
||||
signalFlushedDb(-1);
|
||||
|
||||
/* When diskless RDB loading is used by replicas, it may be configured
|
||||
* in order to save the current DB instead of throwing it away,
|
||||
@@ -1532,10 +1621,15 @@ void readSyncBulkPayload(connection *conn) {
|
||||
if (use_diskless_load &&
|
||||
server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB)
|
||||
{
|
||||
/* Create a backup of server.db[] and initialize to empty
|
||||
* dictionaries */
|
||||
diskless_load_backup = disklessLoadMakeBackups();
|
||||
} else {
|
||||
emptyDb(-1,empty_db_flags,replicationEmptyDbCallback);
|
||||
}
|
||||
/* We call to emptyDb even in case of REPL_DISKLESS_LOAD_SWAPDB
|
||||
* (Where disklessLoadMakeBackups left server.db empty) because we
|
||||
* want to execute all the auxiliary logic of emptyDb (Namely,
|
||||
* fire module events) */
|
||||
emptyDb(-1,empty_db_flags,replicationEmptyDbCallback);
|
||||
|
||||
/* Before loading the DB into memory we need to delete the readable
|
||||
* handler, otherwise it will get called recursively since
|
||||
@@ -1616,25 +1710,44 @@ void readSyncBulkPayload(connection *conn) {
|
||||
killRDBChild();
|
||||
}
|
||||
|
||||
/* Rename rdb like renaming rewrite aof asynchronously. */
|
||||
int old_rdb_fd = open(server.rdb_filename,O_RDONLY|O_NONBLOCK);
|
||||
if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Failed trying to rename the temp DB into %s in "
|
||||
"MASTER <-> REPLICA synchronization: %s",
|
||||
server.rdb_filename, strerror(errno));
|
||||
cancelReplicationHandshake();
|
||||
if (old_rdb_fd != -1) close(old_rdb_fd);
|
||||
return;
|
||||
}
|
||||
/* Close old rdb asynchronously. */
|
||||
if (old_rdb_fd != -1) bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)old_rdb_fd,NULL,NULL);
|
||||
|
||||
if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION) != C_OK) {
|
||||
serverLog(LL_WARNING,
|
||||
"Failed trying to load the MASTER synchronization "
|
||||
"DB from disk");
|
||||
cancelReplicationHandshake();
|
||||
if (server.rdb_del_sync_files && allPersistenceDisabled()) {
|
||||
serverLog(LL_NOTICE,"Removing the RDB file obtained from "
|
||||
"the master. This replica has persistence "
|
||||
"disabled");
|
||||
bg_unlink(server.rdb_filename);
|
||||
}
|
||||
/* Note that there's no point in restarting the AOF on sync failure,
|
||||
it'll be restarted when sync succeeds or replica promoted. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Cleanup. */
|
||||
if (server.rdb_del_sync_files && allPersistenceDisabled()) {
|
||||
serverLog(LL_NOTICE,"Removing the RDB file obtained from "
|
||||
"the master. This replica has persistence "
|
||||
"disabled");
|
||||
bg_unlink(server.rdb_filename);
|
||||
}
|
||||
|
||||
zfree(server.repl_transfer_tmpfile);
|
||||
close(server.repl_transfer_fd);
|
||||
server.repl_transfer_fd = -1;
|
||||
@@ -1656,6 +1769,7 @@ void readSyncBulkPayload(connection *conn) {
|
||||
* we are starting a new history. */
|
||||
memcpy(server.replid,server.master->replid,sizeof(server.replid));
|
||||
server.master_repl_offset = server.master->reploff;
|
||||
server.master_repl_meaningful_offset = server.master->reploff;
|
||||
clearReplicationId2();
|
||||
|
||||
/* Let's create the replication backlog if needed. Slaves need to
|
||||
@@ -1665,6 +1779,11 @@ void readSyncBulkPayload(connection *conn) {
|
||||
if (server.repl_backlog == NULL) createReplicationBacklog();
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Finished with success");
|
||||
|
||||
if (server.supervised_mode == SUPERVISED_SYSTEMD) {
|
||||
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Finished with success. Ready to accept connections.\n");
|
||||
redisCommunicateSystemd("READY=1\n");
|
||||
}
|
||||
|
||||
/* Restart the AOF subsystem now that we finished the sync. This
|
||||
* will trigger an AOF rewrite, and when done will start appending
|
||||
* to the new file. */
|
||||
@@ -2167,6 +2286,10 @@ void syncWithMaster(connection *conn) {
|
||||
|
||||
if (psync_result == PSYNC_CONTINUE) {
|
||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.");
|
||||
if (server.supervised_mode == SUPERVISED_SYSTEMD) {
|
||||
redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Partial Resynchronization accepted. Ready to accept connections.\n");
|
||||
redisCommunicateSystemd("READY=1\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2387,6 +2510,10 @@ void replicationUnsetMaster(void) {
|
||||
moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
|
||||
REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER,
|
||||
NULL);
|
||||
|
||||
/* Restart the AOF subsystem in case we shut it down during a sync when
|
||||
* we were still a slave. */
|
||||
if (server.aof_enabled && server.aof_state == AOF_OFF) restartAOFAfterSYNC();
|
||||
}
|
||||
|
||||
/* This function is called when the slave lose the connection with the
|
||||
@@ -2424,9 +2551,6 @@ void replicaofCommand(client *c) {
|
||||
serverLog(LL_NOTICE,"MASTER MODE enabled (user request from '%s')",
|
||||
client);
|
||||
sdsfree(client);
|
||||
/* Restart the AOF subsystem in case we shut it down during a sync when
|
||||
* we were still a slave. */
|
||||
if (server.aof_enabled && server.aof_state == AOF_OFF) restartAOFAfterSYNC();
|
||||
}
|
||||
} else {
|
||||
long port;
|
||||
@@ -2603,9 +2727,47 @@ void replicationCacheMaster(client *c) {
|
||||
* current offset if no data was lost during the failover. So we use our
|
||||
* current replication ID and offset in order to synthesize a cached master. */
|
||||
void replicationCacheMasterUsingMyself(void) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Before turning into a replica, using my own master parameters "
|
||||
"to synthesize a cached master: I may be able to synchronize with "
|
||||
"the new master with just a partial transfer.");
|
||||
|
||||
/* This will be used to populate the field server.master->reploff
|
||||
* by replicationCreateMasterClient(). We'll later set the created
|
||||
* master as server.cached_master, so the replica will use such
|
||||
* offset for PSYNC. */
|
||||
server.master_initial_offset = server.master_repl_offset;
|
||||
|
||||
/* However if the "meaningful" offset, that is the offset without
|
||||
* the final PINGs in the stream, is different, use this instead:
|
||||
* often when the master is no longer reachable, replicas will never
|
||||
* receive the PINGs, however the master will end with an incremented
|
||||
* offset because of the PINGs and will not be able to incrementally
|
||||
* PSYNC with the new master. */
|
||||
if (server.master_repl_offset > server.master_repl_meaningful_offset) {
|
||||
long long delta = server.master_repl_offset -
|
||||
server.master_repl_meaningful_offset;
|
||||
serverLog(LL_NOTICE,
|
||||
"Using the meaningful offset %lld instead of %lld to exclude "
|
||||
"the final PINGs (%lld bytes difference)",
|
||||
server.master_repl_meaningful_offset,
|
||||
server.master_repl_offset,
|
||||
delta);
|
||||
server.master_initial_offset = server.master_repl_meaningful_offset;
|
||||
server.master_repl_offset = server.master_repl_meaningful_offset;
|
||||
if (server.repl_backlog_histlen <= delta) {
|
||||
server.repl_backlog_histlen = 0;
|
||||
server.repl_backlog_idx = 0;
|
||||
} else {
|
||||
server.repl_backlog_histlen -= delta;
|
||||
server.repl_backlog_idx =
|
||||
(server.repl_backlog_idx + (server.repl_backlog_size - delta)) %
|
||||
server.repl_backlog_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* The master client we create can be set to any DBID, because
|
||||
* the new master will start its replication stream with SELECT. */
|
||||
server.master_initial_offset = server.master_repl_offset;
|
||||
replicationCreateMasterClient(NULL,-1);
|
||||
|
||||
/* Use our own ID / offset. */
|
||||
@@ -2615,7 +2777,6 @@ void replicationCacheMasterUsingMyself(void) {
|
||||
unlinkClient(server.master);
|
||||
server.cached_master = server.master;
|
||||
server.master = NULL;
|
||||
serverLog(LL_NOTICE,"Before turning into a replica, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.");
|
||||
}
|
||||
|
||||
/* Free a cached master, called when there are no longer the conditions for
|
||||
@@ -2995,10 +3156,18 @@ void replicationCron(void) {
|
||||
clientsArePaused();
|
||||
|
||||
if (!manual_failover_in_progress) {
|
||||
long long before_ping = server.master_repl_meaningful_offset;
|
||||
ping_argv[0] = createStringObject("PING",4);
|
||||
replicationFeedSlaves(server.slaves, server.slaveseldb,
|
||||
ping_argv, 1);
|
||||
decrRefCount(ping_argv[0]);
|
||||
/* The server.master_repl_meaningful_offset variable represents
|
||||
* the offset of the replication stream without the pending PINGs.
|
||||
* This is useful to set the right replication offset for PSYNC
|
||||
* when the master is turned into a replica. Otherwise pending
|
||||
* PINGs may not allow it to perform an incremental sync with the
|
||||
* new master. */
|
||||
server.master_repl_meaningful_offset = before_ping;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3133,6 +3302,10 @@ void replicationCron(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove the RDB file used for replication if Redis is not running
|
||||
* with any persistence. */
|
||||
removeRDBUsedToSyncReplicas();
|
||||
|
||||
/* Refresh the number of slaves with lag <= min-slaves-max-lag. */
|
||||
refreshGoodSlavesCount();
|
||||
replication_cron_loops++; /* Incremented with frequency 1 HZ. */
|
||||
|
||||
+48
-25
@@ -485,13 +485,6 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
static size_t cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
|
||||
static int inuse = 0; /* Recursive calls detection. */
|
||||
|
||||
/* Reflect MULTI state */
|
||||
if (server.lua_multi_emitted || (server.lua_caller->flags & CLIENT_MULTI)) {
|
||||
c->flags |= CLIENT_MULTI;
|
||||
} else {
|
||||
c->flags &= ~CLIENT_MULTI;
|
||||
}
|
||||
|
||||
/* By using Lua debug hooks it is possible to trigger a recursive call
|
||||
* to luaRedisGenericCommand(), which normally should never happen.
|
||||
* To make this function reentrant is futile and makes it slower, but
|
||||
@@ -613,8 +606,10 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
}
|
||||
|
||||
/* Check the ACLs. */
|
||||
int acl_retval = ACLCheckCommandPerm(c);
|
||||
int acl_keypos;
|
||||
int acl_retval = ACLCheckCommandPerm(c,&acl_keypos);
|
||||
if (acl_retval != ACL_OK) {
|
||||
addACLLogEntry(c,acl_retval,acl_keypos,NULL);
|
||||
if (acl_retval == ACL_DENIED_CMD)
|
||||
luaPushError(lua, "The user executing the script can't run this "
|
||||
"command or subcommand");
|
||||
@@ -662,12 +657,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
!server.loading && /* Don't care about mem if loading. */
|
||||
!server.masterhost && /* Slave must execute the script. */
|
||||
server.lua_write_dirty == 0 && /* Script had no side effects so far. */
|
||||
server.lua_oom && /* Detected OOM when script start. */
|
||||
(cmd->flags & CMD_DENYOOM))
|
||||
{
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) != C_OK) {
|
||||
luaPushError(lua, shared.oomerr->ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
luaPushError(lua, shared.oomerr->ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (cmd->flags & CMD_RANDOM) server.lua_random_dirty = 1;
|
||||
@@ -679,15 +673,27 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
if (server.cluster_enabled && !server.loading &&
|
||||
!(server.lua_caller->flags & CLIENT_MASTER))
|
||||
{
|
||||
int error_code;
|
||||
/* Duplicate relevant flags in the lua client. */
|
||||
c->flags &= ~(CLIENT_READONLY|CLIENT_ASKING);
|
||||
c->flags |= server.lua_caller->flags & (CLIENT_READONLY|CLIENT_ASKING);
|
||||
if (getNodeByQuery(c,c->cmd,c->argv,c->argc,NULL,NULL) !=
|
||||
if (getNodeByQuery(c,c->cmd,c->argv,c->argc,NULL,&error_code) !=
|
||||
server.cluster->myself)
|
||||
{
|
||||
luaPushError(lua,
|
||||
"Lua script attempted to access a non local key in a "
|
||||
"cluster node");
|
||||
if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) {
|
||||
luaPushError(lua,
|
||||
"Lua script attempted to execute a write command while the "
|
||||
"cluster is down and readonly");
|
||||
} else if (error_code == CLUSTER_REDIR_DOWN_STATE) {
|
||||
luaPushError(lua,
|
||||
"Lua script attempted to execute a command while the "
|
||||
"cluster is down");
|
||||
} else {
|
||||
luaPushError(lua,
|
||||
"Lua script attempted to access a non local key in a "
|
||||
"cluster node");
|
||||
}
|
||||
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
@@ -703,6 +709,9 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
{
|
||||
execCommandPropagateMulti(server.lua_caller);
|
||||
server.lua_multi_emitted = 1;
|
||||
/* Now we are in the MULTI context, the lua_client should be
|
||||
* flag as CLIENT_MULTI. */
|
||||
c->flags |= CLIENT_MULTI;
|
||||
}
|
||||
|
||||
/* Run the command */
|
||||
@@ -950,6 +959,7 @@ int luaLogCommand(lua_State *lua) {
|
||||
lua_pushstring(lua, "Invalid debug level.");
|
||||
return lua_error(lua);
|
||||
}
|
||||
if (level < server.verbosity) return 0;
|
||||
|
||||
/* Glue together all the arguments */
|
||||
log = sdsempty();
|
||||
@@ -1429,6 +1439,22 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
}
|
||||
}
|
||||
|
||||
void prepareLuaClient(void) {
|
||||
/* Select the right DB in the context of the Lua client */
|
||||
selectDb(server.lua_client,server.lua_caller->db->id);
|
||||
server.lua_client->resp = 2; /* Default is RESP2, scripts can change it. */
|
||||
|
||||
/* If we are in MULTI context, flag Lua client as CLIENT_MULTI. */
|
||||
if (server.lua_caller->flags & CLIENT_MULTI) {
|
||||
server.lua_client->flags |= CLIENT_MULTI;
|
||||
}
|
||||
}
|
||||
|
||||
void resetLuaClient(void) {
|
||||
/* After the script done, remove the MULTI state. */
|
||||
server.lua_client->flags &= ~CLIENT_MULTI;
|
||||
}
|
||||
|
||||
void evalGenericCommand(client *c, int evalsha) {
|
||||
lua_State *lua = server.lua;
|
||||
char funcname[43];
|
||||
@@ -1517,10 +1543,6 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys);
|
||||
luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys);
|
||||
|
||||
/* Set the Lua client database and protocol. */
|
||||
selectDb(server.lua_client,c->db->id);
|
||||
server.lua_client->resp = 2; /* Default is RESP2, scripts can change it. */
|
||||
|
||||
/* Set a hook in order to be able to stop the script execution if it
|
||||
* is running for too much time.
|
||||
* We set the hook only if the time limit is enabled as the hook will
|
||||
@@ -1540,11 +1562,15 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
delhook = 1;
|
||||
}
|
||||
|
||||
prepareLuaClient();
|
||||
|
||||
/* At this point whether this script was never seen before or if it was
|
||||
* already defined, we can call it. We have zero arguments and expect
|
||||
* a single return value. */
|
||||
err = lua_pcall(lua,0,1,-2);
|
||||
|
||||
resetLuaClient();
|
||||
|
||||
/* Perform some cleanup that we need to do both on error and success. */
|
||||
if (delhook) lua_sethook(lua,NULL,0,0); /* Disable hook */
|
||||
if (server.lua_timedout) {
|
||||
@@ -1591,11 +1617,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
if (server.lua_replicate_commands) {
|
||||
preventCommandPropagation(c);
|
||||
if (server.lua_multi_emitted) {
|
||||
robj *propargv[1];
|
||||
propargv[0] = createStringObject("EXEC",4);
|
||||
alsoPropagate(server.execCommand,c->db->id,propargv,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(propargv[0]);
|
||||
execCommandPropagateExec(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2453,6 +2475,7 @@ void ldbEval(lua_State *lua, sds *argv, int argc) {
|
||||
ldbLog(sdscatfmt(sdsempty(),"<error> %s",lua_tostring(lua,-1)));
|
||||
lua_pop(lua,1);
|
||||
sdsfree(code);
|
||||
sdsfree(expr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#define __SDS_H
|
||||
|
||||
#define SDS_MAX_PREALLOC (1024*1024)
|
||||
const char *SDS_NOINIT;
|
||||
extern const char *SDS_NOINIT;
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
@@ -36,7 +36,12 @@
|
||||
* the include of your alternate allocator if needed (not needed in order
|
||||
* to use the default libc allocator). */
|
||||
|
||||
#ifndef __SDS_ALLOC_H__
|
||||
#define __SDS_ALLOC_H__
|
||||
|
||||
#include "zmalloc.h"
|
||||
#define s_malloc zmalloc
|
||||
#define s_realloc zrealloc
|
||||
#define s_free zfree
|
||||
|
||||
#endif
|
||||
|
||||
+48
-14
@@ -205,7 +205,8 @@ typedef struct sentinelRedisInstance {
|
||||
dict *slaves; /* Slaves for this master instance. */
|
||||
unsigned int quorum;/* Number of sentinels that need to agree on failure. */
|
||||
int parallel_syncs; /* How many slaves to reconfigure at same time. */
|
||||
char *auth_pass; /* Password to use for AUTH against master & slaves. */
|
||||
char *auth_pass; /* Password to use for AUTH against master & replica. */
|
||||
char *auth_user; /* Username for ACLs AUTH against master & replica. */
|
||||
|
||||
/* Slave specific. */
|
||||
mstime_t master_link_down_time; /* Slave replication link down time. */
|
||||
@@ -1231,6 +1232,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
SENTINEL_DEFAULT_DOWN_AFTER;
|
||||
ri->master_link_down_time = 0;
|
||||
ri->auth_pass = NULL;
|
||||
ri->auth_user = NULL;
|
||||
ri->slave_priority = SENTINEL_DEFAULT_SLAVE_PRIORITY;
|
||||
ri->slave_reconf_sent_time = 0;
|
||||
ri->slave_master_host = NULL;
|
||||
@@ -1289,6 +1291,7 @@ void releaseSentinelRedisInstance(sentinelRedisInstance *ri) {
|
||||
sdsfree(ri->slave_master_host);
|
||||
sdsfree(ri->leader);
|
||||
sdsfree(ri->auth_pass);
|
||||
sdsfree(ri->auth_user);
|
||||
sdsfree(ri->info);
|
||||
releaseSentinelAddr(ri->addr);
|
||||
dictRelease(ri->renamed_commands);
|
||||
@@ -1654,19 +1657,19 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
ri->failover_timeout = atoi(argv[2]);
|
||||
if (ri->failover_timeout <= 0)
|
||||
return "negative or zero time parameter.";
|
||||
} else if (!strcasecmp(argv[0],"parallel-syncs") && argc == 3) {
|
||||
} else if (!strcasecmp(argv[0],"parallel-syncs") && argc == 3) {
|
||||
/* parallel-syncs <name> <milliseconds> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->parallel_syncs = atoi(argv[2]);
|
||||
} else if (!strcasecmp(argv[0],"notification-script") && argc == 3) {
|
||||
} else if (!strcasecmp(argv[0],"notification-script") && argc == 3) {
|
||||
/* notification-script <name> <path> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
if (access(argv[2],X_OK) == -1)
|
||||
return "Notification script seems non existing or non executable.";
|
||||
ri->notification_script = sdsnew(argv[2]);
|
||||
} else if (!strcasecmp(argv[0],"client-reconfig-script") && argc == 3) {
|
||||
} else if (!strcasecmp(argv[0],"client-reconfig-script") && argc == 3) {
|
||||
/* client-reconfig-script <name> <path> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
@@ -1674,11 +1677,16 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
return "Client reconfiguration script seems non existing or "
|
||||
"non executable.";
|
||||
ri->client_reconfig_script = sdsnew(argv[2]);
|
||||
} else if (!strcasecmp(argv[0],"auth-pass") && argc == 3) {
|
||||
} else if (!strcasecmp(argv[0],"auth-pass") && argc == 3) {
|
||||
/* auth-pass <name> <password> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->auth_pass = sdsnew(argv[2]);
|
||||
} else if (!strcasecmp(argv[0],"auth-user") && argc == 3) {
|
||||
/* auth-user <name> <username> */
|
||||
ri = sentinelGetMasterByName(argv[1]);
|
||||
if (!ri) return "No such master with specified name.";
|
||||
ri->auth_user = sdsnew(argv[2]);
|
||||
} else if (!strcasecmp(argv[0],"current-epoch") && argc == 2) {
|
||||
/* current-epoch <epoch> */
|
||||
unsigned long long current_epoch = strtoull(argv[1],NULL,10);
|
||||
@@ -1836,7 +1844,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
|
||||
/* sentinel auth-pass */
|
||||
/* sentinel auth-pass & auth-user */
|
||||
if (master->auth_pass) {
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel auth-pass %s %s",
|
||||
@@ -1844,6 +1852,13 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
|
||||
if (master->auth_user) {
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel auth-user %s %s",
|
||||
master->name, master->auth_user);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
|
||||
/* sentinel config-epoch */
|
||||
line = sdscatprintf(sdsempty(),
|
||||
"sentinel config-epoch %s %llu",
|
||||
@@ -1968,19 +1983,29 @@ werr:
|
||||
* will disconnect and reconnect the link and so forth. */
|
||||
void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
|
||||
char *auth_pass = NULL;
|
||||
char *auth_user = NULL;
|
||||
|
||||
if (ri->flags & SRI_MASTER) {
|
||||
auth_pass = ri->auth_pass;
|
||||
auth_user = ri->auth_user;
|
||||
} else if (ri->flags & SRI_SLAVE) {
|
||||
auth_pass = ri->master->auth_pass;
|
||||
auth_user = ri->master->auth_user;
|
||||
} else if (ri->flags & SRI_SENTINEL) {
|
||||
auth_pass = ACLDefaultUserFirstPassword();
|
||||
auth_pass = server.requirepass;
|
||||
auth_user = NULL;
|
||||
}
|
||||
|
||||
if (auth_pass) {
|
||||
if (auth_pass && auth_user == NULL) {
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s",
|
||||
sentinelInstanceMapCommand(ri,"AUTH"),
|
||||
auth_pass) == C_OK) ri->link->pending_commands++;
|
||||
} else if (auth_pass && auth_user) {
|
||||
/* If we also have an username, use the ACL-style AUTH command
|
||||
* with two arguments, username and password. */
|
||||
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s %s",
|
||||
sentinelInstanceMapCommand(ri,"AUTH"),
|
||||
auth_user, auth_pass) == C_OK) ri->link->pending_commands++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3522,6 +3547,12 @@ void sentinelSetCommand(client *c) {
|
||||
sdsfree(ri->auth_pass);
|
||||
ri->auth_pass = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"auth-user") && moreargs > 0) {
|
||||
/* auth-user <username> */
|
||||
char *value = c->argv[++j]->ptr;
|
||||
sdsfree(ri->auth_user);
|
||||
ri->auth_user = strlen(value) ? sdsnew(value) : NULL;
|
||||
changes++;
|
||||
} else if (!strcasecmp(option,"quorum") && moreargs > 0) {
|
||||
/* quorum <count> */
|
||||
robj *o = c->argv[++j];
|
||||
@@ -3993,11 +4024,14 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
|
||||
* an issue because CLIENT is variadic command, so Redis will not
|
||||
* recognized as a syntax error, and the transaction will not fail (but
|
||||
* only the unsupported command will fail). */
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "%s KILL TYPE normal",
|
||||
sentinelInstanceMapCommand(ri,"CLIENT"));
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
for (int type = 0; type < 2; type++) {
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "%s KILL TYPE %s",
|
||||
sentinelInstanceMapCommand(ri,"CLIENT"),
|
||||
type == 0 ? "normal" : "pubsub");
|
||||
if (retval == C_ERR) return retval;
|
||||
ri->link->pending_commands++;
|
||||
}
|
||||
|
||||
retval = redisAsyncCommand(ri->link->cc,
|
||||
sentinelDiscardReplyCallback, ri, "%s",
|
||||
@@ -4305,7 +4339,7 @@ void sentinelFailoverDetectEnd(sentinelRedisInstance *master) {
|
||||
sentinelRedisInstance *slave = dictGetVal(de);
|
||||
int retval;
|
||||
|
||||
if (slave->flags & (SRI_RECONF_DONE|SRI_RECONF_SENT)) continue;
|
||||
if (slave->flags & (SRI_PROMOTED|SRI_RECONF_DONE|SRI_RECONF_SENT)) continue;
|
||||
if (slave->link->disconnected) continue;
|
||||
|
||||
retval = sentinelSendSlaveOf(slave,
|
||||
|
||||
+216
-234
@@ -238,6 +238,10 @@ struct redisCommand redisCommandTable[] = {
|
||||
"write use-memory @bitmap",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"bitfield_ro",bitfieldroCommand,-2,
|
||||
"read-only fast @bitmap",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"setrange",setrangeCommand,4,
|
||||
"write use-memory @string",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
@@ -579,7 +583,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"select",selectCommand,2,
|
||||
"ok-loading fast @keyspace",
|
||||
"ok-loading fast ok-stale @keyspace",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"swapdb",swapdbCommand,3,
|
||||
@@ -660,7 +664,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"lastsave",lastsaveCommand,1,
|
||||
"read-only random fast @admin @dangerous",
|
||||
"read-only random fast ok-loading ok-stale @admin @dangerous",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"type",typeCommand,2,
|
||||
@@ -668,15 +672,15 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"multi",multiCommand,1,
|
||||
"no-script fast @transaction",
|
||||
"no-script fast ok-loading ok-stale @transaction",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"exec",execCommand,1,
|
||||
"no-script no-monitor no-slowlog @transaction",
|
||||
"no-script no-monitor no-slowlog ok-loading ok-stale @transaction",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"discard",discardCommand,1,
|
||||
"no-script fast @transaction",
|
||||
"no-script fast ok-loading ok-stale @transaction",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"sync",syncCommand,1,
|
||||
@@ -708,7 +712,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"monitor",monitorCommand,1,
|
||||
"admin no-script",
|
||||
"admin no-script ok-loading ok-stale",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"ttl",ttlCommand,2,
|
||||
@@ -740,7 +744,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"debug",debugCommand,-2,
|
||||
"admin no-script",
|
||||
"admin no-script ok-loading ok-stale",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"config",configCommand,-2,
|
||||
@@ -817,14 +821,14 @@ struct redisCommand redisCommandTable[] = {
|
||||
|
||||
{"memory",memoryCommand,-2,
|
||||
"random read-only",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
0,memoryGetKeys,0,0,0,0,0,0},
|
||||
|
||||
{"client",clientCommand,-2,
|
||||
"admin no-script random @connection",
|
||||
"admin no-script random ok-loading ok-stale @connection",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"hello",helloCommand,-2,
|
||||
"no-auth no-script fast no-monitor no-slowlog @connection",
|
||||
"no-auth no-script fast no-monitor ok-loading ok-stale no-slowlog @connection",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
/* EVAL can modify the dataset, however it is not flagged as a write
|
||||
@@ -838,7 +842,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,evalGetKeys,0,0,0,0,0,0},
|
||||
|
||||
{"slowlog",slowlogCommand,-2,
|
||||
"admin random",
|
||||
"admin random ok-loading ok-stale",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"script",scriptCommand,-2,
|
||||
@@ -846,7 +850,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"time",timeCommand,1,
|
||||
"read-only random fast",
|
||||
"read-only random fast ok-loading ok-stale",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"bitop",bitopCommand,-4,
|
||||
@@ -943,11 +947,11 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"xread",xreadCommand,-4,
|
||||
"read-only no-script @stream @blocking",
|
||||
"read-only @stream @blocking",
|
||||
0,xreadGetKeys,1,1,1,0,0,0},
|
||||
|
||||
{"xreadgroup",xreadCommand,-7,
|
||||
"write no-script @stream @blocking",
|
||||
"write @stream @blocking",
|
||||
0,xreadGetKeys,1,1,1,0,0,0},
|
||||
|
||||
{"xgroup",xgroupCommand,-2,
|
||||
@@ -999,8 +1003,12 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"acl",aclCommand,-2,
|
||||
"admin no-script ok-loading ok-stale",
|
||||
0,NULL,0,0,0,0,0,0}
|
||||
"admin no-script no-slowlog ok-loading ok-stale",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"lcs",lcsCommand,-4,
|
||||
"write use-memory @string",
|
||||
0,lcsGetKeys,0,0,0,0,0,0}
|
||||
};
|
||||
|
||||
/*============================ Utility functions ============================ */
|
||||
@@ -1455,12 +1463,20 @@ void updateDictResizePolicy(void) {
|
||||
dictDisableResize();
|
||||
}
|
||||
|
||||
/* Return true if there are no active children processes doing RDB saving,
|
||||
* AOF rewriting, or some side process spawned by a loaded module. */
|
||||
int hasActiveChildProcess() {
|
||||
return server.rdb_child_pid != -1 ||
|
||||
server.aof_child_pid != -1 ||
|
||||
server.module_child_pid != -1;
|
||||
}
|
||||
|
||||
/* Return true if this instance has persistence completely turned off:
|
||||
* both RDB and AOF are disabled. */
|
||||
int allPersistenceDisabled(void) {
|
||||
return server.saveparamslen == 0 && server.aof_state == AOF_OFF;
|
||||
}
|
||||
|
||||
/* ======================= Cron: called every 100 ms ======================== */
|
||||
|
||||
/* Add a sample to the operations per second array of samples. */
|
||||
@@ -1490,42 +1506,6 @@ long long getInstantaneousMetric(int metric) {
|
||||
return sum / STATS_METRIC_SAMPLES;
|
||||
}
|
||||
|
||||
/* Check for timeouts. Returns non-zero if the client was terminated.
|
||||
* The function gets the current time in milliseconds as argument since
|
||||
* it gets called multiple times in a loop, so calling gettimeofday() for
|
||||
* each iteration would be costly without any actual gain. */
|
||||
int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
|
||||
time_t now = now_ms/1000;
|
||||
|
||||
if (server.maxidletime &&
|
||||
!(c->flags & CLIENT_SLAVE) && /* no timeout for slaves */
|
||||
!(c->flags & CLIENT_MASTER) && /* no timeout for masters */
|
||||
!(c->flags & CLIENT_BLOCKED) && /* no timeout for BLPOP */
|
||||
!(c->flags & CLIENT_PUBSUB) && /* no timeout for Pub/Sub clients */
|
||||
(now - c->lastinteraction > server.maxidletime))
|
||||
{
|
||||
serverLog(LL_VERBOSE,"Closing idle client");
|
||||
freeClient(c);
|
||||
return 1;
|
||||
} else if (c->flags & CLIENT_BLOCKED) {
|
||||
/* Blocked OPS timeout is handled with milliseconds resolution.
|
||||
* However note that the actual resolution is limited by
|
||||
* server.hz. */
|
||||
|
||||
if (c->bpop.timeout != 0 && c->bpop.timeout < now_ms) {
|
||||
/* Handle blocking operation specific timeout. */
|
||||
replyToBlockedClientTimedOut(c);
|
||||
unblockClient(c);
|
||||
} else if (server.cluster_enabled) {
|
||||
/* Cluster: handle unblock & redirect of clients blocked
|
||||
* into keys no longer served by this server. */
|
||||
if (clusterRedirectBlockedClientIfNeeded(c))
|
||||
unblockClient(c);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The client query buffer is an sds.c string that can end with a lot of
|
||||
* free space not used, this function reclaims space if needed.
|
||||
*
|
||||
@@ -1613,6 +1593,28 @@ int clientsCronTrackExpansiveClients(client *c) {
|
||||
return 0; /* This function never terminates the client. */
|
||||
}
|
||||
|
||||
/* Iterating all the clients in getMemoryOverheadData() is too slow and
|
||||
* in turn would make the INFO command too slow. So we perform this
|
||||
* computation incrementally and track the (not instantaneous but updated
|
||||
* to the second) total memory used by clients using clinetsCron() in
|
||||
* a more incremental way (depending on server.hz). */
|
||||
int clientsCronTrackClientsMemUsage(client *c) {
|
||||
size_t mem = 0;
|
||||
int type = getClientType(c);
|
||||
mem += getClientOutputBufferMemoryUsage(c);
|
||||
mem += sdsAllocSize(c->querybuf);
|
||||
mem += sizeof(client);
|
||||
/* Now that we have the memory used by the client, remove the old
|
||||
* value from the old categoty, and add it back. */
|
||||
server.stat_clients_type_memory[c->client_cron_last_memory_type] -=
|
||||
c->client_cron_last_memory_usage;
|
||||
server.stat_clients_type_memory[type] += mem;
|
||||
/* Remember what we added and where, to remove it next time. */
|
||||
c->client_cron_last_memory_usage = mem;
|
||||
c->client_cron_last_memory_type = type;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return the max samples in the memory usage of clients tracked by
|
||||
* the function clientsCronTrackExpansiveClients(). */
|
||||
void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) {
|
||||
@@ -1673,6 +1675,7 @@ void clientsCron(void) {
|
||||
if (clientsCronHandleTimeout(c,now)) continue;
|
||||
if (clientsCronResizeQueryBuffer(c)) continue;
|
||||
if (clientsCronTrackExpansiveClients(c)) continue;
|
||||
if (clientsCronTrackClientsMemUsage(c)) continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1683,7 +1686,7 @@ void databasesCron(void) {
|
||||
/* Expire keys by random sampling. Not required for slaves
|
||||
* as master will synthesize DELs for us. */
|
||||
if (server.active_expire_enabled) {
|
||||
if (server.masterhost == NULL) {
|
||||
if (iAmMaster()) {
|
||||
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
|
||||
} else {
|
||||
expireSlaveKeys();
|
||||
@@ -1691,8 +1694,7 @@ void databasesCron(void) {
|
||||
}
|
||||
|
||||
/* Defrag keys gradually. */
|
||||
if (server.active_defrag_enabled)
|
||||
activeDefragCycle();
|
||||
activeDefragCycle();
|
||||
|
||||
/* Perform hash tables rehashing if needed, but only if there are no
|
||||
* other processes saving the DB on disk. Otherwise rehashing is bad
|
||||
@@ -1736,20 +1738,29 @@ void databasesCron(void) {
|
||||
/* We take a cached value of the unix time in the global state because with
|
||||
* virtual memory and aging there is to store the current time in objects at
|
||||
* every object access, and accuracy is not needed. To access a global var is
|
||||
* a lot faster than calling time(NULL) */
|
||||
void updateCachedTime(void) {
|
||||
server.unixtime = time(NULL);
|
||||
server.mstime = mstime();
|
||||
* a lot faster than calling time(NULL).
|
||||
*
|
||||
* This function should be fast because it is called at every command execution
|
||||
* in call(), so it is possible to decide if to update the daylight saving
|
||||
* info or not using the 'update_daylight_info' argument. Normally we update
|
||||
* such info only when calling this function from serverCron() but not when
|
||||
* calling it from call(). */
|
||||
void updateCachedTime(int update_daylight_info) {
|
||||
server.ustime = ustime();
|
||||
server.mstime = server.ustime / 1000;
|
||||
server.unixtime = server.mstime / 1000;
|
||||
|
||||
/* To get information about daylight saving time, we need to call
|
||||
* localtime_r and cache the result. However calling localtime_r in this
|
||||
* context is safe since we will never fork() while here, in the main
|
||||
* thread. The logging function will call a thread safe version of
|
||||
* localtime that has no locks. */
|
||||
struct tm tm;
|
||||
time_t ut = server.unixtime;
|
||||
localtime_r(&ut,&tm);
|
||||
server.daylight_active = tm.tm_isdst;
|
||||
if (update_daylight_info) {
|
||||
struct tm tm;
|
||||
time_t ut = server.unixtime;
|
||||
localtime_r(&ut,&tm);
|
||||
server.daylight_active = tm.tm_isdst;
|
||||
}
|
||||
}
|
||||
|
||||
void checkChildrenDone(void) {
|
||||
@@ -1838,7 +1849,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period);
|
||||
|
||||
/* Update the time cache. */
|
||||
updateCachedTime();
|
||||
updateCachedTime(1);
|
||||
|
||||
server.hz = server.config_hz;
|
||||
/* Adapt the server.hz value to the number of configured clients. If we have
|
||||
@@ -1934,7 +1945,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
/* Show information about connected clients */
|
||||
if (!server.sentinel_mode) {
|
||||
run_with_period(5000) {
|
||||
serverLog(LL_VERBOSE,
|
||||
serverLog(LL_DEBUG,
|
||||
"%lu clients connected (%lu replicas), %zu bytes in use",
|
||||
listLength(server.clients)-listLength(server.slaves),
|
||||
listLength(server.slaves),
|
||||
@@ -2072,8 +2083,15 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
UNUSED(eventLoop);
|
||||
|
||||
/* Handle precise timeouts of blocked clients. */
|
||||
handleBlockedClientsTimeout();
|
||||
|
||||
/* We should handle pending reads clients ASAP after event loop. */
|
||||
handleClientsWithPendingReadsUsingThreads();
|
||||
|
||||
/* Handle TLS pending data. (must be done before flushAppendOnlyFile) */
|
||||
tlsProcessPendingData();
|
||||
|
||||
/* If tls still has pending unread data don't sleep at all. */
|
||||
aeSetDontWait(server.el, tlsHasPendingData());
|
||||
|
||||
@@ -2116,6 +2134,10 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
if (listLength(server.unblocked_clients))
|
||||
processUnblockedClients();
|
||||
|
||||
/* Send the invalidation messages to clients participating to the
|
||||
* client side caching protocol in broadcasting (BCAST) mode. */
|
||||
trackingBroadcastInvalidationMessages();
|
||||
|
||||
/* Write the AOF buffer on disk */
|
||||
flushAppendOnlyFile(0);
|
||||
|
||||
@@ -2137,7 +2159,6 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
void afterSleep(struct aeEventLoop *eventLoop) {
|
||||
UNUSED(eventLoop);
|
||||
if (moduleCount()) moduleAcquireGIL();
|
||||
handleClientsWithPendingReadsUsingThreads();
|
||||
}
|
||||
|
||||
/* =========================== Server initialization ======================== */
|
||||
@@ -2236,6 +2257,8 @@ void createSharedObjects(void) {
|
||||
shared.rpoplpush = createStringObject("RPOPLPUSH",9);
|
||||
shared.zpopmin = createStringObject("ZPOPMIN",7);
|
||||
shared.zpopmax = createStringObject("ZPOPMAX",7);
|
||||
shared.multi = createStringObject("MULTI",5);
|
||||
shared.exec = createStringObject("EXEC",4);
|
||||
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
|
||||
shared.integers[j] =
|
||||
makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
|
||||
@@ -2258,57 +2281,30 @@ void createSharedObjects(void) {
|
||||
void initServerConfig(void) {
|
||||
int j;
|
||||
|
||||
updateCachedTime();
|
||||
updateCachedTime(1);
|
||||
getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE);
|
||||
server.runid[CONFIG_RUN_ID_SIZE] = '\0';
|
||||
changeReplicationId();
|
||||
clearReplicationId2();
|
||||
server.hz = CONFIG_DEFAULT_HZ; /* Initialize it ASAP, even if it may get
|
||||
updated later after loading the config.
|
||||
This value may be used before the server
|
||||
is initialized. */
|
||||
server.timezone = getTimeZone(); /* Initialized by tzset(). */
|
||||
server.configfile = NULL;
|
||||
server.executable = NULL;
|
||||
server.hz = server.config_hz = CONFIG_DEFAULT_HZ;
|
||||
server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ;
|
||||
server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
|
||||
server.port = CONFIG_DEFAULT_SERVER_PORT;
|
||||
server.tls_port = CONFIG_DEFAULT_SERVER_TLS_PORT;
|
||||
server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;
|
||||
server.bindaddr_count = 0;
|
||||
server.unixsocket = NULL;
|
||||
server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM;
|
||||
server.ipfd_count = 0;
|
||||
server.tlsfd_count = 0;
|
||||
server.sofd = -1;
|
||||
server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE;
|
||||
server.gopher_enabled = CONFIG_DEFAULT_GOPHER_ENABLED;
|
||||
server.dbnum = CONFIG_DEFAULT_DBNUM;
|
||||
server.verbosity = CONFIG_DEFAULT_VERBOSITY;
|
||||
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
|
||||
server.tcpkeepalive = CONFIG_DEFAULT_TCP_KEEPALIVE;
|
||||
server.active_expire_enabled = 1;
|
||||
server.jemalloc_bg_thread = 1;
|
||||
server.active_defrag_enabled = CONFIG_DEFAULT_ACTIVE_DEFRAG;
|
||||
server.active_defrag_ignore_bytes = CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES;
|
||||
server.active_defrag_threshold_lower = CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER;
|
||||
server.active_defrag_threshold_upper = CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER;
|
||||
server.active_defrag_cycle_min = CONFIG_DEFAULT_DEFRAG_CYCLE_MIN;
|
||||
server.active_defrag_cycle_max = CONFIG_DEFAULT_DEFRAG_CYCLE_MAX;
|
||||
server.active_defrag_max_scan_fields = CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS;
|
||||
server.proto_max_bulk_len = CONFIG_DEFAULT_PROTO_MAX_BULK_LEN;
|
||||
server.client_max_querybuf_len = PROTO_MAX_QUERYBUF_LEN;
|
||||
server.saveparams = NULL;
|
||||
server.loading = 0;
|
||||
server.logfile = zstrdup(CONFIG_DEFAULT_LOGFILE);
|
||||
server.syslog_enabled = CONFIG_DEFAULT_SYSLOG_ENABLED;
|
||||
server.syslog_ident = zstrdup(CONFIG_DEFAULT_SYSLOG_IDENT);
|
||||
server.syslog_facility = LOG_LOCAL0;
|
||||
server.daemonize = CONFIG_DEFAULT_DAEMONIZE;
|
||||
server.supervised = 0;
|
||||
server.supervised_mode = SUPERVISED_NONE;
|
||||
server.aof_state = AOF_OFF;
|
||||
server.aof_fsync = CONFIG_DEFAULT_AOF_FSYNC;
|
||||
server.aof_no_fsync_on_rewrite = CONFIG_DEFAULT_AOF_NO_FSYNC_ON_REWRITE;
|
||||
server.aof_rewrite_perc = AOF_REWRITE_PERC;
|
||||
server.aof_rewrite_min_size = AOF_REWRITE_MIN_SIZE;
|
||||
server.aof_rewrite_base_size = 0;
|
||||
server.aof_rewrite_scheduled = 0;
|
||||
server.aof_flush_sleep = 0;
|
||||
@@ -2320,63 +2316,18 @@ void initServerConfig(void) {
|
||||
server.aof_fd = -1;
|
||||
server.aof_selected_db = -1; /* Make sure the first time will not match */
|
||||
server.aof_flush_postponed_start = 0;
|
||||
server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
|
||||
server.rdb_save_incremental_fsync = CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC;
|
||||
server.rdb_key_save_delay = CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY;
|
||||
server.key_load_delay = CONFIG_DEFAULT_KEY_LOAD_DELAY;
|
||||
server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED;
|
||||
server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE;
|
||||
server.pidfile = NULL;
|
||||
server.rdb_filename = zstrdup(CONFIG_DEFAULT_RDB_FILENAME);
|
||||
server.aof_filename = zstrdup(CONFIG_DEFAULT_AOF_FILENAME);
|
||||
server.acl_filename = zstrdup(CONFIG_DEFAULT_ACL_FILENAME);
|
||||
server.rdb_compression = CONFIG_DEFAULT_RDB_COMPRESSION;
|
||||
server.rdb_checksum = CONFIG_DEFAULT_RDB_CHECKSUM;
|
||||
server.stop_writes_on_bgsave_err = CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR;
|
||||
server.activerehashing = CONFIG_DEFAULT_ACTIVE_REHASHING;
|
||||
server.active_defrag_running = 0;
|
||||
server.notify_keyspace_events = 0;
|
||||
server.maxclients = CONFIG_DEFAULT_MAX_CLIENTS;
|
||||
server.blocked_clients = 0;
|
||||
memset(server.blocked_clients_by_type,0,
|
||||
sizeof(server.blocked_clients_by_type));
|
||||
server.maxmemory = CONFIG_DEFAULT_MAXMEMORY;
|
||||
server.maxmemory_policy = CONFIG_DEFAULT_MAXMEMORY_POLICY;
|
||||
server.maxmemory_samples = CONFIG_DEFAULT_MAXMEMORY_SAMPLES;
|
||||
server.lfu_log_factor = CONFIG_DEFAULT_LFU_LOG_FACTOR;
|
||||
server.lfu_decay_time = CONFIG_DEFAULT_LFU_DECAY_TIME;
|
||||
server.hash_max_ziplist_entries = OBJ_HASH_MAX_ZIPLIST_ENTRIES;
|
||||
server.hash_max_ziplist_value = OBJ_HASH_MAX_ZIPLIST_VALUE;
|
||||
server.list_max_ziplist_size = OBJ_LIST_MAX_ZIPLIST_SIZE;
|
||||
server.list_compress_depth = OBJ_LIST_COMPRESS_DEPTH;
|
||||
server.set_max_intset_entries = OBJ_SET_MAX_INTSET_ENTRIES;
|
||||
server.zset_max_ziplist_entries = OBJ_ZSET_MAX_ZIPLIST_ENTRIES;
|
||||
server.zset_max_ziplist_value = OBJ_ZSET_MAX_ZIPLIST_VALUE;
|
||||
server.hll_sparse_max_bytes = CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES;
|
||||
server.stream_node_max_bytes = OBJ_STREAM_NODE_MAX_BYTES;
|
||||
server.stream_node_max_entries = OBJ_STREAM_NODE_MAX_ENTRIES;
|
||||
server.shutdown_asap = 0;
|
||||
server.cluster_enabled = 0;
|
||||
server.cluster_node_timeout = CLUSTER_DEFAULT_NODE_TIMEOUT;
|
||||
server.cluster_migration_barrier = CLUSTER_DEFAULT_MIGRATION_BARRIER;
|
||||
server.cluster_slave_validity_factor = CLUSTER_DEFAULT_SLAVE_VALIDITY;
|
||||
server.cluster_require_full_coverage = CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE;
|
||||
server.cluster_slave_no_failover = CLUSTER_DEFAULT_SLAVE_NO_FAILOVER;
|
||||
server.cluster_configfile = zstrdup(CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
server.cluster_announce_ip = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_IP;
|
||||
server.cluster_announce_port = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT;
|
||||
server.cluster_announce_bus_port = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_BUS_PORT;
|
||||
server.cluster_module_flags = CLUSTER_MODULE_FLAG_NONE;
|
||||
server.migrate_cached_sockets = dictCreate(&migrateCacheDictType,NULL);
|
||||
server.next_client_id = 1; /* Client IDs, start from 1 .*/
|
||||
server.loading_process_events_interval_bytes = (1024*1024*2);
|
||||
server.lazyfree_lazy_eviction = CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION;
|
||||
server.lazyfree_lazy_expire = CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE;
|
||||
server.lazyfree_lazy_server_del = CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL;
|
||||
server.always_show_logo = CONFIG_DEFAULT_ALWAYS_SHOW_LOGO;
|
||||
server.lua_time_limit = LUA_SCRIPT_TIME_LIMIT;
|
||||
server.io_threads_num = CONFIG_DEFAULT_IO_THREADS_NUM;
|
||||
server.io_threads_do_reads = CONFIG_DEFAULT_IO_THREADS_DO_READS;
|
||||
|
||||
server.lruclock = getLRUClock();
|
||||
resetServerSaveParams();
|
||||
@@ -2397,31 +2348,15 @@ void initServerConfig(void) {
|
||||
server.repl_transfer_fd = -1;
|
||||
server.repl_transfer_s = NULL;
|
||||
server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
|
||||
server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA;
|
||||
server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY;
|
||||
server.repl_slave_ignore_maxmemory = CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY;
|
||||
server.repl_slave_lazy_flush = CONFIG_DEFAULT_SLAVE_LAZY_FLUSH;
|
||||
server.repl_down_since = 0; /* Never connected, repl is down since EVER. */
|
||||
server.repl_disable_tcp_nodelay = CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY;
|
||||
server.repl_diskless_sync = CONFIG_DEFAULT_REPL_DISKLESS_SYNC;
|
||||
server.repl_diskless_load = CONFIG_DEFAULT_REPL_DISKLESS_LOAD;
|
||||
server.repl_diskless_sync_delay = CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY;
|
||||
server.repl_ping_slave_period = CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD;
|
||||
server.repl_timeout = CONFIG_DEFAULT_REPL_TIMEOUT;
|
||||
server.repl_min_slaves_to_write = CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE;
|
||||
server.repl_min_slaves_max_lag = CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG;
|
||||
server.slave_priority = CONFIG_DEFAULT_SLAVE_PRIORITY;
|
||||
server.slave_announce_ip = CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP;
|
||||
server.slave_announce_port = CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT;
|
||||
server.master_repl_offset = 0;
|
||||
server.master_repl_meaningful_offset = 0;
|
||||
|
||||
/* Replication partial resync backlog */
|
||||
server.repl_backlog = NULL;
|
||||
server.repl_backlog_size = CONFIG_DEFAULT_REPL_BACKLOG_SIZE;
|
||||
server.repl_backlog_histlen = 0;
|
||||
server.repl_backlog_idx = 0;
|
||||
server.repl_backlog_off = 0;
|
||||
server.repl_backlog_time_limit = CONFIG_DEFAULT_REPL_BACKLOG_TIME_LIMIT;
|
||||
server.repl_no_slaves_since = time(NULL);
|
||||
|
||||
/* Client output buffer limits */
|
||||
@@ -2453,16 +2388,7 @@ void initServerConfig(void) {
|
||||
server.pexpireCommand = lookupCommandByCString("pexpire");
|
||||
server.xclaimCommand = lookupCommandByCString("xclaim");
|
||||
server.xgroupCommand = lookupCommandByCString("xgroup");
|
||||
|
||||
/* Slow log */
|
||||
server.slowlog_log_slower_than = CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN;
|
||||
server.slowlog_max_len = CONFIG_DEFAULT_SLOWLOG_MAX_LEN;
|
||||
|
||||
/* Latency monitor */
|
||||
server.latency_monitor_threshold = CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD;
|
||||
|
||||
/* Tracking. */
|
||||
server.tracking_table_max_fill = CONFIG_DEFAULT_TRACKING_TABLE_MAX_FILL;
|
||||
server.rpoplpushCommand = lookupCommandByCString("rpoplpush");
|
||||
|
||||
/* Debugging */
|
||||
server.assert_failed = "<no assertion failed>";
|
||||
@@ -2476,6 +2402,8 @@ void initServerConfig(void) {
|
||||
* script to the slave / AOF. This is the new way starting from
|
||||
* Redis 5. However it is possible to revert it via redis.conf. */
|
||||
server.lua_always_replicate_commands = 1;
|
||||
|
||||
initConfigValues();
|
||||
}
|
||||
|
||||
extern char **environ;
|
||||
@@ -2736,6 +2664,7 @@ void resetServerStats(void) {
|
||||
server.stat_expiredkeys = 0;
|
||||
server.stat_expired_stale_perc = 0;
|
||||
server.stat_expired_time_cap_reached_count = 0;
|
||||
server.stat_expire_cycle_time_used = 0;
|
||||
server.stat_evictedkeys = 0;
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_keyspace_hits = 0;
|
||||
@@ -2759,6 +2688,7 @@ void resetServerStats(void) {
|
||||
}
|
||||
server.stat_net_input_bytes = 0;
|
||||
server.stat_net_output_bytes = 0;
|
||||
server.stat_unexpected_error_replies = 0;
|
||||
server.aof_delayed_fsync = 0;
|
||||
}
|
||||
|
||||
@@ -2774,9 +2704,12 @@ void initServer(void) {
|
||||
server.syslog_facility);
|
||||
}
|
||||
|
||||
/* Initialization after setting defaults from the config system. */
|
||||
server.aof_state = server.aof_enabled ? AOF_ON : AOF_OFF;
|
||||
server.hz = server.config_hz;
|
||||
server.pid = getpid();
|
||||
server.current_client = NULL;
|
||||
server.fixed_time_expire = 0;
|
||||
server.clients = listCreate();
|
||||
server.clients_index = raxNew();
|
||||
server.clients_to_close = listCreate();
|
||||
@@ -2784,6 +2717,7 @@ void initServer(void) {
|
||||
server.monitors = listCreate();
|
||||
server.clients_pending_write = listCreate();
|
||||
server.clients_pending_read = listCreate();
|
||||
server.clients_timeout_table = raxNew();
|
||||
server.slaveseldb = -1; /* Force to emit the first SELECT command. */
|
||||
server.unblocked_clients = listCreate();
|
||||
server.ready_keys = listCreate();
|
||||
@@ -2838,16 +2772,19 @@ void initServer(void) {
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
server.db[j].dict = dictCreate(&dbDictType,NULL);
|
||||
server.db[j].expires = dictCreate(&keyptrDictType,NULL);
|
||||
server.db[j].expires_cursor = 0;
|
||||
server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
|
||||
server.db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
|
||||
server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
|
||||
server.db[j].id = j;
|
||||
server.db[j].avg_ttl = 0;
|
||||
server.db[j].defrag_later = listCreate();
|
||||
listSetFreeMethod(server.db[j].defrag_later,(void (*)(void*))sdsfree);
|
||||
}
|
||||
evictionPoolAlloc(); /* Initialize the LRU keys pool. */
|
||||
server.pubsub_channels = dictCreate(&keylistDictType,NULL);
|
||||
server.pubsub_patterns = listCreate();
|
||||
server.pubsub_patterns_dict = dictCreate(&keylistDictType,NULL);
|
||||
listSetFreeMethod(server.pubsub_patterns,freePubsubPattern);
|
||||
listSetMatchMethod(server.pubsub_patterns,listMatchPubsubPattern);
|
||||
server.cronloops = 0;
|
||||
@@ -2878,6 +2815,8 @@ void initServer(void) {
|
||||
server.stat_rdb_cow_bytes = 0;
|
||||
server.stat_aof_cow_bytes = 0;
|
||||
server.stat_module_cow_bytes = 0;
|
||||
for (int j = 0; j < CLIENT_TYPE_COUNT; j++)
|
||||
server.stat_clients_type_memory[j] = 0;
|
||||
server.cron_malloc_stats.zmalloc_used = 0;
|
||||
server.cron_malloc_stats.process_rss = 0;
|
||||
server.cron_malloc_stats.allocator_allocated = 0;
|
||||
@@ -3246,10 +3185,13 @@ void preventCommandReplication(client *c) {
|
||||
*
|
||||
*/
|
||||
void call(client *c, int flags) {
|
||||
long long dirty, start, duration;
|
||||
long long dirty;
|
||||
ustime_t start, duration;
|
||||
int client_old_flags = c->flags;
|
||||
struct redisCommand *real_cmd = c->cmd;
|
||||
|
||||
server.fixed_time_expire++;
|
||||
|
||||
/* Sent the command to clients in MONITOR mode, only if the commands are
|
||||
* not generated from reading an AOF. */
|
||||
if (listLength(server.monitors) &&
|
||||
@@ -3267,7 +3209,8 @@ void call(client *c, int flags) {
|
||||
|
||||
/* Call the command. */
|
||||
dirty = server.dirty;
|
||||
start = ustime();
|
||||
updateCachedTime(0);
|
||||
start = server.ustime;
|
||||
c->cmd->proc(c);
|
||||
duration = ustime()-start;
|
||||
dirty = server.dirty-dirty;
|
||||
@@ -3351,6 +3294,22 @@ void call(client *c, int flags) {
|
||||
redisOp *rop;
|
||||
|
||||
if (flags & CMD_CALL_PROPAGATE) {
|
||||
int multi_emitted = 0;
|
||||
/* Wrap the commands in server.also_propagate array,
|
||||
* but don't wrap it if we are already in MULTI context,
|
||||
* in case the nested MULTI/EXEC.
|
||||
*
|
||||
* And if the array contains only one command, no need to
|
||||
* wrap it, since the single command is atomic. */
|
||||
if (server.also_propagate.numops > 1 &&
|
||||
!(c->cmd->flags & CMD_MODULE) &&
|
||||
!(c->flags & CLIENT_MULTI) &&
|
||||
!(flags & CMD_CALL_NOWRAP))
|
||||
{
|
||||
execCommandPropagateMulti(c);
|
||||
multi_emitted = 1;
|
||||
}
|
||||
|
||||
for (j = 0; j < server.also_propagate.numops; j++) {
|
||||
rop = &server.also_propagate.ops[j];
|
||||
int target = rop->target;
|
||||
@@ -3360,6 +3319,10 @@ void call(client *c, int flags) {
|
||||
if (target)
|
||||
propagate(rop->cmd,rop->dbid,rop->argv,rop->argc,target);
|
||||
}
|
||||
|
||||
if (multi_emitted) {
|
||||
execCommandPropagateExec(c);
|
||||
}
|
||||
}
|
||||
redisOpArrayFree(&server.also_propagate);
|
||||
}
|
||||
@@ -3370,10 +3333,14 @@ void call(client *c, int flags) {
|
||||
if (c->cmd->flags & CMD_READONLY) {
|
||||
client *caller = (c->flags & CLIENT_LUA && server.lua_caller) ?
|
||||
server.lua_caller : c;
|
||||
if (caller->flags & CLIENT_TRACKING)
|
||||
if (caller->flags & CLIENT_TRACKING &&
|
||||
!(caller->flags & CLIENT_TRACKING_BCAST))
|
||||
{
|
||||
trackingRememberKeys(caller);
|
||||
}
|
||||
}
|
||||
|
||||
server.fixed_time_expire--;
|
||||
server.stat_numcommands++;
|
||||
}
|
||||
|
||||
@@ -3422,10 +3389,10 @@ int processCommand(client *c) {
|
||||
/* Check if the user is authenticated. This check is skipped in case
|
||||
* the default user is flagged as "nopass" and is active. */
|
||||
int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
|
||||
DefaultUser->flags & USER_FLAG_DISABLED) &&
|
||||
(DefaultUser->flags & USER_FLAG_DISABLED)) &&
|
||||
!c->authenticated;
|
||||
if (auth_required) {
|
||||
/* AUTH, HELLO, and no-auth modules are valid even in
|
||||
/* AUTH and HELLO and no auth modules are valid even in
|
||||
* non-authenticated state. */
|
||||
if (!(c->cmd->flags & CMD_NO_AUTH)) {
|
||||
flagTransaction(c);
|
||||
@@ -3436,8 +3403,10 @@ int processCommand(client *c) {
|
||||
|
||||
/* Check if the user can run this command according to the current
|
||||
* ACLs. */
|
||||
int acl_retval = ACLCheckCommandPerm(c);
|
||||
int acl_keypos;
|
||||
int acl_retval = ACLCheckCommandPerm(c,&acl_keypos);
|
||||
if (acl_retval != ACL_OK) {
|
||||
addACLLogEntry(c,acl_retval,acl_keypos,NULL);
|
||||
flagTransaction(c);
|
||||
if (acl_retval == ACL_DENIED_CMD)
|
||||
addReplyErrorFormat(c,
|
||||
@@ -3501,6 +3470,13 @@ int processCommand(client *c) {
|
||||
addReply(c, shared.oomerr);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Save out_of_memory result at script start, otherwise if we check OOM
|
||||
* untill first write within script, memory used by lua stack and
|
||||
* arguments might interfere. */
|
||||
if (c->cmd->proc == evalCommand || c->cmd->proc == evalShaCommand) {
|
||||
server.lua_oom = out_of_memory;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure to use a reasonable amount of memory for client side
|
||||
@@ -3545,6 +3521,7 @@ int processCommand(client *c) {
|
||||
!(c->flags & CLIENT_MASTER) &&
|
||||
c->cmd->flags & CMD_WRITE)
|
||||
{
|
||||
flagTransaction(c);
|
||||
addReply(c, shared.roslaveerr);
|
||||
return C_OK;
|
||||
}
|
||||
@@ -3557,7 +3534,10 @@ int processCommand(client *c) {
|
||||
c->cmd->proc != unsubscribeCommand &&
|
||||
c->cmd->proc != psubscribeCommand &&
|
||||
c->cmd->proc != punsubscribeCommand) {
|
||||
addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context");
|
||||
addReplyErrorFormat(c,
|
||||
"Can't execute '%s': only (P)SUBSCRIBE / "
|
||||
"(P)UNSUBSCRIBE / PING / QUIT are allowed in this context",
|
||||
c->cmd->name);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
@@ -3580,11 +3560,19 @@ int processCommand(client *c) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Lua script too slow? Only allow a limited number of commands. */
|
||||
/* Lua script too slow? Only allow a limited number of commands.
|
||||
* Note that we need to allow the transactions commands, otherwise clients
|
||||
* sending a transaction with pipelining without error checking, may have
|
||||
* the MULTI plus a few initial commands refused, then the timeout
|
||||
* condition resolves, and the bottom-half of the transaction gets
|
||||
* executed, see Github PR #7022. */
|
||||
if (server.lua_timedout &&
|
||||
c->cmd->proc != authCommand &&
|
||||
c->cmd->proc != helloCommand &&
|
||||
c->cmd->proc != replconfCommand &&
|
||||
c->cmd->proc != multiCommand &&
|
||||
c->cmd->proc != execCommand &&
|
||||
c->cmd->proc != discardCommand &&
|
||||
!(c->cmd->proc == shutdownCommand &&
|
||||
c->argc == 2 &&
|
||||
tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&
|
||||
@@ -3636,6 +3624,8 @@ int prepareForShutdown(int flags) {
|
||||
int nosave = flags & SHUTDOWN_NOSAVE;
|
||||
|
||||
serverLog(LL_WARNING,"User requested shutdown...");
|
||||
if (server.supervised_mode == SUPERVISED_SYSTEMD)
|
||||
redisCommunicateSystemd("STOPPING=1\n");
|
||||
|
||||
/* Kill all the Lua debugger forked sessions. */
|
||||
ldbKillForkedSessions();
|
||||
@@ -3677,6 +3667,8 @@ int prepareForShutdown(int flags) {
|
||||
/* Create a new RDB file before exiting. */
|
||||
if ((server.saveparamslen > 0 && !nosave) || save) {
|
||||
serverLog(LL_NOTICE,"Saving the final RDB snapshot before exiting.");
|
||||
if (server.supervised_mode == SUPERVISED_SYSTEMD)
|
||||
redisCommunicateSystemd("STATUS=Saving the final RDB snapshot\n");
|
||||
/* Snapshotting. Perform a SYNC SAVE and exit */
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
@@ -3687,6 +3679,8 @@ int prepareForShutdown(int flags) {
|
||||
* saving aborted, handling special stuff like slaves pending for
|
||||
* synchronization... */
|
||||
serverLog(LL_WARNING,"Error trying to save the DB, can't exit.");
|
||||
if (server.supervised_mode == SUPERVISED_SYSTEMD)
|
||||
redisCommunicateSystemd("STATUS=Error trying to save the DB, can't exit.\n");
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
@@ -3814,6 +3808,7 @@ void addReplyCommand(client *c, struct redisCommand *cmd) {
|
||||
flagcount += addReplyCommandFlag(c,cmd,CMD_SKIP_SLOWLOG, "skip_slowlog");
|
||||
flagcount += addReplyCommandFlag(c,cmd,CMD_ASKING, "asking");
|
||||
flagcount += addReplyCommandFlag(c,cmd,CMD_FAST, "fast");
|
||||
flagcount += addReplyCommandFlag(c,cmd,CMD_NO_AUTH, "no_auth");
|
||||
if ((cmd->getkeys_proc && !(cmd->flags & CMD_MODULE)) ||
|
||||
cmd->flags & CMD_MODULE_GETKEYS)
|
||||
{
|
||||
@@ -3921,7 +3916,7 @@ void bytesToHuman(char *s, unsigned long long n) {
|
||||
/* Create the string returned by the INFO command. This is decoupled
|
||||
* by the INFO command itself as we need to report the same information
|
||||
* on memory corruption problems. */
|
||||
sds genRedisInfoString(char *section) {
|
||||
sds genRedisInfoString(const char *section) {
|
||||
sds info = sdsempty();
|
||||
time_t uptime = server.unixtime-server.stat_starttime;
|
||||
int j;
|
||||
@@ -4016,11 +4011,13 @@ sds genRedisInfoString(char *section) {
|
||||
"client_recent_max_input_buffer:%zu\r\n"
|
||||
"client_recent_max_output_buffer:%zu\r\n"
|
||||
"blocked_clients:%d\r\n"
|
||||
"tracking_clients:%d\r\n",
|
||||
"tracking_clients:%d\r\n"
|
||||
"clients_in_timeout_table:%llu\r\n",
|
||||
listLength(server.clients)-listLength(server.slaves),
|
||||
maxin, maxout,
|
||||
server.blocked_clients,
|
||||
server.tracking_clients);
|
||||
server.tracking_clients,
|
||||
(unsigned long long) raxSize(server.clients_timeout_table));
|
||||
}
|
||||
|
||||
/* Memory */
|
||||
@@ -4256,6 +4253,7 @@ sds genRedisInfoString(char *section) {
|
||||
"expired_keys:%lld\r\n"
|
||||
"expired_stale_perc:%.2f\r\n"
|
||||
"expired_time_cap_reached_count:%lld\r\n"
|
||||
"expire_cycle_cpu_milliseconds:%lld\r\n"
|
||||
"evicted_keys:%lld\r\n"
|
||||
"keyspace_hits:%lld\r\n"
|
||||
"keyspace_misses:%lld\r\n"
|
||||
@@ -4268,7 +4266,9 @@ sds genRedisInfoString(char *section) {
|
||||
"active_defrag_misses:%lld\r\n"
|
||||
"active_defrag_key_hits:%lld\r\n"
|
||||
"active_defrag_key_misses:%lld\r\n"
|
||||
"tracking_used_slots:%lld\r\n",
|
||||
"tracking_total_keys:%lld\r\n"
|
||||
"tracking_total_items:%lld\r\n"
|
||||
"unexpected_error_replies:%lld\r\n",
|
||||
server.stat_numconnections,
|
||||
server.stat_numcommands,
|
||||
getInstantaneousMetric(STATS_METRIC_COMMAND),
|
||||
@@ -4283,6 +4283,7 @@ sds genRedisInfoString(char *section) {
|
||||
server.stat_expiredkeys,
|
||||
server.stat_expired_stale_perc*100,
|
||||
server.stat_expired_time_cap_reached_count,
|
||||
server.stat_expire_cycle_time_used/1000,
|
||||
server.stat_evictedkeys,
|
||||
server.stat_keyspace_hits,
|
||||
server.stat_keyspace_misses,
|
||||
@@ -4295,7 +4296,9 @@ sds genRedisInfoString(char *section) {
|
||||
server.stat_active_defrag_misses,
|
||||
server.stat_active_defrag_key_hits,
|
||||
server.stat_active_defrag_key_misses,
|
||||
trackingGetUsedSlots());
|
||||
(unsigned long long) trackingGetTotalKeys(),
|
||||
(unsigned long long) trackingGetTotalItems(),
|
||||
server.stat_unexpected_error_replies);
|
||||
}
|
||||
|
||||
/* Replication */
|
||||
@@ -4411,6 +4414,7 @@ sds genRedisInfoString(char *section) {
|
||||
"master_replid:%s\r\n"
|
||||
"master_replid2:%s\r\n"
|
||||
"master_repl_offset:%lld\r\n"
|
||||
"master_repl_meaningful_offset:%lld\r\n"
|
||||
"second_repl_offset:%lld\r\n"
|
||||
"repl_backlog_active:%d\r\n"
|
||||
"repl_backlog_size:%lld\r\n"
|
||||
@@ -4419,6 +4423,7 @@ sds genRedisInfoString(char *section) {
|
||||
server.replid,
|
||||
server.replid2,
|
||||
server.master_repl_offset,
|
||||
server.master_repl_meaningful_offset,
|
||||
server.second_replid_offset,
|
||||
server.repl_backlog != NULL,
|
||||
server.repl_backlog_size,
|
||||
@@ -4796,6 +4801,7 @@ void loadDataFromDisk(void) {
|
||||
{
|
||||
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
|
||||
server.master_repl_offset = rsi.repl_offset;
|
||||
server.master_repl_meaningful_offset = rsi.repl_offset;
|
||||
/* If we are a slave, create a cached master from this
|
||||
* information, in order to allow partial resynchronizations
|
||||
* with masters. */
|
||||
@@ -4850,61 +4856,19 @@ int redisSupervisedUpstart(void) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int redisSupervisedSystemd(void) {
|
||||
int redisCommunicateSystemd(const char *sd_notify_msg) {
|
||||
const char *notify_socket = getenv("NOTIFY_SOCKET");
|
||||
int fd = 1;
|
||||
struct sockaddr_un su;
|
||||
struct iovec iov;
|
||||
struct msghdr hdr;
|
||||
int sendto_flags = 0;
|
||||
|
||||
if (!notify_socket) {
|
||||
serverLog(LL_WARNING,
|
||||
"systemd supervision requested, but NOTIFY_SOCKET not found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((strchr("@/", notify_socket[0])) == NULL || strlen(notify_socket) < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
serverLog(LL_NOTICE, "supervised by systemd, will signal readiness");
|
||||
if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Can't connect to systemd socket %s", notify_socket);
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(&su, 0, sizeof(su));
|
||||
su.sun_family = AF_UNIX;
|
||||
strncpy (su.sun_path, notify_socket, sizeof(su.sun_path) -1);
|
||||
su.sun_path[sizeof(su.sun_path) - 1] = '\0';
|
||||
|
||||
if (notify_socket[0] == '@')
|
||||
su.sun_path[0] = '\0';
|
||||
|
||||
memset(&iov, 0, sizeof(iov));
|
||||
iov.iov_base = "READY=1";
|
||||
iov.iov_len = strlen("READY=1");
|
||||
|
||||
memset(&hdr, 0, sizeof(hdr));
|
||||
hdr.msg_name = &su;
|
||||
hdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) +
|
||||
strlen(notify_socket);
|
||||
hdr.msg_iov = &iov;
|
||||
hdr.msg_iovlen = 1;
|
||||
|
||||
unsetenv("NOTIFY_SOCKET");
|
||||
#ifdef HAVE_MSG_NOSIGNAL
|
||||
sendto_flags |= MSG_NOSIGNAL;
|
||||
#endif
|
||||
if (sendmsg(fd, &hdr, sendto_flags) < 0) {
|
||||
serverLog(LL_WARNING, "Can't send notification to systemd");
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
close(fd);
|
||||
return 1;
|
||||
#ifdef HAVE_LIBSYSTEMD
|
||||
(void) sd_notify(0, sd_notify_msg);
|
||||
#else
|
||||
UNUSED(sd_notify_msg);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int redisIsSupervised(int mode) {
|
||||
@@ -4915,17 +4879,27 @@ int redisIsSupervised(int mode) {
|
||||
if (upstart_job) {
|
||||
redisSupervisedUpstart();
|
||||
} else if (notify_socket) {
|
||||
redisSupervisedSystemd();
|
||||
server.supervised_mode = SUPERVISED_SYSTEMD;
|
||||
serverLog(LL_WARNING,
|
||||
"WARNING auto-supervised by systemd - you MUST set appropriate values for TimeoutStartSec and TimeoutStopSec in your service unit.");
|
||||
return redisCommunicateSystemd("STATUS=Redis is loading...\n");
|
||||
}
|
||||
} else if (mode == SUPERVISED_UPSTART) {
|
||||
return redisSupervisedUpstart();
|
||||
} else if (mode == SUPERVISED_SYSTEMD) {
|
||||
return redisSupervisedSystemd();
|
||||
serverLog(LL_WARNING,
|
||||
"WARNING supervised by systemd - you MUST set appropriate values for TimeoutStartSec and TimeoutStopSec in your service unit.");
|
||||
return redisCommunicateSystemd("STATUS=Redis is loading...\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int iAmMaster(void) {
|
||||
return ((!server.cluster_enabled && server.masterhost == NULL) ||
|
||||
(server.cluster_enabled && nodeIsMaster(server.cluster->myself)));
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
struct timeval tv;
|
||||
@@ -5113,6 +5087,14 @@ int main(int argc, char **argv) {
|
||||
serverLog(LL_NOTICE,"Ready to accept connections");
|
||||
if (server.sofd > 0)
|
||||
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
|
||||
if (server.supervised_mode == SUPERVISED_SYSTEMD) {
|
||||
if (!server.masterhost) {
|
||||
redisCommunicateSystemd("STATUS=Ready to accept connections\n");
|
||||
redisCommunicateSystemd("READY=1\n");
|
||||
} else {
|
||||
redisCommunicateSystemd("STATUS=Waiting for MASTER <-> REPLICA sync\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
InitServerLast();
|
||||
sentinelIsRunning();
|
||||
|
||||
+105
-119
@@ -46,10 +46,16 @@
|
||||
#include <pthread.h>
|
||||
#include <syslog.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <lua.h>
|
||||
#include <signal.h>
|
||||
|
||||
#ifdef HAVE_LIBSYSTEMD
|
||||
#include <systemd/sd-daemon.h>
|
||||
#endif
|
||||
|
||||
typedef long long mstime_t; /* millisecond time type. */
|
||||
typedef long long ustime_t; /* microsecond time type. */
|
||||
|
||||
#include "ae.h" /* Event driven programming library */
|
||||
#include "sds.h" /* Dynamic safe strings */
|
||||
@@ -82,18 +88,10 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define C_ERR -1
|
||||
|
||||
/* Static server configuration */
|
||||
#define CONFIG_DEFAULT_DYNAMIC_HZ 1 /* Adapt hz to # of clients.*/
|
||||
#define CONFIG_DEFAULT_HZ 10 /* Time interrupt calls/sec. */
|
||||
#define CONFIG_MIN_HZ 1
|
||||
#define CONFIG_MAX_HZ 500
|
||||
#define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */
|
||||
#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */
|
||||
#define CONFIG_DEFAULT_SERVER_TLS_PORT 0 /* TCP port. */
|
||||
#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */
|
||||
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */
|
||||
#define CONFIG_DEFAULT_DBNUM 16
|
||||
#define CONFIG_DEFAULT_IO_THREADS_NUM 1 /* Single threaded by default */
|
||||
#define CONFIG_DEFAULT_IO_THREADS_DO_READS 0 /* Read + parse from threads? */
|
||||
#define CONFIG_MAX_LINE 1024
|
||||
#define CRON_DBS_PER_CALL 16
|
||||
#define NET_MAX_WRITES_PER_EVENT (1024*64)
|
||||
@@ -101,87 +99,22 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define OBJ_SHARED_INTEGERS 10000
|
||||
#define OBJ_SHARED_BULKHDR_LEN 32
|
||||
#define LOG_MAX_LEN 1024 /* Default maximum length of syslog messages.*/
|
||||
#define AOF_REWRITE_PERC 100
|
||||
#define AOF_REWRITE_MIN_SIZE (64*1024*1024)
|
||||
#define AOF_REWRITE_ITEMS_PER_CMD 64
|
||||
#define AOF_READ_DIFF_INTERVAL_BYTES (1024*10)
|
||||
#define CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN 10000
|
||||
#define CONFIG_DEFAULT_SLOWLOG_MAX_LEN 128
|
||||
#define CONFIG_DEFAULT_MAX_CLIENTS 10000
|
||||
#define CONFIG_AUTHPASS_MAX_LEN 512
|
||||
#define CONFIG_DEFAULT_SLAVE_PRIORITY 100
|
||||
#define CONFIG_DEFAULT_REPL_TIMEOUT 60
|
||||
#define CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD 10
|
||||
#define CONFIG_RUN_ID_SIZE 40
|
||||
#define RDB_EOF_MARK_SIZE 40
|
||||
#define CONFIG_DEFAULT_REPL_BACKLOG_SIZE (1024*1024) /* 1mb */
|
||||
#define CONFIG_DEFAULT_REPL_BACKLOG_TIME_LIMIT (60*60) /* 1 hour */
|
||||
#define CONFIG_REPL_BACKLOG_MIN_SIZE (1024*16) /* 16k */
|
||||
#define CONFIG_BGSAVE_RETRY_DELAY 5 /* Wait a few secs before trying again. */
|
||||
#define CONFIG_DEFAULT_PID_FILE "/var/run/redis.pid"
|
||||
#define CONFIG_DEFAULT_SYSLOG_IDENT "redis"
|
||||
#define CONFIG_DEFAULT_CLUSTER_CONFIG_FILE "nodes.conf"
|
||||
#define CONFIG_DEFAULT_CLUSTER_ANNOUNCE_IP NULL /* Auto detect. */
|
||||
#define CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT 0 /* Use server.port */
|
||||
#define CONFIG_DEFAULT_CLUSTER_ANNOUNCE_BUS_PORT 0 /* Use +10000 offset. */
|
||||
#define CONFIG_DEFAULT_DAEMONIZE 0
|
||||
#define CONFIG_DEFAULT_UNIX_SOCKET_PERM 0
|
||||
#define CONFIG_DEFAULT_TCP_KEEPALIVE 300
|
||||
#define CONFIG_DEFAULT_PROTECTED_MODE 1
|
||||
#define CONFIG_DEFAULT_GOPHER_ENABLED 0
|
||||
#define CONFIG_DEFAULT_LOGFILE ""
|
||||
#define CONFIG_DEFAULT_SYSLOG_ENABLED 0
|
||||
#define CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR 1
|
||||
#define CONFIG_DEFAULT_RDB_COMPRESSION 1
|
||||
#define CONFIG_DEFAULT_RDB_CHECKSUM 1
|
||||
#define CONFIG_DEFAULT_RDB_FILENAME "dump.rdb"
|
||||
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC 0
|
||||
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5
|
||||
#define CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY 0
|
||||
#define CONFIG_DEFAULT_KEY_LOAD_DELAY 0
|
||||
#define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1
|
||||
#define CONFIG_DEFAULT_SLAVE_READ_ONLY 1
|
||||
#define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1
|
||||
#define CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP NULL
|
||||
#define CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT 0
|
||||
#define CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY 0
|
||||
#define CONFIG_DEFAULT_MAXMEMORY 0
|
||||
#define CONFIG_DEFAULT_MAXMEMORY_SAMPLES 5
|
||||
#define CONFIG_DEFAULT_LFU_LOG_FACTOR 10
|
||||
#define CONFIG_DEFAULT_LFU_DECAY_TIME 1
|
||||
#define CONFIG_DEFAULT_AOF_FILENAME "appendonly.aof"
|
||||
#define CONFIG_DEFAULT_AOF_NO_FSYNC_ON_REWRITE 0
|
||||
#define CONFIG_DEFAULT_AOF_LOAD_TRUNCATED 1
|
||||
#define CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE 1
|
||||
#define CONFIG_DEFAULT_ACTIVE_REHASHING 1
|
||||
#define CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1
|
||||
#define CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC 1
|
||||
#define CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE 0
|
||||
#define CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG 10
|
||||
#define CONFIG_DEFAULT_ACL_FILENAME ""
|
||||
#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */
|
||||
#define NET_PEER_ID_LEN (NET_IP_STR_LEN+32) /* Must be enough for ip:port */
|
||||
#define CONFIG_BINDADDR_MAX 16
|
||||
#define CONFIG_MIN_RESERVED_FDS 32
|
||||
#define CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD 0
|
||||
#define CONFIG_DEFAULT_SLAVE_LAZY_FLUSH 0
|
||||
#define CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION 0
|
||||
#define CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE 0
|
||||
#define CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL 0
|
||||
#define CONFIG_DEFAULT_ALWAYS_SHOW_LOGO 0
|
||||
#define CONFIG_DEFAULT_ACTIVE_DEFRAG 0
|
||||
#define CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER 10 /* don't defrag when fragmentation is below 10% */
|
||||
#define CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER 100 /* maximum defrag force at 100% fragmentation */
|
||||
#define CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES (100<<20) /* don't defrag if frag overhead is below 100mb */
|
||||
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MIN 5 /* 5% CPU min (at lower threshold) */
|
||||
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MAX 75 /* 75% CPU max (at upper threshold) */
|
||||
#define CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS 1000 /* keys with more than 1000 fields will be processed separately */
|
||||
#define CONFIG_DEFAULT_PROTO_MAX_BULK_LEN (512ll*1024*1024) /* Bulk request max size */
|
||||
#define CONFIG_DEFAULT_TRACKING_TABLE_MAX_FILL 10 /* 10% tracking table max fill. */
|
||||
|
||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
|
||||
#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* CPU max % for keys collection */
|
||||
#define ACTIVE_EXPIRE_CYCLE_SLOW 0
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST 1
|
||||
|
||||
@@ -311,9 +244,15 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
we return single threaded that the
|
||||
client has already pending commands
|
||||
to be executed. */
|
||||
#define CLIENT_TRACKING (1<<31) /* Client enabled keys tracking in order to
|
||||
#define CLIENT_TRACKING (1ULL<<31) /* Client enabled keys tracking in order to
|
||||
perform client side caching. */
|
||||
#define CLIENT_TRACKING_BROKEN_REDIR (1ULL<<32) /* Target client is invalid. */
|
||||
#define CLIENT_TRACKING_BCAST (1ULL<<33) /* Tracking in BCAST mode. */
|
||||
#define CLIENT_TRACKING_OPTIN (1ULL<<34) /* Tracking in opt-in mode. */
|
||||
#define CLIENT_TRACKING_OPTOUT (1ULL<<35) /* Tracking in opt-out mode. */
|
||||
#define CLIENT_TRACKING_CACHING (1ULL<<36) /* CACHING yes/no was given,
|
||||
depending on optin/optout mode. */
|
||||
#define CLIENT_IN_TO_TABLE (1ULL<<37) /* This client is in the timeout table. */
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if CLIENT_BLOCKED flag is set. */
|
||||
@@ -335,6 +274,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CLIENT_TYPE_SLAVE 1 /* Slaves. */
|
||||
#define CLIENT_TYPE_PUBSUB 2 /* Clients subscribed to PubSub channels. */
|
||||
#define CLIENT_TYPE_MASTER 3 /* Master. */
|
||||
#define CLIENT_TYPE_COUNT 4 /* Total number of client types. */
|
||||
#define CLIENT_TYPE_OBUF_COUNT 3 /* Number of clients to expose to output
|
||||
buffer configuration. Just the first
|
||||
three: normal, slave, pubsub. */
|
||||
@@ -392,7 +332,6 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define LL_NOTICE 2
|
||||
#define LL_WARNING 3
|
||||
#define LL_RAW (1<<10) /* Modifier to log without timestamp */
|
||||
#define CONFIG_DEFAULT_VERBOSITY LL_NOTICE
|
||||
|
||||
/* Supervision options */
|
||||
#define SUPERVISED_NONE 0
|
||||
@@ -403,36 +342,18 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
/* Anti-warning macro... */
|
||||
#define UNUSED(V) ((void) V)
|
||||
|
||||
#define ZSKIPLIST_MAXLEVEL 64 /* Should be enough for 2^64 elements */
|
||||
#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^64 elements */
|
||||
#define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */
|
||||
|
||||
/* Append only defines */
|
||||
#define AOF_FSYNC_NO 0
|
||||
#define AOF_FSYNC_ALWAYS 1
|
||||
#define AOF_FSYNC_EVERYSEC 2
|
||||
#define CONFIG_DEFAULT_AOF_FSYNC AOF_FSYNC_EVERYSEC
|
||||
|
||||
/* Replication diskless load defines */
|
||||
#define REPL_DISKLESS_LOAD_DISABLED 0
|
||||
#define REPL_DISKLESS_LOAD_WHEN_DB_EMPTY 1
|
||||
#define REPL_DISKLESS_LOAD_SWAPDB 2
|
||||
#define CONFIG_DEFAULT_REPL_DISKLESS_LOAD REPL_DISKLESS_LOAD_DISABLED
|
||||
|
||||
/* Zipped structures related defaults */
|
||||
#define OBJ_HASH_MAX_ZIPLIST_ENTRIES 512
|
||||
#define OBJ_HASH_MAX_ZIPLIST_VALUE 64
|
||||
#define OBJ_SET_MAX_INTSET_ENTRIES 512
|
||||
#define OBJ_ZSET_MAX_ZIPLIST_ENTRIES 128
|
||||
#define OBJ_ZSET_MAX_ZIPLIST_VALUE 64
|
||||
#define OBJ_STREAM_NODE_MAX_BYTES 4096
|
||||
#define OBJ_STREAM_NODE_MAX_ENTRIES 100
|
||||
|
||||
/* List defaults */
|
||||
#define OBJ_LIST_MAX_ZIPLIST_SIZE -2
|
||||
#define OBJ_LIST_COMPRESS_DEPTH 0
|
||||
|
||||
/* HyperLogLog defines */
|
||||
#define CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES 3000
|
||||
|
||||
/* Sets operations codes */
|
||||
#define SET_OP_UNION 0
|
||||
@@ -457,11 +378,6 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define MAXMEMORY_ALLKEYS_RANDOM ((6<<8)|MAXMEMORY_FLAG_ALLKEYS)
|
||||
#define MAXMEMORY_NO_EVICTION (7<<8)
|
||||
|
||||
#define CONFIG_DEFAULT_MAXMEMORY_POLICY MAXMEMORY_NO_EVICTION
|
||||
|
||||
/* Scripting */
|
||||
#define LUA_SCRIPT_TIME_LIMIT 5000 /* milliseconds */
|
||||
|
||||
/* Units */
|
||||
#define UNIT_SECONDS 0
|
||||
#define UNIT_MILLISECONDS 1
|
||||
@@ -480,6 +396,8 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CMD_CALL_PROPAGATE_REPL (1<<3)
|
||||
#define CMD_CALL_PROPAGATE (CMD_CALL_PROPAGATE_AOF|CMD_CALL_PROPAGATE_REPL)
|
||||
#define CMD_CALL_FULL (CMD_CALL_SLOWLOG | CMD_CALL_STATS | CMD_CALL_PROPAGATE)
|
||||
#define CMD_CALL_NOWRAP (1<<4) /* Don't wrap also propagate array into
|
||||
MULTI/EXEC: the caller will handle it. */
|
||||
|
||||
/* Command propagation flags, see propagate() function */
|
||||
#define PROPAGATE_NONE 0
|
||||
@@ -504,8 +422,8 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define NOTIFY_EXPIRED (1<<8) /* x */
|
||||
#define NOTIFY_EVICTED (1<<9) /* e */
|
||||
#define NOTIFY_STREAM (1<<10) /* t */
|
||||
#define NOTIFY_KEY_MISS (1<<11) /* m */
|
||||
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_KEY_MISS) /* A flag */
|
||||
#define NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from NOTIFY_ALL on purpose) */
|
||||
#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM) /* A flag */
|
||||
|
||||
/* Get the first bind addr or NULL */
|
||||
#define NET_FIRST_BIND_ADDR (server.bindaddr_count ? server.bindaddr[0] : NULL)
|
||||
@@ -576,6 +494,11 @@ typedef void (*moduleTypeDigestFunc)(struct RedisModuleDigest *digest, void *val
|
||||
typedef size_t (*moduleTypeMemUsageFunc)(const void *value);
|
||||
typedef void (*moduleTypeFreeFunc)(void *value);
|
||||
|
||||
/* A callback that is called when the client authentication changes. This
|
||||
* needs to be exposed since you can't cast a function pointer to (void *) */
|
||||
typedef void (*RedisModuleUserChangedFunc) (uint64_t client_id, void *privdata);
|
||||
|
||||
|
||||
/* The module type, which is referenced in each value of a given type, defines
|
||||
* the methods and links to the module exporting the type. */
|
||||
typedef struct RedisModuleType {
|
||||
@@ -674,7 +597,9 @@ typedef struct RedisModuleDigest {
|
||||
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
|
||||
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
|
||||
|
||||
#define OBJ_SHARED_REFCOUNT INT_MAX
|
||||
#define OBJ_SHARED_REFCOUNT INT_MAX /* Global object never destroyed. */
|
||||
#define OBJ_STATIC_REFCOUNT (INT_MAX-1) /* Object allocated in the stack. */
|
||||
#define OBJ_FIRST_SPECIAL_REFCOUNT OBJ_STATIC_REFCOUNT
|
||||
typedef struct redisObject {
|
||||
unsigned type:4;
|
||||
unsigned encoding:4;
|
||||
@@ -695,7 +620,7 @@ char *getObjectTypeName(robj*);
|
||||
* we'll update it when the structure is changed, to avoid bugs like
|
||||
* bug #85 introduced exactly in this way. */
|
||||
#define initStaticStringObject(_var,_ptr) do { \
|
||||
_var.refcount = 1; \
|
||||
_var.refcount = OBJ_STATIC_REFCOUNT; \
|
||||
_var.type = OBJ_STRING; \
|
||||
_var.encoding = OBJ_ENCODING_RAW; \
|
||||
_var.ptr = _ptr; \
|
||||
@@ -721,6 +646,7 @@ typedef struct redisDb {
|
||||
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
|
||||
int id; /* Database ID */
|
||||
long long avg_ttl; /* Average TTL, just for stats */
|
||||
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
|
||||
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
|
||||
} redisDb;
|
||||
|
||||
@@ -893,13 +819,30 @@ typedef struct client {
|
||||
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
|
||||
sds peerid; /* Cached peer ID. */
|
||||
listNode *client_list_node; /* list node in client list */
|
||||
void *auth_ctx; /* Opaque structure to track module auth */
|
||||
RedisModuleUserChangedFunc auth_callback; /* Module callback to execute
|
||||
* when the authenticated user
|
||||
* changes. */
|
||||
void *auth_callback_privdata; /* Private data that is passed when the auth
|
||||
* changed callback is executed. Opaque for
|
||||
* Redis Core. */
|
||||
void *auth_module; /* The module that owns the callback, which is used
|
||||
* to disconnect the client if the module is
|
||||
* unloaded for cleanup. Opaque for Redis Core.*/
|
||||
|
||||
/* If this client is in tracking mode and this field is non zero,
|
||||
* invalidation messages for keys fetched by this client will be send to
|
||||
* the specified client ID. */
|
||||
uint64_t client_tracking_redirection;
|
||||
|
||||
rax *client_tracking_prefixes; /* A dictionary of prefixes we are already
|
||||
subscribed to in BCAST mode, in the
|
||||
context of client side caching. */
|
||||
/* In clientsCronTrackClientsMemUsage() we track the memory usage of
|
||||
* each client and add it to the sum of all the clients of a given type,
|
||||
* however we need to remember what was the old contribution of each
|
||||
* client, and in which categoty the client was, in order to remove it
|
||||
* before adding it the new value. */
|
||||
uint64_t client_cron_last_memory_usage;
|
||||
int client_cron_last_memory_type;
|
||||
/* Response buffer */
|
||||
int bufpos;
|
||||
char buf[PROTO_REPLY_CHUNK_BYTES];
|
||||
@@ -925,6 +868,7 @@ struct sharedObjectsStruct {
|
||||
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink,
|
||||
*rpop, *lpop, *lpush, *rpoplpush, *zpopmin, *zpopmax, *emptyscan,
|
||||
*multi, *exec,
|
||||
*select[PROTO_SHARED_SELECT_CMDS],
|
||||
*integers[OBJ_SHARED_INTEGERS],
|
||||
*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
|
||||
@@ -1135,7 +1079,9 @@ struct redisServer {
|
||||
list *clients_pending_write; /* There is to write or install handler. */
|
||||
list *clients_pending_read; /* Client has pending read socket buffers. */
|
||||
list *slaves, *monitors; /* List of slaves and MONITORs */
|
||||
client *current_client; /* Current client, only used on crash report */
|
||||
client *current_client; /* Current client executing the command. */
|
||||
rax *clients_timeout_table; /* Radix tree for blocked clients timeouts. */
|
||||
long fixed_time_expire; /* If > 0, expire keys against server.mstime. */
|
||||
rax *clients_index; /* Active clients dictionary by client ID. */
|
||||
int clients_paused; /* True if clients are currently paused */
|
||||
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
|
||||
@@ -1159,7 +1105,7 @@ struct redisServer {
|
||||
*lpopCommand, *rpopCommand, *zpopminCommand,
|
||||
*zpopmaxCommand, *sremCommand, *execCommand,
|
||||
*expireCommand, *pexpireCommand, *xclaimCommand,
|
||||
*xgroupCommand;
|
||||
*xgroupCommand, *rpoplpushCommand;
|
||||
/* Fields used only for stats */
|
||||
time_t stat_starttime; /* Server start time */
|
||||
long long stat_numcommands; /* Number of processed commands */
|
||||
@@ -1167,6 +1113,7 @@ struct redisServer {
|
||||
long long stat_expiredkeys; /* Number of expired keys */
|
||||
double stat_expired_stale_perc; /* Percentage of keys probably expired */
|
||||
long long stat_expired_time_cap_reached_count; /* Early expire cylce stops.*/
|
||||
long long stat_expire_cycle_time_used; /* Cumulative microseconds used. */
|
||||
long long stat_evictedkeys; /* Number of evicted keys (maxmemory) */
|
||||
long long stat_keyspace_hits; /* Number of successful lookups of keys */
|
||||
long long stat_keyspace_misses; /* Number of failed lookups of keys */
|
||||
@@ -1192,6 +1139,8 @@ struct redisServer {
|
||||
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
|
||||
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
|
||||
size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
|
||||
uint64_t stat_clients_type_memory[CLIENT_TYPE_COUNT];/* Mem usage by type */
|
||||
long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */
|
||||
/* The following two are used to track instantaneous metrics, like
|
||||
* number of operations per second, network traffic. */
|
||||
struct {
|
||||
@@ -1205,6 +1154,7 @@ struct redisServer {
|
||||
int maxidletime; /* Client timeout in seconds */
|
||||
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
||||
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
||||
int active_expire_effort; /* From 1 (default) to 10, active effort. */
|
||||
int active_defrag_enabled;
|
||||
int jemalloc_bg_thread; /* Enable jemalloc background thread */
|
||||
size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */
|
||||
@@ -1268,6 +1218,8 @@ struct redisServer {
|
||||
char *rdb_filename; /* Name of RDB file */
|
||||
int rdb_compression; /* Use compression in RDB? */
|
||||
int rdb_checksum; /* Use RDB checksum? */
|
||||
int rdb_del_sync_files; /* Remove RDB files used only for SYNC if
|
||||
the instance does not use persistence. */
|
||||
time_t lastsave; /* Unix time of last successful save */
|
||||
time_t lastbgsave_try; /* Unix time of last attempted bgsave */
|
||||
time_t rdb_save_time_last; /* Time used by last RDB save run. */
|
||||
@@ -1305,6 +1257,7 @@ struct redisServer {
|
||||
char replid[CONFIG_RUN_ID_SIZE+1]; /* My current replication ID. */
|
||||
char replid2[CONFIG_RUN_ID_SIZE+1]; /* replid inherited from master*/
|
||||
long long master_repl_offset; /* My current replication offset */
|
||||
long long master_repl_meaningful_offset; /* Offset minus latest PINGs. */
|
||||
long long second_replid_offset; /* Accept offsets up to this for replid2. */
|
||||
int slaveseldb; /* Last SELECTed DB in replication output */
|
||||
int repl_ping_slave_period; /* Master pings the slave every N seconds */
|
||||
@@ -1379,7 +1332,7 @@ struct redisServer {
|
||||
list *ready_keys; /* List of readyList structures for BLPOP & co */
|
||||
/* Client side caching. */
|
||||
unsigned int tracking_clients; /* # of clients with tracking enabled.*/
|
||||
int tracking_table_max_fill; /* Max fill percentage. */
|
||||
size_t tracking_table_max_keys; /* Max number of keys in tracking table. */
|
||||
/* Sort parameters - qsort_r() is only available under BSD so we
|
||||
* have to take this state global, in order to pass it to sortCompare() */
|
||||
int sort_desc;
|
||||
@@ -1394,7 +1347,7 @@ struct redisServer {
|
||||
size_t zset_max_ziplist_value;
|
||||
size_t hll_sparse_max_bytes;
|
||||
size_t stream_node_max_bytes;
|
||||
int64_t stream_node_max_entries;
|
||||
long long stream_node_max_entries;
|
||||
/* List parameters */
|
||||
int list_max_ziplist_size;
|
||||
int list_compress_depth;
|
||||
@@ -1402,10 +1355,12 @@ struct redisServer {
|
||||
_Atomic time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
time_t timezone; /* Cached timezone. As set by tzset(). */
|
||||
int daylight_active; /* Currently in daylight saving time. */
|
||||
long long mstime; /* 'unixtime' with milliseconds resolution. */
|
||||
mstime_t mstime; /* 'unixtime' in milliseconds. */
|
||||
ustime_t ustime; /* 'unixtime' in microseconds. */
|
||||
/* Pubsub */
|
||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||
list *pubsub_patterns; /* A list of pubsub_patterns */
|
||||
dict *pubsub_patterns_dict; /* A dict of pubsub_patterns */
|
||||
int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
|
||||
xor of NOTIFY_... flags. */
|
||||
/* Cluster */
|
||||
@@ -1426,6 +1381,8 @@ struct redisServer {
|
||||
to set in order to suppress certain
|
||||
native Redis Cluster features. Check the
|
||||
REDISMODULE_CLUSTER_FLAG_*. */
|
||||
int cluster_allow_reads_when_down; /* Are reads allowed when the cluster
|
||||
is down? */
|
||||
/* Scripting */
|
||||
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
|
||||
client *lua_client; /* The "fake client" to query Redis from Lua */
|
||||
@@ -1446,15 +1403,21 @@ struct redisServer {
|
||||
execution. */
|
||||
int lua_kill; /* Kill the script if true. */
|
||||
int lua_always_replicate_commands; /* Default replication type. */
|
||||
int lua_oom; /* OOM detected when script start? */
|
||||
/* Lazy free */
|
||||
int lazyfree_lazy_eviction;
|
||||
int lazyfree_lazy_expire;
|
||||
int lazyfree_lazy_server_del;
|
||||
int lazyfree_lazy_user_del;
|
||||
/* Latency monitor */
|
||||
long long latency_monitor_threshold;
|
||||
dict *latency_events;
|
||||
/* ACLs */
|
||||
char *acl_filename; /* ACL Users file. NULL if not configured. */
|
||||
unsigned long acllog_max_len; /* Maximum length of the ACL LOG list. */
|
||||
sds requirepass; /* Remember the cleartext password set with the
|
||||
old "requirepass" directive for backward
|
||||
compatibility with Redis <= 5. */
|
||||
/* Assert & bug reporting */
|
||||
const char *assert_failed;
|
||||
const char *assert_file;
|
||||
@@ -1602,7 +1565,7 @@ void ModuleForkDoneHandler(int exitcode, int bysignal);
|
||||
int TerminateModuleForkChild(int child_pid, int wait);
|
||||
ssize_t rdbSaveModulesAux(rio *rdb, int when);
|
||||
int moduleAllDatatypesHandleErrors();
|
||||
sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections);
|
||||
sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int sections);
|
||||
void moduleFireServerEvent(uint64_t eid, int subid, void *data);
|
||||
void processModuleLoadingProgressEvent(int is_aof);
|
||||
int moduleTryServeClientBlockedOnKey(client *c, robj *key);
|
||||
@@ -1619,6 +1582,7 @@ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
void exitFromChild(int retcode);
|
||||
size_t redisPopcount(void *s, long count);
|
||||
void redisSetProcTitle(char *title);
|
||||
int redisCommunicateSystemd(const char *sd_notify_msg);
|
||||
|
||||
/* networking.c -- Networking and Client related operations */
|
||||
client *createClient(connection *conn);
|
||||
@@ -1716,13 +1680,15 @@ void addReplyStatusFormat(client *c, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
/* Client side caching (tracking mode) */
|
||||
void enableTracking(client *c, uint64_t redirect_to);
|
||||
void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **prefix, size_t numprefix);
|
||||
void disableTracking(client *c);
|
||||
void trackingRememberKeys(client *c);
|
||||
void trackingInvalidateKey(robj *keyobj);
|
||||
void trackingInvalidateKeysOnFlush(int dbid);
|
||||
void trackingLimitUsedSlots(void);
|
||||
unsigned long long trackingGetUsedSlots(void);
|
||||
uint64_t trackingGetTotalItems(void);
|
||||
uint64_t trackingGetTotalKeys(void);
|
||||
void trackingBroadcastInvalidationMessages(void);
|
||||
|
||||
/* List data type */
|
||||
void listTypeTryConversion(robj *subject, robj *value);
|
||||
@@ -1750,6 +1716,7 @@ void touchWatchedKeysOnFlush(int dbid);
|
||||
void discardTransaction(client *c);
|
||||
void flagTransaction(client *c);
|
||||
void execCommandPropagateMulti(client *c);
|
||||
void execCommandPropagateExec(client *c);
|
||||
|
||||
/* Redis object implementation */
|
||||
void decrRefCount(robj *o);
|
||||
@@ -1844,6 +1811,7 @@ void loadingProgress(off_t pos);
|
||||
void stopLoading(int success);
|
||||
void startSaving(int rdbflags);
|
||||
void stopSaving(int success);
|
||||
int allPersistenceDisabled(void);
|
||||
|
||||
#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
|
||||
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
|
||||
@@ -1888,11 +1856,12 @@ void ACLInit(void);
|
||||
#define ACL_OK 0
|
||||
#define ACL_DENIED_CMD 1
|
||||
#define ACL_DENIED_KEY 2
|
||||
#define ACL_DENIED_AUTH 3 /* Only used for ACL LOG entries. */
|
||||
int ACLCheckUserCredentials(robj *username, robj *password);
|
||||
int ACLAuthenticateUser(client *c, robj *username, robj *password);
|
||||
unsigned long ACLGetCommandID(const char *cmdname);
|
||||
user *ACLGetUserByName(const char *name, size_t namelen);
|
||||
int ACLCheckCommandPerm(client *c);
|
||||
int ACLCheckCommandPerm(client *c, int *keyidxptr);
|
||||
int ACLSetUser(user *u, const char *op, ssize_t oplen);
|
||||
sds ACLDefaultUserFirstPassword(void);
|
||||
uint64_t ACLGetCommandCategoryFlagByName(const char *name);
|
||||
@@ -1904,6 +1873,7 @@ void ACLLoadUsersAtStartup(void);
|
||||
void addReplyCommandCategories(client *c, struct redisCommand *cmd);
|
||||
user *ACLCreateUnlinkedUser();
|
||||
void ACLFreeUserAndKillClients(user *u);
|
||||
void addACLLogEntry(client *c, int reason, int keypos, sds username);
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
@@ -2004,7 +1974,7 @@ void populateCommandTable(void);
|
||||
void resetCommandTableStats(void);
|
||||
void adjustOpenFilesLimit(void);
|
||||
void closeListeningSockets(int unlink_unix_socket);
|
||||
void updateCachedTime(void);
|
||||
void updateCachedTime(int update_daylight_info);
|
||||
void resetServerStats(void);
|
||||
void activeDefragCycle(void);
|
||||
unsigned int getLRUClock(void);
|
||||
@@ -2077,6 +2047,7 @@ void resetServerSaveParams(void);
|
||||
struct rewriteConfigState; /* Forward declaration to export API. */
|
||||
void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *option, sds line, int force);
|
||||
int rewriteConfig(char *path);
|
||||
void initConfigValues();
|
||||
|
||||
/* db.c -- Keyspace access API */
|
||||
int removeExpire(redisDb *db, robj *key);
|
||||
@@ -2094,11 +2065,13 @@ robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags);
|
||||
robj *objectCommandLookup(client *c, robj *key);
|
||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
||||
int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
long long lru_clock);
|
||||
long long lru_clock, int lru_multiplier);
|
||||
#define LOOKUP_NONE 0
|
||||
#define LOOKUP_NOTOUCH (1<<0)
|
||||
void dbAdd(redisDb *db, robj *key, robj *val);
|
||||
int dbAddRDBLoad(redisDb *db, sds key, robj *val);
|
||||
void dbOverwrite(redisDb *db, robj *key, robj *val);
|
||||
void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl, int signal);
|
||||
void setKey(redisDb *db, robj *key, robj *val);
|
||||
int dbExists(redisDb *db, robj *key);
|
||||
robj *dbRandomKey(redisDb *db);
|
||||
@@ -2108,6 +2081,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
|
||||
|
||||
#define EMPTYDB_NO_FLAGS 0 /* No flags. */
|
||||
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
|
||||
#define EMPTYDB_BACKUP (1<<2) /* DB array is a backup for REPL_DISKLESS_LOAD_SWAPDB. */
|
||||
long long emptyDb(int dbnum, int flags, void(callback)(void*));
|
||||
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*));
|
||||
void flushAllDataAndResetRDB(int flags);
|
||||
@@ -2122,8 +2096,8 @@ unsigned int delKeysInSlot(unsigned int hashslot);
|
||||
int verifyClusterConfigWithData(void);
|
||||
void scanGenericCommand(client *c, robj *o, unsigned long cursor);
|
||||
int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor);
|
||||
void slotToKeyAdd(robj *key);
|
||||
void slotToKeyDel(robj *key);
|
||||
void slotToKeyAdd(sds key);
|
||||
void slotToKeyDel(sds key);
|
||||
void slotToKeyFlush(void);
|
||||
int dbAsyncDelete(redisDb *db, robj *key);
|
||||
void emptyDbAsync(redisDb *db);
|
||||
@@ -2140,6 +2114,8 @@ int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
|
||||
/* Cluster */
|
||||
void clusterInit(void);
|
||||
@@ -2182,6 +2158,12 @@ void handleClientsBlockedOnKeys(void);
|
||||
void signalKeyAsReady(redisDb *db, robj *key);
|
||||
void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, robj *target, streamID *ids);
|
||||
|
||||
/* timeout.c -- Blocked clients timeout and connections timeout. */
|
||||
void addClientToTimeoutTable(client *c);
|
||||
void removeClientFromTimeoutTable(client *c);
|
||||
void handleBlockedClientsTimeout(void);
|
||||
int clientsCronHandleTimeout(client *c, mstime_t now_ms);
|
||||
|
||||
/* expire.c -- Handling of expired keys */
|
||||
void activeExpireCycle(int type);
|
||||
void expireSlaveKeys(void);
|
||||
@@ -2223,6 +2205,7 @@ void existsCommand(client *c);
|
||||
void setbitCommand(client *c);
|
||||
void getbitCommand(client *c);
|
||||
void bitfieldCommand(client *c);
|
||||
void bitfieldroCommand(client *c);
|
||||
void setrangeCommand(client *c);
|
||||
void getrangeCommand(client *c);
|
||||
void incrCommand(client *c);
|
||||
@@ -2404,6 +2387,7 @@ void xdelCommand(client *c);
|
||||
void xtrimCommand(client *c);
|
||||
void lolwutCommand(client *c);
|
||||
void aclCommand(client *c);
|
||||
void lcsCommand(client *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
@@ -2419,7 +2403,7 @@ void _serverPanic(const char *file, int line, const char *msg, ...);
|
||||
void bugReportStart(void);
|
||||
void serverLogObjectDebugInfo(const robj *o);
|
||||
void sigsegvHandler(int sig, siginfo_t *info, void *secret);
|
||||
sds genRedisInfoString(char *section);
|
||||
sds genRedisInfoString(const char *section);
|
||||
sds genModulesInfoString(sds info);
|
||||
void enableWatchdog(int period);
|
||||
void disableWatchdog(void);
|
||||
@@ -2439,4 +2423,6 @@ int tlsConfigure(redisTLSContextConfig *ctx_config);
|
||||
#define redisDebugMark() \
|
||||
printf("-- MARK %s:%d --\n", __FILE__, __LINE__)
|
||||
|
||||
int iAmMaster(void);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __SLOWLOG_H__
|
||||
#define __SLOWLOG_H__
|
||||
|
||||
#define SLOWLOG_ENTRY_MAX_ARGC 32
|
||||
#define SLOWLOG_ENTRY_MAX_STRING 128
|
||||
|
||||
@@ -47,3 +50,5 @@ void slowlogPushEntryIfNeeded(client *c, robj **argv, int argc, long long durati
|
||||
|
||||
/* Exported commands */
|
||||
void slowlogCommand(client *c);
|
||||
|
||||
#endif /* __SLOWLOG_H__ */
|
||||
|
||||
@@ -98,6 +98,7 @@ struct client;
|
||||
|
||||
stream *streamNew(void);
|
||||
void freeStream(stream *s);
|
||||
unsigned long streamLength(const robj *subject);
|
||||
size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev, streamCG *group, streamConsumer *consumer, int flags, streamPropInfo *spi);
|
||||
void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev);
|
||||
int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields);
|
||||
@@ -110,5 +111,6 @@ streamNACK *streamCreateNACK(streamConsumer *consumer);
|
||||
void streamDecodeID(void *buf, streamID *id);
|
||||
int streamCompareID(streamID *a, streamID *b);
|
||||
void streamFreeNACK(streamNACK *na);
|
||||
void streamIncrID(streamID *id);
|
||||
|
||||
#endif
|
||||
|
||||
+5
-12
@@ -653,20 +653,13 @@ int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb
|
||||
if (!(dstobj &&
|
||||
checkType(receiver,dstobj,OBJ_LIST)))
|
||||
{
|
||||
/* Propagate the RPOP operation. */
|
||||
argv[0] = shared.rpop;
|
||||
argv[1] = key;
|
||||
propagate(server.rpopCommand,
|
||||
db->id,argv,2,
|
||||
PROPAGATE_AOF|
|
||||
PROPAGATE_REPL);
|
||||
rpoplpushHandlePush(receiver,dstkey,dstobj,
|
||||
value);
|
||||
/* Propagate the LPUSH operation. */
|
||||
argv[0] = shared.lpush;
|
||||
argv[1] = dstkey;
|
||||
argv[2] = value;
|
||||
propagate(server.lpushCommand,
|
||||
/* Propagate the RPOPLPUSH operation. */
|
||||
argv[0] = shared.rpoplpush;
|
||||
argv[1] = key;
|
||||
argv[2] = dstkey;
|
||||
propagate(server.rpoplpushCommand,
|
||||
db->id,argv,3,
|
||||
PROPAGATE_AOF|
|
||||
PROPAGATE_REPL);
|
||||
|
||||
+1
-1
@@ -415,7 +415,7 @@ void spopWithCountCommand(client *c) {
|
||||
|
||||
/* Make sure a key with the name inputted exists, and that it's type is
|
||||
* indeed a set. Otherwise, return nil */
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
|
||||
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.emptyset[c->resp]))
|
||||
== NULL || checkType(c,set,OBJ_SET)) return;
|
||||
|
||||
/* If count is zero, serve an empty set ASAP to avoid special
|
||||
|
||||
+85
-42
@@ -67,6 +67,27 @@ void freeStream(stream *s) {
|
||||
zfree(s);
|
||||
}
|
||||
|
||||
/* Return the length of a stream. */
|
||||
unsigned long streamLength(const robj *subject) {
|
||||
stream *s = subject->ptr;
|
||||
return s->length;
|
||||
}
|
||||
|
||||
/* Set 'id' to be its successor streamID */
|
||||
void streamIncrID(streamID *id) {
|
||||
if (id->seq == UINT64_MAX) {
|
||||
if (id->ms == UINT64_MAX) {
|
||||
/* Special case where 'id' is the last possible streamID... */
|
||||
id->ms = id->seq = 0;
|
||||
} else {
|
||||
id->ms++;
|
||||
id->seq = 0;
|
||||
}
|
||||
} else {
|
||||
id->seq++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate the next stream item ID given the previous one. If the current
|
||||
* milliseconds Unix time is greater than the previous one, just use this
|
||||
* as time part and start with sequence part of zero. Otherwise we use the
|
||||
@@ -77,8 +98,8 @@ void streamNextID(streamID *last_id, streamID *new_id) {
|
||||
new_id->ms = ms;
|
||||
new_id->seq = 0;
|
||||
} else {
|
||||
new_id->ms = last_id->ms;
|
||||
new_id->seq = last_id->seq+1;
|
||||
*new_id = *last_id;
|
||||
streamIncrID(new_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,6 +797,16 @@ int streamDeleteItem(stream *s, streamID *id) {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/* Get the last valid (non-tombstone) streamID of 's'. */
|
||||
void streamLastValidID(stream *s, streamID *maxid)
|
||||
{
|
||||
streamIterator si;
|
||||
streamIteratorStart(&si,s,NULL,NULL,1);
|
||||
int64_t numfields;
|
||||
streamIteratorGetID(&si,maxid,&numfields);
|
||||
streamIteratorStop(&si);
|
||||
}
|
||||
|
||||
/* Emit a reply in the client output buffer by formatting a Stream ID
|
||||
* in the standard <ms>-<seq> format, using the simple string protocol
|
||||
* of REPL. */
|
||||
@@ -817,7 +848,7 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
|
||||
argv[11] = createStringObject("JUSTID",6);
|
||||
argv[12] = createStringObject("LASTID",6);
|
||||
argv[13] = createObjectFromStreamID(&group->last_id);
|
||||
propagate(server.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
alsoPropagate(server.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[3]);
|
||||
decrRefCount(argv[4]);
|
||||
@@ -844,7 +875,7 @@ void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupna
|
||||
argv[2] = key;
|
||||
argv[3] = groupname;
|
||||
argv[4] = createObjectFromStreamID(&group->last_id);
|
||||
propagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
alsoPropagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
decrRefCount(argv[4]);
|
||||
@@ -904,7 +935,6 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
streamIterator si;
|
||||
int64_t numfields;
|
||||
streamID id;
|
||||
int propagate_last_id = 0;
|
||||
|
||||
/* If the client is asking for some history, we serve it using a
|
||||
* different function, so that we return entries *solely* from its
|
||||
@@ -920,6 +950,8 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
arraylen_ptr = addReplyDeferredLen(c);
|
||||
streamIteratorStart(&si,s,start,end,rev);
|
||||
while(streamIteratorGetID(&si,&id,&numfields)) {
|
||||
int propagate_last_id = 0;
|
||||
|
||||
/* Update the group last_id if needed. */
|
||||
if (group && streamCompareID(&id,&group->last_id) > 0) {
|
||||
group->last_id = id;
|
||||
@@ -1037,9 +1069,7 @@ size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start
|
||||
* by the user by other means. In that case we signal it emitting
|
||||
* the ID but then a NULL entry for the fields. */
|
||||
addReplyArrayLen(c,2);
|
||||
streamID id;
|
||||
streamDecodeID(ri.key,&id);
|
||||
addReplyStreamID(c,&id);
|
||||
addReplyStreamID(c,&thisid);
|
||||
addReplyNullArray(c);
|
||||
} else {
|
||||
streamNACK *nack = ri.data;
|
||||
@@ -1073,26 +1103,6 @@ robj *streamTypeLookupWriteOrCreate(client *c, robj *key) {
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Helper function to convert a string to an unsigned long long value.
|
||||
* The function attempts to use the faster string2ll() function inside
|
||||
* Redis: if it fails, strtoull() is used instead. The function returns
|
||||
* 1 if the conversion happened successfully or 0 if the number is
|
||||
* invalid or out of range. */
|
||||
int string2ull(const char *s, unsigned long long *value) {
|
||||
long long ll;
|
||||
if (string2ll(s,strlen(s),&ll)) {
|
||||
if (ll < 0) return 0; /* Negative values are out of range. */
|
||||
*value = ll;
|
||||
return 1;
|
||||
}
|
||||
errno = 0;
|
||||
char *endptr = NULL;
|
||||
*value = strtoull(s,&endptr,10);
|
||||
if (errno == EINVAL || errno == ERANGE || !(*s != '\0' && *endptr == '\0'))
|
||||
return 0; /* strtoull() failed. */
|
||||
return 1; /* Conversion done! */
|
||||
}
|
||||
|
||||
/* Parse a stream ID in the format given by clients to Redis, that is
|
||||
* <ms>-<seq>, and converts it into a streamID structure. If
|
||||
* the specified ID is invalid C_ERR is returned and an error is reported
|
||||
@@ -1220,12 +1230,27 @@ void xaddCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Return ASAP if minimal ID (0-0) was given so we avoid possibly creating
|
||||
* a new stream and have streamAppendItem fail, leaving an empty key in the
|
||||
* database. */
|
||||
if (id_given && id.ms == 0 && id.seq == 0) {
|
||||
addReplyError(c,"The ID specified in XADD must be greater than 0-0");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Lookup the stream at key. */
|
||||
robj *o;
|
||||
stream *s;
|
||||
if ((o = streamTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
s = o->ptr;
|
||||
|
||||
/* Return ASAP if the stream has reached the last possible ID */
|
||||
if (s->last_id.ms == UINT64_MAX && s->last_id.seq == UINT64_MAX) {
|
||||
addReplyError(c,"The stream has exhausted the last possible ID, "
|
||||
"unable to add more items");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Append using the low level function and return the ID. */
|
||||
if (streamAppendItem(s,c->argv+field_pos,(c->argc-field_pos)/2,
|
||||
&id, id_given ? &id : NULL)
|
||||
@@ -1349,6 +1374,11 @@ void xreadCommand(client *c) {
|
||||
int moreargs = c->argc-i-1;
|
||||
char *o = c->argv[i]->ptr;
|
||||
if (!strcasecmp(o,"BLOCK") && moreargs) {
|
||||
if (c->flags & CLIENT_LUA) {
|
||||
/* There is no sense to use BLOCK option within LUA */
|
||||
addReplyErrorFormat(c, "%s command is not allowed with BLOCK option from scripts", (char *)c->argv[0]->ptr);
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[i],&timeout,
|
||||
UNIT_MILLISECONDS) != C_OK) return;
|
||||
@@ -1490,20 +1520,23 @@ void xreadCommand(client *c) {
|
||||
{
|
||||
serve_synchronously = 1;
|
||||
serve_history = 1;
|
||||
} else {
|
||||
} else if (s->length) {
|
||||
/* We also want to serve a consumer in a consumer group
|
||||
* synchronously in case the group top item delivered is smaller
|
||||
* than what the stream has inside. */
|
||||
streamID *last = &groups[i]->last_id;
|
||||
if (s->length && (streamCompareID(&s->last_id, last) > 0)) {
|
||||
streamID maxid, *last = &groups[i]->last_id;
|
||||
streamLastValidID(s, &maxid);
|
||||
if (streamCompareID(&maxid, last) > 0) {
|
||||
serve_synchronously = 1;
|
||||
*gt = *last;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (s->length) {
|
||||
/* For consumers without a group, we serve synchronously if we can
|
||||
* actually provide at least one item from the stream. */
|
||||
if (s->length && (streamCompareID(&s->last_id, gt) > 0)) {
|
||||
streamID maxid;
|
||||
streamLastValidID(s, &maxid);
|
||||
if (streamCompareID(&maxid, gt) > 0) {
|
||||
serve_synchronously = 1;
|
||||
}
|
||||
}
|
||||
@@ -1515,7 +1548,7 @@ void xreadCommand(client *c) {
|
||||
* so start from the next ID, since we want only messages with
|
||||
* IDs greater than start. */
|
||||
streamID start = *gt;
|
||||
start.seq++; /* uint64_t can't overflow in this context. */
|
||||
streamIncrID(&start);
|
||||
|
||||
/* Emit the two elements sub-array consisting of the name
|
||||
* of the stream and the data we extracted from it. */
|
||||
@@ -1821,6 +1854,8 @@ NULL
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-destroy",
|
||||
c->argv[2],c->db->id);
|
||||
/* We want to unblock any XREADGROUP consumers with -NOGROUP. */
|
||||
signalKeyAsReady(c->db,c->argv[2]);
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
@@ -1855,11 +1890,7 @@ void xsetidCommand(client *c) {
|
||||
* item, otherwise the fundamental ID monotonicity assumption is violated. */
|
||||
if (s->length > 0) {
|
||||
streamID maxid;
|
||||
streamIterator si;
|
||||
streamIteratorStart(&si,s,NULL,NULL,1);
|
||||
int64_t numfields;
|
||||
streamIteratorGetID(&si,&maxid,&numfields);
|
||||
streamIteratorStop(&si);
|
||||
streamLastValidID(s,&maxid);
|
||||
|
||||
if (streamCompareID(&id,&maxid) < 0) {
|
||||
addReplyError(c,"The ID specified in XSETID is smaller than the "
|
||||
@@ -1896,11 +1927,21 @@ void xackCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Start parsing the IDs, so that we abort ASAP if there is a syntax
|
||||
* error: the return value of this command cannot be an error in case
|
||||
* the client successfully acknowledged some messages, so it should be
|
||||
* executed in a "all or nothing" fashion. */
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
streamID id;
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
}
|
||||
|
||||
int acknowledged = 0;
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
streamID id;
|
||||
unsigned char buf[sizeof(streamID)];
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK)
|
||||
serverPanic("StreamID invalid after check. Should not be possible.");
|
||||
streamEncodeID(buf,&id);
|
||||
|
||||
/* Lookup the ID in the group PEL: it will have a reference to the
|
||||
@@ -2230,7 +2271,7 @@ void xclaimCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Do the actual claiming. */
|
||||
streamConsumer *consumer = streamLookupConsumer(group,c->argv[3]->ptr,1);
|
||||
streamConsumer *consumer = NULL;
|
||||
void *arraylenptr = addReplyDeferredLen(c);
|
||||
size_t arraylen = 0;
|
||||
for (int j = 5; j <= last_id_arg; j++) {
|
||||
@@ -2282,9 +2323,11 @@ void xclaimCommand(client *c) {
|
||||
if (nack->consumer)
|
||||
raxRemove(nack->consumer->pel,buf,sizeof(buf),NULL);
|
||||
/* Update the consumer and idle time. */
|
||||
if (consumer == NULL)
|
||||
consumer = streamLookupConsumer(group,c->argv[3]->ptr,1);
|
||||
nack->consumer = consumer;
|
||||
nack->delivery_time = deliverytime;
|
||||
/* Set the delivery attempts counter if given, otherwise
|
||||
/* Set the delivery attempts counter if given, otherwise
|
||||
* autoincrement unless JUSTID option provided */
|
||||
if (retrycount >= 0) {
|
||||
nack->delivery_count = retrycount;
|
||||
|
||||
+225
-11
@@ -46,7 +46,7 @@ static int checkStringLength(client *c, long long size) {
|
||||
* options and variants. This function is called in order to implement the
|
||||
* following commands: SET, SETEX, PSETEX, SETNX.
|
||||
*
|
||||
* 'flags' changes the behavior of the command (NX or XX, see belove).
|
||||
* 'flags' changes the behavior of the command (NX or XX, see below).
|
||||
*
|
||||
* 'expire' represents an expire to set in form of a Redis object as passed
|
||||
* by the user. It is interpreted according to the specified 'unit'.
|
||||
@@ -59,10 +59,11 @@ static int checkStringLength(client *c, long long size) {
|
||||
* If abort_reply is NULL, "$-1" is used. */
|
||||
|
||||
#define OBJ_SET_NO_FLAGS 0
|
||||
#define OBJ_SET_NX (1<<0) /* Set if key not exists. */
|
||||
#define OBJ_SET_XX (1<<1) /* Set if key exists. */
|
||||
#define OBJ_SET_EX (1<<2) /* Set if time in seconds is given */
|
||||
#define OBJ_SET_PX (1<<3) /* Set if time in ms in given */
|
||||
#define OBJ_SET_NX (1<<0) /* Set if key not exists. */
|
||||
#define OBJ_SET_XX (1<<1) /* Set if key exists. */
|
||||
#define OBJ_SET_EX (1<<2) /* Set if time in seconds is given */
|
||||
#define OBJ_SET_PX (1<<3) /* Set if time in ms in given */
|
||||
#define OBJ_SET_KEEPTTL (1<<4) /* Set and keep the ttl */
|
||||
|
||||
void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
|
||||
long long milliseconds = 0; /* initialized to avoid any harmness warning */
|
||||
@@ -83,7 +84,7 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
|
||||
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
|
||||
return;
|
||||
}
|
||||
setKey(c->db,key,val);
|
||||
genericSetKey(c->db,key,val,flags & OBJ_SET_KEEPTTL,1);
|
||||
server.dirty++;
|
||||
if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
|
||||
@@ -92,7 +93,7 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
|
||||
addReply(c, ok_reply ? ok_reply : shared.ok);
|
||||
}
|
||||
|
||||
/* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
|
||||
/* SET key value [NX] [XX] [KEEPTTL] [EX <seconds>] [PX <milliseconds>] */
|
||||
void setCommand(client *c) {
|
||||
int j;
|
||||
robj *expire = NULL;
|
||||
@@ -113,8 +114,13 @@ void setCommand(client *c) {
|
||||
!(flags & OBJ_SET_NX))
|
||||
{
|
||||
flags |= OBJ_SET_XX;
|
||||
} else if (!strcasecmp(c->argv[j]->ptr,"KEEPTTL") &&
|
||||
!(flags & OBJ_SET_EX) && !(flags & OBJ_SET_PX))
|
||||
{
|
||||
flags |= OBJ_SET_KEEPTTL;
|
||||
} else if ((a[0] == 'e' || a[0] == 'E') &&
|
||||
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
|
||||
!(flags & OBJ_SET_KEEPTTL) &&
|
||||
!(flags & OBJ_SET_PX) && next)
|
||||
{
|
||||
flags |= OBJ_SET_EX;
|
||||
@@ -123,6 +129,7 @@ void setCommand(client *c) {
|
||||
j++;
|
||||
} else if ((a[0] == 'p' || a[0] == 'P') &&
|
||||
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
|
||||
!(flags & OBJ_SET_KEEPTTL) &&
|
||||
!(flags & OBJ_SET_EX) && next)
|
||||
{
|
||||
flags |= OBJ_SET_PX;
|
||||
@@ -398,7 +405,7 @@ void decrbyCommand(client *c) {
|
||||
|
||||
void incrbyfloatCommand(client *c) {
|
||||
long double incr, value;
|
||||
robj *o, *new, *aux;
|
||||
robj *o, *new, *aux1, *aux2;
|
||||
|
||||
o = lookupKeyWrite(c->db,c->argv[1]);
|
||||
if (o != NULL && checkType(c,o,OBJ_STRING)) return;
|
||||
@@ -424,10 +431,13 @@ void incrbyfloatCommand(client *c) {
|
||||
/* Always replicate INCRBYFLOAT as a SET command with the final value
|
||||
* in order to make sure that differences in float precision or formatting
|
||||
* will not create differences in replicas or after an AOF restart. */
|
||||
aux = createStringObject("SET",3);
|
||||
rewriteClientCommandArgument(c,0,aux);
|
||||
decrRefCount(aux);
|
||||
aux1 = createStringObject("SET",3);
|
||||
rewriteClientCommandArgument(c,0,aux1);
|
||||
decrRefCount(aux1);
|
||||
rewriteClientCommandArgument(c,2,new);
|
||||
aux2 = createStringObject("KEEPTTL",7);
|
||||
rewriteClientCommandArgument(c,3,aux2);
|
||||
decrRefCount(aux2);
|
||||
}
|
||||
|
||||
void appendCommand(client *c) {
|
||||
@@ -469,3 +479,207 @@ void strlenCommand(client *c) {
|
||||
checkType(c,o,OBJ_STRING)) return;
|
||||
addReplyLongLong(c,stringObjectLen(o));
|
||||
}
|
||||
|
||||
/* LCS -- Longest common subsequence.
|
||||
*
|
||||
* LCS [IDX] [MINMATCHLEN <len>]
|
||||
* STRINGS <string> <string> | KEYS <keya> <keyb> */
|
||||
void lcsCommand(client *c) {
|
||||
uint32_t i, j;
|
||||
long long minmatchlen = 0;
|
||||
sds a = NULL, b = NULL;
|
||||
int getlen = 0, getidx = 0, withmatchlen = 0;
|
||||
robj *obja = NULL, *objb = NULL;
|
||||
|
||||
for (j = 1; j < (uint32_t)c->argc; j++) {
|
||||
char *opt = c->argv[j]->ptr;
|
||||
int moreargs = (c->argc-1) - j;
|
||||
|
||||
if (!strcasecmp(opt,"IDX")) {
|
||||
getidx = 1;
|
||||
} else if (!strcasecmp(opt,"LEN")) {
|
||||
getlen = 1;
|
||||
} else if (!strcasecmp(opt,"WITHMATCHLEN")) {
|
||||
withmatchlen = 1;
|
||||
} else if (!strcasecmp(opt,"MINMATCHLEN") && moreargs) {
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j+1],&minmatchlen,NULL)
|
||||
!= C_OK) return;
|
||||
if (minmatchlen < 0) minmatchlen = 0;
|
||||
j++;
|
||||
} else if (!strcasecmp(opt,"STRINGS")) {
|
||||
if (a != NULL) {
|
||||
addReplyError(c,"Either use STRINGS or KEYS");
|
||||
return;
|
||||
}
|
||||
a = c->argv[j+1]->ptr;
|
||||
b = c->argv[j+2]->ptr;
|
||||
j += 2;
|
||||
} else if (!strcasecmp(opt,"KEYS")) {
|
||||
if (a != NULL) {
|
||||
addReplyError(c,"Either use STRINGS or KEYS");
|
||||
return;
|
||||
}
|
||||
obja = lookupKeyRead(c->db,c->argv[j+1]);
|
||||
objb = lookupKeyRead(c->db,c->argv[j+2]);
|
||||
obja = obja ? getDecodedObject(obja) : createStringObject("",0);
|
||||
objb = objb ? getDecodedObject(objb) : createStringObject("",0);
|
||||
a = obja->ptr;
|
||||
b = objb->ptr;
|
||||
j += 2;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Complain if the user passed ambiguous parameters. */
|
||||
if (a == NULL) {
|
||||
addReplyError(c,"Please specify two strings: "
|
||||
"STRINGS or KEYS options are mandatory");
|
||||
return;
|
||||
} else if (getlen && getidx) {
|
||||
addReplyError(c,
|
||||
"If you want both the length and indexes, please "
|
||||
"just use IDX.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compute the LCS using the vanilla dynamic programming technique of
|
||||
* building a table of LCS(x,y) substrings. */
|
||||
uint32_t alen = sdslen(a);
|
||||
uint32_t blen = sdslen(b);
|
||||
|
||||
/* Setup an uint32_t array to store at LCS[i,j] the length of the
|
||||
* LCS A0..i-1, B0..j-1. Note that we have a linear array here, so
|
||||
* we index it as LCS[j+(blen+1)*j] */
|
||||
uint32_t *lcs = zmalloc((alen+1)*(blen+1)*sizeof(uint32_t));
|
||||
#define LCS(A,B) lcs[(B)+((A)*(blen+1))]
|
||||
|
||||
/* Start building the LCS table. */
|
||||
for (uint32_t i = 0; i <= alen; i++) {
|
||||
for (uint32_t j = 0; j <= blen; j++) {
|
||||
if (i == 0 || j == 0) {
|
||||
/* If one substring has length of zero, the
|
||||
* LCS length is zero. */
|
||||
LCS(i,j) = 0;
|
||||
} else if (a[i-1] == b[j-1]) {
|
||||
/* The len LCS (and the LCS itself) of two
|
||||
* sequences with the same final character, is the
|
||||
* LCS of the two sequences without the last char
|
||||
* plus that last char. */
|
||||
LCS(i,j) = LCS(i-1,j-1)+1;
|
||||
} else {
|
||||
/* If the last character is different, take the longest
|
||||
* between the LCS of the first string and the second
|
||||
* minus the last char, and the reverse. */
|
||||
uint32_t lcs1 = LCS(i-1,j);
|
||||
uint32_t lcs2 = LCS(i,j-1);
|
||||
LCS(i,j) = lcs1 > lcs2 ? lcs1 : lcs2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Store the actual LCS string in "result" if needed. We create
|
||||
* it backward, but the length is already known, we store it into idx. */
|
||||
uint32_t idx = LCS(alen,blen);
|
||||
sds result = NULL; /* Resulting LCS string. */
|
||||
void *arraylenptr = NULL; /* Deffered length of the array for IDX. */
|
||||
uint32_t arange_start = alen, /* alen signals that values are not set. */
|
||||
arange_end = 0,
|
||||
brange_start = 0,
|
||||
brange_end = 0;
|
||||
|
||||
/* Do we need to compute the actual LCS string? Allocate it in that case. */
|
||||
int computelcs = getidx || !getlen;
|
||||
if (computelcs) result = sdsnewlen(SDS_NOINIT,idx);
|
||||
|
||||
/* Start with a deferred array if we have to emit the ranges. */
|
||||
uint32_t arraylen = 0; /* Number of ranges emitted in the array. */
|
||||
if (getidx) {
|
||||
addReplyMapLen(c,2);
|
||||
addReplyBulkCString(c,"matches");
|
||||
arraylenptr = addReplyDeferredLen(c);
|
||||
}
|
||||
|
||||
i = alen, j = blen;
|
||||
while (computelcs && i > 0 && j > 0) {
|
||||
int emit_range = 0;
|
||||
if (a[i-1] == b[j-1]) {
|
||||
/* If there is a match, store the character and reduce
|
||||
* the indexes to look for a new match. */
|
||||
result[idx-1] = a[i-1];
|
||||
|
||||
/* Track the current range. */
|
||||
if (arange_start == alen) {
|
||||
arange_start = i-1;
|
||||
arange_end = i-1;
|
||||
brange_start = j-1;
|
||||
brange_end = j-1;
|
||||
} else {
|
||||
/* Let's see if we can extend the range backward since
|
||||
* it is contiguous. */
|
||||
if (arange_start == i && brange_start == j) {
|
||||
arange_start--;
|
||||
brange_start--;
|
||||
} else {
|
||||
emit_range = 1;
|
||||
}
|
||||
}
|
||||
/* Emit the range if we matched with the first byte of
|
||||
* one of the two strings. We'll exit the loop ASAP. */
|
||||
if (arange_start == 0 || brange_start == 0) emit_range = 1;
|
||||
idx--; i--; j--;
|
||||
} else {
|
||||
/* Otherwise reduce i and j depending on the largest
|
||||
* LCS between, to understand what direction we need to go. */
|
||||
uint32_t lcs1 = LCS(i-1,j);
|
||||
uint32_t lcs2 = LCS(i,j-1);
|
||||
if (lcs1 > lcs2)
|
||||
i--;
|
||||
else
|
||||
j--;
|
||||
if (arange_start != alen) emit_range = 1;
|
||||
}
|
||||
|
||||
/* Emit the current range if needed. */
|
||||
uint32_t match_len = arange_end - arange_start + 1;
|
||||
if (emit_range) {
|
||||
if (minmatchlen == 0 || match_len >= minmatchlen) {
|
||||
if (arraylenptr) {
|
||||
addReplyArrayLen(c,2+withmatchlen);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c,arange_start);
|
||||
addReplyLongLong(c,arange_end);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c,brange_start);
|
||||
addReplyLongLong(c,brange_end);
|
||||
if (withmatchlen) addReplyLongLong(c,match_len);
|
||||
arraylen++;
|
||||
}
|
||||
}
|
||||
arange_start = alen; /* Restart at the next match. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Signal modified key, increment dirty, ... */
|
||||
|
||||
/* Reply depending on the given options. */
|
||||
if (arraylenptr) {
|
||||
addReplyBulkCString(c,"len");
|
||||
addReplyLongLong(c,LCS(alen,blen));
|
||||
setDeferredArrayLen(c,arraylenptr,arraylen);
|
||||
} else if (getlen) {
|
||||
addReplyLongLong(c,LCS(alen,blen));
|
||||
} else {
|
||||
addReplyBulkSds(c,result);
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
/* Cleanup. */
|
||||
if (obja) decrRefCount(obja);
|
||||
if (objb) decrRefCount(objb);
|
||||
sdsfree(result);
|
||||
zfree(lcs);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
/* Copyright (c) 2009-2020, 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.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
|
||||
/* ========================== Clients timeouts ============================= */
|
||||
|
||||
/* Check if this blocked client timedout (does nothing if the client is
|
||||
* not blocked right now). If so send a reply, unblock it, and return 1.
|
||||
* Otherwise 0 is returned and no operation is performed. */
|
||||
int checkBlockedClientTimeout(client *c, mstime_t now) {
|
||||
if (c->flags & CLIENT_BLOCKED &&
|
||||
c->bpop.timeout != 0
|
||||
&& c->bpop.timeout < now)
|
||||
{
|
||||
/* Handle blocking operation specific timeout. */
|
||||
replyToBlockedClientTimedOut(c);
|
||||
unblockClient(c);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for timeouts. Returns non-zero if the client was terminated.
|
||||
* The function gets the current time in milliseconds as argument since
|
||||
* it gets called multiple times in a loop, so calling gettimeofday() for
|
||||
* each iteration would be costly without any actual gain. */
|
||||
int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
|
||||
time_t now = now_ms/1000;
|
||||
|
||||
if (server.maxidletime &&
|
||||
/* This handles the idle clients connection timeout if set. */
|
||||
!(c->flags & CLIENT_SLAVE) && /* No timeout for slaves and monitors */
|
||||
!(c->flags & CLIENT_MASTER) && /* No timeout for masters */
|
||||
!(c->flags & CLIENT_BLOCKED) && /* No timeout for BLPOP */
|
||||
!(c->flags & CLIENT_PUBSUB) && /* No timeout for Pub/Sub clients */
|
||||
(now - c->lastinteraction > server.maxidletime))
|
||||
{
|
||||
serverLog(LL_VERBOSE,"Closing idle client");
|
||||
freeClient(c);
|
||||
return 1;
|
||||
} else if (c->flags & CLIENT_BLOCKED) {
|
||||
/* Cluster: handle unblock & redirect of clients blocked
|
||||
* into keys no longer served by this server. */
|
||||
if (server.cluster_enabled) {
|
||||
if (clusterRedirectBlockedClientIfNeeded(c))
|
||||
unblockClient(c);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* For blocked clients timeouts we populate a radix tree of 128 bit keys
|
||||
* composed as such:
|
||||
*
|
||||
* [8 byte big endian expire time]+[8 byte client ID]
|
||||
*
|
||||
* We don't do any cleanup in the Radix tree: when we run the clients that
|
||||
* reached the timeout already, if they are no longer existing or no longer
|
||||
* blocked with such timeout, we just go forward.
|
||||
*
|
||||
* Every time a client blocks with a timeout, we add the client in
|
||||
* the tree. In beforeSleep() we call handleBlockedClientsTimeout() to run
|
||||
* the tree and unblock the clients. */
|
||||
|
||||
#define CLIENT_ST_KEYLEN 16 /* 8 bytes mstime + 8 bytes client ID. */
|
||||
|
||||
/* Given client ID and timeout, write the resulting radix tree key in buf. */
|
||||
void encodeTimeoutKey(unsigned char *buf, uint64_t timeout, client *c) {
|
||||
timeout = htonu64(timeout);
|
||||
memcpy(buf,&timeout,sizeof(timeout));
|
||||
memcpy(buf+8,&c,sizeof(c));
|
||||
if (sizeof(c) == 4) memset(buf+12,0,4); /* Zero padding for 32bit target. */
|
||||
}
|
||||
|
||||
/* Given a key encoded with encodeTimeoutKey(), resolve the fields and write
|
||||
* the timeout into *toptr and the client pointer into *cptr. */
|
||||
void decodeTimeoutKey(unsigned char *buf, uint64_t *toptr, client **cptr) {
|
||||
memcpy(toptr,buf,sizeof(*toptr));
|
||||
*toptr = ntohu64(*toptr);
|
||||
memcpy(cptr,buf+8,sizeof(*cptr));
|
||||
}
|
||||
|
||||
/* Add the specified client id / timeout as a key in the radix tree we use
|
||||
* to handle blocked clients timeouts. The client is not added to the list
|
||||
* if its timeout is zero (block forever). */
|
||||
void addClientToTimeoutTable(client *c) {
|
||||
if (c->bpop.timeout == 0) return;
|
||||
uint64_t timeout = c->bpop.timeout;
|
||||
unsigned char buf[CLIENT_ST_KEYLEN];
|
||||
encodeTimeoutKey(buf,timeout,c);
|
||||
if (raxTryInsert(server.clients_timeout_table,buf,sizeof(buf),NULL,NULL))
|
||||
c->flags |= CLIENT_IN_TO_TABLE;
|
||||
}
|
||||
|
||||
/* Remove the client from the table when it is unblocked for reasons
|
||||
* different than timing out. */
|
||||
void removeClientFromTimeoutTable(client *c) {
|
||||
if (!(c->flags & CLIENT_IN_TO_TABLE)) return;
|
||||
c->flags &= ~CLIENT_IN_TO_TABLE;
|
||||
uint64_t timeout = c->bpop.timeout;
|
||||
unsigned char buf[CLIENT_ST_KEYLEN];
|
||||
encodeTimeoutKey(buf,timeout,c);
|
||||
raxRemove(server.clients_timeout_table,buf,sizeof(buf),NULL);
|
||||
}
|
||||
|
||||
/* This function is called in beforeSleep() in order to unblock clients
|
||||
* that are waiting in blocking operations with a timeout set. */
|
||||
void handleBlockedClientsTimeout(void) {
|
||||
if (raxSize(server.clients_timeout_table) == 0) return;
|
||||
uint64_t now = mstime();
|
||||
raxIterator ri;
|
||||
raxStart(&ri,server.clients_timeout_table);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
|
||||
while(raxNext(&ri)) {
|
||||
uint64_t timeout;
|
||||
client *c;
|
||||
decodeTimeoutKey(ri.key,&timeout,&c);
|
||||
if (timeout >= now) break; /* All the timeouts are in the future. */
|
||||
c->flags &= ~CLIENT_IN_TO_TABLE;
|
||||
checkBlockedClientTimeout(c,now);
|
||||
raxRemove(server.clients_timeout_table,ri.key,ri.key_len,NULL);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
+288
-133
@@ -30,39 +30,34 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
/* The tracking table is constituted by 2^24 radix trees (each tree, and the
|
||||
* table itself, are allocated in a lazy way only when needed) tracking
|
||||
* clients that may have certain keys in their local, client side, cache.
|
||||
*
|
||||
* Keys are grouped into 2^24 slots, in a way similar to Redis Cluster hash
|
||||
* slots, however here the function we use is crc64, taking the least
|
||||
* significant 24 bits of the output.
|
||||
/* The tracking table is constituted by a radix tree of keys, each pointing
|
||||
* to a radix tree of client IDs, used to track the clients that may have
|
||||
* certain keys in their local, client side, cache.
|
||||
*
|
||||
* When a client enables tracking with "CLIENT TRACKING on", each key served to
|
||||
* the client is hashed to one of such slots, and Redis will remember what
|
||||
* client may have keys about such slot. Later, when a key in a given slot is
|
||||
* modified, all the clients that may have local copies of keys in that slot
|
||||
* will receive an invalidation message. There is no distinction of database
|
||||
* number: a single table is used.
|
||||
* the client is remembered in the table mapping the keys to the client IDs.
|
||||
* Later, when a key is modified, all the clients that may have local copy
|
||||
* of such key will receive an invalidation message.
|
||||
*
|
||||
* Clients will normally take frequently requested objects in memory, removing
|
||||
* them when invalidation messages are received. A strategy clients may use is
|
||||
* to just cache objects in a dictionary, associating to each cached object
|
||||
* some incremental epoch, or just a timestamp. When invalidation messages are
|
||||
* received clients may store, in a different table, the timestamp (or epoch)
|
||||
* of the invalidation of such given slot: later when accessing objects, the
|
||||
* eviction of stale objects may be performed in a lazy way by checking if the
|
||||
* cached object timestamp is older than the invalidation timestamp for such
|
||||
* objects.
|
||||
*
|
||||
* The output of the 24 bit hash function is very large (more than 16 million
|
||||
* possible slots), so clients that may want to use less resources may only
|
||||
* use the most significant bits instead of the full 24 bits. */
|
||||
#define TRACKING_TABLE_SIZE (1<<24)
|
||||
rax **TrackingTable = NULL;
|
||||
unsigned long TrackingTableUsedSlots = 0;
|
||||
* them when invalidation messages are received. */
|
||||
rax *TrackingTable = NULL;
|
||||
rax *PrefixTable = NULL;
|
||||
uint64_t TrackingTableTotalItems = 0; /* Total number of IDs stored across
|
||||
the whole tracking table. This gives
|
||||
an hint about the total memory we
|
||||
are using server side for CSC. */
|
||||
robj *TrackingChannelName;
|
||||
|
||||
/* This is the structure that we have as value of the PrefixTable, and
|
||||
* represents the list of keys modified, and the list of clients that need
|
||||
* to be notified, for a given prefix. */
|
||||
typedef struct bcastState {
|
||||
rax *keys; /* Keys modified in the current event loop cycle. */
|
||||
rax *clients; /* Clients subscribed to the notification events for this
|
||||
prefix. */
|
||||
} bcastState;
|
||||
|
||||
/* Remove the tracking state from the client 'c'. Note that there is not much
|
||||
* to do for us here, if not to decrement the counter of the clients in
|
||||
* tracking mode, because we just store the ID of the client in the tracking
|
||||
@@ -70,9 +65,56 @@ robj *TrackingChannelName;
|
||||
* client with many entries in the table is removed, it would cost a lot of
|
||||
* time to do the cleanup. */
|
||||
void disableTracking(client *c) {
|
||||
/* If this client is in broadcasting mode, we need to unsubscribe it
|
||||
* from all the prefixes it is registered to. */
|
||||
if (c->flags & CLIENT_TRACKING_BCAST) {
|
||||
raxIterator ri;
|
||||
raxStart(&ri,c->client_tracking_prefixes);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
bcastState *bs = raxFind(PrefixTable,ri.key,ri.key_len);
|
||||
serverAssert(bs != raxNotFound);
|
||||
raxRemove(bs->clients,(unsigned char*)&c,sizeof(c),NULL);
|
||||
/* Was it the last client? Remove the prefix from the
|
||||
* table. */
|
||||
if (raxSize(bs->clients) == 0) {
|
||||
raxFree(bs->clients);
|
||||
raxFree(bs->keys);
|
||||
zfree(bs);
|
||||
raxRemove(PrefixTable,ri.key,ri.key_len,NULL);
|
||||
}
|
||||
}
|
||||
raxStop(&ri);
|
||||
raxFree(c->client_tracking_prefixes);
|
||||
c->client_tracking_prefixes = NULL;
|
||||
}
|
||||
|
||||
/* Clear flags and adjust the count. */
|
||||
if (c->flags & CLIENT_TRACKING) {
|
||||
server.tracking_clients--;
|
||||
c->flags &= ~(CLIENT_TRACKING|CLIENT_TRACKING_BROKEN_REDIR);
|
||||
c->flags &= ~(CLIENT_TRACKING|CLIENT_TRACKING_BROKEN_REDIR|
|
||||
CLIENT_TRACKING_BCAST|CLIENT_TRACKING_OPTIN|
|
||||
CLIENT_TRACKING_OPTOUT|CLIENT_TRACKING_CACHING);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the client 'c' to track the prefix 'prefix'. If the client 'c' is
|
||||
* already registered for the specified prefix, no operation is performed. */
|
||||
void enableBcastTrackingForPrefix(client *c, char *prefix, size_t plen) {
|
||||
bcastState *bs = raxFind(PrefixTable,(unsigned char*)prefix,sdslen(prefix));
|
||||
/* If this is the first client subscribing to such prefix, create
|
||||
* the prefix in the table. */
|
||||
if (bs == raxNotFound) {
|
||||
bs = zmalloc(sizeof(*bs));
|
||||
bs->keys = raxNew();
|
||||
bs->clients = raxNew();
|
||||
raxInsert(PrefixTable,(unsigned char*)prefix,plen,bs,NULL);
|
||||
}
|
||||
if (raxTryInsert(bs->clients,(unsigned char*)&c,sizeof(c),NULL,NULL)) {
|
||||
if (c->client_tracking_prefixes == NULL)
|
||||
c->client_tracking_prefixes = raxNew();
|
||||
raxInsert(c->client_tracking_prefixes,
|
||||
(unsigned char*)prefix,plen,NULL,NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,24 +125,43 @@ void disableTracking(client *c) {
|
||||
* eventually get freed, we'll send a message to the original client to
|
||||
* inform it of the condition. Multiple clients can redirect the invalidation
|
||||
* messages to the same client ID. */
|
||||
void enableTracking(client *c, uint64_t redirect_to) {
|
||||
if (c->flags & CLIENT_TRACKING) return;
|
||||
void enableTracking(client *c, uint64_t redirect_to, uint64_t options, robj **prefix, size_t numprefix) {
|
||||
if (!(c->flags & CLIENT_TRACKING)) server.tracking_clients++;
|
||||
c->flags |= CLIENT_TRACKING;
|
||||
c->flags &= ~CLIENT_TRACKING_BROKEN_REDIR;
|
||||
c->flags &= ~(CLIENT_TRACKING_BROKEN_REDIR|CLIENT_TRACKING_BCAST|
|
||||
CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT);
|
||||
c->client_tracking_redirection = redirect_to;
|
||||
server.tracking_clients++;
|
||||
if (TrackingTable == NULL) {
|
||||
TrackingTable = zcalloc(sizeof(rax*) * TRACKING_TABLE_SIZE);
|
||||
TrackingTable = raxNew();
|
||||
PrefixTable = raxNew();
|
||||
TrackingChannelName = createStringObject("__redis__:invalidate",20);
|
||||
}
|
||||
|
||||
if (options & CLIENT_TRACKING_BCAST) {
|
||||
c->flags |= CLIENT_TRACKING_BCAST;
|
||||
if (numprefix == 0) enableBcastTrackingForPrefix(c,"",0);
|
||||
for (size_t j = 0; j < numprefix; j++) {
|
||||
sds sdsprefix = prefix[j]->ptr;
|
||||
enableBcastTrackingForPrefix(c,sdsprefix,sdslen(sdsprefix));
|
||||
}
|
||||
}
|
||||
c->flags |= options & (CLIENT_TRACKING_OPTIN|CLIENT_TRACKING_OPTOUT);
|
||||
}
|
||||
|
||||
/* This function is called after the excution of a readonly command in the
|
||||
* case the client 'c' has keys tracking enabled. It will populate the
|
||||
* tracking ivalidation table according to the keys the user fetched, so that
|
||||
* Redis will know what are the clients that should receive an invalidation
|
||||
* message with certain groups of keys are modified. */
|
||||
/* This function is called after the execution of a readonly command in the
|
||||
* case the client 'c' has keys tracking enabled and the tracking is not
|
||||
* in BCAST mode. It will populate the tracking invalidation table according
|
||||
* to the keys the user fetched, so that Redis will know what are the clients
|
||||
* that should receive an invalidation message with certain groups of keys
|
||||
* are modified. */
|
||||
void trackingRememberKeys(client *c) {
|
||||
/* Return if we are in optin/out mode and the right CACHING command
|
||||
* was/wasn't given in order to modify the default behavior. */
|
||||
uint64_t optin = c->flags & CLIENT_TRACKING_OPTIN;
|
||||
uint64_t optout = c->flags & CLIENT_TRACKING_OPTOUT;
|
||||
uint64_t caching_given = c->flags & CLIENT_TRACKING_CACHING;
|
||||
if ((optin && !caching_given) || (optout && caching_given)) return;
|
||||
|
||||
int numkeys;
|
||||
int *keys = getKeysFromCommand(c->cmd,c->argv,c->argc,&numkeys);
|
||||
if (keys == NULL) return;
|
||||
@@ -108,19 +169,30 @@ void trackingRememberKeys(client *c) {
|
||||
for(int j = 0; j < numkeys; j++) {
|
||||
int idx = keys[j];
|
||||
sds sdskey = c->argv[idx]->ptr;
|
||||
uint64_t hash = crc64(0,
|
||||
(unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1);
|
||||
if (TrackingTable[hash] == NULL) {
|
||||
TrackingTable[hash] = raxNew();
|
||||
TrackingTableUsedSlots++;
|
||||
rax *ids = raxFind(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey));
|
||||
if (ids == raxNotFound) {
|
||||
ids = raxNew();
|
||||
int inserted = raxTryInsert(TrackingTable,(unsigned char*)sdskey,
|
||||
sdslen(sdskey),ids, NULL);
|
||||
serverAssert(inserted == 1);
|
||||
}
|
||||
raxTryInsert(TrackingTable[hash],
|
||||
(unsigned char*)&c->id,sizeof(c->id),NULL,NULL);
|
||||
if (raxTryInsert(ids,(unsigned char*)&c->id,sizeof(c->id),NULL,NULL))
|
||||
TrackingTableTotalItems++;
|
||||
}
|
||||
getKeysFreeResult(keys);
|
||||
}
|
||||
|
||||
void sendTrackingMessage(client *c, long long hash) {
|
||||
/* Given a key name, this function sends an invalidation message in the
|
||||
* proper channel (depending on RESP version: PubSub or Push message) and
|
||||
* to the proper client (in case fo redirection), in the context of the
|
||||
* client 'c' with tracking enabled.
|
||||
*
|
||||
* In case the 'proto' argument is non zero, the function will assume that
|
||||
* 'keyname' points to a buffer of 'keylen' bytes already expressed in the
|
||||
* form of Redis RESP protocol, representing an array of keys to send
|
||||
* to the client as value of the invalidation. This is used in BCAST mode
|
||||
* in order to optimized the implementation to use less CPU time. */
|
||||
void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
|
||||
int using_redirection = 0;
|
||||
if (c->client_tracking_redirection) {
|
||||
client *redir = lookupClientByID(c->client_tracking_redirection);
|
||||
@@ -146,36 +218,45 @@ void sendTrackingMessage(client *c, long long hash) {
|
||||
if (c->resp > 2) {
|
||||
addReplyPushLen(c,2);
|
||||
addReplyBulkCBuffer(c,"invalidate",10);
|
||||
addReplyLongLong(c,hash);
|
||||
} else if (using_redirection && c->flags & CLIENT_PUBSUB) {
|
||||
robj *msg = createStringObjectFromLongLong(hash);
|
||||
addReplyPubsubMessage(c,TrackingChannelName,msg);
|
||||
decrRefCount(msg);
|
||||
/* We use a static object to speedup things, however we assume
|
||||
* that addReplyPubsubMessage() will not take a reference. */
|
||||
addReplyPubsubMessage(c,TrackingChannelName,NULL);
|
||||
} else {
|
||||
/* If are here, the client is not using RESP3, nor is
|
||||
* redirecting to another client. We can't send anything to
|
||||
* it since RESP2 does not support push messages in the same
|
||||
* connection. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send the "value" part, which is the array of keys. */
|
||||
if (proto) {
|
||||
addReplyProto(c,keyname,keylen);
|
||||
} else {
|
||||
addReplyArrayLen(c,1);
|
||||
addReplyBulkCBuffer(c,keyname,keylen);
|
||||
}
|
||||
}
|
||||
|
||||
/* Invalidates a caching slot: this is actually the low level implementation
|
||||
* of the API that Redis calls externally, that is trackingInvalidateKey(). */
|
||||
void trackingInvalidateSlot(uint64_t slot) {
|
||||
if (TrackingTable == NULL || TrackingTable[slot] == NULL) return;
|
||||
|
||||
/* This function is called when a key is modified in Redis and in the case
|
||||
* we have at least one client with the BCAST mode enabled.
|
||||
* Its goal is to set the key in the right broadcast state if the key
|
||||
* matches one or more prefixes in the prefix table. Later when we
|
||||
* return to the event loop, we'll send invalidation messages to the
|
||||
* clients subscribed to each prefix. */
|
||||
void trackingRememberKeyToBroadcast(char *keyname, size_t keylen) {
|
||||
raxIterator ri;
|
||||
raxStart(&ri,TrackingTable[slot]);
|
||||
raxStart(&ri,PrefixTable);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
uint64_t id;
|
||||
memcpy(&id,ri.key,ri.key_len);
|
||||
client *c = lookupClientByID(id);
|
||||
if (c == NULL || !(c->flags & CLIENT_TRACKING)) continue;
|
||||
sendTrackingMessage(c,slot);
|
||||
if (ri.key_len > keylen) continue;
|
||||
if (ri.key_len != 0 && memcmp(ri.key,keyname,ri.key_len) != 0)
|
||||
continue;
|
||||
bcastState *bs = ri.data;
|
||||
raxTryInsert(bs->keys,(unsigned char*)keyname,keylen,NULL,NULL);
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
/* Free the tracking table: we'll create the radix tree and populate it
|
||||
* again if more keys will be modified in this caching slot. */
|
||||
raxFree(TrackingTable[slot]);
|
||||
TrackingTable[slot] = NULL;
|
||||
TrackingTableUsedSlots--;
|
||||
}
|
||||
|
||||
/* This function is called from signalModifiedKey() or other places in Redis
|
||||
@@ -183,28 +264,55 @@ void trackingInvalidateSlot(uint64_t slot) {
|
||||
* to send a notification to every client that may have keys about such caching
|
||||
* slot. */
|
||||
void trackingInvalidateKey(robj *keyobj) {
|
||||
if (TrackingTable == NULL || TrackingTableUsedSlots == 0) return;
|
||||
|
||||
if (TrackingTable == NULL) return;
|
||||
sds sdskey = keyobj->ptr;
|
||||
uint64_t hash = crc64(0,
|
||||
(unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1);
|
||||
trackingInvalidateSlot(hash);
|
||||
|
||||
if (raxSize(PrefixTable) > 0)
|
||||
trackingRememberKeyToBroadcast(sdskey,sdslen(sdskey));
|
||||
|
||||
rax *ids = raxFind(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey));
|
||||
if (ids == raxNotFound) return;
|
||||
|
||||
raxIterator ri;
|
||||
raxStart(&ri,ids);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
uint64_t id;
|
||||
memcpy(&id,ri.key,sizeof(id));
|
||||
client *c = lookupClientByID(id);
|
||||
/* Note that if the client is in BCAST mode, we don't want to
|
||||
* send invalidation messages that were pending in the case
|
||||
* previously the client was not in BCAST mode. This can happen if
|
||||
* TRACKING is enabled normally, and then the client switches to
|
||||
* BCAST mode. */
|
||||
if (c == NULL ||
|
||||
!(c->flags & CLIENT_TRACKING)||
|
||||
c->flags & CLIENT_TRACKING_BCAST)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
sendTrackingMessage(c,sdskey,sdslen(sdskey),0);
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
/* Free the tracking table: we'll create the radix tree and populate it
|
||||
* again if more keys will be modified in this caching slot. */
|
||||
TrackingTableTotalItems -= raxSize(ids);
|
||||
raxFree(ids);
|
||||
raxRemove(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey),NULL);
|
||||
}
|
||||
|
||||
/* This function is called when one or all the Redis databases are flushed
|
||||
* (dbid == -1 in case of FLUSHALL). Caching slots are not specific for
|
||||
* each DB but are global: currently what we do is sending a special
|
||||
* (dbid == -1 in case of FLUSHALL). Caching keys are not specific for
|
||||
* each DB but are global: currently what we do is send a special
|
||||
* notification to clients with tracking enabled, invalidating the caching
|
||||
* slot "-1", which means, "all the keys", in order to avoid flooding clients
|
||||
* key "", which means, "all the keys", in order to avoid flooding clients
|
||||
* with many invalidation messages for all the keys they may hold.
|
||||
*
|
||||
* However trying to flush the tracking table here is very costly:
|
||||
* we need scanning 16 million caching slots in the table to check
|
||||
* if they are used, this introduces a big delay. So what we do is to really
|
||||
* flush the table in the case of FLUSHALL. When a FLUSHDB is called instead
|
||||
* we just send the invalidation message to all the clients, but don't
|
||||
* flush the table: it will slowly get garbage collected as more keys
|
||||
* are modified in the used caching slots. */
|
||||
*/
|
||||
void freeTrackingRadixTree(void *rt) {
|
||||
raxFree(rt);
|
||||
}
|
||||
|
||||
void trackingInvalidateKeysOnFlush(int dbid) {
|
||||
if (server.tracking_clients) {
|
||||
listNode *ln;
|
||||
@@ -213,84 +321,131 @@ void trackingInvalidateKeysOnFlush(int dbid) {
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = listNodeValue(ln);
|
||||
if (c->flags & CLIENT_TRACKING) {
|
||||
sendTrackingMessage(c,-1);
|
||||
sendTrackingMessage(c,"",1,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* In case of FLUSHALL, reclaim all the memory used by tracking. */
|
||||
if (dbid == -1 && TrackingTable) {
|
||||
for (int j = 0; j < TRACKING_TABLE_SIZE && TrackingTableUsedSlots > 0; j++) {
|
||||
if (TrackingTable[j] != NULL) {
|
||||
raxFree(TrackingTable[j]);
|
||||
TrackingTable[j] = NULL;
|
||||
TrackingTableUsedSlots--;
|
||||
}
|
||||
}
|
||||
|
||||
/* If there are no clients with tracking enabled, we can even
|
||||
* reclaim the memory used by the table itself. The code assumes
|
||||
* the table is allocated only if there is at least one client alive
|
||||
* with tracking enabled. */
|
||||
if (server.tracking_clients == 0) {
|
||||
zfree(TrackingTable);
|
||||
TrackingTable = NULL;
|
||||
}
|
||||
raxFreeWithCallback(TrackingTable,freeTrackingRadixTree);
|
||||
TrackingTable = raxNew();
|
||||
TrackingTableTotalItems = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tracking forces Redis to remember information about which client may have
|
||||
* keys about certian caching slots. In workloads where there are a lot of
|
||||
* reads, but keys are hardly modified, the amount of information we have
|
||||
* to remember server side could be a lot: for each 16 millions of caching
|
||||
* slots we may end with a radix tree containing many entries.
|
||||
* certain keys. In workloads where there are a lot of reads, but keys are
|
||||
* hardly modified, the amount of information we have to remember server side
|
||||
* could be a lot, with the number of keys being totally not bound.
|
||||
*
|
||||
* So Redis allows the user to configure a maximum fill rate for the
|
||||
* So Redis allows the user to configure a maximum number of keys for the
|
||||
* invalidation table. This function makes sure that we don't go over the
|
||||
* specified fill rate: if we are over, we can just evict informations about
|
||||
* random caching slots, and send invalidation messages to clients like if
|
||||
* the key was modified. */
|
||||
* a random key, and send invalidation messages to clients like if the key was
|
||||
* modified. */
|
||||
void trackingLimitUsedSlots(void) {
|
||||
static unsigned int timeout_counter = 0;
|
||||
|
||||
if (server.tracking_table_max_fill == 0) return; /* No limits set. */
|
||||
unsigned int max_slots =
|
||||
(TRACKING_TABLE_SIZE/100) * server.tracking_table_max_fill;
|
||||
if (TrackingTableUsedSlots <= max_slots) {
|
||||
if (TrackingTable == NULL) return;
|
||||
if (server.tracking_table_max_keys == 0) return; /* No limits set. */
|
||||
size_t max_keys = server.tracking_table_max_keys;
|
||||
if (raxSize(TrackingTable) <= max_keys) {
|
||||
timeout_counter = 0;
|
||||
return; /* Limit not reached. */
|
||||
}
|
||||
|
||||
/* We have to invalidate a few slots to reach the limit again. The effort
|
||||
/* We have to invalidate a few keys to reach the limit again. The effort
|
||||
* we do here is proportional to the number of times we entered this
|
||||
* function and found that we are still over the limit. */
|
||||
int effort = 100 * (timeout_counter+1);
|
||||
|
||||
/* Let's start at a random position, and perform linear probing, in order
|
||||
* to improve cache locality. However once we are able to find an used
|
||||
* slot, jump again randomly, in order to avoid creating big holes in the
|
||||
* table (that will make this funciton use more resourced later). */
|
||||
/* We just remove one key after another by using a random walk. */
|
||||
raxIterator ri;
|
||||
raxStart(&ri,TrackingTable);
|
||||
while(effort > 0) {
|
||||
unsigned int idx = rand() % TRACKING_TABLE_SIZE;
|
||||
do {
|
||||
effort--;
|
||||
idx = (idx+1) % TRACKING_TABLE_SIZE;
|
||||
if (TrackingTable[idx] != NULL) {
|
||||
trackingInvalidateSlot(idx);
|
||||
if (TrackingTableUsedSlots <= max_slots) {
|
||||
timeout_counter = 0;
|
||||
return; /* Return ASAP: we are again under the limit. */
|
||||
} else {
|
||||
break; /* Jump to next random position. */
|
||||
}
|
||||
}
|
||||
} while(effort > 0);
|
||||
effort--;
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
raxRandomWalk(&ri,0);
|
||||
rax *ids = ri.data;
|
||||
TrackingTableTotalItems -= raxSize(ids);
|
||||
raxFree(ids);
|
||||
raxRemove(TrackingTable,ri.key,ri.key_len,NULL);
|
||||
if (raxSize(TrackingTable) <= max_keys) {
|
||||
timeout_counter = 0;
|
||||
raxStop(&ri);
|
||||
return; /* Return ASAP: we are again under the limit. */
|
||||
}
|
||||
}
|
||||
|
||||
/* If we reach this point, we were not able to go under the configured
|
||||
* limit using the maximum effort we had for this run. */
|
||||
raxStop(&ri);
|
||||
timeout_counter++;
|
||||
}
|
||||
|
||||
/* This function will run the prefixes of clients in BCAST mode and
|
||||
* keys that were modified about each prefix, and will send the
|
||||
* notifications to each client in each prefix. */
|
||||
void trackingBroadcastInvalidationMessages(void) {
|
||||
raxIterator ri, ri2;
|
||||
|
||||
/* Return ASAP if there is nothing to do here. */
|
||||
if (TrackingTable == NULL || !server.tracking_clients) return;
|
||||
|
||||
raxStart(&ri,PrefixTable);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
bcastState *bs = ri.data;
|
||||
if (raxSize(bs->keys)) {
|
||||
/* Create the array reply with the list of keys once, then send
|
||||
* it to all the clients subscribed to this prefix. */
|
||||
char buf[32];
|
||||
size_t len = ll2string(buf,sizeof(buf),raxSize(bs->keys));
|
||||
sds proto = sdsempty();
|
||||
proto = sdsMakeRoomFor(proto,raxSize(bs->keys)*15);
|
||||
proto = sdscatlen(proto,"*",1);
|
||||
proto = sdscatlen(proto,buf,len);
|
||||
proto = sdscatlen(proto,"\r\n",2);
|
||||
raxStart(&ri2,bs->keys);
|
||||
raxSeek(&ri2,"^",NULL,0);
|
||||
while(raxNext(&ri2)) {
|
||||
len = ll2string(buf,sizeof(buf),ri2.key_len);
|
||||
proto = sdscatlen(proto,"$",1);
|
||||
proto = sdscatlen(proto,buf,len);
|
||||
proto = sdscatlen(proto,"\r\n",2);
|
||||
proto = sdscatlen(proto,ri2.key,ri2.key_len);
|
||||
proto = sdscatlen(proto,"\r\n",2);
|
||||
}
|
||||
raxStop(&ri2);
|
||||
|
||||
/* Send this array of keys to every client in the list. */
|
||||
raxStart(&ri2,bs->clients);
|
||||
raxSeek(&ri2,"^",NULL,0);
|
||||
while(raxNext(&ri2)) {
|
||||
client *c;
|
||||
memcpy(&c,ri2.key,sizeof(c));
|
||||
sendTrackingMessage(c,proto,sdslen(proto),1);
|
||||
}
|
||||
raxStop(&ri2);
|
||||
|
||||
/* Clean up: we can remove everything from this state, because we
|
||||
* want to only track the new keys that will be accumulated starting
|
||||
* from now. */
|
||||
sdsfree(proto);
|
||||
}
|
||||
raxFree(bs->keys);
|
||||
bs->keys = raxNew();
|
||||
}
|
||||
raxStop(&ri);
|
||||
}
|
||||
|
||||
/* This is just used in order to access the amount of used slots in the
|
||||
* tracking table. */
|
||||
unsigned long long trackingGetUsedSlots(void) {
|
||||
return TrackingTableUsedSlots;
|
||||
uint64_t trackingGetTotalItems(void) {
|
||||
return TrackingTableTotalItems;
|
||||
}
|
||||
|
||||
uint64_t trackingGetTotalKeys(void) {
|
||||
if (TrackingTable == NULL) return 0;
|
||||
return raxSize(TrackingTable);
|
||||
}
|
||||
|
||||
+47
-1
@@ -423,6 +423,26 @@ int string2ll(const char *s, size_t slen, long long *value) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Helper function to convert a string to an unsigned long long value.
|
||||
* The function attempts to use the faster string2ll() function inside
|
||||
* Redis: if it fails, strtoull() is used instead. The function returns
|
||||
* 1 if the conversion happened successfully or 0 if the number is
|
||||
* invalid or out of range. */
|
||||
int string2ull(const char *s, unsigned long long *value) {
|
||||
long long ll;
|
||||
if (string2ll(s,strlen(s),&ll)) {
|
||||
if (ll < 0) return 0; /* Negative values are out of range. */
|
||||
*value = ll;
|
||||
return 1;
|
||||
}
|
||||
errno = 0;
|
||||
char *endptr = NULL;
|
||||
*value = strtoull(s,&endptr,10);
|
||||
if (errno == EINVAL || errno == ERANGE || !(*s != '\0' && *endptr == '\0'))
|
||||
return 0; /* strtoull() failed. */
|
||||
return 1; /* Conversion done! */
|
||||
}
|
||||
|
||||
/* Convert a string into a long. Returns 1 if the string could be parsed into a
|
||||
* (non-overflowing) long, 0 otherwise. The value will be set to the parsed
|
||||
* value when appropriate. */
|
||||
@@ -451,13 +471,14 @@ int string2ld(const char *s, size_t slen, long double *dp) {
|
||||
long double value;
|
||||
char *eptr;
|
||||
|
||||
if (slen >= sizeof(buf)) return 0;
|
||||
if (slen == 0 || slen >= sizeof(buf)) return 0;
|
||||
memcpy(buf,s,slen);
|
||||
buf[slen] = '\0';
|
||||
|
||||
errno = 0;
|
||||
value = strtold(buf, &eptr);
|
||||
if (isspace(buf[0]) || eptr[0] != '\0' ||
|
||||
(size_t)(eptr-buf) != slen ||
|
||||
(errno == ERANGE &&
|
||||
(value == HUGE_VAL || value == -HUGE_VAL || value == 0)) ||
|
||||
errno == EINVAL ||
|
||||
@@ -468,6 +489,27 @@ int string2ld(const char *s, size_t slen, long double *dp) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Convert a string into a double. Returns 1 if the string could be parsed
|
||||
* into a (non-overflowing) double, 0 otherwise. The value will be set to
|
||||
* the parsed value when appropriate.
|
||||
*
|
||||
* Note that this function demands that the string strictly represents
|
||||
* a double: no spaces or other characters before or after the string
|
||||
* representing the number are accepted. */
|
||||
int string2d(const char *s, size_t slen, double *dp) {
|
||||
errno = 0;
|
||||
char *eptr;
|
||||
*dp = strtod(s, &eptr);
|
||||
if (slen == 0 ||
|
||||
isspace(((const char*)s)[0]) ||
|
||||
(size_t)(eptr-(char*)s) != slen ||
|
||||
(errno == ERANGE &&
|
||||
(*dp == HUGE_VAL || *dp == -HUGE_VAL || *dp == 0)) ||
|
||||
isnan(*dp))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Convert a double to a string representation. Returns the number of bytes
|
||||
* required. The representation should always be parsable by strtod(3).
|
||||
* This function does not support human-friendly formatting like ld2string
|
||||
@@ -560,6 +602,10 @@ int ld2string(char *buf, size_t len, long double value, ld2string_mode mode) {
|
||||
}
|
||||
if (*p == '.') l--;
|
||||
}
|
||||
if (l == 2 && buf[0] == '-' && buf[1] == '0') {
|
||||
buf[0] = '0';
|
||||
l = 1;
|
||||
}
|
||||
break;
|
||||
default: return 0; /* Invalid mode. */
|
||||
}
|
||||
|
||||
@@ -53,8 +53,10 @@ uint32_t digits10(uint64_t v);
|
||||
uint32_t sdigits10(int64_t v);
|
||||
int ll2string(char *s, size_t len, long long value);
|
||||
int string2ll(const char *s, size_t slen, long long *value);
|
||||
int string2ull(const char *s, unsigned long long *value);
|
||||
int string2l(const char *s, size_t slen, long *value);
|
||||
int string2ld(const char *s, size_t slen, long double *dp);
|
||||
int string2d(const char *s, size_t slen, double *dp);
|
||||
int d2string(char *buf, size_t len, double value);
|
||||
int ld2string(char *buf, size_t len, long double value, ld2string_mode mode);
|
||||
sds getAbsolutePath(char *filename);
|
||||
|
||||
@@ -388,6 +388,14 @@ int jemalloc_purge() {
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
/* For proc_pidinfo() used later in zmalloc_get_smap_bytes_by_field().
|
||||
* Note that this file cannot be included in zmalloc.h because it includes
|
||||
* a Darwin queue.h file where there is a "LIST_HEAD" macro (!) defined
|
||||
* conficting with Redis user code. */
|
||||
#include <libproc.h>
|
||||
#endif
|
||||
|
||||
/* Get the sum of the specified field (converted form kb to bytes) in
|
||||
* /proc/self/smaps. The field must be specified with trailing ":" as it
|
||||
* apperas in the smaps output.
|
||||
@@ -427,7 +435,28 @@ size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
|
||||
return bytes;
|
||||
}
|
||||
#else
|
||||
/* Get sum of the specified field from libproc api call.
|
||||
* As there are per page value basis we need to convert
|
||||
* them accordingly.
|
||||
*
|
||||
* Note that AnonHugePages is a no-op as THP feature
|
||||
* is not supported in this platform
|
||||
*/
|
||||
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid) {
|
||||
#if defined(__APPLE__)
|
||||
struct proc_regioninfo pri;
|
||||
if (proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri, PROC_PIDREGIONINFO_SIZE) ==
|
||||
PROC_PIDREGIONINFO_SIZE) {
|
||||
if (!strcmp(field, "Private_Dirty:")) {
|
||||
return (size_t)pri.pri_pages_dirtied * 4096;
|
||||
} else if (!strcmp(field, "Rss:")) {
|
||||
return (size_t)pri.pri_pages_resident * 4096;
|
||||
} else if (!strcmp(field, "AnonHugePages:")) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
((void) field);
|
||||
((void) pid);
|
||||
return 0;
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
test "Create a 5 nodes cluster" {
|
||||
create_cluster 5 5
|
||||
}
|
||||
|
||||
test "Cluster should start ok" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 0
|
||||
}
|
||||
|
||||
proc find_non_empty_master {} {
|
||||
set master_id_no {}
|
||||
foreach_redis_id id {
|
||||
if {[RI $id role] eq {master} && [R $id dbsize] > 0} {
|
||||
set master_id_no $id
|
||||
}
|
||||
}
|
||||
return $master_id_no
|
||||
}
|
||||
|
||||
proc get_one_of_my_replica {id} {
|
||||
set replica_port [lindex [lindex [lindex [R $id role] 2] 0] 1]
|
||||
set replica_id_num [get_instance_id_by_port redis $replica_port]
|
||||
return $replica_id_num
|
||||
}
|
||||
|
||||
proc cluster_write_keys_with_expire {id ttl} {
|
||||
set prefix [randstring 20 20 alpha]
|
||||
set port [get_instance_attrib redis $id port]
|
||||
set cluster [redis_cluster 127.0.0.1:$port]
|
||||
for {set j 100} {$j < 200} {incr j} {
|
||||
$cluster setex key_expire.$j $ttl $prefix.$j
|
||||
}
|
||||
$cluster close
|
||||
}
|
||||
|
||||
proc test_slave_load_expired_keys {aof} {
|
||||
test "Slave expired keys is loaded when restarted: appendonly=$aof" {
|
||||
set master_id [find_non_empty_master]
|
||||
set replica_id [get_one_of_my_replica $master_id]
|
||||
|
||||
set master_dbsize [R $master_id dbsize]
|
||||
set slave_dbsize [R $replica_id dbsize]
|
||||
assert_equal $master_dbsize $slave_dbsize
|
||||
|
||||
set data_ttl 5
|
||||
cluster_write_keys_with_expire $master_id $data_ttl
|
||||
after 100
|
||||
set replica_dbsize_1 [R $replica_id dbsize]
|
||||
assert {$replica_dbsize_1 > $slave_dbsize}
|
||||
|
||||
R $replica_id config set appendonly $aof
|
||||
R $replica_id config rewrite
|
||||
|
||||
set start_time [clock seconds]
|
||||
set end_time [expr $start_time+$data_ttl+2]
|
||||
R $replica_id save
|
||||
set replica_dbsize_2 [R $replica_id dbsize]
|
||||
assert {$replica_dbsize_2 > $slave_dbsize}
|
||||
kill_instance redis $replica_id
|
||||
|
||||
set master_port [get_instance_attrib redis $master_id port]
|
||||
exec ../../../src/redis-cli -h 127.0.0.1 -p $master_port debug sleep [expr $data_ttl+3] > /dev/null &
|
||||
|
||||
while {[clock seconds] <= $end_time} {
|
||||
#wait for $data_ttl seconds
|
||||
}
|
||||
restart_instance redis $replica_id
|
||||
|
||||
wait_for_condition 200 50 {
|
||||
[R $replica_id ping] eq {PONG}
|
||||
} else {
|
||||
fail "replica #$replica_id not started"
|
||||
}
|
||||
|
||||
set replica_dbsize_3 [R $replica_id dbsize]
|
||||
assert {$replica_dbsize_3 > $slave_dbsize}
|
||||
}
|
||||
}
|
||||
|
||||
test_slave_load_expired_keys no
|
||||
after 5000
|
||||
test_slave_load_expired_keys yes
|
||||
@@ -4,7 +4,7 @@ set ::tlsdir "tests/tls"
|
||||
|
||||
proc gen_write_load {host port seconds tls} {
|
||||
set start_time [clock seconds]
|
||||
set r [redis $host $port 0 $tls]
|
||||
set r [redis $host $port 1 $tls]
|
||||
$r select 9
|
||||
while 1 {
|
||||
$r set [expr rand()] [expr rand()]
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Test the meaningful offset implementation to make sure masters
|
||||
# are able to PSYNC with replicas even if the replication stream
|
||||
# has pending PINGs at the end.
|
||||
|
||||
start_server {tags {"psync2"}} {
|
||||
start_server {} {
|
||||
# Config
|
||||
set debug_msg 0 ; # Enable additional debug messages
|
||||
|
||||
for {set j 0} {$j < 2} {incr j} {
|
||||
set R($j) [srv [expr 0-$j] client]
|
||||
set R_host($j) [srv [expr 0-$j] host]
|
||||
set R_port($j) [srv [expr 0-$j] port]
|
||||
$R($j) CONFIG SET repl-ping-replica-period 1
|
||||
if {$debug_msg} {puts "Log file: [srv [expr 0-$j] stdout]"}
|
||||
}
|
||||
|
||||
# Setup replication
|
||||
test "PSYNC2 meaningful offset: setup" {
|
||||
$R(1) replicaof $R_host(0) $R_port(0)
|
||||
$R(0) set foo bar
|
||||
wait_for_condition 50 1000 {
|
||||
[$R(0) dbsize] == 1 && [$R(1) dbsize] == 1
|
||||
} else {
|
||||
fail "Replicas not replicating from master"
|
||||
}
|
||||
}
|
||||
|
||||
test "PSYNC2 meaningful offset: write and wait replication" {
|
||||
$R(0) INCR counter
|
||||
$R(0) INCR counter
|
||||
$R(0) INCR counter
|
||||
wait_for_condition 50 1000 {
|
||||
[$R(0) GET counter] eq [$R(1) GET counter]
|
||||
} else {
|
||||
fail "Master and replica don't agree about counter"
|
||||
}
|
||||
}
|
||||
|
||||
# In this test we'll make sure the replica will get stuck, but with
|
||||
# an active connection: this way the master will continue to send PINGs
|
||||
# every second (we modified the PING period earlier)
|
||||
test "PSYNC2 meaningful offset: pause replica and promote it" {
|
||||
$R(1) MULTI
|
||||
$R(1) DEBUG SLEEP 5
|
||||
$R(1) SLAVEOF NO ONE
|
||||
$R(1) EXEC
|
||||
$R(1) ping ; # Wait for it to return back available
|
||||
}
|
||||
|
||||
test "Make the old master a replica of the new one and check conditions" {
|
||||
set sync_partial [status $R(1) sync_partial_ok]
|
||||
assert {$sync_partial == 0}
|
||||
$R(0) REPLICAOF $R_host(1) $R_port(1)
|
||||
wait_for_condition 50 1000 {
|
||||
[status $R(1) sync_partial_ok] == 1
|
||||
} else {
|
||||
fail "The new master was not able to partial sync"
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -114,6 +114,27 @@ start_server {} {
|
||||
}
|
||||
}
|
||||
|
||||
# wait for all the slaves to be in sync with the master
|
||||
set master_ofs [status $R($master_id) master_repl_offset]
|
||||
wait_for_condition 500 100 {
|
||||
$master_ofs == [status $R(0) master_repl_offset] &&
|
||||
$master_ofs == [status $R(1) master_repl_offset] &&
|
||||
$master_ofs == [status $R(2) master_repl_offset] &&
|
||||
$master_ofs == [status $R(3) master_repl_offset] &&
|
||||
$master_ofs == [status $R(4) master_repl_offset]
|
||||
} else {
|
||||
if {$debug_msg} {
|
||||
for {set j 0} {$j < 5} {incr j} {
|
||||
puts "$j: sync_full: [status $R($j) sync_full]"
|
||||
puts "$j: id1 : [status $R($j) master_replid]:[status $R($j) master_repl_offset]"
|
||||
puts "$j: id2 : [status $R($j) master_replid2]:[status $R($j) second_repl_offset]"
|
||||
puts "$j: backlog : firstbyte=[status $R($j) repl_backlog_first_byte_offset] len=[status $R($j) repl_backlog_histlen]"
|
||||
puts "---"
|
||||
}
|
||||
}
|
||||
fail "Slaves are not in sync with the master after too long time."
|
||||
}
|
||||
|
||||
# Put down the old master so that it cannot generate more
|
||||
# replication stream, this way in the next master switch, the time at
|
||||
# which we move slaves away is not important, each will have full
|
||||
|
||||
@@ -70,6 +70,13 @@ start_server {tags {"repl"}} {
|
||||
}
|
||||
}
|
||||
|
||||
test {INCRBYFLOAT replication, should not remove expire} {
|
||||
r set test 1 EX 100
|
||||
r incrbyfloat test 0.1
|
||||
after 1000
|
||||
assert_equal [$A debug digest] [$B debug digest]
|
||||
}
|
||||
|
||||
test {BRPOPLPUSH replication, when blocking against empty list} {
|
||||
set rd [redis_deferring_client]
|
||||
$rd brpoplpush a b 5
|
||||
|
||||
@@ -19,7 +19,10 @@ TEST_MODULES = \
|
||||
propagate.so \
|
||||
misc.so \
|
||||
hooks.so \
|
||||
blockonkeys.so
|
||||
blockonkeys.so \
|
||||
scan.so \
|
||||
datatype.so \
|
||||
auth.so
|
||||
|
||||
.PHONY: all
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "redismodule.h"
|
||||
|
||||
// A simple global user
|
||||
static RedisModuleUser *global = NULL;
|
||||
static long long client_change_delta = 0;
|
||||
|
||||
void UserChangedCallback(uint64_t client_id, void *privdata) {
|
||||
REDISMODULE_NOT_USED(privdata);
|
||||
REDISMODULE_NOT_USED(client_id);
|
||||
client_change_delta++;
|
||||
}
|
||||
|
||||
int Auth_CreateModuleUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (global) {
|
||||
RedisModule_FreeModuleUser(global);
|
||||
}
|
||||
|
||||
global = RedisModule_CreateModuleUser("global");
|
||||
RedisModule_SetModuleUserACL(global, "allcommands");
|
||||
RedisModule_SetModuleUserACL(global, "allkeys");
|
||||
RedisModule_SetModuleUserACL(global, "on");
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
int Auth_AuthModuleUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
uint64_t client_id;
|
||||
RedisModule_AuthenticateClientWithUser(ctx, global, UserChangedCallback, NULL, &client_id);
|
||||
|
||||
return RedisModule_ReplyWithLongLong(ctx, (uint64_t) client_id);
|
||||
}
|
||||
|
||||
int Auth_AuthRealUser(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
|
||||
size_t length;
|
||||
uint64_t client_id;
|
||||
|
||||
RedisModuleString *user_string = argv[1];
|
||||
const char *name = RedisModule_StringPtrLen(user_string, &length);
|
||||
|
||||
if (RedisModule_AuthenticateClientWithACLUser(ctx, name, length,
|
||||
UserChangedCallback, NULL, &client_id) == REDISMODULE_ERR) {
|
||||
return RedisModule_ReplyWithError(ctx, "Invalid user");
|
||||
}
|
||||
|
||||
return RedisModule_ReplyWithLongLong(ctx, (uint64_t) client_id);
|
||||
}
|
||||
|
||||
int Auth_ChangeCount(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
long long result = client_change_delta;
|
||||
client_change_delta = 0;
|
||||
return RedisModule_ReplyWithLongLong(ctx, result);
|
||||
}
|
||||
|
||||
/* This function must be present on each Redis module. It is used in order to
|
||||
* register the commands into the Redis server. */
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (RedisModule_Init(ctx,"testacl",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"auth.authrealuser",
|
||||
Auth_AuthRealUser,"no-auth",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"auth.createmoduleuser",
|
||||
Auth_CreateModuleUser,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"auth.authmoduleuser",
|
||||
Auth_AuthModuleUser,"no-auth",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"auth.changecount",
|
||||
Auth_ChangeCount,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
+105
-28
@@ -109,41 +109,33 @@ int fsl_push(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return RedisModule_ReplyWithError(ctx,"ERR new element has to be greater than the head element");
|
||||
|
||||
fsl->list[fsl->length++] = ele;
|
||||
|
||||
if (fsl->length >= 2)
|
||||
RedisModule_SignalKeyAsReady(ctx, argv[1]);
|
||||
RedisModule_SignalKeyAsReady(ctx, argv[1]);
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
int bpop2_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
int bpop_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
|
||||
|
||||
fsl_t *fsl;
|
||||
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0))
|
||||
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0) || !fsl)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (!fsl || fsl->length < 2)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, 2);
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int bpop2_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
int bpop_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
|
||||
}
|
||||
|
||||
|
||||
/* FSL.BPOP2 <key> <timeout> - Block clients until list has two or more elements.
|
||||
/* FSL.BPOP <key> <timeout> - Block clients until list has two or more elements.
|
||||
* When that happens, unblock client and pop the last two elements (from the right). */
|
||||
int fsl_bpop2(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
int fsl_bpop(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 3)
|
||||
return RedisModule_WrongArity(ctx);
|
||||
|
||||
@@ -155,13 +147,10 @@ int fsl_bpop2(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
|
||||
return REDISMODULE_OK;
|
||||
|
||||
if (!fsl || fsl->length < 2) {
|
||||
/* Key is empty or has <2 elements, we must block */
|
||||
RedisModule_BlockClientOnKeys(ctx, bpop2_reply_callback, bpop2_timeout_callback,
|
||||
if (!fsl) {
|
||||
RedisModule_BlockClientOnKeys(ctx, bpop_reply_callback, bpop_timeout_callback,
|
||||
NULL, timeout, &argv[1], 1, NULL);
|
||||
} else {
|
||||
RedisModule_ReplyWithArray(ctx, 2);
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
}
|
||||
|
||||
@@ -172,13 +161,13 @@ int bpopgt_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
|
||||
long long gt = (long long)RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
long long *pgt = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
|
||||
fsl_t *fsl;
|
||||
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0))
|
||||
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0) || !fsl)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (!fsl || fsl->list[fsl->length-1] <= gt)
|
||||
if (fsl->list[fsl->length-1] <= *pgt)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
@@ -192,10 +181,8 @@ int bpopgt_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int a
|
||||
}
|
||||
|
||||
void bpopgt_free_privdata(RedisModuleCtx *ctx, void *privdata) {
|
||||
/* Nothing to do because privdata is actually a 'long long',
|
||||
* not a pointer to the heap */
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
REDISMODULE_NOT_USED(privdata);
|
||||
RedisModule_Free(privdata);
|
||||
}
|
||||
|
||||
/* FSL.BPOPGT <key> <gt> <timeout> - Block clients until list has an element greater than <gt>.
|
||||
@@ -217,9 +204,11 @@ int fsl_bpopgt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return REDISMODULE_OK;
|
||||
|
||||
if (!fsl || fsl->list[fsl->length-1] <= gt) {
|
||||
/* Key is empty or has <2 elements, we must block */
|
||||
/* We use malloc so the tests in blockedonkeys.tcl can check for memory leaks */
|
||||
long long *pgt = RedisModule_Alloc(sizeof(long long));
|
||||
*pgt = gt;
|
||||
RedisModule_BlockClientOnKeys(ctx, bpopgt_reply_callback, bpopgt_timeout_callback,
|
||||
bpopgt_free_privdata, timeout, &argv[1], 1, (void*)gt);
|
||||
bpopgt_free_privdata, timeout, &argv[1], 1, pgt);
|
||||
} else {
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
}
|
||||
@@ -227,6 +216,88 @@ int fsl_bpopgt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int bpoppush_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
RedisModuleString *src_keyname = RedisModule_GetBlockedClientReadyKey(ctx);
|
||||
RedisModuleString *dst_keyname = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
|
||||
fsl_t *src;
|
||||
if (!get_fsl(ctx, src_keyname, REDISMODULE_READ, 0, &src, 0) || !src)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
fsl_t *dst;
|
||||
if (!get_fsl(ctx, dst_keyname, REDISMODULE_WRITE, 1, &dst, 0) || !dst)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
long long ele = src->list[--src->length];
|
||||
dst->list[dst->length++] = ele;
|
||||
RedisModule_SignalKeyAsReady(ctx, dst_keyname);
|
||||
return RedisModule_ReplyWithLongLong(ctx, ele);
|
||||
}
|
||||
|
||||
int bpoppush_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
|
||||
}
|
||||
|
||||
void bpoppush_free_privdata(RedisModuleCtx *ctx, void *privdata) {
|
||||
RedisModule_FreeString(ctx, privdata);
|
||||
}
|
||||
|
||||
/* FSL.BPOPPUSH <src> <dst> <timeout> - Block clients until <src> has an element.
|
||||
* When that happens, unblock client, pop the last element from <src> and push it to <dst>
|
||||
* (from the right). */
|
||||
int fsl_bpoppush(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 4)
|
||||
return RedisModule_WrongArity(ctx);
|
||||
|
||||
long long timeout;
|
||||
if (RedisModule_StringToLongLong(argv[3],&timeout) != REDISMODULE_OK || timeout < 0)
|
||||
return RedisModule_ReplyWithError(ctx,"ERR invalid timeout");
|
||||
|
||||
fsl_t *src;
|
||||
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &src, 1))
|
||||
return REDISMODULE_OK;
|
||||
|
||||
if (!src) {
|
||||
/* Retain string for reply callback */
|
||||
RedisModule_RetainString(ctx, argv[2]);
|
||||
/* Key is empty, we must block */
|
||||
RedisModule_BlockClientOnKeys(ctx, bpoppush_reply_callback, bpoppush_timeout_callback,
|
||||
bpoppush_free_privdata, timeout, &argv[1], 1, argv[2]);
|
||||
} else {
|
||||
fsl_t *dst;
|
||||
if (!get_fsl(ctx, argv[2], REDISMODULE_WRITE, 1, &dst, 1))
|
||||
return REDISMODULE_OK;
|
||||
long long ele = src->list[--src->length];
|
||||
dst->list[dst->length++] = ele;
|
||||
RedisModule_SignalKeyAsReady(ctx, argv[2]);
|
||||
RedisModule_ReplyWithLongLong(ctx, ele);
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* FSL.GETALL <key> - Reply with an array containing all elements. */
|
||||
int fsl_getall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2)
|
||||
return RedisModule_WrongArity(ctx);
|
||||
|
||||
fsl_t *fsl;
|
||||
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
|
||||
return REDISMODULE_OK;
|
||||
|
||||
if (!fsl)
|
||||
return RedisModule_ReplyWithArray(ctx, 0);
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, fsl->length);
|
||||
for (int i = 0; i < fsl->length; i++)
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[i]);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
@@ -251,11 +322,17 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.push",fsl_push,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpop2",fsl_bpop2,"",0,0,0) == REDISMODULE_ERR)
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpop",fsl_bpop,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpopgt",fsl_bpopgt,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpoppush",fsl_bpoppush,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.getall",fsl_getall,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/* This module current tests a small subset but should be extended in the future
|
||||
* for general ModuleDataType coverage.
|
||||
*/
|
||||
|
||||
#include "redismodule.h"
|
||||
|
||||
static RedisModuleType *datatype = NULL;
|
||||
|
||||
typedef struct {
|
||||
long long intval;
|
||||
RedisModuleString *strval;
|
||||
} DataType;
|
||||
|
||||
static void *datatype_load(RedisModuleIO *io, int encver) {
|
||||
(void) encver;
|
||||
|
||||
int intval = RedisModule_LoadSigned(io);
|
||||
if (RedisModule_IsIOError(io)) return NULL;
|
||||
|
||||
RedisModuleString *strval = RedisModule_LoadString(io);
|
||||
if (RedisModule_IsIOError(io)) return NULL;
|
||||
|
||||
DataType *dt = (DataType *) RedisModule_Alloc(sizeof(DataType));
|
||||
dt->intval = intval;
|
||||
dt->strval = strval;
|
||||
return dt;
|
||||
}
|
||||
|
||||
static void datatype_save(RedisModuleIO *io, void *value) {
|
||||
DataType *dt = (DataType *) value;
|
||||
RedisModule_SaveSigned(io, dt->intval);
|
||||
RedisModule_SaveString(io, dt->strval);
|
||||
}
|
||||
|
||||
static void datatype_free(void *value) {
|
||||
if (value) {
|
||||
DataType *dt = (DataType *) value;
|
||||
|
||||
if (dt->strval) RedisModule_FreeString(NULL, dt->strval);
|
||||
RedisModule_Free(dt);
|
||||
}
|
||||
}
|
||||
|
||||
static int datatype_set(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 4) {
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
long long intval;
|
||||
|
||||
if (RedisModule_StringToLongLong(argv[2], &intval) != REDISMODULE_OK) {
|
||||
RedisModule_ReplyWithError(ctx, "Invalid integr value");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||
DataType *dt = RedisModule_Calloc(sizeof(DataType), 1);
|
||||
dt->intval = intval;
|
||||
dt->strval = argv[3];
|
||||
RedisModule_RetainString(ctx, dt->strval);
|
||||
|
||||
RedisModule_ModuleTypeSetValue(key, datatype, dt);
|
||||
RedisModule_CloseKey(key);
|
||||
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
static int datatype_restore(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 3) {
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
DataType *dt = RedisModule_LoadDataTypeFromString(argv[2], datatype);
|
||||
if (!dt) {
|
||||
RedisModule_ReplyWithError(ctx, "Invalid data");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||
RedisModule_ModuleTypeSetValue(key, datatype, dt);
|
||||
RedisModule_CloseKey(key);
|
||||
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
static int datatype_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) {
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
|
||||
DataType *dt = RedisModule_ModuleTypeGetValue(key);
|
||||
RedisModule_CloseKey(key);
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, 2);
|
||||
RedisModule_ReplyWithLongLong(ctx, dt->intval);
|
||||
RedisModule_ReplyWithString(ctx, dt->strval);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
static int datatype_dump(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) {
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
|
||||
DataType *dt = RedisModule_ModuleTypeGetValue(key);
|
||||
RedisModule_CloseKey(key);
|
||||
|
||||
RedisModuleString *reply = RedisModule_SaveDataTypeToString(ctx, dt, datatype);
|
||||
if (!reply) {
|
||||
RedisModule_ReplyWithError(ctx, "Failed to save");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModule_ReplyWithString(ctx, reply);
|
||||
RedisModule_FreeString(ctx, reply);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
static int datatype_swap(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 3) {
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModuleKey *a = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
|
||||
RedisModuleKey *b = RedisModule_OpenKey(ctx, argv[2], REDISMODULE_WRITE);
|
||||
void *val = RedisModule_ModuleTypeGetValue(a);
|
||||
|
||||
int error = (RedisModule_ModuleTypeReplaceValue(b, datatype, val, &val) == REDISMODULE_ERR ||
|
||||
RedisModule_ModuleTypeReplaceValue(a, datatype, val, NULL) == REDISMODULE_ERR);
|
||||
if (!error)
|
||||
RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
else
|
||||
RedisModule_ReplyWithError(ctx, "ERR failed");
|
||||
|
||||
RedisModule_CloseKey(a);
|
||||
RedisModule_CloseKey(b);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
if (RedisModule_Init(ctx,"datatype",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
RedisModule_SetModuleOptions(ctx, REDISMODULE_OPTIONS_HANDLE_IO_ERRORS);
|
||||
|
||||
RedisModuleTypeMethods datatype_methods = {
|
||||
.version = REDISMODULE_TYPE_METHOD_VERSION,
|
||||
.rdb_load = datatype_load,
|
||||
.rdb_save = datatype_save,
|
||||
.free = datatype_free,
|
||||
};
|
||||
|
||||
datatype = RedisModule_CreateDataType(ctx, "test___dt", 1, &datatype_methods);
|
||||
if (datatype == NULL)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"datatype.set", datatype_set,"deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"datatype.get", datatype_get,"",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"datatype.restore", datatype_restore,"deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"datatype.dump", datatype_dump,"",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"datatype.swap", datatype_swap,"",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -42,7 +42,7 @@ int fork_create(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
|
||||
/* child */
|
||||
RedisModule_Log(ctx, "notice", "fork child started");
|
||||
usleep(200000);
|
||||
usleep(500000);
|
||||
RedisModule_Log(ctx, "notice", "fork child exiting");
|
||||
RedisModule_ExitFromChild(code_to_exit_with);
|
||||
/* unreachable */
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
void InfoFunc(RedisModuleInfoCtx *ctx, int for_crash_report) {
|
||||
RedisModule_InfoAddSection(ctx, "");
|
||||
RedisModule_InfoAddFieldLongLong(ctx, "global", -2);
|
||||
RedisModule_InfoAddFieldULongLong(ctx, "uglobal", (unsigned long long)-2);
|
||||
|
||||
RedisModule_InfoAddSection(ctx, "Spanish");
|
||||
RedisModule_InfoAddFieldCString(ctx, "uno", "one");
|
||||
@@ -29,6 +30,67 @@ void InfoFunc(RedisModuleInfoCtx *ctx, int for_crash_report) {
|
||||
|
||||
}
|
||||
|
||||
int info_get(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, char field_type)
|
||||
{
|
||||
if (argc != 3 && argc != 4) {
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
int err = REDISMODULE_OK;
|
||||
const char *section, *field;
|
||||
section = RedisModule_StringPtrLen(argv[1], NULL);
|
||||
field = RedisModule_StringPtrLen(argv[2], NULL);
|
||||
RedisModuleServerInfoData *info = RedisModule_GetServerInfo(ctx, section);
|
||||
if (field_type=='i') {
|
||||
long long ll = RedisModule_ServerInfoGetFieldSigned(info, field, &err);
|
||||
if (err==REDISMODULE_OK)
|
||||
RedisModule_ReplyWithLongLong(ctx, ll);
|
||||
} else if (field_type=='u') {
|
||||
unsigned long long ll = (unsigned long long)RedisModule_ServerInfoGetFieldUnsigned(info, field, &err);
|
||||
if (err==REDISMODULE_OK)
|
||||
RedisModule_ReplyWithLongLong(ctx, ll);
|
||||
} else if (field_type=='d') {
|
||||
double d = RedisModule_ServerInfoGetFieldDouble(info, field, &err);
|
||||
if (err==REDISMODULE_OK)
|
||||
RedisModule_ReplyWithDouble(ctx, d);
|
||||
} else if (field_type=='c') {
|
||||
const char *str = RedisModule_ServerInfoGetFieldC(info, field);
|
||||
if (str)
|
||||
RedisModule_ReplyWithCString(ctx, str);
|
||||
} else {
|
||||
RedisModuleString *str = RedisModule_ServerInfoGetField(ctx, info, field);
|
||||
if (str) {
|
||||
RedisModule_ReplyWithString(ctx, str);
|
||||
RedisModule_FreeString(ctx, str);
|
||||
} else
|
||||
err=REDISMODULE_ERR;
|
||||
}
|
||||
if (err!=REDISMODULE_OK)
|
||||
RedisModule_ReplyWithError(ctx, "not found");
|
||||
RedisModule_FreeServerInfo(ctx, info);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int info_gets(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return info_get(ctx, argv, argc, 's');
|
||||
}
|
||||
|
||||
int info_getc(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return info_get(ctx, argv, argc, 'c');
|
||||
}
|
||||
|
||||
int info_geti(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return info_get(ctx, argv, argc, 'i');
|
||||
}
|
||||
|
||||
int info_getu(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return info_get(ctx, argv, argc, 'u');
|
||||
}
|
||||
|
||||
int info_getd(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return info_get(ctx, argv, argc, 'd');
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
@@ -37,5 +99,16 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
|
||||
if (RedisModule_RegisterInfoFunc(ctx, InfoFunc) == REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"info.gets", info_gets,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"info.getc", info_getc,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"info.geti", info_geti,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"info.getu", info_getu,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"info.getd", info_getd,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
+112
-10
@@ -6,6 +6,8 @@
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
||||
int test_call_generic(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc<2) {
|
||||
@@ -40,6 +42,58 @@ int test_call_info(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int test_ld_conv(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
UNUSED(argv);
|
||||
UNUSED(argc);
|
||||
long double ld = 0.00000000000000001L;
|
||||
const char *ldstr = "0.00000000000000001";
|
||||
RedisModuleString *s1 = RedisModule_CreateStringFromLongDouble(ctx, ld, 1);
|
||||
RedisModuleString *s2 =
|
||||
RedisModule_CreateString(ctx, ldstr, strlen(ldstr));
|
||||
if (RedisModule_StringCompare(s1, s2) != 0) {
|
||||
char err[4096];
|
||||
snprintf(err, 4096,
|
||||
"Failed to convert long double to string ('%s' != '%s')",
|
||||
RedisModule_StringPtrLen(s1, NULL),
|
||||
RedisModule_StringPtrLen(s2, NULL));
|
||||
RedisModule_ReplyWithError(ctx, err);
|
||||
goto final;
|
||||
}
|
||||
long double ld2 = 0;
|
||||
if (RedisModule_StringToLongDouble(s2, &ld2) == REDISMODULE_ERR) {
|
||||
RedisModule_ReplyWithError(ctx,
|
||||
"Failed to convert string to long double");
|
||||
goto final;
|
||||
}
|
||||
if (ld2 != ld) {
|
||||
char err[4096];
|
||||
snprintf(err, 4096,
|
||||
"Failed to convert string to long double (%.40Lf != %.40Lf)",
|
||||
ld2,
|
||||
ld);
|
||||
RedisModule_ReplyWithError(ctx, err);
|
||||
goto final;
|
||||
}
|
||||
|
||||
/* Make sure we can't convert a string that has \0 in it */
|
||||
char buf[4] = "123";
|
||||
buf[1] = '\0';
|
||||
RedisModuleString *s3 = RedisModule_CreateString(ctx, buf, 3);
|
||||
long double ld3;
|
||||
if (RedisModule_StringToLongDouble(s3, &ld3) == REDISMODULE_OK) {
|
||||
RedisModule_ReplyWithError(ctx, "Invalid string successfully converted to long double");
|
||||
RedisModule_FreeString(ctx, s3);
|
||||
goto final;
|
||||
}
|
||||
RedisModule_FreeString(ctx, s3);
|
||||
|
||||
RedisModule_ReplyWithLongDouble(ctx, ld2);
|
||||
final:
|
||||
RedisModule_FreeString(ctx, s1);
|
||||
RedisModule_FreeString(ctx, s2);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int test_flushall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
@@ -68,16 +122,24 @@ int test_randomkey(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModuleKey *open_key_or_reply(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode) {
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, keyname, mode);
|
||||
if (!key) {
|
||||
RedisModule_ReplyWithError(ctx, "key not found");
|
||||
return NULL;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
int test_getlru(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc<2) {
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
RedisModuleString *keyname = argv[1];
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, keyname, REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
|
||||
long long lru, lfu;
|
||||
RedisModule_GetLRUOrLFU(key, &lfu, &lru);
|
||||
RedisModuleKey *key = open_key_or_reply(ctx, argv[1], REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
|
||||
mstime_t lru;
|
||||
RedisModule_GetLRU(key, &lru);
|
||||
RedisModule_ReplyWithLongLong(ctx, lru);
|
||||
RedisModule_CloseKey(key);
|
||||
return REDISMODULE_OK;
|
||||
@@ -89,12 +151,46 @@ int test_setlru(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
RedisModuleString *keyname = argv[1];
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, keyname, REDISMODULE_WRITE|REDISMODULE_OPEN_KEY_NOTOUCH);
|
||||
long long lru;
|
||||
RedisModule_StringToLongLong(argv[2], &lru);
|
||||
RedisModule_SetLRUOrLFU(key, -1, lru);
|
||||
RedisModule_ReplyWithCString(ctx, "Ok");
|
||||
RedisModuleKey *key = open_key_or_reply(ctx, argv[1], REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
|
||||
mstime_t lru;
|
||||
if (RedisModule_StringToLongLong(argv[2], &lru) != REDISMODULE_OK) {
|
||||
RedisModule_ReplyWithError(ctx, "invalid idle time");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
int was_set = RedisModule_SetLRU(key, lru)==REDISMODULE_OK;
|
||||
RedisModule_ReplyWithLongLong(ctx, was_set);
|
||||
RedisModule_CloseKey(key);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int test_getlfu(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc<2) {
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
RedisModuleKey *key = open_key_or_reply(ctx, argv[1], REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
|
||||
mstime_t lfu;
|
||||
RedisModule_GetLFU(key, &lfu);
|
||||
RedisModule_ReplyWithLongLong(ctx, lfu);
|
||||
RedisModule_CloseKey(key);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int test_setlfu(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc<3) {
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
RedisModuleKey *key = open_key_or_reply(ctx, argv[1], REDISMODULE_READ|REDISMODULE_OPEN_KEY_NOTOUCH);
|
||||
mstime_t lfu;
|
||||
if (RedisModule_StringToLongLong(argv[2], &lfu) != REDISMODULE_OK) {
|
||||
RedisModule_ReplyWithError(ctx, "invalid freq");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
int was_set = RedisModule_SetLFU(key, lfu)==REDISMODULE_OK;
|
||||
RedisModule_ReplyWithLongLong(ctx, was_set);
|
||||
RedisModule_CloseKey(key);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -109,6 +205,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.call_info", test_call_info,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.ld_conversion", test_ld_conv, "",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.flushall", test_flushall,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.dbsize", test_dbsize,"",0,0,0) == REDISMODULE_ERR)
|
||||
@@ -119,6 +217,10 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.getlru", test_getlru,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.setlfu", test_setlfu,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"test.getlfu", test_getlfu,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ void *threadMain(void *arg) {
|
||||
RedisModule_SelectDb(ctx,9); /* Tests ran in database number 9. */
|
||||
for (int i = 0; i < 10; i++) {
|
||||
RedisModule_ThreadSafeContextLock(ctx);
|
||||
RedisModule_Replicate(ctx,"INCR","c","thread");
|
||||
RedisModule_Replicate(ctx,"INCR","c","a-from-thread");
|
||||
RedisModule_Replicate(ctx,"INCR","c","b-from-thread");
|
||||
RedisModule_ThreadSafeContextUnlock(ctx);
|
||||
}
|
||||
RedisModule_FreeThreadSafeContext(ctx);
|
||||
@@ -89,6 +90,38 @@ int propagateTestCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int propagateTest2Command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
/* Replicate two commands to test MULTI/EXEC wrapping. */
|
||||
RedisModule_Replicate(ctx,"INCR","c","counter-1");
|
||||
RedisModule_Replicate(ctx,"INCR","c","counter-2");
|
||||
RedisModule_ReplyWithSimpleString(ctx,"OK");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int propagateTest3Command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
RedisModuleCallReply *reply;
|
||||
|
||||
/* This test mixes multiple propagation systems. */
|
||||
reply = RedisModule_Call(ctx, "INCR", "c!", "using-call");
|
||||
RedisModule_FreeCallReply(reply);
|
||||
|
||||
RedisModule_Replicate(ctx,"INCR","c","counter-1");
|
||||
RedisModule_Replicate(ctx,"INCR","c","counter-2");
|
||||
|
||||
reply = RedisModule_Call(ctx, "INCR", "c!", "after-call");
|
||||
RedisModule_FreeCallReply(reply);
|
||||
|
||||
RedisModule_ReplyWithSimpleString(ctx,"OK");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
@@ -100,5 +133,16 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
propagateTestCommand,
|
||||
"",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"propagate-test-2",
|
||||
propagateTest2Command,
|
||||
"",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"propagate-test-3",
|
||||
propagateTest3Command,
|
||||
"",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
#include "redismodule.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef struct {
|
||||
size_t nkeys;
|
||||
} scan_strings_pd;
|
||||
|
||||
void scan_strings_callback(RedisModuleCtx *ctx, RedisModuleString* keyname, RedisModuleKey* key, void *privdata) {
|
||||
scan_strings_pd* pd = privdata;
|
||||
int was_opened = 0;
|
||||
if (!key) {
|
||||
key = RedisModule_OpenKey(ctx, keyname, REDISMODULE_READ);
|
||||
was_opened = 1;
|
||||
}
|
||||
|
||||
if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_STRING) {
|
||||
size_t len;
|
||||
char * data = RedisModule_StringDMA(key, &len, REDISMODULE_READ);
|
||||
RedisModule_ReplyWithArray(ctx, 2);
|
||||
RedisModule_ReplyWithString(ctx, keyname);
|
||||
RedisModule_ReplyWithStringBuffer(ctx, data, len);
|
||||
pd->nkeys++;
|
||||
}
|
||||
if (was_opened)
|
||||
RedisModule_CloseKey(key);
|
||||
}
|
||||
|
||||
int scan_strings(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
scan_strings_pd pd = {
|
||||
.nkeys = 0,
|
||||
};
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
|
||||
RedisModuleScanCursor* cursor = RedisModule_ScanCursorCreate();
|
||||
while(RedisModule_Scan(ctx, cursor, scan_strings_callback, &pd));
|
||||
RedisModule_ScanCursorDestroy(cursor);
|
||||
|
||||
RedisModule_ReplySetArrayLength(ctx, pd.nkeys);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
RedisModuleCtx *ctx;
|
||||
size_t nreplies;
|
||||
} scan_key_pd;
|
||||
|
||||
void scan_key_callback(RedisModuleKey *key, RedisModuleString* field, RedisModuleString* value, void *privdata) {
|
||||
REDISMODULE_NOT_USED(key);
|
||||
scan_key_pd* pd = privdata;
|
||||
RedisModule_ReplyWithArray(pd->ctx, 2);
|
||||
RedisModule_ReplyWithString(pd->ctx, field);
|
||||
if (value)
|
||||
RedisModule_ReplyWithString(pd->ctx, value);
|
||||
else
|
||||
RedisModule_ReplyWithNull(pd->ctx);
|
||||
pd->nreplies++;
|
||||
}
|
||||
|
||||
int scan_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
{
|
||||
if (argc != 2) {
|
||||
RedisModule_WrongArity(ctx);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
scan_key_pd pd = {
|
||||
.ctx = ctx,
|
||||
.nreplies = 0,
|
||||
};
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ);
|
||||
if (!key) {
|
||||
RedisModule_ReplyWithError(ctx, "not found");
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
|
||||
RedisModuleScanCursor* cursor = RedisModule_ScanCursorCreate();
|
||||
while(RedisModule_ScanKey(key, cursor, scan_key_callback, &pd));
|
||||
RedisModule_ScanCursorDestroy(cursor);
|
||||
|
||||
RedisModule_ReplySetArrayLength(ctx, pd.nreplies);
|
||||
RedisModule_CloseKey(key);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
if (RedisModule_Init(ctx, "scan", 1, REDISMODULE_APIVER_1)== REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "scan.scan_strings", scan_strings, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "scan.scan_key", scan_key, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,12 @@ void *testrdb_type_load(RedisModuleIO *rdb, int encver) {
|
||||
RedisModuleString *str = RedisModule_LoadString(rdb);
|
||||
float f = RedisModule_LoadFloat(rdb);
|
||||
long double ld = RedisModule_LoadLongDouble(rdb);
|
||||
if (RedisModule_IsIOError(rdb))
|
||||
if (RedisModule_IsIOError(rdb)) {
|
||||
RedisModuleCtx *ctx = RedisModule_GetContextFromIO(rdb);
|
||||
if (str)
|
||||
RedisModule_FreeString(ctx, str);
|
||||
return NULL;
|
||||
}
|
||||
/* Using the values only after checking for io errors. */
|
||||
assert(count==1);
|
||||
assert(encver==1);
|
||||
|
||||
+87
-39
@@ -53,6 +53,7 @@ proc kill_server config {
|
||||
}
|
||||
|
||||
# kill server and wait for the process to be totally exited
|
||||
send_data_packet $::test_server_fd server-killing $pid
|
||||
catch {exec kill $pid}
|
||||
if {$::valgrind} {
|
||||
set max_wait 60000
|
||||
@@ -140,6 +141,18 @@ proc tags {tags code} {
|
||||
uplevel 1 $code
|
||||
set ::tags [lrange $::tags 0 end-[llength $tags]]
|
||||
}
|
||||
|
||||
# Write the configuration in the dictionary 'config' in the specified
|
||||
# file name.
|
||||
proc create_server_config_file {filename config} {
|
||||
set fp [open $filename w+]
|
||||
foreach directive [dict keys $config] {
|
||||
puts -nonewline $fp "$directive "
|
||||
puts $fp [dict get $config $directive]
|
||||
}
|
||||
close $fp
|
||||
}
|
||||
|
||||
proc start_server {options {code undefined}} {
|
||||
# If we are running against an external server, we just push the
|
||||
# host/port pair in the stack the first time
|
||||
@@ -221,56 +234,91 @@ proc start_server {options {code undefined}} {
|
||||
|
||||
# write new configuration to temporary file
|
||||
set config_file [tmpfile redis.conf]
|
||||
set fp [open $config_file w+]
|
||||
foreach directive [dict keys $config] {
|
||||
puts -nonewline $fp "$directive "
|
||||
puts $fp [dict get $config $directive]
|
||||
}
|
||||
close $fp
|
||||
create_server_config_file $config_file $config
|
||||
|
||||
set stdout [format "%s/%s" [dict get $config "dir"] "stdout"]
|
||||
set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
|
||||
|
||||
if {$::valgrind} {
|
||||
set pid [exec valgrind --track-origins=yes --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
} elseif ($::stack_logging) {
|
||||
set pid [exec /usr/bin/env MallocStackLogging=1 MallocLogFile=/tmp/malloc_log.txt src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
} else {
|
||||
set pid [exec src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
}
|
||||
# We need a loop here to retry with different ports.
|
||||
set server_started 0
|
||||
while {$server_started == 0} {
|
||||
if {$::verbose} {
|
||||
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
|
||||
}
|
||||
|
||||
# Tell the test server about this new instance.
|
||||
send_data_packet $::test_server_fd server-spawned $pid
|
||||
send_data_packet $::test_server_fd "server-spawning" "port $::port"
|
||||
|
||||
# check that the server actually started
|
||||
# ugly but tries to be as fast as possible...
|
||||
if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
|
||||
if {$::valgrind} {
|
||||
set pid [exec valgrind --track-origins=yes --suppressions=src/valgrind.sup --show-reachable=no --show-possibly-lost=no --leak-check=full src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
} elseif ($::stack_logging) {
|
||||
set pid [exec /usr/bin/env MallocStackLogging=1 MallocLogFile=/tmp/malloc_log.txt src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
} else {
|
||||
set pid [exec src/redis-server $config_file > $stdout 2> $stderr &]
|
||||
}
|
||||
|
||||
if {$::verbose} {
|
||||
puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
|
||||
}
|
||||
# Tell the test server about this new instance.
|
||||
send_data_packet $::test_server_fd server-spawned $pid
|
||||
|
||||
if {$code ne "undefined"} {
|
||||
set serverisup [server_is_up $::host $::port $retrynum]
|
||||
} else {
|
||||
set serverisup 1
|
||||
}
|
||||
# check that the server actually started
|
||||
# ugly but tries to be as fast as possible...
|
||||
if {$::valgrind} {set retrynum 1000} else {set retrynum 100}
|
||||
|
||||
if {$::verbose} {
|
||||
puts ""
|
||||
}
|
||||
# Wait for actual startup
|
||||
set checkperiod 100; # Milliseconds
|
||||
set maxiter [expr {120*1000/100}] ; # Wait up to 2 minutes.
|
||||
set port_busy 0
|
||||
while {![info exists _pid]} {
|
||||
regexp {PID:\s(\d+)} [exec cat $stdout] _ _pid
|
||||
after $checkperiod
|
||||
incr maxiter -1
|
||||
if {$maxiter == 0} {
|
||||
start_server_error $config_file "No PID detected in log $stdout"
|
||||
puts "--- LOG CONTENT ---"
|
||||
puts [exec cat $stdout]
|
||||
puts "-------------------"
|
||||
break
|
||||
}
|
||||
|
||||
if {!$serverisup} {
|
||||
set err {}
|
||||
append err [exec cat $stdout] "\n" [exec cat $stderr]
|
||||
start_server_error $config_file $err
|
||||
return
|
||||
}
|
||||
# Check if the port is actually busy and the server failed
|
||||
# for this reason.
|
||||
if {[regexp {Could not create server TCP} [exec cat $stdout]]} {
|
||||
set port_busy 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# Wait for actual startup
|
||||
while {![info exists _pid]} {
|
||||
regexp {PID:\s(\d+)} [exec cat $stdout] _ _pid
|
||||
after 100
|
||||
# Sometimes we have to try a different port, even if we checked
|
||||
# for availability. Other test clients may grab the port before we
|
||||
# are able to do it for example.
|
||||
if {$port_busy} {
|
||||
puts "Port $::port was already busy, trying another port..."
|
||||
set ::port [find_available_port [expr {$::port+1}]]
|
||||
if {$::tls} {
|
||||
dict set config "tls-port" $::port
|
||||
} else {
|
||||
dict set config port $::port
|
||||
}
|
||||
create_server_config_file $config_file $config
|
||||
continue; # Try again
|
||||
}
|
||||
|
||||
if {$code ne "undefined"} {
|
||||
set serverisup [server_is_up $::host $::port $retrynum]
|
||||
} else {
|
||||
set serverisup 1
|
||||
}
|
||||
|
||||
if {$::verbose} {
|
||||
puts ""
|
||||
}
|
||||
|
||||
if {!$serverisup} {
|
||||
set err {}
|
||||
append err [exec cat $stdout] "\n" [exec cat $stderr]
|
||||
start_server_error $config_file $err
|
||||
return
|
||||
}
|
||||
set server_started 1
|
||||
}
|
||||
|
||||
# setup properties to be able to initialize a client object
|
||||
|
||||
+33
-6
@@ -11,28 +11,55 @@ proc fail {msg} {
|
||||
|
||||
proc assert {condition} {
|
||||
if {![uplevel 1 [list expr $condition]]} {
|
||||
error "assertion:Expected condition '$condition' to be true ([uplevel 1 [list subst -nocommands $condition]])"
|
||||
set context "(context: [info frame -1])"
|
||||
error "assertion:Expected [uplevel 1 [list subst -nocommands $condition]] $context"
|
||||
}
|
||||
}
|
||||
|
||||
proc assert_no_match {pattern value} {
|
||||
if {[string match $pattern $value]} {
|
||||
error "assertion:Expected '$value' to not match '$pattern'"
|
||||
set context "(context: [info frame -1])"
|
||||
error "assertion:Expected '$value' to not match '$pattern' $context"
|
||||
}
|
||||
}
|
||||
|
||||
proc assert_match {pattern value} {
|
||||
if {![string match $pattern $value]} {
|
||||
error "assertion:Expected '$value' to match '$pattern'"
|
||||
set context "(context: [info frame -1])"
|
||||
error "assertion:Expected '$value' to match '$pattern' $context"
|
||||
}
|
||||
}
|
||||
|
||||
proc assert_equal {expected value {detail ""}} {
|
||||
proc assert_equal {value expected {detail ""}} {
|
||||
if {$expected ne $value} {
|
||||
if {$detail ne ""} {
|
||||
set detail " (detail: $detail)"
|
||||
set detail "(detail: $detail)"
|
||||
} else {
|
||||
set detail "(context: [info frame -1])"
|
||||
}
|
||||
error "assertion:Expected '$value' to be equal to '$expected'$detail"
|
||||
error "assertion:Expected '$value' to be equal to '$expected' $detail"
|
||||
}
|
||||
}
|
||||
|
||||
proc assert_lessthan {value expected {detail ""}} {
|
||||
if {!($value < $expected)} {
|
||||
if {$detail ne ""} {
|
||||
set detail "(detail: $detail)"
|
||||
} else {
|
||||
set detail "(context: [info frame -1])"
|
||||
}
|
||||
error "assertion:Expected '$value' to be lessthan to '$expected' $detail"
|
||||
}
|
||||
}
|
||||
|
||||
proc assert_range {value min max {detail ""}} {
|
||||
if {!($value <= $max && $value >= $min)} {
|
||||
if {$detail ne ""} {
|
||||
set detail "(detail: $detail)"
|
||||
} else {
|
||||
set detail "(context: [info frame -1])"
|
||||
}
|
||||
error "assertion:Expected '$value' to be between to '$min' and '$max' $detail"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-10
@@ -47,6 +47,7 @@ set ::all_tests {
|
||||
integration/logging
|
||||
integration/psync2
|
||||
integration/psync2-reg
|
||||
integration/psync2-pingoff
|
||||
unit/pubsub
|
||||
unit/slowlog
|
||||
unit/scripting
|
||||
@@ -87,7 +88,7 @@ set ::file ""; # If set, runs only the tests in this comma separated list
|
||||
set ::curfile ""; # Hold the filename of the current suite
|
||||
set ::accurate 0; # If true runs fuzz tests with more iterations
|
||||
set ::force_failure 0
|
||||
set ::timeout 600; # 10 minutes without progresses will quit the test.
|
||||
set ::timeout 1200; # 20 minutes without progresses will quit the test.
|
||||
set ::last_progress [clock seconds]
|
||||
set ::active_servers {} ; # Pids of active Redis instances.
|
||||
set ::dont_clean 0
|
||||
@@ -211,13 +212,19 @@ proc test_server_main {} {
|
||||
|
||||
# Start the client instances
|
||||
set ::clients_pids {}
|
||||
set start_port [expr {$::port+100}]
|
||||
for {set j 0} {$j < $::numclients} {incr j} {
|
||||
set start_port [find_available_port $start_port]
|
||||
if {$::external} {
|
||||
set p [exec $tclsh [info script] {*}$::argv \
|
||||
--client $port --port $start_port &]
|
||||
--client $port --port $::port &]
|
||||
lappend ::clients_pids $p
|
||||
incr start_port 10
|
||||
} else {
|
||||
set start_port [expr {$::port+100}]
|
||||
for {set j 0} {$j < $::numclients} {incr j} {
|
||||
set start_port [find_available_port $start_port]
|
||||
set p [exec $tclsh [info script] {*}$::argv \
|
||||
--client $port --port $start_port &]
|
||||
lappend ::clients_pids $p
|
||||
incr start_port 10
|
||||
}
|
||||
}
|
||||
|
||||
# Setup global state for the test server
|
||||
@@ -289,7 +296,7 @@ proc read_from_test_client fd {
|
||||
puts "\[$completed_tests_count/$all_tests_count [colorstr yellow $status]\]: $data ($elapsed seconds)"
|
||||
lappend ::clients_time_history $elapsed $data
|
||||
signal_idle_client $fd
|
||||
set ::active_clients_task($fd) DONE
|
||||
set ::active_clients_task($fd) "(DONE) $data"
|
||||
} elseif {$status eq {ok}} {
|
||||
if {!$::quiet} {
|
||||
puts "\[[colorstr green $status]\]: $data"
|
||||
@@ -320,10 +327,16 @@ proc read_from_test_client fd {
|
||||
exit 1
|
||||
} elseif {$status eq {testing}} {
|
||||
set ::active_clients_task($fd) "(IN PROGRESS) $data"
|
||||
} elseif {$status eq {server-spawning}} {
|
||||
set ::active_clients_task($fd) "(SPAWNING SERVER) $data"
|
||||
} elseif {$status eq {server-spawned}} {
|
||||
lappend ::active_servers $data
|
||||
set ::active_clients_task($fd) "(SPAWNED SERVER) pid:$data"
|
||||
} elseif {$status eq {server-killing}} {
|
||||
set ::active_clients_task($fd) "(KILLING SERVER) pid:$data"
|
||||
} elseif {$status eq {server-killed}} {
|
||||
set ::active_servers [lsearch -all -inline -not -exact $::active_servers $data]
|
||||
set ::active_clients_task($fd) "(KILLED SERVER) pid:$data"
|
||||
} else {
|
||||
if {!$::quiet} {
|
||||
puts "\[$status\]: $data"
|
||||
@@ -333,7 +346,7 @@ proc read_from_test_client fd {
|
||||
|
||||
proc show_clients_state {} {
|
||||
# The following loop is only useful for debugging tests that may
|
||||
# enter an infinite loop. Commented out normally.
|
||||
# enter an infinite loop.
|
||||
foreach x $::active_clients {
|
||||
if {[info exist ::active_clients_task($x)]} {
|
||||
puts "$x => $::active_clients_task($x)"
|
||||
@@ -363,8 +376,6 @@ proc signal_idle_client fd {
|
||||
set ::active_clients \
|
||||
[lsearch -all -inline -not -exact $::active_clients $fd]
|
||||
|
||||
if 0 {show_clients_state}
|
||||
|
||||
# New unit to process?
|
||||
if {$::next_test != [llength $::all_tests]} {
|
||||
if {!$::quiet} {
|
||||
@@ -380,6 +391,7 @@ proc signal_idle_client fd {
|
||||
}
|
||||
} else {
|
||||
lappend ::idle_clients $fd
|
||||
set ::active_clients_task($fd) "SLEEPING, no more units to assign"
|
||||
if {[llength $::active_clients] == 0} {
|
||||
the_end
|
||||
}
|
||||
|
||||
@@ -141,4 +141,118 @@ start_server {tags {"acl"}} {
|
||||
r ACL setuser newuser -debug
|
||||
# The test framework will detect a leak if any.
|
||||
}
|
||||
|
||||
test {ACL LOG shows failed command executions at toplevel} {
|
||||
r ACL LOG RESET
|
||||
r ACL setuser antirez >foo on +set ~object:1234
|
||||
r ACL setuser antirez +eval +multi +exec
|
||||
r AUTH antirez foo
|
||||
catch {r GET foo}
|
||||
r AUTH default ""
|
||||
set entry [lindex [r ACL LOG] 0]
|
||||
assert {[dict get $entry username] eq {antirez}}
|
||||
assert {[dict get $entry context] eq {toplevel}}
|
||||
assert {[dict get $entry reason] eq {command}}
|
||||
assert {[dict get $entry object] eq {get}}
|
||||
}
|
||||
|
||||
test {ACL LOG is able to test similar events} {
|
||||
r AUTH antirez foo
|
||||
catch {r GET foo}
|
||||
catch {r GET foo}
|
||||
catch {r GET foo}
|
||||
r AUTH default ""
|
||||
set entry [lindex [r ACL LOG] 0]
|
||||
assert {[dict get $entry count] == 4}
|
||||
}
|
||||
|
||||
test {ACL LOG is able to log keys access violations and key name} {
|
||||
r AUTH antirez foo
|
||||
catch {r SET somekeynotallowed 1234}
|
||||
r AUTH default ""
|
||||
set entry [lindex [r ACL LOG] 0]
|
||||
assert {[dict get $entry reason] eq {key}}
|
||||
assert {[dict get $entry object] eq {somekeynotallowed}}
|
||||
}
|
||||
|
||||
test {ACL LOG RESET is able to flush the entries in the log} {
|
||||
r ACL LOG RESET
|
||||
assert {[llength [r ACL LOG]] == 0}
|
||||
}
|
||||
|
||||
test {ACL LOG can distinguish the transaction context (1)} {
|
||||
r AUTH antirez foo
|
||||
r MULTI
|
||||
catch {r INCR foo}
|
||||
catch {r EXEC}
|
||||
r AUTH default ""
|
||||
set entry [lindex [r ACL LOG] 0]
|
||||
assert {[dict get $entry context] eq {multi}}
|
||||
assert {[dict get $entry object] eq {incr}}
|
||||
}
|
||||
|
||||
test {ACL LOG can distinguish the transaction context (2)} {
|
||||
set rd1 [redis_deferring_client]
|
||||
r ACL SETUSER antirez +incr
|
||||
|
||||
r AUTH antirez foo
|
||||
r MULTI
|
||||
r INCR object:1234
|
||||
$rd1 ACL SETUSER antirez -incr
|
||||
$rd1 read
|
||||
catch {r EXEC}
|
||||
$rd1 close
|
||||
r AUTH default ""
|
||||
set entry [lindex [r ACL LOG] 0]
|
||||
assert {[dict get $entry context] eq {multi}}
|
||||
assert {[dict get $entry object] eq {incr}}
|
||||
r ACL SETUSER antirez -incr
|
||||
}
|
||||
|
||||
test {ACL can log errors in the context of Lua scripting} {
|
||||
r AUTH antirez foo
|
||||
catch {r EVAL {redis.call('incr','foo')} 0}
|
||||
r AUTH default ""
|
||||
set entry [lindex [r ACL LOG] 0]
|
||||
assert {[dict get $entry context] eq {lua}}
|
||||
assert {[dict get $entry object] eq {incr}}
|
||||
}
|
||||
|
||||
test {ACL LOG can accept a numerical argument to show less entries} {
|
||||
r AUTH antirez foo
|
||||
catch {r INCR foo}
|
||||
catch {r INCR foo}
|
||||
catch {r INCR foo}
|
||||
catch {r INCR foo}
|
||||
r AUTH default ""
|
||||
assert {[llength [r ACL LOG]] > 1}
|
||||
assert {[llength [r ACL LOG 2]] == 2}
|
||||
}
|
||||
|
||||
test {ACL LOG can log failed auth attempts} {
|
||||
catch {r AUTH antirez wrong-password}
|
||||
set entry [lindex [r ACL LOG] 0]
|
||||
assert {[dict get $entry context] eq {toplevel}}
|
||||
assert {[dict get $entry reason] eq {auth}}
|
||||
assert {[dict get $entry object] eq {AUTH}}
|
||||
assert {[dict get $entry username] eq {antirez}}
|
||||
}
|
||||
|
||||
test {ACL LOG entries are limited to a maximum amount} {
|
||||
r ACL LOG RESET
|
||||
r CONFIG SET acllog-max-len 5
|
||||
r AUTH antirez foo
|
||||
for {set j 0} {$j < 10} {incr j} {
|
||||
catch {r SET obj:$j 123}
|
||||
}
|
||||
r AUTH default ""
|
||||
assert {[llength [r ACL LOG]] == 5}
|
||||
}
|
||||
|
||||
test {When default user is off, new connections are not authenticated} {
|
||||
r ACL setuser default off
|
||||
catch {set rd1 [redis_deferring_client]} e
|
||||
r ACL setuser default on
|
||||
set e
|
||||
} {*NOAUTH*}
|
||||
}
|
||||
|
||||
@@ -199,3 +199,34 @@ start_server {tags {"bitops"}} {
|
||||
r del mystring
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
start_server {} {
|
||||
set master [srv -1 client]
|
||||
set master_host [srv -1 host]
|
||||
set master_port [srv -1 port]
|
||||
set slave [srv 0 client]
|
||||
|
||||
test {BITFIELD: setup slave} {
|
||||
$slave slaveof $master_host $master_port
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 master_link_status] eq {up}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
}
|
||||
|
||||
test {BITFIELD: write on master, read on slave} {
|
||||
$master del bits
|
||||
assert_equal 0 [$master bitfield bits set u8 0 255]
|
||||
assert_equal 255 [$master bitfield bits set u8 0 100]
|
||||
wait_for_ofs_sync $master $slave
|
||||
assert_equal 100 [$slave bitfield_ro bits get u8 0]
|
||||
}
|
||||
|
||||
test {BITFIELD_RO fails when write option is used} {
|
||||
catch {$slave bitfield_ro bits set u8 0 100 get u8 0} err
|
||||
assert_match {*ERR BITFIELD_RO only supports the GET subcommand*} $err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,4 +219,17 @@ start_server {tags {"expire"}} {
|
||||
set ttl [r ttl foo]
|
||||
assert {$ttl <= 98 && $ttl > 90}
|
||||
}
|
||||
|
||||
test {SET command will remove expire} {
|
||||
r set foo bar EX 100
|
||||
r set foo bar
|
||||
r ttl foo
|
||||
} {-1}
|
||||
|
||||
test {SET - use KEEPTTL option, TTL should not be removed} {
|
||||
r set foo bar EX 100
|
||||
r set foo bar KEEPTTL
|
||||
set ttl [r ttl foo]
|
||||
assert {$ttl <= 100 && $ttl > 90}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ start_server {tags {"geo"}} {
|
||||
r del points
|
||||
r geoadd points -5.6 42.6 test
|
||||
lindex [r geohash points test] 0
|
||||
} {ezs42e44yx}
|
||||
} {ezs42e44yx0}
|
||||
|
||||
test {GEOPOS simple} {
|
||||
r del points
|
||||
|
||||
@@ -57,4 +57,69 @@ start_server {tags {"introspection"}} {
|
||||
fail "Client still listed in CLIENT LIST after SETNAME."
|
||||
}
|
||||
}
|
||||
|
||||
test {CONFIG sanity} {
|
||||
# Do CONFIG GET, CONFIG SET and then CONFIG GET again
|
||||
# Skip immutable configs, one with no get, and other complicated configs
|
||||
set skip_configs {
|
||||
rdbchecksum
|
||||
daemonize
|
||||
io-threads-do-reads
|
||||
tcp-backlog
|
||||
always-show-logo
|
||||
syslog-enabled
|
||||
cluster-enabled
|
||||
aclfile
|
||||
unixsocket
|
||||
pidfile
|
||||
syslog-ident
|
||||
appendfilename
|
||||
supervised
|
||||
syslog-facility
|
||||
databases
|
||||
port
|
||||
io-threads
|
||||
tls-port
|
||||
tls-prefer-server-ciphers
|
||||
tls-cert-file
|
||||
tls-key-file
|
||||
tls-dh-params-file
|
||||
tls-ca-cert-file
|
||||
tls-ca-cert-dir
|
||||
tls-protocols
|
||||
tls-ciphers
|
||||
tls-ciphersuites
|
||||
logfile
|
||||
unixsocketperm
|
||||
slaveof
|
||||
bind
|
||||
requirepass
|
||||
}
|
||||
|
||||
set configs {}
|
||||
foreach {k v} [r config get *] {
|
||||
if {[lsearch $skip_configs $k] != -1} {
|
||||
continue
|
||||
}
|
||||
dict set configs $k $v
|
||||
# try to set the config to the same value it already has
|
||||
r config set $k $v
|
||||
}
|
||||
|
||||
set newconfigs {}
|
||||
foreach {k v} [r config get *] {
|
||||
if {[lsearch $skip_configs $k] != -1} {
|
||||
continue
|
||||
}
|
||||
dict set newconfigs $k $v
|
||||
}
|
||||
|
||||
dict for {k v} $configs {
|
||||
set vv [dict get $newconfigs $k]
|
||||
if {$v != $vv} {
|
||||
fail "config $k mismatch, expecting $v but got $vv"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ start_server {tags {"memefficiency"}} {
|
||||
start_server {tags {"defrag"}} {
|
||||
if {[string match {*jemalloc*} [s mem_allocator]]} {
|
||||
test "Active defrag" {
|
||||
r config set save "" ;# prevent bgsave from interfereing with save below
|
||||
r config set hz 100
|
||||
r config set activedefrag no
|
||||
r config set active-defrag-threshold-lower 5
|
||||
r config set active-defrag-cycle-min 65
|
||||
@@ -46,8 +48,8 @@ start_server {tags {"defrag"}} {
|
||||
r config set active-defrag-ignore-bytes 2mb
|
||||
r config set maxmemory 100mb
|
||||
r config set maxmemory-policy allkeys-lru
|
||||
r debug populate 700000 asdf 150
|
||||
r debug populate 170000 asdf 300
|
||||
r debug populate 700000 asdf1 150
|
||||
r debug populate 170000 asdf2 300
|
||||
r ping ;# trigger eviction following the previous population
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
set frag [s allocator_frag_ratio]
|
||||
@@ -55,6 +57,11 @@ start_server {tags {"defrag"}} {
|
||||
puts "frag $frag"
|
||||
}
|
||||
assert {$frag >= 1.4}
|
||||
|
||||
r config set latency-monitor-threshold 5
|
||||
r latency reset
|
||||
r config set maxmemory 110mb ;# prevent further eviction (not to fail the digest test)
|
||||
set digest [r debug digest]
|
||||
catch {r config set activedefrag yes} e
|
||||
if {![string match {DISABLED*} $e]} {
|
||||
# Wait for the active defrag to start working (decision once a
|
||||
@@ -78,19 +85,37 @@ start_server {tags {"defrag"}} {
|
||||
# Test the the fragmentation is lower.
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
set frag [s allocator_frag_ratio]
|
||||
set max_latency 0
|
||||
foreach event [r latency latest] {
|
||||
lassign $event eventname time latency max
|
||||
if {$eventname == "active-defrag-cycle"} {
|
||||
set max_latency $max
|
||||
}
|
||||
}
|
||||
if {$::verbose} {
|
||||
puts "frag $frag"
|
||||
puts "max latency $max_latency"
|
||||
puts [r latency latest]
|
||||
puts [r latency history active-defrag-cycle]
|
||||
}
|
||||
assert {$frag < 1.1}
|
||||
# due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75,
|
||||
# we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher
|
||||
assert {$max_latency <= 30}
|
||||
} else {
|
||||
set _ ""
|
||||
}
|
||||
} {}
|
||||
# verify the data isn't corrupted or changed
|
||||
set newdigest [r debug digest]
|
||||
assert {$digest eq $newdigest}
|
||||
r save ;# saving an rdb iterates over all the data / pointers
|
||||
} {OK}
|
||||
|
||||
test "Active defrag big keys" {
|
||||
r flushdb
|
||||
r config resetstat
|
||||
r config set save "" ;# prevent bgsave from interfereing with save below
|
||||
r config set hz 100
|
||||
r config set activedefrag no
|
||||
r config set active-defrag-max-scan-fields 1000
|
||||
r config set active-defrag-threshold-lower 5
|
||||
@@ -142,7 +167,7 @@ start_server {tags {"defrag"}} {
|
||||
for {set j 0} {$j < 500000} {incr j} {
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
assert {[r dbsize] == 500010}
|
||||
assert_equal [r dbsize] 500010
|
||||
|
||||
# create some fragmentation
|
||||
for {set j 0} {$j < 500000} {incr j 2} {
|
||||
@@ -151,7 +176,7 @@ start_server {tags {"defrag"}} {
|
||||
for {set j 0} {$j < 500000} {incr j 2} {
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
assert {[r dbsize] == 250010}
|
||||
assert_equal [r dbsize] 250010
|
||||
|
||||
# start defrag
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
@@ -200,14 +225,106 @@ start_server {tags {"defrag"}} {
|
||||
puts [r latency history active-defrag-cycle]
|
||||
}
|
||||
assert {$frag < 1.1}
|
||||
# due to high fragmentation, 10hz, and active-defrag-cycle-max set to 75,
|
||||
# we expect max latency to be not much higher than 75ms
|
||||
assert {$max_latency <= 120}
|
||||
# due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75,
|
||||
# we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher
|
||||
assert {$max_latency <= 30}
|
||||
}
|
||||
# verify the data isn't corrupted or changed
|
||||
set newdigest [r debug digest]
|
||||
assert {$digest eq $newdigest}
|
||||
r save ;# saving an rdb iterates over all the data / pointers
|
||||
} {OK}
|
||||
|
||||
test "Active defrag big list" {
|
||||
r flushdb
|
||||
r config resetstat
|
||||
r config set save "" ;# prevent bgsave from interfereing with save below
|
||||
r config set hz 100
|
||||
r config set activedefrag no
|
||||
r config set active-defrag-max-scan-fields 1000
|
||||
r config set active-defrag-threshold-lower 5
|
||||
r config set active-defrag-cycle-min 65
|
||||
r config set active-defrag-cycle-max 75
|
||||
r config set active-defrag-ignore-bytes 2mb
|
||||
r config set maxmemory 0
|
||||
r config set list-max-ziplist-size 5 ;# list of 500k items will have 100k quicklist nodes
|
||||
|
||||
# create big keys with 10k items
|
||||
set rd [redis_deferring_client]
|
||||
|
||||
set expected_frag 1.7
|
||||
# add a mass of list nodes to two lists (allocations are interlaced)
|
||||
set val [string repeat A 100] ;# 5 items of 100 bytes puts us in the 640 bytes bin, which has 32 regs, so high potential for fragmentation
|
||||
for {set j 0} {$j < 500000} {incr j} {
|
||||
$rd lpush biglist1 $val
|
||||
$rd lpush biglist2 $val
|
||||
}
|
||||
for {set j 0} {$j < 500000} {incr j} {
|
||||
$rd read ; # Discard replies
|
||||
$rd read ; # Discard replies
|
||||
}
|
||||
|
||||
# create some fragmentation
|
||||
r del biglist2
|
||||
|
||||
# start defrag
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
set frag [s allocator_frag_ratio]
|
||||
if {$::verbose} {
|
||||
puts "frag $frag"
|
||||
}
|
||||
|
||||
assert {$frag >= $expected_frag}
|
||||
r config set latency-monitor-threshold 5
|
||||
r latency reset
|
||||
|
||||
set digest [r debug digest]
|
||||
catch {r config set activedefrag yes} e
|
||||
if {![string match {DISABLED*} $e]} {
|
||||
# wait for the active defrag to start working (decision once a second)
|
||||
wait_for_condition 50 100 {
|
||||
[s active_defrag_running] ne 0
|
||||
} else {
|
||||
fail "defrag not started."
|
||||
}
|
||||
|
||||
# wait for the active defrag to stop working
|
||||
wait_for_condition 500 100 {
|
||||
[s active_defrag_running] eq 0
|
||||
} else {
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
puts [r info memory]
|
||||
puts [r info stats]
|
||||
puts [r memory malloc-stats]
|
||||
fail "defrag didn't stop."
|
||||
}
|
||||
|
||||
# test the the fragmentation is lower
|
||||
after 120 ;# serverCron only updates the info once in 100ms
|
||||
set frag [s allocator_frag_ratio]
|
||||
set max_latency 0
|
||||
foreach event [r latency latest] {
|
||||
lassign $event eventname time latency max
|
||||
if {$eventname == "active-defrag-cycle"} {
|
||||
set max_latency $max
|
||||
}
|
||||
}
|
||||
if {$::verbose} {
|
||||
puts "frag $frag"
|
||||
puts "max latency $max_latency"
|
||||
puts [r latency latest]
|
||||
puts [r latency history active-defrag-cycle]
|
||||
}
|
||||
assert {$frag < 1.1}
|
||||
# due to high fragmentation, 100hz, and active-defrag-cycle-max set to 75,
|
||||
# we expect max latency to be not much higher than 7.5ms but due to rare slowness threshold is set higher
|
||||
assert {$max_latency <= 30}
|
||||
}
|
||||
# verify the data isn't corrupted or changed
|
||||
set newdigest [r debug digest]
|
||||
assert {$digest eq $newdigest}
|
||||
r save ;# saving an rdb iterates over all the data / pointers
|
||||
r del biglist1 ;# coverage for quicklistBookmarksClear
|
||||
} {1}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
set testmodule [file normalize tests/modules/auth.so]
|
||||
|
||||
start_server {tags {"modules"}} {
|
||||
r module load $testmodule
|
||||
|
||||
test {Modules can create a user that can be authenticated} {
|
||||
# Make sure we start authenticated with default user
|
||||
r auth default ""
|
||||
assert_equal [r acl whoami] "default"
|
||||
r auth.createmoduleuser
|
||||
|
||||
set id [r auth.authmoduleuser]
|
||||
assert_equal [r client id] $id
|
||||
|
||||
# Verify returned id is the same as our current id and
|
||||
# we are authenticated with the specified user
|
||||
assert_equal [r acl whoami] "global"
|
||||
}
|
||||
|
||||
test {De-authenticating clients is tracked and kills clients} {
|
||||
assert_equal [r auth.changecount] 0
|
||||
r auth.createmoduleuser
|
||||
|
||||
# Catch the I/O exception that was thrown when Redis
|
||||
# disconnected with us.
|
||||
catch { [r ping] } e
|
||||
assert_match {*I/O*} $e
|
||||
|
||||
# Check that a user change was registered
|
||||
assert_equal [r auth.changecount] 1
|
||||
}
|
||||
|
||||
test {Modules cant authenticate with ACLs users that dont exist} {
|
||||
catch { [r auth.authrealuser auth-module-test-fake] } e
|
||||
assert_match {*Invalid user*} $e
|
||||
}
|
||||
|
||||
test {Modules can authenticate with ACL users} {
|
||||
assert_equal [r acl whoami] "default"
|
||||
|
||||
# Create user to auth into
|
||||
r acl setuser auth-module-test on allkeys allcommands
|
||||
|
||||
set id [r auth.authrealuser auth-module-test]
|
||||
|
||||
# Verify returned id is the same as our current id and
|
||||
# we are authenticated with the specified user
|
||||
assert_equal [r client id] $id
|
||||
assert_equal [r acl whoami] "auth-module-test"
|
||||
}
|
||||
|
||||
test {Client callback is called on user switch} {
|
||||
assert_equal [r auth.changecount] 0
|
||||
|
||||
# Auth again and validate change count
|
||||
r auth.authrealuser auth-module-test
|
||||
assert_equal [r auth.changecount] 1
|
||||
|
||||
# Re-auth with the default user
|
||||
r auth default ""
|
||||
assert_equal [r auth.changecount] 1
|
||||
assert_equal [r acl whoami] "default"
|
||||
|
||||
# Re-auth with the default user again, to
|
||||
# verify the callback isn't fired again
|
||||
r auth default ""
|
||||
assert_equal [r auth.changecount] 0
|
||||
assert_equal [r acl whoami] "default"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,37 +3,70 @@ set testmodule [file normalize tests/modules/blockonkeys.so]
|
||||
start_server {tags {"modules"}} {
|
||||
r module load $testmodule
|
||||
|
||||
test "Module client blocked on keys: Circular BPOPPUSH" {
|
||||
set rd1 [redis_deferring_client]
|
||||
set rd2 [redis_deferring_client]
|
||||
|
||||
r del src dst
|
||||
|
||||
$rd1 fsl.bpoppush src dst 0
|
||||
$rd2 fsl.bpoppush dst src 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {2}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
|
||||
r fsl.push src 42
|
||||
|
||||
assert_equal {42} [r fsl.getall src]
|
||||
assert_equal {} [r fsl.getall dst]
|
||||
}
|
||||
|
||||
test "Module client blocked on keys: Self-referential BPOPPUSH" {
|
||||
set rd1 [redis_deferring_client]
|
||||
|
||||
r del src
|
||||
|
||||
$rd1 fsl.bpoppush src src 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r fsl.push src 42
|
||||
|
||||
assert_equal {42} [r fsl.getall src]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (no metadata): No block} {
|
||||
r del k
|
||||
r fsl.push k 33
|
||||
r fsl.push k 34
|
||||
r fsl.bpop2 k 0
|
||||
} {34 33}
|
||||
r fsl.bpop k 0
|
||||
} {34}
|
||||
|
||||
test {Module client blocked on keys (no metadata): Timeout} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
r fsl.push k 33
|
||||
$rd fsl.bpop2 k 1
|
||||
$rd fsl.bpop k 1
|
||||
assert_equal {Request timedout} [$rd read]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (no metadata): Blocked, case 1} {
|
||||
test {Module client blocked on keys (no metadata): Blocked} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
r fsl.push k 33
|
||||
$rd fsl.bpop2 k 0
|
||||
$rd fsl.bpop k 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r fsl.push k 34
|
||||
assert_equal {34 33} [$rd read]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (no metadata): Blocked, case 2} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
r fsl.push k 33
|
||||
r fsl.push k 34
|
||||
$rd fsl.bpop2 k 0
|
||||
assert_equal {34 33} [$rd read]
|
||||
assert_equal {34} [$rd read]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (with metadata): No block} {
|
||||
@@ -45,24 +78,42 @@ start_server {tags {"modules"}} {
|
||||
test {Module client blocked on keys (with metadata): Timeout} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
$rd client id
|
||||
set cid [$rd read]
|
||||
r fsl.push k 33
|
||||
$rd fsl.bpopgt k 35 1
|
||||
assert_equal {Request timedout} [$rd read]
|
||||
r client kill id $cid ;# try to smoke-out client-related memory leak
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (with metadata): Blocked, case 1} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
$rd client id
|
||||
set cid [$rd read]
|
||||
r fsl.push k 33
|
||||
$rd fsl.bpopgt k 33 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r fsl.push k 34
|
||||
assert_equal {34} [$rd read]
|
||||
r client kill id $cid ;# try to smoke-out client-related memory leak
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (with metadata): Blocked, case 2} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
$rd fsl.bpopgt k 35 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r fsl.push k 33
|
||||
r fsl.push k 34
|
||||
r fsl.push k 35
|
||||
@@ -70,16 +121,68 @@ start_server {tags {"modules"}} {
|
||||
assert_equal {36} [$rd read]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (with metadata): Blocked, CLIENT KILL} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
$rd client id
|
||||
set cid [$rd read]
|
||||
$rd fsl.bpopgt k 35 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r client kill id $cid ;# try to smoke-out client-related memory leak
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (with metadata): Blocked, CLIENT UNBLOCK TIMEOUT} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
$rd client id
|
||||
set cid [$rd read]
|
||||
$rd fsl.bpopgt k 35 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r client unblock $cid timeout ;# try to smoke-out client-related memory leak
|
||||
assert_equal {Request timedout} [$rd read]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (with metadata): Blocked, CLIENT UNBLOCK ERROR} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
$rd client id
|
||||
set cid [$rd read]
|
||||
$rd fsl.bpopgt k 35 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r client unblock $cid error ;# try to smoke-out client-related memory leak
|
||||
assert_error "*unblocked*" {$rd read}
|
||||
}
|
||||
|
||||
test {Module client blocked on keys does not wake up on wrong type} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
$rd fsl.bpop2 k 0
|
||||
$rd fsl.bpop k 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r lpush k 12
|
||||
r lpush k 13
|
||||
r lpush k 14
|
||||
r del k
|
||||
r fsl.push k 33
|
||||
r fsl.push k 34
|
||||
assert_equal {34 33} [$rd read]
|
||||
assert_equal {34} [$rd read]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
set testmodule [file normalize tests/modules/datatype.so]
|
||||
|
||||
start_server {tags {"modules"}} {
|
||||
r module load $testmodule
|
||||
|
||||
test {DataType: Test module is sane, GET/SET work.} {
|
||||
r datatype.set dtkey 100 stringval
|
||||
assert {[r datatype.get dtkey] eq {100 stringval}}
|
||||
}
|
||||
|
||||
test {DataType: RM_SaveDataTypeToString(), RM_LoadDataTypeFromString() work} {
|
||||
r datatype.set dtkey -1111 MyString
|
||||
set encoded [r datatype.dump dtkey]
|
||||
|
||||
r datatype.restore dtkeycopy $encoded
|
||||
assert {[r datatype.get dtkeycopy] eq {-1111 MyString}}
|
||||
}
|
||||
|
||||
test {DataType: Handle truncated RM_LoadDataTypeFromString()} {
|
||||
r datatype.set dtkey -1111 MyString
|
||||
set encoded [r datatype.dump dtkey]
|
||||
set truncated [string range $encoded 0 end-1]
|
||||
|
||||
catch {r datatype.restore dtkeycopy $truncated} e
|
||||
set e
|
||||
} {*Invalid*}
|
||||
|
||||
test {DataType: ModuleTypeReplaceValue() happy path works} {
|
||||
r datatype.set key-a 1 AAA
|
||||
r datatype.set key-b 2 BBB
|
||||
|
||||
assert {[r datatype.swap key-a key-b] eq {OK}}
|
||||
assert {[r datatype.get key-a] eq {2 BBB}}
|
||||
assert {[r datatype.get key-b] eq {1 AAA}}
|
||||
}
|
||||
|
||||
test {DataType: ModuleTypeReplaceValue() fails on non-module keys} {
|
||||
r datatype.set key-a 1 AAA
|
||||
r set key-b RedisString
|
||||
|
||||
catch {r datatype.swap key-a key-b} e
|
||||
set e
|
||||
} {*ERR*}
|
||||
}
|
||||
@@ -20,9 +20,8 @@ start_server {tags {"modules"}} {
|
||||
|
||||
test {Module fork kill} {
|
||||
r fork.create 3
|
||||
after 20
|
||||
after 250
|
||||
r fork.kill
|
||||
after 100
|
||||
|
||||
assert {[count_log_message "fork child started"] eq "2"}
|
||||
assert {[count_log_message "Received SIGUSR1 in child"] eq "1"}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
set testmodule [file normalize tests/modules/hooks.so]
|
||||
|
||||
tags "modules" {
|
||||
start_server {} {
|
||||
r module load $testmodule
|
||||
r config set appendonly yes
|
||||
start_server [list overrides [list loadmodule "$testmodule" appendonly yes]] {
|
||||
|
||||
test {Test clients connection / disconnection hooks} {
|
||||
for {set j 0} {$j < 2} {incr j} {
|
||||
@@ -44,15 +42,19 @@ tags "modules" {
|
||||
r set "bar$j" x
|
||||
}
|
||||
# set some configs that will cause many loading progress events during aof loading
|
||||
r config set key-load-delay 1
|
||||
r config set key-load-delay 500
|
||||
r config set dynamic-hz no
|
||||
r config set hz 500
|
||||
r DEBUG LOADAOF
|
||||
assert_equal [r hooks.event_last loading-aof-start] 0
|
||||
assert_equal [r hooks.event_last loading-end] 0
|
||||
assert {[r hooks.event_count loading-rdb-start] == 0}
|
||||
assert {[r hooks.event_count loading-progress-rdb] >= 2} ;# comes from the preamble section
|
||||
assert {[r hooks.event_count loading-progress-aof] >= 2}
|
||||
assert_lessthan 2 [r hooks.event_count loading-progress-rdb] ;# comes from the preamble section
|
||||
assert_lessthan 2 [r hooks.event_count loading-progress-aof]
|
||||
if {$::verbose} {
|
||||
puts "rdb progress events [r hooks.event_count loading-progress-rdb]"
|
||||
puts "aof progress events [r hooks.event_count loading-progress-aof]"
|
||||
}
|
||||
}
|
||||
# undo configs before next test
|
||||
r config set dynamic-hz yes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user