Compare commits
572
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f7d08b76d | ||
|
|
2bea502d25 | ||
|
|
b5931405ff | ||
|
|
fd4413002d | ||
|
|
77ff332b4c | ||
|
|
4f0f799c96 | ||
|
|
0c1273c389 | ||
|
|
708a4e8a9b | ||
|
|
eac4115d36 | ||
|
|
d075df1768 | ||
|
|
80a49c37f9 | ||
|
|
60870d3a10 | ||
|
|
d90f599b4d | ||
|
|
8ee3bddfc7 | ||
|
|
4780fe78ba | ||
|
|
f3b77510ef | ||
|
|
7f3fcedb8c | ||
|
|
f93b2fa524 | ||
|
|
89682d96ea | ||
|
|
920e108f84 | ||
|
|
7569b210d6 | ||
|
|
3c610b4e8d | ||
|
|
16b2d07f0a | ||
|
|
19f3358564 | ||
|
|
4891612bb5 | ||
|
|
4d2a31aed8 | ||
|
|
9f63fc98df | ||
|
|
1a9e70c1d7 | ||
|
|
69b1b5be6b | ||
|
|
1fd97ee7f1 | ||
|
|
1a9855d7d9 | ||
|
|
32a6e3e48e | ||
|
|
c9e6cda9e8 | ||
|
|
d3eeacf93d | ||
|
|
da5dc4583f | ||
|
|
0fefed25e4 | ||
|
|
e9fbc96033 | ||
|
|
08ec8f71ca | ||
|
|
09e1fe274d | ||
|
|
09f9e4b030 | ||
|
|
8d8d68ddf3 | ||
|
|
7a7fbe708a | ||
|
|
b5830486ca | ||
|
|
b7a2a53a8c | ||
|
|
278bd6e3b6 | ||
|
|
61d9a1542a | ||
|
|
0e7ea0aaa3 | ||
|
|
3b38164e8f | ||
|
|
ef9fe9b0cb | ||
|
|
8066d2a197 | ||
|
|
d3f4dec440 | ||
|
|
33f42665a8 | ||
|
|
68d71d8370 | ||
|
|
62588dbfa7 | ||
|
|
bb78454b0f | ||
|
|
7a41047a61 | ||
|
|
4eb3028bc6 | ||
|
|
5d09f9bc8e | ||
|
|
2810de9fc1 | ||
|
|
f5c63ce0ae | ||
|
|
7f289c3b92 | ||
|
|
7ab62d4b92 | ||
|
|
495dd0da54 | ||
|
|
c1ccf0f188 | ||
|
|
a50dad73c2 | ||
|
|
d6294d05cd | ||
|
|
8c56fc864b | ||
|
|
98b1314f07 | ||
|
|
08b03e2322 | ||
|
|
239069dec7 | ||
|
|
90bf631345 | ||
|
|
2f8a07498e | ||
|
|
436ed56d45 | ||
|
|
680f89fbf9 | ||
|
|
388efbf8b6 | ||
|
|
0ccbdcee26 | ||
|
|
707a59c643 | ||
|
|
c696aebdba | ||
|
|
1cac9b4bf2 | ||
|
|
f63c5c7bfe | ||
|
|
668661da25 | ||
|
|
3c2800e333 | ||
|
|
7ac7ffd560 | ||
|
|
cc10172176 | ||
|
|
72420b0d7a | ||
|
|
33a50d24a5 | ||
|
|
6a92836f40 | ||
|
|
f179f71e42 | ||
|
|
1825a4ec22 | ||
|
|
9d20fdb404 | ||
|
|
ded1980e0d | ||
|
|
c3df78c2a4 | ||
|
|
8d38ef20a3 | ||
|
|
9b7009b141 | ||
|
|
0580244217 | ||
|
|
d5194dafd2 | ||
|
|
8897c15406 | ||
|
|
6dd5bad49b | ||
|
|
8302610199 | ||
|
|
dc5edc7bbe | ||
|
|
5f29e2e220 | ||
|
|
e1839ab327 | ||
|
|
3b207b894a | ||
|
|
7e350b0931 | ||
|
|
91238a6031 | ||
|
|
ee2da67cc6 | ||
|
|
78022492d5 | ||
|
|
232dca7fbc | ||
|
|
7f98129a81 | ||
|
|
71265fe389 | ||
|
|
8115be6e10 | ||
|
|
90e7b5a97a | ||
|
|
43151baf61 | ||
|
|
d3c17c9d0f | ||
|
|
296bd0976c | ||
|
|
e08c9c154b | ||
|
|
c6b1252f1d | ||
|
|
ce54e2991c | ||
|
|
c9274498db | ||
|
|
635d8d83d2 | ||
|
|
7c23e534ce | ||
|
|
9ea8ec421d | ||
|
|
1b7407fadf | ||
|
|
3bbf9747a3 | ||
|
|
f72f4ea311 | ||
|
|
84bdd44039 | ||
|
|
ef1833b3f9 | ||
|
|
623afd5e4b | ||
|
|
12b5ff1095 | ||
|
|
254d897e9e | ||
|
|
7f79849caa | ||
|
|
3ef2c8316e | ||
|
|
57aea4632f | ||
|
|
ba5145b8e9 | ||
|
|
76c59f0e44 | ||
|
|
1c6367144b | ||
|
|
6a3fca4c67 | ||
|
|
9ec144ea30 | ||
|
|
d04b521150 | ||
|
|
516f1c7722 | ||
|
|
8db67a556b | ||
|
|
db3d626bac | ||
|
|
71439a07fd | ||
|
|
c8a26834fc | ||
|
|
a13ba75091 | ||
|
|
500e51171d | ||
|
|
7854daa140 | ||
|
|
d38d82af05 | ||
|
|
4d747bb850 | ||
|
|
8824b509b7 | ||
|
|
000b055b76 | ||
|
|
9b2a0d5497 | ||
|
|
134b258252 | ||
|
|
1293e2a565 | ||
|
|
3cc4f469bc | ||
|
|
f4edd2b924 | ||
|
|
cedcc54e4a | ||
|
|
26e98da20b | ||
|
|
0e91093902 | ||
|
|
67452e9136 | ||
|
|
30f666ef4a | ||
|
|
bc6c1c40db | ||
|
|
76419d8d5b | ||
|
|
72ba60699d | ||
|
|
2ca2175362 | ||
|
|
bd7ddd79dd | ||
|
|
d13bc1433e | ||
|
|
44c5bce0f4 | ||
|
|
f066e52659 | ||
|
|
232149662c | ||
|
|
80bccd7195 | ||
|
|
b4867130f4 | ||
|
|
2c6ee0f9b3 | ||
|
|
107e93e75a | ||
|
|
9edb893c7d | ||
|
|
c55254a5f4 | ||
|
|
dfd250132d | ||
|
|
392a2566b6 | ||
|
|
7602f69530 | ||
|
|
c4f3585e1f | ||
|
|
d037e98711 | ||
|
|
e00ab32454 | ||
|
|
94f64de35c | ||
|
|
752d636fa6 | ||
|
|
552091f990 | ||
|
|
2280f4f7c4 | ||
|
|
e084b8ccbf | ||
|
|
fa726e2af5 | ||
|
|
40244b10f0 | ||
|
|
beab31512e | ||
|
|
07ccb642b7 | ||
|
|
60fdaf072d | ||
|
|
48b31b0dce | ||
|
|
ef3ff40206 | ||
|
|
ee223fb8c3 | ||
|
|
41295e5595 | ||
|
|
0ed3970f2c | ||
|
|
605dddbbc0 | ||
|
|
1f43bf29a3 | ||
|
|
69f0c6788f | ||
|
|
bc53a3abb9 | ||
|
|
fefe546068 | ||
|
|
a8862972b6 | ||
|
|
5e86daf947 | ||
|
|
7c8cf5acdd | ||
|
|
39e9eda377 | ||
|
|
62485232e8 | ||
|
|
a54873092e | ||
|
|
1637522f00 | ||
|
|
e8b4291a0f | ||
|
|
8fcfd374d7 | ||
|
|
d7089ddddc | ||
|
|
17b4cd83f4 | ||
|
|
45123169bc | ||
|
|
5ad588f0f4 | ||
|
|
11801e1a78 | ||
|
|
c1f13575d7 | ||
|
|
275a2d49cc | ||
|
|
44ad514185 | ||
|
|
c7951f4304 | ||
|
|
a69bc5befe | ||
|
|
5314099d04 | ||
|
|
c595050012 | ||
|
|
c801283fd2 | ||
|
|
4c4f50e1c1 | ||
|
|
a7b46e0e64 | ||
|
|
80e129d93e | ||
|
|
88805cbb3e | ||
|
|
6b402733e1 | ||
|
|
1c637de98c | ||
|
|
90b52fde57 | ||
|
|
89cbb5df06 | ||
|
|
175515c944 | ||
|
|
3997dd6eaa | ||
|
|
bd80291c36 | ||
|
|
4369cbce08 | ||
|
|
1ed821e28d | ||
|
|
b49bcd01d0 | ||
|
|
09d1849ed5 | ||
|
|
bdf6306f29 | ||
|
|
50222af5f4 | ||
|
|
643ee6e38c | ||
|
|
8b609c9986 | ||
|
|
2710260594 | ||
|
|
a677923d7c | ||
|
|
427e440a58 | ||
|
|
28f9ca4e1d | ||
|
|
4bf9efe20f | ||
|
|
4fbd7a39f8 | ||
|
|
2480db53f1 | ||
|
|
e5e4d2ef08 | ||
|
|
713800d20a | ||
|
|
e79ee263cf | ||
|
|
505cc70ff8 | ||
|
|
3c36561d27 | ||
|
|
3761582ffc | ||
|
|
edc47a3ad2 | ||
|
|
9872af6d52 | ||
|
|
3f399c3bff | ||
|
|
eaaff6211b | ||
|
|
43ebb7ee01 | ||
|
|
de8fdaacfc | ||
|
|
dc8f111251 | ||
|
|
83a6e81db3 | ||
|
|
882ca6962f | ||
|
|
1eb0994ecc | ||
|
|
bcc0916d08 | ||
|
|
1b2f23f3c4 | ||
|
|
de0ae56c84 | ||
|
|
56c3dfa195 | ||
|
|
2687f2283c | ||
|
|
cfbaf8f1f3 | ||
|
|
4e4099b95d | ||
|
|
6dd4d864a9 | ||
|
|
3aff0e8cb5 | ||
|
|
880b563e2d | ||
|
|
83c8783598 | ||
|
|
fd22e3acae | ||
|
|
dfab3cba2a | ||
|
|
a3fb28edce | ||
|
|
f4b4db1387 | ||
|
|
d22f1ef032 | ||
|
|
6455274d1c | ||
|
|
0edbe953ea | ||
|
|
9714bba266 | ||
|
|
eb53f15a3e | ||
|
|
a77f836e0d | ||
|
|
0f0610eb01 | ||
|
|
a745e4234d | ||
|
|
9974be13a4 | ||
|
|
69a628d0f0 | ||
|
|
a04b43c7ea | ||
|
|
8977a90c50 | ||
|
|
3a745674cf | ||
|
|
22770d762c | ||
|
|
fbac534fd3 | ||
|
|
4987233795 | ||
|
|
80c471f58e | ||
|
|
8defa5da3c | ||
|
|
e1e3eacae3 | ||
|
|
b501fd5d3e | ||
|
|
bedc3dee24 | ||
|
|
9a6fa7d082 | ||
|
|
101e419ffc | ||
|
|
726debb835 | ||
|
|
0b87f78a5f | ||
|
|
6aa8ac70a4 | ||
|
|
48040b0266 | ||
|
|
7cc2056965 | ||
|
|
2007d30c9d | ||
|
|
6a2981101b | ||
|
|
041161b7ce | ||
|
|
f04d799bb9 | ||
|
|
affd93654f | ||
|
|
4c405ad07f | ||
|
|
5c6d4b4ad4 | ||
|
|
a67a8dbf2f | ||
|
|
c4ab5a05a7 | ||
|
|
dc0b628a8e | ||
|
|
4566fbc79d | ||
|
|
5eca170c5b | ||
|
|
260b53a284 | ||
|
|
5d12f9d98b | ||
|
|
6b7ad8381a | ||
|
|
3454a04399 | ||
|
|
d6aeca862c | ||
|
|
a996b2a285 | ||
|
|
1a8447b6c1 | ||
|
|
8dde46ad16 | ||
|
|
b362a1b758 | ||
|
|
55e9df8a41 | ||
|
|
b0d2270290 | ||
|
|
af2f668264 | ||
|
|
f9a3e6ef79 | ||
|
|
01e0341a5f | ||
|
|
f9b3ce9a56 | ||
|
|
36e66d861f | ||
|
|
e33fdbe8e3 | ||
|
|
1c8b22486d | ||
|
|
7ded552d6d | ||
|
|
f92b3273fe | ||
|
|
57b6c34308 | ||
|
|
3f82e59c0d | ||
|
|
6a73aca3fe | ||
|
|
ef8413db74 | ||
|
|
05579e38d7 | ||
|
|
11c53f8ca1 | ||
|
|
0bd7091b88 | ||
|
|
5fc16f173b | ||
|
|
45b7f77970 | ||
|
|
8576b0aef7 | ||
|
|
4b0fa7a71f | ||
|
|
282107609f | ||
|
|
18c5ab930d | ||
|
|
4ce6bff28f | ||
|
|
2ba52889b5 | ||
|
|
6a39ece652 | ||
|
|
0ff35370d2 | ||
|
|
2d11ee956f | ||
|
|
851b2ed30f | ||
|
|
148e491148 | ||
|
|
8afca145b9 | ||
|
|
9a278db2c0 | ||
|
|
2647903616 | ||
|
|
27b7fb5a93 | ||
|
|
233aa2d331 | ||
|
|
a84940721f | ||
|
|
6c8a8f2eb7 | ||
|
|
5c75840641 | ||
|
|
2da823c43d | ||
|
|
bfcba42066 | ||
|
|
efed898a4c | ||
|
|
eb0fbd71b4 | ||
|
|
ed08feb70b | ||
|
|
2ffb4413dd | ||
|
|
55dae693bf | ||
|
|
9b3098b93a | ||
|
|
263dbadcbe | ||
|
|
a622f6c018 | ||
|
|
38b0d25af2 | ||
|
|
34ebd8985a | ||
|
|
46286e6411 | ||
|
|
2d4143fd42 | ||
|
|
3546d9ce5f | ||
|
|
b404a6cef7 | ||
|
|
e30ba94fe8 | ||
|
|
9c771145bc | ||
|
|
4a1d6c7df5 | ||
|
|
72e0368a36 | ||
|
|
c7841c2b71 | ||
|
|
1b9b19ba8f | ||
|
|
7da266e6c4 | ||
|
|
93d803c9a6 | ||
|
|
8943403208 | ||
|
|
7673d88df7 | ||
|
|
f1de29b34e | ||
|
|
53fe558eaf | ||
|
|
c92b02ddc2 | ||
|
|
be76ed0c60 | ||
|
|
3fd7315149 | ||
|
|
a22168e49e | ||
|
|
0e222fbec2 | ||
|
|
34a5615e1a | ||
|
|
1d2fcf6f45 | ||
|
|
2546158d2d | ||
|
|
c0952c0d15 | ||
|
|
1f37f1dd53 | ||
|
|
7928f578e2 | ||
|
|
bb2bed7866 | ||
|
|
246980d091 | ||
|
|
fa7de8c499 | ||
|
|
7642f9d517 | ||
|
|
a72af0eac6 | ||
|
|
1d1bf7f032 | ||
|
|
cfd969c777 | ||
|
|
3e1fb5ff4c | ||
|
|
c6c71abe55 | ||
|
|
dfbce91a14 | ||
|
|
1705e42eb2 | ||
|
|
a0dd6f822b | ||
|
|
2eed31a59e | ||
|
|
37fb606cdc | ||
|
|
1898e6ce7f | ||
|
|
82fc63d151 | ||
|
|
839bb52cc2 | ||
|
|
2e1cd82d96 | ||
|
|
17233080c3 | ||
|
|
42bce87a83 | ||
|
|
8bf42f6031 | ||
|
|
c2b104c73c | ||
|
|
7b75f4ae7e | ||
|
|
adc4e031bf | ||
|
|
20ec1f0ced | ||
|
|
8e5423eb85 | ||
|
|
395063d74e | ||
|
|
b221ca41da | ||
|
|
eea0d3c50a | ||
|
|
4cb9ee111e | ||
|
|
5ad888ba17 | ||
|
|
d928487f2b | ||
|
|
af675f0a58 | ||
|
|
f2cd16bea5 | ||
|
|
02d729b492 | ||
|
|
447da44d96 | ||
|
|
868b29252b | ||
|
|
319f2ee659 | ||
|
|
22c166da5a | ||
|
|
c03c591330 | ||
|
|
fcd5ef1624 | ||
|
|
656e4b2f9d | ||
|
|
2c7972cec8 | ||
|
|
1203a04f5e | ||
|
|
aff86fa1f5 | ||
|
|
45a6c5be2a | ||
|
|
186df14811 | ||
|
|
b59f04a099 | ||
|
|
a3f2437bdd | ||
|
|
df9112354b | ||
|
|
5b06bdf457 | ||
|
|
a4d1201eba | ||
|
|
273d8191e1 | ||
|
|
fbbcc6a657 | ||
|
|
70c4bcb7bc | ||
|
|
63addc5c1e | ||
|
|
724740cc19 | ||
|
|
ddc87eef4f | ||
|
|
741f29ea52 | ||
|
|
201168368a | ||
|
|
8c297e8b43 | ||
|
|
bd2f3f6bb1 | ||
|
|
c0c06b8456 | ||
|
|
ab237a8e26 | ||
|
|
1ce3cf7a8f | ||
|
|
3662289995 | ||
|
|
83d4311acd | ||
|
|
a633f8e130 | ||
|
|
24c455381a | ||
|
|
c609f240a5 | ||
|
|
9e97173988 | ||
|
|
0e77cef096 | ||
|
|
acfe9d138a | ||
|
|
4e933e0059 | ||
|
|
41607dfd25 | ||
|
|
1d073a64f7 | ||
|
|
2db31fd4bb | ||
|
|
64242757d7 | ||
|
|
50be4a1f5c | ||
|
|
9a76472d71 | ||
|
|
a330d06c82 | ||
|
|
d42602ffc8 | ||
|
|
7b5f0223f8 | ||
|
|
037b00dece | ||
|
|
0e97ae79b0 | ||
|
|
f7740fafbd | ||
|
|
0c00837669 | ||
|
|
dc600a25cd | ||
|
|
d6827ab638 | ||
|
|
89ec14531b | ||
|
|
cd25ed17b9 | ||
|
|
6bfb4745fa | ||
|
|
4724548e07 | ||
|
|
4b79fdf1d6 | ||
|
|
a1e081f719 | ||
|
|
8b4fe752ec | ||
|
|
9e5bf0471a | ||
|
|
c2ecdcde98 | ||
|
|
4f742bd6bf | ||
|
|
a4efac0067 | ||
|
|
2c07c107cb | ||
|
|
270903d6b2 | ||
|
|
faf3dbfcf9 | ||
|
|
49841a54db | ||
|
|
77a7ec7200 | ||
|
|
48dfd42d72 | ||
|
|
b4b0251ca6 | ||
|
|
f7209749a6 | ||
|
|
5171a3ff8e | ||
|
|
af7069627e | ||
|
|
3ad68a07ff | ||
|
|
68382091a0 | ||
|
|
6b0cdbd903 | ||
|
|
574017b7d2 | ||
|
|
cd2b5a79ff | ||
|
|
8246a38e83 | ||
|
|
b80e4b6925 | ||
|
|
5e0d9841d4 | ||
|
|
e27a040146 | ||
|
|
c5dfff465e | ||
|
|
86aade7475 | ||
|
|
071e235a52 | ||
|
|
6acd8a31a4 | ||
|
|
20bc3786c0 | ||
|
|
2cadef46f7 | ||
|
|
093ec57d06 | ||
|
|
d769ce0a20 | ||
|
|
280b2dc1e4 | ||
|
|
260b32edff | ||
|
|
28e0ca5a8c | ||
|
|
9d2b1f294d | ||
|
|
3d4ad250cd | ||
|
|
5a3d490c50 | ||
|
|
24fb4e3894 | ||
|
|
a67ecb91f9 | ||
|
|
5cdd475066 | ||
|
|
2cd6b001da | ||
|
|
b2a5a70e5a | ||
|
|
eb26a8174a | ||
|
|
f2d65c380d | ||
|
|
b16e5432b6 | ||
|
|
d01af7abe4 | ||
|
|
77acc63eef | ||
|
|
3ddc537605 | ||
|
|
b85086c573 | ||
|
|
4a9670f517 | ||
|
|
d5712d2151 | ||
|
|
3ca02fb939 | ||
|
|
59e51f92d2 | ||
|
|
3502d1f16b | ||
|
|
1317bab0d1 | ||
|
|
63ed0f7593 | ||
|
|
8b9b02ade5 | ||
|
|
d4ed462b39 | ||
|
|
525a7b399b | ||
|
|
ae9687de6f | ||
|
|
9bb7c4b2e5 | ||
|
|
07142f034a | ||
|
|
733348bbb4 | ||
|
|
4223c41587 | ||
|
|
ad4e5ca925 | ||
|
|
d55c5e8caf | ||
|
|
d2aa10d029 | ||
|
|
2ee4a1c980 |
@@ -1,29 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test-ubuntu-latest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: make
|
||||
- name: test
|
||||
run: |
|
||||
sudo apt-get install tcl8.5
|
||||
./runtest --clients 2 --verbose
|
||||
|
||||
build-ubuntu-old:
|
||||
runs-on: ubuntu-16.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: make
|
||||
|
||||
build-macos-latest:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: make
|
||||
run: make
|
||||
@@ -1,8 +1,5 @@
|
||||
.*.swp
|
||||
*.o
|
||||
*.xo
|
||||
*.so
|
||||
*.d
|
||||
*.log
|
||||
dump.rdb
|
||||
redis-benchmark
|
||||
@@ -31,5 +28,3 @@ deps/lua/src/liblua.a
|
||||
.prerequisites
|
||||
*.dSYM
|
||||
Makefile.dep
|
||||
.vscode/*
|
||||
.idea/*
|
||||
|
||||
+3404
-11
File diff suppressed because it is too large
Load Diff
@@ -35,11 +35,6 @@ 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
|
||||
@@ -48,13 +43,6 @@ 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
|
||||
---------
|
||||
|
||||
@@ -137,12 +125,6 @@ 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
|
||||
------------------
|
||||
|
||||
@@ -184,8 +166,6 @@ for Ubuntu and Debian systems:
|
||||
% cd utils
|
||||
% ./install_server.sh
|
||||
|
||||
_Note_: `install_server.sh` will not work on Mac OSX; it is built for Linux only.
|
||||
|
||||
The script will ask you a few questions and will setup everything you need
|
||||
to run Redis properly as a background daemon that will start again on
|
||||
system reboots.
|
||||
@@ -236,7 +216,7 @@ Inside the root are the following important directories:
|
||||
|
||||
* `src`: contains the Redis implementation, written in C.
|
||||
* `tests`: contains the unit tests, implemented in Tcl.
|
||||
* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `antirez/redis`.
|
||||
* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `antirez/redis`. An exception to this rule is `deps/geohash-int` which is the low level geocoding library used by Redis: it originated from a different project, but at this point it diverged so much that it is developed as a separated entity directly inside the Redis repository.
|
||||
|
||||
There are a few more directories but they are not very important for our goals
|
||||
here. We'll focus mostly on `src`, where the Redis implementation is contained,
|
||||
@@ -424,7 +404,7 @@ replicas, or to continue the replication after a disconnection.
|
||||
Other C files
|
||||
---
|
||||
|
||||
* `t_hash.c`, `t_list.c`, `t_set.c`, `t_string.c`, `t_zset.c` and `t_stream.c` contains the implementation of the Redis data types. They implement both an API to access a given data type, and the client commands implementations for these data types.
|
||||
* `t_hash.c`, `t_list.c`, `t_set.c`, `t_string.c` and `t_zset.c` contains the implementation of the Redis data types. They implement both an API to access a given data type, and the client commands implementations for these data types.
|
||||
* `ae.c` implements the Redis event loop, it's a self contained library which is simple to read and understand.
|
||||
* `sds.c` is the Redis string library, check http://github.com/antirez/sds for more information.
|
||||
* `anet.c` is a library to use POSIX networking in a simpler way compared to the raw interface exposed by the kernel.
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
TLS Support
|
||||
===========
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
### Building
|
||||
|
||||
To build with TLS support you'll need OpenSSL development libraries (e.g.
|
||||
libssl-dev on Debian/Ubuntu).
|
||||
|
||||
Run `make BUILD_TLS=yes`.
|
||||
|
||||
### Tests
|
||||
|
||||
To run Redis test suite with TLS, you'll need TLS support for TCL (i.e.
|
||||
`tcl-tls` package on Debian/Ubuntu).
|
||||
|
||||
1. Run `./utils/gen-test-certs.sh` to generate a root CA and a server
|
||||
certificate.
|
||||
|
||||
2. Run `./runtest --tls` or `./runtest-cluster --tls` to run Redis and Redis
|
||||
Cluster tests in TLS mode.
|
||||
|
||||
### Running manually
|
||||
|
||||
To manually run a Redis server with TLS mode (assuming `gen-test-certs.sh` was
|
||||
invoked so sample certificates/keys are available):
|
||||
|
||||
./src/redis-server --tls-port 6379 --port 0 \
|
||||
--tls-cert-file ./tests/tls/redis.crt \
|
||||
--tls-key-file ./tests/tls/redis.key \
|
||||
--tls-ca-cert-file ./tests/tls/ca.crt
|
||||
|
||||
To connect to this Redis server with `redis-cli`:
|
||||
|
||||
./src/redis-cli --tls \
|
||||
--cert ./tests/tls/redis.crt \
|
||||
--key ./tests/tls/redis.key \
|
||||
--cacert ./tests/tls/ca.crt
|
||||
|
||||
This will disable TCP and enable TLS on port 6379. It's also possible to have
|
||||
both TCP and TLS available, but you'll need to assign different ports.
|
||||
|
||||
To make a Replica connect to the master using TLS, use `--tls-replication yes`,
|
||||
and to make Redis Cluster use TLS across nodes use `--tls-cluster yes`.
|
||||
|
||||
Connections
|
||||
-----------
|
||||
|
||||
All socket operations now go through a connection abstraction layer that hides
|
||||
I/O and read/write event handling from the caller.
|
||||
|
||||
**Multi-threading I/O is not currently supported for TLS**, as a TLS connection
|
||||
needs to do its own manipulation of AE events which is not thread safe. The
|
||||
solution is probably to manage independent AE loops for I/O threads and longer
|
||||
term association of connections with threads. This may potentially improve
|
||||
overall performance as well.
|
||||
|
||||
Sync IO for TLS is currently implemented in a hackish way, i.e. making the
|
||||
socket blocking and configuring socket-level timeout. This means the timeout
|
||||
value may not be so accurate, and there would be a lot of syscall overhead.
|
||||
However I believe that getting rid of syncio completely in favor of pure async
|
||||
work is probably a better move than trying to fix that. For replication it would
|
||||
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
|
||||
----------
|
||||
|
||||
- [ ] Add session caching support. Check if/how it's handled by clients to
|
||||
assess how useful/important it is.
|
||||
- [ ] redis-benchmark support. The current implementation is a mix of using
|
||||
hiredis for parsing and basic networking (establishing connections), but
|
||||
directly manipulating sockets for most actions. This will need to be cleaned
|
||||
up for proper TLS support. The best approach is probably to migrate to hiredis
|
||||
async mode.
|
||||
- [ ] redis-cli `--slave` and `--rdb` support.
|
||||
|
||||
Multi-port
|
||||
----------
|
||||
|
||||
Consider the implications of allowing TLS to be configured on a separate port,
|
||||
making Redis listening on multiple ports:
|
||||
|
||||
1. Startup banner port notification
|
||||
2. Proctitle
|
||||
3. How slaves announce themselves
|
||||
4. Cluster bus port calculation
|
||||
Vendored
+1
-5
@@ -41,13 +41,9 @@ distclean:
|
||||
|
||||
.PHONY: distclean
|
||||
|
||||
ifeq ($(BUILD_TLS),yes)
|
||||
HIREDIS_MAKE_FLAGS = USE_SSL=1
|
||||
endif
|
||||
|
||||
hiredis: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
cd hiredis && $(MAKE) static $(HIREDIS_MAKE_FLAGS)
|
||||
cd hiredis && $(MAKE) static
|
||||
|
||||
.PHONY: hiredis
|
||||
|
||||
|
||||
Vendored
+6
@@ -2,6 +2,7 @@ This directory contains all Redis dependencies, except for the libc that
|
||||
should be provided by the operating system.
|
||||
|
||||
* **Jemalloc** is our memory allocator, used as replacement for libc malloc on Linux by default. It has good performances and excellent fragmentation behavior. This component is upgraded from time to time.
|
||||
* **geohash-int** is inside the dependencies directory but is actually part of the Redis project, since it is our private fork (heavily modified) of a library initially developed for Ardb, which is in turn a fork of Redis.
|
||||
* **hiredis** is the official C client library for Redis. It is used by redis-cli, redis-benchmark and Redis Sentinel. It is part of the Redis official ecosystem but is developed externally from the Redis repository, so we just upgrade it as needed.
|
||||
* **linenoise** is a readline replacement. It is developed by the same authors of Redis but is managed as a separated project and updated as needed.
|
||||
* **lua** is Lua 5.1 with minor changes for security and additional libraries.
|
||||
@@ -41,6 +42,11 @@ the following additional steps:
|
||||
changed, otherwise you could just copy the old implementation if you are
|
||||
upgrading just to a similar version of Jemalloc.
|
||||
|
||||
Geohash
|
||||
---
|
||||
|
||||
This is never upgraded since it's part of the Redis project. If there are changes to merge from Ardb there is the need to manually check differences, but at this point the source code is pretty different.
|
||||
|
||||
Hiredis
|
||||
---
|
||||
|
||||
|
||||
@@ -5,4 +5,3 @@
|
||||
/*.dylib
|
||||
/*.a
|
||||
/*.pc
|
||||
*.dSYM
|
||||
|
||||
Vendored
+11
-69
@@ -8,12 +8,6 @@ os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
branches:
|
||||
only:
|
||||
- staging
|
||||
- trying
|
||||
- master
|
||||
|
||||
before_script:
|
||||
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi
|
||||
|
||||
@@ -26,72 +20,20 @@ addons:
|
||||
- libc6-dev-i386
|
||||
- libc6-dbg:i386
|
||||
- gcc-multilib
|
||||
- g++-multilib
|
||||
- valgrind
|
||||
|
||||
env:
|
||||
- BITS="32"
|
||||
- BITS="64"
|
||||
|
||||
script:
|
||||
- EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON";
|
||||
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||
if [ "$BITS" == "32" ]; then
|
||||
CFLAGS="-m32 -Werror";
|
||||
CXXFLAGS="-m32 -Werror";
|
||||
LDFLAGS="-m32";
|
||||
EXTRA_CMAKE_OPTS=;
|
||||
else
|
||||
CFLAGS="-Werror";
|
||||
CXXFLAGS="-Werror";
|
||||
fi;
|
||||
else
|
||||
TEST_PREFIX="valgrind --track-origins=yes --leak-check=full";
|
||||
if [ "$BITS" == "32" ]; then
|
||||
CFLAGS="-m32 -Werror";
|
||||
CXXFLAGS="-m32 -Werror";
|
||||
LDFLAGS="-m32";
|
||||
EXTRA_CMAKE_OPTS=;
|
||||
else
|
||||
CFLAGS="-Werror";
|
||||
CXXFLAGS="-Werror";
|
||||
fi;
|
||||
fi;
|
||||
export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS
|
||||
- mkdir build/ && cd build/
|
||||
- cmake .. ${EXTRA_CMAKE_OPTS}
|
||||
- make VERBOSE=1
|
||||
- ctest -V
|
||||
- CFLAGS="-Werror"
|
||||
- PRE="valgrind --track-origins=yes --leak-check=full"
|
||||
- TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
|
||||
- TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Windows MinGW cross compile on Linux
|
||||
- os: linux
|
||||
dist: xenial
|
||||
compiler: mingw
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- ninja-build
|
||||
- gcc-mingw-w64-x86-64
|
||||
- g++-mingw-w64-x86-64
|
||||
script:
|
||||
- mkdir build && cd build
|
||||
- CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on
|
||||
- ninja -v
|
||||
exclude:
|
||||
- os: osx
|
||||
env: PRE="valgrind --track-origins=yes --leak-check=full"
|
||||
|
||||
# Windows MSVC 2017
|
||||
- os: windows
|
||||
compiler: msvc
|
||||
env:
|
||||
- MATRIX_EVAL="CC=cl.exe && CXX=cl.exe"
|
||||
before_install:
|
||||
- eval "${MATRIX_EVAL}"
|
||||
install:
|
||||
- choco install ninja
|
||||
script:
|
||||
- mkdir build && cd build
|
||||
- cmd.exe /C '"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64 &&
|
||||
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release &&
|
||||
ninja -v'
|
||||
- ctest -V
|
||||
- os: osx
|
||||
env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
|
||||
|
||||
script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example
|
||||
|
||||
Vendored
+6
-56
@@ -1,18 +1,12 @@
|
||||
### 1.0.0 (unreleased)
|
||||
|
||||
**BREAKING CHANGES**:
|
||||
**Fixes**:
|
||||
|
||||
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
|
||||
protocol errors. This is consistent with the RESP specification. On 32-bit
|
||||
platforms, the upper bound is lowered to `SIZE_MAX`.
|
||||
* Catch a buffer overflow when formatting the error message
|
||||
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
|
||||
* Fix warnings, when compiled with -Wshadow
|
||||
* Make hiredis compile in Cygwin on Windows, now CI-tested
|
||||
|
||||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||
|
||||
User code should compare this to `size_t` values as well. If it was used to
|
||||
compare to other values, casting might be necessary or can be removed, if
|
||||
casting was applied before.
|
||||
|
||||
### 0.x.x (unreleased)
|
||||
**BREAKING CHANGES**:
|
||||
|
||||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||
@@ -20,50 +14,6 @@
|
||||
User code should compare this to `size_t` values as well.
|
||||
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
|
||||
|
||||
* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
|
||||
|
||||
### 0.14.0 (2018-09-25)
|
||||
|
||||
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
|
||||
* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
|
||||
* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622])
|
||||
* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8])
|
||||
* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8])
|
||||
* Fix bulk and multi-bulk length truncation (Justin Brewer [109197])
|
||||
* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94])
|
||||
* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6])
|
||||
* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1])
|
||||
* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b])
|
||||
* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96])
|
||||
* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234])
|
||||
* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129])
|
||||
* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c])
|
||||
* Fix libevent leak (zfz [515228])
|
||||
* Clean up GCC warning (Ichito Nagata [2ec774])
|
||||
* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88])
|
||||
* Solaris compilation fix (Donald Whyte [41b07d])
|
||||
* Reorder linker arguments when building examples (Tustfarm-heart [06eedd])
|
||||
* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999])
|
||||
* libuv use after free fix (Paul Scott [cbb956])
|
||||
* Properly close socket fd on reconnect attempt (WSL [64d1ec])
|
||||
* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78])
|
||||
* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5])
|
||||
* Update libevent (Chris Xin [386802])
|
||||
* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e])
|
||||
* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6])
|
||||
* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3])
|
||||
* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb])
|
||||
* Compatibility fix for strerror_r (Tom Lee [bb1747])
|
||||
* Properly detect integer parse/overflow errors (Justin Brewer [93421f])
|
||||
* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40])
|
||||
* Catch a buffer overflow when formatting the error message
|
||||
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
|
||||
* Fix warnings, when compiled with -Wshadow
|
||||
* Make hiredis compile in Cygwin on Windows, now CI-tested
|
||||
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
|
||||
protocol errors. This is consistent with the RESP specification. On 32-bit
|
||||
platforms, the upper bound is lowered to `SIZE_MAX`.
|
||||
|
||||
* Remove backwards compatibility macro's
|
||||
|
||||
This removes the following old function aliases, use the new name now:
|
||||
@@ -144,7 +94,7 @@ The parser, standalone since v0.12.0, can now be compiled on Windows
|
||||
|
||||
* Add IPv6 support
|
||||
|
||||
* Remove possibility of multiple close on same fd
|
||||
* Remove possiblity of multiple close on same fd
|
||||
|
||||
* Add ability to bind source address on connect
|
||||
|
||||
|
||||
Vendored
-90
@@ -1,90 +0,0 @@
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0)
|
||||
INCLUDE(GNUInstallDirs)
|
||||
PROJECT(hiredis)
|
||||
|
||||
OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF)
|
||||
|
||||
MACRO(getVersionBit name)
|
||||
SET(VERSION_REGEX "^#define ${name} (.+)$")
|
||||
FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h"
|
||||
VERSION_BIT REGEX ${VERSION_REGEX})
|
||||
STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}")
|
||||
ENDMACRO(getVersionBit)
|
||||
|
||||
getVersionBit(HIREDIS_MAJOR)
|
||||
getVersionBit(HIREDIS_MINOR)
|
||||
getVersionBit(HIREDIS_PATCH)
|
||||
getVersionBit(HIREDIS_SONAME)
|
||||
SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}")
|
||||
MESSAGE("Detected version: ${VERSION}")
|
||||
|
||||
PROJECT(hiredis VERSION "${VERSION}")
|
||||
|
||||
SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples")
|
||||
|
||||
ADD_LIBRARY(hiredis SHARED
|
||||
async.c
|
||||
dict.c
|
||||
hiredis.c
|
||||
net.c
|
||||
read.c
|
||||
sds.c
|
||||
sockcompat.c)
|
||||
|
||||
SET_TARGET_PROPERTIES(hiredis
|
||||
PROPERTIES
|
||||
VERSION "${HIREDIS_SONAME}")
|
||||
IF(WIN32 OR MINGW)
|
||||
TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32)
|
||||
ENDIF()
|
||||
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .)
|
||||
|
||||
CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
|
||||
|
||||
INSTALL(TARGETS hiredis
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||
|
||||
INSTALL(FILES hiredis.h read.h sds.h async.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
||||
|
||||
INSTALL(DIRECTORY adapters
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
||||
|
||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
|
||||
IF(ENABLE_SSL)
|
||||
IF (NOT OPENSSL_ROOT_DIR)
|
||||
IF (APPLE)
|
||||
SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
FIND_PACKAGE(OpenSSL REQUIRED)
|
||||
ADD_LIBRARY(hiredis_ssl SHARED
|
||||
ssl.c)
|
||||
TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}")
|
||||
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES})
|
||||
CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
|
||||
|
||||
INSTALL(TARGETS hiredis_ssl
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||
|
||||
INSTALL(FILES hiredis_ssl.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
||||
|
||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
ENDIF()
|
||||
|
||||
IF(NOT (WIN32 OR MINGW))
|
||||
ENABLE_TESTING()
|
||||
ADD_EXECUTABLE(hiredis-test test.c)
|
||||
TARGET_LINK_LIBRARIES(hiredis-test hiredis)
|
||||
ADD_TEST(NAME hiredis-test
|
||||
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)
|
||||
ENDIF()
|
||||
|
||||
# Add examples
|
||||
IF(ENABLE_EXAMPLES)
|
||||
ADD_SUBDIRECTORY(examples)
|
||||
ENDIF(ENABLE_EXAMPLES)
|
||||
Vendored
+36
-92
@@ -3,17 +3,11 @@
|
||||
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
|
||||
OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o
|
||||
SSL_OBJ=ssl.o
|
||||
OBJ=net.o hiredis.o sds.o async.o read.o
|
||||
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
|
||||
ifeq ($(USE_SSL),1)
|
||||
EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
|
||||
endif
|
||||
TESTS=hiredis-test
|
||||
LIBNAME=libhiredis
|
||||
SSL_LIBNAME=libhiredis_ssl
|
||||
PKGCONFNAME=hiredis.pc
|
||||
SSL_PKGCONFNAME=hiredis_ssl.pc
|
||||
|
||||
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
|
||||
HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
|
||||
@@ -42,109 +36,71 @@ endef
|
||||
export REDIS_TEST_CONFIG
|
||||
|
||||
# Fallback to gcc when $CC is not in $PATH.
|
||||
CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
||||
CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
|
||||
CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
||||
CXX:=$(shell sh -c 'type $(CXX) >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
|
||||
OPTIMIZATION?=-O3
|
||||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers
|
||||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
|
||||
DEBUG_FLAGS?= -g -ggdb
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
|
||||
REAL_LDFLAGS=$(LDFLAGS)
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(ARCH)
|
||||
REAL_LDFLAGS=$(LDFLAGS) $(ARCH)
|
||||
|
||||
DYLIBSUFFIX=so
|
||||
STLIBSUFFIX=a
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
|
||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
||||
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
|
||||
SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
|
||||
SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
|
||||
STLIB_MAKE_CMD=$(AR) rcs
|
||||
STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
|
||||
|
||||
# Platform-specific overrides
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
|
||||
USE_SSL?=0
|
||||
|
||||
# This is required for test.c only
|
||||
ifeq ($(USE_SSL),1)
|
||||
CFLAGS+=-DHIREDIS_TEST_SSL
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),Linux)
|
||||
SSL_LDFLAGS=-lssl -lcrypto
|
||||
else
|
||||
OPENSSL_PREFIX?=/usr/local/opt/openssl
|
||||
CFLAGS+=-I$(OPENSSL_PREFIX)/include
|
||||
SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
REAL_LDFLAGS+= -ldl -lnsl -lsocket
|
||||
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
|
||||
INSTALL= cp -r
|
||||
endif
|
||||
ifeq ($(uname_S),Darwin)
|
||||
DYLIBSUFFIX=dylib
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
|
||||
ifeq ($(USE_SSL),1)
|
||||
all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
|
||||
endif
|
||||
|
||||
# Deps (use make dep to generate this)
|
||||
async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
|
||||
dict.o: dict.c fmacros.h dict.h
|
||||
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h win32.h
|
||||
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h
|
||||
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h
|
||||
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h
|
||||
read.o: read.c fmacros.h read.h sds.h
|
||||
sds.o: sds.c sds.h
|
||||
sockcompat.o: sockcompat.c sockcompat.h
|
||||
ssl.o: ssl.c hiredis.h
|
||||
test.o: test.c fmacros.h hiredis.h read.h sds.h
|
||||
|
||||
$(DYLIBNAME): $(OBJ)
|
||||
$(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
|
||||
$(DYLIB_MAKE_CMD) $(OBJ)
|
||||
|
||||
$(STLIBNAME): $(OBJ)
|
||||
$(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
|
||||
|
||||
$(SSL_DYLIBNAME): $(SSL_OBJ)
|
||||
$(DYLIB_MAKE_CMD) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
||||
|
||||
$(SSL_STLIBNAME): $(SSL_OBJ)
|
||||
$(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
|
||||
$(STLIB_MAKE_CMD) $(OBJ)
|
||||
|
||||
dynamic: $(DYLIBNAME)
|
||||
static: $(STLIBNAME)
|
||||
ifeq ($(USE_SSL),1)
|
||||
dynamic: $(SSL_DYLIBNAME)
|
||||
static: $(SSL_STLIBNAME)
|
||||
endif
|
||||
|
||||
# Binaries:
|
||||
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME)
|
||||
|
||||
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
|
||||
|
||||
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME)
|
||||
|
||||
hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME)
|
||||
|
||||
hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME)
|
||||
|
||||
ifndef AE_DIR
|
||||
hiredis-example-ae:
|
||||
@@ -161,7 +117,7 @@ hiredis-example-libuv:
|
||||
@false
|
||||
else
|
||||
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME)
|
||||
endif
|
||||
|
||||
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
|
||||
@@ -178,35 +134,38 @@ hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
|
||||
endif
|
||||
|
||||
hiredis-example: examples/example.c $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME)
|
||||
|
||||
examples: $(EXAMPLES)
|
||||
|
||||
TEST_LIBS = $(STLIBNAME)
|
||||
ifeq ($(USE_SSL),1)
|
||||
TEST_LIBS += $(SSL_STLIBNAME) -lssl -lcrypto -lpthread
|
||||
endif
|
||||
hiredis-test: test.o $(TEST_LIBS)
|
||||
hiredis-test: test.o $(STLIBNAME)
|
||||
|
||||
hiredis-%: %.o $(STLIBNAME)
|
||||
$(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
|
||||
$(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME)
|
||||
|
||||
test: hiredis-test
|
||||
./hiredis-test
|
||||
|
||||
check: hiredis-test
|
||||
TEST_SSL=$(USE_SSL) ./test.sh
|
||||
@echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) -
|
||||
$(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \
|
||||
( kill `cat /tmp/hiredis-test-redis.pid` && false )
|
||||
kill `cat /tmp/hiredis-test-redis.pid`
|
||||
|
||||
.c.o:
|
||||
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
|
||||
|
||||
clean:
|
||||
rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
|
||||
rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
|
||||
|
||||
dep:
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
|
||||
$(CC) -MM *.c
|
||||
|
||||
INSTALL?= cp -pPR
|
||||
ifeq ($(uname_S),SunOS)
|
||||
INSTALL?= cp -r
|
||||
endif
|
||||
|
||||
INSTALL?= cp -a
|
||||
|
||||
$(PKGCONFNAME): hiredis.h
|
||||
@echo "Generating $@ for pkgconfig..."
|
||||
@@ -221,24 +180,9 @@ $(PKGCONFNAME): hiredis.h
|
||||
@echo Libs: -L\$${libdir} -lhiredis >> $@
|
||||
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
||||
|
||||
$(SSL_PKGCONFNAME): hiredis.h
|
||||
@echo "Generating $@ for pkgconfig..."
|
||||
@echo prefix=$(PREFIX) > $@
|
||||
@echo exec_prefix=\$${prefix} >> $@
|
||||
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
|
||||
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
|
||||
@echo >> $@
|
||||
@echo Name: hiredis_ssl >> $@
|
||||
@echo Description: SSL Support for hiredis. >> $@
|
||||
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
|
||||
@echo Requires: hiredis >> $@
|
||||
@echo Libs: -L\$${libdir} -lhiredis_ssl >> $@
|
||||
@echo Libs.private: -lssl -lcrypto >> $@
|
||||
|
||||
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
|
||||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
|
||||
$(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH)
|
||||
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
|
||||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
|
||||
$(INSTALL) hiredis.h async.h read.h sds.h adapters $(INSTALL_INCLUDE_PATH)
|
||||
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
|
||||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
|
||||
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
|
||||
|
||||
Vendored
+1
-2
@@ -286,7 +286,6 @@ return `REDIS_ERR`. The function to set the disconnect callback has the followin
|
||||
```c
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
```
|
||||
`ac->data` may be used to pass user data to this callback, the same can be done for redisConnectCallback.
|
||||
### Sending commands and their callbacks
|
||||
|
||||
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
|
||||
@@ -407,6 +406,6 @@ as soon as possible in order to prevent allocation of useless memory.
|
||||
## AUTHORS
|
||||
|
||||
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
|
||||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
|
||||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
|
||||
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
|
||||
Jan-Erik Rediger (janerik at fnordig dot com)
|
||||
|
||||
Vendored
+24
-88
@@ -34,113 +34,48 @@
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
#define REDIS_LIBEVENT_DELETED 0x01
|
||||
#define REDIS_LIBEVENT_ENTERED 0x02
|
||||
|
||||
typedef struct redisLibeventEvents {
|
||||
redisAsyncContext *context;
|
||||
struct event *ev;
|
||||
struct event_base *base;
|
||||
struct timeval tv;
|
||||
short flags;
|
||||
short state;
|
||||
struct event *rev, *wev;
|
||||
} redisLibeventEvents;
|
||||
|
||||
static void redisLibeventDestroy(redisLibeventEvents *e) {
|
||||
free(e);
|
||||
}
|
||||
|
||||
static void redisLibeventHandler(int fd, short event, void *arg) {
|
||||
((void)fd);
|
||||
static void redisLibeventReadEvent(int fd, short event, void *arg) {
|
||||
((void)fd); ((void)event);
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)arg;
|
||||
e->state |= REDIS_LIBEVENT_ENTERED;
|
||||
|
||||
#define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\
|
||||
redisLibeventDestroy(e);\
|
||||
return; \
|
||||
}
|
||||
|
||||
if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
|
||||
redisAsyncHandleTimeout(e->context);
|
||||
CHECK_DELETED();
|
||||
}
|
||||
|
||||
if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
|
||||
redisAsyncHandleRead(e->context);
|
||||
CHECK_DELETED();
|
||||
}
|
||||
|
||||
if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
|
||||
redisAsyncHandleWrite(e->context);
|
||||
CHECK_DELETED();
|
||||
}
|
||||
|
||||
e->state &= ~REDIS_LIBEVENT_ENTERED;
|
||||
#undef CHECK_DELETED
|
||||
redisAsyncHandleRead(e->context);
|
||||
}
|
||||
|
||||
static void redisLibeventUpdate(void *privdata, short flag, int isRemove) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents *)privdata;
|
||||
const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL;
|
||||
|
||||
if (isRemove) {
|
||||
if ((e->flags & flag) == 0) {
|
||||
return;
|
||||
} else {
|
||||
e->flags &= ~flag;
|
||||
}
|
||||
} else {
|
||||
if (e->flags & flag) {
|
||||
return;
|
||||
} else {
|
||||
e->flags |= flag;
|
||||
}
|
||||
}
|
||||
|
||||
event_del(e->ev);
|
||||
event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST,
|
||||
redisLibeventHandler, privdata);
|
||||
event_add(e->ev, tv);
|
||||
static void redisLibeventWriteEvent(int fd, short event, void *arg) {
|
||||
((void)fd); ((void)event);
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)arg;
|
||||
redisAsyncHandleWrite(e->context);
|
||||
}
|
||||
|
||||
static void redisLibeventAddRead(void *privdata) {
|
||||
redisLibeventUpdate(privdata, EV_READ, 0);
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_add(e->rev,NULL);
|
||||
}
|
||||
|
||||
static void redisLibeventDelRead(void *privdata) {
|
||||
redisLibeventUpdate(privdata, EV_READ, 1);
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(e->rev);
|
||||
}
|
||||
|
||||
static void redisLibeventAddWrite(void *privdata) {
|
||||
redisLibeventUpdate(privdata, EV_WRITE, 0);
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_add(e->wev,NULL);
|
||||
}
|
||||
|
||||
static void redisLibeventDelWrite(void *privdata) {
|
||||
redisLibeventUpdate(privdata, EV_WRITE, 1);
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(e->wev);
|
||||
}
|
||||
|
||||
static void redisLibeventCleanup(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
event_del(e->ev);
|
||||
event_free(e->ev);
|
||||
e->ev = NULL;
|
||||
|
||||
if (e->state & REDIS_LIBEVENT_ENTERED) {
|
||||
e->state |= REDIS_LIBEVENT_DELETED;
|
||||
} else {
|
||||
redisLibeventDestroy(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void redisLibeventSetTimeout(void *privdata, struct timeval tv) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents *)privdata;
|
||||
short flags = e->flags;
|
||||
e->flags = 0;
|
||||
e->tv = tv;
|
||||
redisLibeventUpdate(e, flags, 0);
|
||||
event_del(e->rev);
|
||||
event_del(e->wev);
|
||||
free(e);
|
||||
}
|
||||
|
||||
static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
||||
@@ -152,7 +87,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Create container for context and r/w events */
|
||||
e = (redisLibeventEvents*)calloc(1, sizeof(*e));
|
||||
e = (redisLibeventEvents*)malloc(sizeof(*e));
|
||||
e->context = ac;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
@@ -161,12 +96,13 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
||||
ac->ev.addWrite = redisLibeventAddWrite;
|
||||
ac->ev.delWrite = redisLibeventDelWrite;
|
||||
ac->ev.cleanup = redisLibeventCleanup;
|
||||
ac->ev.scheduleTimer = redisLibeventSetTimeout;
|
||||
ac->ev.data = e;
|
||||
|
||||
/* Initialize and install read/write events */
|
||||
e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);
|
||||
e->base = base;
|
||||
e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e);
|
||||
e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e);
|
||||
event_add(e->rev, NULL);
|
||||
event_add(e->wev, NULL);
|
||||
return REDIS_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
Vendored
+6
-3
@@ -15,12 +15,15 @@ typedef struct redisLibuvEvents {
|
||||
|
||||
static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
|
||||
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
|
||||
int ev = (status ? p->events : events);
|
||||
|
||||
if (p->context != NULL && (ev & UV_READABLE)) {
|
||||
if (status != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p->context != NULL && (events & UV_READABLE)) {
|
||||
redisAsyncHandleRead(p->context);
|
||||
}
|
||||
if (p->context != NULL && (ev & UV_WRITABLE)) {
|
||||
if (p->context != NULL && (events & UV_WRITABLE)) {
|
||||
redisAsyncHandleWrite(p->context);
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+18
-6
@@ -1,14 +1,24 @@
|
||||
# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)
|
||||
environment:
|
||||
matrix:
|
||||
- CYG_BASH: C:\cygwin64\bin\bash
|
||||
- CYG_ROOT: C:\cygwin64
|
||||
CYG_SETUP: setup-x86_64.exe
|
||||
CYG_MIRROR: http://cygwin.mirror.constant.com
|
||||
CYG_CACHE: C:\cygwin64\var\cache\setup
|
||||
CYG_BASH: C:\cygwin64\bin\bash
|
||||
CC: gcc
|
||||
- CYG_BASH: C:\cygwin\bin\bash
|
||||
- CYG_ROOT: C:\cygwin
|
||||
CYG_SETUP: setup-x86.exe
|
||||
CYG_MIRROR: http://cygwin.mirror.constant.com
|
||||
CYG_CACHE: C:\cygwin\var\cache\setup
|
||||
CYG_BASH: C:\cygwin\bin\bash
|
||||
CC: gcc
|
||||
CFLAGS: -m32
|
||||
CXXFLAGS: -m32
|
||||
LDFLAGS: -m32
|
||||
TARGET: 32bit
|
||||
TARGET_VARS: 32bit-vars
|
||||
|
||||
# Cache Cygwin files to speed up build
|
||||
cache:
|
||||
- '%CYG_CACHE%'
|
||||
clone_depth: 1
|
||||
|
||||
# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
|
||||
@@ -17,8 +27,10 @@ init:
|
||||
|
||||
# Install needed build dependencies
|
||||
install:
|
||||
- ps: 'Start-FileDownload "http://cygwin.com/$env:CYG_SETUP" -FileName "$env:CYG_SETUP"'
|
||||
- '%CYG_SETUP% --quiet-mode --no-shortcuts --only-site --root "%CYG_ROOT%" --site "%CYG_MIRROR%" --local-package-dir "%CYG_CACHE%" --packages automake,bison,gcc-core,libtool,make,gettext-devel,gettext,intltool,pkg-config,clang,llvm > NUL 2>&1'
|
||||
- '%CYG_BASH% -lc "cygcheck -dc cygwin"'
|
||||
|
||||
build_script:
|
||||
- 'echo building...'
|
||||
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; mkdir build && cd build && cmake .. -G \"Unix Makefiles\" && make VERBOSE=1"'
|
||||
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make LDFLAGS=$LDFLAGS CC=$CC $TARGET CFLAGS=$CFLAGS && make LDFLAGS=$LDFLAGS CC=$CC $TARGET_VARS hiredis-example"'
|
||||
|
||||
Vendored
+80
-159
@@ -32,9 +32,7 @@
|
||||
#include "fmacros.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <strings.h>
|
||||
#endif
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
@@ -42,9 +40,22 @@
|
||||
#include "net.h"
|
||||
#include "dict.c"
|
||||
#include "sds.h"
|
||||
#include "win32.h"
|
||||
|
||||
#include "async_private.h"
|
||||
#define _EL_ADD_READ(ctx) do { \
|
||||
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
|
||||
} while(0)
|
||||
#define _EL_DEL_READ(ctx) do { \
|
||||
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
|
||||
} while(0)
|
||||
#define _EL_ADD_WRITE(ctx) do { \
|
||||
if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
|
||||
} while(0)
|
||||
#define _EL_DEL_WRITE(ctx) do { \
|
||||
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
|
||||
} while(0)
|
||||
#define _EL_CLEANUP(ctx) do { \
|
||||
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
||||
} while(0);
|
||||
|
||||
/* Forward declaration of function in hiredis.c */
|
||||
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
|
||||
@@ -115,7 +126,6 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
||||
ac->ev.addWrite = NULL;
|
||||
ac->ev.delWrite = NULL;
|
||||
ac->ev.cleanup = NULL;
|
||||
ac->ev.scheduleTimer = NULL;
|
||||
|
||||
ac->onConnect = NULL;
|
||||
ac->onDisconnect = NULL;
|
||||
@@ -140,52 +150,56 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) {
|
||||
ac->errstr = c->errstr;
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
|
||||
redisOptions myOptions = *options;
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
|
||||
redisContext *c;
|
||||
redisAsyncContext *ac;
|
||||
|
||||
myOptions.options |= REDIS_OPT_NONBLOCK;
|
||||
c = redisConnectWithOptions(&myOptions);
|
||||
if (c == NULL) {
|
||||
c = redisConnectNonBlock(ip,port);
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ac = redisAsyncInitialize(c);
|
||||
if (ac == NULL) {
|
||||
redisFree(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
return redisAsyncConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.endpoint.tcp.source_addr = source_addr;
|
||||
return redisAsyncConnectWithOptions(&options);
|
||||
redisContext *c = redisConnectBindNonBlock(ip,port,source_addr);
|
||||
redisAsyncContext *ac = redisAsyncInitialize(c);
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.options |= REDIS_OPT_REUSEADDR;
|
||||
options.endpoint.tcp.source_addr = source_addr;
|
||||
return redisAsyncConnectWithOptions(&options);
|
||||
redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr);
|
||||
redisAsyncContext *ac = redisAsyncInitialize(c);
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnectUnix(const char *path) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||
return redisAsyncConnectWithOptions(&options);
|
||||
redisContext *c;
|
||||
redisAsyncContext *ac;
|
||||
|
||||
c = redisConnectUnixNonBlock(path);
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
ac = redisAsyncInitialize(c);
|
||||
if (ac == NULL) {
|
||||
redisFree(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
}
|
||||
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
|
||||
@@ -314,7 +328,7 @@ void redisAsyncFree(redisAsyncContext *ac) {
|
||||
}
|
||||
|
||||
/* Helper function to make the disconnect happen and clean up. */
|
||||
void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
static void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
|
||||
/* Make sure error is accessible if there is any */
|
||||
@@ -322,23 +336,16 @@ void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
|
||||
if (ac->err == 0) {
|
||||
/* For clean disconnects, there should be no pending callbacks. */
|
||||
int ret = __redisShiftCallback(&ac->replies,NULL);
|
||||
assert(ret == REDIS_ERR);
|
||||
assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
|
||||
} else {
|
||||
/* Disconnection is caused by an error, make sure that pending
|
||||
* callbacks cannot call new commands. */
|
||||
c->flags |= REDIS_DISCONNECTING;
|
||||
}
|
||||
|
||||
/* cleanup event library on disconnect.
|
||||
* this is safe to call multiple times */
|
||||
_EL_CLEANUP(ac);
|
||||
|
||||
/* For non-clean disconnects, __redisAsyncFree() will execute pending
|
||||
* callbacks with a NULL-reply. */
|
||||
if (!(c->flags & REDIS_NO_AUTO_FREE)) {
|
||||
__redisAsyncFree(ac);
|
||||
}
|
||||
__redisAsyncFree(ac);
|
||||
}
|
||||
|
||||
/* Tries to do a clean disconnect from Redis, meaning it stops new commands
|
||||
@@ -350,9 +357,6 @@ void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
c->flags |= REDIS_DISCONNECTING;
|
||||
|
||||
/** unset the auto-free flag here, because disconnect undoes this */
|
||||
c->flags &= ~REDIS_NO_AUTO_FREE;
|
||||
if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
|
||||
__redisAsyncDisconnect(ac);
|
||||
}
|
||||
@@ -360,7 +364,6 @@ void redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
|
||||
redisContext *c = &(ac->c);
|
||||
dict *callbacks;
|
||||
redisCallback *cb;
|
||||
dictEntry *de;
|
||||
int pvariant;
|
||||
char *stype;
|
||||
@@ -384,28 +387,16 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
||||
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
|
||||
de = dictFind(callbacks,sname);
|
||||
if (de != NULL) {
|
||||
cb = dictGetEntryVal(de);
|
||||
|
||||
/* If this is an subscribe reply decrease pending counter. */
|
||||
if (strcasecmp(stype+pvariant,"subscribe") == 0) {
|
||||
cb->pending_subs -= 1;
|
||||
}
|
||||
|
||||
memcpy(dstcb,cb,sizeof(*dstcb));
|
||||
memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
|
||||
|
||||
/* If this is an unsubscribe message, remove it. */
|
||||
if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
|
||||
if (cb->pending_subs == 0)
|
||||
dictDelete(callbacks,sname);
|
||||
dictDelete(callbacks,sname);
|
||||
|
||||
/* If this was the last unsubscribe message, revert to
|
||||
* non-subscribe mode. */
|
||||
assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
|
||||
|
||||
/* Unset subscribed flag only when no pipelined pending subscribe. */
|
||||
if (reply->element[2]->integer == 0
|
||||
&& dictSize(ac->sub.channels) == 0
|
||||
&& dictSize(ac->sub.patterns) == 0)
|
||||
if (reply->element[2]->integer == 0)
|
||||
c->flags &= ~REDIS_SUBSCRIBED;
|
||||
}
|
||||
}
|
||||
@@ -419,7 +410,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
||||
|
||||
void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb = {NULL, NULL, 0, NULL};
|
||||
redisCallback cb = {NULL, NULL, NULL};
|
||||
void *reply = NULL;
|
||||
int status;
|
||||
|
||||
@@ -501,34 +492,22 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
* write event fires. When connecting was not successful, the connect callback
|
||||
* is called with a REDIS_ERR status and the context is free'd. */
|
||||
static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
|
||||
int completed = 0;
|
||||
redisContext *c = &(ac->c);
|
||||
if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
|
||||
/* Error! */
|
||||
redisCheckSocketError(c);
|
||||
if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
|
||||
|
||||
if (redisCheckSocketError(c) == REDIS_ERR) {
|
||||
/* Try again later when connect(2) is still in progress. */
|
||||
if (errno == EINPROGRESS)
|
||||
return REDIS_OK;
|
||||
|
||||
if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
|
||||
__redisAsyncDisconnect(ac);
|
||||
return REDIS_ERR;
|
||||
} else if (completed == 1) {
|
||||
/* connected! */
|
||||
if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
return REDIS_OK;
|
||||
} else {
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
void redisAsyncRead(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
|
||||
if (redisBufferRead(c) == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
/* Always re-schedule reads */
|
||||
_EL_ADD_READ(ac);
|
||||
redisProcessCallbacks(ac);
|
||||
}
|
||||
/* Mark context as connected. */
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* This function should be called when the socket is readable.
|
||||
@@ -546,13 +525,28 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
|
||||
return;
|
||||
}
|
||||
|
||||
c->funcs->async_read(ac);
|
||||
if (redisBufferRead(c) == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
/* Always re-schedule reads */
|
||||
_EL_ADD_READ(ac);
|
||||
redisProcessCallbacks(ac);
|
||||
}
|
||||
}
|
||||
|
||||
void redisAsyncWrite(redisAsyncContext *ac) {
|
||||
void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
int done = 0;
|
||||
|
||||
if (!(c->flags & REDIS_CONNECTED)) {
|
||||
/* Abort connect was not successful. */
|
||||
if (__redisAsyncHandleConnect(ac) != REDIS_OK)
|
||||
return;
|
||||
/* Try again later when the context is still not connected. */
|
||||
if (!(c->flags & REDIS_CONNECTED))
|
||||
return;
|
||||
}
|
||||
|
||||
if (redisBufferWrite(c,&done) == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
@@ -567,51 +561,6 @@ void redisAsyncWrite(redisAsyncContext *ac) {
|
||||
}
|
||||
}
|
||||
|
||||
void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
|
||||
if (!(c->flags & REDIS_CONNECTED)) {
|
||||
/* Abort connect was not successful. */
|
||||
if (__redisAsyncHandleConnect(ac) != REDIS_OK)
|
||||
return;
|
||||
/* Try again later when the context is still not connected. */
|
||||
if (!(c->flags & REDIS_CONNECTED))
|
||||
return;
|
||||
}
|
||||
|
||||
c->funcs->async_write(ac);
|
||||
}
|
||||
|
||||
void __redisSetError(redisContext *c, int type, const char *str);
|
||||
|
||||
void redisAsyncHandleTimeout(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
|
||||
if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) {
|
||||
/* Nothing to do - just an idle timeout */
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c->err) {
|
||||
__redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout");
|
||||
}
|
||||
|
||||
if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) {
|
||||
ac->onConnect(ac, REDIS_ERR);
|
||||
}
|
||||
|
||||
while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {
|
||||
__redisRunCallback(ac, &cb, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Don't automatically sever the connection,
|
||||
* rather, allow to ignore <x> responses before the queue is clear
|
||||
*/
|
||||
__redisAsyncDisconnect(ac);
|
||||
}
|
||||
|
||||
/* Sets a pointer to the first argument and its length starting at p. Returns
|
||||
* the number of bytes to skip to get to the following argument. */
|
||||
static const char *nextArgument(const char *start, const char **str, size_t *len) {
|
||||
@@ -634,9 +583,6 @@ static const char *nextArgument(const char *start, const char **str, size_t *len
|
||||
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
struct dict *cbdict;
|
||||
dictEntry *de;
|
||||
redisCallback *existcb;
|
||||
int pvariant, hasnext;
|
||||
const char *cstr, *astr;
|
||||
size_t clen, alen;
|
||||
@@ -650,7 +596,6 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
/* Setup callback */
|
||||
cb.fn = fn;
|
||||
cb.privdata = privdata;
|
||||
cb.pending_subs = 1;
|
||||
|
||||
/* Find out which command will be appended. */
|
||||
p = nextArgument(cmd,&cstr,&clen);
|
||||
@@ -667,18 +612,9 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
|
||||
sname = sdsnewlen(astr,alen);
|
||||
if (pvariant)
|
||||
cbdict = ac->sub.patterns;
|
||||
ret = dictReplace(ac->sub.patterns,sname,&cb);
|
||||
else
|
||||
cbdict = ac->sub.channels;
|
||||
|
||||
de = dictFind(cbdict,sname);
|
||||
|
||||
if (de != NULL) {
|
||||
existcb = dictGetEntryVal(de);
|
||||
cb.pending_subs = existcb->pending_subs + 1;
|
||||
}
|
||||
|
||||
ret = dictReplace(cbdict,sname,&cb);
|
||||
ret = dictReplace(ac->sub.channels,sname,&cb);
|
||||
|
||||
if (ret == 0) sdsfree(sname);
|
||||
}
|
||||
@@ -740,8 +676,6 @@ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *priv
|
||||
int len;
|
||||
int status;
|
||||
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
|
||||
if (len < 0)
|
||||
return REDIS_ERR;
|
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
sdsfree(cmd);
|
||||
return status;
|
||||
@@ -751,16 +685,3 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
return status;
|
||||
}
|
||||
|
||||
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
|
||||
if (!ac->c.timeout) {
|
||||
ac->c.timeout = calloc(1, sizeof(tv));
|
||||
}
|
||||
|
||||
if (tv.tv_sec == ac->c.timeout->tv_sec &&
|
||||
tv.tv_usec == ac->c.timeout->tv_usec) {
|
||||
return;
|
||||
}
|
||||
|
||||
*ac->c.timeout = tv;
|
||||
}
|
||||
|
||||
Vendored
-13
@@ -45,7 +45,6 @@ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
|
||||
typedef struct redisCallback {
|
||||
struct redisCallback *next; /* simple singly linked list */
|
||||
redisCallbackFn *fn;
|
||||
int pending_subs;
|
||||
void *privdata;
|
||||
} redisCallback;
|
||||
|
||||
@@ -57,7 +56,6 @@ typedef struct redisCallbackList {
|
||||
/* Connection callback prototypes */
|
||||
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
|
||||
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
|
||||
typedef void(redisTimerCallback)(void *timer, void *privdata);
|
||||
|
||||
/* Context for an async connection to Redis */
|
||||
typedef struct redisAsyncContext {
|
||||
@@ -82,7 +80,6 @@ typedef struct redisAsyncContext {
|
||||
void (*addWrite)(void *privdata);
|
||||
void (*delWrite)(void *privdata);
|
||||
void (*cleanup)(void *privdata);
|
||||
void (*scheduleTimer)(void *privdata, struct timeval tv);
|
||||
} ev;
|
||||
|
||||
/* Called when either the connection is terminated due to an error or per
|
||||
@@ -95,10 +92,6 @@ typedef struct redisAsyncContext {
|
||||
/* Regular command callbacks */
|
||||
redisCallbackList replies;
|
||||
|
||||
/* Address used for connect() */
|
||||
struct sockaddr *saddr;
|
||||
size_t addrlen;
|
||||
|
||||
/* Subscription callbacks */
|
||||
struct {
|
||||
redisCallbackList invalid;
|
||||
@@ -108,7 +101,6 @@ typedef struct redisAsyncContext {
|
||||
} redisAsyncContext;
|
||||
|
||||
/* Functions that proxy to hiredis */
|
||||
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
|
||||
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
|
||||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
||||
@@ -116,17 +108,12 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
||||
redisAsyncContext *redisAsyncConnectUnix(const char *path);
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
|
||||
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac);
|
||||
void redisAsyncFree(redisAsyncContext *ac);
|
||||
|
||||
/* Handle read/write events */
|
||||
void redisAsyncHandleRead(redisAsyncContext *ac);
|
||||
void redisAsyncHandleWrite(redisAsyncContext *ac);
|
||||
void redisAsyncHandleTimeout(redisAsyncContext *ac);
|
||||
void redisAsyncRead(redisAsyncContext *ac);
|
||||
void redisAsyncWrite(redisAsyncContext *ac);
|
||||
|
||||
/* Command functions for an async context. Write the command to the
|
||||
* output buffer and register the provided callback. */
|
||||
|
||||
Vendored
-72
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __HIREDIS_ASYNC_PRIVATE_H
|
||||
#define __HIREDIS_ASYNC_PRIVATE_H
|
||||
|
||||
#define _EL_ADD_READ(ctx) \
|
||||
do { \
|
||||
refreshTimeout(ctx); \
|
||||
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
|
||||
} while (0)
|
||||
#define _EL_DEL_READ(ctx) do { \
|
||||
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
|
||||
} while(0)
|
||||
#define _EL_ADD_WRITE(ctx) \
|
||||
do { \
|
||||
refreshTimeout(ctx); \
|
||||
if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
|
||||
} while (0)
|
||||
#define _EL_DEL_WRITE(ctx) do { \
|
||||
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
|
||||
} while(0)
|
||||
#define _EL_CLEANUP(ctx) do { \
|
||||
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
||||
ctx->ev.cleanup = NULL; \
|
||||
} while(0);
|
||||
|
||||
static inline void refreshTimeout(redisAsyncContext *ctx) {
|
||||
if (ctx->c.timeout && ctx->ev.scheduleTimer &&
|
||||
(ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) {
|
||||
ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout);
|
||||
// } else {
|
||||
// printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout);
|
||||
// if (ctx->c.timeout){
|
||||
// printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec,
|
||||
// ctx->c.timeout->tv_usec);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void __redisAsyncDisconnect(redisAsyncContext *ac);
|
||||
void redisProcessCallbacks(redisAsyncContext *ac);
|
||||
|
||||
#endif /* __HIREDIS_ASYNC_PRIVATE_H */
|
||||
Vendored
-46
@@ -1,46 +0,0 @@
|
||||
INCLUDE(FindPkgConfig)
|
||||
# Check for GLib
|
||||
|
||||
PKG_CHECK_MODULES(GLIB2 glib-2.0)
|
||||
if (GLIB2_FOUND)
|
||||
INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
|
||||
LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS})
|
||||
ADD_EXECUTABLE(example-glib example-glib.c)
|
||||
TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES})
|
||||
ENDIF(GLIB2_FOUND)
|
||||
|
||||
FIND_PATH(LIBEV ev.h
|
||||
HINTS /usr/local /usr/opt/local
|
||||
ENV LIBEV_INCLUDE_DIR)
|
||||
|
||||
if (LIBEV)
|
||||
# Just compile and link with libev
|
||||
ADD_EXECUTABLE(example-libev example-libev.c)
|
||||
TARGET_LINK_LIBRARIES(example-libev hiredis ev)
|
||||
ENDIF()
|
||||
|
||||
FIND_PATH(LIBEVENT event.h)
|
||||
if (LIBEVENT)
|
||||
ADD_EXECUTABLE(example-libevent example-libevent)
|
||||
TARGET_LINK_LIBRARIES(example-libevent hiredis event)
|
||||
ENDIF()
|
||||
|
||||
FIND_PATH(LIBUV uv.h)
|
||||
IF (LIBUV)
|
||||
ADD_EXECUTABLE(example-libuv example-libuv.c)
|
||||
TARGET_LINK_LIBRARIES(example-libuv hiredis uv)
|
||||
ENDIF()
|
||||
|
||||
IF (APPLE)
|
||||
FIND_LIBRARY(CF CoreFoundation)
|
||||
ADD_EXECUTABLE(example-macosx example-macosx.c)
|
||||
TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF})
|
||||
ENDIF()
|
||||
|
||||
IF (ENABLE_SSL)
|
||||
ADD_EXECUTABLE(example-ssl example-ssl.c)
|
||||
TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl)
|
||||
ENDIF()
|
||||
|
||||
ADD_EXECUTABLE(example example.c)
|
||||
TARGET_LINK_LIBRARIES(example hiredis)
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <hiredis.h>
|
||||
#include <hiredis_ssl.h>
|
||||
#include <async.h>
|
||||
#include <adapters/libevent.h>
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) return;
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* Disconnect after receiving the reply to GET */
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void connectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return;
|
||||
}
|
||||
printf("Connected...\n");
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return;
|
||||
}
|
||||
printf("Disconnected...\n");
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
struct event_base *base = event_base_new();
|
||||
if (argc < 5) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s <key> <host> <port> <cert> <certKey> [ca]\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const char *value = argv[1];
|
||||
size_t nvalue = strlen(value);
|
||||
|
||||
const char *hostname = argv[2];
|
||||
int port = atoi(argv[3]);
|
||||
|
||||
const char *cert = argv[4];
|
||||
const char *certKey = argv[5];
|
||||
const char *caCert = argc > 5 ? argv[6] : NULL;
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect(hostname, port);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) {
|
||||
printf("SSL Error!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
redisLibeventAttach(c,base);
|
||||
redisAsyncSetConnectCallback(c,connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
event_base_dispatch(base);
|
||||
return 0;
|
||||
}
|
||||
+2
-13
@@ -9,12 +9,7 @@
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) {
|
||||
if (c->errstr) {
|
||||
printf("errstr: %s\n", c->errstr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (reply == NULL) return;
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* Disconnect after receiving the reply to GET */
|
||||
@@ -40,14 +35,8 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
struct event_base *base = event_base_new();
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
|
||||
struct timeval tv = {0};
|
||||
tv.tv_sec = 1;
|
||||
options.timeout = &tv;
|
||||
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
|
||||
Vendored
-97
@@ -1,97 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <hiredis.h>
|
||||
#include <hiredis_ssl.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
unsigned int j;
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
if (argc < 4) {
|
||||
printf("Usage: %s <host> <port> <cert> <key> [ca]\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
|
||||
int port = atoi(argv[2]);
|
||||
const char *cert = argv[3];
|
||||
const char *key = argv[4];
|
||||
const char *ca = argc > 4 ? argv[5] : NULL;
|
||||
|
||||
struct timeval tv = { 1, 500000 }; // 1.5 seconds
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, hostname, port);
|
||||
options.timeout = &tv;
|
||||
c = redisConnectWithOptions(&options);
|
||||
|
||||
if (c == NULL || c->err) {
|
||||
if (c) {
|
||||
printf("Connection error: %s\n", c->errstr);
|
||||
redisFree(c);
|
||||
} else {
|
||||
printf("Connection error: can't allocate redis context\n");
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) {
|
||||
printf("Couldn't initialize SSL!\n");
|
||||
printf("Error: %s\n", c->errstr);
|
||||
redisFree(c);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* PING server */
|
||||
reply = redisCommand(c,"PING");
|
||||
printf("PING: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Set a key */
|
||||
reply = redisCommand(c,"SET %s %s", "foo", "hello world");
|
||||
printf("SET: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Set a key using binary safe API */
|
||||
reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5);
|
||||
printf("SET (binary API): %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Try a GET and two INCR */
|
||||
reply = redisCommand(c,"GET foo");
|
||||
printf("GET foo: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
reply = redisCommand(c,"INCR counter");
|
||||
printf("INCR counter: %lld\n", reply->integer);
|
||||
freeReplyObject(reply);
|
||||
/* again ... */
|
||||
reply = redisCommand(c,"INCR counter");
|
||||
printf("INCR counter: %lld\n", reply->integer);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Create a list of numbers, from 0 to 9 */
|
||||
reply = redisCommand(c,"DEL mylist");
|
||||
freeReplyObject(reply);
|
||||
for (j = 0; j < 10; j++) {
|
||||
char buf[64];
|
||||
|
||||
snprintf(buf,64,"%u",j);
|
||||
reply = redisCommand(c,"LPUSH mylist element-%s", buf);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
/* Let's check what we have inside the list */
|
||||
reply = redisCommand(c,"LRANGE mylist 0 -1");
|
||||
if (reply->type == REDIS_REPLY_ARRAY) {
|
||||
for (j = 0; j < reply->elements; j++) {
|
||||
printf("%u) %s\n", j, reply->element[j]->str);
|
||||
}
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Disconnects and frees the context */
|
||||
redisFree(c);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Vendored
+2
-15
@@ -5,27 +5,14 @@
|
||||
#include <hiredis.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
unsigned int j, isunix = 0;
|
||||
unsigned int j;
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
|
||||
|
||||
if (argc > 2) {
|
||||
if (*argv[2] == 'u' || *argv[2] == 'U') {
|
||||
isunix = 1;
|
||||
/* in this case, host is the path to the unix socket */
|
||||
printf("Will connect to unix socket @%s\n", hostname);
|
||||
}
|
||||
}
|
||||
|
||||
int port = (argc > 2) ? atoi(argv[2]) : 6379;
|
||||
|
||||
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
|
||||
if (isunix) {
|
||||
c = redisConnectUnixWithTimeout(hostname, timeout);
|
||||
} else {
|
||||
c = redisConnectWithTimeout(hostname, port, timeout);
|
||||
}
|
||||
c = redisConnectWithTimeout(hostname, port, timeout);
|
||||
if (c == NULL || c->err) {
|
||||
if (c) {
|
||||
printf("Connection error: %s\n", c->errstr);
|
||||
|
||||
Vendored
+16
-3
@@ -1,12 +1,25 @@
|
||||
#ifndef __HIREDIS_FMACRO_H
|
||||
#define __HIREDIS_FMACRO_H
|
||||
|
||||
#define _XOPEN_SOURCE 600
|
||||
#if defined(__linux__)
|
||||
#define _BSD_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
#include <sys/cdefs.h>
|
||||
#endif
|
||||
|
||||
#if defined(__sun__)
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#else
|
||||
#if !(defined(__APPLE__) && defined(__MACH__)) && !(defined(__FreeBSD__))
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
/* Enable TCP_KEEPALIVE */
|
||||
#define _DARWIN_C_SOURCE
|
||||
#define _OSX
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
Vendored
+154
-219
@@ -34,6 +34,7 @@
|
||||
#include "fmacros.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
@@ -41,24 +42,12 @@
|
||||
#include "hiredis.h"
|
||||
#include "net.h"
|
||||
#include "sds.h"
|
||||
#include "async.h"
|
||||
#include "win32.h"
|
||||
|
||||
static redisContextFuncs redisContextDefaultFuncs = {
|
||||
.free_privdata = NULL,
|
||||
.async_read = redisAsyncRead,
|
||||
.async_write = redisAsyncWrite,
|
||||
.read = redisNetRead,
|
||||
.write = redisNetWrite
|
||||
};
|
||||
|
||||
static redisReply *createReplyObject(int type);
|
||||
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
|
||||
static void *createArrayObject(const redisReadTask *task, size_t elements);
|
||||
static void *createArrayObject(const redisReadTask *task, int elements);
|
||||
static void *createIntegerObject(const redisReadTask *task, long long value);
|
||||
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
|
||||
static void *createNilObject(const redisReadTask *task);
|
||||
static void *createBoolObject(const redisReadTask *task, int bval);
|
||||
|
||||
/* Default set of functions to build the reply. Keep in mind that such a
|
||||
* function returning NULL is interpreted as OOM. */
|
||||
@@ -66,9 +55,7 @@ static redisReplyObjectFunctions defaultFunctions = {
|
||||
createStringObject,
|
||||
createArrayObject,
|
||||
createIntegerObject,
|
||||
createDoubleObject,
|
||||
createNilObject,
|
||||
createBoolObject,
|
||||
freeReplyObject
|
||||
};
|
||||
|
||||
@@ -95,19 +82,18 @@ void freeReplyObject(void *reply) {
|
||||
case REDIS_REPLY_INTEGER:
|
||||
break; /* Nothing to free */
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_MAP:
|
||||
case REDIS_REPLY_SET:
|
||||
if (r->element != NULL) {
|
||||
for (j = 0; j < r->elements; j++)
|
||||
freeReplyObject(r->element[j]);
|
||||
if (r->element[j] != NULL)
|
||||
freeReplyObject(r->element[j]);
|
||||
free(r->element);
|
||||
}
|
||||
break;
|
||||
case REDIS_REPLY_ERROR:
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_STRING:
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
free(r->str);
|
||||
if (r->str != NULL)
|
||||
free(r->str);
|
||||
break;
|
||||
}
|
||||
free(r);
|
||||
@@ -121,49 +107,34 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
buf = malloc(len+1);
|
||||
if (buf == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(task->type == REDIS_REPLY_ERROR ||
|
||||
task->type == REDIS_REPLY_STATUS ||
|
||||
task->type == REDIS_REPLY_STRING ||
|
||||
task->type == REDIS_REPLY_VERB);
|
||||
task->type == REDIS_REPLY_STRING);
|
||||
|
||||
/* Copy string value */
|
||||
if (task->type == REDIS_REPLY_VERB) {
|
||||
buf = malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
|
||||
if (buf == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(r->vtype,str,3);
|
||||
r->vtype[3] = '\0';
|
||||
memcpy(buf,str+4,len-4);
|
||||
buf[len-4] = '\0';
|
||||
r->len = len-4;
|
||||
} else {
|
||||
buf = malloc(len+1);
|
||||
if (buf == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(buf,str,len);
|
||||
buf[len] = '\0';
|
||||
r->len = len;
|
||||
}
|
||||
memcpy(buf,str,len);
|
||||
buf[len] = '\0';
|
||||
r->str = buf;
|
||||
r->len = len;
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void *createArrayObject(const redisReadTask *task, size_t elements) {
|
||||
static void *createArrayObject(const redisReadTask *task, int elements) {
|
||||
redisReply *r, *parent;
|
||||
|
||||
r = createReplyObject(task->type);
|
||||
r = createReplyObject(REDIS_REPLY_ARRAY);
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
@@ -179,9 +150,7 @@ static void *createArrayObject(const redisReadTask *task, size_t elements) {
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
@@ -198,41 +167,7 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) {
|
||||
redisReply *r, *parent;
|
||||
|
||||
r = createReplyObject(REDIS_REPLY_DOUBLE);
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
r->dval = value;
|
||||
r->str = malloc(len+1);
|
||||
if (r->str == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* The double reply also has the original protocol string representing a
|
||||
* double as a null terminated string. This way the caller does not need
|
||||
* to format back for string conversion, especially since Redis does efforts
|
||||
* to make the string more human readable avoiding the calssical double
|
||||
* decimal string conversion artifacts. */
|
||||
memcpy(r->str, str, len);
|
||||
r->str[len] = '\0';
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
@@ -247,28 +182,7 @@ static void *createNilObject(const redisReadTask *task) {
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static void *createBoolObject(const redisReadTask *task, int bval) {
|
||||
redisReply *r, *parent;
|
||||
|
||||
r = createReplyObject(REDIS_REPLY_BOOL);
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
r->integer = bval != 0;
|
||||
|
||||
if (task->parent) {
|
||||
parent = task->parent->obj;
|
||||
assert(parent->type == REDIS_REPLY_ARRAY ||
|
||||
parent->type == REDIS_REPLY_MAP ||
|
||||
parent->type == REDIS_REPLY_SET);
|
||||
assert(parent->type == REDIS_REPLY_ARRAY);
|
||||
parent->element[task->idx] = r;
|
||||
}
|
||||
return r;
|
||||
@@ -518,7 +432,11 @@ cleanup:
|
||||
}
|
||||
|
||||
sdsfree(curarg);
|
||||
free(cmd);
|
||||
|
||||
/* No need to check cmd since it is the last statement that can fail,
|
||||
* but do it anyway to be as defensive as possible. */
|
||||
if (cmd != NULL)
|
||||
free(cmd);
|
||||
|
||||
return error_type;
|
||||
}
|
||||
@@ -663,7 +581,7 @@ void __redisSetError(redisContext *c, int type, const char *str) {
|
||||
} else {
|
||||
/* Only REDIS_ERR_IO may lack a description! */
|
||||
assert(type == REDIS_ERR_IO);
|
||||
strerror_r(errno, c->errstr, sizeof(c->errstr));
|
||||
__redis_strerror_r(errno, c->errstr, sizeof(c->errstr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,48 +589,53 @@ redisReader *redisReaderCreate(void) {
|
||||
return redisReaderCreateWithFunctions(&defaultFunctions);
|
||||
}
|
||||
|
||||
static redisContext *redisContextInit(const redisOptions *options) {
|
||||
static redisContext *redisContextInit(void) {
|
||||
redisContext *c;
|
||||
|
||||
c = calloc(1, sizeof(*c));
|
||||
c = calloc(1,sizeof(redisContext));
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->funcs = &redisContextDefaultFuncs;
|
||||
c->err = 0;
|
||||
c->errstr[0] = '\0';
|
||||
c->obuf = sdsempty();
|
||||
c->reader = redisReaderCreate();
|
||||
c->fd = REDIS_INVALID_FD;
|
||||
c->tcp.host = NULL;
|
||||
c->tcp.source_addr = NULL;
|
||||
c->unix_sock.path = NULL;
|
||||
c->timeout = NULL;
|
||||
|
||||
if (c->obuf == NULL || c->reader == NULL) {
|
||||
redisFree(c);
|
||||
return NULL;
|
||||
}
|
||||
(void)options; /* options are used in other functions */
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void redisFree(redisContext *c) {
|
||||
if (c == NULL)
|
||||
return;
|
||||
redisNetClose(c);
|
||||
|
||||
sdsfree(c->obuf);
|
||||
redisReaderFree(c->reader);
|
||||
free(c->tcp.host);
|
||||
free(c->tcp.source_addr);
|
||||
free(c->unix_sock.path);
|
||||
free(c->timeout);
|
||||
free(c->saddr);
|
||||
if (c->funcs->free_privdata) {
|
||||
c->funcs->free_privdata(c->privdata);
|
||||
}
|
||||
memset(c, 0xff, sizeof(*c));
|
||||
if (c->fd > 0)
|
||||
close(c->fd);
|
||||
if (c->obuf != NULL)
|
||||
sdsfree(c->obuf);
|
||||
if (c->reader != NULL)
|
||||
redisReaderFree(c->reader);
|
||||
if (c->tcp.host)
|
||||
free(c->tcp.host);
|
||||
if (c->tcp.source_addr)
|
||||
free(c->tcp.source_addr);
|
||||
if (c->unix_sock.path)
|
||||
free(c->unix_sock.path);
|
||||
if (c->timeout)
|
||||
free(c->timeout);
|
||||
free(c);
|
||||
}
|
||||
|
||||
redisFD redisFreeKeepFd(redisContext *c) {
|
||||
redisFD fd = c->fd;
|
||||
c->fd = REDIS_INVALID_FD;
|
||||
int redisFreeKeepFd(redisContext *c) {
|
||||
int fd = c->fd;
|
||||
c->fd = -1;
|
||||
redisFree(c);
|
||||
return fd;
|
||||
}
|
||||
@@ -721,13 +644,10 @@ int redisReconnect(redisContext *c) {
|
||||
c->err = 0;
|
||||
memset(c->errstr, '\0', strlen(c->errstr));
|
||||
|
||||
if (c->privdata && c->funcs->free_privdata) {
|
||||
c->funcs->free_privdata(c->privdata);
|
||||
c->privdata = NULL;
|
||||
if (c->fd > 0) {
|
||||
close(c->fd);
|
||||
}
|
||||
|
||||
redisNetClose(c);
|
||||
|
||||
sdsfree(c->obuf);
|
||||
redisReaderFree(c->reader);
|
||||
|
||||
@@ -748,107 +668,108 @@ int redisReconnect(redisContext *c) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
redisContext *redisConnectWithOptions(const redisOptions *options) {
|
||||
redisContext *c = redisContextInit(options);
|
||||
if (c == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (!(options->options & REDIS_OPT_NONBLOCK)) {
|
||||
c->flags |= REDIS_BLOCK;
|
||||
}
|
||||
if (options->options & REDIS_OPT_REUSEADDR) {
|
||||
c->flags |= REDIS_REUSEADDR;
|
||||
}
|
||||
if (options->options & REDIS_OPT_NOAUTOFREE) {
|
||||
c->flags |= REDIS_NO_AUTO_FREE;
|
||||
}
|
||||
|
||||
if (options->type == REDIS_CONN_TCP) {
|
||||
redisContextConnectBindTcp(c, options->endpoint.tcp.ip,
|
||||
options->endpoint.tcp.port, options->timeout,
|
||||
options->endpoint.tcp.source_addr);
|
||||
} else if (options->type == REDIS_CONN_UNIX) {
|
||||
redisContextConnectUnix(c, options->endpoint.unix_socket,
|
||||
options->timeout);
|
||||
} else if (options->type == REDIS_CONN_USERFD) {
|
||||
c->fd = options->endpoint.fd;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
} else {
|
||||
// Unknown type - FIXME - FREE
|
||||
return NULL;
|
||||
}
|
||||
if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
|
||||
redisContextSetTimeout(c, *options->timeout);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Connect to a Redis instance. On error the field error in the returned
|
||||
* context will be set to the return value of the error function.
|
||||
* When no set of reply functions is given, the default set will be used. */
|
||||
redisContext *redisConnect(const char *ip, int port) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
return redisConnectWithOptions(&options);
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->flags |= REDIS_BLOCK;
|
||||
redisContextConnectTcp(c,ip,port,NULL);
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.timeout = &tv;
|
||||
return redisConnectWithOptions(&options);
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->flags |= REDIS_BLOCK;
|
||||
redisContextConnectTcp(c,ip,port,&tv);
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectNonBlock(const char *ip, int port) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.options |= REDIS_OPT_NONBLOCK;
|
||||
return redisConnectWithOptions(&options);
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
redisContextConnectTcp(c,ip,port,NULL);
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectBindNonBlock(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.endpoint.tcp.source_addr = source_addr;
|
||||
options.options |= REDIS_OPT_NONBLOCK;
|
||||
return redisConnectWithOptions(&options);
|
||||
redisContext *c = redisContextInit();
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
options.endpoint.tcp.source_addr = source_addr;
|
||||
options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR;
|
||||
return redisConnectWithOptions(&options);
|
||||
redisContext *c = redisContextInit();
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
c->flags |= REDIS_REUSEADDR;
|
||||
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectUnix(const char *path) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||
return redisConnectWithOptions(&options);
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->flags |= REDIS_BLOCK;
|
||||
redisContextConnectUnix(c,path,NULL);
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||
options.timeout = &tv;
|
||||
return redisConnectWithOptions(&options);
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->flags |= REDIS_BLOCK;
|
||||
redisContextConnectUnix(c,path,&tv);
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectUnixNonBlock(const char *path) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||
options.options |= REDIS_OPT_NONBLOCK;
|
||||
return redisConnectWithOptions(&options);
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
redisContextConnectUnix(c,path,NULL);
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectFd(redisFD fd) {
|
||||
redisOptions options = {0};
|
||||
options.type = REDIS_CONN_USERFD;
|
||||
options.endpoint.fd = fd;
|
||||
return redisConnectWithOptions(&options);
|
||||
redisContext *redisConnectFd(int fd) {
|
||||
redisContext *c;
|
||||
|
||||
c = redisContextInit();
|
||||
if (c == NULL)
|
||||
return NULL;
|
||||
|
||||
c->fd = fd;
|
||||
c->flags |= REDIS_BLOCK | REDIS_CONNECTED;
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Set read/write timeout on a blocking socket. */
|
||||
@@ -868,7 +789,7 @@ int redisEnableKeepAlive(redisContext *c) {
|
||||
/* Use this function to handle a read event on the descriptor. It will try
|
||||
* and read some bytes from the socket and feed them to the reply parser.
|
||||
*
|
||||
* After this function is called, you may use redisGetReplyFromReader to
|
||||
* After this function is called, you may use redisContextReadReply to
|
||||
* see if there is a reply available. */
|
||||
int redisBufferRead(redisContext *c) {
|
||||
char buf[1024*16];
|
||||
@@ -878,15 +799,22 @@ int redisBufferRead(redisContext *c) {
|
||||
if (c->err)
|
||||
return REDIS_ERR;
|
||||
|
||||
nread = c->funcs->read(c, buf, sizeof(buf));
|
||||
if (nread > 0) {
|
||||
if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
|
||||
__redisSetError(c, c->reader->err, c->reader->errstr);
|
||||
return REDIS_ERR;
|
||||
nread = read(c->fd,buf,sizeof(buf));
|
||||
if (nread == -1) {
|
||||
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||
/* Try again later */
|
||||
} else {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
} else if (nread < 0) {
|
||||
} else if (nread == 0) {
|
||||
__redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
|
||||
__redisSetError(c,c->reader->err,c->reader->errstr);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
@@ -901,15 +829,21 @@ int redisBufferRead(redisContext *c) {
|
||||
* c->errstr to hold the appropriate error string.
|
||||
*/
|
||||
int redisBufferWrite(redisContext *c, int *done) {
|
||||
int nwritten;
|
||||
|
||||
/* Return early when the context has seen an error. */
|
||||
if (c->err)
|
||||
return REDIS_ERR;
|
||||
|
||||
if (sdslen(c->obuf) > 0) {
|
||||
int nwritten = c->funcs->write(c);
|
||||
if (nwritten < 0) {
|
||||
return REDIS_ERR;
|
||||
nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
|
||||
if (nwritten == -1) {
|
||||
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||
/* Try again later */
|
||||
} else {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
} else if (nwritten > 0) {
|
||||
if (nwritten == (signed)sdslen(c->obuf)) {
|
||||
sdsfree(c->obuf);
|
||||
@@ -1073,8 +1007,9 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap) {
|
||||
|
||||
void *redisCommand(redisContext *c, const char *format, ...) {
|
||||
va_list ap;
|
||||
void *reply = NULL;
|
||||
va_start(ap,format);
|
||||
void *reply = redisvCommand(c,format,ap);
|
||||
reply = redisvCommand(c,format,ap);
|
||||
va_end(ap);
|
||||
return reply;
|
||||
}
|
||||
|
||||
Vendored
+32
-108
@@ -35,18 +35,14 @@
|
||||
#define __HIREDIS_H
|
||||
#include "read.h"
|
||||
#include <stdarg.h> /* for va_list */
|
||||
#ifndef _MSC_VER
|
||||
#include <sys/time.h> /* for struct timeval */
|
||||
#else
|
||||
struct timeval; /* forward declaration */
|
||||
#endif
|
||||
#include <stdint.h> /* uintXX_t, etc */
|
||||
#include "sds.h" /* for sds */
|
||||
|
||||
#define HIREDIS_MAJOR 0
|
||||
#define HIREDIS_MINOR 14
|
||||
#define HIREDIS_PATCH 0
|
||||
#define HIREDIS_SONAME 0.14
|
||||
#define HIREDIS_MINOR 13
|
||||
#define HIREDIS_PATCH 3
|
||||
#define HIREDIS_SONAME 0.13
|
||||
|
||||
/* Connection type can be blocking or non-blocking and is set in the
|
||||
* least significant bit of the flags field in redisContext. */
|
||||
@@ -78,18 +74,36 @@ struct timeval; /* forward declaration */
|
||||
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
|
||||
#define REDIS_REUSEADDR 0x80
|
||||
|
||||
/**
|
||||
* Flag that indicates the user does not want the context to
|
||||
* be automatically freed upon error
|
||||
*/
|
||||
#define REDIS_NO_AUTO_FREE 0x200
|
||||
|
||||
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||
|
||||
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
|
||||
* SO_REUSEADDR is being used. */
|
||||
#define REDIS_CONNECT_RETRIES 10
|
||||
|
||||
/* strerror_r has two completely different prototypes and behaviors
|
||||
* depending on system issues, so we need to operate on the error buffer
|
||||
* differently depending on which strerror_r we're using. */
|
||||
#ifndef _GNU_SOURCE
|
||||
/* "regular" POSIX strerror_r that does the right thing. */
|
||||
#define __redis_strerror_r(errno, buf, len) \
|
||||
do { \
|
||||
strerror_r((errno), (buf), (len)); \
|
||||
} while (0)
|
||||
#else
|
||||
/* "bad" GNU strerror_r we need to clean up after. */
|
||||
#define __redis_strerror_r(errno, buf, len) \
|
||||
do { \
|
||||
char *err_str = strerror_r((errno), (buf), (len)); \
|
||||
/* If return value _isn't_ the start of the buffer we passed in, \
|
||||
* then GNU strerror_r returned an internal static buffer and we \
|
||||
* need to copy the result into our private buffer. */ \
|
||||
if (err_str != (buf)) { \
|
||||
strncpy((buf), err_str, ((len) - 1)); \
|
||||
buf[(len)-1] = '\0'; \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -98,12 +112,8 @@ extern "C" {
|
||||
typedef struct redisReply {
|
||||
int type; /* REDIS_REPLY_* */
|
||||
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
|
||||
double dval; /* The double when type is REDIS_REPLY_DOUBLE */
|
||||
size_t len; /* Length of string */
|
||||
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
||||
and REDIS_REPLY_DOUBLE (in additionl to dval). */
|
||||
char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
|
||||
terminated 3 character content type, such as "txt". */
|
||||
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
|
||||
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
|
||||
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
|
||||
} redisReply;
|
||||
@@ -123,93 +133,14 @@ void redisFreeSdsCommand(sds cmd);
|
||||
|
||||
enum redisConnectionType {
|
||||
REDIS_CONN_TCP,
|
||||
REDIS_CONN_UNIX,
|
||||
REDIS_CONN_USERFD
|
||||
REDIS_CONN_UNIX
|
||||
};
|
||||
|
||||
struct redisSsl;
|
||||
|
||||
#define REDIS_OPT_NONBLOCK 0x01
|
||||
#define REDIS_OPT_REUSEADDR 0x02
|
||||
|
||||
/**
|
||||
* Don't automatically free the async object on a connection failure,
|
||||
* or other implicit conditions. Only free on an explicit call to disconnect() or free()
|
||||
*/
|
||||
#define REDIS_OPT_NOAUTOFREE 0x04
|
||||
|
||||
/* In Unix systems a file descriptor is a regular signed int, with -1
|
||||
* representing an invalid descriptor. In Windows it is a SOCKET
|
||||
* (32- or 64-bit unsigned integer depending on the architecture), where
|
||||
* all bits set (~0) is INVALID_SOCKET. */
|
||||
#ifndef _WIN32
|
||||
typedef int redisFD;
|
||||
#define REDIS_INVALID_FD -1
|
||||
#else
|
||||
#ifdef _WIN64
|
||||
typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */
|
||||
#else
|
||||
typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */
|
||||
#endif
|
||||
#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
/*
|
||||
* the type of connection to use. This also indicates which
|
||||
* `endpoint` member field to use
|
||||
*/
|
||||
int type;
|
||||
/* bit field of REDIS_OPT_xxx */
|
||||
int options;
|
||||
/* timeout value. if NULL, no timeout is used */
|
||||
const struct timeval *timeout;
|
||||
union {
|
||||
/** use this field for tcp/ip connections */
|
||||
struct {
|
||||
const char *source_addr;
|
||||
const char *ip;
|
||||
int port;
|
||||
} tcp;
|
||||
/** use this field for unix domain sockets */
|
||||
const char *unix_socket;
|
||||
/**
|
||||
* use this field to have hiredis operate an already-open
|
||||
* file descriptor */
|
||||
redisFD fd;
|
||||
} endpoint;
|
||||
} redisOptions;
|
||||
|
||||
/**
|
||||
* Helper macros to initialize options to their specified fields.
|
||||
*/
|
||||
#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \
|
||||
(opts)->type = REDIS_CONN_TCP; \
|
||||
(opts)->endpoint.tcp.ip = ip_; \
|
||||
(opts)->endpoint.tcp.port = port_;
|
||||
|
||||
#define REDIS_OPTIONS_SET_UNIX(opts, path) \
|
||||
(opts)->type = REDIS_CONN_UNIX; \
|
||||
(opts)->endpoint.unix_socket = path;
|
||||
|
||||
struct redisAsyncContext;
|
||||
struct redisContext;
|
||||
|
||||
typedef struct redisContextFuncs {
|
||||
void (*free_privdata)(void *);
|
||||
void (*async_read)(struct redisAsyncContext *);
|
||||
void (*async_write)(struct redisAsyncContext *);
|
||||
int (*read)(struct redisContext *, char *, size_t);
|
||||
int (*write)(struct redisContext *);
|
||||
} redisContextFuncs;
|
||||
|
||||
/* Context for a connection to Redis */
|
||||
typedef struct redisContext {
|
||||
const redisContextFuncs *funcs; /* Function table */
|
||||
|
||||
int err; /* Error flags, 0 when there is no error */
|
||||
char errstr[128]; /* String representation of error when applicable */
|
||||
redisFD fd;
|
||||
int fd;
|
||||
int flags;
|
||||
char *obuf; /* Write buffer */
|
||||
redisReader *reader; /* Protocol reader */
|
||||
@@ -227,15 +158,8 @@ typedef struct redisContext {
|
||||
char *path;
|
||||
} unix_sock;
|
||||
|
||||
/* For non-blocking connect */
|
||||
struct sockadr *saddr;
|
||||
size_t addrlen;
|
||||
|
||||
/* Additional private data for hiredis addons such as SSL */
|
||||
void *privdata;
|
||||
} redisContext;
|
||||
|
||||
redisContext *redisConnectWithOptions(const redisOptions *options);
|
||||
redisContext *redisConnect(const char *ip, int port);
|
||||
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
|
||||
redisContext *redisConnectNonBlock(const char *ip, int port);
|
||||
@@ -246,7 +170,7 @@ redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
||||
redisContext *redisConnectUnix(const char *path);
|
||||
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
|
||||
redisContext *redisConnectUnixNonBlock(const char *path);
|
||||
redisContext *redisConnectFd(redisFD fd);
|
||||
redisContext *redisConnectFd(int fd);
|
||||
|
||||
/**
|
||||
* Reconnect the given context using the saved information.
|
||||
@@ -262,7 +186,7 @@ int redisReconnect(redisContext *c);
|
||||
int redisSetTimeout(redisContext *c, const struct timeval tv);
|
||||
int redisEnableKeepAlive(redisContext *c);
|
||||
void redisFree(redisContext *c);
|
||||
redisFD redisFreeKeepFd(redisContext *c);
|
||||
int redisFreeKeepFd(redisContext *c);
|
||||
int redisBufferRead(redisContext *c);
|
||||
int redisBufferWrite(redisContext *c, int *done);
|
||||
|
||||
|
||||
Vendored
-11
@@ -1,11 +0,0 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
pkgincludedir=${includedir}/hiredis
|
||||
|
||||
Name: hiredis
|
||||
Description: Minimalistic C client library for Redis.
|
||||
Version: @PROJECT_VERSION@
|
||||
Libs: -L${libdir} -lhiredis
|
||||
Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64
|
||||
Vendored
-53
@@ -1,53 +0,0 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019, Redis Labs
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __HIREDIS_SSL_H
|
||||
#define __HIREDIS_SSL_H
|
||||
|
||||
/* This is the underlying struct for SSL in ssl.h, which is not included to
|
||||
* keep build dependencies short here.
|
||||
*/
|
||||
struct ssl_st;
|
||||
|
||||
/**
|
||||
* Secure the connection using SSL. This should be done before any command is
|
||||
* executed on the connection.
|
||||
*/
|
||||
int redisSecureConnection(redisContext *c, const char *capath, const char *certpath,
|
||||
const char *keypath, const char *servername);
|
||||
|
||||
/**
|
||||
* Initiate SSL/TLS negotiation on a provided context.
|
||||
*/
|
||||
|
||||
int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
|
||||
|
||||
#endif /* __HIREDIS_SSL_H */
|
||||
Vendored
-12
@@ -1,12 +0,0 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
pkgincludedir=${includedir}/hiredis
|
||||
|
||||
Name: hiredis_ssl
|
||||
Description: SSL Support for hiredis.
|
||||
Version: @PROJECT_VERSION@
|
||||
Requires: hiredis
|
||||
Libs: -L${libdir} -lhiredis_ssl
|
||||
Libs.private: -lssl -lcrypto
|
||||
Vendored
+47
-142
@@ -34,72 +34,43 @@
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <poll.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "net.h"
|
||||
#include "sds.h"
|
||||
#include "sockcompat.h"
|
||||
#include "win32.h"
|
||||
|
||||
/* Defined in hiredis.c */
|
||||
void __redisSetError(redisContext *c, int type, const char *str);
|
||||
|
||||
void redisNetClose(redisContext *c) {
|
||||
if (c && c->fd != REDIS_INVALID_FD) {
|
||||
static void redisContextCloseFd(redisContext *c) {
|
||||
if (c && c->fd >= 0) {
|
||||
close(c->fd);
|
||||
c->fd = REDIS_INVALID_FD;
|
||||
c->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
int redisNetRead(redisContext *c, char *buf, size_t bufcap) {
|
||||
int nread = recv(c->fd, buf, bufcap, 0);
|
||||
if (nread == -1) {
|
||||
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||
/* Try again later */
|
||||
return 0;
|
||||
} else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) {
|
||||
/* especially in windows */
|
||||
__redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout");
|
||||
return -1;
|
||||
} else {
|
||||
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||
return -1;
|
||||
}
|
||||
} else if (nread == 0) {
|
||||
__redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
|
||||
return -1;
|
||||
} else {
|
||||
return nread;
|
||||
}
|
||||
}
|
||||
|
||||
int redisNetWrite(redisContext *c) {
|
||||
int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0);
|
||||
if (nwritten < 0) {
|
||||
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||
/* Try again later */
|
||||
} else {
|
||||
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return nwritten;
|
||||
}
|
||||
|
||||
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
|
||||
int errorno = errno; /* snprintf() may change errno */
|
||||
char buf[128] = { 0 };
|
||||
size_t len = 0;
|
||||
|
||||
if (prefix != NULL)
|
||||
len = snprintf(buf,sizeof(buf),"%s: ",prefix);
|
||||
strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len);
|
||||
__redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len);
|
||||
__redisSetError(c,type,buf);
|
||||
}
|
||||
|
||||
@@ -107,15 +78,15 @@ static int redisSetReuseAddr(redisContext *c) {
|
||||
int on = 1;
|
||||
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||
redisNetClose(c);
|
||||
redisContextCloseFd(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int redisCreateSocket(redisContext *c, int type) {
|
||||
redisFD s;
|
||||
if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) {
|
||||
int s;
|
||||
if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
@@ -129,7 +100,6 @@ static int redisCreateSocket(redisContext *c, int type) {
|
||||
}
|
||||
|
||||
static int redisSetBlocking(redisContext *c, int blocking) {
|
||||
#ifndef _WIN32
|
||||
int flags;
|
||||
|
||||
/* Set the socket nonblocking.
|
||||
@@ -137,7 +107,7 @@ static int redisSetBlocking(redisContext *c, int blocking) {
|
||||
* interrupted by a signal. */
|
||||
if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
|
||||
redisNetClose(c);
|
||||
redisContextCloseFd(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
@@ -148,23 +118,15 @@ static int redisSetBlocking(redisContext *c, int blocking) {
|
||||
|
||||
if (fcntl(c->fd, F_SETFL, flags) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
|
||||
redisNetClose(c);
|
||||
redisContextCloseFd(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
#else
|
||||
u_long mode = blocking ? 0 : 1;
|
||||
if (ioctl(c->fd, FIONBIO, &mode) == -1) {
|
||||
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)");
|
||||
redisNetClose(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
#endif /* _WIN32 */
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisKeepAlive(redisContext *c, int interval) {
|
||||
int val = 1;
|
||||
redisFD fd = c->fd;
|
||||
int fd = c->fd;
|
||||
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
|
||||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||
@@ -173,13 +135,14 @@ int redisKeepAlive(redisContext *c, int interval) {
|
||||
|
||||
val = interval;
|
||||
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#ifdef _OSX
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
#else
|
||||
#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
|
||||
val = interval;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||
return REDIS_ERR;
|
||||
@@ -207,7 +170,7 @@ static int redisSetTcpNoDelay(redisContext *c) {
|
||||
int yes = 1;
|
||||
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
|
||||
redisNetClose(c);
|
||||
redisContextCloseFd(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
@@ -249,50 +212,28 @@ static int redisContextWaitReady(redisContext *c, long msec) {
|
||||
|
||||
if ((res = poll(wfd, 1, msec)) == -1) {
|
||||
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
|
||||
redisNetClose(c);
|
||||
redisContextCloseFd(c);
|
||||
return REDIS_ERR;
|
||||
} else if (res == 0) {
|
||||
errno = ETIMEDOUT;
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||
redisNetClose(c);
|
||||
redisContextCloseFd(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {
|
||||
redisCheckSocketError(c);
|
||||
if (redisCheckSocketError(c) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||
redisNetClose(c);
|
||||
redisContextCloseFd(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
int redisCheckConnectDone(redisContext *c, int *completed) {
|
||||
int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen);
|
||||
if (rc == 0) {
|
||||
*completed = 1;
|
||||
return REDIS_OK;
|
||||
}
|
||||
switch (errno) {
|
||||
case EISCONN:
|
||||
*completed = 1;
|
||||
return REDIS_OK;
|
||||
case EALREADY:
|
||||
case EINPROGRESS:
|
||||
case EWOULDBLOCK:
|
||||
*completed = 0;
|
||||
return REDIS_OK;
|
||||
default:
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
int redisCheckSocketError(redisContext *c) {
|
||||
int err = 0, errno_saved = errno;
|
||||
int err = 0;
|
||||
socklen_t errlen = sizeof(err);
|
||||
|
||||
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
|
||||
@@ -300,10 +241,6 @@ int redisCheckSocketError(redisContext *c) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (err == 0) {
|
||||
err = errno_saved;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
errno = err;
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||
@@ -314,18 +251,11 @@ int redisCheckSocketError(redisContext *c) {
|
||||
}
|
||||
|
||||
int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
||||
const void *to_ptr = &tv;
|
||||
size_t to_sz = sizeof(tv);
|
||||
#ifdef _WIN32
|
||||
DWORD timeout_msec = tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||
to_ptr = &timeout_msec;
|
||||
to_sz = sizeof(timeout_msec);
|
||||
#endif
|
||||
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
|
||||
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) {
|
||||
if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
@@ -335,8 +265,7 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
||||
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
const struct timeval *timeout,
|
||||
const char *source_addr) {
|
||||
redisFD s;
|
||||
int rv, n;
|
||||
int s, rv, n;
|
||||
char _port[6]; /* strlen("65535"); */
|
||||
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
|
||||
int blocking = (c->flags & REDIS_BLOCK);
|
||||
@@ -356,7 +285,8 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
* This is a bit ugly, but atleast it works and doesn't leak memory.
|
||||
**/
|
||||
if (c->tcp.host != addr) {
|
||||
free(c->tcp.host);
|
||||
if (c->tcp.host)
|
||||
free(c->tcp.host);
|
||||
|
||||
c->tcp.host = strdup(addr);
|
||||
}
|
||||
@@ -369,7 +299,8 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
memcpy(c->timeout, timeout, sizeof(struct timeval));
|
||||
}
|
||||
} else {
|
||||
free(c->timeout);
|
||||
if (c->timeout)
|
||||
free(c->timeout);
|
||||
c->timeout = NULL;
|
||||
}
|
||||
|
||||
@@ -405,7 +336,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
}
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
addrretry:
|
||||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD)
|
||||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
|
||||
continue;
|
||||
|
||||
c->fd = s;
|
||||
@@ -425,7 +356,6 @@ addrretry:
|
||||
n = 1;
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
|
||||
sizeof(n)) < 0) {
|
||||
freeaddrinfo(bservinfo);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
@@ -444,34 +374,20 @@ addrretry:
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* For repeat connection */
|
||||
free(c->saddr);
|
||||
c->saddr = malloc(p->ai_addrlen);
|
||||
memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
|
||||
c->addrlen = p->ai_addrlen;
|
||||
|
||||
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
|
||||
if (errno == EHOSTUNREACH) {
|
||||
redisNetClose(c);
|
||||
redisContextCloseFd(c);
|
||||
continue;
|
||||
} else if (errno == EINPROGRESS) {
|
||||
if (blocking) {
|
||||
goto wait_for_ready;
|
||||
}
|
||||
/* This is ok.
|
||||
* Note that even when it's in blocking mode, we unset blocking
|
||||
* for `connect()`
|
||||
*/
|
||||
} else if (errno == EINPROGRESS && !blocking) {
|
||||
/* This is ok. */
|
||||
} else if (errno == EADDRNOTAVAIL && reuseaddr) {
|
||||
if (++reuses >= REDIS_CONNECT_RETRIES) {
|
||||
goto error;
|
||||
} else {
|
||||
redisNetClose(c);
|
||||
redisContextCloseFd(c);
|
||||
goto addrretry;
|
||||
}
|
||||
} else {
|
||||
wait_for_ready:
|
||||
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
|
||||
goto error;
|
||||
}
|
||||
@@ -495,10 +411,7 @@ addrretry:
|
||||
error:
|
||||
rv = REDIS_ERR;
|
||||
end:
|
||||
if(servinfo) {
|
||||
freeaddrinfo(servinfo);
|
||||
}
|
||||
|
||||
freeaddrinfo(servinfo);
|
||||
return rv; // Need to return REDIS_OK if alright
|
||||
}
|
||||
|
||||
@@ -514,12 +427,11 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
|
||||
}
|
||||
|
||||
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
|
||||
#ifndef _WIN32
|
||||
int blocking = (c->flags & REDIS_BLOCK);
|
||||
struct sockaddr_un *sa;
|
||||
struct sockaddr_un sa;
|
||||
long timeout_msec = -1;
|
||||
|
||||
if (redisCreateSocket(c,AF_UNIX) < 0)
|
||||
if (redisCreateSocket(c,AF_LOCAL) < 0)
|
||||
return REDIS_ERR;
|
||||
if (redisSetBlocking(c,0) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
@@ -536,18 +448,17 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
||||
memcpy(c->timeout, timeout, sizeof(struct timeval));
|
||||
}
|
||||
} else {
|
||||
free(c->timeout);
|
||||
if (c->timeout)
|
||||
free(c->timeout);
|
||||
c->timeout = NULL;
|
||||
}
|
||||
|
||||
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
|
||||
sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un)));
|
||||
c->addrlen = sizeof(struct sockaddr_un);
|
||||
sa->sun_family = AF_UNIX;
|
||||
strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
|
||||
if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) {
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS && !blocking) {
|
||||
/* This is ok. */
|
||||
} else {
|
||||
@@ -562,10 +473,4 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
||||
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
return REDIS_OK;
|
||||
#else
|
||||
/* We currently do not support Unix sockets for Windows. */
|
||||
/* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */
|
||||
errno = EPROTONOSUPPORT;
|
||||
return REDIS_ERR;
|
||||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
Vendored
+3
-4
@@ -37,9 +37,9 @@
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
void redisNetClose(redisContext *c);
|
||||
int redisNetRead(redisContext *c, char *buf, size_t bufcap);
|
||||
int redisNetWrite(redisContext *c);
|
||||
#if defined(__sun)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
int redisCheckSocketError(redisContext *c);
|
||||
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
|
||||
@@ -49,6 +49,5 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
|
||||
const char *source_addr);
|
||||
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
|
||||
int redisKeepAlive(redisContext *c, int interval);
|
||||
int redisCheckConnectDone(redisContext *c, int *completed);
|
||||
|
||||
#endif
|
||||
|
||||
Vendored
+49
-205
@@ -29,22 +29,19 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#include <strings.h>
|
||||
#endif
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "read.h"
|
||||
#include "sds.h"
|
||||
#include "win32.h"
|
||||
|
||||
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
||||
size_t len;
|
||||
@@ -55,9 +52,11 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
||||
}
|
||||
|
||||
/* Clear input buffer on errors. */
|
||||
sdsfree(r->buf);
|
||||
r->buf = NULL;
|
||||
r->pos = r->len = 0;
|
||||
if (r->buf != NULL) {
|
||||
sdsfree(r->buf);
|
||||
r->buf = NULL;
|
||||
r->pos = r->len = 0;
|
||||
}
|
||||
|
||||
/* Reset task stack. */
|
||||
r->ridx = -1;
|
||||
@@ -144,79 +143,33 @@ static char *seekNewline(char *s, size_t len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Convert a string into a long long. Returns REDIS_OK if the string could be
|
||||
* parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value
|
||||
* will be set to the parsed value when appropriate.
|
||||
*
|
||||
* Note that this function demands that the string strictly represents
|
||||
* a long long: no spaces or other characters before or after the string
|
||||
* representing the number are accepted, nor zeroes at the start if not
|
||||
* for the string "0" representing the zero number.
|
||||
*
|
||||
* Because of its strictness, it is safe to use this function to check if
|
||||
* you can convert a string into a long long, and obtain back the string
|
||||
* from the number without any loss in the string representation. */
|
||||
static int string2ll(const char *s, size_t slen, long long *value) {
|
||||
const char *p = s;
|
||||
size_t plen = 0;
|
||||
int negative = 0;
|
||||
unsigned long long v;
|
||||
/* Read a long long value starting at *s, under the assumption that it will be
|
||||
* terminated by \r\n. Ambiguously returns -1 for unexpected input. */
|
||||
static long long readLongLong(char *s) {
|
||||
long long v = 0;
|
||||
int dec, mult = 1;
|
||||
char c;
|
||||
|
||||
if (plen == slen)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Special case: first and only digit is 0. */
|
||||
if (slen == 1 && p[0] == '0') {
|
||||
if (value != NULL) *value = 0;
|
||||
return REDIS_OK;
|
||||
if (*s == '-') {
|
||||
mult = -1;
|
||||
s++;
|
||||
} else if (*s == '+') {
|
||||
mult = 1;
|
||||
s++;
|
||||
}
|
||||
|
||||
if (p[0] == '-') {
|
||||
negative = 1;
|
||||
p++; plen++;
|
||||
|
||||
/* Abort on only a negative sign. */
|
||||
if (plen == slen)
|
||||
return REDIS_ERR;
|
||||
while ((c = *(s++)) != '\r') {
|
||||
dec = c - '0';
|
||||
if (dec >= 0 && dec < 10) {
|
||||
v *= 10;
|
||||
v += dec;
|
||||
} else {
|
||||
/* Should not happen... */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* First digit should be 1-9, otherwise the string should just be 0. */
|
||||
if (p[0] >= '1' && p[0] <= '9') {
|
||||
v = p[0]-'0';
|
||||
p++; plen++;
|
||||
} else if (p[0] == '0' && slen == 1) {
|
||||
*value = 0;
|
||||
return REDIS_OK;
|
||||
} else {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
while (plen < slen && p[0] >= '0' && p[0] <= '9') {
|
||||
if (v > (ULLONG_MAX / 10)) /* Overflow. */
|
||||
return REDIS_ERR;
|
||||
v *= 10;
|
||||
|
||||
if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
|
||||
return REDIS_ERR;
|
||||
v += p[0]-'0';
|
||||
|
||||
p++; plen++;
|
||||
}
|
||||
|
||||
/* Return if not all bytes were used. */
|
||||
if (plen < slen)
|
||||
return REDIS_ERR;
|
||||
|
||||
if (negative) {
|
||||
if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
|
||||
return REDIS_ERR;
|
||||
if (value != NULL) *value = -v;
|
||||
} else {
|
||||
if (v > LLONG_MAX) /* Overflow. */
|
||||
return REDIS_ERR;
|
||||
if (value != NULL) *value = v;
|
||||
}
|
||||
return REDIS_OK;
|
||||
return mult*v;
|
||||
}
|
||||
|
||||
static char *readLine(redisReader *r, int *_len) {
|
||||
@@ -245,9 +198,7 @@ static void moveToNextTask(redisReader *r) {
|
||||
|
||||
cur = &(r->rstack[r->ridx]);
|
||||
prv = &(r->rstack[r->ridx-1]);
|
||||
assert(prv->type == REDIS_REPLY_ARRAY ||
|
||||
prv->type == REDIS_REPLY_MAP ||
|
||||
prv->type == REDIS_REPLY_SET);
|
||||
assert(prv->type == REDIS_REPLY_ARRAY);
|
||||
if (cur->idx == prv->elements-1) {
|
||||
r->ridx--;
|
||||
} else {
|
||||
@@ -269,58 +220,10 @@ static int processLineItem(redisReader *r) {
|
||||
|
||||
if ((p = readLine(r,&len)) != NULL) {
|
||||
if (cur->type == REDIS_REPLY_INTEGER) {
|
||||
if (r->fn && r->fn->createInteger) {
|
||||
long long v;
|
||||
if (string2ll(p, len, &v) == REDIS_ERR) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bad integer value");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
obj = r->fn->createInteger(cur,v);
|
||||
} else {
|
||||
if (r->fn && r->fn->createInteger)
|
||||
obj = r->fn->createInteger(cur,readLongLong(p));
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_INTEGER;
|
||||
}
|
||||
} else if (cur->type == REDIS_REPLY_DOUBLE) {
|
||||
if (r->fn && r->fn->createDouble) {
|
||||
char buf[326], *eptr;
|
||||
double d;
|
||||
|
||||
if ((size_t)len >= sizeof(buf)) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Double value is too large");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
memcpy(buf,p,len);
|
||||
buf[len] = '\0';
|
||||
|
||||
if (strcasecmp(buf,",inf") == 0) {
|
||||
d = INFINITY; /* Positive infinite. */
|
||||
} else if (strcasecmp(buf,",-inf") == 0) {
|
||||
d = -INFINITY; /* Nevative infinite. */
|
||||
} else {
|
||||
d = strtod((char*)buf,&eptr);
|
||||
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bad double value");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
obj = r->fn->createDouble(cur,d,buf,len);
|
||||
} else {
|
||||
obj = (void*)REDIS_REPLY_DOUBLE;
|
||||
}
|
||||
} else if (cur->type == REDIS_REPLY_NIL) {
|
||||
if (r->fn && r->fn->createNil)
|
||||
obj = r->fn->createNil(cur);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_NIL;
|
||||
} else if (cur->type == REDIS_REPLY_BOOL) {
|
||||
int bval = p[0] == 't' || p[0] == 'T';
|
||||
if (r->fn && r->fn->createBool)
|
||||
obj = r->fn->createBool(cur,bval);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_BOOL;
|
||||
} else {
|
||||
/* Type will be error or status. */
|
||||
if (r->fn && r->fn->createString)
|
||||
@@ -347,7 +250,7 @@ static int processBulkItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj = NULL;
|
||||
char *p, *s;
|
||||
long long len;
|
||||
long len;
|
||||
unsigned long bytelen;
|
||||
int success = 0;
|
||||
|
||||
@@ -356,20 +259,9 @@ static int processBulkItem(redisReader *r) {
|
||||
if (s != NULL) {
|
||||
p = r->buf+r->pos;
|
||||
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
|
||||
len = readLongLong(p);
|
||||
|
||||
if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bad bulk string length");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bulk string length out of range");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (len == -1) {
|
||||
if (len < 0) {
|
||||
/* The nil object can always be created. */
|
||||
if (r->fn && r->fn->createNil)
|
||||
obj = r->fn->createNil(cur);
|
||||
@@ -380,18 +272,10 @@ static int processBulkItem(redisReader *r) {
|
||||
/* Only continue when the buffer contains the entire bulk item. */
|
||||
bytelen += len+2; /* include \r\n */
|
||||
if (r->pos+bytelen <= r->len) {
|
||||
if ((cur->type == REDIS_REPLY_VERB && len < 4) ||
|
||||
(cur->type == REDIS_REPLY_VERB && s[5] != ':'))
|
||||
{
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Verbatim string 4 bytes of content type are "
|
||||
"missing or incorrectly encoded.");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (r->fn && r->fn->createString)
|
||||
obj = r->fn->createString(cur,s+2,len);
|
||||
else
|
||||
obj = (void*)(long)cur->type;
|
||||
obj = (void*)REDIS_REPLY_STRING;
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
@@ -415,13 +299,12 @@ static int processBulkItem(redisReader *r) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Process the array, map and set types. */
|
||||
static int processAggregateItem(redisReader *r) {
|
||||
static int processMultiBulkItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj;
|
||||
char *p;
|
||||
long long elements;
|
||||
int root = 0, len;
|
||||
long elements;
|
||||
int root = 0;
|
||||
|
||||
/* Set error for nested multi bulks with depth > 7 */
|
||||
if (r->ridx == 8) {
|
||||
@@ -430,21 +313,10 @@ static int processAggregateItem(redisReader *r) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if ((p = readLine(r,&len)) != NULL) {
|
||||
if (string2ll(p, len, &elements) == REDIS_ERR) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Bad multi-bulk length");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if ((p = readLine(r,NULL)) != NULL) {
|
||||
elements = readLongLong(p);
|
||||
root = (r->ridx == 0);
|
||||
|
||||
if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX)) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"Multi-bulk length out of range");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (elements == -1) {
|
||||
if (r->fn && r->fn->createNil)
|
||||
obj = r->fn->createNil(cur);
|
||||
@@ -458,12 +330,10 @@ static int processAggregateItem(redisReader *r) {
|
||||
|
||||
moveToNextTask(r);
|
||||
} else {
|
||||
if (cur->type == REDIS_REPLY_MAP) elements *= 2;
|
||||
|
||||
if (r->fn && r->fn->createArray)
|
||||
obj = r->fn->createArray(cur,elements);
|
||||
else
|
||||
obj = (void*)(long)cur->type;
|
||||
obj = (void*)REDIS_REPLY_ARRAY;
|
||||
|
||||
if (obj == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
@@ -511,30 +381,12 @@ static int processItem(redisReader *r) {
|
||||
case ':':
|
||||
cur->type = REDIS_REPLY_INTEGER;
|
||||
break;
|
||||
case ',':
|
||||
cur->type = REDIS_REPLY_DOUBLE;
|
||||
break;
|
||||
case '_':
|
||||
cur->type = REDIS_REPLY_NIL;
|
||||
break;
|
||||
case '$':
|
||||
cur->type = REDIS_REPLY_STRING;
|
||||
break;
|
||||
case '*':
|
||||
cur->type = REDIS_REPLY_ARRAY;
|
||||
break;
|
||||
case '%':
|
||||
cur->type = REDIS_REPLY_MAP;
|
||||
break;
|
||||
case '~':
|
||||
cur->type = REDIS_REPLY_SET;
|
||||
break;
|
||||
case '#':
|
||||
cur->type = REDIS_REPLY_BOOL;
|
||||
break;
|
||||
case '=':
|
||||
cur->type = REDIS_REPLY_VERB;
|
||||
break;
|
||||
default:
|
||||
__redisReaderSetErrorProtocolByte(r,*p);
|
||||
return REDIS_ERR;
|
||||
@@ -550,17 +402,11 @@ static int processItem(redisReader *r) {
|
||||
case REDIS_REPLY_ERROR:
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_INTEGER:
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
case REDIS_REPLY_NIL:
|
||||
case REDIS_REPLY_BOOL:
|
||||
return processLineItem(r);
|
||||
case REDIS_REPLY_STRING:
|
||||
case REDIS_REPLY_VERB:
|
||||
return processBulkItem(r);
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_MAP:
|
||||
case REDIS_REPLY_SET:
|
||||
return processAggregateItem(r);
|
||||
return processMultiBulkItem(r);
|
||||
default:
|
||||
assert(NULL);
|
||||
return REDIS_ERR; /* Avoid warning. */
|
||||
@@ -570,10 +416,12 @@ static int processItem(redisReader *r) {
|
||||
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
|
||||
redisReader *r;
|
||||
|
||||
r = calloc(1,sizeof(redisReader));
|
||||
r = calloc(sizeof(redisReader),1);
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
r->err = 0;
|
||||
r->errstr[0] = '\0';
|
||||
r->fn = fn;
|
||||
r->buf = sdsempty();
|
||||
r->maxbuf = REDIS_READER_MAX_BUF;
|
||||
@@ -587,11 +435,10 @@ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
|
||||
}
|
||||
|
||||
void redisReaderFree(redisReader *r) {
|
||||
if (r == NULL)
|
||||
return;
|
||||
if (r->reply != NULL && r->fn && r->fn->freeObject)
|
||||
r->fn->freeObject(r->reply);
|
||||
sdsfree(r->buf);
|
||||
if (r->buf != NULL)
|
||||
sdsfree(r->buf);
|
||||
free(r);
|
||||
}
|
||||
|
||||
@@ -670,11 +517,8 @@ int redisReaderGetReply(redisReader *r, void **reply) {
|
||||
|
||||
/* Emit a reply when there is one. */
|
||||
if (r->ridx == -1) {
|
||||
if (reply != NULL) {
|
||||
if (reply != NULL)
|
||||
*reply = r->reply;
|
||||
} else if (r->reply != NULL && r->fn && r->fn->freeObject) {
|
||||
r->fn->freeObject(r->reply);
|
||||
}
|
||||
r->reply = NULL;
|
||||
}
|
||||
return REDIS_OK;
|
||||
|
||||
Vendored
+1
-12
@@ -45,7 +45,6 @@
|
||||
#define REDIS_ERR_EOF 3 /* End of file */
|
||||
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
|
||||
#define REDIS_ERR_OOM 5 /* Out of memory */
|
||||
#define REDIS_ERR_TIMEOUT 6 /* Timed out */
|
||||
#define REDIS_ERR_OTHER 2 /* Everything else... */
|
||||
|
||||
#define REDIS_REPLY_STRING 1
|
||||
@@ -54,14 +53,6 @@
|
||||
#define REDIS_REPLY_NIL 4
|
||||
#define REDIS_REPLY_STATUS 5
|
||||
#define REDIS_REPLY_ERROR 6
|
||||
#define REDIS_REPLY_DOUBLE 7
|
||||
#define REDIS_REPLY_BOOL 8
|
||||
#define REDIS_REPLY_MAP 9
|
||||
#define REDIS_REPLY_SET 10
|
||||
#define REDIS_REPLY_ATTR 11
|
||||
#define REDIS_REPLY_PUSH 12
|
||||
#define REDIS_REPLY_BIGNUM 13
|
||||
#define REDIS_REPLY_VERB 14
|
||||
|
||||
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
|
||||
|
||||
@@ -80,11 +71,9 @@ typedef struct redisReadTask {
|
||||
|
||||
typedef struct redisReplyObjectFunctions {
|
||||
void *(*createString)(const redisReadTask*, char*, size_t);
|
||||
void *(*createArray)(const redisReadTask*, size_t);
|
||||
void *(*createArray)(const redisReadTask*, int);
|
||||
void *(*createInteger)(const redisReadTask*, long long);
|
||||
void *(*createDouble)(const redisReadTask*, double, char*, size_t);
|
||||
void *(*createNil)(const redisReadTask*);
|
||||
void *(*createBool)(const redisReadTask*, int);
|
||||
void (*freeObject)(void*);
|
||||
} redisReplyObjectFunctions;
|
||||
|
||||
|
||||
Vendored
+5
-24
@@ -219,10 +219,7 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
hdrlen = sdsHdrSize(type);
|
||||
if (oldtype==type) {
|
||||
newsh = s_realloc(sh, hdrlen+newlen+1);
|
||||
if (newsh == NULL) {
|
||||
s_free(sh);
|
||||
return NULL;
|
||||
}
|
||||
if (newsh == NULL) return NULL;
|
||||
s = (char*)newsh+hdrlen;
|
||||
} else {
|
||||
/* Since the header size changes, need to move the string forward,
|
||||
@@ -595,7 +592,6 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
/* Make sure there is always space for at least 1 char. */
|
||||
if (sdsavail(s)==0) {
|
||||
s = sdsMakeRoomFor(s,1);
|
||||
if (s == NULL) goto fmt_error;
|
||||
}
|
||||
|
||||
switch(*f) {
|
||||
@@ -609,7 +605,6 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
l = (next == 's') ? strlen(str) : sdslen(str);
|
||||
if (sdsavail(s) < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
if (s == NULL) goto fmt_error;
|
||||
}
|
||||
memcpy(s+i,str,l);
|
||||
sdsinclen(s,l);
|
||||
@@ -626,7 +621,6 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
l = sdsll2str(buf,num);
|
||||
if (sdsavail(s) < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
if (s == NULL) goto fmt_error;
|
||||
}
|
||||
memcpy(s+i,buf,l);
|
||||
sdsinclen(s,l);
|
||||
@@ -644,7 +638,6 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
l = sdsull2str(buf,unum);
|
||||
if (sdsavail(s) < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
if (s == NULL) goto fmt_error;
|
||||
}
|
||||
memcpy(s+i,buf,l);
|
||||
sdsinclen(s,l);
|
||||
@@ -669,10 +662,6 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
/* Add null-term */
|
||||
s[i] = '\0';
|
||||
return s;
|
||||
|
||||
fmt_error:
|
||||
va_end(ap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Remove the part of the string from left and from right composed just of
|
||||
@@ -1029,18 +1018,10 @@ sds *sdssplitargs(const char *line, int *argc) {
|
||||
if (*p) p++;
|
||||
}
|
||||
/* add the token to the vector */
|
||||
{
|
||||
char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
|
||||
if (new_vector == NULL) {
|
||||
s_free(vector);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
vector = new_vector;
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
}
|
||||
vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
} else {
|
||||
/* Even on empty input string return something not NULL. */
|
||||
if (vector == NULL) vector = s_malloc(sizeof(void*));
|
||||
|
||||
Vendored
+14
-17
@@ -34,9 +34,6 @@
|
||||
#define __SDS_H
|
||||
|
||||
#define SDS_MAX_PREALLOC (1024*1024)
|
||||
#ifdef _MSC_VER
|
||||
#define __attribute__(x)
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
@@ -135,20 +132,20 @@ static inline void sdssetlen(sds s, size_t newlen) {
|
||||
case SDS_TYPE_5:
|
||||
{
|
||||
unsigned char *fp = ((unsigned char*)s)-1;
|
||||
*fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS));
|
||||
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
|
||||
}
|
||||
break;
|
||||
case SDS_TYPE_8:
|
||||
SDS_HDR(8,s)->len = (uint8_t)newlen;
|
||||
SDS_HDR(8,s)->len = newlen;
|
||||
break;
|
||||
case SDS_TYPE_16:
|
||||
SDS_HDR(16,s)->len = (uint16_t)newlen;
|
||||
SDS_HDR(16,s)->len = newlen;
|
||||
break;
|
||||
case SDS_TYPE_32:
|
||||
SDS_HDR(32,s)->len = (uint32_t)newlen;
|
||||
SDS_HDR(32,s)->len = newlen;
|
||||
break;
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->len = (uint64_t)newlen;
|
||||
SDS_HDR(64,s)->len = newlen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -159,21 +156,21 @@ static inline void sdsinclen(sds s, size_t inc) {
|
||||
case SDS_TYPE_5:
|
||||
{
|
||||
unsigned char *fp = ((unsigned char*)s)-1;
|
||||
unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
|
||||
unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
|
||||
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
|
||||
}
|
||||
break;
|
||||
case SDS_TYPE_8:
|
||||
SDS_HDR(8,s)->len += (uint8_t)inc;
|
||||
SDS_HDR(8,s)->len += inc;
|
||||
break;
|
||||
case SDS_TYPE_16:
|
||||
SDS_HDR(16,s)->len += (uint16_t)inc;
|
||||
SDS_HDR(16,s)->len += inc;
|
||||
break;
|
||||
case SDS_TYPE_32:
|
||||
SDS_HDR(32,s)->len += (uint32_t)inc;
|
||||
SDS_HDR(32,s)->len += inc;
|
||||
break;
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->len += (uint64_t)inc;
|
||||
SDS_HDR(64,s)->len += inc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -203,16 +200,16 @@ static inline void sdssetalloc(sds s, size_t newlen) {
|
||||
/* Nothing to do, this type has no total allocation info. */
|
||||
break;
|
||||
case SDS_TYPE_8:
|
||||
SDS_HDR(8,s)->alloc = (uint8_t)newlen;
|
||||
SDS_HDR(8,s)->alloc = newlen;
|
||||
break;
|
||||
case SDS_TYPE_16:
|
||||
SDS_HDR(16,s)->alloc = (uint16_t)newlen;
|
||||
SDS_HDR(16,s)->alloc = newlen;
|
||||
break;
|
||||
case SDS_TYPE_32:
|
||||
SDS_HDR(32,s)->alloc = (uint32_t)newlen;
|
||||
SDS_HDR(32,s)->alloc = newlen;
|
||||
break;
|
||||
case SDS_TYPE_64:
|
||||
SDS_HDR(64,s)->alloc = (uint64_t)newlen;
|
||||
SDS_HDR(64,s)->alloc = newlen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-248
@@ -1,248 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#define REDIS_SOCKCOMPAT_IMPLEMENTATION
|
||||
#include "sockcompat.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
static int _wsaErrorToErrno(int err) {
|
||||
switch (err) {
|
||||
case WSAEWOULDBLOCK:
|
||||
return EWOULDBLOCK;
|
||||
case WSAEINPROGRESS:
|
||||
return EINPROGRESS;
|
||||
case WSAEALREADY:
|
||||
return EALREADY;
|
||||
case WSAENOTSOCK:
|
||||
return ENOTSOCK;
|
||||
case WSAEDESTADDRREQ:
|
||||
return EDESTADDRREQ;
|
||||
case WSAEMSGSIZE:
|
||||
return EMSGSIZE;
|
||||
case WSAEPROTOTYPE:
|
||||
return EPROTOTYPE;
|
||||
case WSAENOPROTOOPT:
|
||||
return ENOPROTOOPT;
|
||||
case WSAEPROTONOSUPPORT:
|
||||
return EPROTONOSUPPORT;
|
||||
case WSAEOPNOTSUPP:
|
||||
return EOPNOTSUPP;
|
||||
case WSAEAFNOSUPPORT:
|
||||
return EAFNOSUPPORT;
|
||||
case WSAEADDRINUSE:
|
||||
return EADDRINUSE;
|
||||
case WSAEADDRNOTAVAIL:
|
||||
return EADDRNOTAVAIL;
|
||||
case WSAENETDOWN:
|
||||
return ENETDOWN;
|
||||
case WSAENETUNREACH:
|
||||
return ENETUNREACH;
|
||||
case WSAENETRESET:
|
||||
return ENETRESET;
|
||||
case WSAECONNABORTED:
|
||||
return ECONNABORTED;
|
||||
case WSAECONNRESET:
|
||||
return ECONNRESET;
|
||||
case WSAENOBUFS:
|
||||
return ENOBUFS;
|
||||
case WSAEISCONN:
|
||||
return EISCONN;
|
||||
case WSAENOTCONN:
|
||||
return ENOTCONN;
|
||||
case WSAETIMEDOUT:
|
||||
return ETIMEDOUT;
|
||||
case WSAECONNREFUSED:
|
||||
return ECONNREFUSED;
|
||||
case WSAELOOP:
|
||||
return ELOOP;
|
||||
case WSAENAMETOOLONG:
|
||||
return ENAMETOOLONG;
|
||||
case WSAEHOSTUNREACH:
|
||||
return EHOSTUNREACH;
|
||||
case WSAENOTEMPTY:
|
||||
return ENOTEMPTY;
|
||||
default:
|
||||
/* We just return a generic I/O error if we could not find a relevant error. */
|
||||
return EIO;
|
||||
}
|
||||
}
|
||||
|
||||
static void _updateErrno(int success) {
|
||||
errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError());
|
||||
}
|
||||
|
||||
static int _initWinsock() {
|
||||
static int s_initialized = 0;
|
||||
if (!s_initialized) {
|
||||
static WSADATA wsadata;
|
||||
int err = WSAStartup(MAKEWORD(2,2), &wsadata);
|
||||
if (err != 0) {
|
||||
errno = _wsaErrorToErrno(err);
|
||||
return 0;
|
||||
}
|
||||
s_initialized = 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
|
||||
/* Note: This function is likely to be called before other functions, so run init here. */
|
||||
if (!_initWinsock()) {
|
||||
return EAI_FAIL;
|
||||
}
|
||||
|
||||
switch (getaddrinfo(node, service, hints, res)) {
|
||||
case 0: return 0;
|
||||
case WSATRY_AGAIN: return EAI_AGAIN;
|
||||
case WSAEINVAL: return EAI_BADFLAGS;
|
||||
case WSAEAFNOSUPPORT: return EAI_FAMILY;
|
||||
case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY;
|
||||
case WSAHOST_NOT_FOUND: return EAI_NONAME;
|
||||
case WSATYPE_NOT_FOUND: return EAI_SERVICE;
|
||||
case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE;
|
||||
default: return EAI_FAIL; /* Including WSANO_RECOVERY */
|
||||
}
|
||||
}
|
||||
|
||||
const char *win32_gai_strerror(int errcode) {
|
||||
switch (errcode) {
|
||||
case 0: errcode = 0; break;
|
||||
case EAI_AGAIN: errcode = WSATRY_AGAIN; break;
|
||||
case EAI_BADFLAGS: errcode = WSAEINVAL; break;
|
||||
case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break;
|
||||
case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break;
|
||||
case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break;
|
||||
case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break;
|
||||
case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break;
|
||||
default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */
|
||||
}
|
||||
return gai_strerror(errcode);
|
||||
}
|
||||
|
||||
void win32_freeaddrinfo(struct addrinfo *res) {
|
||||
freeaddrinfo(res);
|
||||
}
|
||||
|
||||
SOCKET win32_socket(int domain, int type, int protocol) {
|
||||
SOCKET s;
|
||||
|
||||
/* Note: This function is likely to be called before other functions, so run init here. */
|
||||
if (!_initWinsock()) {
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
_updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET);
|
||||
return s;
|
||||
}
|
||||
|
||||
int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) {
|
||||
int ret = ioctlsocket(fd, (long)request, argp);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
|
||||
int ret = bind(sockfd, addr, addrlen);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
|
||||
int ret = connect(sockfd, addr, addrlen);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
|
||||
/* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as
|
||||
* EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX
|
||||
* logic consistent. */
|
||||
if (errno == EWOULDBLOCK) {
|
||||
errno = EINPROGRESS;
|
||||
}
|
||||
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) {
|
||||
int ret = 0;
|
||||
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
|
||||
if (*optlen >= sizeof (struct timeval)) {
|
||||
struct timeval *tv = optval;
|
||||
DWORD timeout = 0;
|
||||
socklen_t dwlen = 0;
|
||||
ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen);
|
||||
tv->tv_sec = timeout / 1000;
|
||||
tv->tv_usec = (timeout * 1000) % 1000000;
|
||||
} else {
|
||||
ret = WSAEFAULT;
|
||||
}
|
||||
*optlen = sizeof (struct timeval);
|
||||
} else {
|
||||
ret = getsockopt(sockfd, level, optname, (char*)optval, optlen);
|
||||
}
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {
|
||||
int ret = 0;
|
||||
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
|
||||
struct timeval *tv = optval;
|
||||
DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;
|
||||
ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
|
||||
} else {
|
||||
ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen);
|
||||
}
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_close(SOCKET fd) {
|
||||
int ret = closesocket(fd);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) {
|
||||
int ret = recv(sockfd, (char*)buf, (int)len, flags);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) {
|
||||
int ret = send(sockfd, (const char*)buf, (int)len, flags);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
|
||||
int ret = WSAPoll(fds, nfds, timeout);
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
#endif /* _WIN32 */
|
||||
Vendored
-91
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __SOCKCOMPAT_H
|
||||
#define __SOCKCOMPAT_H
|
||||
|
||||
#ifndef _WIN32
|
||||
/* For POSIX systems we use the standard BSD socket API. */
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <poll.h>
|
||||
#else
|
||||
/* For Windows we use winsock. */
|
||||
#undef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
typedef signed long ssize_t;
|
||||
#endif
|
||||
|
||||
/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */
|
||||
int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
|
||||
const char *win32_gai_strerror(int errcode);
|
||||
void win32_freeaddrinfo(struct addrinfo *res);
|
||||
SOCKET win32_socket(int domain, int type, int protocol);
|
||||
int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp);
|
||||
int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
||||
int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
||||
int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen);
|
||||
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen);
|
||||
int win32_close(SOCKET fd);
|
||||
ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags);
|
||||
ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags);
|
||||
typedef ULONG nfds_t;
|
||||
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||
|
||||
#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION
|
||||
#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res)
|
||||
#undef gai_strerror
|
||||
#define gai_strerror(errcode) win32_gai_strerror(errcode)
|
||||
#define freeaddrinfo(res) win32_freeaddrinfo(res)
|
||||
#define socket(domain, type, protocol) win32_socket(domain, type, protocol)
|
||||
#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp)
|
||||
#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen)
|
||||
#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen)
|
||||
#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen)
|
||||
#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen)
|
||||
#define close(fd) win32_close(fd)
|
||||
#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags)
|
||||
#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags)
|
||||
#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout)
|
||||
#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#endif /* __SOCKCOMPAT_H */
|
||||
Vendored
-448
@@ -1,448 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2019, Redis Labs
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "async.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include "async_private.h"
|
||||
|
||||
void __redisSetError(redisContext *c, int type, const char *str);
|
||||
|
||||
/* The SSL context is attached to SSL/TLS connections as a privdata. */
|
||||
typedef struct redisSSLContext {
|
||||
/**
|
||||
* OpenSSL SSL_CTX; It is optional and will not be set when using
|
||||
* user-supplied SSL.
|
||||
*/
|
||||
SSL_CTX *ssl_ctx;
|
||||
|
||||
/**
|
||||
* OpenSSL SSL object.
|
||||
*/
|
||||
SSL *ssl;
|
||||
|
||||
/**
|
||||
* SSL_write() requires to be called again with the same arguments it was
|
||||
* previously called with in the event of an SSL_read/SSL_write situation
|
||||
*/
|
||||
size_t lastLen;
|
||||
|
||||
/** Whether the SSL layer requires read (possibly before a write) */
|
||||
int wantRead;
|
||||
|
||||
/**
|
||||
* Whether a write was requested prior to a read. If set, the write()
|
||||
* should resume whenever a read takes place, if possible
|
||||
*/
|
||||
int pendingWrite;
|
||||
} redisSSLContext;
|
||||
|
||||
/* Forward declaration */
|
||||
redisContextFuncs redisContextSSLFuncs;
|
||||
|
||||
#ifdef HIREDIS_SSL_TRACE
|
||||
/**
|
||||
* Callback used for debugging
|
||||
*/
|
||||
static void sslLogCallback(const SSL *ssl, int where, int ret) {
|
||||
const char *retstr = "";
|
||||
int should_log = 1;
|
||||
/* Ignore low-level SSL stuff */
|
||||
|
||||
if (where & SSL_CB_ALERT) {
|
||||
should_log = 1;
|
||||
}
|
||||
if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) {
|
||||
should_log = 1;
|
||||
}
|
||||
if ((where & SSL_CB_EXIT) && ret == 0) {
|
||||
should_log = 1;
|
||||
}
|
||||
|
||||
if (!should_log) {
|
||||
return;
|
||||
}
|
||||
|
||||
retstr = SSL_alert_type_string(ret);
|
||||
printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr);
|
||||
|
||||
if (where == SSL_CB_HANDSHAKE_DONE) {
|
||||
printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* OpenSSL global initialization and locking handling callbacks.
|
||||
* Note that this is only required for OpenSSL < 1.1.0.
|
||||
*/
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
#define HIREDIS_USE_CRYPTO_LOCKS
|
||||
#endif
|
||||
|
||||
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
||||
typedef pthread_mutex_t sslLockType;
|
||||
static void sslLockInit(sslLockType *l) {
|
||||
pthread_mutex_init(l, NULL);
|
||||
}
|
||||
static void sslLockAcquire(sslLockType *l) {
|
||||
pthread_mutex_lock(l);
|
||||
}
|
||||
static void sslLockRelease(sslLockType *l) {
|
||||
pthread_mutex_unlock(l);
|
||||
}
|
||||
static pthread_mutex_t *ossl_locks;
|
||||
|
||||
static void opensslDoLock(int mode, int lkid, const char *f, int line) {
|
||||
sslLockType *l = ossl_locks + lkid;
|
||||
|
||||
if (mode & CRYPTO_LOCK) {
|
||||
sslLockAcquire(l);
|
||||
} else {
|
||||
sslLockRelease(l);
|
||||
}
|
||||
|
||||
(void)f;
|
||||
(void)line;
|
||||
}
|
||||
|
||||
static void initOpensslLocks(void) {
|
||||
unsigned ii, nlocks;
|
||||
if (CRYPTO_get_locking_callback() != NULL) {
|
||||
/* Someone already set the callback before us. Don't destroy it! */
|
||||
return;
|
||||
}
|
||||
nlocks = CRYPTO_num_locks();
|
||||
ossl_locks = malloc(sizeof(*ossl_locks) * nlocks);
|
||||
for (ii = 0; ii < nlocks; ii++) {
|
||||
sslLockInit(ossl_locks + ii);
|
||||
}
|
||||
CRYPTO_set_locking_callback(opensslDoLock);
|
||||
}
|
||||
#endif /* HIREDIS_USE_CRYPTO_LOCKS */
|
||||
|
||||
/**
|
||||
* SSL Connection initialization.
|
||||
*/
|
||||
|
||||
static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
|
||||
if (c->privdata) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
c->privdata = calloc(1, sizeof(redisSSLContext));
|
||||
|
||||
c->funcs = &redisContextSSLFuncs;
|
||||
redisSSLContext *rssl = c->privdata;
|
||||
|
||||
rssl->ssl_ctx = ssl_ctx;
|
||||
rssl->ssl = ssl;
|
||||
|
||||
SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||
SSL_set_fd(rssl->ssl, c->fd);
|
||||
SSL_set_connect_state(rssl->ssl);
|
||||
|
||||
ERR_clear_error();
|
||||
int rv = SSL_connect(rssl->ssl);
|
||||
if (rv == 1) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
rv = SSL_get_error(rssl->ssl, rv);
|
||||
if (((c->flags & REDIS_BLOCK) == 0) &&
|
||||
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
if (c->err == 0) {
|
||||
char err[512];
|
||||
if (rv == SSL_ERROR_SYSCALL)
|
||||
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
|
||||
else {
|
||||
unsigned long e = ERR_peek_last_error();
|
||||
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
|
||||
ERR_reason_error_string(e));
|
||||
}
|
||||
__redisSetError(c, REDIS_ERR_IO, err);
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
int redisInitiateSSL(redisContext *c, SSL *ssl) {
|
||||
return redisSSLConnect(c, NULL, ssl);
|
||||
}
|
||||
|
||||
int redisSecureConnection(redisContext *c, const char *capath,
|
||||
const char *certpath, const char *keypath, const char *servername) {
|
||||
|
||||
SSL_CTX *ssl_ctx = NULL;
|
||||
SSL *ssl = NULL;
|
||||
|
||||
/* Initialize global OpenSSL stuff */
|
||||
static int isInit = 0;
|
||||
if (!isInit) {
|
||||
isInit = 1;
|
||||
SSL_library_init();
|
||||
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
||||
initOpensslLocks();
|
||||
#endif
|
||||
}
|
||||
|
||||
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
|
||||
if (!ssl_ctx) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX");
|
||||
goto error;
|
||||
}
|
||||
|
||||
#ifdef HIREDIS_SSL_TRACE
|
||||
SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback);
|
||||
#endif
|
||||
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
||||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
|
||||
if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (capath) {
|
||||
if (!SSL_CTX_load_verify_locations(ssl_ctx, capath, NULL)) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Invalid CA certificate");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (certpath) {
|
||||
if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certpath)) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client certificate");
|
||||
goto error;
|
||||
}
|
||||
if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client key");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
ssl = SSL_new(ssl_ctx);
|
||||
if (!ssl) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
|
||||
goto error;
|
||||
}
|
||||
if (servername) {
|
||||
if (!SSL_set_tlsext_host_name(ssl, servername)) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
return redisSSLConnect(c, ssl_ctx, ssl);
|
||||
|
||||
error:
|
||||
if (ssl) SSL_free(ssl);
|
||||
if (ssl_ctx) SSL_CTX_free(ssl_ctx);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
static int maybeCheckWant(redisSSLContext *rssl, int rv) {
|
||||
/**
|
||||
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
|
||||
* and true is returned. False is returned otherwise
|
||||
*/
|
||||
if (rv == SSL_ERROR_WANT_READ) {
|
||||
rssl->wantRead = 1;
|
||||
return 1;
|
||||
} else if (rv == SSL_ERROR_WANT_WRITE) {
|
||||
rssl->pendingWrite = 1;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of redisContextFuncs for SSL connections.
|
||||
*/
|
||||
|
||||
static void redisSSLFreeContext(void *privdata){
|
||||
redisSSLContext *rsc = privdata;
|
||||
|
||||
if (!rsc) return;
|
||||
if (rsc->ssl) {
|
||||
SSL_free(rsc->ssl);
|
||||
rsc->ssl = NULL;
|
||||
}
|
||||
if (rsc->ssl_ctx) {
|
||||
SSL_CTX_free(rsc->ssl_ctx);
|
||||
rsc->ssl_ctx = NULL;
|
||||
}
|
||||
free(rsc);
|
||||
}
|
||||
|
||||
static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
|
||||
redisSSLContext *rssl = c->privdata;
|
||||
|
||||
int nread = SSL_read(rssl->ssl, buf, bufcap);
|
||||
if (nread > 0) {
|
||||
return nread;
|
||||
} else if (nread == 0) {
|
||||
__redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
|
||||
return -1;
|
||||
} else {
|
||||
int err = SSL_get_error(rssl->ssl, nread);
|
||||
if (c->flags & REDIS_BLOCK) {
|
||||
/**
|
||||
* In blocking mode, we should never end up in a situation where
|
||||
* we get an error without it being an actual error, except
|
||||
* in the case of EINTR, which can be spuriously received from
|
||||
* debuggers or whatever.
|
||||
*/
|
||||
if (errno == EINTR) {
|
||||
return 0;
|
||||
} else {
|
||||
const char *msg = NULL;
|
||||
if (errno == EAGAIN) {
|
||||
msg = "Resource temporarily unavailable";
|
||||
}
|
||||
__redisSetError(c, REDIS_ERR_IO, msg);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We can very well get an EWOULDBLOCK/EAGAIN, however
|
||||
*/
|
||||
if (maybeCheckWant(rssl, err)) {
|
||||
return 0;
|
||||
} else {
|
||||
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int redisSSLWrite(redisContext *c) {
|
||||
redisSSLContext *rssl = c->privdata;
|
||||
|
||||
size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
|
||||
int rv = SSL_write(rssl->ssl, c->obuf, len);
|
||||
|
||||
if (rv > 0) {
|
||||
rssl->lastLen = 0;
|
||||
} else if (rv < 0) {
|
||||
rssl->lastLen = len;
|
||||
|
||||
int err = SSL_get_error(rssl->ssl, rv);
|
||||
if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
|
||||
return 0;
|
||||
} else {
|
||||
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void redisSSLAsyncRead(redisAsyncContext *ac) {
|
||||
int rv;
|
||||
redisSSLContext *rssl = ac->c.privdata;
|
||||
redisContext *c = &ac->c;
|
||||
|
||||
rssl->wantRead = 0;
|
||||
|
||||
if (rssl->pendingWrite) {
|
||||
int done;
|
||||
|
||||
/* This is probably just a write event */
|
||||
rssl->pendingWrite = 0;
|
||||
rv = redisBufferWrite(c, &done);
|
||||
if (rv == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
return;
|
||||
} else if (!done) {
|
||||
_EL_ADD_WRITE(ac);
|
||||
}
|
||||
}
|
||||
|
||||
rv = redisBufferRead(c);
|
||||
if (rv == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
_EL_ADD_READ(ac);
|
||||
redisProcessCallbacks(ac);
|
||||
}
|
||||
}
|
||||
|
||||
static void redisSSLAsyncWrite(redisAsyncContext *ac) {
|
||||
int rv, done = 0;
|
||||
redisSSLContext *rssl = ac->c.privdata;
|
||||
redisContext *c = &ac->c;
|
||||
|
||||
rssl->pendingWrite = 0;
|
||||
rv = redisBufferWrite(c, &done);
|
||||
if (rv == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
if (rssl->wantRead) {
|
||||
/* Need to read-before-write */
|
||||
rssl->pendingWrite = 1;
|
||||
_EL_DEL_WRITE(ac);
|
||||
} else {
|
||||
/* No extra reads needed, just need to write more */
|
||||
_EL_ADD_WRITE(ac);
|
||||
}
|
||||
} else {
|
||||
/* Already done! */
|
||||
_EL_DEL_WRITE(ac);
|
||||
}
|
||||
|
||||
/* Always reschedule a read */
|
||||
_EL_ADD_READ(ac);
|
||||
}
|
||||
|
||||
redisContextFuncs redisContextSSLFuncs = {
|
||||
.free_privdata = redisSSLFreeContext,
|
||||
.async_read = redisSSLAsyncRead,
|
||||
.async_write = redisSSLAsyncWrite,
|
||||
.read = redisSSLRead,
|
||||
.write = redisSSLWrite
|
||||
};
|
||||
|
||||
Vendored
+23
-212
@@ -3,9 +3,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <netdb.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
@@ -13,16 +11,12 @@
|
||||
#include <limits.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
#include "hiredis_ssl.h"
|
||||
#endif
|
||||
#include "net.h"
|
||||
|
||||
enum connection_type {
|
||||
CONN_TCP,
|
||||
CONN_UNIX,
|
||||
CONN_FD,
|
||||
CONN_SSL
|
||||
CONN_FD
|
||||
};
|
||||
|
||||
struct config {
|
||||
@@ -37,14 +31,6 @@ struct config {
|
||||
struct {
|
||||
const char *path;
|
||||
} unix_sock;
|
||||
|
||||
struct {
|
||||
const char *host;
|
||||
int port;
|
||||
const char *ca_cert;
|
||||
const char *cert;
|
||||
const char *key;
|
||||
} ssl;
|
||||
};
|
||||
|
||||
/* The following lines make up our testing "framework" :) */
|
||||
@@ -105,27 +91,11 @@ static int disconnect(redisContext *c, int keep_fd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void do_ssl_handshake(redisContext *c, struct config config) {
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
redisSecureConnection(c, config.ssl.ca_cert, config.ssl.cert, config.ssl.key, NULL);
|
||||
if (c->err) {
|
||||
printf("SSL error: %s\n", c->errstr);
|
||||
redisFree(c);
|
||||
exit(1);
|
||||
}
|
||||
#else
|
||||
(void) c;
|
||||
(void) config;
|
||||
#endif
|
||||
}
|
||||
|
||||
static redisContext *do_connect(struct config config) {
|
||||
static redisContext *connect(struct config config) {
|
||||
redisContext *c = NULL;
|
||||
|
||||
if (config.type == CONN_TCP) {
|
||||
c = redisConnect(config.tcp.host, config.tcp.port);
|
||||
} else if (config.type == CONN_SSL) {
|
||||
c = redisConnect(config.ssl.host, config.ssl.port);
|
||||
} else if (config.type == CONN_UNIX) {
|
||||
c = redisConnectUnix(config.unix_sock.path);
|
||||
} else if (config.type == CONN_FD) {
|
||||
@@ -149,21 +119,9 @@ static redisContext *do_connect(struct config config) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (config.type == CONN_SSL) {
|
||||
do_ssl_handshake(c, config);
|
||||
}
|
||||
|
||||
return select_database(c);
|
||||
}
|
||||
|
||||
static void do_reconnect(redisContext *c, struct config config) {
|
||||
redisReconnect(c);
|
||||
|
||||
if (config.type == CONN_SSL) {
|
||||
do_ssl_handshake(c, config);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_format_commands(void) {
|
||||
char *cmd;
|
||||
int len;
|
||||
@@ -290,7 +248,7 @@ static void test_append_formatted_commands(struct config config) {
|
||||
char *cmd;
|
||||
int len;
|
||||
|
||||
c = do_connect(config);
|
||||
c = connect(config);
|
||||
|
||||
test("Append format command: ");
|
||||
|
||||
@@ -344,82 +302,6 @@ static void test_reply_reader(void) {
|
||||
strncasecmp(reader->errstr,"No support for",14) == 0);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Correctly parses LLONG_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ":9223372036854775807\r\n",22);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
|
||||
((redisReply*)reply)->integer == LLONG_MAX);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when > LLONG_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ":9223372036854775808\r\n",22);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bad integer value") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Correctly parses LLONG_MIN: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ":-9223372036854775808\r\n",23);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
|
||||
((redisReply*)reply)->integer == LLONG_MIN);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when < LLONG_MIN: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ":-9223372036854775809\r\n",23);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bad integer value") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when array < -1: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when bulk < -1: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "$-2\r\nasdf\r\n",11);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
#if LLONG_MAX > SIZE_MAX
|
||||
test("Set error when array > SIZE_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when bulk > SIZE_MAX: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
#endif
|
||||
|
||||
test("Works with NULL functions for reply: ");
|
||||
reader = redisReaderCreate();
|
||||
reader->fn = NULL;
|
||||
@@ -474,35 +356,20 @@ static void test_free_null(void) {
|
||||
test_cond(reply == NULL);
|
||||
}
|
||||
|
||||
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
|
||||
static void test_blocking_connection_errors(void) {
|
||||
redisContext *c;
|
||||
struct addrinfo hints = {.ai_family = AF_INET};
|
||||
struct addrinfo *ai_tmp = NULL;
|
||||
|
||||
int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
|
||||
if (rv != 0) {
|
||||
// Address does *not* exist
|
||||
test("Returns error when host cannot be resolved: ");
|
||||
// First see if this domain name *actually* resolves to NXDOMAIN
|
||||
c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);
|
||||
test_cond(
|
||||
c->err == REDIS_ERR_OTHER &&
|
||||
(strcmp(c->errstr, "Name or service not known") == 0 ||
|
||||
strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
|
||||
strcmp(c->errstr, "Name does not resolve") == 0 ||
|
||||
strcmp(c->errstr,
|
||||
"nodename nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr, "No address associated with hostname") == 0 ||
|
||||
strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
|
||||
strcmp(c->errstr,
|
||||
"hostname nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr, "no address associated with name") == 0));
|
||||
redisFree(c);
|
||||
} else {
|
||||
printf("Skipping NXDOMAIN test. Found evil ISP!\n");
|
||||
freeaddrinfo(ai_tmp);
|
||||
}
|
||||
test("Returns error when host cannot be resolved: ");
|
||||
c = redisConnect((char*)"idontexist.test", 6379);
|
||||
test_cond(c->err == REDIS_ERR_OTHER &&
|
||||
(strcmp(c->errstr,"Name or service not known") == 0 ||
|
||||
strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 ||
|
||||
strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr,"No address associated with hostname") == 0 ||
|
||||
strcmp(c->errstr,"Temporary failure in name resolution") == 0 ||
|
||||
strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr,"no address associated with name") == 0));
|
||||
redisFree(c);
|
||||
|
||||
test("Returns error when the port is not open: ");
|
||||
c = redisConnect((char*)"localhost", 1);
|
||||
@@ -520,7 +387,7 @@ static void test_blocking_connection(struct config config) {
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
|
||||
c = do_connect(config);
|
||||
c = connect(config);
|
||||
|
||||
test("Is able to deliver commands: ");
|
||||
reply = redisCommand(c,"PING");
|
||||
@@ -601,7 +468,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
const char *cmd = "DEBUG SLEEP 3\r\n";
|
||||
struct timeval tv;
|
||||
|
||||
c = do_connect(config);
|
||||
c = connect(config);
|
||||
test("Successfully completes a command when the timeout is not exceeded: ");
|
||||
reply = redisCommand(c,"SET foo fast");
|
||||
freeReplyObject(reply);
|
||||
@@ -613,10 +480,9 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
freeReplyObject(reply);
|
||||
disconnect(c, 0);
|
||||
|
||||
c = do_connect(config);
|
||||
c = connect(config);
|
||||
test("Does not return a reply when the command times out: ");
|
||||
redisAppendFormattedCommand(c, cmd, strlen(cmd));
|
||||
s = c->funcs->write(c);
|
||||
s = write(c->fd, cmd, strlen(cmd));
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 10000;
|
||||
redisSetTimeout(c, tv);
|
||||
@@ -625,7 +491,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("Reconnect properly reconnects after a timeout: ");
|
||||
do_reconnect(c, config);
|
||||
redisReconnect(c);
|
||||
reply = redisCommand(c, "PING");
|
||||
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
||||
freeReplyObject(reply);
|
||||
@@ -633,7 +499,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
test("Reconnect properly uses owned parameters: ");
|
||||
config.tcp.host = "foo";
|
||||
config.unix_sock.path = "foo";
|
||||
do_reconnect(c, config);
|
||||
redisReconnect(c);
|
||||
reply = redisCommand(c, "PING");
|
||||
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
||||
freeReplyObject(reply);
|
||||
@@ -648,7 +514,7 @@ static void test_blocking_io_errors(struct config config) {
|
||||
int major, minor;
|
||||
|
||||
/* Connect to target given by config. */
|
||||
c = do_connect(config);
|
||||
c = connect(config);
|
||||
{
|
||||
/* Find out Redis version to determine the path for the next test */
|
||||
const char *field = "redis_version:";
|
||||
@@ -683,7 +549,7 @@ static void test_blocking_io_errors(struct config config) {
|
||||
strcmp(c->errstr,"Server closed the connection") == 0);
|
||||
redisFree(c);
|
||||
|
||||
c = do_connect(config);
|
||||
c = connect(config);
|
||||
test("Returns I/O error on socket timeout: ");
|
||||
struct timeval tv = { 0, 1000 };
|
||||
assert(redisSetTimeout(c,tv) == REDIS_OK);
|
||||
@@ -717,7 +583,7 @@ static void test_invalid_timeout_errors(struct config config) {
|
||||
}
|
||||
|
||||
static void test_throughput(struct config config) {
|
||||
redisContext *c = do_connect(config);
|
||||
redisContext *c = connect(config);
|
||||
redisReply **replies;
|
||||
int i, num;
|
||||
long long t1, t2;
|
||||
@@ -750,17 +616,6 @@ static void test_throughput(struct config config) {
|
||||
free(replies);
|
||||
printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
t1 = usec();
|
||||
for (i = 0; i < num; i++) {
|
||||
replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
|
||||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
|
||||
}
|
||||
t2 = usec();
|
||||
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
||||
free(replies);
|
||||
printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||
|
||||
num = 10000;
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
for (i = 0; i < num; i++)
|
||||
@@ -789,19 +644,6 @@ static void test_throughput(struct config config) {
|
||||
free(replies);
|
||||
printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*num);
|
||||
for (i = 0; i < num; i++)
|
||||
redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
|
||||
t1 = usec();
|
||||
for (i = 0; i < num; i++) {
|
||||
assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
|
||||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
|
||||
}
|
||||
t2 = usec();
|
||||
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
|
||||
free(replies);
|
||||
printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
|
||||
|
||||
disconnect(c, 0);
|
||||
}
|
||||
|
||||
@@ -936,23 +778,6 @@ int main(int argc, char **argv) {
|
||||
throughput = 0;
|
||||
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
|
||||
test_inherit_fd = 0;
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
|
||||
argv++; argc--;
|
||||
cfg.ssl.port = atoi(argv[0]);
|
||||
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) {
|
||||
argv++; argc--;
|
||||
cfg.ssl.host = argv[0];
|
||||
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) {
|
||||
argv++; argc--;
|
||||
cfg.ssl.ca_cert = argv[0];
|
||||
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) {
|
||||
argv++; argc--;
|
||||
cfg.ssl.cert = argv[0];
|
||||
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) {
|
||||
argv++; argc--;
|
||||
cfg.ssl.key = argv[0];
|
||||
#endif
|
||||
} else {
|
||||
fprintf(stderr, "Invalid argument: %s\n", argv[0]);
|
||||
exit(1);
|
||||
@@ -981,20 +806,6 @@ int main(int argc, char **argv) {
|
||||
test_blocking_io_errors(cfg);
|
||||
if (throughput) test_throughput(cfg);
|
||||
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
if (cfg.ssl.port && cfg.ssl.host) {
|
||||
printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
|
||||
cfg.type = CONN_SSL;
|
||||
|
||||
test_blocking_connection(cfg);
|
||||
test_blocking_connection_timeouts(cfg);
|
||||
test_blocking_io_errors(cfg);
|
||||
test_invalid_timeout_errors(cfg);
|
||||
test_append_formatted_commands(cfg);
|
||||
if (throughput) test_throughput(cfg);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (test_inherit_fd) {
|
||||
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
|
||||
cfg.type = CONN_FD;
|
||||
|
||||
Vendored
-70
@@ -1,70 +0,0 @@
|
||||
#!/bin/sh -ue
|
||||
|
||||
REDIS_SERVER=${REDIS_SERVER:-redis-server}
|
||||
REDIS_PORT=${REDIS_PORT:-56379}
|
||||
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
|
||||
TEST_SSL=${TEST_SSL:-0}
|
||||
SSL_TEST_ARGS=
|
||||
|
||||
tmpdir=$(mktemp -d)
|
||||
PID_FILE=${tmpdir}/hiredis-test-redis.pid
|
||||
SOCK_FILE=${tmpdir}/hiredis-test-redis.sock
|
||||
|
||||
if [ "$TEST_SSL" = "1" ]; then
|
||||
SSL_CA_CERT=${tmpdir}/ca.crt
|
||||
SSL_CA_KEY=${tmpdir}/ca.key
|
||||
SSL_CERT=${tmpdir}/redis.crt
|
||||
SSL_KEY=${tmpdir}/redis.key
|
||||
|
||||
openssl genrsa -out ${tmpdir}/ca.key 4096
|
||||
openssl req \
|
||||
-x509 -new -nodes -sha256 \
|
||||
-key ${SSL_CA_KEY} \
|
||||
-days 3650 \
|
||||
-subj '/CN=Hiredis Test CA' \
|
||||
-out ${SSL_CA_CERT}
|
||||
openssl genrsa -out ${SSL_KEY} 2048
|
||||
openssl req \
|
||||
-new -sha256 \
|
||||
-key ${SSL_KEY} \
|
||||
-subj '/CN=Hiredis Test Cert' | \
|
||||
openssl x509 \
|
||||
-req -sha256 \
|
||||
-CA ${SSL_CA_CERT} \
|
||||
-CAkey ${SSL_CA_KEY} \
|
||||
-CAserial ${tmpdir}/ca.txt \
|
||||
-CAcreateserial \
|
||||
-days 365 \
|
||||
-out ${SSL_CERT}
|
||||
|
||||
SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}"
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
set +e
|
||||
kill $(cat ${PID_FILE})
|
||||
rm -rf ${tmpdir}
|
||||
}
|
||||
trap cleanup INT TERM EXIT
|
||||
|
||||
cat > ${tmpdir}/redis.conf <<EOF
|
||||
daemonize yes
|
||||
pidfile ${PID_FILE}
|
||||
port ${REDIS_PORT}
|
||||
bind 127.0.0.1
|
||||
unixsocket ${SOCK_FILE}
|
||||
EOF
|
||||
|
||||
if [ "$TEST_SSL" = "1" ]; then
|
||||
cat >> ${tmpdir}/redis.conf <<EOF
|
||||
tls-port ${REDIS_SSL_PORT}
|
||||
tls-ca-cert-file ${SSL_CA_CERT}
|
||||
tls-cert-file ${SSL_CERT}
|
||||
tls-key-file ${SSL_KEY}
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat ${tmpdir}/redis.conf
|
||||
${REDIS_SERVER} ${tmpdir}/redis.conf
|
||||
|
||||
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS}
|
||||
Vendored
+2
-16
@@ -2,20 +2,10 @@
|
||||
#define _WIN32_HELPER_INCLUDE
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#include <winsock2.h> /* for struct timeval */
|
||||
|
||||
#ifndef inline
|
||||
#define inline __inline
|
||||
#endif
|
||||
|
||||
#ifndef strcasecmp
|
||||
#define strcasecmp stricmp
|
||||
#endif
|
||||
|
||||
#ifndef strncasecmp
|
||||
#define strncasecmp strnicmp
|
||||
#endif
|
||||
|
||||
#ifndef va_copy
|
||||
#define va_copy(d,s) ((d) = (s))
|
||||
#endif
|
||||
@@ -47,10 +37,6 @@ __inline int c99_snprintf(char* str, size_t size, const char* format, ...)
|
||||
return count;
|
||||
}
|
||||
#endif
|
||||
#endif /* _MSC_VER */
|
||||
|
||||
#ifdef _WIN32
|
||||
#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#endif /* _WIN32_HELPER_INCLUDE */
|
||||
#endif
|
||||
#endif
|
||||
+1
-7
@@ -787,13 +787,7 @@ background_thread_stats_read(tsdn_t *tsdn, background_thread_stats_t *stats) {
|
||||
nstime_init(&stats->run_interval, 0);
|
||||
for (unsigned i = 0; i < max_background_threads; i++) {
|
||||
background_thread_info_t *info = &background_thread_info[i];
|
||||
if (malloc_mutex_trylock(tsdn, &info->mtx)) {
|
||||
/*
|
||||
* Each background thread run may take a long time;
|
||||
* avoid waiting on the stats if the thread is active.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
malloc_mutex_lock(tsdn, &info->mtx);
|
||||
if (info->state != background_thread_stopped) {
|
||||
num_runs += info->tot_n_runs;
|
||||
nstime_add(&stats->run_interval, &info->tot_sleep_time);
|
||||
|
||||
Vendored
+1
-24
@@ -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, 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.
|
||||
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.
|
||||
|
||||
## Terminals, in 2010.
|
||||
|
||||
@@ -126,24 +126,6 @@ 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
|
||||
@@ -240,8 +222,3 @@ 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,7 +55,6 @@ 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] != '/') {
|
||||
@@ -66,10 +65,6 @@ 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
+3
-29
@@ -125,7 +125,6 @@ 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. */
|
||||
@@ -198,19 +197,6 @@ 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;
|
||||
@@ -499,8 +485,6 @@ 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)
|
||||
@@ -539,11 +523,7 @@ 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));
|
||||
if (maskmode == 1) {
|
||||
while (len--) abAppend(&ab,"*",1);
|
||||
} else {
|
||||
abAppend(&ab,buf,len);
|
||||
}
|
||||
abAppend(&ab,buf,len);
|
||||
/* Show hits if any. */
|
||||
refreshShowHints(&ab,l,plen);
|
||||
/* Erase to right */
|
||||
@@ -597,12 +577,7 @@ static void refreshMultiLine(struct linenoiseState *l) {
|
||||
|
||||
/* Write the prompt and the current buffer content */
|
||||
abAppend(&ab,l->prompt,strlen(l->prompt));
|
||||
if (maskmode == 1) {
|
||||
unsigned int i;
|
||||
for (i = 0; i < l->len; i++) abAppend(&ab,"*",1);
|
||||
} else {
|
||||
abAppend(&ab,l->buf,l->len);
|
||||
}
|
||||
abAppend(&ab,l->buf,l->len);
|
||||
|
||||
/* Show hits if any. */
|
||||
refreshShowHints(&ab,l,plen);
|
||||
@@ -670,8 +645,7 @@ 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. */
|
||||
char d = (maskmode==1) ? '*' : c;
|
||||
if (write(l->ofd,&d,1) == -1) return -1;
|
||||
if (write(l->ofd,&c,1) == -1) return -1;
|
||||
} else {
|
||||
refreshLine(l);
|
||||
}
|
||||
|
||||
Vendored
-2
@@ -65,8 +65,6 @@ int linenoiseHistoryLoad(const char *filename);
|
||||
void linenoiseClearScreen(void);
|
||||
void linenoiseSetMultiLine(int ml);
|
||||
void linenoisePrintKeyCodes(void);
|
||||
void linenoiseMaskModeEnable(void);
|
||||
void linenoiseMaskModeDisable(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+60
-452
@@ -129,75 +129,6 @@ timeout 0
|
||||
# Redis default starting with Redis 3.2.1.
|
||||
tcp-keepalive 300
|
||||
|
||||
################################# TLS/SSL #####################################
|
||||
|
||||
# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration
|
||||
# directive can be used to define TLS-listening ports. To enable TLS on the
|
||||
# default port, use:
|
||||
#
|
||||
# port 0
|
||||
# tls-port 6379
|
||||
|
||||
# Configure a X.509 certificate and private key to use for authenticating the
|
||||
# server to connected clients, masters or cluster peers. These files should be
|
||||
# PEM formatted.
|
||||
#
|
||||
# tls-cert-file redis.crt
|
||||
# tls-key-file redis.key
|
||||
|
||||
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
|
||||
#
|
||||
# tls-dh-params-file redis.dh
|
||||
|
||||
# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL
|
||||
# clients and peers. Redis requires an explicit configuration of at least one
|
||||
# of these, and will not implicitly use the system wide configuration.
|
||||
#
|
||||
# tls-ca-cert-file ca.crt
|
||||
# tls-ca-cert-dir /etc/ssl/certs
|
||||
|
||||
# By default, clients (including replica servers) on a TLS port are required
|
||||
# to authenticate using valid client side certificates.
|
||||
#
|
||||
# It is possible to disable authentication using this directive.
|
||||
#
|
||||
# tls-auth-clients no
|
||||
|
||||
# By default, a Redis replica does not attempt to establish a TLS connection
|
||||
# with its master.
|
||||
#
|
||||
# Use the following directive to enable TLS on replication links.
|
||||
#
|
||||
# tls-replication yes
|
||||
|
||||
# By default, the Redis Cluster bus uses a plain TCP connection. To enable
|
||||
# TLS for the bus protocol, use the following directive:
|
||||
#
|
||||
# tls-cluster yes
|
||||
|
||||
# Explicitly specify TLS versions to support. Allowed values are case insensitive
|
||||
# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1)
|
||||
#
|
||||
# tls-protocols TLSv1.2
|
||||
|
||||
# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information
|
||||
# about the syntax of this string.
|
||||
#
|
||||
# Note: this configuration applies only to <= TLSv1.2.
|
||||
#
|
||||
# tls-ciphers DEFAULT:!MEDIUM
|
||||
|
||||
# Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more
|
||||
# information about the syntax of this string, and specifically for TLSv1.3
|
||||
# ciphersuites.
|
||||
#
|
||||
# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256
|
||||
|
||||
# When choosing a cipher, use the server's preference instead of the client
|
||||
# preference. By default, the server follows the client's preference.
|
||||
#
|
||||
# tls-prefer-server-ciphers yes
|
||||
|
||||
################################# GENERAL #####################################
|
||||
|
||||
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
||||
@@ -321,19 +252,6 @@ 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
|
||||
@@ -373,17 +291,6 @@ dir ./
|
||||
# refuse the replica request.
|
||||
#
|
||||
# masterauth <master-password>
|
||||
#
|
||||
# However this is not enough if you are using Redis ACLs (for Redis version
|
||||
# 6 or greater), and the default user is not capable of running the PSYNC
|
||||
# command and/or other commands needed for replication. In this case it's
|
||||
# better to configure a special user to use with replication, and specify the
|
||||
# masteruser configuration as such:
|
||||
#
|
||||
# masteruser <username>
|
||||
#
|
||||
# When masteruser is specified, the replica will authenticate against its
|
||||
# master using the new AUTH form: AUTH <username> <password>.
|
||||
|
||||
# When a replica loses its connection with the master, or when the replication
|
||||
# is still in progress, the replica can act in two different ways:
|
||||
@@ -418,11 +325,13 @@ replica-read-only yes
|
||||
|
||||
# Replication SYNC strategy: disk or socket.
|
||||
#
|
||||
# New replicas and reconnecting replicas that are not able to continue the
|
||||
# replication process just receiving differences, need to do what is called a
|
||||
# "full synchronization". An RDB file is transmitted from the master to the
|
||||
# replicas.
|
||||
# -------------------------------------------------------
|
||||
# WARNING: DISKLESS REPLICATION IS EXPERIMENTAL CURRENTLY
|
||||
# -------------------------------------------------------
|
||||
#
|
||||
# New replicas and reconnecting replicas that are not able to continue the replication
|
||||
# process just receiving differences, need to do what is called a "full
|
||||
# synchronization". An RDB file is transmitted from the master to the replicas.
|
||||
# The transmission can happen in two different ways:
|
||||
#
|
||||
# 1) Disk-backed: The Redis master creates a new process that writes the RDB
|
||||
@@ -432,14 +341,14 @@ replica-read-only yes
|
||||
# RDB file to replica sockets, without touching the disk at all.
|
||||
#
|
||||
# With disk-backed replication, while the RDB file is generated, more replicas
|
||||
# can be queued and served with the RDB file as soon as the current child
|
||||
# producing the RDB file finishes its work. With diskless replication instead
|
||||
# once the transfer starts, new replicas arriving will be queued and a new
|
||||
# transfer will start when the current one terminates.
|
||||
# can be queued and served with the RDB file as soon as the current child producing
|
||||
# the RDB file finishes its work. With diskless replication instead once
|
||||
# the transfer starts, new replicas arriving will be queued and a new transfer
|
||||
# will start when the current one terminates.
|
||||
#
|
||||
# When diskless replication is used, the master waits a configurable amount of
|
||||
# time (in seconds) before starting the transfer in the hope that multiple
|
||||
# replicas will arrive and the transfer can be parallelized.
|
||||
# time (in seconds) before starting the transfer in the hope that multiple replicas
|
||||
# will arrive and the transfer can be parallelized.
|
||||
#
|
||||
# With slow disks and fast (large bandwidth) networks, diskless replication
|
||||
# works better.
|
||||
@@ -450,42 +359,16 @@ repl-diskless-sync no
|
||||
# to the replicas.
|
||||
#
|
||||
# This is important since once the transfer starts, it is not possible to serve
|
||||
# new replicas arriving, that will be queued for the next RDB transfer, so the
|
||||
# server waits a delay in order to let more replicas arrive.
|
||||
# new replicas arriving, that will be queued for the next RDB transfer, so the server
|
||||
# waits a delay in order to let more replicas arrive.
|
||||
#
|
||||
# The delay is specified in seconds, and by default is 5 seconds. To disable
|
||||
# it entirely just set it to 0 seconds and the transfer will start ASAP.
|
||||
repl-diskless-sync-delay 5
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# WARNING: RDB diskless load is experimental. Since in this setup the replica
|
||||
# does not immediately store an RDB on disk, it may cause data loss during
|
||||
# failovers. RDB diskless load + Redis modules not handling I/O reads may also
|
||||
# cause Redis to abort in case of I/O errors during the initial synchronization
|
||||
# stage with the master. Use only if your do what you are doing.
|
||||
# -----------------------------------------------------------------------------
|
||||
#
|
||||
# Replica can load the RDB it reads from the replication link directly from the
|
||||
# socket, or store the RDB to a file and read that file after it was completely
|
||||
# recived from the master.
|
||||
#
|
||||
# In many cases the disk is slower than the network, and storing and loading
|
||||
# the RDB file may increase replication time (and even increase the master's
|
||||
# Copy on Write memory and salve buffers).
|
||||
# However, parsing the RDB file directly from the socket may mean that we have
|
||||
# to flush the contents of the current database before the full rdb was
|
||||
# received. For this reason we have the following options:
|
||||
#
|
||||
# "disabled" - Don't use diskless load (store the rdb file to the disk first)
|
||||
# "on-empty-db" - Use diskless load only when it is completely safe.
|
||||
# "swapdb" - Keep a copy of the current db contents in RAM while parsing
|
||||
# the data directly from the socket. note that this requires
|
||||
# sufficient memory, if you don't have it, you risk an OOM kill.
|
||||
repl-diskless-load disabled
|
||||
|
||||
# Replicas send PINGs to server in a predefined interval. It's possible to
|
||||
# change this interval with the repl_ping_replica_period option. The default
|
||||
# value is 10 seconds.
|
||||
# Replicas send PINGs to server in a predefined interval. It's possible to change
|
||||
# this interval with the repl_ping_replica_period option. The default value is 10
|
||||
# seconds.
|
||||
#
|
||||
# repl-ping-replica-period 10
|
||||
|
||||
@@ -517,10 +400,10 @@ repl-diskless-load disabled
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# Set the replication backlog size. The backlog is a buffer that accumulates
|
||||
# replica data when replicas are disconnected for some time, so that when a
|
||||
# replica wants to reconnect again, often a full resync is not needed, but a
|
||||
# partial resync is enough, just passing the portion of data the replica
|
||||
# missed while disconnected.
|
||||
# replica data when replicas are disconnected for some time, so that when a replica
|
||||
# wants to reconnect again, often a full resync is not needed, but a partial
|
||||
# resync is enough, just passing the portion of data the replica missed while
|
||||
# disconnected.
|
||||
#
|
||||
# The bigger the replication backlog, the longer the time the replica can be
|
||||
# disconnected and later be able to perform a partial resynchronization.
|
||||
@@ -542,13 +425,13 @@ repl-disable-tcp-nodelay no
|
||||
#
|
||||
# repl-backlog-ttl 3600
|
||||
|
||||
# The replica priority is an integer number published by Redis in the INFO
|
||||
# output. It is used by Redis Sentinel in order to select a replica to promote
|
||||
# into a master if the master is no longer working correctly.
|
||||
# The replica priority is an integer number published by Redis in the INFO output.
|
||||
# It is used by Redis Sentinel in order to select a replica to promote into a
|
||||
# master if the master is no longer working correctly.
|
||||
#
|
||||
# A replica with a low priority number is considered better for promotion, so
|
||||
# for instance if there are three replicas with priority 10, 100, 25 Sentinel
|
||||
# will pick the one with priority 10, that is the lowest.
|
||||
# for instance if there are three replicas with priority 10, 100, 25 Sentinel will
|
||||
# pick the one with priority 10, that is the lowest.
|
||||
#
|
||||
# However a special priority of 0 marks the replica as not able to perform the
|
||||
# role of master, so a replica with priority of 0 will never be selected by
|
||||
@@ -608,162 +491,22 @@ replica-priority 100
|
||||
# replica-announce-ip 5.5.5.5
|
||||
# replica-announce-port 1234
|
||||
|
||||
############################### KEYS TRACKING #################################
|
||||
|
||||
# Redis implements server assisted support for client side caching of values.
|
||||
# This is implemented using an invalidation table that remembers, using
|
||||
# 16 millions of slots, what clients may have certain subsets of keys. In turn
|
||||
# this is used in order to send invalidation messages to clients. Please
|
||||
# to understand more about the feature check this page:
|
||||
#
|
||||
# https://redis.io/topics/client-side-caching
|
||||
#
|
||||
# When tracking is enabled for a client, all the read only queries are assumed
|
||||
# to be cached: this will force Redis to store information in the invalidation
|
||||
# table. When keys are modified, such information is flushed away, and
|
||||
# invalidation messages are sent to the clients. However if the workload is
|
||||
# heavily dominated by reads, Redis could use more and more memory in order
|
||||
# to track the keys fetched by many clients.
|
||||
#
|
||||
# For this reason it is possible to configure a maximum fill value for the
|
||||
# invalidation table. By default it is set to 10%, and once this limit is
|
||||
# reached, Redis will start to evict caching slots in the invalidation table
|
||||
# even if keys are not modified, just to reclaim memory: this will in turn
|
||||
# force the clients to invalidate the cached values. Basically the table
|
||||
# maximum fill rate is a trade off between the memory you want to spend server
|
||||
# side to track information about who cached what, and the ability of clients
|
||||
# to retain cached objects in memory.
|
||||
#
|
||||
# If you set the value to 0, it means there are no limits, and all the 16
|
||||
# millions of caching slots can be used at the same time. In the "stats"
|
||||
# INFO section, you can find information about the amount of caching slots
|
||||
# used at every given moment.
|
||||
#
|
||||
# tracking-table-max-fill 10
|
||||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
# Require clients to issue AUTH <PASSWORD> before processing any other
|
||||
# commands. This might be useful in environments in which you do not trust
|
||||
# others with access to the host running redis-server.
|
||||
#
|
||||
# This should stay commented out for backward compatibility and because most
|
||||
# people do not need auth (e.g. they run their own servers).
|
||||
#
|
||||
# Warning: since Redis is pretty fast an outside user can try up to
|
||||
# 1 million passwords per second against a modern box. This means that you
|
||||
# should use very strong passwords, otherwise they will be very easy to break.
|
||||
# Note that because the password is really a shared secret between the client
|
||||
# and the server, and should not be memorized by any human, the password
|
||||
# can be easily a long string from /dev/urandom or whatever, so by using a
|
||||
# long and unguessable password no brute force attack will be possible.
|
||||
|
||||
# Redis ACL users are defined in the following format:
|
||||
#
|
||||
# user <username> ... acl rules ...
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# user worker +@list +@connection ~jobs:* on >ffa9203c493aa99
|
||||
#
|
||||
# The special username "default" is used for new connections. If this user
|
||||
# has the "nopass" rule, then new connections will be immediately authenticated
|
||||
# as the "default" user without the need of any password provided via the
|
||||
# AUTH command. Otherwise if the "default" user is not flagged with "nopass"
|
||||
# the connections will start in not authenticated state, and will require
|
||||
# AUTH (or the HELLO command AUTH option) in order to be authenticated and
|
||||
# start to work.
|
||||
#
|
||||
# The ACL rules that describe what an user can do are the following:
|
||||
#
|
||||
# on Enable the user: it is possible to authenticate as this user.
|
||||
# off Disable the user: it's no longer possible to authenticate
|
||||
# with this user, however the already authenticated connections
|
||||
# will still work.
|
||||
# +<command> Allow the execution of that command
|
||||
# -<command> Disallow the execution of that command
|
||||
# +@<category> Allow the execution of all the commands in such category
|
||||
# with valid categories are like @admin, @set, @sortedset, ...
|
||||
# and so forth, see the full list in the server.c file where
|
||||
# the Redis command table is described and defined.
|
||||
# The special category @all means all the commands, but currently
|
||||
# present in the server, and that will be loaded in the future
|
||||
# via modules.
|
||||
# +<command>|subcommand Allow a specific subcommand of an otherwise
|
||||
# disabled command. Note that this form is not
|
||||
# allowed as negative like -DEBUG|SEGFAULT, but
|
||||
# only additive starting with "+".
|
||||
# allcommands Alias for +@all. Note that it implies the ability to execute
|
||||
# all the future commands loaded via the modules system.
|
||||
# nocommands Alias for -@all.
|
||||
# ~<pattern> Add a pattern of keys that can be mentioned as part of
|
||||
# commands. For instance ~* allows all the keys. The pattern
|
||||
# is a glob-style pattern like the one of KEYS.
|
||||
# It is possible to specify multiple patterns.
|
||||
# allkeys Alias for ~*
|
||||
# resetkeys Flush the list of allowed keys patterns.
|
||||
# ><password> Add this passowrd to the list of valid password for the user.
|
||||
# For example >mypass will add "mypass" to the list.
|
||||
# This directive clears the "nopass" flag (see later).
|
||||
# <<password> Remove this password from the list of valid passwords.
|
||||
# nopass All the set passwords of the user are removed, and the user
|
||||
# is flagged as requiring no password: it means that every
|
||||
# password will work against this user. If this directive is
|
||||
# used for the default user, every new connection will be
|
||||
# immediately authenticated with the default user without
|
||||
# any explicit AUTH command required. Note that the "resetpass"
|
||||
# directive will clear this condition.
|
||||
# resetpass Flush the list of allowed passwords. Moreover removes the
|
||||
# "nopass" status. After "resetpass" the user has no associated
|
||||
# passwords and there is no way to authenticate without adding
|
||||
# some password (or setting it as "nopass" later).
|
||||
# reset Performs the following actions: resetpass, resetkeys, off,
|
||||
# -@all. The user returns to the same state it has immediately
|
||||
# after its creation.
|
||||
#
|
||||
# ACL rules can be specified in any order: for instance you can start with
|
||||
# passwords, then flags, or key patterns. However note that the additive
|
||||
# and subtractive rules will CHANGE MEANING depending on the ordering.
|
||||
# For instance see the following example:
|
||||
#
|
||||
# user alice on +@all -DEBUG ~* >somepassword
|
||||
#
|
||||
# This will allow "alice" to use all the commands with the exception of the
|
||||
# DEBUG command, since +@all added all the commands to the set of the commands
|
||||
# alice can use, and later DEBUG was removed. However if we invert the order
|
||||
# of two ACL rules the result will be different:
|
||||
#
|
||||
# user alice on -DEBUG +@all ~* >somepassword
|
||||
#
|
||||
# Now DEBUG was removed when alice had yet no commands in the set of allowed
|
||||
# commands, later all the commands are added, so the user will be able to
|
||||
# execute everything.
|
||||
#
|
||||
# Basically ACL rules are processed left-to-right.
|
||||
#
|
||||
# For more information about ACL configuration please refer to
|
||||
# the Redis web site at https://redis.io/topics/acl
|
||||
|
||||
# Using an external ACL file
|
||||
#
|
||||
# Instead of configuring users here in this file, it is possible to use
|
||||
# a stand-alone file just listing users. The two methods cannot be mixed:
|
||||
# if you configure users here and at the same time you activate the exteranl
|
||||
# ACL file, the server will refuse to start.
|
||||
#
|
||||
# The format of the external ACL user file is exactly the same as the
|
||||
# format that is used inside redis.conf to describe users.
|
||||
#
|
||||
# aclfile /etc/redis/users.acl
|
||||
|
||||
# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatiblity
|
||||
# layer on top of the new ACL system. The option effect will be just setting
|
||||
# the password for the default user. Clients will still authenticate using
|
||||
# AUTH <password> as usually, or more explicitly with AUTH default <password>
|
||||
# if they follow the new protocol: both will work.
|
||||
# 150k passwords per second against a good box. This means that you should
|
||||
# use a very strong password otherwise it will be very easy to break.
|
||||
#
|
||||
# requirepass foobared
|
||||
|
||||
# Command renaming (DEPRECATED).
|
||||
#
|
||||
# ------------------------------------------------------------------------
|
||||
# WARNING: avoid using this option if possible. Instead use ACLs to remove
|
||||
# commands from the default user, and put them only in some admin user you
|
||||
# create for administrative purposes.
|
||||
# ------------------------------------------------------------------------
|
||||
# Command renaming.
|
||||
#
|
||||
# It is possible to change the name of dangerous commands in a shared
|
||||
# environment. For instance the CONFIG command may be renamed into something
|
||||
@@ -823,13 +566,13 @@ replica-priority 100
|
||||
# maxmemory <bytes>
|
||||
|
||||
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
|
||||
# is reached. You can select one from the following behaviors:
|
||||
# is reached. You can select among five behaviors:
|
||||
#
|
||||
# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
|
||||
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
|
||||
# allkeys-lru -> Evict any key using approximated LRU.
|
||||
# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
|
||||
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
|
||||
# allkeys-lfu -> Evict any key using approximated LFU.
|
||||
# volatile-random -> Remove a random key having an expire set.
|
||||
# volatile-random -> Remove a random key among the ones with an expire set.
|
||||
# allkeys-random -> Remove a random key, any key.
|
||||
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
|
||||
# noeviction -> Don't evict anything, just return an error on write operations.
|
||||
@@ -870,37 +613,20 @@ replica-priority 100
|
||||
# DEL commands to the replica as keys evict in the master side.
|
||||
#
|
||||
# This behavior ensures that masters and replicas stay consistent, and is usually
|
||||
# what you want, however if your replica is writable, or you want the replica
|
||||
# to have a different memory setting, and you are sure all the writes performed
|
||||
# to the replica are idempotent, then you may change this default (but be sure
|
||||
# to understand what you are doing).
|
||||
# what you want, however if your replica is writable, or you want the replica to have
|
||||
# a different memory setting, and you are sure all the writes performed to the
|
||||
# replica are idempotent, then you may change this default (but be sure to understand
|
||||
# what you are doing).
|
||||
#
|
||||
# Note that since the replica by default does not evict, it may end using more
|
||||
# memory than the one set via maxmemory (there are certain buffers that may
|
||||
# be larger on the replica, or data structures may sometimes take more memory
|
||||
# and so forth). So make sure you monitor your replicas and make sure they
|
||||
# have enough memory to never hit a real out-of-memory condition before the
|
||||
# master hits the configured maxmemory setting.
|
||||
# be larger on the replica, or data structures may sometimes take more memory and so
|
||||
# forth). So make sure you monitor your replicas and make sure they have enough
|
||||
# memory to never hit a real out-of-memory condition before the master hits
|
||||
# the configured maxmemory setting.
|
||||
#
|
||||
# replica-ignore-maxmemory yes
|
||||
|
||||
# Redis reclaims expired keys in two ways: upon access when those keys are
|
||||
# found to be expired, and also in background, in what is called the
|
||||
# "active expire key". The key space is slowly and interactively scanned
|
||||
# looking for expired keys to reclaim, so that it is possible to free memory
|
||||
# of keys that are expired and will never be accessed again in a short time.
|
||||
#
|
||||
# The default effort of the expire cycle will try to avoid having more than
|
||||
# ten percent of expired keys still in memory, and will try to avoid consuming
|
||||
# more than 25% of total memory and to add latency to the system. However
|
||||
# it is possible to increase the expire "effort" that is normally set to
|
||||
# "1", to a greater value, up to the value "10". At its maximum value the
|
||||
# system will use more CPU, longer cycles (and technically may introduce
|
||||
# more latency), and will tollerate less already expired keys still present
|
||||
# in the system. It's a tradeoff betweeen memory, CPU and latecy.
|
||||
#
|
||||
# active-expire-effort 1
|
||||
|
||||
############################# LAZY FREEING ####################################
|
||||
|
||||
# Redis has two primitives to delete keys. One is called DEL and is a blocking
|
||||
@@ -950,52 +676,6 @@ lazyfree-lazy-expire no
|
||||
lazyfree-lazy-server-del no
|
||||
replica-lazy-flush 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
|
||||
@@ -1252,22 +932,6 @@ 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.
|
||||
|
||||
@@ -1374,11 +1038,7 @@ 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)
|
||||
# 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).
|
||||
# A Alias for g$lshzxe, so that the "AKE" string means all the events.
|
||||
#
|
||||
# The "notify-keyspace-events" takes as argument a string that is composed
|
||||
# of zero or multiple characters. The empty string means that notifications
|
||||
@@ -1399,61 +1059,6 @@ latency-monitor-threshold 0
|
||||
# specify at least one of K or E, no events will be delivered.
|
||||
notify-keyspace-events ""
|
||||
|
||||
############################### GOPHER SERVER #################################
|
||||
|
||||
# Redis contains an implementation of the Gopher protocol, as specified in
|
||||
# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt).
|
||||
#
|
||||
# The Gopher protocol was very popular in the late '90s. It is an alternative
|
||||
# to the web, and the implementation both server and client side is so simple
|
||||
# that the Redis server has just 100 lines of code in order to implement this
|
||||
# support.
|
||||
#
|
||||
# What do you do with Gopher nowadays? Well Gopher never *really* died, and
|
||||
# lately there is a movement in order for the Gopher more hierarchical content
|
||||
# composed of just plain text documents to be resurrected. Some want a simpler
|
||||
# internet, others believe that the mainstream internet became too much
|
||||
# controlled, and it's cool to create an alternative space for people that
|
||||
# want a bit of fresh air.
|
||||
#
|
||||
# Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol
|
||||
# as a gift.
|
||||
#
|
||||
# --- HOW IT WORKS? ---
|
||||
#
|
||||
# The Redis Gopher support uses the inline protocol of Redis, and specifically
|
||||
# two kind of inline requests that were anyway illegal: an empty request
|
||||
# or any request that starts with "/" (there are no Redis commands starting
|
||||
# with such a slash). Normal RESP2/RESP3 requests are completely out of the
|
||||
# path of the Gopher protocol implementation and are served as usually as well.
|
||||
#
|
||||
# If you open a connection to Redis when Gopher is enabled and send it
|
||||
# a string like "/foo", if there is a key named "/foo" it is served via the
|
||||
# Gopher protocol.
|
||||
#
|
||||
# In order to create a real Gopher "hole" (the name of a Gopher site in Gopher
|
||||
# talking), you likely need a script like the following:
|
||||
#
|
||||
# https://github.com/antirez/gopher2redis
|
||||
#
|
||||
# --- SECURITY WARNING ---
|
||||
#
|
||||
# If you plan to put Redis on the internet in a publicly accessible address
|
||||
# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance.
|
||||
# Once a password is set:
|
||||
#
|
||||
# 1. The Gopher server (when enabled, not by default) will still serve
|
||||
# content via Gopher.
|
||||
# 2. However other commands cannot be called before the client will
|
||||
# authenticate.
|
||||
#
|
||||
# So use the 'requirepass' option to protect your instance.
|
||||
#
|
||||
# To enable Gopher support uncomment the following line and set
|
||||
# the option from no (the default) to yes.
|
||||
#
|
||||
# gopher-enabled no
|
||||
|
||||
############################### ADVANCED CONFIG ###############################
|
||||
|
||||
# Hashes are encoded using a memory efficient data structure when they have a
|
||||
@@ -1628,7 +1233,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
|
||||
# When dynamic HZ is enabled, the actual configured HZ will be used as
|
||||
# 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
|
||||
@@ -1701,6 +1306,10 @@ 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?
|
||||
# -------------------------------
|
||||
#
|
||||
@@ -1740,7 +1349,7 @@ rdb-save-incremental-fsync yes
|
||||
# a good idea to leave the defaults untouched.
|
||||
|
||||
# Enabled active defragmentation
|
||||
# activedefrag no
|
||||
# activedefrag yes
|
||||
|
||||
# Minimum amount of fragmentation waste to start active defrag
|
||||
# active-defrag-ignore-bytes 100mb
|
||||
@@ -1751,14 +1360,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, to be used when the lower
|
||||
# threshold is reached
|
||||
# active-defrag-cycle-min 1
|
||||
# Minimal effort for defrag in CPU percentage
|
||||
# active-defrag-cycle-min 5
|
||||
|
||||
# Maximal effort for defrag in CPU percentage, to be used when the upper
|
||||
# threshold is reached
|
||||
# active-defrag-cycle-max 25
|
||||
# Maximal effort for defrag in CPU percentage
|
||||
# active-defrag-cycle-max 75
|
||||
|
||||
# Maximum number of set/hash/zset/list fields that will be processed from
|
||||
# the main dictionary scan
|
||||
# active-defrag-max-scan-fields 1000
|
||||
|
||||
|
||||
+1
-13
@@ -13,16 +13,4 @@ then
|
||||
fi
|
||||
|
||||
make -C tests/modules && \
|
||||
$TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/commandfilter \
|
||||
--single unit/moduleapi/fork \
|
||||
--single unit/moduleapi/testrdb \
|
||||
--single unit/moduleapi/infotest \
|
||||
--single unit/moduleapi/propagate \
|
||||
--single unit/moduleapi/hooks \
|
||||
--single unit/moduleapi/misc \
|
||||
--single unit/moduleapi/blockonkeys \
|
||||
--single unit/moduleapi/scan \
|
||||
--single unit/moduleapi/datatype \
|
||||
--single unit/moduleapi/auth \
|
||||
"${@}"
|
||||
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/testrdb "${@}"
|
||||
|
||||
@@ -102,18 +102,6 @@ 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
|
||||
@@ -124,14 +112,6 @@ 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
|
||||
|
||||
+5
-42
@@ -20,7 +20,7 @@ DEPENDENCY_TARGETS=hiredis linenoise lua
|
||||
NODEPS:=clean distclean
|
||||
|
||||
# Default settings
|
||||
STD=-std=c11 -pedantic -DREDIS_STATIC=''
|
||||
STD=-std=c99 -pedantic -DREDIS_STATIC=''
|
||||
ifneq (,$(findstring clang,$(CC)))
|
||||
ifneq (,$(findstring FreeBSD,$(uname_S)))
|
||||
STD+=-Wno-c11-extensions
|
||||
@@ -32,7 +32,6 @@ 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
|
||||
@@ -103,8 +102,6 @@ else
|
||||
ifeq ($(uname_S),Darwin)
|
||||
# Darwin
|
||||
FINAL_LIBS+= -ldl
|
||||
OPENSSL_CFLAGS=-I/usr/local/opt/openssl/include
|
||||
OPENSSL_LDFLAGS=-L/usr/local/opt/openssl/lib
|
||||
else
|
||||
ifeq ($(uname_S),AIX)
|
||||
# AIX
|
||||
@@ -141,30 +138,6 @@ 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
|
||||
@@ -181,12 +154,6 @@ ifeq ($(MALLOC),jemalloc)
|
||||
FINAL_LIBS := ../deps/jemalloc/lib/libjemalloc.a $(FINAL_LIBS)
|
||||
endif
|
||||
|
||||
ifeq ($(BUILD_TLS),yes)
|
||||
FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CFLAGS)
|
||||
FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
|
||||
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a -lssl -lcrypto
|
||||
endif
|
||||
|
||||
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
|
||||
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
|
||||
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
|
||||
@@ -206,11 +173,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
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
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_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o zmalloc.o redis-benchmark.o
|
||||
REDIS_CHECK_RDB_NAME=redis-check-rdb
|
||||
REDIS_CHECK_AOF_NAME=redis-check-aof
|
||||
|
||||
@@ -283,18 +250,14 @@ $(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) -MMD -o $@ -c $<
|
||||
$(REDIS_CC) -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
|
||||
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ typedef struct list {
|
||||
#define listSetMatchMethod(l,m) ((l)->match = (m))
|
||||
|
||||
#define listGetDupMethod(l) ((l)->dup)
|
||||
#define listGetFreeMethod(l) ((l)->free)
|
||||
#define listGetFree(l) ((l)->free)
|
||||
#define listGetMatchMethod(l) ((l)->match)
|
||||
|
||||
/* Prototypes */
|
||||
|
||||
@@ -76,7 +76,6 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
|
||||
eventLoop->maxfd = -1;
|
||||
eventLoop->beforesleep = NULL;
|
||||
eventLoop->aftersleep = NULL;
|
||||
eventLoop->flags = 0;
|
||||
if (aeApiCreate(eventLoop) == -1) goto err;
|
||||
/* Events with mask == AE_NONE are not set. So let's initialize the
|
||||
* vector with it. */
|
||||
@@ -98,14 +97,6 @@ int aeGetSetSize(aeEventLoop *eventLoop) {
|
||||
return eventLoop->setsize;
|
||||
}
|
||||
|
||||
/* Tells the next iteration/s of the event processing to set timeout of 0. */
|
||||
void aeSetDontWait(aeEventLoop *eventLoop, int noWait) {
|
||||
if (noWait)
|
||||
eventLoop->flags |= AE_DONT_WAIT;
|
||||
else
|
||||
eventLoop->flags &= ~AE_DONT_WAIT;
|
||||
}
|
||||
|
||||
/* Resize the maximum set size of the event loop.
|
||||
* If the requested set size is smaller than the current set size, but
|
||||
* there is already a file descriptor in use that is >= the requested
|
||||
@@ -135,14 +126,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -368,8 +351,8 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
* if flags has AE_FILE_EVENTS set, file events are processed.
|
||||
* if flags has AE_TIME_EVENTS set, time events are processed.
|
||||
* if flags has AE_DONT_WAIT set the function returns ASAP until all
|
||||
* the events that's possible to process without to wait are processed.
|
||||
* if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
|
||||
* the events that's possible to process without to wait are processed.
|
||||
*
|
||||
* The function returns the number of events processed. */
|
||||
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
@@ -423,11 +406,6 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
}
|
||||
}
|
||||
|
||||
if (eventLoop->flags & AE_DONT_WAIT) {
|
||||
tv.tv_sec = tv.tv_usec = 0;
|
||||
tvp = &tv;
|
||||
}
|
||||
|
||||
/* Call the multiplexing API, will return only on timeout or when
|
||||
* some event fires. */
|
||||
numevents = aeApiPoll(eventLoop, tvp);
|
||||
@@ -464,7 +442,6 @@ 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. */
|
||||
@@ -477,11 +454,8 @@ 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 = &eventLoop->events[fd]; /* Refresh in case of resize. */
|
||||
if ((fe->mask & mask & AE_READABLE) &&
|
||||
(!fired || fe->wfileProc != fe->rfileProc))
|
||||
{
|
||||
if (invert && fe->mask & mask & AE_READABLE) {
|
||||
if (!fired || fe->wfileProc != fe->rfileProc) {
|
||||
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
|
||||
fired++;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@ typedef struct aeEventLoop {
|
||||
void *apidata; /* This is used for polling API specific data */
|
||||
aeBeforeSleepProc *beforesleep;
|
||||
aeBeforeSleepProc *aftersleep;
|
||||
int flags;
|
||||
} aeEventLoop;
|
||||
|
||||
/* Prototypes */
|
||||
@@ -129,6 +128,5 @@ void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep
|
||||
void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep);
|
||||
int aeGetSetSize(aeEventLoop *eventLoop);
|
||||
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
|
||||
void aeSetDontWait(aeEventLoop *eventLoop, int noWait);
|
||||
|
||||
#endif
|
||||
|
||||
+2
-2
@@ -121,8 +121,8 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
||||
|
||||
if (e->events & EPOLLIN) mask |= AE_READABLE;
|
||||
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
|
||||
if (e->events & EPOLLERR) mask |= AE_WRITABLE|AE_READABLE;
|
||||
if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE;
|
||||
if (e->events & EPOLLERR) mask |= AE_WRITABLE;
|
||||
if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
|
||||
eventLoop->fired[j].fd = e->data.fd;
|
||||
eventLoop->fired[j].mask = mask;
|
||||
}
|
||||
|
||||
+11
-25
@@ -193,20 +193,6 @@ int anetSendTimeout(char *err, int fd, long long ms) {
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
/* Set the socket receive timeout (SO_RCVTIMEO socket option) to the specified
|
||||
* number of milliseconds, or disable it if the 'ms' argument is zero. */
|
||||
int anetRecvTimeout(char *err, int fd, long long ms) {
|
||||
struct timeval tv;
|
||||
|
||||
tv.tv_sec = ms/1000;
|
||||
tv.tv_usec = (ms%1000)*1000;
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
|
||||
anetSetError(err, "setsockopt SO_RCVTIMEO: %s", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to
|
||||
* do the actual work. It resolves the hostname "host" and set the string
|
||||
* representation of the IP address into the buffer pointed by "ipbuf".
|
||||
@@ -279,8 +265,8 @@ static int anetCreateSocket(char *err, int domain) {
|
||||
#define ANET_CONNECT_NONE 0
|
||||
#define ANET_CONNECT_NONBLOCK 1
|
||||
#define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */
|
||||
static int anetTcpGenericConnect(char *err, const char *addr, int port,
|
||||
const char *source_addr, int flags)
|
||||
static int anetTcpGenericConnect(char *err, char *addr, int port,
|
||||
char *source_addr, int flags)
|
||||
{
|
||||
int s = ANET_ERR, rv;
|
||||
char portstr[6]; /* strlen("65535") + 1; */
|
||||
@@ -359,31 +345,31 @@ end:
|
||||
}
|
||||
}
|
||||
|
||||
int anetTcpConnect(char *err, const char *addr, int port)
|
||||
int anetTcpConnect(char *err, char *addr, int port)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockConnect(char *err, const char *addr, int port)
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port,
|
||||
const char *source_addr)
|
||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port,
|
||||
char *source_addr)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,
|
||||
ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
|
||||
const char *source_addr)
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port,
|
||||
char *source_addr)
|
||||
{
|
||||
return anetTcpGenericConnect(err,addr,port,source_addr,
|
||||
ANET_CONNECT_NONBLOCK|ANET_CONNECT_BE_BINDING);
|
||||
}
|
||||
|
||||
int anetUnixGenericConnect(char *err, const char *path, int flags)
|
||||
int anetUnixGenericConnect(char *err, char *path, int flags)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_un sa;
|
||||
@@ -411,12 +397,12 @@ int anetUnixGenericConnect(char *err, const char *path, int flags)
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetUnixConnect(char *err, const char *path)
|
||||
int anetUnixConnect(char *err, char *path)
|
||||
{
|
||||
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE);
|
||||
}
|
||||
|
||||
int anetUnixNonBlockConnect(char *err, const char *path)
|
||||
int anetUnixNonBlockConnect(char *err, char *path)
|
||||
{
|
||||
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
|
||||
}
|
||||
|
||||
+6
-7
@@ -49,12 +49,12 @@
|
||||
#undef ip_len
|
||||
#endif
|
||||
|
||||
int anetTcpConnect(char *err, const char *addr, int port);
|
||||
int anetTcpNonBlockConnect(char *err, const char *addr, int port);
|
||||
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr);
|
||||
int anetUnixConnect(char *err, const char *path);
|
||||
int anetUnixNonBlockConnect(char *err, const char *path);
|
||||
int anetTcpConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr);
|
||||
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port, char *source_addr);
|
||||
int anetUnixConnect(char *err, char *path);
|
||||
int anetUnixNonBlockConnect(char *err, char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
@@ -70,7 +70,6 @@ int anetEnableTcpNoDelay(char *err, int fd);
|
||||
int anetDisableTcpNoDelay(char *err, int fd);
|
||||
int anetTcpKeepAlive(char *err, int fd);
|
||||
int anetSendTimeout(char *err, int fd, long long ms);
|
||||
int anetRecvTimeout(char *err, int fd, long long ms);
|
||||
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port);
|
||||
int anetKeepAlive(char *err, int fd, int interval);
|
||||
int anetSockName(int fd, char *ip, size_t ip_len, int *port);
|
||||
|
||||
@@ -210,7 +210,7 @@ void aof_background_fsync(int fd) {
|
||||
}
|
||||
|
||||
/* Kills an AOFRW child process if exists */
|
||||
void killAppendOnlyChild(void) {
|
||||
static void killAppendOnlyChild(void) {
|
||||
int statloc;
|
||||
/* No AOFRW child? return. */
|
||||
if (server.aof_child_pid == -1) return;
|
||||
@@ -227,8 +227,6 @@ void killAppendOnlyChild(void) {
|
||||
server.aof_rewrite_time_start = -1;
|
||||
/* Close pipes used for IPC between the two processes. */
|
||||
aofClosePipes();
|
||||
closeChildInfoPipe();
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
|
||||
/* Called when the user switches from "appendonly yes" to "appendonly no"
|
||||
@@ -242,7 +240,6 @@ void stopAppendOnly(void) {
|
||||
server.aof_fd = -1;
|
||||
server.aof_selected_db = -1;
|
||||
server.aof_state = AOF_OFF;
|
||||
server.aof_rewrite_scheduled = 0;
|
||||
killAppendOnlyChild();
|
||||
}
|
||||
|
||||
@@ -265,9 +262,9 @@ int startAppendOnly(void) {
|
||||
strerror(errno));
|
||||
return C_ERR;
|
||||
}
|
||||
if (hasActiveChildProcess() && server.aof_child_pid == -1) {
|
||||
if (server.rdb_child_pid != -1) {
|
||||
server.aof_rewrite_scheduled = 1;
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible.");
|
||||
} else {
|
||||
/* If there is a pending AOF rewrite, we need to switch it off and
|
||||
* start a new one: the old one cannot be reused because it is not
|
||||
@@ -304,7 +301,9 @@ ssize_t aofWrite(int fd, const char *buf, size_t len) {
|
||||
nwritten = write(fd, buf, len);
|
||||
|
||||
if (nwritten < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
return totwritten ? totwritten : -1;
|
||||
}
|
||||
|
||||
@@ -386,10 +385,6 @@ void flushAppendOnlyFile(int force) {
|
||||
* there is much to do about the whole server stopping for power problems
|
||||
* or alike */
|
||||
|
||||
if (server.aof_flush_sleep && sdslen(server.aof_buf)) {
|
||||
usleep(server.aof_flush_sleep);
|
||||
}
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
|
||||
latencyEndMonitor(latency);
|
||||
@@ -400,7 +395,7 @@ void flushAppendOnlyFile(int force) {
|
||||
* useful for graphing / monitoring purposes. */
|
||||
if (sync_in_progress) {
|
||||
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
|
||||
} else if (hasActiveChildProcess()) {
|
||||
} else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) {
|
||||
latencyAddSampleIfNeeded("aof-write-active-child",latency);
|
||||
} else {
|
||||
latencyAddSampleIfNeeded("aof-write-alone",latency);
|
||||
@@ -496,8 +491,9 @@ void flushAppendOnlyFile(int force) {
|
||||
try_fsync:
|
||||
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
|
||||
* children doing I/O in the background. */
|
||||
if (server.aof_no_fsync_on_rewrite && hasActiveChildProcess())
|
||||
return;
|
||||
if (server.aof_no_fsync_on_rewrite &&
|
||||
(server.aof_child_pid != -1 || server.rdb_child_pid != -1))
|
||||
return;
|
||||
|
||||
/* Perform the fsync if needed. */
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
@@ -653,12 +649,11 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
||||
|
||||
/* In Redis commands are always executed in the context of a client, so in
|
||||
* order to load the append only file we need to create a fake client. */
|
||||
struct client *createAOFClient(void) {
|
||||
struct client *createFakeClient(void) {
|
||||
struct client *c = zmalloc(sizeof(*c));
|
||||
|
||||
selectDb(c,0);
|
||||
c->id = CLIENT_ID_AOF; /* So modules can identify it's the AOF client. */
|
||||
c->conn = NULL;
|
||||
c->fd = -1;
|
||||
c->name = NULL;
|
||||
c->querybuf = sdsempty();
|
||||
c->querybuf_peak = 0;
|
||||
@@ -675,8 +670,6 @@ struct client *createAOFClient(void) {
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
c->watched_keys = listCreate();
|
||||
c->peerid = NULL;
|
||||
c->resp = 2;
|
||||
c->user = NULL;
|
||||
listSetFreeMethod(c->reply,freeClientReplyValue);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
initClientMultiState(c);
|
||||
@@ -731,8 +724,8 @@ int loadAppendOnlyFile(char *filename) {
|
||||
* to the same file we're about to read. */
|
||||
server.aof_state = AOF_OFF;
|
||||
|
||||
fakeClient = createAOFClient();
|
||||
startLoadingFile(fp, filename, RDBFLAGS_AOF_PREAMBLE);
|
||||
fakeClient = createFakeClient();
|
||||
startLoading(fp);
|
||||
|
||||
/* Check if this AOF file has an RDB preamble. In that case we need to
|
||||
* load the RDB file and later continue loading the AOF tail. */
|
||||
@@ -747,7 +740,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
|
||||
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
|
||||
rioInitWithFile(&rdb,fp);
|
||||
if (rdbLoadRio(&rdb,RDBFLAGS_AOF_PREAMBLE,NULL) != C_OK) {
|
||||
if (rdbLoadRio(&rdb,NULL,1) != C_OK) {
|
||||
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
|
||||
goto readerr;
|
||||
} else {
|
||||
@@ -768,7 +761,6 @@ int loadAppendOnlyFile(char *filename) {
|
||||
if (!(loops++ % 1000)) {
|
||||
loadingProgress(ftello(fp));
|
||||
processEventsWhileBlocked();
|
||||
processModuleLoadingProgressEvent(1);
|
||||
}
|
||||
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
@@ -852,8 +844,6 @@ int loadAppendOnlyFile(char *filename) {
|
||||
freeFakeClientArgv(fakeClient);
|
||||
fakeClient->cmd = NULL;
|
||||
if (server.aof_load_truncated) valid_up_to = ftello(fp);
|
||||
if (server.key_load_delay)
|
||||
usleep(server.key_load_delay);
|
||||
}
|
||||
|
||||
/* This point can only be reached when EOF is reached without errors.
|
||||
@@ -871,7 +861,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
||||
fclose(fp);
|
||||
freeFakeClient(fakeClient);
|
||||
server.aof_state = old_aof_state;
|
||||
stopLoading(1);
|
||||
stopLoading();
|
||||
aofUpdateCurrentSize();
|
||||
server.aof_rewrite_base_size = server.aof_current_size;
|
||||
server.aof_fsync_offset = server.aof_current_size;
|
||||
@@ -880,7 +870,6 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
||||
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
|
||||
if (!feof(fp)) {
|
||||
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
|
||||
fclose(fp);
|
||||
serverLog(LL_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
@@ -911,13 +900,11 @@ uxeof: /* Unexpected AOF end of file. */
|
||||
}
|
||||
}
|
||||
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
|
||||
fclose(fp);
|
||||
serverLog(LL_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.");
|
||||
exit(1);
|
||||
|
||||
fmterr: /* Format error. */
|
||||
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
|
||||
fclose(fp);
|
||||
serverLog(LL_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>");
|
||||
exit(1);
|
||||
}
|
||||
@@ -1212,13 +1199,12 @@ 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,&id) == 0) return 0;
|
||||
if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"x",1) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"y",1) == 0) return 0;
|
||||
}
|
||||
@@ -1413,11 +1399,9 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
if (server.aof_rewrite_incremental_fsync)
|
||||
rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
|
||||
|
||||
startSaving(RDBFLAGS_AOF_PREAMBLE);
|
||||
|
||||
if (server.aof_use_rdb_preamble) {
|
||||
int error;
|
||||
if (rdbSaveRio(&aof,&error,RDBFLAGS_AOF_PREAMBLE,NULL) == C_ERR) {
|
||||
if (rdbSaveRio(&aof,&error,RDB_SAVE_AOF_PREAMBLE,NULL) == C_ERR) {
|
||||
errno = error;
|
||||
goto werr;
|
||||
}
|
||||
@@ -1480,18 +1464,15 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
if (rename(tmpfile,filename) == -1) {
|
||||
serverLog(LL_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));
|
||||
unlink(tmpfile);
|
||||
stopSaving(0);
|
||||
return C_ERR;
|
||||
}
|
||||
serverLog(LL_NOTICE,"SYNC append only file rewrite performed");
|
||||
stopSaving(1);
|
||||
return C_OK;
|
||||
|
||||
werr:
|
||||
serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
||||
fclose(fp);
|
||||
unlink(tmpfile);
|
||||
stopSaving(0);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
@@ -1587,24 +1568,39 @@ void aofClosePipes(void) {
|
||||
*/
|
||||
int rewriteAppendOnlyFileBackground(void) {
|
||||
pid_t childpid;
|
||||
long long start;
|
||||
|
||||
if (hasActiveChildProcess()) return C_ERR;
|
||||
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
|
||||
if (aofCreatePipes() != C_OK) return C_ERR;
|
||||
openChildInfoPipe();
|
||||
if ((childpid = redisFork()) == 0) {
|
||||
start = ustime();
|
||||
if ((childpid = fork()) == 0) {
|
||||
char tmpfile[256];
|
||||
|
||||
/* Child */
|
||||
closeListeningSockets(0);
|
||||
redisSetProcTitle("redis-aof-rewrite");
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
||||
sendChildCOWInfo(CHILD_INFO_TYPE_AOF, "AOF rewrite");
|
||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
||||
|
||||
if (private_dirty) {
|
||||
serverLog(LL_NOTICE,
|
||||
"AOF rewrite: %zu MB of memory used by copy-on-write",
|
||||
private_dirty/(1024*1024));
|
||||
}
|
||||
|
||||
server.child_info_data.cow_size = private_dirty;
|
||||
sendChildInfo(CHILD_INFO_TYPE_AOF);
|
||||
exitFromChild(0);
|
||||
} else {
|
||||
exitFromChild(1);
|
||||
}
|
||||
} else {
|
||||
/* Parent */
|
||||
server.stat_fork_time = ustime()-start;
|
||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
||||
if (childpid == -1) {
|
||||
closeChildInfoPipe();
|
||||
serverLog(LL_WARNING,
|
||||
@@ -1618,6 +1614,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
server.aof_rewrite_scheduled = 0;
|
||||
server.aof_rewrite_time_start = time(NULL);
|
||||
server.aof_child_pid = childpid;
|
||||
updateDictResizePolicy();
|
||||
/* We set appendseldb to -1 in order to force the next call to the
|
||||
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
||||
* accumulated by the parent into server.aof_rewrite_buf will start
|
||||
@@ -1632,14 +1629,13 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
void bgrewriteaofCommand(client *c) {
|
||||
if (server.aof_child_pid != -1) {
|
||||
addReplyError(c,"Background append only file rewriting already in progress");
|
||||
} else if (hasActiveChildProcess()) {
|
||||
} else if (server.rdb_child_pid != -1) {
|
||||
server.aof_rewrite_scheduled = 1;
|
||||
addReplyStatus(c,"Background append only file rewriting scheduled");
|
||||
} else if (rewriteAppendOnlyFileBackground() == C_OK) {
|
||||
addReplyStatus(c,"Background append only file rewriting started");
|
||||
} else {
|
||||
addReplyError(c,"Can't execute an AOF background rewriting. "
|
||||
"Please check the server logs for more information.");
|
||||
addReply(c,shared.err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1648,9 +1644,6 @@ void aofRemoveTempFile(pid_t childpid) {
|
||||
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) childpid);
|
||||
unlink(tmpfile);
|
||||
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) childpid);
|
||||
unlink(tmpfile);
|
||||
}
|
||||
|
||||
/* Update the server.aof_current_size field explicitly using stat(2)
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
const char *ascii_logo =
|
||||
char *ascii_logo =
|
||||
" _._ \n"
|
||||
" _.-``__ ''-._ \n"
|
||||
" _.-`` `. `_. ''-._ Redis %s (%s/%d) %s bit\n"
|
||||
|
||||
@@ -27,9 +27,6 @@
|
||||
* 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);
|
||||
@@ -43,5 +40,3 @@ 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
|
||||
|
||||
+4
-24
@@ -902,9 +902,6 @@ 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 */
|
||||
@@ -914,10 +911,7 @@ struct bitfieldOp {
|
||||
int sign; /* True if signed, otherwise unsigned op. */
|
||||
};
|
||||
|
||||
/* 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) {
|
||||
void bitfieldCommand(client *c) {
|
||||
robj *o;
|
||||
size_t bitoffset;
|
||||
int j, numops = 0, changes = 0;
|
||||
@@ -1005,12 +999,6 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
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,
|
||||
@@ -1020,7 +1008,7 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
}
|
||||
}
|
||||
|
||||
addReplyArrayLen(c,numops);
|
||||
addReplyMultiBulkLen(c,numops);
|
||||
|
||||
/* Actually process the operations. */
|
||||
for (j = 0; j < numops; j++) {
|
||||
@@ -1065,7 +1053,7 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
setSignedBitfield(o->ptr,thisop->offset,
|
||||
thisop->bits,newval);
|
||||
} else {
|
||||
addReplyNull(c);
|
||||
addReply(c,shared.nullbulk);
|
||||
}
|
||||
} else {
|
||||
uint64_t oldval, newval, wrapped, retval;
|
||||
@@ -1094,7 +1082,7 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
setUnsignedBitfield(o->ptr,thisop->offset,
|
||||
thisop->bits,newval);
|
||||
} else {
|
||||
addReplyNull(c);
|
||||
addReply(c,shared.nullbulk);
|
||||
}
|
||||
}
|
||||
changes++;
|
||||
@@ -1141,11 +1129,3 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
}
|
||||
zfree(ops);
|
||||
}
|
||||
|
||||
void bitfieldCommand(client *c) {
|
||||
bitfieldGeneric(c, BITFIELD_FLAG_NONE);
|
||||
}
|
||||
|
||||
void bitfieldroCommand(client *c) {
|
||||
bitfieldGeneric(c, BITFIELD_FLAG_READONLY);
|
||||
}
|
||||
|
||||
+197
-270
@@ -77,18 +77,10 @@ int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb
|
||||
* 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 (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");
|
||||
@@ -96,6 +88,7 @@ int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int
|
||||
}
|
||||
|
||||
if (tval > 0) {
|
||||
if (unit == UNIT_SECONDS) tval *= 1000;
|
||||
tval += mstime();
|
||||
}
|
||||
*timeout = tval;
|
||||
@@ -111,7 +104,6 @@ void blockClient(client *c, int btype) {
|
||||
c->btype = btype;
|
||||
server.blocked_clients++;
|
||||
server.blocked_clients_by_type[btype]++;
|
||||
addClientToShortTimeoutTable(c);
|
||||
}
|
||||
|
||||
/* This function is called in the beforeSleep() function of the event loop
|
||||
@@ -175,7 +167,6 @@ void unblockClient(client *c) {
|
||||
} else if (c->btype == BLOCKED_WAIT) {
|
||||
unblockClientWaitingReplicas(c);
|
||||
} else if (c->btype == BLOCKED_MODULE) {
|
||||
if (moduleClientIsBlockedOnKeys(c)) unblockClientWaitingData(c);
|
||||
unblockClientFromModule(c);
|
||||
} else {
|
||||
serverPanic("Unknown btype in unblockClient().");
|
||||
@@ -196,7 +187,7 @@ void replyToBlockedClientTimedOut(client *c) {
|
||||
if (c->btype == BLOCKED_LIST ||
|
||||
c->btype == BLOCKED_ZSET ||
|
||||
c->btype == BLOCKED_STREAM) {
|
||||
addReplyNullArray(c);
|
||||
addReply(c,shared.nullmultibulk);
|
||||
} else if (c->btype == BLOCKED_WAIT) {
|
||||
addReplyLongLong(c,replicationCountAcksByOffset(c->bpop.reploffset));
|
||||
} else if (c->btype == BLOCKED_MODULE) {
|
||||
@@ -231,250 +222,6 @@ void disconnectAllBlockedClients(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function for handleClientsBlockedOnKeys(). This function is called
|
||||
* when there may be clients blocked on a list key, and there may be new
|
||||
* data to fetch (the key is ready). */
|
||||
void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
|
||||
/* We serve clients in the same order they blocked for
|
||||
* this key, from the first blocked to the last. */
|
||||
dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
if (de) {
|
||||
list *clients = dictGetVal(de);
|
||||
int numclients = listLength(clients);
|
||||
|
||||
while(numclients--) {
|
||||
listNode *clientnode = listFirst(clients);
|
||||
client *receiver = clientnode->value;
|
||||
|
||||
if (receiver->btype != BLOCKED_LIST) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
continue;
|
||||
}
|
||||
|
||||
robj *dstkey = receiver->bpop.target;
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == blpopCommand) ?
|
||||
LIST_HEAD : LIST_TAIL;
|
||||
robj *value = listTypePop(o,where);
|
||||
|
||||
if (value) {
|
||||
/* Protect receiver->bpop.target, that will be
|
||||
* freed by the next unblockClient()
|
||||
* call. */
|
||||
if (dstkey) incrRefCount(dstkey);
|
||||
unblockClient(receiver);
|
||||
|
||||
if (serveClientBlockedOnList(receiver,
|
||||
rl->key,dstkey,rl->db,value,
|
||||
where) == C_ERR)
|
||||
{
|
||||
/* If we failed serving the client we need
|
||||
* to also undo the POP operation. */
|
||||
listTypePush(o,value,where);
|
||||
}
|
||||
|
||||
if (dstkey) decrRefCount(dstkey);
|
||||
decrRefCount(value);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (listTypeLength(o) == 0) {
|
||||
dbDelete(rl->db,rl->key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",rl->key,rl->db->id);
|
||||
}
|
||||
/* We don't call signalModifiedKey() as it was already called
|
||||
* when an element was pushed on the list. */
|
||||
}
|
||||
|
||||
/* Helper function for handleClientsBlockedOnKeys(). This function is called
|
||||
* when there may be clients blocked on a sorted set key, and there may be new
|
||||
* data to fetch (the key is ready). */
|
||||
void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
|
||||
/* We serve clients in the same order they blocked for
|
||||
* this key, from the first blocked to the last. */
|
||||
dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
if (de) {
|
||||
list *clients = dictGetVal(de);
|
||||
int numclients = listLength(clients);
|
||||
unsigned long zcard = zsetLength(o);
|
||||
|
||||
while(numclients-- && zcard) {
|
||||
listNode *clientnode = listFirst(clients);
|
||||
client *receiver = clientnode->value;
|
||||
|
||||
if (receiver->btype != BLOCKED_ZSET) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
continue;
|
||||
}
|
||||
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == bzpopminCommand)
|
||||
? ZSET_MIN : ZSET_MAX;
|
||||
unblockClient(receiver);
|
||||
genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
|
||||
zcard--;
|
||||
|
||||
/* Replicate the command. */
|
||||
robj *argv[2];
|
||||
struct redisCommand *cmd = where == ZSET_MIN ?
|
||||
server.zpopminCommand :
|
||||
server.zpopmaxCommand;
|
||||
argv[0] = createStringObject(cmd->name,strlen(cmd->name));
|
||||
argv[1] = rl->key;
|
||||
incrRefCount(rl->key);
|
||||
propagate(cmd,receiver->db->id,
|
||||
argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function for handleClientsBlockedOnKeys(). This function is called
|
||||
* when there may be clients blocked on a stream key, and there may be new
|
||||
* data to fetch (the key is ready). */
|
||||
void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
||||
dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
stream *s = o->ptr;
|
||||
|
||||
/* We need to provide the new data arrived on the stream
|
||||
* to all the clients that are waiting for an offset smaller
|
||||
* than the current top item. */
|
||||
if (de) {
|
||||
list *clients = dictGetVal(de);
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
listRewind(clients,&li);
|
||||
|
||||
while((ln = listNext(&li))) {
|
||||
client *receiver = listNodeValue(ln);
|
||||
if (receiver->btype != BLOCKED_STREAM) continue;
|
||||
streamID *gt = dictFetchValue(receiver->bpop.keys,
|
||||
rl->key);
|
||||
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and update the
|
||||
* last ID the client is blocked for: this is needed
|
||||
* because serving other clients in the same consumer
|
||||
* group will alter the "last ID" of the consumer
|
||||
* group, and clients blocked in a consumer group are
|
||||
* always blocked for the ">" ID: we need to deliver
|
||||
* only new messages and avoid unblocking the client
|
||||
* otherwise. */
|
||||
streamCG *group = NULL;
|
||||
if (receiver->bpop.xread_group) {
|
||||
group = streamLookupCG(s,
|
||||
receiver->bpop.xread_group->ptr);
|
||||
/* If the group was not found, send an error
|
||||
* to the consumer. */
|
||||
if (!group) {
|
||||
addReplyError(receiver,
|
||||
"-NOGROUP the consumer group this client "
|
||||
"was blocked on no longer exists");
|
||||
unblockClient(receiver);
|
||||
continue;
|
||||
} else {
|
||||
*gt = group->last_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (streamCompareID(&s->last_id, gt) > 0) {
|
||||
streamID start = *gt;
|
||||
streamIncrID(&start);
|
||||
|
||||
/* Lookup the consumer for the group, if any. */
|
||||
streamConsumer *consumer = NULL;
|
||||
int noack = 0;
|
||||
|
||||
if (group) {
|
||||
consumer = streamLookupConsumer(group,
|
||||
receiver->bpop.xread_consumer->ptr,
|
||||
1);
|
||||
noack = receiver->bpop.xread_group_noack;
|
||||
}
|
||||
|
||||
/* Emit the two elements sub-array consisting of
|
||||
* the name of the stream and the data we
|
||||
* extracted from it. Wrapped in a single-item
|
||||
* array, since we have just one key. */
|
||||
if (receiver->resp == 2) {
|
||||
addReplyArrayLen(receiver,1);
|
||||
addReplyArrayLen(receiver,2);
|
||||
} else {
|
||||
addReplyMapLen(receiver,1);
|
||||
}
|
||||
addReplyBulk(receiver,rl->key);
|
||||
|
||||
streamPropInfo pi = {
|
||||
rl->key,
|
||||
receiver->bpop.xread_group
|
||||
};
|
||||
streamReplyWithRange(receiver,s,&start,NULL,
|
||||
receiver->bpop.xread_count,
|
||||
0, group, consumer, noack, &pi);
|
||||
|
||||
/* Note that after we unblock the client, 'gt'
|
||||
* and other receiver->bpop stuff are no longer
|
||||
* valid, so we must do the setup above before
|
||||
* this call. */
|
||||
unblockClient(receiver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function for handleClientsBlockedOnKeys(). This function is called
|
||||
* in order to check if we can serve clients blocked by modules using
|
||||
* RM_BlockClientOnKeys(), when the corresponding key was signaled as ready:
|
||||
* our goal here is to call the RedisModuleBlockedClient reply() callback to
|
||||
* see if the key is really able to serve the client, and in that case,
|
||||
* unblock it. */
|
||||
void serveClientsBlockedOnKeyByModule(readyList *rl) {
|
||||
dictEntry *de;
|
||||
|
||||
/* We serve clients in the same order they blocked for
|
||||
* this key, from the first blocked to the last. */
|
||||
de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
if (de) {
|
||||
list *clients = dictGetVal(de);
|
||||
int numclients = listLength(clients);
|
||||
|
||||
while(numclients--) {
|
||||
listNode *clientnode = listFirst(clients);
|
||||
client *receiver = clientnode->value;
|
||||
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again: clients here may not be
|
||||
* ready to be served, so they'll remain in the list
|
||||
* sometimes. We want also be able to skip clients that are
|
||||
* not blocked for the MODULE type safely. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
|
||||
if (receiver->btype != BLOCKED_MODULE) continue;
|
||||
|
||||
/* Note that if *this* client cannot be served by this key,
|
||||
* it does not mean that another client that is next into the
|
||||
* list cannot be served as well: they may be blocked by
|
||||
* different modules with different triggers to consider if a key
|
||||
* is ready or not. This means we can't exit the loop but need
|
||||
* to continue after the first failure. */
|
||||
if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue;
|
||||
|
||||
moduleUnblockClient(receiver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This function should be called by Redis every time a single command,
|
||||
* a MULTI/EXEC block, or a Lua script, terminated its execution after
|
||||
* being called by a client. It handles serving clients blocked in
|
||||
@@ -527,18 +274,198 @@ void handleClientsBlockedOnKeys(void) {
|
||||
|
||||
/* Serve clients blocked on list key. */
|
||||
robj *o = lookupKeyWrite(rl->db,rl->key);
|
||||
if (o != NULL && o->type == OBJ_LIST) {
|
||||
dictEntry *de;
|
||||
|
||||
if (o != NULL) {
|
||||
if (o->type == OBJ_LIST)
|
||||
serveClientsBlockedOnListKey(o,rl);
|
||||
else if (o->type == OBJ_ZSET)
|
||||
serveClientsBlockedOnSortedSetKey(o,rl);
|
||||
else if (o->type == OBJ_STREAM)
|
||||
serveClientsBlockedOnStreamKey(o,rl);
|
||||
/* We want to serve clients blocked on module keys
|
||||
* regardless of the object type: we don't know what the
|
||||
* module is trying to accomplish right now. */
|
||||
serveClientsBlockedOnKeyByModule(rl);
|
||||
/* We serve clients in the same order they blocked for
|
||||
* this key, from the first blocked to the last. */
|
||||
de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
if (de) {
|
||||
list *clients = dictGetVal(de);
|
||||
int numclients = listLength(clients);
|
||||
|
||||
while(numclients--) {
|
||||
listNode *clientnode = listFirst(clients);
|
||||
client *receiver = clientnode->value;
|
||||
|
||||
if (receiver->btype != BLOCKED_LIST) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
continue;
|
||||
}
|
||||
|
||||
robj *dstkey = receiver->bpop.target;
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == blpopCommand) ?
|
||||
LIST_HEAD : LIST_TAIL;
|
||||
robj *value = listTypePop(o,where);
|
||||
|
||||
if (value) {
|
||||
/* Protect receiver->bpop.target, that will be
|
||||
* freed by the next unblockClient()
|
||||
* call. */
|
||||
if (dstkey) incrRefCount(dstkey);
|
||||
unblockClient(receiver);
|
||||
|
||||
if (serveClientBlockedOnList(receiver,
|
||||
rl->key,dstkey,rl->db,value,
|
||||
where) == C_ERR)
|
||||
{
|
||||
/* If we failed serving the client we need
|
||||
* to also undo the POP operation. */
|
||||
listTypePush(o,value,where);
|
||||
}
|
||||
|
||||
if (dstkey) decrRefCount(dstkey);
|
||||
decrRefCount(value);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (listTypeLength(o) == 0) {
|
||||
dbDelete(rl->db,rl->key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",rl->key,rl->db->id);
|
||||
}
|
||||
/* We don't call signalModifiedKey() as it was already called
|
||||
* when an element was pushed on the list. */
|
||||
}
|
||||
|
||||
/* Serve clients blocked on sorted set key. */
|
||||
else if (o != NULL && o->type == OBJ_ZSET) {
|
||||
dictEntry *de;
|
||||
|
||||
/* We serve clients in the same order they blocked for
|
||||
* this key, from the first blocked to the last. */
|
||||
de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
if (de) {
|
||||
list *clients = dictGetVal(de);
|
||||
int numclients = listLength(clients);
|
||||
unsigned long zcard = zsetLength(o);
|
||||
|
||||
while(numclients-- && zcard) {
|
||||
listNode *clientnode = listFirst(clients);
|
||||
client *receiver = clientnode->value;
|
||||
|
||||
if (receiver->btype != BLOCKED_ZSET) {
|
||||
/* Put at the tail, so that at the next call
|
||||
* we'll not run into it again. */
|
||||
listDelNode(clients,clientnode);
|
||||
listAddNodeTail(clients,receiver);
|
||||
continue;
|
||||
}
|
||||
|
||||
int where = (receiver->lastcmd &&
|
||||
receiver->lastcmd->proc == bzpopminCommand)
|
||||
? ZSET_MIN : ZSET_MAX;
|
||||
unblockClient(receiver);
|
||||
genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
|
||||
zcard--;
|
||||
|
||||
/* Replicate the command. */
|
||||
robj *argv[2];
|
||||
struct redisCommand *cmd = where == ZSET_MIN ?
|
||||
server.zpopminCommand :
|
||||
server.zpopmaxCommand;
|
||||
argv[0] = createStringObject(cmd->name,strlen(cmd->name));
|
||||
argv[1] = rl->key;
|
||||
incrRefCount(rl->key);
|
||||
propagate(cmd,receiver->db->id,
|
||||
argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Serve clients blocked on stream key. */
|
||||
else if (o != NULL && o->type == OBJ_STREAM) {
|
||||
dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
|
||||
stream *s = o->ptr;
|
||||
|
||||
/* We need to provide the new data arrived on the stream
|
||||
* to all the clients that are waiting for an offset smaller
|
||||
* than the current top item. */
|
||||
if (de) {
|
||||
list *clients = dictGetVal(de);
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
listRewind(clients,&li);
|
||||
|
||||
while((ln = listNext(&li))) {
|
||||
client *receiver = listNodeValue(ln);
|
||||
if (receiver->btype != BLOCKED_STREAM) continue;
|
||||
streamID *gt = dictFetchValue(receiver->bpop.keys,
|
||||
rl->key);
|
||||
|
||||
/* If we blocked in the context of a consumer
|
||||
* group, we need to resolve the group and update the
|
||||
* last ID the client is blocked for: this is needed
|
||||
* because serving other clients in the same consumer
|
||||
* group will alter the "last ID" of the consumer
|
||||
* group, and clients blocked in a consumer group are
|
||||
* always blocked for the ">" ID: we need to deliver
|
||||
* only new messages and avoid unblocking the client
|
||||
* otherwise. */
|
||||
streamCG *group = NULL;
|
||||
if (receiver->bpop.xread_group) {
|
||||
group = streamLookupCG(s,
|
||||
receiver->bpop.xread_group->ptr);
|
||||
/* If the group was not found, send an error
|
||||
* to the consumer. */
|
||||
if (!group) {
|
||||
addReplyError(receiver,
|
||||
"-NOGROUP the consumer group this client "
|
||||
"was blocked on no longer exists");
|
||||
unblockClient(receiver);
|
||||
continue;
|
||||
} else {
|
||||
*gt = group->last_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (streamCompareID(&s->last_id, gt) > 0) {
|
||||
streamID start = *gt;
|
||||
streamIncrID(&start);
|
||||
|
||||
/* Lookup the consumer for the group, if any. */
|
||||
streamConsumer *consumer = NULL;
|
||||
int noack = 0;
|
||||
|
||||
if (group) {
|
||||
consumer = streamLookupConsumer(group,
|
||||
receiver->bpop.xread_consumer->ptr,
|
||||
1);
|
||||
noack = receiver->bpop.xread_group_noack;
|
||||
}
|
||||
|
||||
/* Emit the two elements sub-array consisting of
|
||||
* the name of the stream and the data we
|
||||
* extracted from it. Wrapped in a single-item
|
||||
* array, since we have just one key. */
|
||||
addReplyMultiBulkLen(receiver,1);
|
||||
addReplyMultiBulkLen(receiver,2);
|
||||
addReplyBulk(receiver,rl->key);
|
||||
|
||||
streamPropInfo pi = {
|
||||
rl->key,
|
||||
receiver->bpop.xread_group
|
||||
};
|
||||
streamReplyWithRange(receiver,s,&start,NULL,
|
||||
receiver->bpop.xread_count,
|
||||
0, group, consumer, noack, &pi);
|
||||
|
||||
/* Note that after we unblock the client, 'gt'
|
||||
* and other receiver->bpop stuff are no longer
|
||||
* valid, so we must do the setup above before
|
||||
* this call. */
|
||||
unblockClient(receiver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
server.fixed_time_expire--;
|
||||
|
||||
@@ -665,7 +592,7 @@ void unblockClientWaitingData(client *c) {
|
||||
* the same key again and again in the list in case of multiple pushes
|
||||
* made by a script or in the context of MULTI/EXEC.
|
||||
*
|
||||
* The list will be finally processed by handleClientsBlockedOnKeys() */
|
||||
* The list will be finally processed by handleClientsBlockedOnLists() */
|
||||
void signalKeyAsReady(redisDb *db, robj *key) {
|
||||
readyList *rl;
|
||||
|
||||
|
||||
@@ -80,8 +80,6 @@ void receiveChildInfo(void) {
|
||||
server.stat_rdb_cow_bytes = server.child_info_data.cow_size;
|
||||
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
|
||||
server.stat_aof_cow_bytes = server.child_info_data.cow_size;
|
||||
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_MODULE) {
|
||||
server.stat_module_cow_bytes = server.child_info_data.cow_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+148
-202
@@ -49,7 +49,7 @@ clusterNode *myself = NULL;
|
||||
clusterNode *createClusterNode(char *nodename, int flags);
|
||||
int clusterAddNode(clusterNode *node);
|
||||
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void clusterReadHandler(connection *conn);
|
||||
void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void clusterSendPing(clusterLink *link, int type);
|
||||
void clusterSendFail(char *nodename);
|
||||
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request);
|
||||
@@ -490,8 +490,7 @@ void clusterInit(void) {
|
||||
/* Port sanity check II
|
||||
* The other handshake port check is triggered too late to stop
|
||||
* us from trying to use a too-high cluster port number. */
|
||||
int port = server.tls_cluster ? server.tls_port : server.port;
|
||||
if (port > (65535-CLUSTER_PORT_INCR)) {
|
||||
if (server.port > (65535-CLUSTER_PORT_INCR)) {
|
||||
serverLog(LL_WARNING, "Redis port number too high. "
|
||||
"Cluster communication port is 10,000 port "
|
||||
"numbers higher than your Redis port. "
|
||||
@@ -499,7 +498,8 @@ void clusterInit(void) {
|
||||
"lower than 55535.");
|
||||
exit(1);
|
||||
}
|
||||
if (listenToPort(port+CLUSTER_PORT_INCR,
|
||||
|
||||
if (listenToPort(server.port+CLUSTER_PORT_INCR,
|
||||
server.cfd,&server.cfd_count) == C_ERR)
|
||||
{
|
||||
exit(1);
|
||||
@@ -521,8 +521,8 @@ void clusterInit(void) {
|
||||
|
||||
/* Set myself->port / cport to my listening ports, we'll just need to
|
||||
* discover the IP address via MEET messages. */
|
||||
myself->port = port;
|
||||
myself->cport = port+CLUSTER_PORT_INCR;
|
||||
myself->port = server.port;
|
||||
myself->cport = server.port+CLUSTER_PORT_INCR;
|
||||
if (server.cluster_announce_port)
|
||||
myself->port = server.cluster_announce_port;
|
||||
if (server.cluster_announce_bus_port)
|
||||
@@ -606,7 +606,7 @@ clusterLink *createClusterLink(clusterNode *node) {
|
||||
link->sndbuf = sdsempty();
|
||||
link->rcvbuf = sdsempty();
|
||||
link->node = node;
|
||||
link->conn = NULL;
|
||||
link->fd = -1;
|
||||
return link;
|
||||
}
|
||||
|
||||
@@ -614,45 +614,23 @@ clusterLink *createClusterLink(clusterNode *node) {
|
||||
* This function will just make sure that the original node associated
|
||||
* with this link will have the 'link' field set to NULL. */
|
||||
void freeClusterLink(clusterLink *link) {
|
||||
if (link->conn) {
|
||||
connClose(link->conn);
|
||||
link->conn = NULL;
|
||||
if (link->fd != -1) {
|
||||
aeDeleteFileEvent(server.el, link->fd, AE_READABLE|AE_WRITABLE);
|
||||
}
|
||||
sdsfree(link->sndbuf);
|
||||
sdsfree(link->rcvbuf);
|
||||
if (link->node)
|
||||
link->node->link = NULL;
|
||||
close(link->fd);
|
||||
zfree(link);
|
||||
}
|
||||
|
||||
static void clusterConnAcceptHandler(connection *conn) {
|
||||
clusterLink *link;
|
||||
|
||||
if (connGetState(conn) != CONN_STATE_CONNECTED) {
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error accepting cluster node connection: %s", connGetLastError(conn));
|
||||
connClose(conn);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create a link object we use to handle the connection.
|
||||
* It gets passed to the readable handler when data is available.
|
||||
* Initiallly the link->node pointer is set to NULL as we don't know
|
||||
* which node is, but the right node is references once we know the
|
||||
* node identity. */
|
||||
link = createClusterLink(NULL);
|
||||
link->conn = conn;
|
||||
connSetPrivateData(conn, link);
|
||||
|
||||
/* Register read handler */
|
||||
connSetReadHandler(conn, clusterReadHandler);
|
||||
}
|
||||
|
||||
#define MAX_CLUSTER_ACCEPTS_PER_CALL 1000
|
||||
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cport, cfd;
|
||||
int max = MAX_CLUSTER_ACCEPTS_PER_CALL;
|
||||
char cip[NET_IP_STR_LEN];
|
||||
clusterLink *link;
|
||||
UNUSED(el);
|
||||
UNUSED(mask);
|
||||
UNUSED(privdata);
|
||||
@@ -669,25 +647,19 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
"Error accepting cluster node: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
|
||||
connection *conn = server.tls_cluster ? connCreateAcceptedTLS(cfd,1) : connCreateAcceptedSocket(cfd);
|
||||
connNonBlock(conn);
|
||||
connEnableTcpNoDelay(conn);
|
||||
anetNonBlock(NULL,cfd);
|
||||
anetEnableTcpNoDelay(NULL,cfd);
|
||||
|
||||
/* Use non-blocking I/O for cluster messages. */
|
||||
serverLog(LL_VERBOSE,"Accepting cluster node connection from %s:%d", cip, cport);
|
||||
|
||||
/* Accept the connection now. connAccept() may call our handler directly
|
||||
* or schedule it for later depending on connection implementation.
|
||||
*/
|
||||
if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) {
|
||||
if (connGetState(conn) == CONN_STATE_ERROR)
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error accepting cluster node connection: %s",
|
||||
connGetLastError(conn));
|
||||
connClose(conn);
|
||||
return;
|
||||
}
|
||||
serverLog(LL_VERBOSE,"Accepted cluster node %s:%d", cip, cport);
|
||||
/* Create a link object we use to handle the connection.
|
||||
* It gets passed to the readable handler when data is available.
|
||||
* Initiallly the link->node pointer is set to NULL as we don't know
|
||||
* which node is, but the right node is references once we know the
|
||||
* node identity. */
|
||||
link = createClusterLink(NULL);
|
||||
link->fd = cfd;
|
||||
aeCreateFileEvent(server.el,cfd,AE_READABLE,clusterReadHandler,link);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1488,7 +1460,7 @@ void nodeIp2String(char *buf, clusterLink *link, char *announced_ip) {
|
||||
memcpy(buf,announced_ip,NET_IP_STR_LEN);
|
||||
buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */
|
||||
} else {
|
||||
connPeerToString(link->conn, buf, NET_IP_STR_LEN, NULL);
|
||||
anetPeerToString(link->fd, buf, NET_IP_STR_LEN, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1792,7 +1764,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
{
|
||||
char ip[NET_IP_STR_LEN];
|
||||
|
||||
if (connSockName(link->conn,ip,sizeof(ip),NULL) != -1 &&
|
||||
if (anetSockName(link->fd,ip,sizeof(ip),NULL) != -1 &&
|
||||
strcmp(ip,myself->ip))
|
||||
{
|
||||
memcpy(myself->ip,ip,NET_IP_STR_LEN);
|
||||
@@ -2159,76 +2131,35 @@ void handleLinkIOError(clusterLink *link) {
|
||||
/* Send data. This is handled using a trivial send buffer that gets
|
||||
* consumed by write(). We don't try to optimize this for speed too much
|
||||
* as this is a very low traffic channel. */
|
||||
void clusterWriteHandler(connection *conn) {
|
||||
clusterLink *link = connGetPrivateData(conn);
|
||||
void clusterWriteHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
clusterLink *link = (clusterLink*) privdata;
|
||||
ssize_t nwritten;
|
||||
UNUSED(el);
|
||||
UNUSED(mask);
|
||||
|
||||
nwritten = connWrite(conn, link->sndbuf, sdslen(link->sndbuf));
|
||||
nwritten = write(fd, link->sndbuf, sdslen(link->sndbuf));
|
||||
if (nwritten <= 0) {
|
||||
serverLog(LL_DEBUG,"I/O error writing to node link: %s",
|
||||
(nwritten == -1) ? connGetLastError(conn) : "short write");
|
||||
(nwritten == -1) ? strerror(errno) : "short write");
|
||||
handleLinkIOError(link);
|
||||
return;
|
||||
}
|
||||
sdsrange(link->sndbuf,nwritten,-1);
|
||||
if (sdslen(link->sndbuf) == 0)
|
||||
connSetWriteHandler(link->conn, NULL);
|
||||
}
|
||||
|
||||
/* A connect handler that gets called when a connection to another node
|
||||
* gets established.
|
||||
*/
|
||||
void clusterLinkConnectHandler(connection *conn) {
|
||||
clusterLink *link = connGetPrivateData(conn);
|
||||
clusterNode *node = link->node;
|
||||
|
||||
/* Check if connection succeeded */
|
||||
if (connGetState(conn) != CONN_STATE_CONNECTED) {
|
||||
serverLog(LL_VERBOSE, "Connection with Node %.40s at %s:%d failed: %s",
|
||||
node->name, node->ip, node->cport,
|
||||
connGetLastError(conn));
|
||||
freeClusterLink(link);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Register a read handler from now on */
|
||||
connSetReadHandler(conn, clusterReadHandler);
|
||||
|
||||
/* Queue a PING in the new connection ASAP: this is crucial
|
||||
* to avoid false positives in failure detection.
|
||||
*
|
||||
* If the node is flagged as MEET, we send a MEET message instead
|
||||
* of a PING one, to force the receiver to add us in its node
|
||||
* table. */
|
||||
mstime_t old_ping_sent = node->ping_sent;
|
||||
clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ?
|
||||
CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
|
||||
if (old_ping_sent) {
|
||||
/* If there was an active ping before the link was
|
||||
* disconnected, we want to restore the ping time, otherwise
|
||||
* replaced by the clusterSendPing() call. */
|
||||
node->ping_sent = old_ping_sent;
|
||||
}
|
||||
/* We can clear the flag after the first packet is sent.
|
||||
* If we'll never receive a PONG, we'll never send new packets
|
||||
* to this node. Instead after the PONG is received and we
|
||||
* are no longer in meet/handshake status, we want to send
|
||||
* normal PING packets. */
|
||||
node->flags &= ~CLUSTER_NODE_MEET;
|
||||
|
||||
serverLog(LL_DEBUG,"Connecting with Node %.40s at %s:%d",
|
||||
node->name, node->ip, node->cport);
|
||||
aeDeleteFileEvent(server.el, link->fd, AE_WRITABLE);
|
||||
}
|
||||
|
||||
/* Read data. Try to read the first field of the header first to check the
|
||||
* full length of the packet. When a whole packet is in memory this function
|
||||
* will call the function to process the packet. And so forth. */
|
||||
void clusterReadHandler(connection *conn) {
|
||||
clusterMsg buf[1];
|
||||
void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
char buf[sizeof(clusterMsg)];
|
||||
ssize_t nread;
|
||||
clusterMsg *hdr;
|
||||
clusterLink *link = connGetPrivateData(conn);
|
||||
clusterLink *link = (clusterLink*) privdata;
|
||||
unsigned int readlen, rcvbuflen;
|
||||
UNUSED(el);
|
||||
UNUSED(mask);
|
||||
|
||||
while(1) { /* Read as long as there is data to read. */
|
||||
rcvbuflen = sdslen(link->rcvbuf);
|
||||
@@ -2256,13 +2187,13 @@ void clusterReadHandler(connection *conn) {
|
||||
if (readlen > sizeof(buf)) readlen = sizeof(buf);
|
||||
}
|
||||
|
||||
nread = connRead(conn,buf,readlen);
|
||||
if (nread == -1 && (connGetState(conn) == CONN_STATE_CONNECTED)) return; /* No more data ready. */
|
||||
nread = read(fd,buf,readlen);
|
||||
if (nread == -1 && errno == EAGAIN) return; /* No more data ready. */
|
||||
|
||||
if (nread <= 0) {
|
||||
/* I/O error... */
|
||||
serverLog(LL_DEBUG,"I/O error reading from node link: %s",
|
||||
(nread == 0) ? "connection closed" : connGetLastError(conn));
|
||||
(nread == 0) ? "connection closed" : strerror(errno));
|
||||
handleLinkIOError(link);
|
||||
return;
|
||||
} else {
|
||||
@@ -2291,7 +2222,8 @@ void clusterReadHandler(connection *conn) {
|
||||
* from event handlers that will do stuff with the same link later. */
|
||||
void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
|
||||
if (sdslen(link->sndbuf) == 0 && msglen != 0)
|
||||
connSetWriteHandlerWithBarrier(link->conn, clusterWriteHandler, 1);
|
||||
aeCreateFileEvent(server.el,link->fd,AE_WRITABLE|AE_BARRIER,
|
||||
clusterWriteHandler,link);
|
||||
|
||||
link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
|
||||
|
||||
@@ -2357,12 +2289,11 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
|
||||
}
|
||||
|
||||
/* Handle cluster-announce-port as well. */
|
||||
int port = server.tls_cluster ? server.tls_port : server.port;
|
||||
int announced_port = server.cluster_announce_port ?
|
||||
server.cluster_announce_port : port;
|
||||
server.cluster_announce_port : server.port;
|
||||
int announced_cport = server.cluster_announce_bus_port ?
|
||||
server.cluster_announce_bus_port :
|
||||
(port + CLUSTER_PORT_INCR);
|
||||
(server.port + CLUSTER_PORT_INCR);
|
||||
|
||||
memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots));
|
||||
memset(hdr->slaveof,0,CLUSTER_NAMELEN);
|
||||
@@ -2599,8 +2530,7 @@ void clusterBroadcastPong(int target) {
|
||||
*
|
||||
* If link is NULL, then the message is broadcasted to the whole cluster. */
|
||||
void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
||||
unsigned char *payload;
|
||||
clusterMsg buf[1];
|
||||
unsigned char buf[sizeof(clusterMsg)], *payload;
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
uint32_t totlen;
|
||||
uint32_t channel_len, message_len;
|
||||
@@ -2620,7 +2550,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
||||
|
||||
/* Try to use the local buffer if possible */
|
||||
if (totlen < sizeof(buf)) {
|
||||
payload = (unsigned char*)buf;
|
||||
payload = buf;
|
||||
} else {
|
||||
payload = zmalloc(totlen);
|
||||
memcpy(payload,hdr,sizeof(*hdr));
|
||||
@@ -2637,7 +2567,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
||||
|
||||
decrRefCount(channel);
|
||||
decrRefCount(message);
|
||||
if (payload != (unsigned char*)buf) zfree(payload);
|
||||
if (payload != buf) zfree(payload);
|
||||
}
|
||||
|
||||
/* Send a FAIL message to all the nodes we are able to contact.
|
||||
@@ -2646,7 +2576,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
||||
* we switch the node state to CLUSTER_NODE_FAIL and ask all the other
|
||||
* nodes to do the same ASAP. */
|
||||
void clusterSendFail(char *nodename) {
|
||||
clusterMsg buf[1];
|
||||
unsigned char buf[sizeof(clusterMsg)];
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
|
||||
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAIL);
|
||||
@@ -2658,7 +2588,7 @@ void clusterSendFail(char *nodename) {
|
||||
* slots configuration. The node name, slots bitmap, and configEpoch info
|
||||
* are included. */
|
||||
void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
||||
clusterMsg buf[1];
|
||||
unsigned char buf[sizeof(clusterMsg)];
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
|
||||
if (link == NULL) return;
|
||||
@@ -2666,7 +2596,7 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
||||
memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN);
|
||||
hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch);
|
||||
memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots));
|
||||
clusterSendMessage(link,(unsigned char*)buf,ntohl(hdr->totlen));
|
||||
clusterSendMessage(link,buf,ntohl(hdr->totlen));
|
||||
}
|
||||
|
||||
/* Send a MODULE message.
|
||||
@@ -2674,8 +2604,7 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
||||
* If link is NULL, then the message is broadcasted to the whole cluster. */
|
||||
void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
||||
unsigned char *payload, uint32_t len) {
|
||||
unsigned char *heapbuf;
|
||||
clusterMsg buf[1];
|
||||
unsigned char buf[sizeof(clusterMsg)], *heapbuf;
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
uint32_t totlen;
|
||||
|
||||
@@ -2690,7 +2619,7 @@ void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
||||
|
||||
/* Try to use the local buffer if possible */
|
||||
if (totlen < sizeof(buf)) {
|
||||
heapbuf = (unsigned char*)buf;
|
||||
heapbuf = buf;
|
||||
} else {
|
||||
heapbuf = zmalloc(totlen);
|
||||
memcpy(heapbuf,hdr,sizeof(*hdr));
|
||||
@@ -2703,7 +2632,7 @@ void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
||||
else
|
||||
clusterBroadcastMessage(heapbuf,totlen);
|
||||
|
||||
if (heapbuf != (unsigned char*)buf) zfree(heapbuf);
|
||||
if (heapbuf != buf) zfree(heapbuf);
|
||||
}
|
||||
|
||||
/* This function gets a cluster node ID string as target, the same way the nodes
|
||||
@@ -2747,7 +2676,7 @@ void clusterPropagatePublish(robj *channel, robj *message) {
|
||||
* Note that we send the failover request to everybody, master and slave nodes,
|
||||
* but only the masters are supposed to reply to our query. */
|
||||
void clusterRequestFailoverAuth(void) {
|
||||
clusterMsg buf[1];
|
||||
unsigned char buf[sizeof(clusterMsg)];
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
uint32_t totlen;
|
||||
|
||||
@@ -2763,7 +2692,7 @@ void clusterRequestFailoverAuth(void) {
|
||||
|
||||
/* Send a FAILOVER_AUTH_ACK message to the specified node. */
|
||||
void clusterSendFailoverAuth(clusterNode *node) {
|
||||
clusterMsg buf[1];
|
||||
unsigned char buf[sizeof(clusterMsg)];
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
uint32_t totlen;
|
||||
|
||||
@@ -2771,12 +2700,12 @@ void clusterSendFailoverAuth(clusterNode *node) {
|
||||
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK);
|
||||
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||
hdr->totlen = htonl(totlen);
|
||||
clusterSendMessage(node->link,(unsigned char*)buf,totlen);
|
||||
clusterSendMessage(node->link,buf,totlen);
|
||||
}
|
||||
|
||||
/* Send a MFSTART message to the specified node. */
|
||||
void clusterSendMFStart(clusterNode *node) {
|
||||
clusterMsg buf[1];
|
||||
unsigned char buf[sizeof(clusterMsg)];
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
uint32_t totlen;
|
||||
|
||||
@@ -2784,7 +2713,7 @@ void clusterSendMFStart(clusterNode *node) {
|
||||
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_MFSTART);
|
||||
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||
hdr->totlen = htonl(totlen);
|
||||
clusterSendMessage(node->link,(unsigned char*)buf,totlen);
|
||||
clusterSendMessage(node->link,buf,totlen);
|
||||
}
|
||||
|
||||
/* Vote for the node asking for our vote if there are the conditions. */
|
||||
@@ -3467,11 +3396,13 @@ void clusterCron(void) {
|
||||
}
|
||||
|
||||
if (node->link == NULL) {
|
||||
clusterLink *link = createClusterLink(node);
|
||||
link->conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
|
||||
connSetPrivateData(link->conn, link);
|
||||
if (connConnect(link->conn, node->ip, node->cport, NET_FIRST_BIND_ADDR,
|
||||
clusterLinkConnectHandler) == -1) {
|
||||
int fd;
|
||||
mstime_t old_ping_sent;
|
||||
clusterLink *link;
|
||||
|
||||
fd = anetTcpNonBlockBindConnect(server.neterr, node->ip,
|
||||
node->cport, NET_FIRST_BIND_ADDR);
|
||||
if (fd == -1) {
|
||||
/* We got a synchronous error from connect before
|
||||
* clusterSendPing() had a chance to be called.
|
||||
* If node->ping_sent is zero, failure detection can't work,
|
||||
@@ -3481,11 +3412,37 @@ void clusterCron(void) {
|
||||
serverLog(LL_DEBUG, "Unable to connect to "
|
||||
"Cluster Node [%s]:%d -> %s", node->ip,
|
||||
node->cport, server.neterr);
|
||||
|
||||
freeClusterLink(link);
|
||||
continue;
|
||||
}
|
||||
link = createClusterLink(node);
|
||||
link->fd = fd;
|
||||
node->link = link;
|
||||
aeCreateFileEvent(server.el,link->fd,AE_READABLE,
|
||||
clusterReadHandler,link);
|
||||
/* Queue a PING in the new connection ASAP: this is crucial
|
||||
* to avoid false positives in failure detection.
|
||||
*
|
||||
* If the node is flagged as MEET, we send a MEET message instead
|
||||
* of a PING one, to force the receiver to add us in its node
|
||||
* table. */
|
||||
old_ping_sent = node->ping_sent;
|
||||
clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ?
|
||||
CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
|
||||
if (old_ping_sent) {
|
||||
/* If there was an active ping before the link was
|
||||
* disconnected, we want to restore the ping time, otherwise
|
||||
* replaced by the clusterSendPing() call. */
|
||||
node->ping_sent = old_ping_sent;
|
||||
}
|
||||
/* We can clear the flag after the first packet is sent.
|
||||
* If we'll never receive a PONG, we'll never send new packets
|
||||
* to this node. Instead after the PONG is received and we
|
||||
* are no longer in meet/handshake status, we want to send
|
||||
* normal PING packets. */
|
||||
node->flags &= ~CLUSTER_NODE_MEET;
|
||||
|
||||
serverLog(LL_DEBUG,"Connecting with Node %.40s at %s:%d",
|
||||
node->name, node->ip, node->cport);
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
@@ -4184,7 +4141,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
*/
|
||||
|
||||
int num_masters = 0;
|
||||
void *slot_replylen = addReplyDeferredLen(c);
|
||||
void *slot_replylen = addDeferredMultiBulkLength(c);
|
||||
|
||||
dictEntry *de;
|
||||
dictIterator *di = dictGetSafeIterator(server.cluster->nodes);
|
||||
@@ -4204,7 +4161,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
}
|
||||
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
|
||||
int nested_elements = 3; /* slots (2) + master addr (1). */
|
||||
void *nested_replylen = addReplyDeferredLen(c);
|
||||
void *nested_replylen = addDeferredMultiBulkLength(c);
|
||||
|
||||
if (bit && j == CLUSTER_SLOTS-1) j++;
|
||||
|
||||
@@ -4220,7 +4177,7 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
start = -1;
|
||||
|
||||
/* First node reply position is always the master */
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyMultiBulkLen(c, 3);
|
||||
addReplyBulkCString(c, node->ip);
|
||||
addReplyLongLong(c, node->port);
|
||||
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
|
||||
@@ -4230,19 +4187,19 @@ void clusterReplyMultiBulkSlots(client *c) {
|
||||
/* This loop is copy/pasted from clusterGenNodeDescription()
|
||||
* with modifications for per-slot node aggregation */
|
||||
if (nodeFailed(node->slaves[i])) continue;
|
||||
addReplyArrayLen(c, 3);
|
||||
addReplyMultiBulkLen(c, 3);
|
||||
addReplyBulkCString(c, node->slaves[i]->ip);
|
||||
addReplyLongLong(c, node->slaves[i]->port);
|
||||
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
|
||||
nested_elements++;
|
||||
}
|
||||
setDeferredArrayLen(c, nested_replylen, nested_elements);
|
||||
setDeferredMultiBulkLength(c, nested_replylen, nested_elements);
|
||||
num_masters++;
|
||||
}
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredArrayLen(c, slot_replylen, num_masters);
|
||||
setDeferredMultiBulkLength(c, slot_replylen, num_masters);
|
||||
}
|
||||
|
||||
void clusterCommand(client *c) {
|
||||
@@ -4262,7 +4219,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 information about the cluster.",
|
||||
"INFO - Return onformation 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.",
|
||||
@@ -4273,7 +4230,6 @@ 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
|
||||
@@ -4309,9 +4265,12 @@ NULL
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
|
||||
/* CLUSTER NODES */
|
||||
sds nodes = clusterGenNodesDescription(0);
|
||||
addReplyVerbatim(c,nodes,sdslen(nodes),"txt");
|
||||
sdsfree(nodes);
|
||||
robj *o;
|
||||
sds ci = clusterGenNodesDescription(0);
|
||||
|
||||
o = createObject(OBJ_STRING,ci);
|
||||
addReplyBulk(c,o);
|
||||
decrRefCount(o);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
|
||||
/* CLUSTER MYID */
|
||||
addReplyBulkCBuffer(c,myself->name, CLUSTER_NAMELEN);
|
||||
@@ -4553,8 +4512,10 @@ NULL
|
||||
"cluster_stats_messages_received:%lld\r\n", tot_msg_received);
|
||||
|
||||
/* Produce the reply protocol. */
|
||||
addReplyVerbatim(c,info,sdslen(info),"txt");
|
||||
sdsfree(info);
|
||||
addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n",
|
||||
(unsigned long)sdslen(info)));
|
||||
addReplySds(c,info);
|
||||
addReply(c,shared.crlf);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) {
|
||||
int retval = clusterSaveConfig(1);
|
||||
|
||||
@@ -4602,7 +4563,7 @@ NULL
|
||||
|
||||
keys = zmalloc(sizeof(robj*)*maxkeys);
|
||||
numkeys = getKeysInSlot(slot, keys, maxkeys);
|
||||
addReplyArrayLen(c,numkeys);
|
||||
addReplyMultiBulkLen(c,numkeys);
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
addReplyBulk(c,keys[j]);
|
||||
decrRefCount(keys[j]);
|
||||
@@ -4681,7 +4642,7 @@ NULL
|
||||
return;
|
||||
}
|
||||
|
||||
addReplyArrayLen(c,n->numslaves);
|
||||
addReplyMultiBulkLen(c,n->numslaves);
|
||||
for (j = 0; j < n->numslaves; j++) {
|
||||
sds ni = clusterGenNodeDescription(n->slaves[j]);
|
||||
addReplyBulkCString(c,ni);
|
||||
@@ -4885,12 +4846,12 @@ int verifyDumpPayload(unsigned char *p, size_t len) {
|
||||
* DUMP is actually not used by Redis Cluster but it is the obvious
|
||||
* complement of RESTORE and can be useful for different applications. */
|
||||
void dumpCommand(client *c) {
|
||||
robj *o;
|
||||
robj *o, *dumpobj;
|
||||
rio payload;
|
||||
|
||||
/* Check if the key is here. */
|
||||
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
|
||||
addReplyNull(c);
|
||||
addReply(c,shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4898,7 +4859,9 @@ void dumpCommand(client *c) {
|
||||
createDumpPayload(&payload,o,c->argv[1]);
|
||||
|
||||
/* Transfer to the client */
|
||||
addReplyBulkSds(c,payload.io.buffer.ptr);
|
||||
dumpobj = createObject(OBJ_STRING,payload.io.buffer.ptr);
|
||||
addReplyBulk(c,dumpobj);
|
||||
decrRefCount(dumpobj);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4981,9 +4944,8 @@ void restoreCommand(client *c) {
|
||||
if (!absttl) ttl+=mstime();
|
||||
setExpire(c,c->db,c->argv[1],ttl);
|
||||
}
|
||||
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock,1000);
|
||||
objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",c->argv[1],c->db->id);
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -4998,7 +4960,7 @@ void restoreCommand(client *c) {
|
||||
#define MIGRATE_SOCKET_CACHE_TTL 10 /* close cached sockets after 10 sec. */
|
||||
|
||||
typedef struct migrateCachedSocket {
|
||||
connection *conn;
|
||||
int fd;
|
||||
long last_dbid;
|
||||
time_t last_use_time;
|
||||
} migrateCachedSocket;
|
||||
@@ -5015,7 +4977,7 @@ typedef struct migrateCachedSocket {
|
||||
* should be called so that the connection will be created from scratch
|
||||
* the next time. */
|
||||
migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long timeout) {
|
||||
connection *conn;
|
||||
int fd;
|
||||
sds name = sdsempty();
|
||||
migrateCachedSocket *cs;
|
||||
|
||||
@@ -5035,27 +4997,34 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
|
||||
/* Too many items, drop one at random. */
|
||||
dictEntry *de = dictGetRandomKey(server.migrate_cached_sockets);
|
||||
cs = dictGetVal(de);
|
||||
connClose(cs->conn);
|
||||
close(cs->fd);
|
||||
zfree(cs);
|
||||
dictDelete(server.migrate_cached_sockets,dictGetKey(de));
|
||||
}
|
||||
|
||||
/* Create the socket */
|
||||
conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
|
||||
if (connBlockingConnect(conn, c->argv[1]->ptr, atoi(c->argv[2]->ptr), timeout)
|
||||
!= C_OK) {
|
||||
addReplySds(c,
|
||||
sdsnew("-IOERR error or timeout connecting to the client\r\n"));
|
||||
connClose(conn);
|
||||
fd = anetTcpNonBlockConnect(server.neterr,c->argv[1]->ptr,
|
||||
atoi(c->argv[2]->ptr));
|
||||
if (fd == -1) {
|
||||
sdsfree(name);
|
||||
addReplyErrorFormat(c,"Can't connect to target node: %s",
|
||||
server.neterr);
|
||||
return NULL;
|
||||
}
|
||||
anetEnableTcpNoDelay(server.neterr,fd);
|
||||
|
||||
/* Check if it connects within the specified timeout. */
|
||||
if ((aeWait(fd,AE_WRITABLE,timeout) & AE_WRITABLE) == 0) {
|
||||
sdsfree(name);
|
||||
addReplySds(c,
|
||||
sdsnew("-IOERR error or timeout connecting to the client\r\n"));
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
connEnableTcpNoDelay(conn);
|
||||
|
||||
/* Add to the cache and return it to the caller. */
|
||||
cs = zmalloc(sizeof(*cs));
|
||||
cs->conn = conn;
|
||||
|
||||
cs->fd = fd;
|
||||
cs->last_dbid = -1;
|
||||
cs->last_use_time = server.unixtime;
|
||||
dictAdd(server.migrate_cached_sockets,name,cs);
|
||||
@@ -5076,7 +5045,7 @@ void migrateCloseSocket(robj *host, robj *port) {
|
||||
return;
|
||||
}
|
||||
|
||||
connClose(cs->conn);
|
||||
close(cs->fd);
|
||||
zfree(cs);
|
||||
dictDelete(server.migrate_cached_sockets,name);
|
||||
sdsfree(name);
|
||||
@@ -5090,7 +5059,7 @@ void migrateCloseTimedoutSockets(void) {
|
||||
migrateCachedSocket *cs = dictGetVal(de);
|
||||
|
||||
if ((server.unixtime - cs->last_use_time) > MIGRATE_SOCKET_CACHE_TTL) {
|
||||
connClose(cs->conn);
|
||||
close(cs->fd);
|
||||
zfree(cs);
|
||||
dictDelete(server.migrate_cached_sockets,dictGetKey(de));
|
||||
}
|
||||
@@ -5272,7 +5241,7 @@ try_again:
|
||||
|
||||
while ((towrite = sdslen(buf)-pos) > 0) {
|
||||
towrite = (towrite > (64*1024) ? (64*1024) : towrite);
|
||||
nwritten = connSyncWrite(cs->conn,buf+pos,towrite,timeout);
|
||||
nwritten = syncWrite(cs->fd,buf+pos,towrite,timeout);
|
||||
if (nwritten != (signed)towrite) {
|
||||
write_error = 1;
|
||||
goto socket_err;
|
||||
@@ -5286,11 +5255,11 @@ try_again:
|
||||
char buf2[1024]; /* Restore reply. */
|
||||
|
||||
/* Read the AUTH reply if needed. */
|
||||
if (password && connSyncReadLine(cs->conn, buf0, sizeof(buf0), timeout) <= 0)
|
||||
if (password && syncReadLine(cs->fd, buf0, sizeof(buf0), timeout) <= 0)
|
||||
goto socket_err;
|
||||
|
||||
/* Read the SELECT reply if needed. */
|
||||
if (select && connSyncReadLine(cs->conn, buf1, sizeof(buf1), timeout) <= 0)
|
||||
if (select && syncReadLine(cs->fd, buf1, sizeof(buf1), timeout) <= 0)
|
||||
goto socket_err;
|
||||
|
||||
/* Read the RESTORE replies. */
|
||||
@@ -5305,7 +5274,7 @@ try_again:
|
||||
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
|
||||
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
if (connSyncReadLine(cs->conn, buf2, sizeof(buf2), timeout) <= 0) {
|
||||
if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0) {
|
||||
socket_error = 1;
|
||||
break;
|
||||
}
|
||||
@@ -5330,7 +5299,6 @@ 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. */
|
||||
@@ -5493,8 +5461,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 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. */
|
||||
* CLUSTER_REDIR_DOWN_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;
|
||||
@@ -5612,27 +5580,10 @@ 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 only serve the request
|
||||
* if it is a read command and when allow_reads_when_down is enabled. */
|
||||
/* Cluster is globally down but we got keys? We can't serve the request. */
|
||||
if (server.cluster->state != CLUSTER_OK) {
|
||||
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. */
|
||||
}
|
||||
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Return the hashslot by reference. */
|
||||
@@ -5701,8 +5652,6 @@ 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 ||
|
||||
@@ -5737,10 +5686,7 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
dictEntry *de;
|
||||
dictIterator *di;
|
||||
|
||||
/* 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 the cluster is down, unblock the client with the right error. */
|
||||
if (server.cluster->state == CLUSTER_FAIL) {
|
||||
clusterRedirectClient(c,NULL,0,CLUSTER_REDIR_DOWN_STATE);
|
||||
return 1;
|
||||
|
||||
+6
-2
@@ -13,10 +13,15 @@
|
||||
|
||||
/* 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. */
|
||||
@@ -29,14 +34,13 @@
|
||||
#define CLUSTER_REDIR_MOVED 4 /* -MOVED redirection required. */
|
||||
#define CLUSTER_REDIR_DOWN_STATE 5 /* -CLUSTERDOWN, global state. */
|
||||
#define CLUSTER_REDIR_DOWN_UNBOUND 6 /* -CLUSTERDOWN, unbound slot. */
|
||||
#define CLUSTER_REDIR_DOWN_RO_STATE 7 /* -CLUSTERDOWN, allow reads. */
|
||||
|
||||
struct clusterNode;
|
||||
|
||||
/* clusterLink encapsulates everything needed to talk with a remote node. */
|
||||
typedef struct clusterLink {
|
||||
mstime_t ctime; /* Link creation time */
|
||||
connection *conn; /* Connection to remote node */
|
||||
int fd; /* TCP socket file descriptor */
|
||||
sds sndbuf; /* Packet send buffer */
|
||||
sds rcvbuf; /* Packet reception buffer */
|
||||
struct clusterNode *node; /* Node related to this link if any, or NULL */
|
||||
|
||||
+993
-985
File diff suppressed because it is too large
Load Diff
@@ -1,413 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Redis Labs
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "connhelpers.h"
|
||||
|
||||
/* The connections module provides a lean abstraction of network connections
|
||||
* to avoid direct socket and async event management across the Redis code base.
|
||||
*
|
||||
* It does NOT provide advanced connection features commonly found in similar
|
||||
* libraries such as complete in/out buffer management, throttling, etc. These
|
||||
* functions remain in networking.c.
|
||||
*
|
||||
* The primary goal is to allow transparent handling of TCP and TLS based
|
||||
* connections. To do so, connections have the following properties:
|
||||
*
|
||||
* 1. A connection may live before its corresponding socket exists. This
|
||||
* allows various context and configuration setting to be handled before
|
||||
* establishing the actual connection.
|
||||
* 2. The caller may register/unregister logical read/write handlers to be
|
||||
* called when the connection has data to read from/can accept writes.
|
||||
* These logical handlers may or may not correspond to actual AE events,
|
||||
* depending on the implementation (for TCP they are; for TLS they aren't).
|
||||
*/
|
||||
|
||||
ConnectionType CT_Socket;
|
||||
|
||||
/* When a connection is created we must know its type already, but the
|
||||
* underlying socket may or may not exist:
|
||||
*
|
||||
* - For accepted connections, it exists as we do not model the listen/accept
|
||||
* part; So caller calls connCreateSocket() followed by connAccept().
|
||||
* - For outgoing connections, the socket is created by the connection module
|
||||
* itself; So caller calls connCreateSocket() followed by connConnect(),
|
||||
* which registers a connect callback that fires on connected/error state
|
||||
* (and after any transport level handshake was done).
|
||||
*
|
||||
* NOTE: An earlier version relied on connections being part of other structs
|
||||
* and not independently allocated. This could lead to further optimizations
|
||||
* like using container_of(), etc. However it was discontinued in favor of
|
||||
* this approach for these reasons:
|
||||
*
|
||||
* 1. In some cases conns are created/handled outside the context of the
|
||||
* containing struct, in which case it gets a bit awkward to copy them.
|
||||
* 2. Future implementations may wish to allocate arbitrary data for the
|
||||
* connection.
|
||||
* 3. The container_of() approach is anyway risky because connections may
|
||||
* be embedded in different structs, not just client.
|
||||
*/
|
||||
|
||||
connection *connCreateSocket() {
|
||||
connection *conn = zcalloc(sizeof(connection));
|
||||
conn->type = &CT_Socket;
|
||||
conn->fd = -1;
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
/* Create a new socket-type connection that is already associated with
|
||||
* an accepted connection.
|
||||
*
|
||||
* The socket is not read for I/O until connAccept() was called and
|
||||
* invoked the connection-level accept handler.
|
||||
*/
|
||||
connection *connCreateAcceptedSocket(int fd) {
|
||||
connection *conn = connCreateSocket();
|
||||
conn->fd = fd;
|
||||
conn->state = CONN_STATE_ACCEPTING;
|
||||
return conn;
|
||||
}
|
||||
|
||||
static int connSocketConnect(connection *conn, const char *addr, int port, const char *src_addr,
|
||||
ConnectionCallbackFunc connect_handler) {
|
||||
int fd = anetTcpNonBlockBestEffortBindConnect(NULL,addr,port,src_addr);
|
||||
if (fd == -1) {
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
conn->last_errno = errno;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
conn->fd = fd;
|
||||
conn->state = CONN_STATE_CONNECTING;
|
||||
|
||||
conn->conn_handler = connect_handler;
|
||||
aeCreateFileEvent(server.el, conn->fd, AE_WRITABLE,
|
||||
conn->type->ae_handler, conn);
|
||||
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Returns true if a write handler is registered */
|
||||
int connHasWriteHandler(connection *conn) {
|
||||
return conn->write_handler != NULL;
|
||||
}
|
||||
|
||||
/* Returns true if a read handler is registered */
|
||||
int connHasReadHandler(connection *conn) {
|
||||
return conn->read_handler != NULL;
|
||||
}
|
||||
|
||||
/* Associate a private data pointer with the connection */
|
||||
void connSetPrivateData(connection *conn, void *data) {
|
||||
conn->private_data = data;
|
||||
}
|
||||
|
||||
/* Get the associated private data pointer */
|
||||
void *connGetPrivateData(connection *conn) {
|
||||
return conn->private_data;
|
||||
}
|
||||
|
||||
/* ------ Pure socket connections ------- */
|
||||
|
||||
/* A very incomplete list of implementation-specific calls. Much of the above shall
|
||||
* move here as we implement additional connection types.
|
||||
*/
|
||||
|
||||
/* Close the connection and free resources. */
|
||||
static void connSocketClose(connection *conn) {
|
||||
if (conn->fd != -1) {
|
||||
aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
|
||||
aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
|
||||
close(conn->fd);
|
||||
conn->fd = -1;
|
||||
}
|
||||
|
||||
/* If called from within a handler, schedule the close but
|
||||
* keep the connection until the handler returns.
|
||||
*/
|
||||
if (connHasRefs(conn)) {
|
||||
conn->flags |= CONN_FLAG_CLOSE_SCHEDULED;
|
||||
return;
|
||||
}
|
||||
|
||||
zfree(conn);
|
||||
}
|
||||
|
||||
static int connSocketWrite(connection *conn, const void *data, size_t data_len) {
|
||||
int ret = write(conn->fd, data, data_len);
|
||||
if (ret < 0 && errno != EAGAIN) {
|
||||
conn->last_errno = errno;
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
|
||||
int ret = read(conn->fd, buf, buf_len);
|
||||
if (!ret) {
|
||||
conn->state = CONN_STATE_CLOSED;
|
||||
} else if (ret < 0 && errno != EAGAIN) {
|
||||
conn->last_errno = errno;
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
|
||||
int ret = C_OK;
|
||||
|
||||
if (conn->state != CONN_STATE_ACCEPTING) return C_ERR;
|
||||
conn->state = CONN_STATE_CONNECTED;
|
||||
|
||||
connIncrRefs(conn);
|
||||
if (!callHandler(conn, accept_handler)) ret = C_ERR;
|
||||
connDecrRefs(conn);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Register a write handler, to be called when the connection is writable.
|
||||
* If NULL, the existing handler is removed.
|
||||
*
|
||||
* The barrier flag indicates a write barrier is requested, resulting with
|
||||
* CONN_FLAG_WRITE_BARRIER set. This will ensure that the write handler is
|
||||
* always called before and not after the read handler in a single event
|
||||
* loop.
|
||||
*/
|
||||
static int connSocketSetWriteHandler(connection *conn, ConnectionCallbackFunc func, int barrier) {
|
||||
if (func == conn->write_handler) return C_OK;
|
||||
|
||||
conn->write_handler = func;
|
||||
if (barrier)
|
||||
conn->flags |= CONN_FLAG_WRITE_BARRIER;
|
||||
else
|
||||
conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
|
||||
if (!conn->write_handler)
|
||||
aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
|
||||
else
|
||||
if (aeCreateFileEvent(server.el,conn->fd,AE_WRITABLE,
|
||||
conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Register a read handler, to be called when the connection is readable.
|
||||
* If NULL, the existing handler is removed.
|
||||
*/
|
||||
static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
|
||||
if (func == conn->read_handler) return C_OK;
|
||||
|
||||
conn->read_handler = func;
|
||||
if (!conn->read_handler)
|
||||
aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
|
||||
else
|
||||
if (aeCreateFileEvent(server.el,conn->fd,
|
||||
AE_READABLE,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
static const char *connSocketGetLastError(connection *conn) {
|
||||
return strerror(conn->last_errno);
|
||||
}
|
||||
|
||||
static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask)
|
||||
{
|
||||
UNUSED(el);
|
||||
UNUSED(fd);
|
||||
connection *conn = clientData;
|
||||
|
||||
if (conn->state == CONN_STATE_CONNECTING &&
|
||||
(mask & AE_WRITABLE) && conn->conn_handler) {
|
||||
|
||||
if (connGetSocketError(conn)) {
|
||||
conn->last_errno = errno;
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
} else {
|
||||
conn->state = CONN_STATE_CONNECTED;
|
||||
}
|
||||
|
||||
if (!conn->write_handler) aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
|
||||
|
||||
if (!callHandler(conn, conn->conn_handler)) return;
|
||||
conn->conn_handler = NULL;
|
||||
}
|
||||
|
||||
/* Normally we execute the readable event first, and the writable
|
||||
* event later. This is useful as sometimes we may be able
|
||||
* to serve the reply of a query immediately after processing the
|
||||
* query.
|
||||
*
|
||||
* However if WRITE_BARRIER is set in the mask, our application is
|
||||
* asking us to do the reverse: never fire the writable event
|
||||
* after the readable. In such a case, we invert the calls.
|
||||
* This is useful when, for instance, we want to do things
|
||||
* in the beforeSleep() hook, like fsync'ing a file to disk,
|
||||
* before replying to a client. */
|
||||
int invert = conn->flags & CONN_FLAG_WRITE_BARRIER;
|
||||
|
||||
int call_write = (mask & AE_WRITABLE) && conn->write_handler;
|
||||
int call_read = (mask & AE_READABLE) && conn->read_handler;
|
||||
|
||||
/* Handle normal I/O flows */
|
||||
if (!invert && call_read) {
|
||||
if (!callHandler(conn, conn->read_handler)) return;
|
||||
}
|
||||
/* Fire the writable event. */
|
||||
if (call_write) {
|
||||
if (!callHandler(conn, conn->write_handler)) return;
|
||||
}
|
||||
/* If we have to invert the call, fire the readable event now
|
||||
* after the writable one. */
|
||||
if (invert && call_read) {
|
||||
if (!callHandler(conn, conn->read_handler)) return;
|
||||
}
|
||||
}
|
||||
|
||||
static int connSocketBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
|
||||
int fd = anetTcpNonBlockConnect(NULL,addr,port);
|
||||
if (fd == -1) {
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
conn->last_errno = errno;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if ((aeWait(fd, AE_WRITABLE, timeout) & AE_WRITABLE) == 0) {
|
||||
conn->state = CONN_STATE_ERROR;
|
||||
conn->last_errno = ETIMEDOUT;
|
||||
}
|
||||
|
||||
conn->fd = fd;
|
||||
conn->state = CONN_STATE_CONNECTED;
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Connection-based versions of syncio.c functions.
|
||||
* NOTE: This should ideally be refactored out in favor of pure async work.
|
||||
*/
|
||||
|
||||
static ssize_t connSocketSyncWrite(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||
return syncWrite(conn->fd, ptr, size, timeout);
|
||||
}
|
||||
|
||||
static ssize_t connSocketSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||
return syncRead(conn->fd, ptr, size, timeout);
|
||||
}
|
||||
|
||||
static ssize_t connSocketSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||
return syncReadLine(conn->fd, ptr, size, timeout);
|
||||
}
|
||||
|
||||
|
||||
ConnectionType CT_Socket = {
|
||||
.ae_handler = connSocketEventHandler,
|
||||
.close = connSocketClose,
|
||||
.write = connSocketWrite,
|
||||
.read = connSocketRead,
|
||||
.accept = connSocketAccept,
|
||||
.connect = connSocketConnect,
|
||||
.set_write_handler = connSocketSetWriteHandler,
|
||||
.set_read_handler = connSocketSetReadHandler,
|
||||
.get_last_error = connSocketGetLastError,
|
||||
.blocking_connect = connSocketBlockingConnect,
|
||||
.sync_write = connSocketSyncWrite,
|
||||
.sync_read = connSocketSyncRead,
|
||||
.sync_readline = connSocketSyncReadLine
|
||||
};
|
||||
|
||||
|
||||
int connGetSocketError(connection *conn) {
|
||||
int sockerr = 0;
|
||||
socklen_t errlen = sizeof(sockerr);
|
||||
|
||||
if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
|
||||
sockerr = errno;
|
||||
return sockerr;
|
||||
}
|
||||
|
||||
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
|
||||
return anetPeerToString(conn ? conn->fd : -1, ip, ip_len, port);
|
||||
}
|
||||
|
||||
int connFormatPeer(connection *conn, char *buf, size_t buf_len) {
|
||||
return anetFormatPeer(conn ? conn->fd : -1, buf, buf_len);
|
||||
}
|
||||
|
||||
int connSockName(connection *conn, char *ip, size_t ip_len, int *port) {
|
||||
return anetSockName(conn->fd, ip, ip_len, port);
|
||||
}
|
||||
|
||||
int connBlock(connection *conn) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetBlock(NULL, conn->fd);
|
||||
}
|
||||
|
||||
int connNonBlock(connection *conn) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetNonBlock(NULL, conn->fd);
|
||||
}
|
||||
|
||||
int connEnableTcpNoDelay(connection *conn) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetEnableTcpNoDelay(NULL, conn->fd);
|
||||
}
|
||||
|
||||
int connDisableTcpNoDelay(connection *conn) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetDisableTcpNoDelay(NULL, conn->fd);
|
||||
}
|
||||
|
||||
int connKeepAlive(connection *conn, int interval) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetKeepAlive(NULL, conn->fd, interval);
|
||||
}
|
||||
|
||||
int connSendTimeout(connection *conn, long long ms) {
|
||||
return anetSendTimeout(NULL, conn->fd, ms);
|
||||
}
|
||||
|
||||
int connRecvTimeout(connection *conn, long long ms) {
|
||||
return anetRecvTimeout(NULL, conn->fd, ms);
|
||||
}
|
||||
|
||||
int connGetState(connection *conn) {
|
||||
return conn->state;
|
||||
}
|
||||
|
||||
/* Return a text that describes the connection, suitable for inclusion
|
||||
* in CLIENT LIST and similar outputs.
|
||||
*
|
||||
* For sockets, we always return "fd=<fdnum>" to maintain compatibility.
|
||||
*/
|
||||
const char *connGetInfo(connection *conn, char *buf, size_t buf_len) {
|
||||
snprintf(buf, buf_len-1, "fd=%i", conn->fd);
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019, Redis Labs
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __REDIS_CONNECTION_H
|
||||
#define __REDIS_CONNECTION_H
|
||||
|
||||
#define CONN_INFO_LEN 32
|
||||
|
||||
struct aeEventLoop;
|
||||
typedef struct connection connection;
|
||||
|
||||
typedef enum {
|
||||
CONN_STATE_NONE = 0,
|
||||
CONN_STATE_CONNECTING,
|
||||
CONN_STATE_ACCEPTING,
|
||||
CONN_STATE_CONNECTED,
|
||||
CONN_STATE_CLOSED,
|
||||
CONN_STATE_ERROR
|
||||
} ConnectionState;
|
||||
|
||||
#define CONN_FLAG_CLOSE_SCHEDULED (1<<0) /* Closed scheduled by a handler */
|
||||
#define CONN_FLAG_WRITE_BARRIER (1<<1) /* Write barrier requested */
|
||||
|
||||
typedef void (*ConnectionCallbackFunc)(struct connection *conn);
|
||||
|
||||
typedef struct ConnectionType {
|
||||
void (*ae_handler)(struct aeEventLoop *el, int fd, void *clientData, int mask);
|
||||
int (*connect)(struct connection *conn, const char *addr, int port, const char *source_addr, ConnectionCallbackFunc connect_handler);
|
||||
int (*write)(struct connection *conn, const void *data, size_t data_len);
|
||||
int (*read)(struct connection *conn, void *buf, size_t buf_len);
|
||||
void (*close)(struct connection *conn);
|
||||
int (*accept)(struct connection *conn, ConnectionCallbackFunc accept_handler);
|
||||
int (*set_write_handler)(struct connection *conn, ConnectionCallbackFunc handler, int barrier);
|
||||
int (*set_read_handler)(struct connection *conn, ConnectionCallbackFunc handler);
|
||||
const char *(*get_last_error)(struct connection *conn);
|
||||
int (*blocking_connect)(struct connection *conn, const char *addr, int port, long long timeout);
|
||||
ssize_t (*sync_write)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
|
||||
ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
|
||||
ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
|
||||
} ConnectionType;
|
||||
|
||||
struct connection {
|
||||
ConnectionType *type;
|
||||
ConnectionState state;
|
||||
short int flags;
|
||||
short int refs;
|
||||
int last_errno;
|
||||
void *private_data;
|
||||
ConnectionCallbackFunc conn_handler;
|
||||
ConnectionCallbackFunc write_handler;
|
||||
ConnectionCallbackFunc read_handler;
|
||||
int fd;
|
||||
};
|
||||
|
||||
/* The connection module does not deal with listening and accepting sockets,
|
||||
* so we assume we have a socket when an incoming connection is created.
|
||||
*
|
||||
* The fd supplied should therefore be associated with an already accept()ed
|
||||
* socket.
|
||||
*
|
||||
* connAccept() may directly call accept_handler(), or return and call it
|
||||
* at a later time. This behavior is a bit awkward but aims to reduce the need
|
||||
* to wait for the next event loop, if no additional handshake is required.
|
||||
*
|
||||
* IMPORTANT: accept_handler may decide to close the connection, calling connClose().
|
||||
* To make this safe, the connection is only marked with CONN_FLAG_CLOSE_SCHEDULED
|
||||
* in this case, and connAccept() returns with an error.
|
||||
*
|
||||
* connAccept() callers must always check the return value and on error (C_ERR)
|
||||
* a connClose() must be called.
|
||||
*/
|
||||
|
||||
static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
|
||||
return conn->type->accept(conn, accept_handler);
|
||||
}
|
||||
|
||||
/* Establish a connection. The connect_handler will be called when the connection
|
||||
* is established, or if an error has occured.
|
||||
*
|
||||
* The connection handler will be responsible to set up any read/write handlers
|
||||
* as needed.
|
||||
*
|
||||
* If C_ERR is returned, the operation failed and the connection handler shall
|
||||
* not be expected.
|
||||
*/
|
||||
static inline int connConnect(connection *conn, const char *addr, int port, const char *src_addr,
|
||||
ConnectionCallbackFunc connect_handler) {
|
||||
return conn->type->connect(conn, addr, port, src_addr, connect_handler);
|
||||
}
|
||||
|
||||
/* Blocking connect.
|
||||
*
|
||||
* NOTE: This is implemented in order to simplify the transition to the abstract
|
||||
* connections, but should probably be refactored out of cluster.c and replication.c,
|
||||
* in favor of a pure async implementation.
|
||||
*/
|
||||
static inline int connBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
|
||||
return conn->type->blocking_connect(conn, addr, port, timeout);
|
||||
}
|
||||
|
||||
/* Write to connection, behaves the same as write(2).
|
||||
*
|
||||
* Like write(2), a short write is possible. A -1 return indicates an error.
|
||||
*
|
||||
* The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
|
||||
* connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
|
||||
*/
|
||||
static inline int connWrite(connection *conn, const void *data, size_t data_len) {
|
||||
return conn->type->write(conn, data, data_len);
|
||||
}
|
||||
|
||||
/* Read from the connection, behaves the same as read(2).
|
||||
*
|
||||
* Like read(2), a short read is possible. A return value of 0 will indicate the
|
||||
* connection was closed, and -1 will indicate an error.
|
||||
*
|
||||
* The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
|
||||
* connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
|
||||
*/
|
||||
static inline int connRead(connection *conn, void *buf, size_t buf_len) {
|
||||
return conn->type->read(conn, buf, buf_len);
|
||||
}
|
||||
|
||||
/* Register a write handler, to be called when the connection is writable.
|
||||
* If NULL, the existing handler is removed.
|
||||
*/
|
||||
static inline int connSetWriteHandler(connection *conn, ConnectionCallbackFunc func) {
|
||||
return conn->type->set_write_handler(conn, func, 0);
|
||||
}
|
||||
|
||||
/* Register a read handler, to be called when the connection is readable.
|
||||
* If NULL, the existing handler is removed.
|
||||
*/
|
||||
static inline int connSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
|
||||
return conn->type->set_read_handler(conn, func);
|
||||
}
|
||||
|
||||
/* Set a write handler, and possibly enable a write barrier, this flag is
|
||||
* cleared when write handler is changed or removed.
|
||||
* With barroer enabled, we never fire the event if the read handler already
|
||||
* fired in the same event loop iteration. Useful when you want to persist
|
||||
* things to disk before sending replies, and want to do that in a group fashion. */
|
||||
static inline int connSetWriteHandlerWithBarrier(connection *conn, ConnectionCallbackFunc func, int barrier) {
|
||||
return conn->type->set_write_handler(conn, func, barrier);
|
||||
}
|
||||
|
||||
static inline void connClose(connection *conn) {
|
||||
conn->type->close(conn);
|
||||
}
|
||||
|
||||
/* Returns the last error encountered by the connection, as a string. If no error,
|
||||
* a NULL is returned.
|
||||
*/
|
||||
static inline const char *connGetLastError(connection *conn) {
|
||||
return conn->type->get_last_error(conn);
|
||||
}
|
||||
|
||||
static inline ssize_t connSyncWrite(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||
return conn->type->sync_write(conn, ptr, size, timeout);
|
||||
}
|
||||
|
||||
static inline ssize_t connSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||
return conn->type->sync_read(conn, ptr, size, timeout);
|
||||
}
|
||||
|
||||
static inline ssize_t connSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||
return conn->type->sync_readline(conn, ptr, size, timeout);
|
||||
}
|
||||
|
||||
connection *connCreateSocket();
|
||||
connection *connCreateAcceptedSocket(int fd);
|
||||
|
||||
connection *connCreateTLS();
|
||||
connection *connCreateAcceptedTLS(int fd, int require_auth);
|
||||
|
||||
void connSetPrivateData(connection *conn, void *data);
|
||||
void *connGetPrivateData(connection *conn);
|
||||
int connGetState(connection *conn);
|
||||
int connHasWriteHandler(connection *conn);
|
||||
int connHasReadHandler(connection *conn);
|
||||
int connGetSocketError(connection *conn);
|
||||
|
||||
/* anet-style wrappers to conns */
|
||||
int connBlock(connection *conn);
|
||||
int connNonBlock(connection *conn);
|
||||
int connEnableTcpNoDelay(connection *conn);
|
||||
int connDisableTcpNoDelay(connection *conn);
|
||||
int connKeepAlive(connection *conn, int interval);
|
||||
int connSendTimeout(connection *conn, long long ms);
|
||||
int connRecvTimeout(connection *conn, long long ms);
|
||||
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port);
|
||||
int connFormatPeer(connection *conn, char *buf, size_t buf_len);
|
||||
int connSockName(connection *conn, char *ip, size_t ip_len, int *port);
|
||||
const char *connGetInfo(connection *conn, char *buf, size_t buf_len);
|
||||
|
||||
/* Helpers for tls special considerations */
|
||||
int tlsHasPendingData();
|
||||
void tlsProcessPendingData();
|
||||
|
||||
#endif /* __REDIS_CONNECTION_H */
|
||||
@@ -1,88 +0,0 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019, Redis Labs
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __REDIS_CONNHELPERS_H
|
||||
#define __REDIS_CONNHELPERS_H
|
||||
|
||||
#include "connection.h"
|
||||
|
||||
/* These are helper functions that are common to different connection
|
||||
* implementations (currently sockets in connection.c and TLS in tls.c).
|
||||
*
|
||||
* Currently helpers implement the mechanisms for invoking connection
|
||||
* handlers and tracking connection references, to allow safe destruction
|
||||
* of connections from within a handler.
|
||||
*/
|
||||
|
||||
/* Incremenet connection references.
|
||||
*
|
||||
* Inside a connection handler, we guarantee refs >= 1 so it is always
|
||||
* safe to connClose().
|
||||
*
|
||||
* In other cases where we don't want to prematurely lose the connection,
|
||||
* it can go beyond 1 as well; currently it is only done by connAccept().
|
||||
*/
|
||||
static inline void connIncrRefs(connection *conn) {
|
||||
conn->refs++;
|
||||
}
|
||||
|
||||
/* Decrement connection references.
|
||||
*
|
||||
* Note that this is not intended to provide any automatic free logic!
|
||||
* callHandler() takes care of that for the common flows, and anywhere an
|
||||
* explicit connIncrRefs() is used, the caller is expected to take care of
|
||||
* that.
|
||||
*/
|
||||
|
||||
static inline void connDecrRefs(connection *conn) {
|
||||
conn->refs--;
|
||||
}
|
||||
|
||||
static inline int connHasRefs(connection *conn) {
|
||||
return conn->refs;
|
||||
}
|
||||
|
||||
/* Helper for connection implementations to call handlers:
|
||||
* 1. Increment refs to protect the connection.
|
||||
* 2. Execute the handler (if set).
|
||||
* 3. Decrement refs and perform deferred close, if refs==0.
|
||||
*/
|
||||
static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) {
|
||||
connIncrRefs(conn);
|
||||
if (handler) handler(conn);
|
||||
connDecrRefs(conn);
|
||||
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
|
||||
if (!connHasRefs(conn)) connClose(conn);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* __REDIS_CONNHELPERS_H */
|
||||
@@ -1,835 +0,0 @@
|
||||
#ifndef _CRC16_TABLE_H__
|
||||
#define _CRC16_TABLE_H__
|
||||
|
||||
/* A table of the shortest possible alphanumeric string that is mapped by redis' crc16
|
||||
* to any given redis cluster slot.
|
||||
*
|
||||
* The array indexes are slot numbers, so that given a desired slot, this string is guaranteed
|
||||
* to make redis cluster route a request to the shard holding this slot
|
||||
*/
|
||||
|
||||
const char *crc16_slot_table[] = {
|
||||
"06S", "Qi", "5L5", "4Iu", "4gY", "460", "1Y7", "1LV", "0QG", "ru", "7Ok", "4ji", "4DE", "65n", "2JH", "I8", "F9", "SX", "7nF", "4KD",
|
||||
"4eh", "6PK", "2ke", "1Ng", "0Sv", "4L", "491", "4hX", "4Ft", "5C4", "2Hy", "09R", "021", "0cX", "4Xv", "6mU", "6Cy", "42R", "0Mt", "nF",
|
||||
"cv", "1Pe", "5kK", "6NI", "74L", "4UF", "0nh", "MZ", "2TJ", "0ai", "4ZG", "6od", "6AH", "40c", "0OE", "lw", "aG", "0Bu", "5iz", "6Lx",
|
||||
"5R7", "4Ww", "0lY", "Ok", "5n3", "4ks", "8YE", "7g", "2KR", "1nP", "714", "64t", "69D", "4Ho", "07I", "Ps", "2hN", "1ML", "4fC", "7CA",
|
||||
"avs", "4iB", "0Rl", "5V", "2Ic", "08H", "4Gn", "66E", "aUo", "b4e", "05x", "RB", "8f", "8VD", "4dr", "5a2", "4zp", "6OS", "bl", "355",
|
||||
"0or", "1j2", "75V", "bno", "4Yl", "6lO", "Ap", "0bB", "0Ln", "2yM", "6Bc", "43H", "4xA", "6Mb", "22D", "14", "0mC", "Nq", "6cN", "4Vm",
|
||||
"ban", "aDl", "CA", "14Z", "8GG", "mm", "549", "41y", "53t", "464", "1Y3", "1LR", "06W", "Qm", "5L1", "4Iq", "4DA", "65j", "2JL", "1oN",
|
||||
"0QC", "6y", "7Oo", "4jm", "4el", "6PO", "9x", "1Nc", "04f", "2EM", "7nB", "bqs", "4Fp", "5C0", "d6F", "09V", "0Sr", "4H", "495", "bRo",
|
||||
"aio", "42V", "0Mp", "nB", "025", "17u", "4Xr", "6mQ", "74H", "4UB", "0nl", "3Kn", "cr", "1Pa", "5kO", "6NM", "6AL", "40g", "0OA", "ls",
|
||||
"2TN", "0am", "4ZC", "aEr", "5R3", "4Ws", "18t", "Oo", "aC", "0Bq", "bCl", "afn", "2KV", "1nT", "5Uz", "64p", "5n7", "4kw", "0PY", "7c",
|
||||
"2hJ", "1MH", "4fG", "6Sd", "7mi", "4Hk", "07M", "Pw", "2Ig", "08L", "4Gj", "66A", "7LD", "4iF", "0Rh", "5R", "8b", "1Oy", "4dv", "5a6",
|
||||
"7oX", "4JZ", "0qt", "RF", "0ov", "LD", "4A9", "4TX", "4zt", "6OW", "bh", "0AZ", "z9", "oX", "6Bg", "43L", "4Yh", "6lK", "At", "0bF",
|
||||
"0mG", "Nu", "6cJ", "4Vi", "4xE", "6Mf", "2vH", "10", "8GC", "mi", "5p5", "4uu", "5Kx", "4N8", "CE", "1pV", "0QO", "6u", "7Oc", "4ja",
|
||||
"4DM", "65f", "3Za", "I0", "0rS", "Qa", "68V", "b7F", "4gQ", "468", "dSo", "285", "274", "4D", "499", "4hP", "b8G", "67W", "0h3", "09Z",
|
||||
"F1", "SP", "7nN", "4KL", "51I", "6PC", "9t", "1No", "21g", "1Pm", "5kC", "6NA", "74D", "4UN", "X3", "MR", "029", "0cP", "bbM", "79t",
|
||||
"4c3", "42Z", "8Dd", "nN", "aO", "8Ke", "4yS", "4l2", "76u", "635", "0lQ", "Oc", "BS", "W2", "4ZO", "6ol", "7Qa", "40k", "0OM", "2zn",
|
||||
"69L", "4Hg", "07A", "2Fj", "2hF", "k6", "4fK", "6Sh", "7Ny", "6K9", "0PU", "7o", "2KZ", "1nX", "4EW", "4P6", "7oT", "4JV", "05p", "RJ",
|
||||
"8n", "1Ou", "4dz", "6QY", "7LH", "4iJ", "d7", "qV", "2Ik", "1li", "4Gf", "66M", "4Yd", "6lG", "Ax", "0bJ", "z5", "oT", "6Bk", "4wH",
|
||||
"4zx", "aeI", "bd", "0AV", "0oz", "LH", "4A5", "4TT", "5Kt", "4N4", "CI", "14R", "0NW", "me", "541", "41q", "4xI", "6Mj", "22L", "u4",
|
||||
"0mK", "Ny", "6cF", "4Ve", "4DI", "65b", "2JD", "I4", "0QK", "6q", "7Og", "4je", "4gU", "4r4", "2iX", "1LZ", "0rW", "Qe", "5L9", "4Iy",
|
||||
"4Fx", "5C8", "0h7", "1mw", "0Sz", "pH", "7MV", "4hT", "4ed", "6PG", "9p", "1Nk", "F5", "ST", "7nJ", "4KH", "7pH", "4UJ", "X7", "MV",
|
||||
"cz", "1Pi", "5kG", "6NE", "4c7", "4vV", "0Mx", "nJ", "0v5", "0cT", "4Xz", "6mY", "6bX", "5GZ", "0lU", "Og", "aK", "0By", "4yW", "4l6",
|
||||
"6AD", "40o", "0OI", "2zj", "BW", "W6", "4ZK", "6oh", "2hB", "k2", "4fO", "6Sl", "69H", "4Hc", "07E", "2Fn", "d5e", "83m", "4ES", "4P2",
|
||||
"a0F", "bQL", "0PQ", "7k", "8j", "1Oq", "50W", "hbv", "7oP", "4JR", "05t", "RN", "2Io", "08D", "4Gb", "66I", "7LL", "4iN", "d3", "5Z",
|
||||
"z1", "oP", "6Bo", "43D", "5IA", "6lC", "2Wm", "0bN", "8ff", "LL", "4A1", "4TP", "cPn", "aeM", "0T3", "0AR", "0NS", "ma", "545", "41u",
|
||||
"5Kp", "4N0", "CM", "14V", "0mO", "2Xl", "6cB", "4Va", "4xM", "6Mn", "22H", "18", "04s", "SI", "7nW", "4KU", "4ey", "6PZ", "9m", "1Nv",
|
||||
"e4", "pU", "7MK", "4hI", "4Fe", "67N", "2Hh", "09C", "06B", "Qx", "68O", "4Id", "4gH", "6Rk", "2iE", "j5", "0QV", "6l", "5o8", "4jx",
|
||||
"4DT", "4Q5", "2JY", "82j", "BJ", "0ax", "4ZV", "4O7", "552", "40r", "0OT", "lf", "aV", "t7", "4yJ", "6Li", "6bE", "4Wf", "0lH", "Oz",
|
||||
"2Vj", "0cI", "4Xg", "6mD", "6Ch", "42C", "0Me", "nW", "cg", "1Pt", "5kZ", "6NX", "7pU", "4UW", "0ny", "MK", "7LQ", "4iS", "267", "5G",
|
||||
"0i0", "08Y", "b9D", "66T", "7oM", "4JO", "G2", "RS", "8w", "1Ol", "4dc", "7Aa", "atS", "4kb", "0PL", "7v", "2KC", "H3", "4EN", "64e",
|
||||
"69U", "b6E", "07X", "Pb", "dRl", "296", "4fR", "4s3", "4xP", "4m1", "22U", "8Jf", "0mR", "0x3", "77v", "626", "5Km", "6no", "CP", "V1",
|
||||
"0NN", "3kL", "7Pb", "41h", "4za", "6OB", "20d", "0AO", "Y0", "LQ", "6an", "4TM", "bcN", "78w", "Aa", "0bS", "8Eg", "oM", "4b0", "43Y",
|
||||
"51T", "azL", "9i", "1Nr", "04w", "SM", "7nS", "4KQ", "4Fa", "67J", "2Hl", "09G", "e0", "4Y", "7MO", "4hM", "4gL", "6Ro", "2iA", "j1",
|
||||
"06F", "2Gm", "68K", "5YA", "4DP", "4Q1", "d4f", "82n", "0QR", "6h", "a1E", "bPO", "556", "40v", "0OP", "lb", "BN", "15U", "4ZR", "4O3",
|
||||
"6bA", "4Wb", "0lL", "2Yo", "aR", "t3", "4yN", "6Lm", "6Cl", "42G", "0Ma", "nS", "2Vn", "0cM", "4Xc", "79i", "74Y", "4US", "8ge", "MO",
|
||||
"cc", "1Pp", "bAL", "adN", "0i4", "1lt", "5WZ", "66P", "7LU", "4iW", "0Ry", "5C", "8s", "1Oh", "4dg", "6QD", "7oI", "4JK", "G6", "RW",
|
||||
"2KG", "H7", "4EJ", "64a", "7Nd", "4kf", "0PH", "7r", "1X8", "1MY", "4fV", "4s7", "69Q", "4Hz", "0sT", "Pf", "0mV", "Nd", "5S8", "4Vx",
|
||||
"4xT", "4m5", "22Q", "0Cz", "0NJ", "mx", "7Pf", "41l", "5Ki", "6nk", "CT", "V5", "Y4", "LU", "6aj", "4TI", "4ze", "6OF", "by", "0AK",
|
||||
"2l9", "oI", "4b4", "4wU", "4Yy", "6lZ", "Ae", "0bW", "0So", "4U", "7MC", "4hA", "4Fm", "67F", "3XA", "09K", "0ps", "SA", "aTl", "b5f",
|
||||
"4eq", "6PR", "9e", "8WG", "8XF", "6d", "5o0", "4jp", "707", "65w", "1z2", "1oS", "06J", "Qp", "68G", "4Il", "53i", "6Rc", "2iM", "1LO",
|
||||
"23G", "07", "4yB", "6La", "6bM", "4Wn", "18i", "Or", "BB", "0ap", "c4D", "aEo", "5q2", "40z", "8FD", "ln", "co", "346", "5kR", "6NP",
|
||||
"74U", "bol", "0nq", "MC", "2Vb", "0cA", "4Xo", "6mL", "7SA", "42K", "0Mm", "2xN", "7oE", "4JG", "05a", "2DJ", "2jf", "1Od", "4dk", "6QH",
|
||||
"482", "5yz", "0Ru", "5O", "0i8", "08Q", "4Gw", "5B7", "5M6", "4Hv", "07P", "Pj", "1X4", "1MU", "4fZ", "473", "7Nh", "4kj", "0PD", "sv",
|
||||
"2KK", "1nI", "4EF", "64m", "5Ke", "6ng", "CX", "V9", "0NF", "mt", "7Pj", "4uh", "4xX", "4m9", "1F6", "0Cv", "0mZ", "Nh", "5S4", "4Vt",
|
||||
"4Yu", "6lV", "Ai", "16r", "0Lw", "oE", "4b8", "43Q", "4zi", "6OJ", "bu", "0AG", "Y8", "LY", "6af", "4TE", "4Fi", "67B", "2Hd", "09O",
|
||||
"e8", "4Q", "7MG", "4hE", "4eu", "6PV", "9a", "1Nz", "0pw", "SE", "aTh", "4KY", "4DX", "4Q9", "1z6", "1oW", "0QZ", "rh", "5o4", "4jt",
|
||||
"4gD", "6Rg", "2iI", "j9", "06N", "Qt", "68C", "4Ih", "6bI", "4Wj", "0lD", "Ov", "aZ", "03", "4yF", "6Le", "5q6", "4tv", "0OX", "lj",
|
||||
"BF", "0at", "4ZZ", "6oy", "74Q", "5Ez", "0nu", "MG", "ck", "1Px", "5kV", "6NT", "6Cd", "42O", "0Mi", "2xJ", "2Vf", "0cE", "4Xk", "6mH",
|
||||
"2jb", "8VY", "4do", "6QL", "7oA", "4JC", "05e", "2DN", "d7E", "08U", "4Gs", "5B3", "486", "bSl", "0Rq", "5K", "1X0", "1MQ", "52w", "477",
|
||||
"5M2", "4Hr", "07T", "Pn", "2KO", "1nM", "4EB", "64i", "7Nl", "4kn", "8YX", "7z", "0NB", "mp", "7Pn", "41d", "5Ka", "6nc", "2UM", "14G",
|
||||
"19w", "Nl", "5S0", "4Vp", "bBo", "agm", "1F2", "0Cr", "0Ls", "oA", "ahl", "43U", "4Yq", "6lR", "Am", "16v", "0oo", "2ZL", "6ab", "4TA",
|
||||
"4zm", "6ON", "bq", "0AC", "2VY", "0cz", "4XT", "4M5", "570", "42p", "0MV", "nd", "cT", "v5", "5ki", "6Nk", "74n", "4Ud", "0nJ", "Mx",
|
||||
"By", "0aK", "4Ze", "6oF", "6Aj", "40A", "y4", "lU", "ae", "0BW", "4yy", "581", "4B4", "4WU", "18R", "OI", "06q", "QK", "7lU", "4IW",
|
||||
"53R", "6RX", "0I4", "1Lt", "g6", "rW", "7OI", "4jK", "4Dg", "65L", "2Jj", "1oh", "0pH", "Sz", "7nd", "4Kf", "4eJ", "6Pi", "2kG", "h7",
|
||||
"0ST", "4n", "7Mx", "4hz", "4FV", "4S7", "1x8", "09p", "4zR", "4o3", "bN", "8Hd", "0oP", "Lb", "75t", "604", "4YN", "6lm", "AR", "T3",
|
||||
"0LL", "2yo", "6BA", "43j", "4xc", "agR", "22f", "0CM", "0ma", "NS", "6cl", "4VO", "baL", "aDN", "Cc", "14x", "8Ge", "mO", "7PQ", "4uS",
|
||||
"7NS", "4kQ", "245", "7E", "0k2", "1nr", "coo", "64V", "69f", "4HM", "E0", "PQ", "2hl", "1Mn", "4fa", "6SB", "7Lb", "5yA", "0RN", "5t",
|
||||
"2IA", "J1", "4GL", "66g", "aUM", "b4G", "05Z", "0d3", "8D", "8Vf", "4dP", "459", "574", "42t", "0MR", "0X3", "dln", "17W", "4XP", "4M1",
|
||||
"74j", "5EA", "0nN", "3KL", "cP", "29", "5km", "6No", "6An", "40E", "y0", "lQ", "2Tl", "0aO", "4Za", "6oB", "4B0", "4WQ", "18V", "OM",
|
||||
"aa", "0BS", "bCN", "585", "53V", "axN", "0I0", "1Lp", "06u", "QO", "68x", "4IS", "4Dc", "65H", "2Jn", "1ol", "g2", "rS", "7OM", "4jO",
|
||||
"4eN", "6Pm", "9Z", "h3", "04D", "2Eo", "aTS", "4Kb", "4FR", "4S3", "d6d", "09t", "0SP", "4j", "a3G", "bRM", "0oT", "Lf", "6aY", "4Tz",
|
||||
"4zV", "4o7", "bJ", "0Ax", "0LH", "oz", "6BE", "43n", "4YJ", "6li", "AV", "T7", "0me", "NW", "6ch", "4VK", "4xg", "6MD", "22b", "0CI",
|
||||
"0Ny", "mK", "7PU", "4uW", "5KZ", "6nX", "Cg", "1pt", "0k6", "1nv", "4Ey", "64R", "7NW", "4kU", "241", "7A", "2hh", "1Mj", "4fe", "6SF",
|
||||
"69b", "4HI", "E4", "PU", "2IE", "J5", "4GH", "66c", "7Lf", "4id", "0RJ", "5p", "2jY", "8Vb", "4dT", "4q5", "5O8", "4Jx", "0qV", "Rd",
|
||||
"21E", "25", "5ka", "6Nc", "74f", "4Ul", "0nB", "Mp", "1f2", "0cr", "bbo", "79V", "578", "42x", "395", "nl", "am", "364", "4yq", "589",
|
||||
"76W", "bmn", "0ls", "OA", "Bq", "0aC", "4Zm", "6oN", "6Ab", "40I", "0Oo", "2zL", "0Qm", "6W", "7OA", "4jC", "4Do", "65D", "2Jb", "82Q",
|
||||
"06y", "QC", "68t", "b7d", "4gs", "5b3", "dSM", "8UE", "8ZD", "4f", "5m2", "4hr", "725", "67u", "1x0", "09x", "04H", "Sr", "7nl", "4Kn",
|
||||
"4eB", "6Pa", "9V", "1NM", "4YF", "6le", "AZ", "0bh", "0LD", "ov", "6BI", "43b", "4zZ", "6Oy", "bF", "0At", "0oX", "Lj", "5Q6", "4Tv",
|
||||
"5KV", "6nT", "Ck", "14p", "0Nu", "mG", "7PY", "41S", "4xk", "6MH", "22n", "0CE", "0mi", "2XJ", "6cd", "4VG", "69n", "4HE", "E8", "PY",
|
||||
"2hd", "1Mf", "4fi", "6SJ", "ath", "4kY", "0Pw", "7M", "2Kx", "1nz", "4Eu", "6pV", "5O4", "4Jt", "05R", "Rh", "8L", "1OW", "4dX", "451",
|
||||
"7Lj", "4ih", "0RF", "qt", "2II", "J9", "4GD", "66o", "74b", "4Uh", "0nF", "Mt", "cX", "21", "5ke", "6Ng", "5s4", "4vt", "0MZ", "nh",
|
||||
"1f6", "0cv", "4XX", "4M9", "4B8", "4WY", "0lw", "OE", "ai", "1Rz", "4yu", "6LV", "6Af", "40M", "y8", "lY", "Bu", "0aG", "4Zi", "6oJ",
|
||||
"4Dk", "6qH", "2Jf", "1od", "0Qi", "6S", "7OE", "4jG", "4gw", "5b7", "0I8", "1Lx", "0ru", "QG", "68p", "5Yz", "4FZ", "67q", "1x4", "1mU",
|
||||
"0SX", "4b", "5m6", "4hv", "4eF", "6Pe", "9R", "1NI", "04L", "Sv", "7nh", "4Kj", "8EX", "or", "6BM", "43f", "4YB", "6la", "2WO", "0bl",
|
||||
"8fD", "Ln", "5Q2", "4Tr", "cPL", "aeo", "bB", "0Ap", "0Nq", "mC", "ajn", "41W", "5KR", "6nP", "Co", "14t", "0mm", "2XN", "77I", "4VC",
|
||||
"4xo", "6ML", "22j", "0CA", "3xA", "1Mb", "4fm", "6SN", "69j", "4HA", "07g", "2FL", "d5G", "83O", "4Eq", "64Z", "a0d", "bQn", "0Ps", "7I",
|
||||
"8H", "1OS", "50u", "455", "5O0", "4Jp", "05V", "Rl", "2IM", "08f", "5Wa", "66k", "7Ln", "4il", "0RB", "5x", "Bh", "0aZ", "4Zt", "6oW",
|
||||
"4a9", "40P", "0Ov", "lD", "at", "0BF", "4yh", "6LK", "6bg", "4WD", "Z9", "OX", "2VH", "U8", "4XE", "6mf", "6CJ", "42a", "0MG", "nu",
|
||||
"cE", "1PV", "5kx", "4n8", "5P5", "4Uu", "8gC", "Mi", "04Q", "Sk", "5N7", "4Kw", "51r", "442", "9O", "1NT", "0SE", "pw", "7Mi", "4hk",
|
||||
"4FG", "67l", "2HJ", "09a", "3", "QZ", "68m", "4IF", "4gj", "6RI", "2ig", "1Le", "0Qt", "6N", "7OX", "4jZ", "4Dv", "5A6", "0j9", "1oy",
|
||||
"4xr", "6MQ", "22w", "377", "0mp", "NB", "77T", "blm", "5KO", "6nM", "Cr", "14i", "0Nl", "3kn", "ajs", "41J", "4zC", "aer", "20F", "36",
|
||||
"0oA", "Ls", "6aL", "4To", "bcl", "78U", "AC", "0bq", "386", "oo", "5r3", "4ws", "5l1", "4iq", "9Kf", "5e", "1y3", "1lR", "736", "66v",
|
||||
"7oo", "4Jm", "05K", "Rq", "8U", "1ON", "4dA", "6Qb", "7NB", "bQs", "0Pn", "7T", "2Ka", "1nc", "4El", "64G", "69w", "b6g", "07z", "1v2",
|
||||
"dRN", "8TF", "4fp", "5c0", "akm", "40T", "0Or", "1J2", "Bl", "15w", "4Zp", "6oS", "6bc", "5Ga", "0ln", "2YM", "ap", "0BB", "4yl", "6LO",
|
||||
"6CN", "42e", "0MC", "nq", "2VL", "0co", "4XA", "6mb", "5P1", "4Uq", "8gG", "Mm", "cA", "1PR", "bAn", "adl", "51v", "446", "9K", "1NP",
|
||||
"04U", "So", "5N3", "4Ks", "4FC", "67h", "2HN", "09e", "0SA", "ps", "7Mm", "4ho", "4gn", "6RM", "2ic", "1La", "7", "2GO", "68i", "4IB",
|
||||
"4Dr", "5A2", "d4D", "82L", "0Qp", "6J", "a1g", "bPm", "0mt", "NF", "6cy", "4VZ", "4xv", "6MU", "0V9", "0CX", "0Nh", "mZ", "7PD", "41N",
|
||||
"5KK", "6nI", "Cv", "14m", "0oE", "Lw", "6aH", "4Tk", "4zG", "6Od", "20B", "32", "0LY", "ok", "5r7", "4ww", "5Iz", "6lx", "AG", "0bu",
|
||||
"1y7", "1lV", "4GY", "4R8", "5l5", "4iu", "1Bz", "5a", "8Q", "i8", "4dE", "6Qf", "7ok", "4Ji", "05O", "Ru", "2Ke", "1ng", "4Eh", "64C",
|
||||
"7NF", "4kD", "f9", "7P", "2hy", "3m9", "4ft", "5c4", "69s", "4HX", "0sv", "PD", "23e", "0BN", "5iA", "6LC", "6bo", "4WL", "Z1", "OP",
|
||||
"0t3", "0aR", "c4f", "aEM", "4a1", "40X", "8Ff", "lL", "cM", "8Ig", "5kp", "4n0", "74w", "617", "0nS", "Ma", "3Fa", "U0", "4XM", "6mn",
|
||||
"6CB", "42i", "0MO", "2xl", "0SM", "4w", "7Ma", "4hc", "4FO", "67d", "2HB", "K2", "04Y", "Sc", "aTN", "b5D", "4eS", "4p2", "9G", "8We",
|
||||
"256", "6F", "7OP", "4jR", "cnl", "65U", "0j1", "1oq", "D3", "QR", "68e", "4IN", "4gb", "6RA", "2io", "1Lm", "5KG", "6nE", "Cz", "14a",
|
||||
"x7", "mV", "7PH", "41B", "4xz", "592", "0V5", "0CT", "0mx", "NJ", "4C7", "4VV", "4YW", "4L6", "AK", "0by", "0LU", "og", "563", "43s",
|
||||
"4zK", "6Oh", "bW", "w6", "0oI", "2Zj", "6aD", "4Tg", "7og", "4Je", "05C", "Ry", "2jD", "i4", "4dI", "6Qj", "5l9", "4iy", "0RW", "5m",
|
||||
"2IX", "08s", "4GU", "4R4", "7mV", "4HT", "07r", "PH", "0H7", "1Mw", "4fx", "5c8", "7NJ", "4kH", "f5", "sT", "2Ki", "1nk", "4Ed", "64O",
|
||||
"6bk", "4WH", "Z5", "OT", "ax", "0BJ", "4yd", "6LG", "4a5", "4tT", "0Oz", "lH", "Bd", "0aV", "4Zx", "aEI", "5P9", "4Uy", "0nW", "Me",
|
||||
"cI", "1PZ", "5kt", "4n4", "6CF", "42m", "0MK", "ny", "2VD", "U4", "4XI", "6mj", "4FK", "6sh", "2HF", "K6", "0SI", "4s", "7Me", "4hg",
|
||||
"4eW", "4p6", "9C", "1NX", "0pU", "Sg", "7ny", "6k9", "4Dz", "65Q", "0j5", "1ou", "0Qx", "6B", "7OT", "4jV", "4gf", "6RE", "2ik", "1Li",
|
||||
"D7", "QV", "68a", "4IJ", "x3", "mR", "7PL", "41F", "5KC", "6nA", "2Uo", "14e", "19U", "NN", "4C3", "4VR", "bBM", "596", "0V1", "0CP",
|
||||
"0LQ", "oc", "567", "43w", "4YS", "4L2", "AO", "16T", "0oM", "2Zn", "75i", "4Tc", "4zO", "6Ol", "bS", "w2", "8Y", "i0", "4dM", "6Qn",
|
||||
"7oc", "4Ja", "05G", "2Dl", "d7g", "08w", "4GQ", "4R0", "a2D", "bSN", "0RS", "5i", "0H3", "1Ms", "52U", "ayM", "7mR", "4HP", "07v", "PL",
|
||||
"2Km", "1no", "5UA", "64K", "7NN", "4kL", "f1", "7X", "5nw", "4k7", "fJ", "0Ex", "0kT", "Hf", "6eY", "4Pz", "5Mk", "6hi", "EV", "P7",
|
||||
"0HH", "kz", "6FE", "47n", "48o", "6ID", "26b", "0GI", "0ie", "JW", "6gh", "4RK", "5OZ", "6jX", "Gg", "0dU", "0Jy", "iK", "4d6", "4qW",
|
||||
"4z4", "4oU", "1DZ", "3A", "Ye", "0zW", "4Ay", "5D9", "6yj", "4LI", "A4", "TU", "zy", "0YK", "4be", "6WF", "6XG", "4md", "0VJ", "1p",
|
||||
"2ME", "N5", "4CH", "62c", "5K8", "4Nx", "0uV", "Vd", "xH", "8Rb", "5pu", "4u5", "D", "13W", "5Lq", "4I1", "534", "46t", "0IR", "28y",
|
||||
"gP", "69", "5om", "6Jo", "6dC", "5AA", "0jN", "3OL", "2Pl", "0eO", "aT1", "6kB", "6En", "44E", "98", "hQ", "ea", "0FS", "49u", "abL",
|
||||
"4F0", "4SQ", "8ag", "KM", "02u", "UO", "4X2", "4MS", "57V", "a8F", "0M0", "0XQ", "c2", "vS", "7KM", "4nO", "5PB", "61H", "2Nn", "1kl",
|
||||
"00D", "2Ao", "6zA", "4Ob", "4aN", "6Tm", "yR", "l3", "0WP", "0j", "a7G", "58W", "4BR", "4W3", "ZN", "84l", "0kP", "Hb", "71t", "644",
|
||||
"5ns", "4k3", "fN", "8Ld", "0HL", "29g", "6FA", "47j", "5Mo", "6hm", "ER", "P3", "0ia", "JS", "6gl", "4RO", "48k", "7Ya", "26f", "0GM",
|
||||
"8Ce", "iO", "4d2", "4qS", "beL", "hYw", "Gc", "0dQ", "Ya", "0zS", "cko", "60V", "4z0", "4oQ", "205", "3E", "2ll", "0YO", "4ba", "6WB",
|
||||
"6yn", "4LM", "A0", "TQ", "2MA", "N1", "4CL", "62g", "6XC", "59I", "0VN", "1t", "xL", "8Rf", "54y", "419", "aQM", "b0G", "01Z", "3PP",
|
||||
"530", "46p", "0IV", "jd", "DH", "0gz", "5Lu", "4I5", "6dG", "4Qd", "0jJ", "Ix", "gT", "r5", "5oi", "6Jk", "6Ej", "44A", "0Kg", "hU",
|
||||
"Fy", "0eK", "5ND", "6kF", "4F4", "4SU", "1xZ", "KI", "ee", "0FW", "49q", "5x9", "57R", "6VX", "0M4", "0XU", "02q", "UK", "4X6", "4MW",
|
||||
"5PF", "61L", "2Nj", "1kh", "c6", "vW", "7KI", "4nK", "4aJ", "6Ti", "yV", "l7", "0tH", "Wz", "6zE", "4Of", "4BV", "4W7", "ZJ", "0yx",
|
||||
"0WT", "0n", "6YY", "4lz", "5Mc", "6ha", "2SO", "0fl", "1Xa", "kr", "6FM", "47f", "bDm", "aao", "fB", "0Ep", "8bD", "Hn", "5U2", "4Pr",
|
||||
"5OR", "5Z3", "Go", "10t", "0Jq", "iC", "ann", "45W", "48g", "6IL", "ds", "0GA", "0im", "3Lo", "73I", "4RC", "6yb", "4LA", "03g", "2BL",
|
||||
"zq", "0YC", "4bm", "6WN", "a4d", "bUn", "0Ts", "3I", "Ym", "87O", "4Aq", "5D1", "5K0", "4Np", "01V", "Vl", "2nQ", "1KS", "54u", "415",
|
||||
"6XO", "4ml", "0VB", "1x", "2MM", "0xn", "5Sa", "62k", "gX", "61", "5oe", "6Jg", "6dK", "4Qh", "0jF", "It", "L", "0gv", "5Ly", "4I9",
|
||||
"5w4", "4rt", "0IZ", "jh", "ei", "1Vz", "5mT", "5x5", "4F8", "4SY", "0hw", "KE", "Fu", "0eG", "5NH", "6kJ", "6Ef", "44M", "90", "hY",
|
||||
"0Ui", "2S", "7KE", "4nG", "5PJ", "6uH", "Xw", "1kd", "0vu", "UG", "6xx", "790", "4cw", "5f7", "0M8", "0XY", "0WX", "0b", "5i6", "4lv",
|
||||
"4BZ", "63q", "ZF", "0yt", "00L", "Wv", "6zI", "4Oj", "4aF", "6Te", "yZ", "0Zh", "0HD", "kv", "6FI", "47b", "5Mg", "6he", "EZ", "0fh",
|
||||
"0kX", "Hj", "5U6", "4Pv", "7N9", "6Ky", "fF", "0Et", "0Ju", "iG", "6Dx", "45S", "5OV", "5Z7", "Gk", "0dY", "0ii", "3Lk", "6gd", "4RG",
|
||||
"48c", "6IH", "dw", "0GE", "zu", "0YG", "4bi", "6WJ", "6yf", "4LE", "A8", "TY", "Yi", "1jz", "4Au", "5D5", "4z8", "4oY", "0Tw", "3M",
|
||||
"xD", "1KW", "54q", "411", "5K4", "4Nt", "01R", "Vh", "2MI", "N9", "4CD", "62o", "6XK", "4mh", "0VF", "ut", "6dO", "4Ql", "0jB", "Ip",
|
||||
"25E", "65", "5oa", "6Jc", "538", "46x", "9Pg", "jl", "H", "0gr", "bfo", "aCm", "72W", "bin", "0hs", "KA", "em", "324", "49y", "5x1",
|
||||
"6Eb", "44I", "94", "3nm", "Fq", "0eC", "5NL", "6kN", "5PN", "61D", "Xs", "86Q", "0Um", "2W", "7KA", "4nC", "4cs", "5f3", "39W", "8QE",
|
||||
"02y", "UC", "aRn", "794", "765", "63u", "ZB", "0yp", "9Ne", "0f", "5i2", "4lr", "4aB", "6Ta", "2oO", "0Zl", "00H", "Wr", "6zM", "4On",
|
||||
"5lW", "5y6", "dj", "0GX", "0it", "JF", "6gy", "4RZ", "5OK", "6jI", "Gv", "0dD", "83", "iZ", "6De", "45N", "5nf", "6Kd", "24B", "72",
|
||||
"0kE", "Hw", "6eH", "4Pk", "5Mz", "6hx", "EG", "0fu", "0HY", "kk", "5v7", "4sw", "5h5", "4mu", "1Fz", "1a", "2MT", "0xw", "4CY", "4V8",
|
||||
"7kk", "4Ni", "01O", "Vu", "xY", "m8", "54l", "6Uf", "6Zg", "4oD", "b9", "3P", "Yt", "0zF", "4Ah", "60C", "4Y9", "4LX", "0wv", "TD",
|
||||
"zh", "0YZ", "4bt", "5g4", "Fl", "11w", "5NQ", "6kS", "aom", "44T", "0Kr", "1N2", "ep", "0FB", "49d", "6HO", "6fc", "5Ca", "0hn", "3Ml",
|
||||
"U", "0go", "bfr", "6ib", "6GN", "46e", "0IC", "jq", "gA", "0Ds", "bEn", "hyU", "5T1", "4Qq", "8cG", "Im", "00U", "Wo", "5J3", "4Os",
|
||||
"55v", "406", "yC", "0Zq", "0WA", "ts", "6YL", "4lo", "4BC", "63h", "2LN", "0ym", "02d", "2CO", "6xa", "4MB", "4cn", "6VM", "2mc", "1Ha",
|
||||
"0Up", "2J", "a5g", "bTm", "5PS", "5E2", "Xn", "86L", "0ip", "JB", "73T", "bhm", "48z", "5y2", "dn", "337", "87", "3on", "6Da", "45J",
|
||||
"5OO", "6jM", "Gr", "10i", "0kA", "Hs", "6eL", "4Po", "5nb", "aar", "24F", "76", "8AE", "ko", "5v3", "4ss", "bgl", "aBn", "EC", "0fq",
|
||||
"2MP", "0xs", "776", "62v", "5h1", "4mq", "9Of", "1e", "2nL", "1KN", "54h", "6Ub", "7ko", "4Nm", "01K", "Vq", "Yp", "0zB", "4Al", "60G",
|
||||
"6Zc", "bUs", "0Tn", "3T", "zl", "8PF", "4bp", "5g0", "aSm", "787", "03z", "1r2", "4e9", "44P", "0Kv", "hD", "Fh", "0eZ", "5NU", "6kW",
|
||||
"6fg", "4SD", "0hj", "KX", "et", "0FF", "5mI", "6HK", "6GJ", "46a", "0IG", "ju", "Q", "Q8", "5Ld", "6if", "5T5", "4Qu", "1zz", "Ii",
|
||||
"gE", "0Dw", "5ox", "4j8", "55r", "402", "yG", "0Zu", "00Q", "Wk", "5J7", "4Ow", "4BG", "63l", "2LJ", "0yi", "0WE", "tw", "6YH", "4lk",
|
||||
"4cj", "6VI", "2mg", "0XD", "0vh", "UZ", "6xe", "4MF", "5PW", "5E6", "Xj", "1ky", "0Ut", "2N", "7KX", "4nZ", "5OC", "6jA", "2Qo", "0dL",
|
||||
"1ZA", "iR", "6Dm", "45F", "48v", "acO", "db", "0GP", "94M", "JN", "4G3", "4RR", "5Mr", "4H2", "EO", "12T", "0HQ", "kc", "527", "47w",
|
||||
"5nn", "6Kl", "fS", "s2", "0kM", "3NO", "71i", "4Pc", "7kc", "4Na", "01G", "3PM", "xQ", "m0", "54d", "6Un", "a6D", "59T", "0VS", "1i",
|
||||
"197", "85o", "4CQ", "4V0", "4Y1", "4LP", "03v", "TL", "0L3", "0YR", "56U", "a9E", "6Zo", "4oL", "b1", "3X", "2Om", "0zN", "5QA", "60K",
|
||||
"ex", "0FJ", "49l", "6HG", "6fk", "4SH", "0hf", "KT", "Fd", "0eV", "5NY", "aAI", "4e5", "4pT", "0Kz", "hH", "gI", "1TZ", "5ot", "4j4",
|
||||
"5T9", "4Qy", "0jW", "Ie", "DU", "Q4", "5Lh", "6ij", "6GF", "46m", "0IK", "jy", "0WI", "0s", "6YD", "4lg", "4BK", "6wh", "ZW", "O6",
|
||||
"0tU", "Wg", "6zX", "6o9", "4aW", "4t6", "yK", "0Zy", "0Ux", "2B", "7KT", "4nV", "bzI", "61Q", "Xf", "1ku", "02l", "UV", "6xi", "4MJ",
|
||||
"4cf", "6VE", "2mk", "0XH", "0Jd", "iV", "6Di", "45B", "5OG", "6jE", "Gz", "0dH", "0ix", "JJ", "4G7", "4RV", "48r", "6IY", "df", "0GT",
|
||||
"0HU", "kg", "523", "47s", "5Mv", "4H6", "EK", "0fy", "0kI", "3NK", "6eD", "4Pg", "5nj", "6Kh", "fW", "s6", "xU", "m4", "5ph", "6Uj",
|
||||
"7kg", "4Ne", "01C", "Vy", "193", "1hZ", "4CU", "4V4", "5h9", "4my", "0VW", "1m", "zd", "0YV", "4bx", "5g8", "4Y5", "4LT", "03r", "TH",
|
||||
"Yx", "0zJ", "4Ad", "60O", "6Zk", "4oH", "b5", "wT", "6fo", "4SL", "0hb", "KP", "27e", "0FN", "49h", "6HC", "4e1", "44X", "8Bf", "hL",
|
||||
"0p3", "0eR", "bdO", "aAM", "70w", "657", "0jS", "Ia", "gM", "8Mg", "5op", "4j0", "6GB", "46i", "0IO", "28d", "Y", "Q0", "5Ll", "6in",
|
||||
"4BO", "63d", "ZS", "O2", "0WM", "0w", "7Ia", "4lc", "4aS", "4t2", "yO", "8Se", "00Y", "Wc", "aPN", "b1D", "bzM", "61U", "Xb", "1kq",
|
||||
"216", "2F", "7KP", "4nR", "4cb", "6VA", "2mo", "0XL", "02h", "UR", "6xm", "4MN", "5j7", "4ow", "0TY", "3c", "YG", "0zu", "5Qz", "60p",
|
||||
"6yH", "4Lk", "03M", "Tw", "2lJ", "0Yi", "4bG", "6Wd", "6Xe", "4mF", "0Vh", "1R", "2Mg", "0xD", "4Cj", "62A", "7kX", "4NZ", "0ut", "VF",
|
||||
"xj", "1Ky", "5pW", "5e6", "5nU", "6KW", "fh", "0EZ", "0kv", "HD", "4E9", "4PX", "5MI", "6hK", "Et", "0fF", "0Hj", "kX", "6Fg", "47L",
|
||||
"48M", "6If", "dY", "50", "0iG", "Ju", "6gJ", "4Ri", "5Ox", "4J8", "GE", "0dw", "1Zz", "ii", "5t5", "4qu", "02W", "Um", "5H1", "4Mq",
|
||||
"57t", "424", "2mP", "0Xs", "0UC", "2y", "7Ko", "4nm", "bzr", "61j", "2NL", "1kN", "00f", "2AM", "6zc", "bus", "4al", "6TO", "yp", "0ZB",
|
||||
"0Wr", "0H", "a7e", "58u", "4Bp", "5G0", "Zl", "84N", "f", "13u", "5LS", "5Y2", "amo", "46V", "0Ip", "jB", "gr", "1Ta", "5oO", "6JM",
|
||||
"6da", "4QB", "0jl", "3On", "2PN", "0em", "5Nb", "aAr", "6EL", "44g", "0KA", "hs", "eC", "0Fq", "49W", "abn", "5V3", "4Ss", "8aE", "Ko",
|
||||
"YC", "0zq", "754", "60t", "5j3", "4os", "9Md", "3g", "2lN", "0Ym", "4bC", "7GA", "6yL", "4Lo", "03I", "Ts", "2Mc", "1ha", "4Cn", "62E",
|
||||
"6Xa", "4mB", "0Vl", "1V", "xn", "8RD", "5pS", "5e2", "aQo", "b0e", "01x", "VB", "0kr", "1n2", "71V", "bjo", "5nQ", "6KS", "fl", "315",
|
||||
"0Hn", "29E", "6Fc", "47H", "5MM", "6hO", "Ep", "0fB", "0iC", "Jq", "6gN", "4Rm", "48I", "6Ib", "26D", "54", "8CG", "im", "509", "45y",
|
||||
"ben", "hYU", "GA", "0ds", "4cY", "420", "2mT", "0Xw", "02S", "Ui", "5H5", "4Mu", "5Pd", "61n", "XY", "M8", "0UG", "vu", "7Kk", "4ni",
|
||||
"4ah", "6TK", "yt", "0ZF", "B9", "WX", "6zg", "4OD", "4Bt", "5G4", "Zh", "0yZ", "0Wv", "0L", "4y9", "4lX", "6Gy", "46R", "0It", "jF",
|
||||
"b", "0gX", "5LW", "5Y6", "6de", "4QF", "0jh", "IZ", "gv", "0DD", "5oK", "6JI", "6EH", "44c", "0KE", "hw", "2PJ", "0ei", "5Nf", "6kd",
|
||||
"5V7", "4Sw", "0hY", "Kk", "eG", "0Fu", "49S", "6Hx", "7ia", "4Lc", "03E", "2Bn", "zS", "o2", "4bO", "6Wl", "a4F", "bUL", "0TQ", "3k",
|
||||
"YO", "87m", "4AS", "4T2", "7kP", "4NR", "01t", "VN", "xb", "1Kq", "54W", "hfv", "6Xm", "4mN", "1FA", "1Z", "2Mo", "0xL", "4Cb", "62I",
|
||||
"5MA", "6hC", "2Sm", "0fN", "0Hb", "kP", "6Fo", "47D", "bDO", "aaM", "0P3", "0ER", "8bf", "HL", "4E1", "4PP", "5Op", "4J0", "GM", "10V",
|
||||
"0JS", "ia", "505", "45u", "48E", "6In", "dQ", "58", "0iO", "3LM", "6gB", "4Ra", "0UK", "2q", "7Kg", "4ne", "5Ph", "61b", "XU", "M4",
|
||||
"0vW", "Ue", "5H9", "4My", "4cU", "4v4", "2mX", "1HZ", "0Wz", "tH", "4y5", "4lT", "4Bx", "5G8", "Zd", "0yV", "B5", "WT", "6zk", "4OH",
|
||||
"4ad", "6TG", "yx", "0ZJ", "gz", "0DH", "5oG", "6JE", "6di", "4QJ", "0jd", "IV", "n", "0gT", "680", "6iY", "4g7", "4rV", "0Ix", "jJ",
|
||||
"eK", "0Fy", "5mv", "4h6", "6fX", "5CZ", "0hU", "Kg", "FW", "S6", "5Nj", "6kh", "6ED", "44o", "0KI", "3nK", "zW", "o6", "4bK", "6Wh",
|
||||
"6yD", "4Lg", "03A", "2Bj", "YK", "0zy", "4AW", "4T6", "6ZX", "6O9", "0TU", "3o", "xf", "1Ku", "54S", "6UY", "7kT", "4NV", "01p", "VJ",
|
||||
"2Mk", "0xH", "4Cf", "62M", "6Xi", "4mJ", "0Vd", "uV", "0Hf", "kT", "6Fk", "4sH", "5ME", "6hG", "Ex", "0fJ", "0kz", "HH", "4E5", "4PT",
|
||||
"5nY", "aaI", "fd", "0EV", "0JW", "ie", "501", "45q", "5Ot", "4J4", "GI", "10R", "0iK", "Jy", "6gF", "4Re", "48A", "6Ij", "dU", "q4",
|
||||
"5Pl", "61f", "XQ", "M0", "0UO", "2u", "7Kc", "4na", "4cQ", "428", "39u", "8Qg", "0vS", "Ua", "aRL", "b3F", "bxO", "63W", "0l3", "0yR",
|
||||
"234", "0D", "4y1", "4lP", "55I", "6TC", "2om", "0ZN", "B1", "WP", "6zo", "4OL", "6dm", "4QN", "1zA", "IR", "25g", "0DL", "5oC", "6JA",
|
||||
"4g3", "46Z", "9PE", "jN", "j", "0gP", "684", "aCO", "72u", "675", "0hQ", "Kc", "eO", "8Oe", "5mr", "4h2", "7Ua", "44k", "0KM", "3nO",
|
||||
"FS", "S2", "5Nn", "6kl", "4x6", "4mW", "0Vy", "1C", "0m4", "0xU", "5SZ", "62P", "7kI", "4NK", "C6", "VW", "2nj", "1Kh", "54N", "6UD",
|
||||
"6ZE", "4of", "0TH", "3r", "YV", "L7", "4AJ", "60a", "6yY", "4Lz", "0wT", "Tf", "zJ", "0Yx", "4bV", "4w7", "5lu", "4i5", "dH", "0Gz",
|
||||
"0iV", "Jd", "5W8", "4Rx", "5Oi", "6jk", "GT", "R5", "0JJ", "ix", "6DG", "45l", "5nD", "6KF", "fy", "0EK", "0kg", "HU", "6ej", "4PI",
|
||||
"5MX", "5X9", "Ee", "0fW", "1XZ", "kI", "4f4", "4sU", "00w", "WM", "4Z0", "4OQ", "55T", "hgu", "ya", "0ZS", "a0", "0Y", "6Yn", "4lM",
|
||||
"4Ba", "63J", "2Ll", "0yO", "02F", "2Cm", "6xC", "aG0", "4cL", "6Vo", "2mA", "n1", "0UR", "2h", "a5E", "bTO", "5Pq", "4U1", "XL", "86n",
|
||||
"FN", "11U", "5Ns", "4K3", "516", "44v", "0KP", "hb", "eR", "p3", "49F", "6Hm", "6fA", "4Sb", "0hL", "3MN", "w", "0gM", "5LB", "7ya",
|
||||
"6Gl", "46G", "0Ia", "jS", "gc", "0DQ", "bEL", "hyw", "4D2", "4QS", "8ce", "IO", "0m0", "0xQ", "byL", "62T", "4x2", "4mS", "227", "1G",
|
||||
"2nn", "1Kl", "54J", "7Ea", "7kM", "4NO", "C2", "VS", "YR", "L3", "4AN", "60e", "6ZA", "4ob", "0TL", "3v", "zN", "8Pd", "4bR", "4w3",
|
||||
"aSO", "b2E", "03X", "Tb", "0iR", "3LP", "73v", "666", "48X", "4i1", "dL", "8Nf", "0JN", "3oL", "6DC", "45h", "5Om", "6jo", "GP", "R1",
|
||||
"0kc", "HQ", "6en", "4PM", "a09", "6KB", "24d", "0EO", "8Ag", "kM", "4f0", "47Y", "697", "aBL", "Ea", "0fS", "4ay", "5d9", "ye", "0ZW",
|
||||
"00s", "WI", "4Z4", "4OU", "4Be", "63N", "Zy", "0yK", "a4", "tU", "6Yj", "4lI", "4cH", "6Vk", "2mE", "n5", "02B", "Ux", "6xG", "4Md",
|
||||
"5Pu", "4U5", "XH", "86j", "0UV", "2l", "5k8", "4nx", "512", "44r", "0KT", "hf", "FJ", "0ex", "5Nw", "4K7", "6fE", "4Sf", "0hH", "Kz",
|
||||
"eV", "p7", "49B", "6Hi", "6Gh", "46C", "0Ie", "jW", "s", "0gI", "5LF", "6iD", "4D6", "4QW", "0jy", "IK", "gg", "0DU", "5oZ", "6JX",
|
||||
"7kA", "4NC", "01e", "3Po", "xs", "8RY", "54F", "6UL", "a6f", "59v", "0Vq", "1K", "d3E", "85M", "4Cs", "5F3", "5I2", "4Lr", "03T", "Tn",
|
||||
"zB", "0Yp", "56w", "437", "6ZM", "4on", "1Da", "3z", "2OO", "0zl", "4AB", "60i", "5Oa", "6jc", "2QM", "0dn", "0JB", "ip", "6DO", "45d",
|
||||
"48T", "acm", "1B2", "0Gr", "94o", "Jl", "5W0", "4Rp", "5MP", "5X1", "Em", "12v", "0Hs", "kA", "all", "47U", "5nL", "6KN", "fq", "0EC",
|
||||
"0ko", "3Nm", "6eb", "4PA", "a8", "0Q", "6Yf", "4lE", "4Bi", "63B", "Zu", "0yG", "0tw", "WE", "4Z8", "4OY", "4au", "5d5", "yi", "1Jz",
|
||||
"0UZ", "vh", "5k4", "4nt", "5Py", "4U9", "XD", "1kW", "02N", "Ut", "6xK", "4Mh", "4cD", "6Vg", "2mI", "n9", "eZ", "43", "49N", "6He",
|
||||
"6fI", "4Sj", "0hD", "Kv", "FF", "0et", "7n9", "6ky", "5u6", "4pv", "0KX", "hj", "gk", "0DY", "5oV", "5z7", "6dx", "5Az", "0ju", "IG",
|
||||
"Dw", "0gE", "5LJ", "6iH", "6Gd", "46O", "0Ii", "28B", "xw", "1Kd", "54B", "6UH", "7kE", "4NG", "01a", "3Pk", "0m8", "0xY", "4Cw", "5F7",
|
||||
"6Xx", "59r", "0Vu", "1O", "zF", "0Yt", "4bZ", "433", "5I6", "4Lv", "03P", "Tj", "YZ", "0zh", "4AF", "60m", "6ZI", "4oj", "0TD", "wv",
|
||||
"0JF", "it", "6DK", "4qh", "5Oe", "6jg", "GX", "R9", "0iZ", "Jh", "5W4", "4Rt", "48P", "4i9", "dD", "0Gv", "0Hw", "kE", "4f8", "47Q",
|
||||
"5MT", "5X5", "Ei", "12r", "0kk", "HY", "6ef", "4PE", "5nH", "6KJ", "fu", "0EG", "4Bm", "63F", "Zq", "0yC", "0Wo", "0U", "6Yb", "4lA",
|
||||
"4aq", "5d1", "ym", "8SG", "0ts", "WA", "aPl", "b1f", "747", "61w", "2NQ", "1kS", "9Lg", "2d", "5k0", "4np", "57i", "6Vc", "2mM", "0Xn",
|
||||
"02J", "Up", "6xO", "4Ml", "6fM", "4Sn", "1xa", "Kr", "27G", "47", "49J", "6Ha", "5u2", "44z", "8BD", "hn", "FB", "0ep", "bdm", "aAo",
|
||||
"70U", "bkl", "0jq", "IC", "go", "306", "5oR", "5z3", "7WA", "46K", "0Im", "28F", "Ds", "0gA", "5LN", "6iL", "0cY", "020", "6mT", "4Xw",
|
||||
"42S", "6Cx", "nG", "0Mu", "1Pd", "cw", "6NH", "5kJ", "4UG", "74M", "3Kk", "0ni", "0ah", "BZ", "6oe", "4ZF", "40b", "6AI", "lv", "0OD",
|
||||
"0Bt", "aF", "6Ly", "4yZ", "4Wv", "5R6", "Oj", "0lX", "Qh", "06R", "4It", "5L4", "461", "4gX", "1LW", "1Y6", "rt", "0QF", "4jh", "7Oj",
|
||||
"65o", "4DD", "I9", "2JI", "SY", "F8", "4KE", "7nG", "6PJ", "4ei", "1Nf", "2kd", "4M", "0Sw", "4hY", "490", "5C5", "4Fu", "09S", "2Hx",
|
||||
"6OR", "4zq", "354", "bm", "LA", "0os", "bnn", "75W", "6lN", "4Ym", "0bC", "Aq", "2yL", "0Lo", "43I", "6Bb", "6Mc", "5ha", "15", "22E",
|
||||
"Np", "0mB", "4Vl", "6cO", "aDm", "bao", "1pS", "1e2", "ml", "8GF", "41x", "548", "4kr", "5n2", "7f", "8YD", "1nQ", "2KS", "64u", "715",
|
||||
"4Hn", "69E", "Pr", "07H", "1MM", "2hO", "6Sa", "4fB", "4iC", "7LA", "5W", "0Rm", "08I", "2Ib", "66D", "4Go", "b4d", "aUn", "RC", "05y",
|
||||
"8VE", "8g", "5a3", "4ds", "42W", "ain", "nC", "0Mq", "17t", "024", "6mP", "4Xs", "4UC", "74I", "3Ko", "0nm", "8IY", "cs", "6NL", "5kN",
|
||||
"40f", "6AM", "lr", "8FX", "0al", "2TO", "6oa", "4ZB", "4Wr", "5R2", "On", "18u", "0Bp", "aB", "afo", "bCm", "465", "53u", "1LS", "1Y2",
|
||||
"Ql", "06V", "4Ip", "5L0", "65k", "5Ta", "1oO", "2JM", "6x", "0QB", "4jl", "7On", "6PN", "4em", "1Nb", "9y", "2EL", "04g", "4KA", "7nC",
|
||||
"5C1", "4Fq", "09W", "d6G", "4I", "0Ss", "bRn", "494", "LE", "0ow", "4TY", "4A8", "6OV", "4zu", "1Qz", "bi", "oY", "z8", "43M", "6Bf",
|
||||
"6lJ", "4Yi", "0bG", "Au", "Nt", "0mF", "4Vh", "6cK", "6Mg", "4xD", "11", "22A", "mh", "0NZ", "4ut", "5p4", "4N9", "5Ky", "1pW", "CD",
|
||||
"1nU", "2KW", "64q", "4EZ", "4kv", "5n6", "7b", "0PX", "1MI", "2hK", "6Se", "4fF", "4Hj", "69A", "Pv", "07L", "08M", "2If", "6rH", "4Gk",
|
||||
"4iG", "7LE", "5S", "0Ri", "1Ox", "8c", "5a7", "4dw", "5Zz", "7oY", "RG", "0qu", "1Pl", "21f", "adR", "5kB", "4UO", "74E", "MS", "X2",
|
||||
"0cQ", "028", "79u", "bbL", "4vS", "4c2", "nO", "8De", "8Kd", "aN", "4l3", "4yR", "634", "76t", "Ob", "0lP", "W3", "BR", "6om", "4ZN",
|
||||
"40j", "6AA", "2zo", "0OL", "6t", "0QN", "5zA", "7Ob", "65g", "4DL", "I1", "2JA", "0g3", "06Z", "b7G", "68W", "469", "4gP", "284", "dSn",
|
||||
"4E", "275", "4hQ", "498", "67V", "b8F", "1mr", "0h2", "SQ", "F0", "4KM", "7nO", "6PB", "4ea", "1Nn", "9u", "6lF", "4Ye", "0bK", "Ay",
|
||||
"oU", "z4", "43A", "6Bj", "6OZ", "4zy", "0AW", "be", "LI", "2O9", "4TU", "4A4", "4N5", "5Ku", "14S", "CH", "md", "0NV", "41p", "540",
|
||||
"6Mk", "4xH", "u5", "22M", "Nx", "0mJ", "4Vd", "6cG", "4Hf", "69M", "Pz", "0sH", "k7", "2hG", "6Si", "4fJ", "4kz", "7Nx", "7n", "0PT",
|
||||
"1nY", "dqh", "4P7", "4EV", "4JW", "7oU", "RK", "05q", "1Ot", "8o", "6QX", "50R", "4iK", "7LI", "qW", "d6", "08A", "2Ij", "66L", "4Gg",
|
||||
"4UK", "74A", "MW", "X6", "1Ph", "21b", "6ND", "5kF", "4vW", "4c6", "nK", "0My", "0cU", "0v4", "6mX", "5HZ", "4Wz", "6bY", "Of", "0lT",
|
||||
"0Bx", "aJ", "4l7", "4yV", "40n", "6AE", "lz", "0OH", "W7", "BV", "6oi", "4ZJ", "65c", "4DH", "I5", "2JE", "6p", "0QJ", "4jd", "7Of",
|
||||
"4r5", "4gT", "280", "2iY", "Qd", "0rV", "4Ix", "5L8", "5C9", "4Fy", "1mv", "0h6", "4A", "1CZ", "4hU", "7MW", "6PF", "4ee", "1Nj", "9q",
|
||||
"SU", "F4", "4KI", "7nK", "oQ", "z0", "43E", "6Bn", "6lB", "4Ya", "0bO", "2Wl", "LM", "8fg", "4TQ", "4A0", "aeL", "cPo", "0AS", "ba",
|
||||
"3kP", "0NR", "41t", "544", "4N1", "5Kq", "14W", "CL", "2Xm", "0mN", "5FA", "6cC", "6Mo", "4xL", "19", "22I", "k3", "2hC", "6Sm", "4fN",
|
||||
"4Hb", "69I", "2Fo", "07D", "83l", "d5d", "4P3", "4ER", "bQM", "a0G", "7j", "0PP", "1Op", "8k", "hbw", "50V", "4JS", "7oQ", "RO", "05u",
|
||||
"08E", "2In", "66H", "4Gc", "4iO", "7LM", "qS", "d2", "0ay", "BK", "4O6", "4ZW", "40s", "553", "lg", "0OU", "t6", "aW", "6Lh", "4yK",
|
||||
"4Wg", "6bD", "2Yj", "0lI", "0cH", "2Vk", "6mE", "4Xf", "42B", "6Ci", "nV", "0Md", "1Pu", "cf", "6NY", "bAI", "4UV", "7pT", "MJ", "0nx",
|
||||
"SH", "04r", "4KT", "7nV", "azI", "4ex", "1Nw", "9l", "pT", "e5", "4hH", "7MJ", "67O", "4Fd", "09B", "2Hi", "Qy", "06C", "4Ie", "68N",
|
||||
"6Rj", "4gI", "j4", "2iD", "6m", "0QW", "4jy", "5o9", "4Q4", "4DU", "1oZ", "2JX", "4m0", "4xQ", "8Jg", "22T", "Na", "0mS", "627", "77w",
|
||||
"6nn", "5Kl", "V0", "CQ", "3kM", "0NO", "41i", "7Pc", "6OC", "5jA", "0AN", "20e", "LP", "Y1", "4TL", "6ao", "78v", "bcO", "0bR", "0w3",
|
||||
"oL", "8Ef", "43X", "4b1", "4iR", "7LP", "5F", "266", "08X", "0i1", "66U", "b9E", "4JN", "7oL", "RR", "G3", "1Om", "8v", "6QA", "4db",
|
||||
"4kc", "7Na", "7w", "0PM", "H2", "2KB", "64d", "4EO", "b6D", "69T", "Pc", "07Y", "297", "dRm", "4s2", "4fS", "40w", "557", "lc", "0OQ",
|
||||
"15T", "BO", "4O2", "4ZS", "4Wc", "76i", "2Yn", "0lM", "t2", "aS", "6Ll", "4yO", "42F", "6Cm", "nR", "8Dx", "0cL", "2Vo", "6mA", "4Xb",
|
||||
"4UR", "74X", "MN", "8gd", "1Pq", "cb", "adO", "bAM", "azM", "51U", "1Ns", "9h", "SL", "04v", "4KP", "7nR", "67K", "5VA", "09F", "2Hm",
|
||||
"4X", "e1", "4hL", "7MN", "6Rn", "4gM", "j0", "3ya", "2Gl", "06G", "4Ia", "68J", "4Q0", "4DQ", "82o", "d4g", "6i", "0QS", "bPN", "a1D",
|
||||
"Ne", "0mW", "4Vy", "5S9", "4m4", "4xU", "1SZ", "22P", "my", "0NK", "41m", "7Pg", "6nj", "5Kh", "V4", "CU", "LT", "Y5", "4TH", "6ak",
|
||||
"6OG", "4zd", "0AJ", "bx", "oH", "0Lz", "4wT", "4b5", "78r", "4Yx", "0bV", "Ad", "1lu", "0i5", "66Q", "4Gz", "4iV", "7LT", "5B", "0Rx",
|
||||
"1Oi", "8r", "6QE", "4df", "4JJ", "7oH", "RV", "G7", "H6", "2KF", "6ph", "4EK", "4kg", "7Ne", "7s", "0PI", "1MX", "1X9", "4s6", "4fW",
|
||||
"5XZ", "69P", "Pg", "0sU", "06", "23F", "afr", "4yC", "4Wo", "6bL", "Os", "0lA", "0aq", "BC", "aEn", "c4E", "4ts", "5q3", "lo", "8FE",
|
||||
"347", "cn", "6NQ", "5kS", "bom", "74T", "MB", "0np", "17i", "2Vc", "6mM", "4Xn", "42J", "6Ca", "2xO", "0Ml", "4T", "0Sn", "5xa", "7MB",
|
||||
"67G", "4Fl", "09J", "2Ha", "1u2", "04z", "b5g", "aTm", "6PS", "4ep", "8WF", "9d", "6e", "8XG", "4jq", "5o1", "65v", "706", "1oR", "1z3",
|
||||
"Qq", "06K", "4Im", "68F", "6Rb", "4gA", "1LN", "2iL", "6nf", "5Kd", "V8", "CY", "mu", "0NG", "41a", "7Pk", "4m8", "4xY", "0Cw", "1F7",
|
||||
"Ni", "19r", "4Vu", "5S5", "6lW", "4Yt", "0bZ", "Ah", "oD", "0Lv", "43P", "4b9", "6OK", "4zh", "0AF", "bt", "LX", "Y9", "4TD", "6ag",
|
||||
"4JF", "7oD", "RZ", "0qh", "1Oe", "2jg", "6QI", "4dj", "4iZ", "483", "5N", "0Rt", "08P", "0i9", "5B6", "4Gv", "4Hw", "5M7", "Pk", "07Q",
|
||||
"1MT", "1X5", "472", "52r", "4kk", "7Ni", "sw", "0PE", "1nH", "2KJ", "64l", "4EG", "4Wk", "6bH", "Ow", "0lE", "02", "23B", "6Ld", "4yG",
|
||||
"4tw", "5q7", "lk", "0OY", "0au", "BG", "6ox", "5Jz", "4UZ", "74P", "MF", "0nt", "1Py", "cj", "6NU", "5kW", "42N", "6Ce", "nZ", "0Mh",
|
||||
"0cD", "2Vg", "6mI", "4Xj", "67C", "4Fh", "09N", "2He", "4P", "e9", "4hD", "7MF", "6PW", "4et", "3n9", "2ky", "SD", "0pv", "4KX", "7nZ",
|
||||
"4Q8", "4DY", "1oV", "1z7", "6a", "1Az", "4ju", "5o5", "6Rf", "4gE", "j8", "2iH", "Qu", "06O", "4Ii", "68B", "mq", "0NC", "41e", "7Po",
|
||||
"6nb", "bar", "14F", "2UL", "Nm", "19v", "4Vq", "5S1", "agl", "bBn", "0Cs", "1F3", "1I2", "0Lr", "43T", "ahm", "6lS", "4Yp", "16w", "Al",
|
||||
"2ZM", "0on", "5Da", "6ac", "6OO", "4zl", "0AB", "bp", "1Oa", "8z", "6QM", "4dn", "4JB", "aUs", "2DO", "05d", "08T", "d7D", "5B2", "4Gr",
|
||||
"bSm", "487", "5J", "0Rp", "1MP", "1X1", "476", "52v", "4Hs", "5M3", "Po", "07U", "1nL", "2KN", "64h", "4EC", "4ko", "7Nm", "ss", "0PA",
|
||||
"QJ", "06p", "4IV", "7lT", "6RY", "4gz", "1Lu", "0I5", "rV", "g7", "4jJ", "7OH", "65M", "4Df", "1oi", "2Jk", "2Ej", "04A", "4Kg", "7ne",
|
||||
"6Ph", "4eK", "h6", "2kF", "4o", "0SU", "5xZ", "7My", "4S6", "4FW", "09q", "1x9", "17R", "2VX", "4M4", "4XU", "42q", "571", "ne", "0MW",
|
||||
"v4", "cU", "6Nj", "5kh", "4Ue", "74o", "My", "0nK", "0aJ", "Bx", "6oG", "4Zd", "4tH", "6Ak", "lT", "y5", "0BV", "ad", "580", "4yx",
|
||||
"4WT", "4B5", "OH", "0lz", "4kP", "7NR", "7D", "244", "1ns", "0k3", "64W", "con", "4HL", "69g", "PP", "E1", "1Mo", "2hm", "6SC", "52I",
|
||||
"4ia", "7Lc", "5u", "0RO", "J0", "3Ya", "66f", "4GM", "b4F", "aUL", "Ra", "0qS", "8Vg", "8E", "458", "4dQ", "4o2", "4zS", "8He", "bO",
|
||||
"Lc", "0oQ", "605", "75u", "6ll", "4YO", "T2", "AS", "2yn", "0LM", "43k", "7Ra", "6MA", "4xb", "0CL", "22g", "NR", "19I", "4VN", "6cm",
|
||||
"aDO", "baM", "14y", "Cb", "mN", "8Gd", "41Z", "7PP", "axO", "53W", "1Lq", "0I1", "QN", "06t", "4IR", "68y", "65I", "4Db", "1om", "2Jo",
|
||||
"6Z", "g3", "4jN", "7OL", "6Pl", "4eO", "h2", "2kB", "2En", "04E", "4Kc", "7na", "4S2", "4FS", "09u", "d6e", "4k", "0SQ", "bRL", "a3F",
|
||||
"42u", "575", "na", "0MS", "17V", "dlo", "4M0", "4XQ", "4Ua", "74k", "3KM", "0nO", "28", "cQ", "6Nn", "5kl", "40D", "6Ao", "lP", "y1",
|
||||
"0aN", "2Tm", "6oC", "5JA", "4WP", "4B1", "OL", "18W", "0BR", "0W3", "584", "bCO", "1nw", "0k7", "64S", "4Ex", "4kT", "7NV", "sH", "0Pz",
|
||||
"1Mk", "2hi", "6SG", "4fd", "4HH", "69c", "PT", "E5", "J4", "2ID", "66b", "4GI", "4ie", "7Lg", "5q", "0RK", "1OZ", "8A", "4q4", "4dU",
|
||||
"4Jy", "5O9", "Re", "0qW", "Lg", "0oU", "5DZ", "6aX", "4o6", "4zW", "0Ay", "bK", "2yj", "0LI", "43o", "6BD", "6lh", "4YK", "T6", "AW",
|
||||
"NV", "0md", "4VJ", "6ci", "6ME", "4xf", "0CH", "22c", "mJ", "0Nx", "4uV", "7PT", "6nY", "baI", "1pu", "Cf", "6V", "0Ql", "4jB", "aus",
|
||||
"65E", "4Dn", "1oa", "2Jc", "QB", "06x", "b7e", "68u", "5b2", "4gr", "8UD", "dSL", "4g", "8ZE", "4hs", "5m3", "67t", "724", "09y", "1x1",
|
||||
"Ss", "04I", "4Ko", "7nm", "azr", "4eC", "1NL", "9W", "24", "21D", "6Nb", "bAr", "4Um", "74g", "Mq", "0nC", "0cs", "1f3", "79W", "bbn",
|
||||
"42y", "579", "nm", "394", "365", "al", "588", "4yp", "bmo", "76V", "1i2", "0lr", "0aB", "Bp", "6oO", "4Zl", "40H", "6Ac", "2zM", "0On",
|
||||
"4HD", "69o", "PX", "E9", "1Mg", "2he", "6SK", "4fh", "4kX", "7NZ", "7L", "0Pv", "3N9", "2Ky", "6pW", "4Et", "4Ju", "5O5", "Ri", "05S",
|
||||
"1OV", "8M", "450", "4dY", "4ii", "7Lk", "qu", "0RG", "J8", "2IH", "66n", "4GE", "6ld", "4YG", "0bi", "2WJ", "ow", "0LE", "43c", "6BH",
|
||||
"6Ox", "5jz", "0Au", "bG", "Lk", "0oY", "4Tw", "5Q7", "6nU", "5KW", "14q", "Cj", "mF", "0Nt", "41R", "7PX", "6MI", "4xj", "0CD", "22o",
|
||||
"NZ", "0mh", "4VF", "6ce", "65A", "4Dj", "1oe", "2Jg", "6R", "0Qh", "4jF", "7OD", "5b6", "4gv", "1Ly", "0I9", "QF", "0rt", "4IZ", "68q",
|
||||
"67p", "5Vz", "1mT", "1x5", "4c", "0SY", "4hw", "5m7", "6Pd", "4eG", "1NH", "9S", "Sw", "04M", "4Kk", "7ni", "4Ui", "74c", "Mu", "0nG",
|
||||
"20", "cY", "6Nf", "5kd", "4vu", "5s5", "ni", "390", "0cw", "1f7", "4M8", "4XY", "4WX", "4B9", "OD", "0lv", "0BZ", "ah", "6LW", "4yt",
|
||||
"40L", "6Ag", "lX", "y9", "0aF", "Bt", "6oK", "4Zh", "1Mc", "2ha", "6SO", "4fl", "5Xa", "69k", "2FM", "07f", "83N", "d5F", "6pS", "4Ep",
|
||||
"bQo", "a0e", "7H", "0Pr", "1OR", "8I", "454", "50t", "4Jq", "5O1", "Rm", "05W", "08g", "2IL", "66j", "4GA", "4im", "7Lo", "5y", "0RC",
|
||||
"os", "0LA", "43g", "6BL", "78I", "4YC", "0bm", "2WN", "Lo", "8fE", "4Ts", "5Q3", "aen", "cPM", "0Aq", "bC", "mB", "0Np", "41V", "ajo",
|
||||
"6nQ", "5KS", "14u", "Cn", "2XO", "0ml", "4VB", "6ca", "6MM", "4xn", "1Sa", "22k", "Sj", "04P", "4Kv", "5N6", "443", "4eZ", "1NU", "9N",
|
||||
"pv", "0SD", "4hj", "7Mh", "67m", "4FF", "1mI", "2HK", "2GJ", "2", "4IG", "68l", "6RH", "4gk", "1Ld", "2if", "6O", "0Qu", "5zz", "7OY",
|
||||
"5A7", "4Dw", "1ox", "0j8", "15r", "Bi", "6oV", "4Zu", "40Q", "4a8", "lE", "0Ow", "0BG", "au", "6LJ", "4yi", "4WE", "6bf", "OY", "Z8",
|
||||
"U9", "2VI", "6mg", "4XD", "4vh", "6CK", "nt", "0MF", "1PW", "cD", "4n9", "5ky", "4Ut", "5P4", "Mh", "0nZ", "4ip", "5l0", "5d", "9Kg",
|
||||
"08z", "1y2", "66w", "737", "4Jl", "7on", "Rp", "05J", "1OO", "8T", "6Qc", "50i", "4kA", "7NC", "7U", "0Po", "1nb", "dqS", "64F", "4Em",
|
||||
"b6f", "69v", "PA", "0ss", "8TG", "dRO", "5c1", "4fq", "6MP", "4xs", "376", "22v", "NC", "0mq", "bll", "77U", "6nL", "5KN", "14h", "Cs",
|
||||
"3ko", "0Nm", "41K", "7PA", "6Oa", "4zB", "37", "20G", "Lr", "8fX", "4Tn", "6aM", "78T", "bcm", "0bp", "AB", "on", "387", "43z", "5r2",
|
||||
"447", "51w", "1NQ", "9J", "Sn", "04T", "4Kr", "5N2", "67i", "4FB", "09d", "2HO", "4z", "1Ca", "4hn", "7Ml", "6RL", "4go", "8UY", "2ib",
|
||||
"2GN", "6", "4IC", "68h", "5A3", "4Ds", "82M", "d4E", "6K", "0Qq", "bPl", "a1f", "40U", "akl", "lA", "0Os", "15v", "Bm", "6oR", "4Zq",
|
||||
"4WA", "6bb", "2YL", "0lo", "0BC", "aq", "6LN", "4ym", "42d", "6CO", "np", "0MB", "0cn", "2VM", "6mc", "5Ha", "4Up", "5P0", "Ml", "8gF",
|
||||
"1PS", "1E2", "adm", "bAo", "1lW", "1y6", "4R9", "4GX", "4it", "5l4", "qh", "0RZ", "i9", "8P", "6Qg", "4dD", "4Jh", "7oj", "Rt", "05N",
|
||||
"1nf", "2Kd", "64B", "4Ei", "4kE", "7NG", "7Q", "f8", "1Mz", "2hx", "5c5", "4fu", "4HY", "69r", "PE", "0sw", "NG", "0mu", "5Fz", "6cx",
|
||||
"6MT", "4xw", "0CY", "0V8", "3kk", "0Ni", "41O", "7PE", "6nH", "5KJ", "14l", "Cw", "Lv", "0oD", "4Tj", "6aI", "6Oe", "4zF", "33", "bZ",
|
||||
"oj", "0LX", "4wv", "5r6", "6ly", "4YZ", "0bt", "AF", "4v", "0SL", "4hb", "awS", "67e", "4FN", "K3", "2HC", "Sb", "04X", "b5E", "aTO",
|
||||
"4p3", "4eR", "8Wd", "9F", "6G", "257", "4jS", "7OQ", "65T", "cnm", "1op", "0j0", "QS", "D2", "4IO", "68d", "7Ba", "4gc", "1Ll", "2in",
|
||||
"0BO", "23d", "6LB", "4ya", "4WM", "6bn", "OQ", "Z0", "0aS", "Ba", "aEL", "c4g", "40Y", "4a0", "lM", "8Fg", "8If", "cL", "4n1", "5kq",
|
||||
"616", "74v", "3KP", "0nR", "U1", "2VA", "6mo", "4XL", "42h", "6CC", "2xm", "0MN", "4Jd", "7of", "Rx", "05B", "i5", "2jE", "6Qk", "4dH",
|
||||
"4ix", "5l8", "5l", "0RV", "08r", "2IY", "4R5", "4GT", "4HU", "7mW", "PI", "07s", "1Mv", "0H6", "5c9", "4fy", "4kI", "7NK", "sU", "f4",
|
||||
"1nj", "2Kh", "64N", "4Ee", "6nD", "5KF", "1ph", "2Uj", "mW", "x6", "41C", "7PI", "593", "5hZ", "0CU", "0V4", "NK", "0my", "4VW", "4C6",
|
||||
"4L7", "4YV", "0bx", "AJ", "of", "0LT", "43r", "562", "6Oi", "4zJ", "w7", "bV", "Lz", "0oH", "4Tf", "6aE", "67a", "4FJ", "K7", "2HG",
|
||||
"4r", "0SH", "4hf", "7Md", "4p7", "4eV", "1NY", "9B", "Sf", "0pT", "4Kz", "7nx", "65P", "5TZ", "1ot", "0j4", "6C", "0Qy", "4jW", "7OU",
|
||||
"6RD", "4gg", "1Lh", "2ij", "QW", "D6", "4IK", "7lI", "4WI", "6bj", "OU", "Z4", "0BK", "ay", "6LF", "4ye", "4tU", "4a4", "lI", "2o9",
|
||||
"0aW", "Be", "6oZ", "4Zy", "4Ux", "5P8", "Md", "0nV", "8Ib", "cH", "4n5", "5ku", "42l", "6CG", "nx", "0MJ", "U5", "2VE", "6mk", "4XH",
|
||||
"i1", "8X", "6Qo", "4dL", "5ZA", "7ob", "2Dm", "05F", "08v", "d7f", "4R1", "4GP", "bSO", "a2E", "5h", "0RR", "1Mr", "0H2", "ayL", "52T",
|
||||
"4HQ", "69z", "PM", "07w", "1nn", "2Kl", "64J", "4Ea", "4kM", "7NO", "7Y", "f0", "mS", "x2", "41G", "7PM", "aDR", "5KB", "14d", "2Un",
|
||||
"NO", "19T", "4VS", "4C2", "597", "bBL", "0CQ", "0V0", "ob", "0LP", "43v", "566", "4L3", "4YR", "16U", "AN", "2Zo", "0oL", "4Tb", "6aA",
|
||||
"6Om", "4zN", "w3", "bR", "4oT", "4z5", "wH", "0Tz", "0zV", "Yd", "5D8", "4Ax", "4LH", "6yk", "TT", "A5", "0YJ", "zx", "6WG", "4bd",
|
||||
"4me", "6XF", "1q", "0VK", "N4", "2MD", "62b", "4CI", "4Ny", "5K9", "Ve", "0uW", "1KZ", "xI", "4u4", "5pt", "4k6", "5nv", "0Ey", "fK",
|
||||
"Hg", "0kU", "641", "6eX", "6hh", "5Mj", "P6", "EW", "29b", "0HI", "47o", "6FD", "6IE", "48n", "0GH", "dz", "JV", "0id", "4RJ", "6gi",
|
||||
"6jY", "beI", "0dT", "Gf", "iJ", "0Jx", "4qV", "4d7", "UN", "02t", "4MR", "4X3", "a8G", "57W", "0XP", "0M1", "2Z", "c3", "4nN", "7KL",
|
||||
"61I", "5PC", "1km", "2No", "2An", "00E", "4Oc", "7ja", "6Tl", "4aO", "l2", "yS", "0k", "0WQ", "58V", "a7F", "4W2", "4BS", "84m", "ZO",
|
||||
"13V", "E", "4I0", "5Lp", "46u", "535", "ja", "0IS", "68", "gQ", "6Jn", "5ol", "4Qa", "6dB", "3OM", "0jO", "0eN", "2Pm", "6kC", "5NA",
|
||||
"44D", "6Eo", "hP", "99", "0FR", "0S3", "abM", "49t", "4SP", "4F1", "KL", "8af", "0zR", "0o3", "60W", "ckn", "4oP", "4z1", "3D", "204",
|
||||
"0YN", "2lm", "6WC", "56I", "4LL", "6yo", "TP", "A1", "N0", "903", "62f", "4CM", "4ma", "6XB", "1u", "0VO", "8Rg", "xM", "418", "54x",
|
||||
"b0F", "aQL", "Va", "0uS", "Hc", "0kQ", "645", "71u", "4k2", "5nr", "8Le", "fO", "29f", "0HM", "47k", "7Va", "6hl", "5Mn", "P2", "ES",
|
||||
"JR", "1yA", "4RN", "6gm", "6IA", "48j", "0GL", "26g", "iN", "8Cd", "45Z", "4d3", "hYv", "beM", "0dP", "Gb", "6VY", "4cz", "0XT", "0M5",
|
||||
"UJ", "02p", "4MV", "4X7", "61M", "5PG", "1ki", "Xz", "vV", "c7", "4nJ", "7KH", "6Th", "4aK", "l6", "yW", "2Aj", "00A", "4Og", "6zD",
|
||||
"4W6", "4BW", "0yy", "ZK", "0o", "0WU", "58R", "6YX", "46q", "531", "je", "0IW", "13R", "A", "4I4", "5Lt", "4Qe", "6dF", "Iy", "0jK",
|
||||
"r4", "gU", "6Jj", "5oh", "4pH", "6Ek", "hT", "0Kf", "0eJ", "Fx", "6kG", "5NE", "4ST", "4F5", "KH", "0hz", "0FV", "ed", "5x8", "49p",
|
||||
"bvs", "6yc", "2BM", "03f", "0YB", "zp", "6WO", "4bl", "bUo", "a4e", "3H", "0Tr", "87N", "Yl", "5D0", "4Ap", "4Nq", "5K1", "Vm", "01W",
|
||||
"1KR", "xA", "414", "54t", "4mm", "6XN", "1y", "0VC", "0xo", "2ML", "62j", "4CA", "7xA", "5Mb", "0fm", "2SN", "ks", "0HA", "47g", "6FL",
|
||||
"aan", "bDl", "0Eq", "fC", "Ho", "8bE", "4Ps", "5U3", "5Z2", "5OS", "10u", "Gn", "iB", "0Jp", "45V", "ano", "6IM", "48f", "1Wa", "dr",
|
||||
"3Ln", "0il", "4RB", "6ga", "2R", "0Uh", "4nF", "7KD", "61A", "5PK", "1ke", "Xv", "UF", "0vt", "4MZ", "6xy", "5f6", "4cv", "0XX", "0M9",
|
||||
"0c", "0WY", "4lw", "5i7", "63p", "5Rz", "0yu", "ZG", "Ww", "00M", "4Ok", "6zH", "6Td", "4aG", "0Zi", "2oJ", "60", "gY", "6Jf", "5od",
|
||||
"4Qi", "6dJ", "Iu", "0jG", "0gw", "M", "4I8", "5Lx", "4ru", "5w5", "ji", "1Yz", "0FZ", "eh", "5x4", "5mU", "4SX", "4F9", "KD", "0hv",
|
||||
"0eF", "Ft", "6kK", "5NI", "44L", "6Eg", "hX", "91", "0YF", "zt", "6WK", "4bh", "4LD", "6yg", "TX", "A9", "0zZ", "Yh", "5D4", "4At",
|
||||
"4oX", "4z9", "3L", "0Tv", "1KV", "xE", "410", "54p", "4Nu", "5K5", "Vi", "01S", "N8", "2MH", "62n", "4CE", "4mi", "6XJ", "uu", "0VG",
|
||||
"kw", "0HE", "47c", "6FH", "6hd", "5Mf", "0fi", "2SJ", "Hk", "0kY", "4Pw", "5U7", "6Kx", "5nz", "0Eu", "fG", "iF", "0Jt", "45R", "6Dy",
|
||||
"5Z6", "5OW", "0dX", "Gj", "JZ", "0ih", "4RF", "6ge", "6II", "48b", "0GD", "dv", "61E", "5PO", "1ka", "Xr", "2V", "0Ul", "4nB", "aqs",
|
||||
"5f2", "4cr", "8QD", "39V", "UB", "02x", "795", "aRo", "63t", "764", "0yq", "ZC", "0g", "9Nd", "4ls", "5i3", "7DA", "4aC", "0Zm", "2oN",
|
||||
"Ws", "00I", "4Oo", "6zL", "4Qm", "6dN", "Iq", "0jC", "64", "25D", "6Jb", "bEr", "46y", "539", "jm", "9Pf", "0gs", "I", "aCl", "bfn",
|
||||
"bio", "72V", "1m2", "0hr", "325", "el", "5x0", "49x", "44H", "6Ec", "3nl", "95", "0eB", "Fp", "6kO", "5NM", "4mt", "5h4", "uh", "0VZ",
|
||||
"0xv", "2MU", "4V9", "4CX", "4Nh", "7kj", "Vt", "01N", "m9", "xX", "6Ug", "54m", "4oE", "6Zf", "3Q", "b8", "0zG", "Yu", "60B", "4Ai",
|
||||
"4LY", "4Y8", "TE", "0ww", "1Iz", "zi", "5g5", "4bu", "5y7", "5lV", "0GY", "dk", "JG", "0iu", "5Bz", "6gx", "6jH", "5OJ", "0dE", "Gw",
|
||||
"3ok", "82", "45O", "6Dd", "6Ke", "5ng", "73", "fZ", "Hv", "0kD", "4Pj", "6eI", "6hy", "7m9", "0ft", "EF", "kj", "0HX", "4sv", "5v6",
|
||||
"Wn", "00T", "4Or", "5J2", "407", "55w", "0Zp", "yB", "0z", "1Ga", "4ln", "6YM", "63i", "4BB", "0yl", "2LO", "2CN", "02e", "4MC", "7hA",
|
||||
"6VL", "4co", "0XA", "2mb", "2K", "0Uq", "bTl", "a5f", "5E3", "5PR", "86M", "Xo", "11v", "Fm", "6kR", "5NP", "44U", "aol", "hA", "0Ks",
|
||||
"0FC", "eq", "6HN", "49e", "4SA", "6fb", "3Mm", "0ho", "0gn", "T", "6ic", "5La", "46d", "6GO", "jp", "0IB", "0Dr", "1A2", "hyT", "bEo",
|
||||
"4Qp", "5T0", "Il", "8cF", "0xr", "2MQ", "62w", "777", "4mp", "5h0", "1d", "9Og", "1KO", "2nM", "6Uc", "54i", "4Nl", "7kn", "Vp", "01J",
|
||||
"0zC", "Yq", "60F", "4Am", "4oA", "6Zb", "3U", "0To", "8PG", "zm", "5g1", "4bq", "786", "aSl", "TA", "0ws", "JC", "0iq", "bhl", "73U",
|
||||
"5y3", "5lR", "336", "do", "3oo", "86", "45K", "7TA", "6jL", "5ON", "0dA", "Gs", "Hr", "8bX", "4Pn", "6eM", "6Ka", "5nc", "77", "24G",
|
||||
"kn", "8AD", "47z", "5v2", "aBo", "bgm", "0fp", "EB", "403", "4aZ", "0Zt", "yF", "Wj", "00P", "4Ov", "5J6", "63m", "4BF", "0yh", "ZZ",
|
||||
"tv", "0WD", "4lj", "6YI", "6VH", "4ck", "0XE", "2mf", "2CJ", "02a", "4MG", "6xd", "5E7", "5PV", "1kx", "Xk", "2O", "0Uu", "bTh", "7KY",
|
||||
"44Q", "4e8", "hE", "0Kw", "11r", "Fi", "6kV", "5NT", "4SE", "6ff", "KY", "0hk", "0FG", "eu", "6HJ", "49a", "4rh", "6GK", "jt", "0IF",
|
||||
"Q9", "P", "6ig", "5Le", "4Qt", "5T4", "Ih", "0jZ", "0Dv", "gD", "4j9", "5oy", "aD0", "7kb", "3PL", "01F", "m1", "xP", "6Uo", "54e",
|
||||
"59U", "a6E", "1h", "0VR", "85n", "196", "4V1", "4CP", "4LQ", "4Y0", "TM", "03w", "0YS", "za", "a9D", "56T", "4oM", "6Zn", "3Y", "b0",
|
||||
"0zO", "2Ol", "60J", "4Aa", "7za", "5OB", "0dM", "2Qn", "iS", "0Ja", "45G", "6Dl", "acN", "48w", "0GQ", "dc", "JO", "94L", "4RS", "4G2",
|
||||
"4H3", "5Ms", "12U", "EN", "kb", "0HP", "47v", "526", "6Km", "5no", "s3", "fR", "3NN", "0kL", "4Pb", "6eA", "0r", "0WH", "4lf", "6YE",
|
||||
"63a", "4BJ", "O7", "ZV", "Wf", "0tT", "4Oz", "6zY", "4t7", "4aV", "0Zx", "yJ", "2C", "0Uy", "4nW", "7KU", "61P", "5PZ", "1kt", "Xg",
|
||||
"UW", "02m", "4MK", "6xh", "6VD", "4cg", "0XI", "2mj", "0FK", "ey", "6HF", "49m", "4SI", "6fj", "KU", "0hg", "0eW", "Fe", "6kZ", "5NX",
|
||||
"4pU", "4e4", "hI", "2k9", "0Dz", "gH", "4j5", "5ou", "4Qx", "5T8", "Id", "0jV", "Q5", "DT", "6ik", "5Li", "46l", "6GG", "jx", "0IJ",
|
||||
"m5", "xT", "6Uk", "54a", "4Nd", "7kf", "Vx", "01B", "0xz", "192", "4V5", "4CT", "4mx", "5h8", "1l", "0VV", "0YW", "ze", "5g9", "4by",
|
||||
"4LU", "4Y4", "TI", "03s", "0zK", "Yy", "60N", "4Ae", "4oI", "6Zj", "wU", "b4", "iW", "0Je", "45C", "6Dh", "6jD", "5OF", "0dI", "2Qj",
|
||||
"JK", "0iy", "4RW", "4G6", "6IX", "48s", "0GU", "dg", "kf", "0HT", "47r", "522", "4H7", "5Mw", "0fx", "EJ", "Hz", "0kH", "4Pf", "6eE",
|
||||
"6Ki", "5nk", "s7", "fV", "63e", "4BN", "O3", "ZR", "0v", "0WL", "4lb", "6YA", "4t3", "4aR", "8Sd", "yN", "Wb", "00X", "b1E", "aPO",
|
||||
"61T", "bzL", "1kp", "Xc", "2G", "217", "4nS", "7KQ", "7Fa", "4cc", "0XM", "2mn", "US", "02i", "4MO", "6xl", "4SM", "6fn", "KQ", "0hc",
|
||||
"0FO", "27d", "6HB", "49i", "44Y", "4e0", "hM", "8Bg", "0eS", "Fa", "aAL", "bdN", "656", "70v", "3OP", "0jR", "8Mf", "gL", "4j1", "5oq",
|
||||
"46h", "6GC", "28e", "0IN", "Q1", "X", "6io", "5Lm", "6KV", "5nT", "1Uz", "fi", "HE", "0kw", "4PY", "4E8", "6hJ", "5MH", "0fG", "Eu",
|
||||
"kY", "0Hk", "47M", "6Ff", "6Ig", "48L", "51", "dX", "Jt", "0iF", "4Rh", "6gK", "4J9", "5Oy", "0dv", "GD", "ih", "0JZ", "4qt", "5t4",
|
||||
"4ov", "5j6", "3b", "0TX", "0zt", "YF", "60q", "4AZ", "4Lj", "6yI", "Tv", "03L", "0Yh", "zZ", "6We", "4bF", "4mG", "6Xd", "1S", "0Vi",
|
||||
"0xE", "2Mf", "6vH", "4Ck", "bth", "7kY", "VG", "0uu", "1Kx", "xk", "5e7", "5pV", "13t", "g", "5Y3", "5LR", "46W", "amn", "jC", "0Iq",
|
||||
"0DA", "gs", "6JL", "5oN", "4QC", "70I", "3Oo", "0jm", "0el", "2PO", "6ka", "5Nc", "44f", "6EM", "hr", "8BX", "0Fp", "eB", "abo", "49V",
|
||||
"4Sr", "5V2", "Kn", "8aD", "Ul", "02V", "4Mp", "5H0", "425", "57u", "0Xr", "2mQ", "2x", "0UB", "4nl", "7Kn", "61k", "5Pa", "1kO", "2NM",
|
||||
"2AL", "00g", "4OA", "6zb", "6TN", "4am", "0ZC", "yq", "0I", "0Ws", "58t", "a7d", "5G1", "4Bq", "84O", "Zm", "HA", "0ks", "bjn", "71W",
|
||||
"6KR", "5nP", "314", "fm", "29D", "0Ho", "47I", "6Fb", "6hN", "5ML", "0fC", "Eq", "Jp", "0iB", "4Rl", "6gO", "6Ic", "48H", "55", "26E",
|
||||
"il", "8CF", "45x", "508", "hYT", "beo", "0dr", "1a2", "0zp", "YB", "60u", "755", "4or", "5j2", "3f", "9Me", "0Yl", "2lO", "6Wa", "4bB",
|
||||
"4Ln", "6yM", "Tr", "03H", "0xA", "2Mb", "62D", "4Co", "4mC", "7HA", "1W", "0Vm", "8RE", "xo", "5e3", "54Z", "b0d", "aQn", "VC", "01y",
|
||||
"46S", "6Gx", "jG", "0Iu", "0gY", "c", "5Y7", "5LV", "4QG", "6dd", "3Ok", "0ji", "0DE", "gw", "6JH", "5oJ", "44b", "6EI", "hv", "0KD",
|
||||
"0eh", "FZ", "6ke", "5Ng", "4Sv", "5V6", "Kj", "0hX", "0Ft", "eF", "6Hy", "49R", "421", "4cX", "0Xv", "2mU", "Uh", "02R", "4Mt", "5H4",
|
||||
"61o", "5Pe", "M9", "XX", "vt", "0UF", "4nh", "7Kj", "6TJ", "4ai", "0ZG", "yu", "WY", "B8", "4OE", "6zf", "5G5", "4Bu", "1iz", "Zi",
|
||||
"0M", "0Ww", "4lY", "4y8", "6hB", "aW1", "0fO", "2Sl", "kQ", "0Hc", "47E", "6Fn", "aaL", "bDN", "0ES", "fa", "HM", "8bg", "4PQ", "4E0",
|
||||
"4J1", "5Oq", "10W", "GL", "3oP", "0JR", "45t", "504", "6Io", "48D", "59", "dP", "3LL", "0iN", "5BA", "6gC", "4Lb", "6yA", "2Bo", "03D",
|
||||
"o3", "zR", "6Wm", "4bN", "bUM", "a4G", "3j", "0TP", "87l", "YN", "4T3", "4AR", "4NS", "7kQ", "VO", "01u", "1Kp", "xc", "hfw", "54V",
|
||||
"4mO", "6Xl", "uS", "0Va", "0xM", "2Mn", "62H", "4Cc", "0DI", "25b", "6JD", "5oF", "4QK", "6dh", "IW", "0je", "0gU", "o", "6iX", "5LZ",
|
||||
"4rW", "4g6", "jK", "0Iy", "0Fx", "eJ", "4h7", "5mw", "4Sz", "6fY", "Kf", "0hT", "S7", "FV", "6ki", "5Nk", "44n", "6EE", "hz", "0KH",
|
||||
"2p", "0UJ", "4nd", "7Kf", "61c", "5Pi", "M5", "XT", "Ud", "0vV", "4Mx", "5H8", "4v5", "4cT", "0Xz", "2mY", "0A", "1GZ", "4lU", "4y4",
|
||||
"5G9", "4By", "0yW", "Ze", "WU", "B4", "4OI", "6zj", "6TF", "4ae", "0ZK", "yy", "kU", "0Hg", "47A", "6Fj", "6hF", "5MD", "0fK", "Ey",
|
||||
"HI", "2K9", "4PU", "4E4", "6KZ", "5nX", "0EW", "fe", "id", "0JV", "45p", "500", "4J5", "5Ou", "0dz", "GH", "Jx", "0iJ", "4Rd", "6gG",
|
||||
"6Ik", "5li", "q5", "dT", "o7", "zV", "6Wi", "4bJ", "4Lf", "6yE", "Tz", "0wH", "0zx", "YJ", "4T7", "4AV", "4oz", "6ZY", "3n", "0TT",
|
||||
"1Kt", "xg", "6UX", "54R", "4NW", "7kU", "VK", "01q", "0xI", "2Mj", "62L", "4Cg", "4mK", "6Xh", "uW", "0Ve", "4QO", "6dl", "IS", "0ja",
|
||||
"0DM", "25f", "7Za", "5oB", "4rS", "4g2", "jO", "9PD", "0gQ", "k", "aCN", "685", "674", "72t", "Kb", "0hP", "8Od", "eN", "4h3", "49Z",
|
||||
"44j", "6EA", "3nN", "0KL", "S3", "FR", "6km", "5No", "61g", "5Pm", "M1", "XP", "2t", "0UN", "ad0", "7Kb", "429", "4cP", "8Qf", "39t",
|
||||
"0c3", "02Z", "b3G", "aRM", "63V", "bxN", "0yS", "Za", "0E", "235", "4lQ", "4y0", "6TB", "4aa", "0ZO", "2ol", "WQ", "B0", "4OM", "6zn",
|
||||
"4i4", "5lt", "1WZ", "dI", "Je", "0iW", "4Ry", "5W9", "6jj", "5Oh", "R4", "GU", "iy", "0JK", "45m", "6DF", "6KG", "5nE", "0EJ", "fx",
|
||||
"HT", "0kf", "4PH", "6ek", "5X8", "5MY", "0fV", "Ed", "kH", "0Hz", "4sT", "4f5", "4mV", "4x7", "1B", "0Vx", "0xT", "0m5", "62Q", "4Cz",
|
||||
"4NJ", "7kH", "VV", "C7", "1Ki", "xz", "6UE", "54O", "4og", "6ZD", "3s", "0TI", "L6", "YW", "6th", "4AK", "6l9", "6yX", "Tg", "0wU",
|
||||
"0Yy", "zK", "4w6", "4bW", "11T", "FO", "4K2", "5Nr", "44w", "517", "hc", "0KQ", "p2", "eS", "6Hl", "49G", "4Sc", "72i", "3MO", "0hM",
|
||||
"0gL", "v", "6iA", "5LC", "46F", "6Gm", "jR", "1YA", "0DP", "gb", "hyv", "bEM", "4QR", "4D3", "IN", "8cd", "WL", "00v", "4OP", "4Z1",
|
||||
"hgt", "55U", "0ZR", "0O3", "0X", "a1", "4lL", "6Yo", "63K", "5RA", "0yN", "2Lm", "2Cl", "02G", "4Ma", "6xB", "6Vn", "4cM", "n0", "39i",
|
||||
"2i", "0US", "bTN", "a5D", "4U0", "5Pp", "86o", "XM", "Ja", "0iS", "667", "73w", "4i0", "48Y", "8Ng", "dM", "3oM", "0JO", "45i", "6DB",
|
||||
"6jn", "5Ol", "R0", "GQ", "HP", "0kb", "4PL", "6eo", "6KC", "5nA", "0EN", "24e", "kL", "8Af", "47X", "4f1", "aBM", "696", "0fR", "0s3",
|
||||
"0xP", "0m1", "62U", "byM", "4mR", "4x3", "1F", "226", "1Km", "2no", "6UA", "54K", "4NN", "7kL", "VR", "C3", "L2", "YS", "60d", "4AO",
|
||||
"4oc", "7Ja", "3w", "0TM", "8Pe", "zO", "4w2", "4bS", "b2D", "aSN", "Tc", "03Y", "44s", "513", "hg", "0KU", "0ey", "FK", "4K6", "5Nv",
|
||||
"4Sg", "6fD", "3MK", "0hI", "p6", "eW", "6Hh", "49C", "46B", "6Gi", "jV", "0Id", "0gH", "r", "6iE", "5LG", "4QV", "4D7", "IJ", "0jx",
|
||||
"0DT", "gf", "6JY", "bEI", "5d8", "4ax", "0ZV", "yd", "WH", "00r", "4OT", "4Z5", "63O", "4Bd", "0yJ", "Zx", "tT", "a5", "4lH", "6Yk",
|
||||
"6Vj", "4cI", "n4", "2mD", "Uy", "02C", "4Me", "6xF", "4U4", "5Pt", "1kZ", "XI", "2m", "0UW", "4ny", "5k9", "6jb", "ber", "0do", "2QL",
|
||||
"iq", "0JC", "45e", "6DN", "acl", "48U", "0Gs", "dA", "Jm", "94n", "4Rq", "5W1", "5X0", "5MQ", "12w", "El", "1M2", "0Hr", "47T", "alm",
|
||||
"6KO", "5nM", "0EB", "fp", "3Nl", "0kn", "bjs", "6ec", "4NB", "aQs", "3Pn", "01d", "1Ka", "xr", "6UM", "54G", "59w", "a6g", "1J", "0Vp",
|
||||
"85L", "d3D", "5F2", "4Cr", "4Ls", "5I3", "To", "03U", "0Yq", "zC", "436", "56v", "4oo", "6ZL", "ws", "0TA", "0zm", "2ON", "60h", "4AC",
|
||||
"42", "27B", "6Hd", "49O", "4Sk", "6fH", "Kw", "0hE", "0eu", "FG", "6kx", "5Nz", "4pw", "5u7", "hk", "0KY", "0DX", "gj", "5z6", "5oW",
|
||||
"4QZ", "6dy", "IF", "0jt", "0gD", "Dv", "6iI", "5LK", "46N", "6Ge", "jZ", "0Ih", "0P", "a9", "4lD", "6Yg", "63C", "4Bh", "0yF", "Zt",
|
||||
"WD", "0tv", "4OX", "4Z9", "5d4", "4at", "0ZZ", "yh", "2a", "1Ez", "4nu", "5k5", "4U8", "5Px", "1kV", "XE", "Uu", "02O", "4Mi", "6xJ",
|
||||
"6Vf", "4cE", "n8", "2mH", "iu", "0JG", "45a", "6DJ", "6jf", "5Od", "R8", "GY", "Ji", "1yz", "4Ru", "5W5", "4i8", "48Q", "0Gw", "dE",
|
||||
"kD", "0Hv", "47P", "4f9", "5X4", "5MU", "0fZ", "Eh", "HX", "0kj", "4PD", "6eg", "6KK", "5nI", "0EF", "ft", "1Ke", "xv", "6UI", "54C",
|
||||
"4NF", "7kD", "VZ", "0uh", "0xX", "0m9", "5F6", "4Cv", "4mZ", "6Xy", "1N", "0Vt", "0Yu", "zG", "432", "56r", "4Lw", "5I7", "Tk", "03Q",
|
||||
"0zi", "2OJ", "60l", "4AG", "4ok", "6ZH", "ww", "0TE", "4So", "6fL", "Ks", "0hA", "46", "27F", "7XA", "49K", "4ps", "5u3", "ho", "8BE",
|
||||
"0eq", "FC", "aAn", "bdl", "bkm", "70T", "IB", "0jp", "307", "gn", "5z2", "5oS", "46J", "6Ga", "28G", "0Il", "13i", "z", "6iM", "5LO",
|
||||
"63G", "4Bl", "0yB", "Zp", "0T", "0Wn", "58i", "6Yc", "5d0", "4ap", "8SF", "yl", "1q2", "00z", "b1g", "aPm", "61v", "746", "1kR", "XA",
|
||||
"2e", "9Lf", "4nq", "5k1", "6Vb", "4cA", "0Xo", "2mL", "Uq", "02K", "4Mm", "6xN", "8YG", "7e", "5n1", "4kq", "716", "64v", "2KP", "1nR",
|
||||
"07K", "Pq", "69F", "4Hm", "4fA", "6Sb", "2hL", "1MN", "0Rn", "5T", "7LB", "5ya", "4Gl", "66G", "2Ia", "08J", "05z", "1t2", "aUm", "b4g",
|
||||
"4dp", "5a0", "8d", "8VF", "bn", "357", "4zr", "6OQ", "75T", "bnm", "0op", "LB", "Ar", "16i", "4Yn", "6lM", "6Ba", "43J", "0Ll", "2yO",
|
||||
"22F", "16", "4xC", "agr", "6cL", "4Vo", "0mA", "Ns", "CC", "14X", "bal", "aDn", "5p3", "4us", "8GE", "mo", "5L7", "4Iw", "06Q", "Qk",
|
||||
"1Y5", "1LT", "53r", "462", "7Oi", "4jk", "0QE", "rw", "2JJ", "1oH", "4DG", "65l", "7nD", "4KF", "0ph", "SZ", "2kg", "1Ne", "4ej", "6PI",
|
||||
"493", "4hZ", "0St", "4N", "0h9", "09P", "4Fv", "5C6", "4Xt", "6mW", "023", "0cZ", "0Mv", "nD", "4c9", "42P", "5kI", "6NK", "ct", "1Pg",
|
||||
"X9", "MX", "74N", "4UD", "4ZE", "6of", "BY", "W8", "0OG", "lu", "6AJ", "40a", "4yY", "4l8", "aE", "0Bw", "18r", "Oi", "5R5", "4Wu",
|
||||
"4EY", "4P8", "2KT", "1nV", "8YC", "7a", "5n5", "4ku", "4fE", "6Sf", "2hH", "k8", "07O", "Pu", "69B", "4Hi", "4Gh", "66C", "2Ie", "08N",
|
||||
"d9", "5P", "7LF", "4iD", "4dt", "5a4", "2jy", "3o9", "0qv", "RD", "7oZ", "4JX", "6ay", "4TZ", "0ot", "LF", "bj", "0AX", "4zv", "6OU",
|
||||
"6Be", "43N", "0Lh", "oZ", "Av", "0bD", "4Yj", "6lI", "6cH", "4Vk", "0mE", "Nw", "22B", "12", "4xG", "6Md", "5p7", "4uw", "0NY", "mk",
|
||||
"CG", "1pT", "5Kz", "6nx", "1Y1", "1LP", "53v", "466", "5L3", "4Is", "06U", "Qo", "2JN", "1oL", "4DC", "65h", "7Om", "4jo", "0QA", "rs",
|
||||
"9z", "1Na", "4en", "6PM", "aTs", "4KB", "04d", "2EO", "d6D", "09T", "4Fr", "5C2", "497", "bRm", "0Sp", "4J", "0Mr", "1H2", "aim", "42T",
|
||||
"4Xp", "6mS", "027", "17w", "0nn", "3Kl", "74J", "5Ea", "5kM", "6NO", "cp", "1Pc", "0OC", "lq", "6AN", "40e", "4ZA", "6ob", "2TL", "0ao",
|
||||
"18v", "Om", "5R1", "4Wq", "bCn", "afl", "aA", "0Bs", "07C", "Py", "69N", "4He", "4fI", "6Sj", "2hD", "k4", "0PW", "7m", "5n9", "4ky",
|
||||
"4EU", "4P4", "2KX", "1nZ", "05r", "RH", "7oV", "4JT", "4dx", "5a8", "8l", "1Ow", "d5", "qT", "7LJ", "4iH", "4Gd", "66O", "2Ii", "08B",
|
||||
"Az", "0bH", "4Yf", "6lE", "6Bi", "43B", "z7", "oV", "bf", "0AT", "4zz", "6OY", "4A7", "4TV", "0ox", "LJ", "CK", "14P", "5Kv", "4N6",
|
||||
"543", "41s", "0NU", "mg", "22N", "u6", "4xK", "6Mh", "6cD", "4Vg", "0mI", "2Xj", "7Oa", "4jc", "0QM", "6w", "2JB", "I2", "4DO", "65d",
|
||||
"68T", "b7D", "06Y", "Qc", "dSm", "287", "4gS", "4r2", "7MP", "4hR", "276", "4F", "0h1", "09X", "b8E", "67U", "7nL", "4KN", "F3", "SR",
|
||||
"9v", "1Nm", "4eb", "6PA", "5kA", "6NC", "21e", "1Po", "X1", "MP", "74F", "4UL", "bbO", "79v", "0v3", "0cR", "8Df", "nL", "4c1", "42X",
|
||||
"4yQ", "4l0", "aM", "8Kg", "0lS", "Oa", "76w", "637", "4ZM", "6on", "BQ", "W0", "0OO", "2zl", "6AB", "40i", "4fM", "6Sn", "3xa", "k0",
|
||||
"07G", "2Fl", "69J", "4Ha", "4EQ", "4P0", "d5g", "83o", "0PS", "7i", "a0D", "bQN", "50U", "hbt", "8h", "1Os", "05v", "RL", "7oR", "4JP",
|
||||
"5WA", "66K", "2Im", "08F", "d1", "5X", "7LN", "4iL", "6Bm", "43F", "z3", "oR", "2Wo", "0bL", "4Yb", "6lA", "4A3", "4TR", "8fd", "LN",
|
||||
"bb", "0AP", "cPl", "aeO", "547", "41w", "0NQ", "mc", "CO", "14T", "5Kr", "4N2", "77i", "4Vc", "0mM", "2Xn", "22J", "u2", "4xO", "6Ml",
|
||||
"2JF", "I6", "4DK", "6qh", "7Oe", "4jg", "0QI", "6s", "1Y9", "1LX", "4gW", "4r6", "68P", "5YZ", "0rU", "Qg", "0h5", "1mu", "4Fz", "67Q",
|
||||
"7MT", "4hV", "0Sx", "4B", "9r", "1Ni", "4ef", "6PE", "7nH", "4KJ", "F7", "SV", "X5", "MT", "74B", "4UH", "5kE", "6NG", "cx", "1Pk",
|
||||
"0Mz", "nH", "4c5", "4vT", "4Xx", "79r", "0v7", "0cV", "0lW", "Oe", "5R9", "4Wy", "4yU", "4l4", "aI", "1RZ", "0OK", "ly", "6AF", "40m",
|
||||
"4ZI", "6oj", "BU", "W4", "265", "5E", "488", "4iQ", "b9F", "66V", "0i2", "1lr", "G0", "RQ", "7oO", "4JM", "4da", "6QB", "8u", "1On",
|
||||
"0PN", "7t", "7Nb", "aa0", "4EL", "64g", "2KA", "H1", "07Z", "0f3", "69W", "b6G", "4fP", "479", "dRn", "294", "22W", "8Jd", "4xR", "4m3",
|
||||
"77t", "624", "0mP", "Nb", "CR", "V3", "5Ko", "6nm", "ajS", "41j", "0NL", "3kN", "20f", "0AM", "4zc", "aeR", "6al", "4TO", "Y2", "LS",
|
||||
"Ac", "0bQ", "bcL", "78u", "4b2", "4wS", "8Ee", "oO", "7nU", "4KW", "04q", "SK", "9o", "1Nt", "51R", "6PX", "7MI", "4hK", "e6", "pW",
|
||||
"2Hj", "09A", "4Fg", "67L", "68M", "4If", "0rH", "Qz", "2iG", "j7", "4gJ", "6Ri", "7Ox", "4jz", "0QT", "6n", "1z8", "1oY", "4DV", "4Q7",
|
||||
"4ZT", "4O5", "BH", "0az", "0OV", "ld", "550", "40p", "4yH", "6Lk", "aT", "t5", "0lJ", "Ox", "6bG", "4Wd", "4Xe", "6mF", "2Vh", "0cK",
|
||||
"0Mg", "nU", "6Cj", "42A", "5kX", "6NZ", "ce", "1Pv", "2N9", "MI", "7pW", "4UU", "4Gy", "5B9", "0i6", "1lv", "1BZ", "5A", "7LW", "4iU",
|
||||
"4de", "6QF", "8q", "1Oj", "G4", "RU", "7oK", "4JI", "4EH", "64c", "2KE", "H5", "0PJ", "7p", "7Nf", "4kd", "4fT", "4s5", "2hY", "290",
|
||||
"0sV", "Pd", "5M8", "4Hx", "6cY", "4Vz", "0mT", "Nf", "1F8", "0Cx", "4xV", "4m7", "7Pd", "41n", "0NH", "mz", "CV", "V7", "5Kk", "6ni",
|
||||
"6ah", "4TK", "Y6", "LW", "20b", "0AI", "4zg", "6OD", "4b6", "4wW", "0Ly", "oK", "Ag", "0bU", "5IZ", "6lX", "9k", "1Np", "51V", "azN",
|
||||
"7nQ", "4KS", "04u", "SO", "2Hn", "09E", "4Fc", "67H", "7MM", "4hO", "e2", "pS", "2iC", "j3", "4gN", "6Rm", "68I", "4Ib", "06D", "2Go",
|
||||
"d4d", "82l", "4DR", "4Q3", "a1G", "bPM", "0QP", "6j", "0OR", "0Z3", "554", "40t", "4ZP", "4O1", "BL", "15W", "0lN", "2Ym", "6bC", "5GA",
|
||||
"4yL", "6Lo", "aP", "09", "0Mc", "nQ", "6Cn", "42E", "4Xa", "6mB", "2Vl", "0cO", "8gg", "MM", "7pS", "4UQ", "bAN", "adL", "ca", "1Pr",
|
||||
"G8", "RY", "7oG", "4JE", "4di", "6QJ", "2jd", "1Of", "0Rw", "5M", "480", "4iY", "4Gu", "5B5", "2Ix", "08S", "07R", "Ph", "5M4", "4Ht",
|
||||
"4fX", "471", "1X6", "1MW", "0PF", "st", "7Nj", "4kh", "4ED", "64o", "2KI", "H9", "CZ", "14A", "5Kg", "6ne", "7Ph", "41b", "0ND", "mv",
|
||||
"1F4", "0Ct", "4xZ", "6My", "5S6", "4Vv", "0mX", "Nj", "Ak", "0bY", "4Yw", "6lT", "6Bx", "43S", "0Lu", "oG", "bw", "0AE", "4zk", "6OH",
|
||||
"6ad", "4TG", "0oi", "2ZJ", "7MA", "4hC", "0Sm", "4W", "2Hb", "09I", "4Fo", "67D", "aTn", "b5d", "04y", "SC", "9g", "8WE", "4es", "6PP",
|
||||
"5o2", "4jr", "8XD", "6f", "1z0", "1oQ", "705", "65u", "68E", "4In", "06H", "Qr", "2iO", "1LM", "4gB", "6Ra", "5ia", "6Lc", "23E", "05",
|
||||
"0lB", "Op", "6bO", "4Wl", "c4F", "aEm", "1d2", "0ar", "8FF", "ll", "558", "40x", "5kP", "6NR", "cm", "344", "0ns", "MA", "74W", "bon",
|
||||
"4Xm", "6mN", "3FA", "0cC", "0Mo", "2xL", "6Cb", "42I", "4dm", "6QN", "8y", "1Ob", "05g", "2DL", "7oC", "4JA", "4Gq", "5B1", "d7G", "08W",
|
||||
"0Rs", "5I", "484", "bSn", "52u", "475", "1X2", "1MS", "07V", "Pl", "5M0", "4Hp", "5Ua", "64k", "2KM", "1nO", "0PB", "7x", "7Nn", "4kl",
|
||||
"7Pl", "41f", "8GX", "mr", "2UO", "14E", "5Kc", "6na", "5S2", "4Vr", "19u", "Nn", "1F0", "0Cp", "bBm", "ago", "ahn", "43W", "0Lq", "oC",
|
||||
"Ao", "16t", "4Ys", "6lP", "75I", "4TC", "0om", "2ZN", "bs", "0AA", "4zo", "6OL", "2Hf", "09M", "4Fk", "6sH", "7ME", "4hG", "0Si", "4S",
|
||||
"9c", "1Nx", "4ew", "6PT", "7nY", "bqh", "0pu", "SG", "1z4", "1oU", "4DZ", "65q", "5o6", "4jv", "0QX", "6b", "2iK", "1LI", "4gF", "6Re",
|
||||
"68A", "4Ij", "06L", "Qv", "0lF", "Ot", "6bK", "4Wh", "4yD", "6Lg", "aX", "01", "0OZ", "lh", "5q4", "4tt", "4ZX", "4O9", "BD", "0av",
|
||||
"0nw", "ME", "74S", "4UY", "5kT", "6NV", "ci", "1Pz", "0Mk", "nY", "6Cf", "42M", "4Xi", "6mJ", "2Vd", "0cG", "bL", "8Hf", "4zP", "4o1",
|
||||
"75v", "606", "0oR", "0z3", "AP", "T1", "4YL", "6lo", "6BC", "43h", "0LN", "2ym", "22d", "0CO", "4xa", "6MB", "6cn", "4VM", "0mc", "NQ",
|
||||
"Ca", "14z", "baN", "aDL", "7PS", "41Y", "8Gg", "mM", "247", "7G", "7NQ", "4kS", "com", "64T", "0k0", "1np", "E2", "PS", "69d", "4HO",
|
||||
"4fc", "7Ca", "2hn", "1Ml", "0RL", "5v", "avS", "4ib", "4GN", "66e", "2IC", "J3", "05X", "Rb", "aUO", "b4E", "4dR", "4q3", "8F", "8Vd",
|
||||
"4XV", "4M7", "1f8", "0cx", "0MT", "nf", "572", "42r", "5kk", "6Ni", "cV", "v7", "0nH", "Mz", "74l", "4Uf", "4Zg", "6oD", "2Tj", "0aI",
|
||||
"y6", "lW", "6Ah", "40C", "5iZ", "583", "ag", "0BU", "0ly", "OK", "4B6", "4WW", "7lW", "4IU", "06s", "QI", "0I6", "1Lv", "4gy", "5b9",
|
||||
"7OK", "4jI", "g4", "rU", "2Jh", "1oj", "4De", "65N", "7nf", "4Kd", "04B", "Sx", "2kE", "h5", "4eH", "6Pk", "5m8", "4hx", "0SV", "4l",
|
||||
"2HY", "09r", "4FT", "4S5", "5Q8", "4Tx", "0oV", "Ld", "bH", "0Az", "4zT", "4o5", "6BG", "43l", "0LJ", "ox", "AT", "T5", "4YH", "6lk",
|
||||
"6cj", "4VI", "0mg", "NU", "2vh", "0CK", "4xe", "6MF", "7PW", "4uU", "2n9", "mI", "Ce", "1pv", "5KX", "6nZ", "5UZ", "64P", "0k4", "1nt",
|
||||
"0Py", "7C", "7NU", "4kW", "4fg", "6SD", "2hj", "1Mh", "E6", "PW", "7mI", "4HK", "4GJ", "66a", "2IG", "J7", "0RH", "5r", "7Ld", "4if",
|
||||
"4dV", "4q7", "8B", "1OY", "0qT", "Rf", "7ox", "4Jz", "0MP", "nb", "576", "42v", "4XR", "4M3", "dll", "17U", "0nL", "3KN", "74h", "4Ub",
|
||||
"5ko", "6Nm", "cR", "v3", "y2", "lS", "6Al", "40G", "4Zc", "aER", "2Tn", "0aM", "18T", "OO", "4B2", "4WS", "bCL", "587", "ac", "0BQ",
|
||||
"0I2", "1Lr", "53T", "axL", "68z", "4IQ", "06w", "QM", "2Jl", "1on", "4Da", "65J", "7OO", "4jM", "g0", "6Y", "9X", "h1", "4eL", "6Po",
|
||||
"7nb", "aA0", "04F", "2Em", "d6f", "09v", "4FP", "4S1", "a3E", "bRO", "0SR", "4h", "AX", "T9", "4YD", "6lg", "6BK", "4wh", "0LF", "ot",
|
||||
"bD", "0Av", "4zX", "4o9", "5Q4", "4Tt", "0oZ", "Lh", "Ci", "14r", "5KT", "6nV", "ajh", "41Q", "0Nw", "mE", "22l", "0CG", "4xi", "6MJ",
|
||||
"6cf", "4VE", "0mk", "NY", "07a", "2FJ", "69l", "4HG", "4fk", "6SH", "2hf", "1Md", "0Pu", "7O", "7NY", "bQh", "4Ew", "6pT", "0k8", "1nx",
|
||||
"05P", "Rj", "5O6", "4Jv", "4dZ", "453", "8N", "1OU", "0RD", "qv", "7Lh", "4ij", "4GF", "66m", "2IK", "1lI", "5kc", "6Na", "21G", "27",
|
||||
"8gX", "Mr", "74d", "4Un", "bbm", "79T", "1f0", "0cp", "397", "nn", "5s2", "42z", "4ys", "6LP", "ao", "366", "0lq", "OC", "76U", "bml",
|
||||
"4Zo", "6oL", "Bs", "0aA", "0Om", "2zN", "7QA", "40K", "7OC", "4jA", "0Qo", "6U", "3ZA", "1ob", "4Dm", "65F", "68v", "b7f", "0rs", "QA",
|
||||
"dSO", "8UG", "4gq", "5b1", "5m0", "4hp", "8ZF", "4d", "1x2", "09z", "727", "67w", "7nn", "4Kl", "04J", "Sp", "9T", "1NO", "51i", "6Pc",
|
||||
"6BO", "43d", "0LB", "op", "2WM", "0bn", "5Ia", "6lc", "5Q0", "4Tp", "8fF", "Ll", "1D2", "0Ar", "cPN", "aem", "ajl", "41U", "0Ns", "mA",
|
||||
"Cm", "14v", "5KP", "6nR", "6cb", "4VA", "0mo", "2XL", "22h", "0CC", "4xm", "6MN", "4fo", "6SL", "2hb", "8TY", "07e", "2FN", "69h", "4HC",
|
||||
"4Es", "64X", "d5E", "83M", "0Pq", "7K", "a0f", "bQl", "50w", "457", "8J", "1OQ", "05T", "Rn", "5O2", "4Jr", "4GB", "66i", "2IO", "08d",
|
||||
"1Ba", "5z", "7Ll", "4in", "0nD", "Mv", "7ph", "4Uj", "5kg", "6Ne", "cZ", "23", "0MX", "nj", "5s6", "4vv", "4XZ", "6my", "1f4", "0ct",
|
||||
"0lu", "OG", "6bx", "5Gz", "4yw", "6LT", "ak", "0BY", "0Oi", "2zJ", "6Ad", "40O", "4Zk", "6oH", "Bw", "0aE", "2Jd", "1of", "4Di", "65B",
|
||||
"7OG", "4jE", "g8", "6Q", "2ix", "1Lz", "4gu", "5b5", "68r", "4IY", "0rw", "QE", "1x6", "1mW", "4FX", "4S9", "5m4", "4ht", "0SZ", "ph",
|
||||
"9P", "h9", "4eD", "6Pg", "7nj", "4Kh", "04N", "St", "22u", "375", "4xp", "598", "77V", "blo", "0mr", "1h2", "Cp", "14k", "5KM", "6nO",
|
||||
"7PB", "41H", "0Nn", "3kl", "20D", "34", "4zA", "6Ob", "6aN", "4Tm", "0oC", "Lq", "AA", "0bs", "bcn", "78W", "569", "43y", "384", "om",
|
||||
"9Kd", "5g", "5l3", "4is", "734", "66t", "1y1", "08y", "05I", "Rs", "7om", "4Jo", "4dC", "7AA", "8W", "1OL", "0Pl", "7V", "ats", "4kB",
|
||||
"4En", "64E", "2Kc", "1na", "07x", "PB", "69u", "b6e", "4fr", "5c2", "dRL", "8TD", "4Zv", "6oU", "Bj", "0aX", "0Ot", "lF", "6Ay", "40R",
|
||||
"4yj", "6LI", "av", "0BD", "0lh", "OZ", "6be", "4WF", "4XG", "6md", "2VJ", "0ci", "0ME", "nw", "6CH", "42c", "5kz", "6Nx", "cG", "1PT",
|
||||
"0nY", "Mk", "5P7", "4Uw", "5N5", "4Ku", "04S", "Si", "9M", "1NV", "4eY", "440", "7Mk", "4hi", "0SG", "pu", "2HH", "K8", "4FE", "67n",
|
||||
"68o", "4ID", "1", "QX", "2ie", "1Lg", "4gh", "6RK", "7OZ", "4jX", "0Qv", "6L", "2Jy", "3O9", "4Dt", "5A4", "4C9", "4VX", "0mv", "ND",
|
||||
"22q", "0CZ", "4xt", "6MW", "7PF", "41L", "x9", "mX", "Ct", "14o", "5KI", "6nK", "6aJ", "4Ti", "0oG", "Lu", "bY", "30", "4zE", "6Of",
|
||||
"5r5", "4wu", "380", "oi", "AE", "0bw", "4YY", "4L8", "5Wz", "66p", "1y5", "1lT", "0RY", "5c", "5l7", "4iw", "4dG", "6Qd", "8S", "1OH",
|
||||
"05M", "Rw", "7oi", "4Jk", "4Ej", "64A", "2Kg", "1ne", "0Ph", "7R", "7ND", "4kF", "4fv", "5c6", "0H9", "1My", "0st", "PF", "69q", "4HZ",
|
||||
"0Op", "lB", "ako", "40V", "4Zr", "6oQ", "Bn", "15u", "0ll", "2YO", "6ba", "4WB", "4yn", "6LM", "ar", "1Ra", "0MA", "ns", "6CL", "42g",
|
||||
"4XC", "79I", "2VN", "0cm", "8gE", "Mo", "5P3", "4Us", "bAl", "adn", "cC", "1PP", "9I", "1NR", "51t", "444", "5N1", "4Kq", "04W", "Sm",
|
||||
"2HL", "09g", "4FA", "67j", "7Mo", "4hm", "0SC", "4y", "2ia", "1Lc", "4gl", "6RO", "68k", "5Ya", "5", "2GM", "d4F", "82N", "4Dp", "5A0",
|
||||
"a1e", "bPo", "0Qr", "6H", "Cx", "14c", "5KE", "6nG", "7PJ", "4uH", "x5", "mT", "0V7", "0CV", "4xx", "590", "4C5", "4VT", "0mz", "NH",
|
||||
"AI", "16R", "4YU", "4L4", "561", "43q", "0LW", "oe", "bU", "w4", "4zI", "6Oj", "6aF", "4Te", "0oK", "Ly", "05A", "2Dj", "7oe", "4Jg",
|
||||
"4dK", "6Qh", "2jF", "i6", "0RU", "5o", "7Ly", "5yZ", "4GW", "4R6", "1y9", "08q", "07p", "PJ", "7mT", "4HV", "4fz", "6SY", "0H5", "1Mu",
|
||||
"f7", "sV", "7NH", "4kJ", "4Ef", "64M", "2Kk", "1ni", "4yb", "6LA", "23g", "0BL", "Z3", "OR", "6bm", "4WN", "c4d", "aEO", "Bb", "0aP",
|
||||
"8Fd", "lN", "4a3", "40Z", "5kr", "4n2", "cO", "8Ie", "0nQ", "Mc", "74u", "615", "4XO", "6ml", "2VB", "U2", "0MM", "2xn", "7Sa", "42k",
|
||||
"7Mc", "4ha", "0SO", "4u", "3Xa", "K0", "4FM", "67f", "aTL", "b5F", "0pS", "Sa", "9E", "8Wg", "4eQ", "448", "7OR", "4jP", "254", "6D",
|
||||
"0j3", "1os", "cnn", "65W", "68g", "4IL", "9", "QP", "2im", "1Lo", "53I", "6RC", "7PN", "41D", "x1", "mP", "2Um", "14g", "5KA", "6nC",
|
||||
"4C1", "4VP", "19W", "NL", "0V3", "0CR", "bBO", "594", "565", "43u", "0LS", "oa", "AM", "16V", "4YQ", "4L0", "6aB", "4Ta", "0oO", "2Zl",
|
||||
"bQ", "38", "4zM", "6On", "4dO", "6Ql", "2jB", "i2", "05E", "2Dn", "7oa", "4Jc", "4GS", "4R2", "d7e", "08u", "0RQ", "5k", "a2F", "bSL",
|
||||
"52W", "ayO", "0H1", "1Mq", "07t", "PN", "69y", "4HR", "4Eb", "64I", "2Ko", "1nm", "f3", "7Z", "7NL", "4kN", "Z7", "OV", "6bi", "4WJ",
|
||||
"4yf", "6LE", "az", "0BH", "0Ox", "lJ", "4a7", "4tV", "4Zz", "6oY", "Bf", "0aT", "0nU", "Mg", "74q", "5EZ", "5kv", "4n6", "cK", "1PX",
|
||||
"0MI", "2xj", "6CD", "42o", "4XK", "6mh", "2VF", "U6", "2HD", "K4", "4FI", "67b", "7Mg", "4he", "0SK", "4q", "9A", "1NZ", "4eU", "4p4",
|
||||
"5N9", "4Ky", "0pW", "Se", "0j7", "1ow", "4Dx", "5A8", "7OV", "4jT", "0Qz", "rH", "2ii", "1Lk", "4gd", "6RG", "68c", "4IH", "D5", "QT",
|
||||
"5Ls", "4I3", "F", "13U", "0IP", "jb", "536", "46v", "5oo", "6Jm", "gR", "r3", "0jL", "3ON", "6dA", "4Qb", "5NB", "aAR", "2Pn", "0eM",
|
||||
"0Ka", "hS", "6El", "44G", "49w", "abN", "ec", "0FQ", "8ae", "KO", "4F2", "4SS", "4X0", "4MQ", "02w", "UM", "0M2", "0XS", "57T", "a8D",
|
||||
"7KO", "4nM", "c0", "2Y", "2Nl", "1kn", "aJ1", "61J", "6zC", "aE0", "00F", "2Am", "yP", "l1", "4aL", "6To", "a7E", "58U", "0WR", "0h",
|
||||
"ZL", "84n", "4BP", "4W1", "fH", "0Ez", "5nu", "4k5", "5U8", "4Px", "0kV", "Hd", "ET", "P5", "5Mi", "6hk", "6FG", "47l", "0HJ", "kx",
|
||||
"dy", "0GK", "48m", "6IF", "6gj", "4RI", "0ig", "JU", "Ge", "0dW", "5OX", "5Z9", "4d4", "4qU", "1ZZ", "iI", "0Ty", "3C", "4z6", "4oW",
|
||||
"5QZ", "60P", "Yg", "0zU", "A6", "TW", "6yh", "4LK", "4bg", "6WD", "2lj", "0YI", "0VH", "1r", "6XE", "4mf", "4CJ", "62a", "2MG", "N7",
|
||||
"0uT", "Vf", "7kx", "4Nz", "5pw", "4u7", "xJ", "1KY", "0IT", "jf", "532", "46r", "5Lw", "4I7", "B", "0gx", "0jH", "Iz", "6dE", "4Qf",
|
||||
"5ok", "6Ji", "gV", "r7", "0Ke", "hW", "6Eh", "44C", "5NF", "6kD", "2Pj", "0eI", "0hy", "KK", "4F6", "4SW", "49s", "6HX", "eg", "0FU",
|
||||
"0M6", "0XW", "4cy", "5f9", "4X4", "4MU", "02s", "UI", "Xy", "1kj", "5PD", "61N", "7KK", "4nI", "c4", "vU", "yT", "l5", "4aH", "6Tk",
|
||||
"6zG", "4Od", "00B", "Wx", "ZH", "0yz", "4BT", "4W5", "5i8", "4lx", "0WV", "0l", "71v", "646", "0kR", "3NP", "fL", "8Lf", "5nq", "4k1",
|
||||
"6FC", "47h", "0HN", "29e", "EP", "P1", "5Mm", "6ho", "6gn", "4RM", "0ic", "JQ", "26d", "0GO", "48i", "6IB", "4d0", "45Y", "8Cg", "iM",
|
||||
"Ga", "0dS", "beN", "hYu", "ckm", "60T", "Yc", "0zQ", "207", "3G", "4z2", "4oS", "4bc", "7Ga", "2ln", "0YM", "A2", "TS", "6yl", "4LO",
|
||||
"4CN", "62e", "2MC", "N3", "0VL", "1v", "6XA", "4mb", "5ps", "4u3", "xN", "8Rd", "01X", "Vb", "aQO", "b0E", "5og", "6Je", "gZ", "63",
|
||||
"0jD", "Iv", "6dI", "4Qj", "7l9", "6iy", "N", "0gt", "0IX", "jj", "5w6", "4rv", "5mV", "5x7", "ek", "0FY", "0hu", "KG", "6fx", "5Cz",
|
||||
"5NJ", "6kH", "Fw", "0eE", "92", "3nk", "6Ed", "44O", "7KG", "4nE", "c8", "2Q", "Xu", "1kf", "5PH", "61B", "4X8", "4MY", "0vw", "UE",
|
||||
"2mx", "1Hz", "4cu", "5f5", "5i4", "4lt", "0WZ", "th", "ZD", "0yv", "4BX", "4W9", "6zK", "4Oh", "00N", "Wt", "yX", "l9", "4aD", "6Tg",
|
||||
"2SM", "0fn", "5Ma", "6hc", "6FO", "47d", "0HB", "kp", "24Y", "0Er", "bDo", "aam", "5U0", "4Pp", "8bF", "Hl", "Gm", "10v", "5OP", "5Z1",
|
||||
"anl", "45U", "0Js", "iA", "dq", "0GC", "48e", "6IN", "6gb", "4RA", "0io", "3Lm", "03e", "2BN", "7iA", "4LC", "4bo", "6WL", "zs", "0YA",
|
||||
"0Tq", "3K", "a4f", "bUl", "4As", "5D3", "Yo", "87M", "01T", "Vn", "5K2", "4Nr", "54w", "417", "xB", "1KQ", "1Fa", "1z", "6XM", "4mn",
|
||||
"4CB", "62i", "2MO", "0xl", "1za", "Ir", "6dM", "4Qn", "5oc", "6Ja", "25G", "67", "9Pe", "jn", "5w2", "46z", "bfm", "aCo", "J", "0gp",
|
||||
"0hq", "KC", "72U", "bil", "5mR", "5x3", "eo", "326", "96", "3no", "7UA", "44K", "5NN", "6kL", "Fs", "0eA", "Xq", "1kb", "5PL", "61F",
|
||||
"7KC", "4nA", "0Uo", "2U", "39U", "8QG", "4cq", "5f1", "aRl", "796", "0vs", "UA", "2LQ", "0yr", "767", "63w", "5i0", "4lp", "9Ng", "0d",
|
||||
"2oM", "0Zn", "55i", "6Tc", "6zO", "4Ol", "00J", "Wp", "6FK", "4sh", "0HF", "kt", "EX", "P9", "5Me", "6hg", "5U4", "4Pt", "0kZ", "Hh",
|
||||
"fD", "0Ev", "5ny", "4k9", "4d8", "45Q", "0Jw", "iE", "Gi", "10r", "5OT", "5Z5", "6gf", "4RE", "0ik", "JY", "du", "0GG", "48a", "6IJ",
|
||||
"4bk", "6WH", "zw", "0YE", "03a", "2BJ", "6yd", "4LG", "4Aw", "5D7", "Yk", "0zY", "0Tu", "3O", "6Zx", "bUh", "54s", "413", "xF", "1KU",
|
||||
"01P", "Vj", "5K6", "4Nv", "4CF", "62m", "2MK", "0xh", "0VD", "uv", "6XI", "4mj", "5NS", "6kQ", "Fn", "11u", "0Kp", "hB", "aoo", "44V",
|
||||
"49f", "6HM", "er", "1Va", "0hl", "3Mn", "6fa", "4SB", "5Lb", "7yA", "W", "0gm", "0IA", "js", "6GL", "46g", "bEl", "hyW", "gC", "0Dq",
|
||||
"8cE", "Io", "5T3", "4Qs", "5J1", "4Oq", "00W", "Wm", "yA", "0Zs", "55t", "404", "6YN", "4lm", "0WC", "0y", "2LL", "0yo", "4BA", "63j",
|
||||
"6xc", "bws", "02f", "2CM", "2ma", "0XB", "4cl", "6VO", "a5e", "bTo", "0Ur", "2H", "Xl", "86N", "5PQ", "5E0", "dh", "0GZ", "5lU", "5y4",
|
||||
"4G9", "4RX", "0iv", "JD", "Gt", "0dF", "5OI", "6jK", "6Dg", "45L", "81", "iX", "fY", "70", "5nd", "6Kf", "6eJ", "4Pi", "0kG", "Hu",
|
||||
"EE", "0fw", "5Mx", "4H8", "5v5", "4su", "1Xz", "ki", "0VY", "1c", "5h7", "4mw", "5Sz", "62p", "2MV", "0xu", "01M", "Vw", "7ki", "4Nk",
|
||||
"54n", "6Ud", "2nJ", "1KH", "0Th", "3R", "6Ze", "4oF", "4Aj", "60A", "Yv", "0zD", "0wt", "TF", "6yy", "4LZ", "4bv", "5g6", "zj", "0YX",
|
||||
"0Kt", "hF", "6Ey", "44R", "5NW", "6kU", "Fj", "0eX", "0hh", "KZ", "6fe", "4SF", "49b", "6HI", "ev", "0FD", "0IE", "jw", "6GH", "46c",
|
||||
"5Lf", "6id", "S", "0gi", "0jY", "Ik", "5T7", "4Qw", "5oz", "6Jx", "gG", "0Du", "yE", "0Zw", "4aY", "400", "5J5", "4Ou", "00S", "Wi",
|
||||
"ZY", "O8", "4BE", "63n", "6YJ", "4li", "0WG", "tu", "2me", "0XF", "4ch", "6VK", "6xg", "4MD", "02b", "UX", "Xh", "3K9", "5PU", "5E4",
|
||||
"7KZ", "4nX", "0Uv", "2L", "73V", "bho", "0ir", "1l2", "dl", "335", "48x", "5y0", "6Dc", "45H", "85", "3ol", "Gp", "0dB", "5OM", "6jO",
|
||||
"6eN", "4Pm", "0kC", "Hq", "24D", "74", "bDr", "6Kb", "529", "47y", "8AG", "km", "EA", "0fs", "bgn", "aBl", "774", "62t", "199", "0xq",
|
||||
"9Od", "1g", "5h3", "4ms", "54j", "7EA", "2nN", "1KL", "01I", "Vs", "7km", "4No", "4An", "60E", "Yr", "1ja", "0Tl", "3V", "6Za", "4oB",
|
||||
"4br", "5g2", "zn", "8PD", "03x", "TB", "aSo", "785", "49n", "6HE", "ez", "0FH", "0hd", "KV", "6fi", "4SJ", "bdI", "6kY", "Ff", "0eT",
|
||||
"0Kx", "hJ", "4e7", "4pV", "5ov", "4j6", "gK", "0Dy", "0jU", "Ig", "6dX", "5AZ", "5Lj", "6ih", "DW", "Q6", "0II", "28b", "6GD", "46o",
|
||||
"6YF", "4le", "0WK", "0q", "ZU", "O4", "4BI", "63b", "5J9", "4Oy", "0tW", "We", "yI", "1JZ", "4aU", "4t4", "7KV", "4nT", "0Uz", "vH",
|
||||
"Xd", "1kw", "5PY", "5E8", "6xk", "4MH", "02n", "UT", "2mi", "0XJ", "4cd", "6VG", "2Qm", "0dN", "5OA", "6jC", "6Do", "45D", "89", "iP",
|
||||
"0R3", "0GR", "48t", "acM", "4G1", "4RP", "94O", "JL", "EM", "12V", "5Mp", "4H0", "525", "47u", "0HS", "ka", "fQ", "78", "5nl", "6Kn",
|
||||
"6eB", "4Pa", "0kO", "3NM", "01E", "3PO", "7ka", "4Nc", "54f", "6Ul", "xS", "m2", "0VQ", "1k", "a6F", "59V", "4CS", "4V2", "195", "85m",
|
||||
"03t", "TN", "4Y3", "4LR", "56W", "a9G", "zb", "0YP", "b3", "3Z", "6Zm", "4oN", "4Ab", "60I", "2Oo", "0zL", "1xA", "KR", "6fm", "4SN",
|
||||
"49j", "6HA", "27g", "0FL", "8Bd", "hN", "4e3", "44Z", "bdM", "aAO", "Fb", "0eP", "0jQ", "Ic", "70u", "655", "5or", "4j2", "gO", "8Me",
|
||||
"0IM", "28f", "7Wa", "46k", "5Ln", "6il", "DS", "Q2", "ZQ", "O0", "4BM", "63f", "6YB", "4la", "0WO", "0u", "yM", "8Sg", "4aQ", "408",
|
||||
"aPL", "b1F", "0tS", "Wa", "0n3", "1ks", "bzO", "61W", "7KR", "4nP", "214", "2D", "2mm", "0XN", "57I", "6VC", "6xo", "4ML", "02j", "UP",
|
||||
"6Dk", "4qH", "0Jf", "iT", "Gx", "0dJ", "5OE", "6jG", "4G5", "4RT", "0iz", "JH", "dd", "0GV", "48p", "5y8", "521", "47q", "0HW", "ke",
|
||||
"EI", "12R", "5Mt", "4H4", "6eF", "4Pe", "0kK", "Hy", "fU", "s4", "5nh", "6Kj", "54b", "6Uh", "xW", "m6", "01A", "3PK", "7ke", "4Ng",
|
||||
"4CW", "4V6", "191", "0xy", "0VU", "1o", "6XX", "59R", "4bz", "6WY", "zf", "0YT", "03p", "TJ", "4Y7", "4LV", "4Af", "60M", "Yz", "0zH",
|
||||
"b7", "wV", "6Zi", "4oJ", "5H3", "4Ms", "02U", "Uo", "2mR", "0Xq", "57v", "426", "7Km", "4no", "0UA", "vs", "2NN", "1kL", "5Pb", "61h",
|
||||
"6za", "4OB", "00d", "2AO", "yr", "1Ja", "4an", "6TM", "a7g", "58w", "0Wp", "0J", "Zn", "84L", "4Br", "5G2", "5LQ", "5Y0", "d", "13w",
|
||||
"0Ir", "1L2", "amm", "46T", "5oM", "6JO", "gp", "0DB", "0jn", "3Ol", "6dc", "5Aa", "bdr", "6kb", "2PL", "0eo", "0KC", "hq", "6EN", "44e",
|
||||
"49U", "abl", "eA", "0Fs", "8aG", "Km", "5V1", "4Sq", "1Dz", "3a", "5j5", "4ou", "4AY", "4T8", "YE", "0zw", "03O", "Tu", "6yJ", "4Li",
|
||||
"4bE", "6Wf", "zY", "o8", "0Vj", "1P", "6Xg", "4mD", "4Ch", "62C", "2Me", "0xF", "0uv", "VD", "7kZ", "4NX", "5pU", "5e4", "xh", "3k9",
|
||||
"fj", "0EX", "5nW", "6KU", "6ey", "4PZ", "0kt", "HF", "Ev", "0fD", "5MK", "6hI", "6Fe", "47N", "0Hh", "kZ", "26B", "52", "48O", "6Id",
|
||||
"6gH", "4Rk", "0iE", "Jw", "GG", "0du", "5Oz", "6jx", "5t7", "4qw", "0JY", "ik", "2mV", "0Xu", "57r", "422", "5H7", "4Mw", "02Q", "Uk",
|
||||
"2NJ", "1kH", "5Pf", "61l", "7Ki", "4nk", "0UE", "vw", "yv", "0ZD", "4aj", "6TI", "6ze", "4OF", "0th", "WZ", "Zj", "0yX", "4Bv", "5G6",
|
||||
"6Yy", "4lZ", "0Wt", "0N", "0Iv", "jD", "4g9", "46P", "5LU", "5Y4", "Dh", "0gZ", "0jj", "IX", "6dg", "4QD", "5oI", "6JK", "gt", "0DF",
|
||||
"0KG", "hu", "6EJ", "44a", "5Nd", "6kf", "FY", "S8", "1xz", "Ki", "5V5", "4Su", "49Q", "4h8", "eE", "0Fw", "756", "60v", "YA", "0zs",
|
||||
"9Mf", "3e", "5j1", "4oq", "4bA", "6Wb", "2lL", "0Yo", "03K", "Tq", "6yN", "4Lm", "4Cl", "62G", "2Ma", "0xB", "0Vn", "1T", "6Xc", "59i",
|
||||
"54Y", "5e0", "xl", "8RF", "01z", "1p2", "aQm", "b0g", "71T", "bjm", "0kp", "HB", "fn", "317", "5nS", "6KQ", "6Fa", "47J", "0Hl", "29G",
|
||||
"Er", "12i", "5MO", "6hM", "6gL", "4Ro", "0iA", "Js", "26F", "56", "48K", "7YA", "5t3", "4qs", "8CE", "io", "GC", "0dq", "bel", "hYW",
|
||||
"7Ke", "4ng", "0UI", "2s", "XW", "M6", "5Pj", "6uh", "6xX", "6m9", "0vU", "Ug", "2mZ", "0Xy", "4cW", "4v6", "4y7", "4lV", "0Wx", "0B",
|
||||
"Zf", "0yT", "4Bz", "63Q", "6zi", "4OJ", "B7", "WV", "yz", "0ZH", "4af", "6TE", "5oE", "6JG", "gx", "0DJ", "0jf", "IT", "6dk", "4QH",
|
||||
"5LY", "5Y8", "l", "0gV", "0Iz", "jH", "4g5", "4rT", "5mt", "4h4", "eI", "1VZ", "0hW", "Ke", "5V9", "4Sy", "5Nh", "6kj", "FU", "S4",
|
||||
"0KK", "hy", "6EF", "44m", "03G", "2Bl", "6yB", "4La", "4bM", "6Wn", "zQ", "o0", "0TS", "3i", "a4D", "bUN", "4AQ", "4T0", "YM", "87o",
|
||||
"01v", "VL", "7kR", "4NP", "54U", "hft", "0N3", "1Ks", "0Vb", "1X", "6Xo", "4mL", "5SA", "62K", "2Mm", "0xN", "2So", "0fL", "5MC", "6hA",
|
||||
"6Fm", "47F", "1XA", "kR", "fb", "0EP", "bDM", "aaO", "4E3", "4PR", "8bd", "HN", "GO", "10T", "5Or", "4J2", "507", "45w", "0JQ", "ic",
|
||||
"dS", "q2", "48G", "6Il", "73i", "4Rc", "0iM", "3LO", "XS", "M2", "5Pn", "61d", "7Ka", "4nc", "0UM", "2w", "39w", "8Qe", "4cS", "4v2",
|
||||
"aRN", "b3D", "02Y", "Uc", "Zb", "0yP", "bxM", "63U", "4y3", "4lR", "236", "0F", "2oo", "0ZL", "4ab", "6TA", "6zm", "4ON", "B3", "WR",
|
||||
"0jb", "IP", "6do", "4QL", "5oA", "6JC", "25e", "0DN", "9PG", "jL", "4g1", "46X", "686", "aCM", "h", "0gR", "0hS", "Ka", "72w", "677",
|
||||
"49Y", "4h0", "eM", "8Og", "0KO", "3nM", "6EB", "44i", "5Nl", "6kn", "FQ", "S0", "4bI", "6Wj", "zU", "o4", "03C", "Ty", "6yF", "4Le",
|
||||
"4AU", "4T4", "YI", "1jZ", "0TW", "3m", "5j9", "4oy", "54Q", "5e8", "xd", "1Kw", "01r", "VH", "7kV", "4NT", "4Cd", "62O", "2Mi", "0xJ",
|
||||
"0Vf", "uT", "6Xk", "4mH", "6Fi", "47B", "0Hd", "kV", "Ez", "0fH", "5MG", "6hE", "4E7", "4PV", "0kx", "HJ", "ff", "0ET", "bDI", "6KY",
|
||||
"503", "45s", "0JU", "ig", "GK", "0dy", "5Ov", "4J6", "6gD", "4Rg", "0iI", "3LK", "dW", "q6", "48C", "6Ih", "4Z2", "4OS", "00u", "WO",
|
||||
"yc", "0ZQ", "55V", "hgw", "6Yl", "4lO", "a2", "tS", "2Ln", "0yM", "4Bc", "63H", "6xA", "4Mb", "02D", "2Co", "2mC", "n3", "4cN", "6Vm",
|
||||
"a5G", "bTM", "0UP", "2j", "XN", "86l", "5Ps", "4U3", "5Nq", "4K1", "FL", "11W", "0KR", "3nP", "514", "44t", "49D", "6Ho", "eP", "49",
|
||||
"0hN", "3ML", "6fC", "5CA", "aV1", "6iB", "u", "0gO", "0Ic", "jQ", "6Gn", "46E", "bEN", "hyu", "ga", "0DS", "8cg", "IM", "4D0", "4QQ",
|
||||
"1FZ", "1A", "4x4", "4mU", "4Cy", "5F9", "0m6", "0xW", "C4", "VU", "7kK", "4NI", "54L", "6UF", "xy", "1Kj", "0TJ", "3p", "6ZG", "4od",
|
||||
"4AH", "60c", "YT", "L5", "0wV", "Td", "5I8", "4Lx", "4bT", "4w5", "zH", "0Yz", "dJ", "0Gx", "5lw", "4i7", "6gY", "4Rz", "0iT", "Jf",
|
||||
"GV", "R7", "5Ok", "6ji", "6DE", "45n", "0JH", "iz", "24b", "0EI", "5nF", "6KD", "6eh", "4PK", "0ke", "HW", "Eg", "0fU", "5MZ", "6hX",
|
||||
"4f6", "4sW", "0Hy", "kK", "yg", "0ZU", "55R", "6TX", "4Z6", "4OW", "00q", "WK", "2Lj", "0yI", "4Bg", "63L", "6Yh", "4lK", "a6", "tW",
|
||||
"2mG", "n7", "4cJ", "6Vi", "6xE", "4Mf", "0vH", "Uz", "XJ", "1kY", "5Pw", "4U7", "7Kx", "4nz", "0UT", "2n", "0KV", "hd", "510", "44p",
|
||||
"5Nu", "4K5", "FH", "0ez", "0hJ", "Kx", "6fG", "4Sd", "5mi", "6Hk", "eT", "p5", "0Ig", "jU", "6Gj", "46A", "5LD", "6iF", "q", "0gK",
|
||||
"1zZ", "II", "4D4", "4QU", "5oX", "5z9", "ge", "0DW", "byN", "62V", "0m2", "0xS", "225", "1E", "4x0", "4mQ", "54H", "6UB", "2nl", "1Kn",
|
||||
"C0", "VQ", "7kO", "4NM", "4AL", "60g", "YP", "L1", "0TN", "3t", "6ZC", "ae0", "4bP", "439", "zL", "8Pf", "03Z", "0b3", "aSM", "b2G",
|
||||
"73t", "664", "0iP", "Jb", "dN", "8Nd", "48Z", "4i3", "6DA", "45j", "0JL", "3oN", "GR", "R3", "5Oo", "6jm", "6el", "4PO", "0ka", "HS",
|
||||
"24f", "0EM", "5nB", "aaR", "4f2", "4sS", "8Ae", "kO", "Ec", "0fQ", "695", "aBN", "6Yd", "4lG", "0Wi", "0S", "Zw", "0yE", "4Bk", "6wH",
|
||||
"6zx", "buh", "0tu", "WG", "yk", "0ZY", "4aw", "5d7", "5k6", "4nv", "0UX", "2b", "XF", "1kU", "741", "61q", "6xI", "4Mj", "02L", "Uv",
|
||||
"2mK", "0Xh", "4cF", "6Ve", "49L", "6Hg", "eX", "41", "0hF", "Kt", "6fK", "4Sh", "5Ny", "4K9", "FD", "0ev", "0KZ", "hh", "5u4", "4pt",
|
||||
"5oT", "5z5", "gi", "1Tz", "0jw", "IE", "4D8", "4QY", "5LH", "6iJ", "Du", "0gG", "0Ik", "jY", "6Gf", "46M", "01g", "3Pm", "7kC", "4NA",
|
||||
"54D", "6UN", "xq", "1Kb", "0Vs", "1I", "a6d", "59t", "4Cq", "5F1", "d3G", "85O", "03V", "Tl", "5I0", "4Lp", "56u", "435", "2lQ", "0Yr",
|
||||
"0TB", "3x", "6ZO", "4ol", "5Qa", "60k", "2OM", "0zn", "2QO", "0dl", "5Oc", "6ja", "6DM", "45f", "1Za", "ir", "dB", "0Gp", "48V", "aco",
|
||||
"5W2", "4Rr", "94m", "Jn", "Eo", "12t", "5MR", "5X3", "aln", "47W", "0Hq", "kC", "fs", "0EA", "5nN", "6KL", "71I", "4PC", "0km", "3No",
|
||||
"Zs", "0yA", "4Bo", "63D", "7IA", "4lC", "0Wm", "0W", "yo", "8SE", "4as", "5d3", "aPn", "b1d", "00y", "WC", "XB", "1kQ", "745", "61u",
|
||||
"5k2", "4nr", "9Le", "2f", "2mO", "0Xl", "4cB", "6Va", "6xM", "4Mn", "02H", "Ur", "0hB", "Kp", "6fO", "4Sl", "49H", "6Hc", "27E", "45",
|
||||
"8BF", "hl", "518", "44x", "bdo", "aAm", "2PQ", "0er", "0js", "IA", "70W", "bkn", "5oP", "5z1", "gm", "304", "0Io", "28D", "6Gb", "46I",
|
||||
"5LL", "6iN", "y", "0gC", "5pH", "6UJ", "xu", "1Kf", "C8", "VY", "7kG", "4NE", "4Cu", "5F5", "2Mx", "1hz", "0Vw", "1M", "4x8", "4mY",
|
||||
"4bX", "431", "zD", "0Yv", "03R", "Th", "5I4", "4Lt", "4AD", "60o", "YX", "L9", "0TF", "wt", "6ZK", "4oh", "6DI", "45b", "0JD", "iv",
|
||||
"GZ", "0dh", "5Og", "6je", "5W6", "4Rv", "0iX", "Jj", "dF", "0Gt", "48R", "6Iy", "6Fx", "47S", "0Hu", "kG", "Ek", "0fY", "5MV", "5X7",
|
||||
"6ed", "4PG", "0ki", "3Nk", "fw", "0EE", "5nJ", "6KH", "356", "bo", "6OP", "4zs", "bnl", "75U", "LC", "0oq", "0bA", "As", "6lL", "4Yo",
|
||||
"43K", "7RA", "2yN", "0Lm", "17", "22G", "6Ma", "4xB", "4Vn", "6cM", "Nr", "19i", "14Y", "CB", "aDo", "bam", "41z", "5p2", "mn", "8GD",
|
||||
"7d", "8YF", "4kp", "5n0", "64w", "717", "1nS", "2KQ", "Pp", "07J", "4Hl", "69G", "6Sc", "52i", "1MO", "2hM", "5U", "0Ro", "4iA", "7LC",
|
||||
"66F", "4Gm", "08K", "3YA", "RA", "0qs", "b4f", "aUl", "5a1", "4dq", "8VG", "8e", "6mV", "4Xu", "17r", "022", "nE", "0Mw", "42Q", "4c8",
|
||||
"6NJ", "5kH", "1Pf", "cu", "MY", "X8", "4UE", "74O", "6og", "4ZD", "W9", "BX", "lt", "0OF", "4th", "6AK", "4l9", "4yX", "0Bv", "aD",
|
||||
"Oh", "0lZ", "4Wt", "5R4", "4Iv", "5L6", "Qj", "06P", "1LU", "1Y4", "463", "4gZ", "4jj", "7Oh", "rv", "0QD", "1oI", "2JK", "65m", "4DF",
|
||||
"4KG", "7nE", "2EJ", "04a", "1Nd", "2kf", "6PH", "4ek", "5xz", "492", "4O", "0Su", "09Q", "0h8", "5C7", "4Fw", "5Dz", "6ax", "LG", "0ou",
|
||||
"0AY", "bk", "6OT", "4zw", "43O", "6Bd", "2yJ", "0Li", "0bE", "Aw", "6lH", "4Yk", "4Vj", "6cI", "Nv", "0mD", "13", "22C", "6Me", "4xF",
|
||||
"4uv", "5p6", "mj", "0NX", "1pU", "CF", "6ny", "7k9", "4P9", "4EX", "1nW", "2KU", "sh", "0PZ", "4kt", "5n4", "6Sg", "4fD", "k9", "2hI",
|
||||
"Pt", "07N", "4Hh", "69C", "66B", "4Gi", "08O", "2Id", "5Q", "d8", "4iE", "7LG", "5a5", "4du", "1Oz", "8a", "RE", "0qw", "4JY", "aUh",
|
||||
"nA", "0Ms", "42U", "ail", "6mR", "4Xq", "17v", "026", "3Km", "0no", "4UA", "74K", "6NN", "5kL", "1Pb", "cq", "lp", "0OB", "40d", "6AO",
|
||||
"6oc", "5Ja", "0an", "2TM", "Ol", "18w", "4Wp", "5R0", "afm", "bCo", "0Br", "1G2", "1LQ", "1Y0", "467", "53w", "4Ir", "5L2", "Qn", "06T",
|
||||
"1oM", "2JO", "65i", "4DB", "4jn", "7Ol", "6z", "1Aa", "8WY", "2kb", "6PL", "4eo", "4KC", "7nA", "2EN", "04e", "09U", "d6E", "5C3", "4Fs",
|
||||
"bRl", "496", "4K", "0Sq", "0bI", "2Wj", "6lD", "4Yg", "43C", "6Bh", "oW", "z6", "0AU", "bg", "6OX", "5jZ", "4TW", "4A6", "LK", "0oy",
|
||||
"14Q", "CJ", "4N7", "5Kw", "41r", "542", "mf", "0NT", "u7", "22O", "6Mi", "4xJ", "4Vf", "6cE", "Nz", "0mH", "Px", "07B", "4Hd", "69O",
|
||||
"6Sk", "4fH", "k5", "2hE", "7l", "0PV", "4kx", "5n8", "4P5", "4ET", "83j", "2KY", "RI", "05s", "4JU", "7oW", "5a9", "4dy", "1Ov", "8m",
|
||||
"qU", "d4", "4iI", "7LK", "66N", "4Ge", "08C", "2Ih", "6NB", "a59", "1Pn", "21d", "MQ", "X0", "4UM", "74G", "79w", "bbN", "0cS", "0v2",
|
||||
"nM", "8Dg", "42Y", "4c0", "4l1", "4yP", "8Kf", "aL", "0y3", "0lR", "636", "76v", "6oo", "4ZL", "W1", "BP", "2zm", "0ON", "40h", "6AC",
|
||||
"4jb", "auS", "6v", "0QL", "I3", "2JC", "65e", "4DN", "b7E", "68U", "Qb", "06X", "286", "dSl", "4r3", "4gR", "4hS", "7MQ", "4G", "277",
|
||||
"09Y", "0h0", "67T", "b8D", "4KO", "7nM", "SS", "F2", "1Nl", "9w", "azR", "4ec", "43G", "6Bl", "oS", "z2", "0bM", "2Wn", "78i", "4Yc",
|
||||
"4TS", "4A2", "LO", "8fe", "0AQ", "bc", "aeN", "cPm", "41v", "546", "mb", "0NP", "14U", "CN", "4N3", "5Ks", "4Vb", "6cA", "2Xo", "0mL",
|
||||
"u3", "22K", "6Mm", "4xN", "6So", "4fL", "k1", "2hA", "2Fm", "07F", "5XA", "69K", "4P1", "4EP", "83n", "d5f", "7h", "0PR", "bQO", "a0E",
|
||||
"hbu", "50T", "1Or", "8i", "RM", "05w", "4JQ", "7oS", "66J", "4Ga", "08G", "2Il", "5Y", "d0", "4iM", "7LO", "MU", "X4", "4UI", "74C",
|
||||
"6NF", "5kD", "1Pj", "cy", "nI", "2m9", "4vU", "4c4", "6mZ", "4Xy", "0cW", "0v6", "Od", "0lV", "4Wx", "5R8", "4l5", "4yT", "0Bz", "aH",
|
||||
"lx", "0OJ", "40l", "6AG", "6ok", "4ZH", "W5", "BT", "I7", "2JG", "65a", "4DJ", "4jf", "7Od", "6r", "0QH", "1LY", "1Y8", "4r7", "4gV",
|
||||
"4Iz", "68Q", "Qf", "0rT", "1mt", "0h4", "67P", "5VZ", "4hW", "7MU", "4C", "0Sy", "1Nh", "9s", "6PD", "4eg", "4KK", "7nI", "SW", "F6",
|
||||
"8Je", "22V", "4m2", "4xS", "625", "77u", "Nc", "0mQ", "V2", "CS", "6nl", "5Kn", "41k", "7Pa", "3kO", "0NM", "0AL", "20g", "6OA", "4zb",
|
||||
"4TN", "6am", "LR", "Y3", "0bP", "Ab", "78t", "bcM", "43Z", "4b3", "oN", "8Ed", "5D", "264", "4iP", "489", "66W", "b9G", "08Z", "0i3",
|
||||
"RP", "G1", "4JL", "7oN", "6QC", "50I", "1Oo", "8t", "7u", "0PO", "4ka", "7Nc", "64f", "4EM", "H0", "963", "Pa", "0sS", "b6F", "69V",
|
||||
"478", "4fQ", "295", "dRo", "4O4", "4ZU", "15R", "BI", "le", "0OW", "40q", "551", "6Lj", "4yI", "t4", "aU", "Oy", "0lK", "4We", "6bF",
|
||||
"6mG", "4Xd", "0cJ", "2Vi", "nT", "0Mf", "4vH", "6Ck", "adI", "5kY", "1Pw", "cd", "MH", "0nz", "4UT", "7pV", "4KV", "7nT", "SJ", "04p",
|
||||
"1Nu", "9n", "6PY", "4ez", "4hJ", "7MH", "pV", "e7", "1mi", "2Hk", "67M", "4Ff", "4Ig", "68L", "2Gj", "06A", "j6", "2iF", "6Rh", "4gK",
|
||||
"5zZ", "7Oy", "6o", "0QU", "1oX", "1z9", "4Q6", "4DW", "5FZ", "6cX", "Ng", "0mU", "0Cy", "1F9", "4m6", "4xW", "41o", "7Pe", "3kK", "0NI",
|
||||
"V6", "CW", "6nh", "5Kj", "4TJ", "6ai", "LV", "Y7", "0AH", "bz", "6OE", "4zf", "4wV", "4b7", "oJ", "0Lx", "0bT", "Af", "6lY", "4Yz",
|
||||
"5B8", "4Gx", "1lw", "0i7", "qH", "0Rz", "4iT", "7LV", "6QG", "4dd", "1Ok", "8p", "RT", "G5", "4JH", "7oJ", "64b", "4EI", "H4", "2KD",
|
||||
"7q", "0PK", "4ke", "7Ng", "4s4", "4fU", "1MZ", "2hX", "Pe", "0sW", "4Hy", "5M9", "la", "0OS", "40u", "555", "4O0", "4ZQ", "15V", "BM",
|
||||
"2Yl", "0lO", "4Wa", "6bB", "6Ln", "4yM", "08", "aQ", "nP", "0Mb", "42D", "6Co", "6mC", "5HA", "0cN", "2Vm", "ML", "8gf", "4UP", "74Z",
|
||||
"adM", "bAO", "1Ps", "0U3", "1Nq", "9j", "azO", "51W", "4KR", "7nP", "SN", "04t", "09D", "2Ho", "67I", "4Fb", "4hN", "7ML", "4Z", "e3",
|
||||
"j2", "2iB", "6Rl", "4gO", "4Ic", "68H", "2Gn", "06E", "82m", "d4e", "4Q2", "4DS", "bPL", "a1F", "6k", "0QQ", "1pH", "2UJ", "6nd", "5Kf",
|
||||
"41c", "7Pi", "mw", "0NE", "0Cu", "1F5", "6Mx", "5hz", "4Vw", "5S7", "Nk", "0mY", "0bX", "Aj", "6lU", "4Yv", "43R", "6By", "oF", "0Lt",
|
||||
"0AD", "bv", "6OI", "4zj", "4TF", "6ae", "LZ", "0oh", "RX", "G9", "4JD", "7oF", "6QK", "4dh", "1Og", "2je", "5L", "0Rv", "4iX", "481",
|
||||
"5B4", "4Gt", "08R", "2Iy", "Pi", "07S", "4Hu", "5M5", "470", "4fY", "1MV", "1X7", "su", "0PG", "4ki", "7Nk", "64n", "4EE", "H8", "2KH",
|
||||
"6Lb", "4yA", "04", "23D", "Oq", "0lC", "4Wm", "6bN", "aEl", "c4G", "0as", "BA", "lm", "8FG", "40y", "559", "6NS", "5kQ", "345", "cl",
|
||||
"1k2", "0nr", "boo", "74V", "6mO", "4Xl", "0cB", "2Va", "2xM", "0Mn", "42H", "6Cc", "4hB", "aws", "4V", "0Sl", "09H", "2Hc", "67E", "4Fn",
|
||||
"b5e", "aTo", "SB", "04x", "8WD", "9f", "6PQ", "4er", "4js", "5o3", "6g", "8XE", "1oP", "1z1", "65t", "704", "4Io", "68D", "Qs", "06I",
|
||||
"1LL", "2iN", "7BA", "4gC", "41g", "7Pm", "ms", "0NA", "14D", "2UN", "aDr", "5Kb", "4Vs", "5S3", "No", "19t", "0Cq", "1F1", "agn", "bBl",
|
||||
"43V", "aho", "oB", "0Lp", "16u", "An", "6lQ", "4Yr", "4TB", "6aa", "2ZO", "0ol", "1Qa", "br", "6OM", "4zn", "6QO", "4dl", "1Oc", "8x",
|
||||
"2DM", "05f", "5Za", "7oB", "5B0", "4Gp", "08V", "d7F", "5H", "0Rr", "bSo", "485", "474", "52t", "1MR", "1X3", "Pm", "07W", "4Hq", "5M1",
|
||||
"64j", "4EA", "1nN", "2KL", "7y", "0PC", "4km", "7No", "Ou", "0lG", "4Wi", "6bJ", "6Lf", "4yE", "00", "aY", "li", "8FC", "4tu", "5q5",
|
||||
"4O8", "4ZY", "0aw", "BE", "MD", "0nv", "4UX", "74R", "6NW", "5kU", "341", "ch", "nX", "0Mj", "42L", "6Cg", "6mK", "4Xh", "0cF", "2Ve",
|
||||
"09L", "2Hg", "67A", "4Fj", "4hF", "7MD", "4R", "0Sh", "1Ny", "9b", "6PU", "4ev", "4KZ", "7nX", "SF", "0pt", "1oT", "1z5", "65p", "5Tz",
|
||||
"4jw", "5o7", "6c", "0QY", "1LH", "2iJ", "6Rd", "4gG", "4Ik", "7li", "Qw", "06M", "7F", "246", "4kR", "7NP", "64U", "col", "1nq", "0k1",
|
||||
"PR", "E3", "4HN", "69e", "6SA", "4fb", "1Mm", "2ho", "5w", "0RM", "4ic", "7La", "66d", "4GO", "J2", "2IB", "Rc", "05Y", "b4D", "aUN",
|
||||
"4q2", "4dS", "8Ve", "8G", "8Hg", "bM", "4o0", "4zQ", "607", "75w", "La", "0oS", "T0", "AQ", "6ln", "4YM", "43i", "6BB", "2yl", "0LO",
|
||||
"0CN", "22e", "6MC", "5hA", "4VL", "6co", "NP", "0mb", "1ps", "0u3", "aDM", "baO", "41X", "7PR", "mL", "8Gf", "4IT", "7lV", "QH", "06r",
|
||||
"1Lw", "0I7", "5b8", "4gx", "4jH", "7OJ", "rT", "g5", "1ok", "2Ji", "65O", "4Dd", "4Ke", "7ng", "Sy", "04C", "h4", "2kD", "6Pj", "4eI",
|
||||
"4hy", "5m9", "4m", "0SW", "09s", "2HX", "4S4", "4FU", "4M6", "4XW", "0cy", "1f9", "ng", "0MU", "42s", "573", "6Nh", "5kj", "v6", "cW",
|
||||
"3KK", "0nI", "4Ug", "74m", "6oE", "4Zf", "0aH", "Bz", "lV", "y7", "40B", "6Ai", "582", "4yz", "0BT", "af", "OJ", "0lx", "4WV", "4B7",
|
||||
"64Q", "4Ez", "1nu", "0k5", "7B", "0Px", "4kV", "7NT", "6SE", "4ff", "1Mi", "2hk", "PV", "E7", "4HJ", "69a", "6rh", "4GK", "J6", "2IF",
|
||||
"5s", "0RI", "4ig", "7Le", "4q6", "4dW", "1OX", "8C", "Rg", "0qU", "5ZZ", "7oy", "4Ty", "5Q9", "Le", "0oW", "1QZ", "bI", "4o4", "4zU",
|
||||
"43m", "6BF", "oy", "0LK", "T4", "AU", "6lj", "4YI", "4VH", "6ck", "NT", "0mf", "0CJ", "22a", "6MG", "4xd", "4uT", "7PV", "mH", "0Nz",
|
||||
"1pw", "Cd", "aDI", "5KY", "1Ls", "0I3", "axM", "53U", "4IP", "7lR", "QL", "06v", "1oo", "2Jm", "65K", "5TA", "4jL", "7ON", "6X", "g1",
|
||||
"h0", "9Y", "6Pn", "4eM", "4Ka", "7nc", "2El", "04G", "09w", "d6g", "4S0", "4FQ", "bRN", "a3D", "4i", "0SS", "nc", "0MQ", "42w", "577",
|
||||
"4M2", "4XS", "17T", "dlm", "3KO", "0nM", "4Uc", "74i", "6Nl", "5kn", "v2", "cS", "lR", "y3", "40F", "6Am", "6oA", "4Zb", "0aL", "2To",
|
||||
"ON", "18U", "4WR", "4B3", "586", "bCM", "0BP", "ab", "PZ", "0sh", "4HF", "69m", "6SI", "4fj", "1Me", "2hg", "7N", "0Pt", "4kZ", "7NX",
|
||||
"6pU", "4Ev", "1ny", "0k9", "Rk", "05Q", "4Jw", "5O7", "452", "50r", "1OT", "8O", "qw", "0RE", "4ik", "7Li", "66l", "4GG", "08a", "2IJ",
|
||||
"T8", "AY", "6lf", "4YE", "43a", "6BJ", "ou", "0LG", "0Aw", "bE", "4o8", "4zY", "4Tu", "5Q5", "Li", "8fC", "14s", "Ch", "6nW", "5KU",
|
||||
"41P", "7PZ", "mD", "0Nv", "0CF", "22m", "6MK", "4xh", "4VD", "6cg", "NX", "0mj", "5za", "7OB", "6T", "0Qn", "1oc", "2Ja", "65G", "4Dl",
|
||||
"b7g", "68w", "1w2", "06z", "8UF", "dSN", "5b0", "4gp", "4hq", "5m1", "4e", "8ZG", "1mR", "1x3", "67v", "726", "4Km", "7no", "Sq", "04K",
|
||||
"1NN", "9U", "6Pb", "4eA", "adr", "5kb", "26", "21F", "Ms", "0nA", "4Uo", "74e", "79U", "bbl", "0cq", "1f1", "no", "396", "4vs", "5s3",
|
||||
"6LQ", "4yr", "367", "an", "OB", "0lp", "bmm", "76T", "6oM", "4Zn", "15i", "Br", "2zO", "0Ol", "40J", "6Aa", "6SM", "4fn", "1Ma", "2hc",
|
||||
"2FO", "07d", "4HB", "69i", "64Y", "4Er", "83L", "d5D", "7J", "0Pp", "bQm", "a0g", "456", "50v", "1OP", "8K", "Ro", "05U", "4Js", "5O3",
|
||||
"66h", "4GC", "08e", "2IN", "qs", "0RA", "4io", "7Lm", "43e", "6BN", "oq", "0LC", "0bo", "2WL", "6lb", "4YA", "4Tq", "5Q1", "Lm", "8fG",
|
||||
"0As", "bA", "ael", "cPO", "41T", "ajm", "1K2", "0Nr", "14w", "Cl", "6nS", "5KQ", "5Fa", "6cc", "2XM", "0mn", "0CB", "22i", "6MO", "4xl",
|
||||
"1og", "2Je", "65C", "4Dh", "4jD", "7OF", "6P", "g9", "3l9", "2iy", "5b4", "4gt", "4IX", "68s", "QD", "0rv", "1mV", "1x7", "4S8", "4FY",
|
||||
"4hu", "5m5", "4a", "1Cz", "h8", "9Q", "6Pf", "4eE", "4Ki", "7nk", "Su", "04O", "Mw", "0nE", "4Uk", "74a", "6Nd", "5kf", "22", "21B",
|
||||
"nk", "0MY", "4vw", "5s7", "6mx", "5Hz", "0cu", "1f5", "OF", "0lt", "4WZ", "6by", "6LU", "4yv", "0BX", "aj", "lZ", "0Oh", "40N", "6Ae",
|
||||
"6oI", "4Zj", "0aD", "Bv", "5f", "9Ke", "4ir", "5l2", "66u", "735", "08x", "1y0", "Rr", "05H", "4Jn", "7ol", "6Qa", "4dB", "1OM", "8V",
|
||||
"7W", "0Pm", "4kC", "7NA", "64D", "4Eo", "83Q", "2Kb", "PC", "07y", "b6d", "69t", "5c3", "4fs", "8TE", "dRM", "374", "22t", "599", "4xq",
|
||||
"bln", "77W", "NA", "0ms", "14j", "Cq", "6nN", "5KL", "41I", "7PC", "3km", "0No", "35", "20E", "6Oc", "5ja", "4Tl", "6aO", "Lp", "0oB",
|
||||
"0br", "1g2", "78V", "bco", "43x", "568", "ol", "385", "4Kt", "5N4", "Sh", "04R", "1NW", "9L", "441", "4eX", "4hh", "7Mj", "pt", "0SF",
|
||||
"K9", "2HI", "67o", "4FD", "4IE", "68n", "QY", "0", "1Lf", "2id", "6RJ", "4gi", "4jY", "auh", "6M", "0Qw", "1oz", "2Jx", "5A5", "4Du",
|
||||
"6oT", "4Zw", "0aY", "Bk", "lG", "0Ou", "40S", "6Ax", "6LH", "4yk", "0BE", "aw", "2YJ", "0li", "4WG", "6bd", "6me", "4XF", "0ch", "2VK",
|
||||
"nv", "0MD", "42b", "6CI", "6Ny", "7K9", "1PU", "cF", "Mj", "0nX", "4Uv", "5P6", "66q", "4GZ", "1lU", "1y4", "5b", "0RX", "4iv", "5l6",
|
||||
"6Qe", "4dF", "1OI", "8R", "Rv", "05L", "4Jj", "7oh", "6pH", "4Ek", "1nd", "2Kf", "7S", "0Pi", "4kG", "7NE", "5c7", "4fw", "1Mx", "0H8",
|
||||
"PG", "0su", "5Xz", "69p", "4VY", "4C8", "NE", "0mw", "1Sz", "22p", "6MV", "4xu", "41M", "7PG", "mY", "x8", "14n", "Cu", "6nJ", "5KH",
|
||||
"4Th", "6aK", "Lt", "0oF", "31", "bX", "6Og", "4zD", "4wt", "5r4", "oh", "0LZ", "0bv", "AD", "4L9", "4YX", "1NS", "9H", "445", "51u",
|
||||
"4Kp", "5N0", "Sl", "04V", "09f", "2HM", "67k", "5Va", "4hl", "7Mn", "4x", "0SB", "1Lb", "3yA", "6RN", "4gm", "4IA", "68j", "2GL", "4",
|
||||
"82O", "d4G", "5A1", "4Dq", "bPn", "a1d", "6I", "0Qs", "lC", "0Oq", "40W", "akn", "6oP", "4Zs", "15t", "Bo", "2YN", "0lm", "4WC", "76I",
|
||||
"6LL", "4yo", "0BA", "as", "nr", "8DX", "42f", "6CM", "6ma", "4XB", "0cl", "2VO", "Mn", "8gD", "4Ur", "5P2", "ado", "bAm", "1PQ", "cB",
|
||||
"Rz", "0qH", "4Jf", "7od", "6Qi", "4dJ", "i7", "2jG", "5n", "0RT", "4iz", "7Lx", "4R7", "4GV", "08p", "1y8", "PK", "07q", "4HW", "7mU",
|
||||
"6SX", "52R", "1Mt", "0H4", "sW", "f6", "4kK", "7NI", "64L", "4Eg", "1nh", "2Kj", "14b", "Cy", "6nF", "5KD", "41A", "7PK", "mU", "x4",
|
||||
"0CW", "0V6", "591", "4xy", "4VU", "4C4", "NI", "19R", "0bz", "AH", "4L5", "4YT", "43p", "560", "od", "0LV", "w5", "bT", "6Ok", "4zH",
|
||||
"4Td", "6aG", "Lx", "0oJ", "5xA", "7Mb", "4t", "0SN", "K1", "2HA", "67g", "4FL", "b5G", "aTM", "0e3", "04Z", "8Wf", "9D", "449", "4eP",
|
||||
"4jQ", "7OS", "6E", "255", "1or", "0j2", "65V", "cno", "4IM", "68f", "QQ", "8", "1Ln", "2il", "6RB", "4ga", "afR", "4yc", "0BM", "23f",
|
||||
"OS", "Z2", "4WO", "6bl", "aEN", "c4e", "0aQ", "Bc", "lO", "8Fe", "4tS", "4a2", "4n3", "5ks", "8Id", "cN", "Mb", "0nP", "614", "74t",
|
||||
"6mm", "4XN", "U3", "2VC", "2xo", "0ML", "42j", "6CA", "6Qm", "4dN", "i3", "8Z", "2Do", "05D", "4Jb", "aUS", "4R3", "4GR", "08t", "d7d",
|
||||
"5j", "0RP", "bSM", "a2G", "ayN", "52V", "1Mp", "0H0", "PO", "07u", "4HS", "69x", "64H", "4Ec", "1nl", "2Kn", "sS", "f2", "4kO", "7NM",
|
||||
"41E", "7PO", "mQ", "x0", "14f", "2Ul", "6nB", "aQ1", "4VQ", "4C0", "NM", "19V", "0CS", "0V2", "595", "bBN", "43t", "564", "0Y3", "0LR",
|
||||
"16W", "AL", "4L1", "4YP", "5DA", "6aC", "2Zm", "0oN", "39", "bP", "6Oo", "4zL", "K5", "2HE", "67c", "4FH", "4hd", "7Mf", "4p", "0SJ",
|
||||
"8Wb", "2kY", "4p5", "4eT", "4Kx", "5N8", "Sd", "0pV", "1ov", "0j6", "5A9", "4Dy", "4jU", "7OW", "6A", "1AZ", "1Lj", "2ih", "6RF", "4ge",
|
||||
"4II", "68b", "QU", "D4", "OW", "Z6", "4WK", "6bh", "6LD", "4yg", "0BI", "23b", "lK", "0Oy", "4tW", "4a6", "6oX", "5JZ", "0aU", "Bg",
|
||||
"Mf", "0nT", "4Uz", "74p", "4n7", "5kw", "1PY", "cJ", "nz", "0MH", "42n", "6CE", "6mi", "4XJ", "U7", "2VG", "4MP", "4X1", "UL", "02v",
|
||||
"0XR", "0M3", "a8E", "57U", "4nL", "7KN", "2X", "c1", "1ko", "2Nm", "61K", "5PA", "4Oa", "6zB", "2Al", "00G", "l0", "yQ", "6Tn", "4aM",
|
||||
"58T", "a7D", "0i", "0WS", "84o", "ZM", "4W0", "4BQ", "4I2", "5Lr", "13T", "G", "jc", "0IQ", "46w", "537", "6Jl", "5on", "r2", "gS",
|
||||
"3OO", "0jM", "4Qc", "70i", "6kA", "5NC", "0eL", "2Po", "hR", "8Bx", "44F", "6Em", "abO", "49v", "0FP", "eb", "KN", "8ad", "4SR", "4F3",
|
||||
"3B", "0Tx", "4oV", "4z7", "60Q", "4Az", "0zT", "Yf", "TV", "A7", "4LJ", "6yi", "6WE", "4bf", "0YH", "zz", "1s", "0VI", "4mg", "6XD",
|
||||
"6vh", "4CK", "N6", "2MF", "Vg", "0uU", "6n9", "7ky", "4u6", "5pv", "1KX", "xK", "1UZ", "fI", "4k4", "5nt", "4Py", "5U9", "He", "0kW",
|
||||
"P4", "EU", "6hj", "5Mh", "47m", "6FF", "ky", "0HK", "0GJ", "dx", "6IG", "48l", "4RH", "6gk", "JT", "0if", "0dV", "Gd", "5Z8", "5OY",
|
||||
"4qT", "4d5", "iH", "0Jz", "0XV", "0M7", "5f8", "4cx", "4MT", "4X5", "UH", "02r", "1kk", "Xx", "61O", "5PE", "4nH", "7KJ", "vT", "c5",
|
||||
"l4", "yU", "6Tj", "4aI", "4Oe", "6zF", "Wy", "00C", "1iZ", "ZI", "4W4", "4BU", "4ly", "5i9", "0m", "0WW", "jg", "0IU", "46s", "533",
|
||||
"4I6", "5Lv", "0gy", "C", "3OK", "0jI", "4Qg", "6dD", "6Jh", "5oj", "r6", "gW", "hV", "0Kd", "44B", "6Ei", "6kE", "5NG", "0eH", "Fz",
|
||||
"KJ", "0hx", "4SV", "4F7", "6HY", "49r", "0FT", "ef", "60U", "ckl", "0zP", "Yb", "3F", "206", "4oR", "4z3", "6WA", "4bb", "0YL", "2lo",
|
||||
"TR", "A3", "4LN", "6ym", "62d", "4CO", "N2", "2MB", "1w", "0VM", "4mc", "7Ha", "4u2", "54z", "8Re", "xO", "Vc", "01Y", "b0D", "aQN",
|
||||
"647", "71w", "Ha", "0kS", "8Lg", "fM", "4k0", "5np", "47i", "6FB", "29d", "0HO", "P0", "EQ", "6hn", "5Ml", "4RL", "6go", "JP", "0ib",
|
||||
"0GN", "26e", "6IC", "48h", "45X", "4d1", "iL", "8Cf", "0dR", "0q3", "hYt", "beO", "4nD", "7KF", "2P", "c9", "1kg", "Xt", "61C", "5PI",
|
||||
"4MX", "4X9", "UD", "0vv", "0XZ", "2my", "5f4", "4ct", "4lu", "5i5", "0a", "1Gz", "0yw", "ZE", "4W8", "4BY", "4Oi", "6zJ", "Wu", "00O",
|
||||
"l8", "yY", "6Tf", "4aE", "6Jd", "5of", "62", "25B", "Iw", "0jE", "4Qk", "6dH", "6ix", "5Lz", "0gu", "O", "jk", "0IY", "4rw", "5w7",
|
||||
"5x6", "5mW", "0FX", "ej", "KF", "0ht", "4SZ", "6fy", "6kI", "5NK", "0eD", "Fv", "hZ", "93", "44N", "6Ee", "2BO", "03d", "4LB", "6ya",
|
||||
"6WM", "4bn", "1Ia", "zr", "3J", "0Tp", "bUm", "a4g", "5D2", "4Ar", "87L", "Yn", "Vo", "01U", "4Ns", "5K3", "416", "54v", "1KP", "xC",
|
||||
"us", "0VA", "4mo", "6XL", "62h", "4CC", "0xm", "2MN", "0fo", "2SL", "6hb", "bgr", "47e", "6FN", "kq", "0HC", "0Es", "fA", "aal", "bDn",
|
||||
"4Pq", "5U1", "Hm", "8bG", "10w", "Gl", "5Z0", "5OQ", "45T", "anm", "1O2", "0Jr", "0GB", "dp", "6IO", "48d", "5Ba", "6gc", "3Ll", "0in",
|
||||
"1kc", "Xp", "61G", "5PM", "bTs", "7KB", "2T", "0Un", "8QF", "39T", "5f0", "4cp", "797", "aRm", "1s2", "02z", "0ys", "ZA", "63v", "766",
|
||||
"4lq", "5i1", "0e", "9Nf", "0Zo", "2oL", "6Tb", "4aA", "4Om", "6zN", "Wq", "00K", "Is", "0jA", "4Qo", "6dL", "7ZA", "5ob", "66", "25F",
|
||||
"jo", "9Pd", "4rs", "5w3", "aCn", "bfl", "0gq", "K", "KB", "0hp", "bim", "72T", "5x2", "49z", "327", "en", "3nn", "97", "44J", "6Ea",
|
||||
"6kM", "5NO", "11i", "Fr", "6WI", "4bj", "0YD", "zv", "TZ", "0wh", "4LF", "6ye", "5D6", "4Av", "0zX", "Yj", "3N", "0Tt", "4oZ", "6Zy",
|
||||
"412", "54r", "1KT", "xG", "Vk", "01Q", "4Nw", "5K7", "62l", "4CG", "0xi", "2MJ", "uw", "0VE", "4mk", "6XH", "47a", "6FJ", "ku", "0HG",
|
||||
"P8", "EY", "6hf", "5Md", "4Pu", "5U5", "Hi", "8bC", "0Ew", "fE", "4k8", "5nx", "45P", "4d9", "iD", "0Jv", "0dZ", "Gh", "5Z4", "5OU",
|
||||
"4RD", "6gg", "JX", "0ij", "0GF", "dt", "6IK", "5lI", "4Op", "5J0", "Wl", "00V", "0Zr", "2oQ", "405", "55u", "4ll", "6YO", "0x", "0WB",
|
||||
"0yn", "2LM", "63k", "5Ra", "4MA", "6xb", "2CL", "02g", "0XC", "39I", "6VN", "4cm", "bTn", "a5d", "2I", "0Us", "86O", "Xm", "5E1", "5PP",
|
||||
"6kP", "5NR", "11t", "Fo", "hC", "0Kq", "44W", "aon", "6HL", "49g", "0FA", "es", "3Mo", "0hm", "4SC", "72I", "6ia", "5Lc", "0gl", "V",
|
||||
"jr", "1Ya", "46f", "6GM", "hyV", "bEm", "0Dp", "gB", "In", "8cD", "4Qr", "5T2", "1b", "0VX", "4mv", "5h6", "62q", "4CZ", "0xt", "2MW",
|
||||
"Vv", "01L", "4Nj", "7kh", "6Ue", "54o", "1KI", "xZ", "3S", "0Ti", "4oG", "6Zd", "6tH", "4Ak", "0zE", "Yw", "TG", "0wu", "780", "6yx",
|
||||
"5g7", "4bw", "0YY", "zk", "1Wz", "di", "5y5", "5lT", "4RY", "4G8", "JE", "0iw", "0dG", "Gu", "6jJ", "5OH", "45M", "6Df", "iY", "80",
|
||||
"71", "fX", "6Kg", "5ne", "4Ph", "6eK", "Ht", "0kF", "0fv", "ED", "4H9", "5My", "4st", "5v4", "kh", "0HZ", "0Zv", "yD", "401", "4aX",
|
||||
"4Ot", "5J4", "Wh", "00R", "O9", "ZX", "63o", "4BD", "4lh", "6YK", "tt", "0WF", "0XG", "2md", "6VJ", "4ci", "4ME", "6xf", "UY", "02c",
|
||||
"1kz", "Xi", "5E5", "5PT", "4nY", "aqh", "2M", "0Uw", "hG", "0Ku", "44S", "6Ex", "6kT", "5NV", "0eY", "Fk", "3Mk", "0hi", "4SG", "6fd",
|
||||
"6HH", "49c", "0FE", "ew", "jv", "0ID", "46b", "6GI", "6ie", "5Lg", "0gh", "R", "Ij", "0jX", "4Qv", "5T6", "6Jy", "7O9", "0Dt", "gF",
|
||||
"62u", "775", "0xp", "198", "1f", "9Oe", "4mr", "5h2", "6Ua", "54k", "1KM", "2nO", "Vr", "01H", "4Nn", "7kl", "60D", "4Ao", "0zA", "Ys",
|
||||
"3W", "0Tm", "4oC", "7JA", "5g3", "4bs", "8PE", "zo", "TC", "03y", "784", "aSn", "bhn", "73W", "JA", "0is", "334", "dm", "5y1", "48y",
|
||||
"45I", "6Db", "3om", "84", "0dC", "Gq", "6jN", "5OL", "4Pl", "6eO", "Hp", "0kB", "75", "24E", "6Kc", "5na", "47x", "528", "kl", "8AF",
|
||||
"0fr", "1c2", "aBm", "bgo", "4ld", "6YG", "0p", "0WJ", "O5", "ZT", "63c", "4BH", "4Ox", "5J8", "Wd", "0tV", "0Zz", "yH", "4t5", "4aT",
|
||||
"4nU", "7KW", "2A", "1EZ", "1kv", "Xe", "5E9", "5PX", "4MI", "6xj", "UU", "02o", "0XK", "2mh", "6VF", "4ce", "6HD", "49o", "0FI", "27b",
|
||||
"KW", "0he", "4SK", "6fh", "6kX", "5NZ", "0eU", "Fg", "hK", "0Ky", "4pW", "4e6", "4j7", "5ow", "0Dx", "gJ", "If", "0jT", "4Qz", "6dY",
|
||||
"6ii", "5Lk", "Q7", "DV", "jz", "0IH", "46n", "6GE", "3PN", "01D", "4Nb", "aQS", "6Um", "54g", "m3", "xR", "1j", "0VP", "59W", "a6G",
|
||||
"4V3", "4CR", "85l", "194", "TO", "03u", "4LS", "4Y2", "a9F", "56V", "0YQ", "zc", "wS", "b2", "4oO", "6Zl", "60H", "4Ac", "0zM", "2On",
|
||||
"0dO", "2Ql", "6jB", "aU1", "45E", "6Dn", "iQ", "88", "0GS", "da", "acL", "48u", "4RQ", "4G0", "JM", "94N", "12W", "EL", "4H1", "5Mq",
|
||||
"47t", "524", "29y", "0HR", "79", "fP", "6Ko", "5nm", "aZ0", "6eC", "3NL", "0kN", "O1", "ZP", "63g", "4BL", "58I", "6YC", "0t", "0WN",
|
||||
"8Sf", "yL", "409", "4aP", "b1G", "aPM", "0a3", "00Z", "1kr", "Xa", "61V", "bzN", "4nQ", "7KS", "2E", "215", "0XO", "2ml", "6VB", "4ca",
|
||||
"4MM", "6xn", "UQ", "02k", "KS", "0ha", "4SO", "6fl", "7Xa", "49k", "0FM", "27f", "hO", "8Be", "4pS", "4e2", "aAN", "bdL", "0eQ", "Fc",
|
||||
"Ib", "0jP", "654", "70t", "4j3", "5os", "8Md", "gN", "28g", "0IL", "46j", "6GA", "6im", "5Lo", "Q3", "Z", "6Ui", "54c", "m7", "xV",
|
||||
"Vz", "0uH", "4Nf", "7kd", "4V7", "4CV", "0xx", "190", "1n", "0VT", "4mz", "6XY", "6WX", "56R", "0YU", "zg", "TK", "03q", "4LW", "4Y6",
|
||||
"60L", "4Ag", "0zI", "2Oj", "wW", "b6", "4oK", "6Zh", "45A", "6Dj", "iU", "0Jg", "0dK", "Gy", "6jF", "5OD", "4RU", "4G4", "JI", "1yZ",
|
||||
"0GW", "de", "5y9", "48q", "47p", "520", "kd", "0HV", "0fz", "EH", "4H5", "5Mu", "4Pd", "6eG", "Hx", "0kJ", "s5", "fT", "6Kk", "5ni",
|
||||
"5Y1", "5LP", "13v", "e", "jA", "0Is", "46U", "aml", "6JN", "5oL", "0DC", "gq", "3Om", "0jo", "4QA", "6db", "6kc", "5Na", "0en", "2PM",
|
||||
"hp", "0KB", "44d", "6EO", "abm", "49T", "0Fr", "1C2", "Kl", "8aF", "4Sp", "5V0", "4Mr", "5H2", "Un", "02T", "0Xp", "2mS", "427", "57w",
|
||||
"4nn", "7Kl", "2z", "1Ea", "1kM", "2NO", "61i", "5Pc", "4OC", "7jA", "2AN", "00e", "0ZA", "ys", "6TL", "4ao", "58v", "a7f", "0K", "0Wq",
|
||||
"84M", "Zo", "5G3", "4Bs", "0EY", "fk", "6KT", "5nV", "bjh", "6ex", "HG", "0ku", "0fE", "Ew", "6hH", "5MJ", "47O", "6Fd", "29B", "0Hi",
|
||||
"53", "dZ", "6Ie", "48N", "4Rj", "6gI", "Jv", "0iD", "0dt", "GF", "6jy", "7o9", "4qv", "5t6", "ij", "0JX", "wh", "0TZ", "4ot", "5j4",
|
||||
"4T9", "4AX", "0zv", "YD", "Tt", "03N", "4Lh", "6yK", "6Wg", "4bD", "o9", "zX", "1Q", "0Vk", "4mE", "6Xf", "62B", "4Ci", "0xG", "2Md",
|
||||
"VE", "0uw", "4NY", "aQh", "5e5", "5pT", "1Kz", "xi", "jE", "0Iw", "46Q", "4g8", "5Y5", "5LT", "13r", "a", "IY", "0jk", "4QE", "6df",
|
||||
"6JJ", "5oH", "0DG", "gu", "ht", "0KF", "4ph", "6EK", "6kg", "5Ne", "S9", "FX", "Kh", "0hZ", "4St", "5V4", "4h9", "49P", "0Fv", "eD",
|
||||
"0Xt", "2mW", "423", "4cZ", "4Mv", "5H6", "Uj", "02P", "1kI", "XZ", "61m", "5Pg", "4nj", "7Kh", "vv", "0UD", "0ZE", "yw", "6TH", "4ak",
|
||||
"4OG", "6zd", "2AJ", "00a", "0yY", "Zk", "5G7", "4Bw", "58r", "6Yx", "0O", "0Wu", "bjl", "71U", "HC", "0kq", "316", "fo", "6KP", "5nR",
|
||||
"47K", "7VA", "29F", "0Hm", "0fA", "Es", "6hL", "5MN", "4Rn", "6gM", "Jr", "1ya", "57", "26G", "6Ia", "48J", "45z", "5t2", "in", "8CD",
|
||||
"0dp", "GB", "hYV", "bem", "60w", "757", "0zr", "2OQ", "3d", "9Mg", "4op", "5j0", "6Wc", "56i", "0Yn", "2lM", "Tp", "03J", "4Ll", "6yO",
|
||||
"62F", "4Cm", "0xC", "dwS", "1U", "0Vo", "4mA", "6Xb", "5e1", "54X", "8RG", "xm", "VA", "0us", "b0f", "aQl", "6JF", "5oD", "0DK", "gy",
|
||||
"IU", "0jg", "4QI", "6dj", "5Y9", "5LX", "0gW", "m", "jI", "1YZ", "4rU", "4g4", "4h5", "5mu", "0Fz", "eH", "Kd", "0hV", "4Sx", "5V8",
|
||||
"6kk", "5Ni", "S5", "FT", "hx", "0KJ", "44l", "6EG", "4nf", "7Kd", "2r", "0UH", "M7", "XV", "61a", "5Pk", "4Mz", "6xY", "Uf", "0vT",
|
||||
"0Xx", "39r", "4v7", "4cV", "4lW", "4y6", "0C", "0Wy", "0yU", "Zg", "63P", "5RZ", "4OK", "6zh", "WW", "B6", "0ZI", "2oj", "6TD", "4ag",
|
||||
"0fM", "2Sn", "7xa", "5MB", "47G", "6Fl", "kS", "0Ha", "0EQ", "fc", "aaN", "bDL", "4PS", "4E2", "HO", "8be", "10U", "GN", "4J3", "5Os",
|
||||
"45v", "506", "ib", "0JP", "q3", "dR", "6Im", "48F", "4Rb", "6gA", "3LN", "0iL", "2Bm", "03F", "aF0", "6yC", "6Wo", "4bL", "o1", "zP",
|
||||
"3h", "0TR", "bUO", "a4E", "4T1", "4AP", "87n", "YL", "VM", "01w", "4NQ", "7kS", "hfu", "54T", "1Kr", "xa", "1Y", "0Vc", "4mM", "6Xn",
|
||||
"62J", "4Ca", "0xO", "2Ml", "IQ", "0jc", "4QM", "6dn", "6JB", "a19", "0DO", "25d", "jM", "9PF", "46Y", "4g0", "aCL", "687", "0gS", "i",
|
||||
"3MP", "0hR", "676", "72v", "4h1", "49X", "8Of", "eL", "3nL", "0KN", "44h", "6EC", "6ko", "5Nm", "S1", "FP", "M3", "XR", "61e", "5Po",
|
||||
"4nb", "aqS", "2v", "0UL", "8Qd", "39v", "4v3", "4cR", "b3E", "aRO", "Ub", "02X", "0yQ", "Zc", "63T", "bxL", "4lS", "4y2", "0G", "237",
|
||||
"0ZM", "2on", "7Da", "4ac", "4OO", "6zl", "WS", "B2", "47C", "6Fh", "kW", "0He", "0fI", "2Sj", "6hD", "5MF", "4PW", "4E6", "HK", "0ky",
|
||||
"0EU", "fg", "6KX", "5nZ", "45r", "502", "if", "0JT", "0dx", "GJ", "4J7", "5Ow", "4Rf", "6gE", "Jz", "0iH", "q7", "dV", "6Ii", "48B",
|
||||
"6Wk", "4bH", "o5", "zT", "Tx", "03B", "4Ld", "6yG", "4T5", "4AT", "0zz", "YH", "3l", "0TV", "4ox", "5j8", "5e9", "54P", "1Kv", "xe",
|
||||
"VI", "01s", "4NU", "7kW", "62N", "4Ce", "0xK", "2Mh", "uU", "0Vg", "4mI", "6Xj", "4K0", "5Np", "11V", "FM", "ha", "0KS", "44u", "515",
|
||||
"6Hn", "49E", "48", "eQ", "3MM", "0hO", "4Sa", "6fB", "6iC", "5LA", "0gN", "t", "jP", "0Ib", "46D", "6Go", "hyt", "bEO", "0DR", "0Q3",
|
||||
"IL", "8cf", "4QP", "4D1", "4OR", "4Z3", "WN", "00t", "0ZP", "yb", "hgv", "55W", "4lN", "6Ym", "0Z", "a3", "0yL", "2Lo", "63I", "4Bb",
|
||||
"4Mc", "7ha", "2Cn", "02E", "n2", "2mB", "6Vl", "4cO", "bTL", "a5F", "2k", "0UQ", "86m", "XO", "4U2", "5Pr", "0Gy", "dK", "4i6", "5lv",
|
||||
"5BZ", "6gX", "Jg", "0iU", "R6", "GW", "6jh", "5Oj", "45o", "6DD", "3oK", "0JI", "0EH", "fz", "6KE", "5nG", "4PJ", "6ei", "HV", "0kd",
|
||||
"0fT", "Ef", "6hY", "690", "4sV", "4f7", "kJ", "0Hx", "uH", "0Vz", "4mT", "4x5", "5F8", "4Cx", "0xV", "0m7", "VT", "C5", "4NH", "7kJ",
|
||||
"6UG", "54M", "1Kk", "xx", "3q", "0TK", "4oe", "6ZF", "60b", "4AI", "L4", "YU", "Te", "0wW", "4Ly", "5I9", "4w4", "4bU", "1IZ", "zI",
|
||||
"he", "0KW", "44q", "511", "4K4", "5Nt", "11R", "FI", "Ky", "0hK", "4Se", "6fF", "6Hj", "49A", "p4", "eU", "jT", "0If", "4rH", "6Gk",
|
||||
"6iG", "5LE", "0gJ", "p", "IH", "0jz", "4QT", "4D5", "5z8", "5oY", "0DV", "gd", "0ZT", "yf", "6TY", "4az", "4OV", "4Z7", "WJ", "00p",
|
||||
"0yH", "Zz", "63M", "4Bf", "4lJ", "6Yi", "tV", "a7", "n6", "2mF", "6Vh", "4cK", "4Mg", "6xD", "2Cj", "02A", "1kX", "XK", "4U6", "5Pv",
|
||||
"6N9", "7Ky", "2o", "0UU", "665", "73u", "Jc", "0iQ", "8Ne", "dO", "4i2", "5lr", "45k", "7Ta", "3oO", "0JM", "R2", "GS", "6jl", "5On",
|
||||
"4PN", "6em", "HR", "8bx", "0EL", "24g", "6KA", "5nC", "47Z", "4f3", "kN", "8Ad", "0fP", "Eb", "aBO", "694", "62W", "byO", "0xR", "0m3",
|
||||
"1D", "224", "4mP", "4x1", "6UC", "54I", "1Ko", "2nm", "VP", "C1", "4NL", "7kN", "60f", "4AM", "L0", "YQ", "3u", "0TO", "4oa", "6ZB",
|
||||
"438", "4bQ", "8Pg", "zM", "Ta", "0wS", "b2F", "aSL", "6Hf", "49M", "40", "eY", "Ku", "0hG", "4Si", "6fJ", "4K8", "5Nx", "0ew", "FE",
|
||||
"hi", "8BC", "4pu", "5u5", "5z4", "5oU", "0DZ", "gh", "ID", "0jv", "4QX", "4D9", "6iK", "5LI", "0gF", "Dt", "jX", "0Ij", "46L", "6Gg",
|
||||
"4lF", "6Ye", "0R", "0Wh", "0yD", "Zv", "63A", "4Bj", "4OZ", "6zy", "WF", "0tt", "0ZX", "yj", "5d6", "4av", "4nw", "5k7", "2c", "0UY",
|
||||
"1kT", "XG", "61p", "5Pz", "4Mk", "6xH", "Uw", "02M", "0Xi", "2mJ", "6Vd", "4cG", "0dm", "2QN", "7zA", "5Ob", "45g", "6DL", "is", "0JA",
|
||||
"0Gq", "dC", "acn", "48W", "4Rs", "5W3", "Jo", "94l", "12u", "En", "5X2", "5MS", "47V", "alo", "kB", "0Hp", "1Ua", "fr", "6KM", "5nO",
|
||||
"4PB", "6ea", "3Nn", "0kl", "3Pl", "01f", "bts", "7kB", "6UO", "54E", "1Kc", "xp", "1H", "0Vr", "59u", "a6e", "5F0", "4Cp", "85N", "d3F",
|
||||
"Tm", "03W", "4Lq", "5I1", "434", "56t", "0Ys", "zA", "3y", "0TC", "4om", "6ZN", "60j", "4AA", "0zo", "2OL", "Kq", "0hC", "4Sm", "6fN",
|
||||
"6Hb", "49I", "44", "27D", "hm", "8BG", "44y", "519", "aAl", "bdn", "0es", "FA", "1o2", "0jr", "bko", "70V", "5z0", "5oQ", "305", "gl",
|
||||
"28E", "0In", "46H", "6Gc", "6iO", "5LM", "0gB", "x", "1ia", "Zr", "63E", "4Bn", "4lB", "6Ya", "0V", "0Wl", "8SD", "yn", "5d2", "4ar",
|
||||
"b1e", "aPo", "WB", "00x", "1kP", "XC", "61t", "744", "4ns", "5k3", "2g", "9Ld", "0Xm", "2mN", "7FA", "4cC", "4Mo", "6xL", "Us", "02I",
|
||||
"45c", "6DH", "iw", "0JE", "0di", "2QJ", "6jd", "5Of", "4Rw", "5W7", "Jk", "0iY", "0Gu", "dG", "6Ix", "48S", "47R", "6Fy", "kF", "0Ht",
|
||||
"0fX", "Ej", "5X6", "5MW", "4PF", "6ee", "HZ", "0kh", "0ED", "fv", "6KI", "5nK", "6UK", "54A", "1Kg", "xt", "VX", "C9", "4ND", "7kF",
|
||||
"5F4", "4Ct", "0xZ", "2My", "1L", "0Vv", "4mX", "4x9", "430", "4bY", "0Yw", "zE", "Ti", "03S", "4Lu", "5I5", "60n", "4AE", "L8", "YY",
|
||||
"wu", "0TG", "4oi", "6ZJ" };
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -60,7 +60,10 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
/* Update the access time for the ageing algorithm.
|
||||
* Don't do it if we have a saving child, as this will trigger
|
||||
* a copy on write madness. */
|
||||
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
|
||||
if (server.rdb_child_pid == -1 &&
|
||||
server.aof_child_pid == -1 &&
|
||||
!(flags & LOOKUP_NOTOUCH))
|
||||
{
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
updateLFU(val);
|
||||
} else {
|
||||
@@ -80,7 +83,6 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
* 1. A key gets expired if it reached it's TTL.
|
||||
* 2. The key last access time is updated.
|
||||
* 3. The global keys hits/misses stats are updated (reported in INFO).
|
||||
* 4. If keyspace notifications are enabled, a "keymiss" notification is fired.
|
||||
*
|
||||
* This API should not be used when we write to the key after obtaining
|
||||
* the object linked to the key, but only for read only operations.
|
||||
@@ -104,7 +106,6 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
* to return NULL ASAP. */
|
||||
if (server.masterhost == NULL) {
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -126,15 +127,12 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
server.current_client->cmd->flags & CMD_READONLY)
|
||||
{
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
val = lookupKey(db,key,flags);
|
||||
if (val == NULL) {
|
||||
if (val == NULL)
|
||||
server.stat_keyspace_misses++;
|
||||
notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
|
||||
}
|
||||
else
|
||||
server.stat_keyspace_hits++;
|
||||
return val;
|
||||
@@ -151,13 +149,9 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
|
||||
*
|
||||
* Returns the linked value object if the key exists or NULL if the key
|
||||
* does not exist in the specified DB. */
|
||||
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
|
||||
expireIfNeeded(db,key);
|
||||
return lookupKey(db,key,flags);
|
||||
}
|
||||
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
||||
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
|
||||
expireIfNeeded(db,key);
|
||||
return lookupKey(db,key,LOOKUP_NONE);
|
||||
}
|
||||
|
||||
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
|
||||
@@ -216,28 +210,20 @@ 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),
|
||||
* unless 'keepttl' is true.
|
||||
* 3) The expire time of the key is reset (the key is made persistent).
|
||||
*
|
||||
* All the new keys in the database should be created via this interface. */
|
||||
void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl) {
|
||||
void setKey(redisDb *db, robj *key, robj *val) {
|
||||
if (lookupKeyWrite(db,key) == NULL) {
|
||||
dbAdd(db,key,val);
|
||||
} else {
|
||||
dbOverwrite(db,key,val);
|
||||
}
|
||||
incrRefCount(val);
|
||||
if (!keepttl) removeExpire(db,key);
|
||||
removeExpire(db,key);
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
@@ -255,7 +241,7 @@ robj *dbRandomKey(redisDb *db) {
|
||||
sds key;
|
||||
robj *keyobj;
|
||||
|
||||
de = dictGetFairRandomKey(db->dict);
|
||||
de = dictGetRandomKey(db->dict);
|
||||
if (de == NULL) return NULL;
|
||||
|
||||
key = dictGetKey(de);
|
||||
@@ -347,19 +333,14 @@ 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
|
||||
* 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.
|
||||
* EMPTYDB_ASYNC if we want the memory to be freed in a different thread
|
||||
* and the function to return ASAP.
|
||||
*
|
||||
* On success the fuction returns the number of keys removed from the
|
||||
* database(s). Otherwise -1 is returned in the specific case the
|
||||
* DB number is out of range, and errno is set to EINVAL. */
|
||||
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*)) {
|
||||
long long emptyDb(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) {
|
||||
@@ -367,19 +348,6 @@ long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Pre-flush actions */
|
||||
if (!backup) {
|
||||
/* Fire the flushdb modules event. */
|
||||
moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
|
||||
REDISMODULE_SUBEVENT_FLUSHDB_START,
|
||||
&fi);
|
||||
|
||||
/* Make sure the WATCHed keys are affected by the FLUSH* commands.
|
||||
* Note that we need to call the function while the keys are still
|
||||
* there. */
|
||||
signalFlushedDb(dbnum);
|
||||
}
|
||||
|
||||
int startdb, enddb;
|
||||
if (dbnum == -1) {
|
||||
startdb = 0;
|
||||
@@ -389,40 +357,25 @@ long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(
|
||||
}
|
||||
|
||||
for (int j = startdb; j <= enddb; j++) {
|
||||
removed += dictSize(dbarray[j].dict);
|
||||
removed += dictSize(server.db[j].dict);
|
||||
if (async) {
|
||||
emptyDbAsync(&dbarray[j]);
|
||||
emptyDbAsync(&server.db[j]);
|
||||
} else {
|
||||
dictEmpty(dbarray[j].dict,callback);
|
||||
dictEmpty(dbarray[j].expires,callback);
|
||||
dictEmpty(server.db[j].dict,callback);
|
||||
dictEmpty(server.db[j].expires,callback);
|
||||
}
|
||||
}
|
||||
|
||||
/* Post-flush actions */
|
||||
if (!backup) {
|
||||
if (server.cluster_enabled) {
|
||||
if (async) {
|
||||
slotToKeyFlushAsync();
|
||||
} else {
|
||||
slotToKeyFlush();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
if (dbnum == -1) flushSlaveKeysWithExpireList();
|
||||
return removed;
|
||||
}
|
||||
|
||||
long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
return emptyDbGeneric(server.db, dbnum, flags, callback);
|
||||
}
|
||||
|
||||
int selectDb(client *c, int id) {
|
||||
if (id < 0 || id >= server.dbnum)
|
||||
return C_ERR;
|
||||
@@ -430,15 +383,6 @@ int selectDb(client *c, int id) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
long long dbTotalServerKeyCount() {
|
||||
long long total = 0;
|
||||
int j;
|
||||
for (j = 0; j < server.dbnum; j++) {
|
||||
total += dictSize(server.db[j].dict);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Hooks for key space changes.
|
||||
*
|
||||
@@ -450,12 +394,10 @@ long long dbTotalServerKeyCount() {
|
||||
|
||||
void signalModifiedKey(redisDb *db, robj *key) {
|
||||
touchWatchedKey(db,key);
|
||||
trackingInvalidateKey(key);
|
||||
}
|
||||
|
||||
void signalFlushedDb(int dbid) {
|
||||
touchWatchedKeysOnFlush(dbid);
|
||||
trackingInvalidateKeysOnFlush(dbid);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -484,10 +426,32 @@ int getFlushCommandFlags(client *c, int *flags) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Flushes the whole server data set. */
|
||||
void flushAllDataAndResetRDB(int flags) {
|
||||
/* FLUSHDB [ASYNC]
|
||||
*
|
||||
* Flushes the currently SELECTed Redis DB. */
|
||||
void flushdbCommand(client *c) {
|
||||
int flags;
|
||||
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
signalFlushedDb(c->db->id);
|
||||
server.dirty += emptyDb(c->db->id,flags,NULL);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
/* FLUSHALL [ASYNC]
|
||||
*
|
||||
* Flushes the whole server data set. */
|
||||
void flushallCommand(client *c) {
|
||||
int flags;
|
||||
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
signalFlushedDb(-1);
|
||||
server.dirty += emptyDb(-1,flags,NULL);
|
||||
if (server.rdb_child_pid != -1) killRDBChild();
|
||||
addReply(c,shared.ok);
|
||||
if (server.rdb_child_pid != -1) {
|
||||
kill(server.rdb_child_pid,SIGUSR1);
|
||||
rdbRemoveTempFile(server.rdb_child_pid);
|
||||
}
|
||||
if (server.saveparamslen > 0) {
|
||||
/* Normally rdbSave() will reset dirty, but we don't want this here
|
||||
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
|
||||
@@ -498,41 +462,6 @@ void flushAllDataAndResetRDB(int flags) {
|
||||
server.dirty = saved_dirty;
|
||||
}
|
||||
server.dirty++;
|
||||
#if defined(USE_JEMALLOC)
|
||||
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
||||
* for large databases, flushdb blocks for long anyway, so a bit more won't
|
||||
* harm and this way the flush and purge will be synchroneus. */
|
||||
if (!(flags & EMPTYDB_ASYNC))
|
||||
jemalloc_purge();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* FLUSHDB [ASYNC]
|
||||
*
|
||||
* Flushes the currently SELECTed Redis DB. */
|
||||
void flushdbCommand(client *c) {
|
||||
int flags;
|
||||
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
server.dirty += emptyDb(c->db->id,flags,NULL);
|
||||
addReply(c,shared.ok);
|
||||
#if defined(USE_JEMALLOC)
|
||||
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
||||
* for large databases, flushdb blocks for long anyway, so a bit more won't
|
||||
* harm and this way the flush and purge will be synchroneus. */
|
||||
if (!(flags & EMPTYDB_ASYNC))
|
||||
jemalloc_purge();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* FLUSHALL [ASYNC]
|
||||
*
|
||||
* Flushes the whole server data set. */
|
||||
void flushallCommand(client *c) {
|
||||
int flags;
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
flushAllDataAndResetRDB(flags);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
|
||||
/* This command implements DEL and LAZYDEL. */
|
||||
@@ -596,7 +525,7 @@ void randomkeyCommand(client *c) {
|
||||
robj *key;
|
||||
|
||||
if ((key = dbRandomKey(c->db)) == NULL) {
|
||||
addReplyNull(c);
|
||||
addReply(c,shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -610,7 +539,7 @@ void keysCommand(client *c) {
|
||||
sds pattern = c->argv[1]->ptr;
|
||||
int plen = sdslen(pattern), allkeys;
|
||||
unsigned long numkeys = 0;
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
|
||||
di = dictGetSafeIterator(c->db->dict);
|
||||
allkeys = (pattern[0] == '*' && plen == 1);
|
||||
@@ -628,7 +557,7 @@ void keysCommand(client *c) {
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredArrayLen(c,replylen,numkeys);
|
||||
setDeferredMultiBulkLength(c,replylen,numkeys);
|
||||
}
|
||||
|
||||
/* This callback is used by scanGenericCommand in order to collect elements
|
||||
@@ -682,7 +611,7 @@ int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor) {
|
||||
}
|
||||
|
||||
/* This command implements SCAN, HSCAN and SSCAN commands.
|
||||
* If object 'o' is passed, then it must be a Hash, Set or Zset object, otherwise
|
||||
* If object 'o' is passed, then it must be a Hash or Set object, otherwise
|
||||
* if 'o' is NULL the command will operate on the dictionary associated with
|
||||
* the current database.
|
||||
*
|
||||
@@ -698,7 +627,6 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
listNode *node, *nextnode;
|
||||
long count = 10;
|
||||
sds pat = NULL;
|
||||
sds typename = NULL;
|
||||
int patlen = 0, use_pattern = 0;
|
||||
dict *ht;
|
||||
|
||||
@@ -735,10 +663,6 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
use_pattern = !(pat[0] == '*' && patlen == 1);
|
||||
|
||||
i += 2;
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "type") && o == NULL && j >= 2) {
|
||||
/* SCAN for a particular type only applies to the db dict */
|
||||
typename = c->argv[i+1]->ptr;
|
||||
i+= 2;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
goto cleanup;
|
||||
@@ -833,13 +757,6 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Filter an element if it isn't the type we want. */
|
||||
if (!filter && o == NULL && typename){
|
||||
robj* typecheck = lookupKeyReadWithFlags(c->db, kobj, LOOKUP_NOTOUCH);
|
||||
char* type = getObjectTypeName(typecheck);
|
||||
if (strcasecmp((char*) typename, type)) filter = 1;
|
||||
}
|
||||
|
||||
/* Filter element if it is an expired key. */
|
||||
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
|
||||
|
||||
@@ -865,10 +782,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
}
|
||||
|
||||
/* Step 4: Reply to the client. */
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyMultiBulkLen(c, 2);
|
||||
addReplyBulkLongLong(c,cursor);
|
||||
|
||||
addReplyArrayLen(c, listLength(keys));
|
||||
addReplyMultiBulkLen(c, listLength(keys));
|
||||
while ((node = listFirst(keys)) != NULL) {
|
||||
robj *kobj = listNodeValue(node);
|
||||
addReplyBulk(c, kobj);
|
||||
@@ -896,8 +813,11 @@ void lastsaveCommand(client *c) {
|
||||
addReplyLongLong(c,server.lastsave);
|
||||
}
|
||||
|
||||
char* getObjectTypeName(robj *o) {
|
||||
char* type;
|
||||
void typeCommand(client *c) {
|
||||
robj *o;
|
||||
char *type;
|
||||
|
||||
o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
|
||||
if (o == NULL) {
|
||||
type = "none";
|
||||
} else {
|
||||
@@ -915,13 +835,7 @@ char* getObjectTypeName(robj *o) {
|
||||
default: type = "unknown"; break;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
void typeCommand(client *c) {
|
||||
robj *o;
|
||||
o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
|
||||
addReplyStatus(c, getObjectTypeName(o));
|
||||
addReplyStatus(c,type);
|
||||
}
|
||||
|
||||
void shutdownCommand(client *c) {
|
||||
@@ -1053,13 +967,6 @@ 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);
|
||||
}
|
||||
@@ -1090,7 +997,7 @@ void scanDatabaseForReadyLists(redisDb *db) {
|
||||
*
|
||||
* Returns C_ERR if at least one of the DB ids are out of range, otherwise
|
||||
* C_OK is returned. */
|
||||
int dbSwapDatabases(long id1, long id2) {
|
||||
int dbSwapDatabases(int id1, int id2) {
|
||||
if (id1 < 0 || id1 >= server.dbnum ||
|
||||
id2 < 0 || id2 >= server.dbnum) return C_ERR;
|
||||
if (id1 == id2) return C_OK;
|
||||
@@ -1103,12 +1010,10 @@ 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
|
||||
@@ -1296,17 +1201,13 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
propagateExpire(db,key,server.lazyfree_lazy_expire);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",key,db->id);
|
||||
int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
if (retval) signalModifiedKey(db,key);
|
||||
return retval;
|
||||
return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
|
||||
dbSyncDelete(db,key);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* 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). */
|
||||
@@ -1321,12 +1222,7 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
|
||||
last = cmd->lastkey;
|
||||
if (last < 0) last = argc+last;
|
||||
|
||||
int count = ((last - cmd->firstkey)+1);
|
||||
keys = getKeysTempBuffer;
|
||||
if (count > MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int)*count);
|
||||
|
||||
keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1));
|
||||
for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
|
||||
if (j >= argc) {
|
||||
/* Modules commands, and standard commands with a not fixed number
|
||||
@@ -1336,7 +1232,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) {
|
||||
getKeysFreeResult(keys);
|
||||
zfree(keys);
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
} else {
|
||||
@@ -1372,8 +1268,7 @@ int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *nu
|
||||
|
||||
/* Free the result of getKeysFromCommand. */
|
||||
void getKeysFreeResult(int *result) {
|
||||
if (result != getKeysTempBuffer)
|
||||
zfree(result);
|
||||
zfree(result);
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from following commands:
|
||||
@@ -1394,9 +1289,7 @@ 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 = getKeysTempBuffer;
|
||||
if (num+1>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int)*(num+1));
|
||||
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;
|
||||
@@ -1422,10 +1315,7 @@ int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
*numkeys = num;
|
||||
|
||||
/* Add all key positions for argv[3...n] to keys[] */
|
||||
@@ -1446,7 +1336,7 @@ int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
UNUSED(cmd);
|
||||
|
||||
num = 0;
|
||||
keys = getKeysTempBuffer; /* Alloc 2 places for the worst case. */
|
||||
keys = zmalloc(sizeof(int)*2); /* Alloc 2 places for the worst case. */
|
||||
|
||||
keys[num++] = 1; /* <sort-key> is always present. */
|
||||
|
||||
@@ -1504,10 +1394,7 @@ int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkey
|
||||
}
|
||||
}
|
||||
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
|
||||
keys = zmalloc(sizeof(int)*num);
|
||||
for (i = 0; i < num; i++) keys[i] = first+i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
@@ -1540,9 +1427,7 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
|
||||
* argv[1] = key,
|
||||
* argv[5...n] = stored key if present
|
||||
*/
|
||||
keys = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
|
||||
/* Add all key positions to keys[] */
|
||||
keys[0] = 1;
|
||||
@@ -1553,22 +1438,6 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
|
||||
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) {
|
||||
@@ -1607,10 +1476,7 @@ 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 = getKeysTempBuffer;
|
||||
if (num>MAX_KEYS_BUFFER)
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
for (i = streams_pos+1; i < argc-num; i++) keys[i-streams_pos-1] = i;
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
|
||||
+9
-169
@@ -297,56 +297,6 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_JEMALLOC
|
||||
void mallctl_int(client *c, robj **argv, int argc) {
|
||||
int ret;
|
||||
/* start with the biggest size (int64), and if that fails, try smaller sizes (int32, bool) */
|
||||
int64_t old = 0, val;
|
||||
if (argc > 1) {
|
||||
long long ll;
|
||||
if (getLongLongFromObjectOrReply(c, argv[1], &ll, NULL) != C_OK)
|
||||
return;
|
||||
val = ll;
|
||||
}
|
||||
size_t sz = sizeof(old);
|
||||
while (sz > 0) {
|
||||
if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, argc > 1? &val: NULL, argc > 1?sz: 0))) {
|
||||
if (ret==EINVAL) {
|
||||
/* size might be wrong, try a smaller one */
|
||||
sz /= 2;
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
val <<= 8*sz;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
addReplyErrorFormat(c,"%s", strerror(ret));
|
||||
return;
|
||||
} else {
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
old >>= 64 - 8*sz;
|
||||
#endif
|
||||
addReplyLongLong(c, old);
|
||||
return;
|
||||
}
|
||||
}
|
||||
addReplyErrorFormat(c,"%s", strerror(EINVAL));
|
||||
}
|
||||
|
||||
void mallctl_string(client *c, robj **argv, int argc) {
|
||||
int ret;
|
||||
char *old;
|
||||
size_t sz = sizeof(old);
|
||||
/* for strings, it seems we need to first get the old value, before overriding it. */
|
||||
if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, NULL, 0))) {
|
||||
addReplyErrorFormat(c,"%s", strerror(ret));
|
||||
return;
|
||||
}
|
||||
addReplyBulkCString(c, old);
|
||||
if(argc > 1)
|
||||
je_mallctl(argv[0]->ptr, NULL, 0, &argv[1]->ptr, sizeof(char*));
|
||||
}
|
||||
#endif
|
||||
|
||||
void debugCommand(client *c) {
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
@@ -355,7 +305,6 @@ 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.",
|
||||
@@ -363,7 +312,6 @@ 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.",
|
||||
@@ -371,15 +319,10 @@ void debugCommand(client *c) {
|
||||
"SDSLEN <key> -- Show low level SDS string info representing key and value.",
|
||||
"SEGFAULT -- Crash the server with sigsegv.",
|
||||
"SET-ACTIVE-EXPIRE <0|1> -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.",
|
||||
"AOF-FLUSH-SLEEP <microsec> -- Server will sleep before flushing the AOF, this is used for testing",
|
||||
"SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.",
|
||||
"STRUCTSIZE -- Return the size of different Redis core C structures.",
|
||||
"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
|
||||
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
|
||||
#ifdef USE_JEMALLOC
|
||||
"MALLCTL <key> [<val>] -- Get or set a malloc tunning integer.",
|
||||
"MALLCTL-STR <key> [<val>] -- Get or set a malloc tunning string.",
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@@ -419,7 +362,7 @@ NULL
|
||||
}
|
||||
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
protectClient(c);
|
||||
int ret = rdbLoad(server.rdb_filename,NULL,RDBFLAGS_NONE);
|
||||
int ret = rdbLoad(server.rdb_filename,NULL);
|
||||
unprotectClient(c);
|
||||
if (ret != C_OK) {
|
||||
addReplyError(c,"Error trying to load the RDB dump");
|
||||
@@ -574,7 +517,7 @@ NULL
|
||||
sdsfree(d);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"digest-value") && c->argc >= 2) {
|
||||
/* DEBUG DIGEST-VALUE key key key ... key. */
|
||||
addReplyArrayLen(c,c->argc-2);
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
unsigned char digest[20];
|
||||
memset(digest,0,20); /* Start with a clean result */
|
||||
@@ -586,58 +529,6 @@ NULL
|
||||
addReplyStatus(c,d);
|
||||
sdsfree(d);
|
||||
}
|
||||
} 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] */
|
||||
char *name = c->argv[2]->ptr;
|
||||
if (!strcasecmp(name,"string")) {
|
||||
addReplyBulkCString(c,"Hello World");
|
||||
} else if (!strcasecmp(name,"integer")) {
|
||||
addReplyLongLong(c,12345);
|
||||
} else if (!strcasecmp(name,"double")) {
|
||||
addReplyDouble(c,3.14159265359);
|
||||
} else if (!strcasecmp(name,"bignum")) {
|
||||
addReplyProto(c,"(1234567999999999999999999999999999999\r\n",40);
|
||||
} else if (!strcasecmp(name,"null")) {
|
||||
addReplyNull(c);
|
||||
} else if (!strcasecmp(name,"array")) {
|
||||
addReplyArrayLen(c,3);
|
||||
for (int j = 0; j < 3; j++) addReplyLongLong(c,j);
|
||||
} else if (!strcasecmp(name,"set")) {
|
||||
addReplySetLen(c,3);
|
||||
for (int j = 0; j < 3; j++) addReplyLongLong(c,j);
|
||||
} else if (!strcasecmp(name,"map")) {
|
||||
addReplyMapLen(c,3);
|
||||
for (int j = 0; j < 3; j++) {
|
||||
addReplyLongLong(c,j);
|
||||
addReplyBool(c, j == 1);
|
||||
}
|
||||
} else if (!strcasecmp(name,"attrib")) {
|
||||
addReplyAttributeLen(c,1);
|
||||
addReplyBulkCString(c,"key-popularity");
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyBulkCString(c,"key:123");
|
||||
addReplyLongLong(c,90);
|
||||
/* Attributes are not real replies, so a well formed reply should
|
||||
* also have a normal reply type after the attribute. */
|
||||
addReplyBulkCString(c,"Some real reply following the attribute");
|
||||
} else if (!strcasecmp(name,"push")) {
|
||||
addReplyPushLen(c,2);
|
||||
addReplyBulkCString(c,"server-cpu-usage");
|
||||
addReplyLongLong(c,42);
|
||||
/* Push replies are not synchronous replies, so we emit also a
|
||||
* normal reply in order for blocking clients just discarding the
|
||||
* push reply, to actually consume the reply and continue. */
|
||||
addReplyBulkCString(c,"Some real reply following the push reply");
|
||||
} else if (!strcasecmp(name,"true")) {
|
||||
addReplyBool(c,1);
|
||||
} else if (!strcasecmp(name,"false")) {
|
||||
addReplyBool(c,0);
|
||||
} 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");
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
|
||||
double dtime = strtod(c->argv[2]->ptr,NULL);
|
||||
long long utime = dtime*1000000;
|
||||
@@ -652,11 +543,6 @@ NULL
|
||||
{
|
||||
server.active_expire_enabled = atoi(c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"aof-flush-sleep") &&
|
||||
c->argc == 3)
|
||||
{
|
||||
server.aof_flush_sleep = atoi(c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"lua-always-replicate-commands") &&
|
||||
c->argc == 3)
|
||||
{
|
||||
@@ -685,12 +571,9 @@ NULL
|
||||
sds stats = sdsempty();
|
||||
char buf[4096];
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK) {
|
||||
sdsfree(stats);
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK)
|
||||
return;
|
||||
}
|
||||
if (dbid < 0 || dbid >= server.dbnum) {
|
||||
sdsfree(stats);
|
||||
addReplyError(c,"Out of range database");
|
||||
return;
|
||||
}
|
||||
@@ -703,8 +586,7 @@ NULL
|
||||
dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
|
||||
stats = sdscat(stats,buf);
|
||||
|
||||
addReplyVerbatim(c,stats,sdslen(stats),"txt");
|
||||
sdsfree(stats);
|
||||
addReplyBulkSds(c,stats);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) {
|
||||
robj *o;
|
||||
dict *ht = NULL;
|
||||
@@ -731,7 +613,7 @@ NULL
|
||||
} else {
|
||||
char buf[4096];
|
||||
dictGetStats(buf,sizeof(buf),ht);
|
||||
addReplyVerbatim(c,buf,strlen(buf),"txt");
|
||||
addReplyBulkCString(c,buf);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) {
|
||||
serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id");
|
||||
@@ -742,14 +624,6 @@ NULL
|
||||
{
|
||||
stringmatchlen_fuzz_test();
|
||||
addReplyStatus(c,"Apparently Redis did not crash: test passed");
|
||||
#ifdef USE_JEMALLOC
|
||||
} else if(!strcasecmp(c->argv[1]->ptr,"mallctl") && c->argc >= 3) {
|
||||
mallctl_int(c, c->argv+2, c->argc-2);
|
||||
return;
|
||||
} else if(!strcasecmp(c->argv[1]->ptr,"mallctl-str") && c->argc >= 3) {
|
||||
mallctl_string(c, c->argv+2, c->argc-2);
|
||||
return;
|
||||
#endif
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
@@ -773,12 +647,11 @@ void _serverAssert(const char *estr, const char *file, int line) {
|
||||
|
||||
void _serverAssertPrintClientInfo(const client *c) {
|
||||
int j;
|
||||
char conninfo[CONN_INFO_LEN];
|
||||
|
||||
bugReportStart();
|
||||
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
|
||||
serverLog(LL_WARNING,"client->flags = %llu", (unsigned long long) c->flags);
|
||||
serverLog(LL_WARNING,"client->conn = %s", connGetInfo(c->conn, conninfo, sizeof(conninfo)));
|
||||
serverLog(LL_WARNING,"client->flags = %d", c->flags);
|
||||
serverLog(LL_WARNING,"client->fd = %d", c->fd);
|
||||
serverLog(LL_WARNING,"client->argc = %d", c->argc);
|
||||
for (j=0; j < c->argc; j++) {
|
||||
char buf[128];
|
||||
@@ -878,7 +751,7 @@ static void *getMcontextEip(ucontext_t *uc) {
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
/* Linux */
|
||||
#if defined(__i386__) || defined(__ILP32__)
|
||||
#if defined(__i386__)
|
||||
return (void*) uc->uc_mcontext.gregs[14]; /* Linux 32 */
|
||||
#elif defined(__X86_64__) || defined(__x86_64__)
|
||||
return (void*) uc->uc_mcontext.gregs[16]; /* Linux 64 */
|
||||
@@ -990,7 +863,7 @@ void logRegisters(ucontext_t *uc) {
|
||||
/* Linux */
|
||||
#elif defined(__linux__)
|
||||
/* Linux x86 */
|
||||
#if defined(__i386__) || defined(__ILP32__)
|
||||
#if defined(__i386__)
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
|
||||
@@ -1185,33 +1058,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");
|
||||
@@ -1439,12 +1285,6 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
/* Log dump of processor registers */
|
||||
logRegisters(uc);
|
||||
|
||||
/* Log Modules INFO */
|
||||
serverLogRaw(LL_WARNING|LL_RAW, "\n------ MODULES INFO OUTPUT ------\n");
|
||||
infostring = modulesCollectInfo(sdsempty(), NULL, 1, 0);
|
||||
serverLogRaw(LL_WARNING|LL_RAW, infostring);
|
||||
sdsfree(infostring);
|
||||
|
||||
#if defined(HAVE_PROC_MAPS)
|
||||
/* Test memory */
|
||||
serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n");
|
||||
|
||||
+44
-98
@@ -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) 2020, Oran Agra
|
||||
* Copyright (c) 2020, Redis Labs, Inc
|
||||
* Copyright (c) 2017, Oran Agra
|
||||
* Copyright (c) 2017, Redis Labs, Inc
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -374,7 +374,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
||||
if ((newele = activeDefragStringOb(ele, &defragged)))
|
||||
de->v.val = newele, defragged++;
|
||||
} else if (dict_val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) {
|
||||
void *newptr, *ptr = dictGetVal(de);
|
||||
void *newptr, *ptr = ln->value;
|
||||
if ((newptr = activeDefragAlloc(ptr)))
|
||||
ln->value = newptr, defragged++;
|
||||
}
|
||||
@@ -408,32 +408,25 @@ dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sd
|
||||
return NULL;
|
||||
}
|
||||
|
||||
long activeDefragQuickListNode(quicklist *ql, quicklistNode **node_ref) {
|
||||
quicklistNode *newnode, *node = *node_ref;
|
||||
long activeDefragQuickListNodes(quicklist *ql) {
|
||||
quicklistNode *node = ql->head, *newnode;
|
||||
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) {
|
||||
defragged += activeDefragQuickListNode(ql, &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;
|
||||
node = node->next;
|
||||
}
|
||||
return defragged;
|
||||
@@ -447,48 +440,12 @@ void defragLater(redisDb *db, dictEntry *kde) {
|
||||
listAddNodeTail(db->defrag_later, key);
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
long scanLaterList(robj *ob) {
|
||||
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;
|
||||
|
||||
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;
|
||||
server.stat_active_defrag_scanned+=ql->len;
|
||||
return activeDefragQuickListNodes(ql);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
@@ -681,8 +638,7 @@ int scanLaterStraemListpacks(robj *ob, unsigned long *cursor, long long endtime,
|
||||
void *newdata = activeDefragAlloc(ri.data);
|
||||
if (newdata)
|
||||
raxSetData(ri.node, ri.data=newdata), (*defragged)++;
|
||||
server.stat_active_defrag_scanned++;
|
||||
if (++iterations > 128) {
|
||||
if (++iterations > 16) {
|
||||
if (ustime() > endtime) {
|
||||
serverAssert(ri.key_len==sizeof(last));
|
||||
memcpy(last,ri.key,ri.key_len);
|
||||
@@ -944,7 +900,8 @@ int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime) {
|
||||
if (de) {
|
||||
robj *ob = dictGetVal(de);
|
||||
if (ob->type == OBJ_LIST) {
|
||||
return scanLaterList(ob, cursor, endtime, &server.stat_active_defrag_hits);
|
||||
server.stat_active_defrag_hits += scanLaterList(ob);
|
||||
*cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
server.stat_active_defrag_hits += scanLaterSet(ob, cursor);
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
@@ -962,12 +919,10 @@ 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;
|
||||
@@ -975,15 +930,16 @@ 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 (!defrag_later_cursor) {
|
||||
if (!cursor) {
|
||||
listNode *head = listFirst(db->defrag_later);
|
||||
|
||||
/* Move on to next key */
|
||||
if (defrag_later_current_key) {
|
||||
serverAssert(defrag_later_current_key == head->value);
|
||||
if (current_key) {
|
||||
serverAssert(current_key == head->value);
|
||||
sdsfree(head->value);
|
||||
listDelNode(db->defrag_later, head);
|
||||
defrag_later_cursor = 0;
|
||||
defrag_later_current_key = NULL;
|
||||
cursor = 0;
|
||||
current_key = NULL;
|
||||
}
|
||||
|
||||
/* stop if we reached the last one. */
|
||||
@@ -992,18 +948,23 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
return 0;
|
||||
|
||||
/* start a new key */
|
||||
defrag_later_current_key = head->value;
|
||||
defrag_later_cursor = 0;
|
||||
current_key = head->value;
|
||||
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, defrag_later_current_key);
|
||||
dictEntry *de = dictFind(db->dict, current_key);
|
||||
key_defragged = server.stat_active_defrag_hits;
|
||||
do {
|
||||
int quit = 0;
|
||||
if (defragLaterItem(de, &defrag_later_cursor, endtime))
|
||||
if (defragLaterItem(de, &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. */
|
||||
@@ -1021,7 +982,7 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
prev_defragged = server.stat_active_defrag_hits;
|
||||
prev_scanned = server.stat_active_defrag_scanned;
|
||||
}
|
||||
} while(defrag_later_cursor);
|
||||
} while(cursor);
|
||||
if(key_defragged != server.stat_active_defrag_hits)
|
||||
server.stat_active_defrag_key_hits++;
|
||||
else
|
||||
@@ -1078,22 +1039,7 @@ 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())
|
||||
if (server.aof_child_pid!=-1 || server.rdb_child_pid!=-1)
|
||||
return; /* Defragging memory while there's a fork will just do damage. */
|
||||
|
||||
/* Once a second, check if we the fragmentation justfies starting a scan
|
||||
|
||||
-31
@@ -739,30 +739,6 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
return stored;
|
||||
}
|
||||
|
||||
/* This is like dictGetRandomKey() from the POV of the API, but will do more
|
||||
* work to ensure a better distribution of the returned element.
|
||||
*
|
||||
* This function improves the distribution because the dictGetRandomKey()
|
||||
* problem is that it selects a random bucket, then it selects a random
|
||||
* element from the chain in the bucket. However elements being in different
|
||||
* chain lengths will have different probabilities of being reported. With
|
||||
* this function instead what we do is to consider a "linear" range of the table
|
||||
* that may be constituted of N buckets with chains of different lengths
|
||||
* appearing one after the other. Then we report a random element in the range.
|
||||
* In this way we smooth away the problem of different chain lenghts. */
|
||||
#define GETFAIR_NUM_ENTRIES 15
|
||||
dictEntry *dictGetFairRandomKey(dict *d) {
|
||||
dictEntry *entries[GETFAIR_NUM_ENTRIES];
|
||||
unsigned int count = dictGetSomeKeys(d,entries,GETFAIR_NUM_ENTRIES);
|
||||
/* Note that dictGetSomeKeys() may return zero elements in an unlucky
|
||||
* run() even if there are actually elements inside the hash table. So
|
||||
* when we get zero, we call the true dictGetRandomKey() that will always
|
||||
* yeld the element if the hash table has at least one. */
|
||||
if (count == 0) return dictGetRandomKey(d);
|
||||
unsigned int idx = rand() % count;
|
||||
return entries[idx];
|
||||
}
|
||||
|
||||
/* Function to reverse bits. Algorithm from:
|
||||
* http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
|
||||
static unsigned long rev(unsigned long v) {
|
||||
@@ -871,10 +847,6 @@ 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;
|
||||
@@ -941,9 +913,6 @@ unsigned long dictScan(dict *d,
|
||||
} while (v & (m0 ^ m1));
|
||||
}
|
||||
|
||||
/* undo the ++ at the top */
|
||||
d->iterators--;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
@@ -166,7 +166,6 @@ dictIterator *dictGetSafeIterator(dict *d);
|
||||
dictEntry *dictNext(dictIterator *iter);
|
||||
void dictReleaseIterator(dictIterator *iter);
|
||||
dictEntry *dictGetRandomKey(dict *d);
|
||||
dictEntry *dictGetFairRandomKey(dict *d);
|
||||
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
|
||||
void dictGetStats(char *buf, size_t bufsize, dict *d);
|
||||
uint64_t dictGenHashFunction(const void *key, int len);
|
||||
|
||||
+5
-4
@@ -78,7 +78,7 @@ unsigned int getLRUClock(void) {
|
||||
unsigned int LRU_CLOCK(void) {
|
||||
unsigned int lruclock;
|
||||
if (1000/server.hz <= LRU_CLOCK_RESOLUTION) {
|
||||
lruclock = server.lruclock;
|
||||
atomicGet(server.lruclock,lruclock);
|
||||
} else {
|
||||
lruclock = getLRUClock();
|
||||
}
|
||||
@@ -444,7 +444,6 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
||||
* Otehrwise if we are over the memory limit, but not enough memory
|
||||
* was freed to return back under the limit, the function returns C_ERR. */
|
||||
int freeMemoryIfNeeded(void) {
|
||||
int keys_freed = 0;
|
||||
/* By default replicas should ignore maxmemory
|
||||
* and just be masters exact copies. */
|
||||
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
|
||||
@@ -468,7 +467,7 @@ int freeMemoryIfNeeded(void) {
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
while (mem_freed < mem_tofree) {
|
||||
int j, k, i;
|
||||
int j, k, i, keys_freed = 0;
|
||||
static unsigned int next_db = 0;
|
||||
sds bestkey = NULL;
|
||||
int bestdbid;
|
||||
@@ -599,7 +598,9 @@ int freeMemoryIfNeeded(void) {
|
||||
mem_freed = mem_tofree;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (!keys_freed) {
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
goto cant_free; /* nothing to free... */
|
||||
|
||||
+35
-119
@@ -64,7 +64,6 @@ int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
dbSyncDelete(db,keyobj);
|
||||
notifyKeyspaceEvent(NOTIFY_EXPIRED,
|
||||
"expired",keyobj,db->id);
|
||||
trackingInvalidateKey(keyobj);
|
||||
decrRefCount(keyobj);
|
||||
server.stat_expiredkeys++;
|
||||
return 1;
|
||||
@@ -78,63 +77,24 @@ 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.
|
||||
*
|
||||
* 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.
|
||||
* No more than CRON_DBS_PER_CALL databases are tested at every
|
||||
* iteration.
|
||||
*
|
||||
* 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).
|
||||
* 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.
|
||||
*
|
||||
* 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:
|
||||
* Expire cycle type:
|
||||
*
|
||||
* 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. 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. */
|
||||
* as specified by the ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC define. */
|
||||
|
||||
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. */
|
||||
@@ -152,16 +112,10 @@ 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, unless the percentage of estimated stale keys is
|
||||
* too high. Also never repeat a fast cycle for the same period
|
||||
* for time limit. Also don't repeat a fast cycle for the same period
|
||||
* as the fast cycle total duration itself. */
|
||||
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;
|
||||
|
||||
if (!timelimit_exit) return;
|
||||
if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;
|
||||
last_fast_cycle = start;
|
||||
}
|
||||
|
||||
@@ -175,16 +129,16 @@ void activeExpireCycle(int type) {
|
||||
if (dbs_per_call > server.dbnum || timelimit_exit)
|
||||
dbs_per_call = server.dbnum;
|
||||
|
||||
/* 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
|
||||
/* 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
|
||||
* server.hz times per second, the following is the max amount of
|
||||
* microseconds we can spend in this function. */
|
||||
timelimit = config_cycle_slow_time_perc*1000000/server.hz/100;
|
||||
timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
|
||||
timelimit_exit = 0;
|
||||
if (timelimit <= 0) timelimit = 1;
|
||||
|
||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST)
|
||||
timelimit = config_cycle_fast_duration; /* in microseconds. */
|
||||
timelimit = ACTIVE_EXPIRE_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
|
||||
@@ -193,9 +147,7 @@ void activeExpireCycle(int type) {
|
||||
long total_expired = 0;
|
||||
|
||||
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
|
||||
/* Expired and checked in a single loop. */
|
||||
unsigned long expired, sampled;
|
||||
|
||||
int expired;
|
||||
redisDb *db = server.db+(current_db % server.dbnum);
|
||||
|
||||
/* Increment the DB now so we are sure if we run out of time
|
||||
@@ -203,10 +155,8 @@ void activeExpireCycle(int type) {
|
||||
* distribute the time evenly across DBs. */
|
||||
current_db++;
|
||||
|
||||
/* 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". */
|
||||
/* Continue to expire if at the end of the cycle more than 25%
|
||||
* of the keys were expired. */
|
||||
do {
|
||||
unsigned long num, slots;
|
||||
long long now, ttl_sum;
|
||||
@@ -221,8 +171,8 @@ void activeExpireCycle(int type) {
|
||||
slots = dictSlots(db->expires);
|
||||
now = mstime();
|
||||
|
||||
/* When there are less than 1% filled slots, sampling the key
|
||||
* space is expensive, so stop here waiting for better times...
|
||||
/* When there are less than 1% filled slots getting random
|
||||
* keys 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;
|
||||
@@ -230,58 +180,27 @@ 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 > config_keys_per_loop)
|
||||
num = config_keys_per_loop;
|
||||
if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
|
||||
num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
|
||||
|
||||
/* 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;
|
||||
while (num--) {
|
||||
dictEntry *de;
|
||||
long long ttl;
|
||||
|
||||
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++;
|
||||
}
|
||||
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++;
|
||||
}
|
||||
db->expires_cursor++;
|
||||
total_sampled++;
|
||||
}
|
||||
total_expired += expired;
|
||||
total_sampled += sampled;
|
||||
|
||||
/* Update the average TTL stats for this database. */
|
||||
if (ttl_samples) {
|
||||
@@ -305,15 +224,12 @@ void activeExpireCycle(int type) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* 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);
|
||||
/* 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);
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -466,7 +466,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.emptyarray)) == NULL ||
|
||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.emptymultibulk)) == NULL ||
|
||||
checkType(c, zobj, OBJ_ZSET)) {
|
||||
return;
|
||||
}
|
||||
@@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
|
||||
/* If no matching results, the user gets an empty reply. */
|
||||
if (ga->used == 0 && storekey == NULL) {
|
||||
addReply(c,shared.emptyarray);
|
||||
addReply(c, shared.emptymultibulk);
|
||||
geoArrayFree(ga);
|
||||
return;
|
||||
}
|
||||
@@ -597,11 +597,11 @@ void georadiusGeneric(client *c, int flags) {
|
||||
if (withhash)
|
||||
option_length++;
|
||||
|
||||
/* The array len we send is exactly result_length. The result is
|
||||
/* The multibulk len we send is exactly result_length. The result is
|
||||
* either all strings of just zset members *or* a nested multi-bulk
|
||||
* reply containing the zset member string _and_ all the additional
|
||||
* options the user enabled for this request. */
|
||||
addReplyArrayLen(c, returned_items);
|
||||
addReplyMultiBulkLen(c, returned_items);
|
||||
|
||||
/* Finally send results back to the caller */
|
||||
int i;
|
||||
@@ -613,7 +613,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
* as a nested multi-bulk. Add 1 to account for result value
|
||||
* itself. */
|
||||
if (option_length)
|
||||
addReplyArrayLen(c, option_length + 1);
|
||||
addReplyMultiBulkLen(c, option_length + 1);
|
||||
|
||||
addReplyBulkSds(c,gp->member);
|
||||
gp->member = NULL;
|
||||
@@ -625,7 +625,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
addReplyLongLong(c, gp->score);
|
||||
|
||||
if (withcoords) {
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyMultiBulkLen(c, 2);
|
||||
addReplyHumanLongDouble(c, gp->longitude);
|
||||
addReplyHumanLongDouble(c, gp->latitude);
|
||||
}
|
||||
@@ -706,11 +706,11 @@ void geohashCommand(client *c) {
|
||||
|
||||
/* Geohash elements one after the other, using a null bulk reply for
|
||||
* missing elements. */
|
||||
addReplyArrayLen(c,c->argc-2);
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
double score;
|
||||
if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
|
||||
addReplyNull(c);
|
||||
addReply(c,shared.nullbulk);
|
||||
} else {
|
||||
/* The internal format we use for geocoding is a bit different
|
||||
* than the standard, since we use as initial latitude range
|
||||
@@ -721,7 +721,7 @@ void geohashCommand(client *c) {
|
||||
/* Decode... */
|
||||
double xy[2];
|
||||
if (!decodeGeohash(score,xy)) {
|
||||
addReplyNull(c);
|
||||
addReply(c,shared.nullbulk);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -737,15 +737,7 @@ void geohashCommand(client *c) {
|
||||
char buf[12];
|
||||
int i;
|
||||
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;
|
||||
}
|
||||
int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
|
||||
buf[i] = geoalphabet[idx];
|
||||
}
|
||||
buf[11] = '\0';
|
||||
@@ -767,19 +759,19 @@ void geoposCommand(client *c) {
|
||||
|
||||
/* Report elements one after the other, using a null bulk reply for
|
||||
* missing elements. */
|
||||
addReplyArrayLen(c,c->argc-2);
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
double score;
|
||||
if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
|
||||
addReplyNullArray(c);
|
||||
addReply(c,shared.nullmultibulk);
|
||||
} else {
|
||||
/* Decode... */
|
||||
double xy[2];
|
||||
if (!decodeGeohash(score,xy)) {
|
||||
addReplyNullArray(c);
|
||||
addReply(c,shared.nullmultibulk);
|
||||
continue;
|
||||
}
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyHumanLongDouble(c,xy[0]);
|
||||
addReplyHumanLongDouble(c,xy[1]);
|
||||
}
|
||||
@@ -805,7 +797,7 @@ void geodistCommand(client *c) {
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp]))
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.nullbulk))
|
||||
== NULL || checkType(c, zobj, OBJ_ZSET)) return;
|
||||
|
||||
/* Get the scores. We need both otherwise NULL is returned. */
|
||||
@@ -813,13 +805,13 @@ void geodistCommand(client *c) {
|
||||
if (zsetScore(zobj, c->argv[2]->ptr, &score1) == C_ERR ||
|
||||
zsetScore(zobj, c->argv[3]->ptr, &score2) == C_ERR)
|
||||
{
|
||||
addReplyNull(c);
|
||||
addReply(c,shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Decode & compute the distance. */
|
||||
if (!decodeGeohash(score1,xyxy) || !decodeGeohash(score2,xyxy+2))
|
||||
addReplyNull(c);
|
||||
addReply(c,shared.nullbulk);
|
||||
else
|
||||
addReplyDoubleDistance(c,
|
||||
geohashGetDistance(xyxy[0],xyxy[1],xyxy[2],xyxy[3]) / to_meter);
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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"
|
||||
|
||||
/* Emit an item in Gopher directory listing format:
|
||||
* <type><descr><TAB><selector><TAB><hostname><TAB><port>
|
||||
* If descr or selector are NULL, then the "(NULL)" string is used instead. */
|
||||
void addReplyGopherItem(client *c, const char *type, const char *descr,
|
||||
const char *selector, const char *hostname, int port)
|
||||
{
|
||||
sds item = sdscatfmt(sdsempty(),"%s%s\t%s\t%s\t%i\r\n",
|
||||
type, descr,
|
||||
selector ? selector : "(NULL)",
|
||||
hostname ? hostname : "(NULL)",
|
||||
port);
|
||||
addReplyProto(c,item,sdslen(item));
|
||||
sdsfree(item);
|
||||
}
|
||||
|
||||
/* This is called by processInputBuffer() when an inline request is processed
|
||||
* with Gopher mode enabled, and the request happens to have zero or just one
|
||||
* argument. In such case we get the relevant key and reply using the Gopher
|
||||
* protocol. */
|
||||
void processGopherRequest(client *c) {
|
||||
robj *keyname = c->argc == 0 ? createStringObject("/",1) : c->argv[0];
|
||||
robj *o = lookupKeyRead(c->db,keyname);
|
||||
|
||||
/* If there is no such key, return with a Gopher error. */
|
||||
if (o == NULL || o->type != OBJ_STRING) {
|
||||
char *errstr;
|
||||
if (o == NULL)
|
||||
errstr = "Error: no content at the specified key";
|
||||
else
|
||||
errstr = "Error: selected key type is invalid "
|
||||
"for Gopher output";
|
||||
addReplyGopherItem(c,"i",errstr,NULL,NULL,0);
|
||||
addReplyGopherItem(c,"i","Redis Gopher server",NULL,NULL,0);
|
||||
} else {
|
||||
addReply(c,o);
|
||||
}
|
||||
|
||||
/* Cleanup, also make sure to emit the final ".CRLF" line. Note that
|
||||
* the connection will be closed immediately after this because the client
|
||||
* will be flagged with CLIENT_CLOSE_AFTER_REPLY, in accordance with the
|
||||
* Gopher protocol. */
|
||||
if (c->argc == 0) decrRefCount(keyname);
|
||||
|
||||
/* Note that in theory we should terminate the Gopher request with
|
||||
* ".<CR><LF>" (called Lastline in the RFC) like that:
|
||||
*
|
||||
* addReplyProto(c,".\r\n",3);
|
||||
*
|
||||
* However after examining the current clients landscape, it's probably
|
||||
* going to do more harm than good for several reasons:
|
||||
*
|
||||
* 1. Clients should not have any issue with missing .<CR><LF> as for
|
||||
* specification, and in the real world indeed certain servers
|
||||
* implementations never used to send the terminator.
|
||||
*
|
||||
* 2. Redis does not know if it's serving a text file or a binary file:
|
||||
* at the same time clients will not remove the ".<CR><LF>" bytes at
|
||||
* tne end when downloading a binary file from the server, so adding
|
||||
* the "Lastline" terminator without knowing the content is just
|
||||
* dangerous.
|
||||
*
|
||||
* 3. The utility gopher2redis.rb that we provide for Redis, and any
|
||||
* other similar tool you may use as Gopher authoring system for
|
||||
* Redis, can just add the "Lastline" when needed.
|
||||
*/
|
||||
}
|
||||
+3
-3
@@ -1242,7 +1242,7 @@ void pfcountCommand(client *c) {
|
||||
if (o == NULL) continue; /* Assume empty HLL for non existing var.*/
|
||||
if (isHLLObjectOrReply(c,o) != C_OK) return;
|
||||
|
||||
/* Merge with this HLL with our 'max' HLL by setting max[i]
|
||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
||||
* to MAX(max[i],hll[i]). */
|
||||
if (hllMerge(registers,o) == C_ERR) {
|
||||
addReplySds(c,sdsnew(invalid_hll_err));
|
||||
@@ -1329,7 +1329,7 @@ void pfmergeCommand(client *c) {
|
||||
hdr = o->ptr;
|
||||
if (hdr->encoding == HLL_DENSE) use_dense = 1;
|
||||
|
||||
/* Merge with this HLL with our 'max' HLL by setting max[i]
|
||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
||||
* to MAX(max[i],hll[i]). */
|
||||
if (hllMerge(max,o) == C_ERR) {
|
||||
addReplySds(c,sdsnew(invalid_hll_err));
|
||||
@@ -1519,7 +1519,7 @@ void pfdebugCommand(client *c) {
|
||||
}
|
||||
|
||||
hdr = o->ptr;
|
||||
addReplyArrayLen(c,HLL_REGISTERS);
|
||||
addReplyMultiBulkLen(c,HLL_REGISTERS);
|
||||
for (j = 0; j < HLL_REGISTERS; j++) {
|
||||
uint8_t val;
|
||||
|
||||
|
||||
+10
-10
@@ -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 created on demand in order to avoid
|
||||
* of time series, each time serie is craeted on demand in order to avoid
|
||||
* having a fixed list to maintain. */
|
||||
void latencyMonitorInit(void) {
|
||||
server.latency_events = dictCreate(&latencyTimeSeriesDictType,NULL);
|
||||
@@ -95,7 +95,7 @@ void latencyMonitorInit(void) {
|
||||
* This function is usually called via latencyAddSampleIfNeeded(), that
|
||||
* is a macro that only adds the sample if the latency is higher than
|
||||
* server.latency_monitor_threshold. */
|
||||
void latencyAddSample(const char *event, mstime_t latency) {
|
||||
void latencyAddSample(char *event, mstime_t latency) {
|
||||
struct latencyTimeSeries *ts = dictFetchValue(server.latency_events,event);
|
||||
time_t now = time(NULL);
|
||||
int prev;
|
||||
@@ -476,19 +476,19 @@ sds createLatencyReport(void) {
|
||||
/* latencyCommand() helper to produce a time-delay reply for all the samples
|
||||
* in memory for the specified time series. */
|
||||
void latencyCommandReplyWithSamples(client *c, struct latencyTimeSeries *ts) {
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
int samples = 0, j;
|
||||
|
||||
for (j = 0; j < LATENCY_TS_LEN; j++) {
|
||||
int i = (ts->idx + j) % LATENCY_TS_LEN;
|
||||
|
||||
if (ts->samples[i].time == 0) continue;
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyLongLong(c,ts->samples[i].time);
|
||||
addReplyLongLong(c,ts->samples[i].latency);
|
||||
samples++;
|
||||
}
|
||||
setDeferredArrayLen(c,replylen,samples);
|
||||
setDeferredMultiBulkLength(c,replylen,samples);
|
||||
}
|
||||
|
||||
/* latencyCommand() helper to produce the reply for the LATEST subcommand,
|
||||
@@ -497,14 +497,14 @@ void latencyCommandReplyWithLatestEvents(client *c) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
addReplyArrayLen(c,dictSize(server.latency_events));
|
||||
addReplyMultiBulkLen(c,dictSize(server.latency_events));
|
||||
di = dictGetIterator(server.latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = dictGetKey(de);
|
||||
struct latencyTimeSeries *ts = dictGetVal(de);
|
||||
int last = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
|
||||
|
||||
addReplyArrayLen(c,4);
|
||||
addReplyMultiBulkLen(c,4);
|
||||
addReplyBulkCString(c,event);
|
||||
addReplyLongLong(c,ts->samples[last].time);
|
||||
addReplyLongLong(c,ts->samples[last].latency);
|
||||
@@ -583,7 +583,7 @@ NULL
|
||||
/* LATENCY HISTORY <event> */
|
||||
ts = dictFetchValue(server.latency_events,c->argv[2]->ptr);
|
||||
if (ts == NULL) {
|
||||
addReplyArrayLen(c,0);
|
||||
addReplyMultiBulkLen(c,0);
|
||||
} else {
|
||||
latencyCommandReplyWithSamples(c,ts);
|
||||
}
|
||||
@@ -599,7 +599,7 @@ NULL
|
||||
event = dictGetKey(de);
|
||||
|
||||
graph = latencyCommandGenSparkeline(event,ts);
|
||||
addReplyVerbatim(c,graph,sdslen(graph),"txt");
|
||||
addReplyBulkCString(c,graph);
|
||||
sdsfree(graph);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) {
|
||||
/* LATENCY LATEST */
|
||||
@@ -608,7 +608,7 @@ NULL
|
||||
/* LATENCY DOCTOR */
|
||||
sds report = createLatencyReport();
|
||||
|
||||
addReplyVerbatim(c,report,sdslen(report),"txt");
|
||||
addReplyBulkCBuffer(c,report,sdslen(report));
|
||||
sdsfree(report);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) {
|
||||
/* LATENCY RESET */
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ struct latencyStats {
|
||||
};
|
||||
|
||||
void latencyMonitorInit(void);
|
||||
void latencyAddSample(const char *event, mstime_t latency);
|
||||
void latencyAddSample(char *event, mstime_t latency);
|
||||
int THPIsEnabled(void);
|
||||
|
||||
/* Latency monitoring macros. */
|
||||
|
||||
+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* not by 400 is not leap. */
|
||||
else return 1; /* If div by 100 and 400 is 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. */
|
||||
}
|
||||
|
||||
void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst) {
|
||||
|
||||
+2
-134
@@ -34,11 +34,8 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "lolwut.h"
|
||||
#include <math.h>
|
||||
|
||||
void lolwut5Command(client *c);
|
||||
void lolwut6Command(client *c);
|
||||
|
||||
/* The default target for LOLWUT if no matching version was found.
|
||||
* This is what unstable versions of Redis will display. */
|
||||
@@ -46,143 +43,14 @@ void lolwutUnstableCommand(client *c) {
|
||||
sds rendered = sdsnew("Redis ver. ");
|
||||
rendered = sdscat(rendered,REDIS_VERSION);
|
||||
rendered = sdscatlen(rendered,"\n",1);
|
||||
addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
|
||||
sdsfree(rendered);
|
||||
addReplyBulkSds(c,rendered);
|
||||
}
|
||||
|
||||
/* LOLWUT [VERSION <version>] [... version specific arguments ...] */
|
||||
void lolwutCommand(client *c) {
|
||||
char *v = REDIS_VERSION;
|
||||
char verstr[64];
|
||||
|
||||
if (c->argc >= 3 && !strcasecmp(c->argv[1]->ptr,"version")) {
|
||||
long ver;
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&ver,NULL) != C_OK) return;
|
||||
snprintf(verstr,sizeof(verstr),"%u.0.0",(unsigned int)ver);
|
||||
v = verstr;
|
||||
|
||||
/* Adjust argv/argc to filter the "VERSION ..." option, since the
|
||||
* specific LOLWUT version implementations don't know about it
|
||||
* and expect their arguments. */
|
||||
c->argv += 2;
|
||||
c->argc -= 2;
|
||||
}
|
||||
|
||||
if ((v[0] == '5' && v[1] == '.' && v[2] != '9') ||
|
||||
if ((v[0] == '5' && v[1] == '.') ||
|
||||
(v[0] == '4' && v[1] == '.' && v[2] == '9'))
|
||||
lolwut5Command(c);
|
||||
else if ((v[0] == '6' && v[1] == '.' && v[2] != '9') ||
|
||||
(v[0] == '5' && v[1] == '.' && v[2] == '9'))
|
||||
lolwut6Command(c);
|
||||
else
|
||||
lolwutUnstableCommand(c);
|
||||
|
||||
/* Fix back argc/argv in case of VERSION argument. */
|
||||
if (v == verstr) {
|
||||
c->argv -= 2;
|
||||
c->argc += 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================== LOLWUT Canvase ===============================
|
||||
* Many LOWUT versions will likely print some computer art to the screen.
|
||||
* This is the case with LOLWUT 5 and LOLWUT 6, so here there is a generic
|
||||
* canvas implementation that can be reused. */
|
||||
|
||||
/* Allocate and return a new canvas of the specified size. */
|
||||
lwCanvas *lwCreateCanvas(int width, int height, int bgcolor) {
|
||||
lwCanvas *canvas = zmalloc(sizeof(*canvas));
|
||||
canvas->width = width;
|
||||
canvas->height = height;
|
||||
canvas->pixels = zmalloc(width*height);
|
||||
memset(canvas->pixels,bgcolor,width*height);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/* Free the canvas created by lwCreateCanvas(). */
|
||||
void lwFreeCanvas(lwCanvas *canvas) {
|
||||
zfree(canvas->pixels);
|
||||
zfree(canvas);
|
||||
}
|
||||
|
||||
/* Set a pixel to the specified color. Color is 0 or 1, where zero means no
|
||||
* dot will be displyed, and 1 means dot will be displayed.
|
||||
* Coordinates are arranged so that left-top corner is 0,0. You can write
|
||||
* out of the size of the canvas without issues. */
|
||||
void lwDrawPixel(lwCanvas *canvas, int x, int y, int color) {
|
||||
if (x < 0 || x >= canvas->width ||
|
||||
y < 0 || y >= canvas->height) return;
|
||||
canvas->pixels[x+y*canvas->width] = color;
|
||||
}
|
||||
|
||||
/* Return the value of the specified pixel on the canvas. */
|
||||
int lwGetPixel(lwCanvas *canvas, int x, int y) {
|
||||
if (x < 0 || x >= canvas->width ||
|
||||
y < 0 || y >= canvas->height) return 0;
|
||||
return canvas->pixels[x+y*canvas->width];
|
||||
}
|
||||
|
||||
/* Draw a line from x1,y1 to x2,y2 using the Bresenham algorithm. */
|
||||
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color) {
|
||||
int dx = abs(x2-x1);
|
||||
int dy = abs(y2-y1);
|
||||
int sx = (x1 < x2) ? 1 : -1;
|
||||
int sy = (y1 < y2) ? 1 : -1;
|
||||
int err = dx-dy, e2;
|
||||
|
||||
while(1) {
|
||||
lwDrawPixel(canvas,x1,y1,color);
|
||||
if (x1 == x2 && y1 == y2) break;
|
||||
e2 = err*2;
|
||||
if (e2 > -dy) {
|
||||
err -= dy;
|
||||
x1 += sx;
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx;
|
||||
y1 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw a square centered at the specified x,y coordinates, with the specified
|
||||
* rotation angle and size. In order to write a rotated square, we use the
|
||||
* trivial fact that the parametric equation:
|
||||
*
|
||||
* x = sin(k)
|
||||
* y = cos(k)
|
||||
*
|
||||
* Describes a circle for values going from 0 to 2*PI. So basically if we start
|
||||
* at 45 degrees, that is k = PI/4, with the first point, and then we find
|
||||
* the other three points incrementing K by PI/2 (90 degrees), we'll have the
|
||||
* points of the square. In order to rotate the square, we just start with
|
||||
* k = PI/4 + rotation_angle, and we are done.
|
||||
*
|
||||
* Of course the vanilla equations above will describe the square inside a
|
||||
* circle of radius 1, so in order to draw larger squares we'll have to
|
||||
* multiply the obtained coordinates, and then translate them. However this
|
||||
* is much simpler than implementing the abstract concept of 2D shape and then
|
||||
* performing the rotation/translation transformation, so for LOLWUT it's
|
||||
* a good approach. */
|
||||
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color) {
|
||||
int px[4], py[4];
|
||||
|
||||
/* Adjust the desired size according to the fact that the square inscribed
|
||||
* into a circle of radius 1 has the side of length SQRT(2). This way
|
||||
* size becomes a simple multiplication factor we can use with our
|
||||
* coordinates to magnify them. */
|
||||
size /= 1.4142135623;
|
||||
size = round(size);
|
||||
|
||||
/* Compute the four points. */
|
||||
float k = M_PI/4 + angle;
|
||||
for (int j = 0; j < 4; j++) {
|
||||
px[j] = round(sin(k) * size + x);
|
||||
py[j] = round(cos(k) * size + y);
|
||||
k += M_PI/2;
|
||||
}
|
||||
|
||||
/* Draw the square. */
|
||||
for (int j = 0; j < 4; j++)
|
||||
lwDrawLine(canvas,px[j],py[j],px[(j+1)%4],py[(j+1)%4],color);
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018-2019, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* This structure represents our canvas. Drawing functions will take a pointer
|
||||
* to a canvas to write to it. Later the canvas can be rendered to a string
|
||||
* suitable to be printed on the screen, using unicode Braille characters. */
|
||||
|
||||
/* This represents a very simple generic canvas in order to draw stuff.
|
||||
* It's up to each LOLWUT versions to translate what they draw to the
|
||||
* screen, depending on the result to accomplish. */
|
||||
typedef struct lwCanvas {
|
||||
int width;
|
||||
int height;
|
||||
char *pixels;
|
||||
} lwCanvas;
|
||||
|
||||
/* Drawing functions implemented inside lolwut.c. */
|
||||
lwCanvas *lwCreateCanvas(int width, int height, int bgcolor);
|
||||
void lwFreeCanvas(lwCanvas *canvas);
|
||||
void lwDrawPixel(lwCanvas *canvas, int x, int y, int color);
|
||||
int lwGetPixel(lwCanvas *canvas, int x, int y);
|
||||
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color);
|
||||
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color);
|
||||
+112
-7
@@ -34,9 +34,17 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "lolwut.h"
|
||||
#include <math.h>
|
||||
|
||||
/* This structure represents our canvas. Drawing functions will take a pointer
|
||||
* to a canvas to write to it. Later the canvas can be rendered to a string
|
||||
* suitable to be printed on the screen, using unicode Braille characters. */
|
||||
typedef struct lwCanvas {
|
||||
int width;
|
||||
int height;
|
||||
char *pixels;
|
||||
} lwCanvas;
|
||||
|
||||
/* Translate a group of 8 pixels (2x4 vertical rectangle) to the corresponding
|
||||
* braille character. The byte should correspond to the pixels arranged as
|
||||
* follows, where 0 is the least significant bit, and 7 the most significant
|
||||
@@ -61,6 +69,104 @@ void lwTranslatePixelsGroup(int byte, char *output) {
|
||||
output[2] = 0x80 | (code & 0x3F); /* 10-xxxxxx */
|
||||
}
|
||||
|
||||
/* Allocate and return a new canvas of the specified size. */
|
||||
lwCanvas *lwCreateCanvas(int width, int height) {
|
||||
lwCanvas *canvas = zmalloc(sizeof(*canvas));
|
||||
canvas->width = width;
|
||||
canvas->height = height;
|
||||
canvas->pixels = zmalloc(width*height);
|
||||
memset(canvas->pixels,0,width*height);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/* Free the canvas created by lwCreateCanvas(). */
|
||||
void lwFreeCanvas(lwCanvas *canvas) {
|
||||
zfree(canvas->pixels);
|
||||
zfree(canvas);
|
||||
}
|
||||
|
||||
/* Set a pixel to the specified color. Color is 0 or 1, where zero means no
|
||||
* dot will be displyed, and 1 means dot will be displayed.
|
||||
* Coordinates are arranged so that left-top corner is 0,0. You can write
|
||||
* out of the size of the canvas without issues. */
|
||||
void lwDrawPixel(lwCanvas *canvas, int x, int y, int color) {
|
||||
if (x < 0 || x >= canvas->width ||
|
||||
y < 0 || y >= canvas->height) return;
|
||||
canvas->pixels[x+y*canvas->width] = color;
|
||||
}
|
||||
|
||||
/* Return the value of the specified pixel on the canvas. */
|
||||
int lwGetPixel(lwCanvas *canvas, int x, int y) {
|
||||
if (x < 0 || x >= canvas->width ||
|
||||
y < 0 || y >= canvas->height) return 0;
|
||||
return canvas->pixels[x+y*canvas->width];
|
||||
}
|
||||
|
||||
/* Draw a line from x1,y1 to x2,y2 using the Bresenham algorithm. */
|
||||
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color) {
|
||||
int dx = abs(x2-x1);
|
||||
int dy = abs(y2-y1);
|
||||
int sx = (x1 < x2) ? 1 : -1;
|
||||
int sy = (y1 < y2) ? 1 : -1;
|
||||
int err = dx-dy, e2;
|
||||
|
||||
while(1) {
|
||||
lwDrawPixel(canvas,x1,y1,color);
|
||||
if (x1 == x2 && y1 == y2) break;
|
||||
e2 = err*2;
|
||||
if (e2 > -dy) {
|
||||
err -= dy;
|
||||
x1 += sx;
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx;
|
||||
y1 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw a square centered at the specified x,y coordinates, with the specified
|
||||
* rotation angle and size. In order to write a rotated square, we use the
|
||||
* trivial fact that the parametric equation:
|
||||
*
|
||||
* x = sin(k)
|
||||
* y = cos(k)
|
||||
*
|
||||
* Describes a circle for values going from 0 to 2*PI. So basically if we start
|
||||
* at 45 degrees, that is k = PI/4, with the first point, and then we find
|
||||
* the other three points incrementing K by PI/2 (90 degrees), we'll have the
|
||||
* points of the square. In order to rotate the square, we just start with
|
||||
* k = PI/4 + rotation_angle, and we are done.
|
||||
*
|
||||
* Of course the vanilla equations above will describe the square inside a
|
||||
* circle of radius 1, so in order to draw larger squares we'll have to
|
||||
* multiply the obtained coordinates, and then translate them. However this
|
||||
* is much simpler than implementing the abstract concept of 2D shape and then
|
||||
* performing the rotation/translation transformation, so for LOLWUT it's
|
||||
* a good approach. */
|
||||
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle) {
|
||||
int px[4], py[4];
|
||||
|
||||
/* Adjust the desired size according to the fact that the square inscribed
|
||||
* into a circle of radius 1 has the side of length SQRT(2). This way
|
||||
* size becomes a simple multiplication factor we can use with our
|
||||
* coordinates to magnify them. */
|
||||
size /= 1.4142135623;
|
||||
size = round(size);
|
||||
|
||||
/* Compute the four points. */
|
||||
float k = M_PI/4 + angle;
|
||||
for (int j = 0; j < 4; j++) {
|
||||
px[j] = round(sin(k) * size + x);
|
||||
py[j] = round(cos(k) * size + y);
|
||||
k += M_PI/2;
|
||||
}
|
||||
|
||||
/* Draw the square. */
|
||||
for (int j = 0; j < 4; j++)
|
||||
lwDrawLine(canvas,px[j],py[j],px[(j+1)%4],py[(j+1)%4],1);
|
||||
}
|
||||
|
||||
/* Schotter, the output of LOLWUT of Redis 5, is a computer graphic art piece
|
||||
* generated by Georg Nees in the 60s. It explores the relationship between
|
||||
* caos and order.
|
||||
@@ -74,7 +180,7 @@ lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_
|
||||
int padding = canvas_width > 4 ? 2 : 0;
|
||||
float square_side = (float)(canvas_width-padding*2) / squares_per_row;
|
||||
int canvas_height = square_side * squares_per_col + padding*2;
|
||||
lwCanvas *canvas = lwCreateCanvas(canvas_width, canvas_height, 0);
|
||||
lwCanvas *canvas = lwCreateCanvas(canvas_width, canvas_height);
|
||||
|
||||
for (int y = 0; y < squares_per_col; y++) {
|
||||
for (int x = 0; x < squares_per_row; x++) {
|
||||
@@ -94,7 +200,7 @@ lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_
|
||||
sx += r2*square_side/3;
|
||||
sy += r3*square_side/3;
|
||||
}
|
||||
lwDrawSquare(canvas,sx,sy,square_side,angle,1);
|
||||
lwDrawSquare(canvas,sx,sy,square_side,angle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +212,7 @@ lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_
|
||||
* logical canvas. The actual returned string will require a terminal that is
|
||||
* width/2 large and height/4 tall in order to hold the whole image without
|
||||
* overflowing or scrolling, since each Barille character is 2x4. */
|
||||
static sds renderCanvas(lwCanvas *canvas) {
|
||||
sds lwRenderCanvas(lwCanvas *canvas) {
|
||||
sds text = sdsempty();
|
||||
for (int y = 0; y < canvas->height; y += 4) {
|
||||
for (int x = 0; x < canvas->width; x += 2) {
|
||||
@@ -166,12 +272,11 @@ void lolwut5Command(client *c) {
|
||||
|
||||
/* Generate some computer art and reply. */
|
||||
lwCanvas *canvas = lwDrawSchotter(cols,squares_per_row,squares_per_col);
|
||||
sds rendered = renderCanvas(canvas);
|
||||
sds rendered = lwRenderCanvas(canvas);
|
||||
rendered = sdscat(rendered,
|
||||
"\nGeorg Nees - schotter, plotter on paper, 1968. Redis ver. ");
|
||||
rendered = sdscat(rendered,REDIS_VERSION);
|
||||
rendered = sdscatlen(rendered,"\n",1);
|
||||
addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
|
||||
sdsfree(rendered);
|
||||
addReplyBulkSds(c,rendered);
|
||||
lwFreeCanvas(canvas);
|
||||
}
|
||||
|
||||
-201
@@ -1,201 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* This file implements the LOLWUT command. The command should do something
|
||||
* fun and interesting, and should be replaced by a new implementation at
|
||||
* each new version of Redis.
|
||||
*
|
||||
* Thanks to Michele Hiki Falcone for the original image that ispired
|
||||
* the image, part of his game, Plaguemon.
|
||||
*
|
||||
* Thanks to the Shhh computer art collective for the help in tuning the
|
||||
* output to have a better artistic effect.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "lolwut.h"
|
||||
|
||||
/* Render the canvas using the four gray levels of the standard color
|
||||
* terminal: they match very well to the grayscale display of the gameboy. */
|
||||
static sds renderCanvas(lwCanvas *canvas) {
|
||||
sds text = sdsempty();
|
||||
for (int y = 0; y < canvas->height; y++) {
|
||||
for (int x = 0; x < canvas->width; x++) {
|
||||
int color = lwGetPixel(canvas,x,y);
|
||||
char *ce; /* Color escape sequence. */
|
||||
|
||||
/* Note that we set both the foreground and background color.
|
||||
* This way we are able to get a more consistent result among
|
||||
* different terminals implementations. */
|
||||
switch(color) {
|
||||
case 0: ce = "0;30;40m"; break; /* Black */
|
||||
case 1: ce = "0;90;100m"; break; /* Gray 1 */
|
||||
case 2: ce = "0;37;47m"; break; /* Gray 2 */
|
||||
case 3: ce = "0;97;107m"; break; /* White */
|
||||
default: ce = "0;30;40m"; break; /* Just for safety. */
|
||||
}
|
||||
text = sdscatprintf(text,"\033[%s \033[0m",ce);
|
||||
}
|
||||
if (y != canvas->height-1) text = sdscatlen(text,"\n",1);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/* Draw a skyscraper on the canvas, according to the parameters in the
|
||||
* 'skyscraper' structure. Window colors are random and are always one
|
||||
* of the two grays. */
|
||||
struct skyscraper {
|
||||
int xoff; /* X offset. */
|
||||
int width; /* Pixels width. */
|
||||
int height; /* Pixels height. */
|
||||
int windows; /* Draw windows if true. */
|
||||
int color; /* Color of the skyscraper. */
|
||||
};
|
||||
|
||||
void generateSkyscraper(lwCanvas *canvas, struct skyscraper *si) {
|
||||
int starty = canvas->height-1;
|
||||
int endy = starty - si->height + 1;
|
||||
for (int y = starty; y >= endy; y--) {
|
||||
for (int x = si->xoff; x < si->xoff+si->width; x++) {
|
||||
/* The roof is four pixels less wide. */
|
||||
if (y == endy && (x <= si->xoff+1 || x >= si->xoff+si->width-2))
|
||||
continue;
|
||||
int color = si->color;
|
||||
/* Alter the color if this is a place where we want to
|
||||
* draw a window. We check that we are in the inner part of the
|
||||
* skyscraper, so that windows are far from the borders. */
|
||||
if (si->windows &&
|
||||
x > si->xoff+1 &&
|
||||
x < si->xoff+si->width-2 &&
|
||||
y > endy+1 &&
|
||||
y < starty-1)
|
||||
{
|
||||
/* Calculate the x,y position relative to the start of
|
||||
* the window area. */
|
||||
int relx = x - (si->xoff+1);
|
||||
int rely = y - (endy+1);
|
||||
|
||||
/* Note that we want the windows to be two pixels wide
|
||||
* but just one pixel tall, because terminal "pixels"
|
||||
* (characters) are not square. */
|
||||
if (relx/2 % 2 && rely % 2) {
|
||||
do {
|
||||
color = 1 + rand() % 2;
|
||||
} while (color == si->color);
|
||||
/* Except we want adjacent pixels creating the same
|
||||
* window to be the same color. */
|
||||
if (relx % 2) color = lwGetPixel(canvas,x-1,y);
|
||||
}
|
||||
}
|
||||
lwDrawPixel(canvas,x,y,color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate a skyline inspired by the parallax backgrounds of 8 bit games. */
|
||||
void generateSkyline(lwCanvas *canvas) {
|
||||
struct skyscraper si;
|
||||
|
||||
/* First draw the background skyscraper without windows, using the
|
||||
* two different grays. We use two passes to make sure that the lighter
|
||||
* ones are always in the background. */
|
||||
for (int color = 2; color >= 1; color--) {
|
||||
si.color = color;
|
||||
for (int offset = -10; offset < canvas->width;) {
|
||||
offset += rand() % 8;
|
||||
si.xoff = offset;
|
||||
si.width = 10 + rand()%9;
|
||||
if (color == 2)
|
||||
si.height = canvas->height/2 + rand()%canvas->height/2;
|
||||
else
|
||||
si.height = canvas->height/2 + rand()%canvas->height/3;
|
||||
si.windows = 0;
|
||||
generateSkyscraper(canvas, &si);
|
||||
if (color == 2)
|
||||
offset += si.width/2;
|
||||
else
|
||||
offset += si.width+1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now draw the foreground skyscraper with the windows. */
|
||||
si.color = 0;
|
||||
for (int offset = -10; offset < canvas->width;) {
|
||||
offset += rand() % 8;
|
||||
si.xoff = offset;
|
||||
si.width = 5 + rand()%14;
|
||||
if (si.width % 4) si.width += (si.width % 3);
|
||||
si.height = canvas->height/3 + rand()%canvas->height/2;
|
||||
si.windows = 1;
|
||||
generateSkyscraper(canvas, &si);
|
||||
offset += si.width+5;
|
||||
}
|
||||
}
|
||||
|
||||
/* The LOLWUT 6 command:
|
||||
*
|
||||
* LOLWUT [columns] [rows]
|
||||
*
|
||||
* By default the command uses 80 columns, 40 squares per row
|
||||
* per column.
|
||||
*/
|
||||
void lolwut6Command(client *c) {
|
||||
long cols = 80;
|
||||
long rows = 20;
|
||||
|
||||
/* Parse the optional arguments if any. */
|
||||
if (c->argc > 1 &&
|
||||
getLongFromObjectOrReply(c,c->argv[1],&cols,NULL) != C_OK)
|
||||
return;
|
||||
|
||||
if (c->argc > 2 &&
|
||||
getLongFromObjectOrReply(c,c->argv[2],&rows,NULL) != C_OK)
|
||||
return;
|
||||
|
||||
/* Limits. We want LOLWUT to be always reasonably fast and cheap to execute
|
||||
* so we have maximum number of columns, rows, and output resulution. */
|
||||
if (cols < 1) cols = 1;
|
||||
if (cols > 1000) cols = 1000;
|
||||
if (rows < 1) rows = 1;
|
||||
if (rows > 1000) rows = 1000;
|
||||
|
||||
/* Generate the city skyline and reply. */
|
||||
lwCanvas *canvas = lwCreateCanvas(cols,rows,3);
|
||||
generateSkyline(canvas);
|
||||
sds rendered = renderCanvas(canvas);
|
||||
rendered = sdscat(rendered,
|
||||
"\nDedicated to the 8 bit game developers of past and present.\n"
|
||||
"Original 8 bit image from Plaguemon by hikikomori. Redis ver. ");
|
||||
rendered = sdscat(rendered,REDIS_VERSION);
|
||||
rendered = sdscatlen(rendered,"\n",1);
|
||||
addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
|
||||
sdsfree(rendered);
|
||||
lwFreeCanvas(canvas);
|
||||
}
|
||||
+123
-2406
File diff suppressed because it is too large
Load Diff
+1
-12
@@ -13,7 +13,7 @@ endif
|
||||
|
||||
.SUFFIXES: .c .so .xo .o
|
||||
|
||||
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so
|
||||
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
@@ -46,17 +46,6 @@ hellotimer.so: hellotimer.xo
|
||||
hellodict.xo: ../redismodule.h
|
||||
|
||||
hellodict.so: hellodict.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
hellohook.xo: ../redismodule.h
|
||||
|
||||
hellohook.so: hellohook.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
helloacl.xo: ../redismodule.h
|
||||
|
||||
helloacl.so: helloacl.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
testmodule.xo: ../redismodule.h
|
||||
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
/* ACL API example - An example for performing custom synchronous and
|
||||
* asynchronous password authentication.
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright 2019 Amazon.com, Inc. or its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "../redismodule.h"
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// A simple global user
|
||||
static RedisModuleUser *global;
|
||||
static uint64_t global_auth_client_id = 0;
|
||||
|
||||
/* HELLOACL.REVOKE
|
||||
* Synchronously revoke access from a user. */
|
||||
int RevokeCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/* HELLOACL.RESET
|
||||
* Synchronously delete and re-create a module user. */
|
||||
int ResetCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
RedisModule_FreeModuleUser(global);
|
||||
global = RedisModule_CreateModuleUser("global");
|
||||
RedisModule_SetModuleUserACL(global, "allcommands");
|
||||
RedisModule_SetModuleUserACL(global, "allkeys");
|
||||
RedisModule_SetModuleUserACL(global, "on");
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
/* Callback handler for user changes, use this to notify a module of
|
||||
* changes to users authenticated by the module */
|
||||
void HelloACL_UserChanged(uint64_t client_id, void *privdata) {
|
||||
REDISMODULE_NOT_USED(privdata);
|
||||
REDISMODULE_NOT_USED(client_id);
|
||||
global_auth_client_id = 0;
|
||||
}
|
||||
|
||||
/* HELLOACL.AUTHGLOBAL
|
||||
* 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_client_id) {
|
||||
return RedisModule_ReplyWithError(ctx, "Global user currently used");
|
||||
}
|
||||
|
||||
RedisModule_AuthenticateClientWithUser(ctx, global, HelloACL_UserChanged, NULL, &global_auth_client_id);
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
#define TIMEOUT_TIME 1000
|
||||
|
||||
/* Reply callback for auth command HELLOACL.AUTHASYNC */
|
||||
int HelloACL_Reply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
size_t length;
|
||||
|
||||
RedisModuleString *user_string = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
const char *name = RedisModule_StringPtrLen(user_string, &length);
|
||||
|
||||
if (RedisModule_AuthenticateClientWithACLUser(ctx, name, length, NULL, NULL, NULL) ==
|
||||
REDISMODULE_ERR) {
|
||||
return RedisModule_ReplyWithError(ctx, "Invalid Username or password");
|
||||
}
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
/* Timeout callback for auth command HELLOACL.AUTHASYNC */
|
||||
int HelloACL_Timeout(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
|
||||
}
|
||||
|
||||
/* Private data frees data for HELLOACL.AUTHASYNC command. */
|
||||
void HelloACL_FreeData(RedisModuleCtx *ctx, void *privdata) {
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
RedisModule_FreeString(NULL, privdata);
|
||||
}
|
||||
|
||||
/* Background authentication can happen here. */
|
||||
void *HelloACL_ThreadMain(void *args) {
|
||||
void **targs = args;
|
||||
RedisModuleBlockedClient *bc = targs[0];
|
||||
RedisModuleString *user = targs[1];
|
||||
RedisModule_Free(targs);
|
||||
|
||||
RedisModule_UnblockClient(bc,user);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* HELLOACL.AUTHASYNC
|
||||
* Asynchronously assigns an ACL user to the current context. */
|
||||
int AuthAsyncCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
|
||||
pthread_t tid;
|
||||
RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx, HelloACL_Reply, HelloACL_Timeout, HelloACL_FreeData, TIMEOUT_TIME);
|
||||
|
||||
|
||||
void **targs = RedisModule_Alloc(sizeof(void*)*2);
|
||||
targs[0] = bc;
|
||||
targs[1] = RedisModule_CreateStringFromString(NULL, argv[1]);
|
||||
|
||||
if (pthread_create(&tid, NULL, HelloACL_ThreadMain, targs) != 0) {
|
||||
RedisModule_AbortBlock(bc);
|
||||
return RedisModule_ReplyWithError(ctx, "-ERR Can't start thread");
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* 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,"helloacl",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return 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)
|
||||
return 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)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
global = RedisModule_CreateModuleUser("global");
|
||||
RedisModule_SetModuleUserACL(global, "allcommands");
|
||||
RedisModule_SetModuleUserACL(global, "allkeys");
|
||||
RedisModule_SetModuleUserACL(global, "on");
|
||||
|
||||
global_auth_client_id = 0;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/* Server hooks API example
|
||||
*
|
||||
* -----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2019, 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.
|
||||
*/
|
||||
|
||||
#define REDISMODULE_EXPERIMENTAL_API
|
||||
#include "../redismodule.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Client state change callback. */
|
||||
void clientChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||
{
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
REDISMODULE_NOT_USED(e);
|
||||
|
||||
RedisModuleClientInfo *ci = data;
|
||||
printf("Client %s event for client #%llu %s:%d\n",
|
||||
(sub == REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED) ?
|
||||
"connection" : "disconnection",
|
||||
ci->id,ci->addr,ci->port);
|
||||
}
|
||||
|
||||
void flushdbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||
{
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
REDISMODULE_NOT_USED(e);
|
||||
|
||||
RedisModuleFlushInfo *fi = data;
|
||||
if (sub == REDISMODULE_SUBEVENT_FLUSHDB_START) {
|
||||
if (fi->dbnum != -1) {
|
||||
RedisModuleCallReply *reply;
|
||||
reply = RedisModule_Call(ctx,"DBSIZE","");
|
||||
long long numkeys = RedisModule_CallReplyInteger(reply);
|
||||
printf("FLUSHDB event of database %d started (%lld keys in DB)\n",
|
||||
fi->dbnum, numkeys);
|
||||
RedisModule_FreeCallReply(reply);
|
||||
} else {
|
||||
printf("FLUSHALL event started\n");
|
||||
}
|
||||
} else {
|
||||
if (fi->dbnum != -1) {
|
||||
printf("FLUSHDB event of database %d ended\n",fi->dbnum);
|
||||
} else {
|
||||
printf("FLUSHALL event ended\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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,"hellohook",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
RedisModule_SubscribeToServerEvent(ctx,
|
||||
RedisModuleEvent_ClientChange, clientChangeCallback);
|
||||
RedisModule_SubscribeToServerEvent(ctx,
|
||||
RedisModuleEvent_FlushDB, flushdbCallback);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -129,7 +129,6 @@ int HelloTypeInsert_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
|
||||
|
||||
/* Insert the new element. */
|
||||
HelloTypeInsert(hto,value);
|
||||
RedisModule_SignalKeyAsReady(ctx,argv[1]);
|
||||
|
||||
RedisModule_ReplyWithLongLong(ctx,hto->len);
|
||||
RedisModule_ReplicateVerbatim(ctx);
|
||||
@@ -191,77 +190,6 @@ int HelloTypeLen_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* ====================== Example of a blocking command ==================== */
|
||||
|
||||
/* Reply callback for blocking command HELLOTYPE.BRANGE, this will get
|
||||
* called when the key we blocked for is ready: we need to check if we
|
||||
* can really serve the client, and reply OK or ERR accordingly. */
|
||||
int HelloBlock_Reply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
|
||||
RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx,keyname,REDISMODULE_READ);
|
||||
int type = RedisModule_KeyType(key);
|
||||
if (type != REDISMODULE_KEYTYPE_MODULE ||
|
||||
RedisModule_ModuleTypeGetType(key) != HelloType)
|
||||
{
|
||||
RedisModule_CloseKey(key);
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
/* In case the key is able to serve our blocked client, let's directly
|
||||
* use our original command implementation to make this example simpler. */
|
||||
RedisModule_CloseKey(key);
|
||||
return HelloTypeRange_RedisCommand(ctx,argv,argc-1);
|
||||
}
|
||||
|
||||
/* Timeout callback for blocking command HELLOTYPE.BRANGE */
|
||||
int HelloBlock_Timeout(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
return RedisModule_ReplyWithSimpleString(ctx,"Request timedout");
|
||||
}
|
||||
|
||||
/* Private data freeing callback for HELLOTYPE.BRANGE command. */
|
||||
void HelloBlock_FreeData(RedisModuleCtx *ctx, void *privdata) {
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
RedisModule_Free(privdata);
|
||||
}
|
||||
|
||||
/* HELLOTYPE.BRANGE key first count timeout -- This is a blocking verison of
|
||||
* the RANGE operation, in order to show how to use the API
|
||||
* RedisModule_BlockClientOnKeys(). */
|
||||
int HelloTypeBRange_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 5) return RedisModule_WrongArity(ctx);
|
||||
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
|
||||
REDISMODULE_READ|REDISMODULE_WRITE);
|
||||
int type = RedisModule_KeyType(key);
|
||||
if (type != REDISMODULE_KEYTYPE_EMPTY &&
|
||||
RedisModule_ModuleTypeGetType(key) != HelloType)
|
||||
{
|
||||
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||
}
|
||||
|
||||
/* Parse the timeout before even trying to serve the client synchronously,
|
||||
* so that we always fail ASAP on syntax errors. */
|
||||
long long timeout;
|
||||
if (RedisModule_StringToLongLong(argv[4],&timeout) != REDISMODULE_OK) {
|
||||
return RedisModule_ReplyWithError(ctx,
|
||||
"ERR invalid timeout parameter");
|
||||
}
|
||||
|
||||
/* Can we serve the reply synchronously? */
|
||||
if (type != REDISMODULE_KEYTYPE_EMPTY) {
|
||||
return HelloTypeRange_RedisCommand(ctx,argv,argc-1);
|
||||
}
|
||||
|
||||
/* Otherwise let's block on the key. */
|
||||
void *privdata = RedisModule_Alloc(100);
|
||||
RedisModule_BlockClientOnKeys(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,timeout,argv+1,1,privdata);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* ========================== "hellotype" type methods ======================= */
|
||||
|
||||
@@ -354,9 +282,5 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
HelloTypeLen_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"hellotype.brange",
|
||||
HelloTypeBRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -109,9 +109,9 @@ int TestStringPrintf(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc < 3) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
RedisModuleString *s = RedisModule_CreateStringPrintf(ctx,
|
||||
"Got %d args. argv[1]: %s, argv[2]: %s",
|
||||
argc,
|
||||
RedisModuleString *s = RedisModule_CreateStringPrintf(ctx,
|
||||
"Got %d args. argv[1]: %s, argv[2]: %s",
|
||||
argc,
|
||||
RedisModule_StringPtrLen(argv[1], NULL),
|
||||
RedisModule_StringPtrLen(argv[2], NULL)
|
||||
);
|
||||
@@ -133,7 +133,7 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
|
||||
RedisModuleKey *k = RedisModule_OpenKey(ctx, RedisModule_CreateStringPrintf(ctx, "unlinked"), REDISMODULE_WRITE | REDISMODULE_READ);
|
||||
if (!k) return failTest(ctx, "Could not create key");
|
||||
|
||||
|
||||
if (REDISMODULE_ERR == RedisModule_StringSet(k, RedisModule_CreateStringPrintf(ctx, "Foobar"))) {
|
||||
return failTest(ctx, "Could not set string value");
|
||||
}
|
||||
@@ -152,7 +152,7 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return failTest(ctx, "Could not verify key to be unlinked");
|
||||
}
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
|
||||
|
||||
}
|
||||
|
||||
int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event,
|
||||
@@ -188,10 +188,6 @@ int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
RedisModule_Call(ctx, "LPUSH", "cc", "l", "y");
|
||||
RedisModule_Call(ctx, "LPUSH", "cc", "l", "y");
|
||||
|
||||
/* Miss some keys intentionally so we will get a "keymiss" notification. */
|
||||
RedisModule_Call(ctx, "GET", "c", "nosuchkey");
|
||||
RedisModule_Call(ctx, "SMEMBERS", "c", "nosuchkey");
|
||||
|
||||
size_t sz;
|
||||
const char *rep;
|
||||
RedisModuleCallReply *r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "foo");
|
||||
@@ -229,16 +225,6 @@ int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
FAIL("Wrong reply for l");
|
||||
}
|
||||
|
||||
r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "nosuchkey");
|
||||
if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) {
|
||||
FAIL("Wrong or no reply for nosuchkey");
|
||||
} else {
|
||||
rep = RedisModule_CallReplyStringPtr(r, &sz);
|
||||
if (sz != 1 || *rep != '2') {
|
||||
FAIL("Got reply '%.*s'. expected '2'", sz, rep);
|
||||
}
|
||||
}
|
||||
|
||||
RedisModule_Call(ctx, "FLUSHDB", "");
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
@@ -437,7 +423,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
if (RedisModule_CreateCommand(ctx,"test.ctxflags",
|
||||
TestCtxFlags,"readonly",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"test.unlink",
|
||||
TestUnlink,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
@@ -449,8 +435,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
RedisModule_SubscribeToKeyspaceEvents(ctx,
|
||||
REDISMODULE_NOTIFY_HASH |
|
||||
REDISMODULE_NOTIFY_SET |
|
||||
REDISMODULE_NOTIFY_STRING |
|
||||
REDISMODULE_NOTIFY_KEY_MISS,
|
||||
REDISMODULE_NOTIFY_STRING,
|
||||
NotifyCallback);
|
||||
if (RedisModule_CreateCommand(ctx,"test.notify",
|
||||
TestNotifications,"write deny-oom",1,1,1) == REDISMODULE_ERR)
|
||||
|
||||
+7
-23
@@ -106,13 +106,11 @@ 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) {
|
||||
propagate(server.multiCommand,c->db->id,&shared.multi,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
}
|
||||
robj *multistring = createStringObject("MULTI",5);
|
||||
|
||||
void execCommandPropagateExec(client *c) {
|
||||
propagate(server.execCommand,c->db->id,&shared.exec,1,
|
||||
propagate(server.multiCommand,c->db->id,&multistring,1,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(multistring);
|
||||
}
|
||||
|
||||
void execCommand(client *c) {
|
||||
@@ -136,7 +134,7 @@ void execCommand(client *c) {
|
||||
* in the second an EXECABORT error is returned. */
|
||||
if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) {
|
||||
addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr :
|
||||
shared.nullarray[c->resp]);
|
||||
shared.nullmultibulk);
|
||||
discardTransaction(c);
|
||||
goto handle_monitor;
|
||||
}
|
||||
@@ -151,7 +149,7 @@ void execCommand(client *c) {
|
||||
{
|
||||
addReplyError(c,
|
||||
"Transaction contains write commands but instance "
|
||||
"is now a read-only replica. EXEC aborted.");
|
||||
"is now a read-only slave. EXEC aborted.");
|
||||
discardTransaction(c);
|
||||
goto handle_monitor;
|
||||
}
|
||||
@@ -161,7 +159,7 @@ void execCommand(client *c) {
|
||||
orig_argv = c->argv;
|
||||
orig_argc = c->argc;
|
||||
orig_cmd = c->cmd;
|
||||
addReplyArrayLen(c,c->mstate.count);
|
||||
addReplyMultiBulkLen(c,c->mstate.count);
|
||||
for (j = 0; j < c->mstate.count; j++) {
|
||||
c->argc = c->mstate.commands[j].argc;
|
||||
c->argv = c->mstate.commands[j].argv;
|
||||
@@ -177,21 +175,7 @@ void execCommand(client *c) {
|
||||
must_propagate = 1;
|
||||
}
|
||||
|
||||
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. "
|
||||
"This command is no longer allowed for the "
|
||||
"following reason: %s",
|
||||
(acl_retval == ACL_DENIED_CMD) ?
|
||||
"no permission to execute the command or subcommand" :
|
||||
"no permission to touch the specified keys");
|
||||
} else {
|
||||
call(c,server.loading ? CMD_CALL_NONE : CMD_CALL_FULL);
|
||||
}
|
||||
call(c,server.loading ? CMD_CALL_NONE : CMD_CALL_FULL);
|
||||
|
||||
/* Commands may alter argc/argv, restore mstate. */
|
||||
c->mstate.commands[j].argc = c->argc;
|
||||
|
||||
+245
-1063
File diff suppressed because it is too large
Load Diff
+2
-4
@@ -55,7 +55,6 @@ int keyspaceEventsStringToFlags(char *classes) {
|
||||
case 'K': flags |= NOTIFY_KEYSPACE; break;
|
||||
case 'E': flags |= NOTIFY_KEYEVENT; break;
|
||||
case 't': flags |= NOTIFY_STREAM; break;
|
||||
case 'm': flags |= NOTIFY_KEY_MISS; break;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
@@ -85,7 +84,6 @@ sds keyspaceEventsFlagsToString(int flags) {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -102,12 +100,12 @@ void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
|
||||
int len = -1;
|
||||
char buf[24];
|
||||
|
||||
/* If any modules are interested in events, notify the module system now.
|
||||
/* If any modules are interested in events, notify the module system now.
|
||||
* This bypasses the notifications configuration, but the module engine
|
||||
* will only call event subscribers if the event type matches the types
|
||||
* they are interested in. */
|
||||
moduleNotifyKeyspaceEvent(type, event, key, dbid);
|
||||
|
||||
|
||||
/* If notifications for this class of events are off, return ASAP. */
|
||||
if (!(server.notify_keyspace_events & type)) return;
|
||||
|
||||
|
||||
+71
-39
@@ -178,7 +178,7 @@ robj *createStringObjectFromLongLongForValue(long long value) {
|
||||
* The 'humanfriendly' option is used for INCRBYFLOAT and HINCRBYFLOAT. */
|
||||
robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
int len = ld2string(buf,sizeof(buf),value,humanfriendly? LD_STR_HUMAN: LD_STR_AUTO);
|
||||
int len = ld2string(buf,sizeof(buf),value,humanfriendly);
|
||||
return createStringObject(buf,len);
|
||||
}
|
||||
|
||||
@@ -606,13 +606,21 @@ 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)) {
|
||||
if (!string2d(o->ptr, sdslen(o->ptr), &value))
|
||||
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))
|
||||
return C_ERR;
|
||||
} else if (o->encoding == OBJ_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
@@ -640,13 +648,21 @@ 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)) {
|
||||
if (!string2ld(o->ptr, sdslen(o->ptr), &value))
|
||||
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))
|
||||
return C_ERR;
|
||||
} else if (o->encoding == OBJ_ENCODING_INT) {
|
||||
value = (long)o->ptr;
|
||||
@@ -974,29 +990,38 @@ 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;
|
||||
size_t mem_normal = 0, mem_slaves = 0;
|
||||
|
||||
listRewind(server.clients,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
size_t mem_curr = 0;
|
||||
client *c = listNodeValue(ln);
|
||||
int type = getClientType(c);
|
||||
mem_curr += getClientOutputBufferMemoryUsage(c);
|
||||
mem_curr += sdsAllocSize(c->querybuf);
|
||||
mem_curr += sizeof(client);
|
||||
if (type == CLIENT_TYPE_SLAVE)
|
||||
mem_slaves += mem_curr;
|
||||
else
|
||||
mem_normal += mem_curr;
|
||||
if (c->flags & CLIENT_SLAVE && !(c->flags & CLIENT_MONITOR))
|
||||
continue;
|
||||
mem += getClientOutputBufferMemoryUsage(c);
|
||||
mem += sdsAllocSize(c->querybuf);
|
||||
mem += sizeof(client);
|
||||
}
|
||||
mh->clients_slaves = mem_slaves;
|
||||
mh->clients_normal = mem_normal;
|
||||
mem = mem_slaves + mem_normal;
|
||||
}
|
||||
mh->clients_normal = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
mem = 0;
|
||||
@@ -1102,13 +1127,13 @@ sds getMemoryDoctorReport(void) {
|
||||
num_reports++;
|
||||
}
|
||||
|
||||
/* Allocator rss is higher than 1.1 and 10MB ? */
|
||||
/* Allocator fss 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 rss is higher than 1.1 and 10MB ? */
|
||||
/* Non-Allocator fss 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++;
|
||||
@@ -1184,20 +1209,19 @@ sds getMemoryDoctorReport(void) {
|
||||
* The lru_idle and lru_clock args are only relevant if policy
|
||||
* 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, int lru_multiplier) {
|
||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
long long lru_clock) {
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
if (lfu_freq >= 0) {
|
||||
serverAssert(lfu_freq <= 255);
|
||||
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
|
||||
return 1;
|
||||
}
|
||||
} else if (lru_idle >= 0) {
|
||||
/* Provided LRU idle time is in seconds. Scale
|
||||
* 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*lru_multiplier/LRU_CLOCK_RESOLUTION;
|
||||
lru_idle = lru_idle*1000/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
|
||||
@@ -1207,9 +1231,7 @@ int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
if (lru_abs < 0)
|
||||
lru_abs = (lru_clock+(LRU_CLOCK_MAX/2)) % LRU_CLOCK_MAX;
|
||||
val->lru = lru_abs;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ======================= The OBJECT and MEMORY commands =================== */
|
||||
@@ -1245,15 +1267,15 @@ NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
addReplyLongLong(c,o->refcount);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
addReplyBulkCString(c,strEncoding(o->encoding));
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
addReplyError(c,"An LFU maxmemory policy is selected, idle time not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
@@ -1261,7 +1283,7 @@ NULL
|
||||
}
|
||||
addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"freq") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
|
||||
== NULL) return;
|
||||
if (!(server.maxmemory_policy & MAXMEMORY_FLAG_LFU)) {
|
||||
addReplyError(c,"An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
@@ -1313,7 +1335,7 @@ NULL
|
||||
}
|
||||
}
|
||||
if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
|
||||
addReplyNull(c);
|
||||
addReply(c, shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
size_t usage = objectComputeSize(dictGetVal(de),samples);
|
||||
@@ -1323,7 +1345,7 @@ NULL
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"stats") && c->argc == 2) {
|
||||
struct redisMemOverhead *mh = getMemoryOverheadData();
|
||||
|
||||
addReplyMapLen(c,25+mh->num_dbs);
|
||||
addReplyMultiBulkLen(c,(25+mh->num_dbs)*2);
|
||||
|
||||
addReplyBulkCString(c,"peak.allocated");
|
||||
addReplyLongLong(c,mh->peak_allocated);
|
||||
@@ -1353,7 +1375,7 @@ NULL
|
||||
char dbname[32];
|
||||
snprintf(dbname,sizeof(dbname),"db.%zd",mh->db[j].dbid);
|
||||
addReplyBulkCString(c,dbname);
|
||||
addReplyMapLen(c,2);
|
||||
addReplyMultiBulkLen(c,4);
|
||||
|
||||
addReplyBulkCString(c,"overhead.hashtable.main");
|
||||
addReplyLongLong(c,mh->db[j].overhead_ht_main);
|
||||
@@ -1418,20 +1440,30 @@ NULL
|
||||
#if defined(USE_JEMALLOC)
|
||||
sds info = sdsempty();
|
||||
je_malloc_stats_print(inputCatSds, &info, NULL);
|
||||
addReplyVerbatim(c,info,sdslen(info),"txt");
|
||||
sdsfree(info);
|
||||
addReplyBulkSds(c, info);
|
||||
#else
|
||||
addReplyBulkCString(c,"Stats not supported for the current allocator");
|
||||
#endif
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) {
|
||||
sds report = getMemoryDoctorReport();
|
||||
addReplyVerbatim(c,report,sdslen(report),"txt");
|
||||
sdsfree(report);
|
||||
addReplyBulkSds(c,report);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"purge") && c->argc == 2) {
|
||||
if (jemalloc_purge() == 0)
|
||||
addReply(c, shared.ok);
|
||||
else
|
||||
addReplyError(c, "Error purging dirty pages");
|
||||
#if defined(USE_JEMALLOC)
|
||||
char tmp[32];
|
||||
unsigned narenas = 0;
|
||||
size_t sz = sizeof(unsigned);
|
||||
if (!je_mallctl("arenas.narenas", &narenas, &sz, NULL, 0)) {
|
||||
sprintf(tmp, "arena.%d.purge", narenas);
|
||||
if (!je_mallctl(tmp, NULL, 0, NULL, 0)) {
|
||||
addReply(c, shared.ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
addReplyError(c, "Error purging dirty pages");
|
||||
#else
|
||||
addReply(c, shared.ok);
|
||||
/* Nothing to do for other allocators. */
|
||||
#endif
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try MEMORY HELP", (char*)c->argv[1]->ptr);
|
||||
}
|
||||
|
||||
+52
-105
@@ -29,97 +29,6 @@
|
||||
|
||||
#include "server.h"
|
||||
|
||||
int clientSubscriptionsCount(client *c);
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Pubsub client replies API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* 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]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.messagebulk);
|
||||
addReplyBulk(c,channel);
|
||||
if (msg) addReplyBulk(c,msg);
|
||||
}
|
||||
|
||||
/* Send a pubsub message of type "pmessage" to the client. The difference
|
||||
* with the "message" type delivered by addReplyPubsubMessage() is that
|
||||
* this message format also includes the pattern that matched the message. */
|
||||
void addReplyPubsubPatMessage(client *c, robj *pat, robj *channel, robj *msg) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[4]);
|
||||
else
|
||||
addReplyPushLen(c,4);
|
||||
addReply(c,shared.pmessagebulk);
|
||||
addReplyBulk(c,pat);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,msg);
|
||||
}
|
||||
|
||||
/* Send the pubsub subscription notification to the client. */
|
||||
void addReplyPubsubSubscribed(client *c, robj *channel) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.subscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
/* Send the pubsub unsubscription notification to the client.
|
||||
* Channel can be NULL: this is useful when the client sends a mass
|
||||
* unsubscribe command but there are no channels to unsubscribe from: we
|
||||
* still send a notification. */
|
||||
void addReplyPubsubUnsubscribed(client *c, robj *channel) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.unsubscribebulk);
|
||||
if (channel)
|
||||
addReplyBulk(c,channel);
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
/* Send the pubsub pattern subscription notification to the client. */
|
||||
void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.psubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
/* Send the pubsub pattern unsubscription notification to the client.
|
||||
* Pattern can be NULL: this is useful when the client sends a mass
|
||||
* punsubscribe command but there are no pattern to unsubscribe from: we
|
||||
* still send a notification. */
|
||||
void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
|
||||
if (c->resp == 2)
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
else
|
||||
addReplyPushLen(c,3);
|
||||
addReply(c,shared.punsubscribebulk);
|
||||
if (pattern)
|
||||
addReplyBulk(c,pattern);
|
||||
else
|
||||
addReplyNull(c);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Pubsub low level API
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -167,7 +76,10 @@ int pubsubSubscribeChannel(client *c, robj *channel) {
|
||||
listAddNodeTail(clients,c);
|
||||
}
|
||||
/* Notify the client */
|
||||
addReplyPubsubSubscribed(c,channel);
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.subscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -199,7 +111,14 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
|
||||
}
|
||||
}
|
||||
/* Notify the client */
|
||||
if (notify) addReplyPubsubUnsubscribed(c,channel);
|
||||
if (notify) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.unsubscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
|
||||
}
|
||||
decrRefCount(channel); /* it is finally safe to release it */
|
||||
return retval;
|
||||
}
|
||||
@@ -219,7 +138,10 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
listAddNodeTail(server.pubsub_patterns,pat);
|
||||
}
|
||||
/* Notify the client */
|
||||
addReplyPubsubPatSubscribed(c,pattern);
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.psubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -240,7 +162,13 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
listDelNode(server.pubsub_patterns,ln);
|
||||
}
|
||||
/* Notify the client */
|
||||
if (notify) addReplyPubsubPatUnsubscribed(c,pattern);
|
||||
if (notify) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.punsubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
}
|
||||
decrRefCount(pattern);
|
||||
return retval;
|
||||
}
|
||||
@@ -258,7 +186,13 @@ int pubsubUnsubscribeAllChannels(client *c, int notify) {
|
||||
count += pubsubUnsubscribeChannel(c,channel,notify);
|
||||
}
|
||||
/* We were subscribed to nothing? Still reply to the client. */
|
||||
if (notify && count == 0) addReplyPubsubUnsubscribed(c,NULL);
|
||||
if (notify && count == 0) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.unsubscribebulk);
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
return count;
|
||||
}
|
||||
@@ -276,7 +210,14 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) {
|
||||
|
||||
count += pubsubUnsubscribePattern(c,pattern,notify);
|
||||
}
|
||||
if (notify && count == 0) addReplyPubsubPatUnsubscribed(c,NULL);
|
||||
if (notify && count == 0) {
|
||||
/* We were subscribed to nothing? Still reply to the client. */
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.punsubscribebulk);
|
||||
addReply(c,shared.nullbulk);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -297,7 +238,11 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
listRewind(list,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = ln->value;
|
||||
addReplyPubsubMessage(c,channel,message);
|
||||
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.messagebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyBulk(c,message);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
@@ -311,10 +256,12 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
if (stringmatchlen((char*)pat->pattern->ptr,
|
||||
sdslen(pat->pattern->ptr),
|
||||
(char*)channel->ptr,
|
||||
sdslen(channel->ptr),0))
|
||||
{
|
||||
addReplyPubsubPatMessage(pat->client,
|
||||
pat->pattern,channel,message);
|
||||
sdslen(channel->ptr),0)) {
|
||||
addReply(pat->client,shared.mbulkhdr[4]);
|
||||
addReply(pat->client,shared.pmessagebulk);
|
||||
addReplyBulk(pat->client,pat->pattern);
|
||||
addReplyBulk(pat->client,channel);
|
||||
addReplyBulk(pat->client,message);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
@@ -396,7 +343,7 @@ NULL
|
||||
long mblen = 0;
|
||||
void *replylen;
|
||||
|
||||
replylen = addReplyDeferredLen(c);
|
||||
replylen = addDeferredMultiBulkLength(c);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *cobj = dictGetKey(de);
|
||||
sds channel = cobj->ptr;
|
||||
@@ -409,12 +356,12 @@ NULL
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredArrayLen(c,replylen,mblen);
|
||||
setDeferredMultiBulkLength(c,replylen,mblen);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) {
|
||||
/* PUBSUB NUMSUB [Channel_1 ... Channel_N] */
|
||||
int j;
|
||||
|
||||
addReplyArrayLen(c,(c->argc-2)*2);
|
||||
addReplyMultiBulkLen(c,(c->argc-2)*2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
list *l = dictFetchValue(server.pubsub_channels,c->argv[j]);
|
||||
|
||||
|
||||
+2
-148
@@ -70,12 +70,6 @@ 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 { \
|
||||
@@ -106,11 +100,10 @@ quicklist *quicklistCreate(void) {
|
||||
quicklist->count = 0;
|
||||
quicklist->compress = 0;
|
||||
quicklist->fill = -2;
|
||||
quicklist->bookmark_count = 0;
|
||||
return quicklist;
|
||||
}
|
||||
|
||||
#define COMPRESS_MAX (1 << QL_COMP_BITS)
|
||||
#define COMPRESS_MAX (1 << 16)
|
||||
void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
|
||||
if (compress > COMPRESS_MAX) {
|
||||
compress = COMPRESS_MAX;
|
||||
@@ -120,7 +113,7 @@ void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
|
||||
quicklist->compress = compress;
|
||||
}
|
||||
|
||||
#define FILL_MAX (1 << (QL_FILL_BITS-1))
|
||||
#define FILL_MAX (1 << 15)
|
||||
void quicklistSetFill(quicklist *quicklist, int fill) {
|
||||
if (fill > FILL_MAX) {
|
||||
fill = FILL_MAX;
|
||||
@@ -176,7 +169,6 @@ void quicklistRelease(quicklist *quicklist) {
|
||||
quicklist->len--;
|
||||
current = next;
|
||||
}
|
||||
quicklistBookmarksClear(quicklist);
|
||||
zfree(quicklist);
|
||||
}
|
||||
|
||||
@@ -586,15 +578,6 @@ 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)
|
||||
@@ -1427,87 +1410,6 @@ 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>
|
||||
@@ -2739,54 +2641,6 @@ 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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user